da3eb35841c5bdb807dab4c929daa4320a39956f
   1#!/usr/bin/python
   2#
   3# p4-fast-export.py
   4#
   5# Author: Simon Hausmann <hausmann@kde.org>
   6# License: MIT <http://www.opensource.org/licenses/mit-license.php>
   7#
   8# TODO:
   9#       - support integrations (at least p4i)
  10#       - support incremental imports
  11#       - create tags
  12#       - instead of reading all files into a variable try to pipe from
  13#       - support p4 submit (hah!)
  14#       - don't hardcode the import to master
  15#
  16import os, string, sys, time
  17import marshal, popen2
  18
  19if len(sys.argv) != 2:
  20    print "usage: %s //depot/path[@revRange]" % sys.argv[0]
  21    print "\n    example:"
  22    print "    %s //depot/my/project/ -- to import everything"
  23    print "    %s //depot/my/project/@1,6 -- to import only from revision 1 to 6"
  24    print ""
  25    print "    (a ... is not needed in the path p4 specification, it's added implicitly)"
  26    print ""
  27    sys.exit(1)
  28
  29master = "refs/heads/p4"
  30branch = "refs/heads/p4-import"
  31prefix = sys.argv[1]
  32changeRange = ""
  33try:
  34    atIdx = prefix.index("@")
  35    changeRange = prefix[atIdx:]
  36    prefix = prefix[0:atIdx]
  37except ValueError:
  38    changeRange = ""
  39
  40if prefix.endswith("..."):
  41    prefix = prefix[:-3]
  42
  43if not prefix.endswith("/"):
  44    prefix += "/"
  45
  46def p4CmdList(cmd):
  47    pipe = os.popen("p4 -G %s" % cmd, "rb")
  48    result = []
  49    try:
  50        while True:
  51            entry = marshal.load(pipe)
  52            result.append(entry)
  53    except EOFError:
  54        pass
  55    pipe.close()
  56    return result
  57
  58def p4Cmd(cmd):
  59    list = p4CmdList(cmd)
  60    result = {}
  61    for entry in list:
  62        result.update(entry)
  63    return result;
  64
  65def getUserMap():
  66    users = {}
  67
  68    for output in p4CmdList("users"):
  69        if not output.has_key("User"):
  70            continue
  71        users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
  72    return users
  73
  74users = getUserMap()
  75topMerge = ""
  76
  77incremental = 0
  78# try incremental import
  79if len(changeRange) == 0:
  80    try:
  81        sout, sin, serr = popen2.popen3("git-name-rev --tags `git-rev-parse %s`" % master)
  82        output = sout.read()
  83        tagIdx = output.index(" tags/p4/")
  84        caretIdx = output.index("^")
  85        revision = int(output[tagIdx + 9 : caretIdx]) + 1
  86        changeRange = "@%s,#head" % revision
  87        topMerge = os.popen("git-rev-parse %s" % master).read()[:-1]
  88        incremental = 1
  89    except:
  90        pass
  91
  92if incremental == 0:
  93    branch = master
  94
  95output = os.popen("p4 changes %s...%s" % (prefix, changeRange)).readlines()
  96
  97changes = []
  98for line in output:
  99    changeNum = line.split(" ")[1]
 100    changes.append(changeNum)
 101
 102changes.reverse()
 103
 104if len(changes) == 0:
 105    print "no changes to import!"
 106    sys.exit(1)
 107
 108sys.stderr.write("\n")
 109
 110tz = - time.timezone / 36
 111
 112gitOutput, gitStream, gitError = popen2.popen3("git-fast-import")
 113
 114cnt = 1
 115for change in changes:
 116    description = p4Cmd("describe %s" % change)
 117
 118    sys.stdout.write("\rimporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
 119    sys.stdout.flush()
 120    cnt = cnt + 1
 121
 122    epoch = description["time"]
 123    author = description["user"]
 124
 125    gitStream.write("commit %s\n" % branch)
 126    committer = ""
 127    if author in users:
 128        committer = "%s %s %s" % (users[author], epoch, tz)
 129    else:
 130        committer = "%s <a@b> %s %s" % (author, epoch, tz)
 131
 132    gitStream.write("committer %s\n" % committer)
 133
 134    gitStream.write("data <<EOT\n")
 135    gitStream.write(description["desc"])
 136    gitStream.write("\n[ imported from %s; change %s ]\n" % (prefix, change))
 137    gitStream.write("EOT\n\n")
 138
 139    if len(topMerge) > 0:
 140        gitStream.write("merge %s\n" % topMerge)
 141        topMerge = ""
 142
 143    fnum = 0
 144    while description.has_key("depotFile%s" % fnum):
 145        path = description["depotFile%s" % fnum]
 146        if not path.startswith(prefix):
 147            print "\nchanged files: ignoring path %s outside of %s in change %s" % (path, prefix, change)
 148            fnum = fnum + 1
 149            continue
 150
 151        rev = description["rev%s" % fnum]
 152        depotPath = path + "#" + rev
 153        relPath = path[len(prefix):]
 154        action = description["action%s" % fnum]
 155
 156        if action == "delete":
 157            gitStream.write("D %s\n" % relPath)
 158        else:
 159            mode = 644
 160            if description["type%s" % fnum].startswith("x"):
 161                mode = 755
 162
 163            data = os.popen("p4 print -q \"%s\"" % depotPath, "rb").read()
 164
 165            gitStream.write("M %s inline %s\n" % (mode, relPath))
 166            gitStream.write("data %s\n" % len(data))
 167            gitStream.write(data)
 168            gitStream.write("\n")
 169
 170        fnum = fnum + 1
 171
 172    gitStream.write("\n")
 173
 174    gitStream.write("tag p4/%s\n" % change)
 175    gitStream.write("from %s\n" % branch);
 176    gitStream.write("tagger %s\n" % committer);
 177    gitStream.write("data 0\n\n")
 178
 179
 180gitStream.close()
 181gitOutput.close()
 182gitError.close()
 183
 184if incremental == 1:
 185    os.popen("git rebase p4-import p4")
 186    os.popen("git branch -d p4-import")
 187
 188print ""