summaryrefslogtreecommitdiff
path: root/git-p4import.py
diff options
context:
space:
mode:
Diffstat (limited to 'git-p4import.py')
-rw-r--r--git-p4import.py357
1 files changed, 357 insertions, 0 deletions
diff --git a/git-p4import.py b/git-p4import.py
new file mode 100644
index 0000000..74172ab
--- /dev/null
+++ b/git-p4import.py
@@ -0,0 +1,357 @@
+#!/usr/bin/python
+#
+# This tool is copyright (c) 2006, Sean Estabrooks.
+# It is released under the Gnu Public License, version 2.
+#
+# Import Perforce branches into Git repositories.
+# Checking out the files is done by calling the standard p4
+# client which you must have properly configured yourself
+#
+
+import marshal
+import os
+import sys
+import time
+import getopt
+
+from signal import signal, \
+ SIGPIPE, SIGINT, SIG_DFL, \
+ default_int_handler
+
+signal(SIGPIPE, SIG_DFL)
+s = signal(SIGINT, SIG_DFL)
+if s != default_int_handler:
+ signal(SIGINT, s)
+
+
+def die(msg, *args):
+ for a in args:
+ msg = "%s %s" % (msg, a)
+ print "git-p4import fatal error:", msg
+ sys.exit(1)
+
+def usage():
+ print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
+ sys.exit(1)
+
+verbosity = 1
+logfile = "/dev/null"
+ignore_warnings = False
+stitch = 0
+
+def report(level, msg, *args):
+ global verbosity
+ global logfile
+ for a in args:
+ msg = "%s %s" % (msg, a)
+ fd = open(logfile, "a")
+ fd.writelines(msg)
+ fd.close()
+ if level <= verbosity:
+ print msg
+
+class p4_command:
+ def __init__(self, _repopath):
+ try:
+ global logfile
+ self.userlist = {}
+ if _repopath[-1] == '/':
+ self.repopath = _repopath[:-1]
+ else:
+ self.repopath = _repopath
+ if self.repopath[-4:] != "/...":
+ self.repopath= "%s/..." % self.repopath
+ f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
+ a = f.readlines()
+ if f.close():
+ raise
+ except:
+ die("Could not find the \"p4\" command")
+
+ def p4(self, cmd, *args):
+ global logfile
+ cmd = "%s %s" % (cmd, ' '.join(args))
+ report(2, "P4:", cmd)
+ f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
+ list = []
+ while 1:
+ try:
+ list.append(marshal.load(f))
+ except EOFError:
+ break
+ self.ret = f.close()
+ return list
+
+ def sync(self, id, force=False, trick=False, test=False):
+ if force:
+ ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
+ elif trick:
+ ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
+ elif test:
+ ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
+ else:
+ ret = self.p4("sync %s@%s"%(self.repopath, id))[0]
+ if ret['code'] == "error":
+ data = ret['data'].upper()
+ if data.find('VIEW') > 0:
+ die("Perforce reports %s is not in client view"% self.repopath)
+ elif data.find('UP-TO-DATE') < 0:
+ die("Could not sync files from perforce", self.repopath)
+
+ def changes(self, since=0):
+ try:
+ list = []
+ for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
+ list.append(rec['change'])
+ list.reverse()
+ return list
+ except:
+ return []
+
+ def authors(self, filename):
+ f=open(filename)
+ for l in f.readlines():
+ self.userlist[l[:l.find('=')].rstrip()] = \
+ (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
+ f.close()
+ for f,e in self.userlist.items():
+ report(2, f, ":", e[0], " <", e[1], ">")
+
+ def _get_user(self, id):
+ if not self.userlist.has_key(id):
+ try:
+ user = self.p4("users", id)[0]
+ self.userlist[id] = (user['FullName'], user['Email'])
+ except:
+ self.userlist[id] = (id, "")
+ return self.userlist[id]
+
+ def _format_date(self, ticks):
+ symbol='+'
+ name = time.tzname[0]
+ offset = time.timezone
+ if ticks[8]:
+ name = time.tzname[1]
+ offset = time.altzone
+ if offset < 0:
+ offset *= -1
+ symbol = '-'
+ localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
+ tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
+ return "%s %s" % (tickso, localo)
+
+ def where(self):
+ try:
+ return self.p4("where %s" % self.repopath)[-1]['path']
+ except:
+ return ""
+
+ def describe(self, num):
+ desc = self.p4("describe -s", num)[0]
+ self.msg = desc['desc']
+ self.author, self.email = self._get_user(desc['user'])
+ self.date = self._format_date(time.localtime(long(desc['time'])))
+ return self
+
+class git_command:
+ def __init__(self):
+ try:
+ self.version = self.git("--version")[0][12:].rstrip()
+ except:
+ die("Could not find the \"git\" command")
+ try:
+ self.gitdir = self.get_single("rev-parse --git-dir")
+ report(2, "gdir:", self.gitdir)
+ except:
+ die("Not a git repository... did you forget to \"git init-db\" ?")
+ try:
+ self.cdup = self.get_single("rev-parse --show-cdup")
+ if self.cdup != "":
+ os.chdir(self.cdup)
+ self.topdir = os.getcwd()
+ report(2, "topdir:", self.topdir)
+ except:
+ die("Could not find top git directory")
+
+ def git(self, cmd):
+ global logfile
+ report(2, "GIT:", cmd)
+ f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
+ r=f.readlines()
+ self.ret = f.close()
+ return r
+
+ def get_single(self, cmd):
+ return self.git(cmd)[0].rstrip()
+
+ def current_branch(self):
+ try:
+ testit = self.git("rev-parse --verify HEAD")[0]
+ return self.git("symbolic-ref HEAD")[0][11:].rstrip()
+ except:
+ return None
+
+ def get_config(self, variable):
+ try:
+ return self.git("repo-config --get %s" % variable)[0].rstrip()
+ except:
+ return None
+
+ def set_config(self, variable, value):
+ try:
+ self.git("repo-config %s %s"%(variable, value) )
+ except:
+ die("Could not set %s to " % variable, value)
+
+ def make_tag(self, name, head):
+ self.git("tag -f %s %s"%(name,head))
+
+ def top_change(self, branch):
+ try:
+ a=self.get_single("name-rev --tags refs/heads/%s" % branch)
+ loc = a.find(' tags/') + 6
+ if a[loc:loc+3] != "p4/":
+ raise
+ return int(a[loc+3:][:-2])
+ except:
+ return 0
+
+ def update_index(self):
+ self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
+
+ def checkout(self, branch):
+ self.git("checkout %s" % branch)
+
+ def repoint_head(self, branch):
+ self.git("symbolic-ref HEAD refs/heads/%s" % branch)
+
+ def remove_files(self):
+ self.git("ls-files | xargs rm")
+
+ def clean_directories(self):
+ self.git("clean -d")
+
+ def fresh_branch(self, branch):
+ report(1, "Creating new branch", branch)
+ self.git("ls-files | xargs rm")
+ os.remove(".git/index")
+ self.repoint_head(branch)
+ self.git("clean -d")
+
+ def basedir(self):
+ return self.topdir
+
+ def commit(self, author, email, date, msg, id):
+ self.update_index()
+ fd=open(".msg", "w")
+ fd.writelines(msg)
+ fd.close()
+ try:
+ current = self.get_single("rev-parse --verify HEAD")
+ head = "-p HEAD"
+ except:
+ current = ""
+ head = ""
+ tree = self.get_single("write-tree")
+ for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
+ os.environ['GIT_AUTHOR_%s'%r] = l
+ os.environ['GIT_COMMITTER_%s'%r] = l
+ commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
+ os.remove(".msg")
+ self.make_tag("p4/%s"%id, commit)
+ self.git("update-ref HEAD %s %s" % (commit, current) )
+
+
+try:
+ opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
+ ["authors=","help","stitch=","timezone=","log=","ignore"])
+except getopt.GetoptError:
+ usage()
+
+for o, a in opts:
+ if o == "-q":
+ verbosity = 0
+ if o == "-v":
+ verbosity += 1
+ if o in ("--log"):
+ logfile = a
+ if o in ("-h", "--help"):
+ usage()
+ if o in ("--ignore"):
+ ignore_warnings = True
+
+git = git_command()
+branch=git.current_branch()
+
+for o, a in opts:
+ if o in ("-t", "--timezone"):
+ git.set_config("perforce.timezone", a)
+ if o in ("--stitch"):
+ git.set_config("perforce.%s.path" % branch, a)
+ stitch = 1
+
+if len(args) == 2:
+ branch = args[1]
+ git.checkout(branch)
+ if branch == git.current_branch():
+ die("Branch %s already exists!" % branch)
+ report(1, "Setting perforce to ", args[0])
+ git.set_config("perforce.%s.path" % branch, args[0])
+elif len(args) != 0:
+ die("You must specify the perforce //depot/path and git branch")
+
+p4path = git.get_config("perforce.%s.path" % branch)
+if p4path == None:
+ die("Do not know Perforce //depot/path for git branch", branch)
+
+p4 = p4_command(p4path)
+
+for o, a in opts:
+ if o in ("-a", "--authors"):
+ p4.authors(a)
+
+localdir = git.basedir()
+if p4.where()[:len(localdir)] != localdir:
+ report(1, "**WARNING** Appears p4 client is misconfigured")
+ report(1, " for sync from %s to %s" % (p4.repopath, localdir))
+ if ignore_warnings != True:
+ die("Reconfigure or use \"--ignore\" on command line")
+
+if stitch == 0:
+ top = git.top_change(branch)
+else:
+ top = 0
+changes = p4.changes(top)
+count = len(changes)
+if count == 0:
+ report(1, "Already up to date...")
+ sys.exit(0)
+
+ptz = git.get_config("perforce.timezone")
+if ptz:
+ report(1, "Setting timezone to", ptz)
+ os.environ['TZ'] = ptz
+ time.tzset()
+
+if stitch == 1:
+ git.remove_files()
+ git.clean_directories()
+ p4.sync(changes[0], force=True)
+elif top == 0 and branch != git.current_branch():
+ p4.sync(changes[0], test=True)
+ report(1, "Creating new initial commit");
+ git.fresh_branch(branch)
+ p4.sync(changes[0], force=True)
+else:
+ p4.sync(changes[0], trick=True)
+
+report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
+for id in changes:
+ report(1, "Importing changeset", id)
+ change = p4.describe(id)
+ p4.sync(id)
+ git.commit(change.author, change.email, change.date, change.msg, id)
+ if stitch == 1:
+ git.clean_directories()
+ stitch = 0
+