Merge branch 'jc/merge-blobs'
[git] / contrib / p4import / git-p4import.py
1 #!/usr/bin/env python
2 #
3 # This tool is copyright (c) 2006, Sean Estabrooks.
4 # It is released under the Gnu Public License, version 2.
5 #
6 # Import Perforce branches into Git repositories.
7 # Checking out the files is done by calling the standard p4
8 # client which you must have properly configured yourself
9 #
10
11 import marshal
12 import os
13 import sys
14 import time
15 import getopt
16
17 if sys.hexversion < 0x02020000:
18    # The behavior of the marshal module changed significantly in 2.2
19    sys.stderr.write("git-p4import.py: requires Python 2.2 or later.\n")
20    sys.exit(1)
21
22 from signal import signal, \
23    SIGPIPE, SIGINT, SIG_DFL, \
24    default_int_handler
25
26 signal(SIGPIPE, SIG_DFL)
27 s = signal(SIGINT, SIG_DFL)
28 if s != default_int_handler:
29    signal(SIGINT, s)
30
31 def die(msg, *args):
32     for a in args:
33         msg = "%s %s" % (msg, a)
34     print "git-p4import fatal error:", msg
35     sys.exit(1)
36
37 def usage():
38     print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
39     sys.exit(1)
40
41 verbosity = 1
42 logfile = "/dev/null"
43 ignore_warnings = False
44 stitch = 0
45 tagall = True
46
47 def report(level, msg, *args):
48     global verbosity
49     global logfile
50     for a in args:
51         msg = "%s %s" % (msg, a)
52     fd = open(logfile, "a")
53     fd.writelines(msg)
54     fd.close()
55     if level <= verbosity:
56         print msg
57
58 class p4_command:
59     def __init__(self, _repopath):
60         try:
61             global logfile
62             self.userlist = {}
63             if _repopath[-1] == '/':
64                 self.repopath = _repopath[:-1]
65             else:
66                 self.repopath = _repopath
67             if self.repopath[-4:] != "/...":
68                 self.repopath= "%s/..." % self.repopath
69             f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
70             a = f.readlines()
71             if f.close():
72                 raise
73         except:
74                 die("Could not find the \"p4\" command")
75
76     def p4(self, cmd, *args):
77         global logfile
78         cmd = "%s %s" % (cmd, ' '.join(args))
79         report(2, "P4:", cmd)
80         f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
81         list = []
82         while 1:
83            try:
84                 list.append(marshal.load(f))
85            except EOFError:
86                 break
87         self.ret = f.close()
88         return list
89
90     def sync(self, id, force=False, trick=False, test=False):
91         if force:
92             ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
93         elif trick:
94             ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
95         elif test:
96             ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
97         else:
98             ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
99         if ret['code'] == "error":
100              data = ret['data'].upper()
101              if data.find('VIEW') > 0:
102                  die("Perforce reports %s is not in client view"% self.repopath)
103              elif data.find('UP-TO-DATE') < 0:
104                  die("Could not sync files from perforce", self.repopath)
105
106     def changes(self, since=0):
107         try:
108             list = []
109             for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
110                 list.append(rec['change'])
111             list.reverse()
112             return list
113         except:
114             return []
115
116     def authors(self, filename):
117         f=open(filename)
118         for l in f.readlines():
119             self.userlist[l[:l.find('=')].rstrip()] = \
120                     (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
121         f.close()
122         for f,e in self.userlist.items():
123                 report(2, f, ":", e[0], "  <", e[1], ">")
124
125     def _get_user(self, id):
126         if not self.userlist.has_key(id):
127             try:
128                 user = self.p4("users", id)[0]
129                 self.userlist[id] = (user['FullName'], user['Email'])
130             except:
131                 self.userlist[id] = (id, "")
132         return self.userlist[id]
133
134     def _format_date(self, ticks):
135         symbol='+'
136         name = time.tzname[0]
137         offset = time.timezone
138         if ticks[8]:
139             name = time.tzname[1]
140             offset = time.altzone
141         if offset < 0:
142             offset *= -1
143             symbol = '-'
144         localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
145         tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
146         return "%s %s" % (tickso, localo)
147
148     def where(self):
149         try:
150             return self.p4("where %s" % self.repopath)[-1]['path']
151         except:
152             return ""
153
154     def describe(self, num):
155         desc = self.p4("describe -s", num)[0]
156         self.msg = desc['desc']
157         self.author, self.email = self._get_user(desc['user'])
158         self.date = self._format_date(time.localtime(long(desc['time'])))
159         return self
160
161 class git_command:
162     def __init__(self):
163         try:
164             self.version = self.git("--version")[0][12:].rstrip()
165         except:
166             die("Could not find the \"git\" command")
167         try:
168             self.gitdir = self.get_single("rev-parse --git-dir")
169             report(2, "gdir:", self.gitdir)
170         except:
171             die("Not a git repository... did you forget to \"git init\" ?")
172         try:
173             self.cdup = self.get_single("rev-parse --show-cdup")
174             if self.cdup != "":
175                 os.chdir(self.cdup)
176             self.topdir = os.getcwd()
177             report(2, "topdir:", self.topdir)
178         except:
179             die("Could not find top git directory")
180
181     def git(self, cmd):
182         global logfile
183         report(2, "GIT:", cmd)
184         f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
185         r=f.readlines()
186         self.ret = f.close()
187         return r
188
189     def get_single(self, cmd):
190         return self.git(cmd)[0].rstrip()
191
192     def current_branch(self):
193         try:
194             testit = self.git("rev-parse --verify HEAD")[0]
195             return self.git("symbolic-ref HEAD")[0][11:].rstrip()
196         except:
197             return None
198
199     def get_config(self, variable):
200         try:
201             return self.git("config --get %s" % variable)[0].rstrip()
202         except:
203             return None
204
205     def set_config(self, variable, value):
206         try:
207             self.git("config %s %s"%(variable, value) )
208         except:
209             die("Could not set %s to " % variable, value)
210
211     def make_tag(self, name, head):
212         self.git("tag -f %s %s"%(name,head))
213
214     def top_change(self, branch):
215         try:
216             a=self.get_single("name-rev --tags refs/heads/%s" % branch)
217             loc = a.find(' tags/') + 6
218             if a[loc:loc+3] != "p4/":
219                 raise
220             return int(a[loc+3:][:-2])
221         except:
222             return 0
223
224     def update_index(self):
225         self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
226
227     def checkout(self, branch):
228         self.git("checkout %s" % branch)
229
230     def repoint_head(self, branch):
231         self.git("symbolic-ref HEAD refs/heads/%s" % branch)
232
233     def remove_files(self):
234         self.git("ls-files | xargs rm")
235
236     def clean_directories(self):
237         self.git("clean -d")
238
239     def fresh_branch(self, branch):
240         report(1, "Creating new branch", branch)
241         self.git("ls-files | xargs rm")
242         os.remove(".git/index")
243         self.repoint_head(branch)
244         self.git("clean -d")
245
246     def basedir(self):
247         return self.topdir
248
249     def commit(self, author, email, date, msg, id):
250         self.update_index()
251         fd=open(".msg", "w")
252         fd.writelines(msg)
253         fd.close()
254         try:
255                 current = self.get_single("rev-parse --verify HEAD")
256                 head = "-p HEAD"
257         except:
258                 current = ""
259                 head = ""
260         tree = self.get_single("write-tree")
261         for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
262             os.environ['GIT_AUTHOR_%s'%r] = l
263             os.environ['GIT_COMMITTER_%s'%r] = l
264         commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
265         os.remove(".msg")
266         self.make_tag("p4/%s"%id, commit)
267         self.git("update-ref HEAD %s %s" % (commit, current) )
268
269 try:
270     opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
271             ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
272 except getopt.GetoptError:
273     usage()
274
275 for o, a in opts:
276     if o == "-q":
277         verbosity = 0
278     if o == "-v":
279         verbosity += 1
280     if o in ("--log"):
281         logfile = a
282     if o in ("--notags"):
283         tagall = False
284     if o in ("-h", "--help"):
285         usage()
286     if o in ("--ignore"):
287         ignore_warnings = True
288
289 git = git_command()
290 branch=git.current_branch()
291
292 for o, a in opts:
293     if o in ("-t", "--timezone"):
294         git.set_config("perforce.timezone", a)
295     if o in ("--stitch"):
296         git.set_config("perforce.%s.path" % branch, a)
297         stitch = 1
298
299 if len(args) == 2:
300     branch = args[1]
301     git.checkout(branch)
302     if branch == git.current_branch():
303         die("Branch %s already exists!" % branch)
304     report(1, "Setting perforce to ", args[0])
305     git.set_config("perforce.%s.path" % branch, args[0])
306 elif len(args) != 0:
307     die("You must specify the perforce //depot/path and git branch")
308
309 p4path = git.get_config("perforce.%s.path" % branch)
310 if p4path == None:
311     die("Do not know Perforce //depot/path for git branch", branch)
312
313 p4 = p4_command(p4path)
314
315 for o, a in opts:
316     if o in ("-a", "--authors"):
317         p4.authors(a)
318
319 localdir = git.basedir()
320 if p4.where()[:len(localdir)] != localdir:
321     report(1, "**WARNING** Appears p4 client is misconfigured")
322     report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
323     if ignore_warnings != True:
324         die("Reconfigure or use \"--ignore\" on command line")
325
326 if stitch == 0:
327     top = git.top_change(branch)
328 else:
329     top = 0
330 changes = p4.changes(top)
331 count = len(changes)
332 if count == 0:
333     report(1, "Already up to date...")
334     sys.exit(0)
335
336 ptz = git.get_config("perforce.timezone")
337 if ptz:
338     report(1, "Setting timezone to", ptz)
339     os.environ['TZ'] = ptz
340     time.tzset()
341
342 if stitch == 1:
343     git.remove_files()
344     git.clean_directories()
345     p4.sync(changes[0], force=True)
346 elif top == 0 and branch != git.current_branch():
347     p4.sync(changes[0], test=True)
348     report(1, "Creating new initial commit");
349     git.fresh_branch(branch)
350     p4.sync(changes[0], force=True)
351 else:
352     p4.sync(changes[0], trick=True)
353
354 report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
355 for id in changes:
356     report(1, "Importing changeset", id)
357     change = p4.describe(id)
358     p4.sync(id)
359     if tagall :
360             git.commit(change.author, change.email, change.date, change.msg, id)
361     else:
362             git.commit(change.author, change.email, change.date, change.msg, "import")
363     if stitch == 1:
364         git.clean_directories()
365         stitch = 0