| #!/usr/bin/env 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 | |
| tagall = True | |
| 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\" ?") | |
| 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("config --get %s" % variable)[0].rstrip() | |
| except: | |
| return None | |
| def set_config(self, variable, value): | |
| try: | |
| self.git("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","notags"]) | |
| 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 ("--notags"): | |
| tagall = False | |
| 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) | |
| if tagall : | |
| git.commit(change.author, change.email, change.date, change.msg, id) | |
| else: | |
| git.commit(change.author, change.email, change.date, change.msg, "import") | |
| if stitch == 1: | |
| git.clean_directories() | |
| stitch = 0 |