contrib / p4import / git-p4import.pyon commit remote-bzr: make bzr branches configurable per-repo (a11b0ac)
   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
  11import marshal
  12import os
  13import sys
  14import time
  15import getopt
  16
  17if 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
  22from signal import signal, \
  23   SIGPIPE, SIGINT, SIG_DFL, \
  24   default_int_handler
  25
  26signal(SIGPIPE, SIG_DFL)
  27s = signal(SIGINT, SIG_DFL)
  28if s != default_int_handler:
  29   signal(SIGINT, s)
  30
  31def 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
  37def usage():
  38    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
  39    sys.exit(1)
  40
  41verbosity = 1
  42logfile = "/dev/null"
  43ignore_warnings = False
  44stitch = 0
  45tagall = True
  46
  47def 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
  58class 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
 161class 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
 269try:
 270    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
 271            ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
 272except getopt.GetoptError:
 273    usage()
 274
 275for 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
 289git = git_command()
 290branch=git.current_branch()
 291
 292for 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
 299if 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])
 306elif len(args) != 0:
 307    die("You must specify the perforce //depot/path and git branch")
 308
 309p4path = git.get_config("perforce.%s.path" % branch)
 310if p4path == None:
 311    die("Do not know Perforce //depot/path for git branch", branch)
 312
 313p4 = p4_command(p4path)
 314
 315for o, a in opts:
 316    if o in ("-a", "--authors"):
 317        p4.authors(a)
 318
 319localdir = git.basedir()
 320if 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
 326if stitch == 0:
 327    top = git.top_change(branch)
 328else:
 329    top = 0
 330changes = p4.changes(top)
 331count = len(changes)
 332if count == 0:
 333    report(1, "Already up to date...")
 334    sys.exit(0)
 335
 336ptz = git.get_config("perforce.timezone")
 337if ptz:
 338    report(1, "Setting timezone to", ptz)
 339    os.environ['TZ'] = ptz
 340    time.tzset()
 341
 342if stitch == 1:
 343    git.remove_files()
 344    git.clean_directories()
 345    p4.sync(changes[0], force=True)
 346elif 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)
 351else:
 352    p4.sync(changes[0], trick=True)
 353
 354report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
 355for 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