02f4b36af35b8a248a94759159d85f05f34b2ba4
   1#!/usr/bin/python
   2#
   3# p4-git-sync.py
   4#
   5# Author: Simon Hausmann <hausmann@kde.org>
   6# Copyright: 2007 Simon Hausmann <hausmann@kde.org>
   7#            2007 Trolltech ASA
   8# License: MIT <http://www.opensource.org/licenses/mit-license.php>
   9#
  10
  11import os, string, shelve, stat
  12import getopt, sys, marshal, tempfile
  13
  14def p4CmdList(cmd):
  15    cmd = "p4 -G %s" % cmd
  16    pipe = os.popen(cmd, "rb")
  17
  18    result = []
  19    try:
  20        while True:
  21            entry = marshal.load(pipe)
  22            result.append(entry)
  23    except EOFError:
  24        pass
  25    pipe.close()
  26
  27    return result
  28
  29def p4Cmd(cmd):
  30    list = p4CmdList(cmd)
  31    result = {}
  32    for entry in list:
  33        result.update(entry)
  34    return result;
  35
  36try:
  37    opts, args = getopt.getopt(sys.argv[1:], "", [ "continue", "git-dir=", "origin=", "reset", "master=",
  38                                                   "submit-log-subst=", "log-substitutions=", "interactive",
  39                                                   "dry-run" ])
  40except getopt.GetoptError:
  41    print "fixme, syntax error"
  42    sys.exit(1)
  43
  44logSubstitutions = {}
  45logSubstitutions["<enter description here>"] = "%log%"
  46logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
  47gitdir = os.environ.get("GIT_DIR", "")
  48origin = "origin"
  49master = "master"
  50firstTime = True
  51reset = False
  52interactive = False
  53dryRun = False
  54
  55for o, a in opts:
  56    if o == "--git-dir":
  57        gitdir = a
  58    elif o == "--origin":
  59        origin = a
  60    elif o == "--master":
  61        master = a
  62    elif o == "--continue":
  63        firstTime = False
  64    elif o == "--reset":
  65        reset = True
  66        firstTime = True
  67    elif o == "--submit-log-subst":
  68        key = a.split("%")[0]
  69        value = a.split("%")[1]
  70        logSubstitutions[key] = value
  71    elif o == "--log-substitutions":
  72        for line in open(a, "r").readlines():
  73            tokens = line[:-1].split("=")
  74            logSubstitutions[tokens[0]] = tokens[1]
  75    elif o == "--interactive":
  76        interactive = True
  77    elif o == "--dry-run":
  78        dryRun = True
  79
  80if len(gitdir) == 0:
  81    gitdir = ".git"
  82else:
  83    os.environ["GIT_DIR"] = gitdir
  84
  85configFile = gitdir + "/p4-git-sync.cfg"
  86
  87origin = "origin"
  88if len(args) == 1:
  89    origin = args[0]
  90
  91def die(msg):
  92    sys.stderr.write(msg + "\n")
  93    sys.exit(1)
  94
  95def system(cmd):
  96    if os.system(cmd) != 0:
  97        die("command failed: %s" % cmd)
  98
  99def check():
 100    if len(p4CmdList("opened ...")) > 0:
 101        die("You have files opened with perforce! Close them before starting the sync.")
 102
 103def start(config):
 104    if len(config) > 0 and not reset:
 105        die("Cannot start sync. Previous sync config found at %s" % configFile)
 106
 107    #if len(os.popen("git-update-index --refresh").read()) > 0:
 108    #    die("Your working tree is not clean. Check with git status!")
 109
 110    commits = []
 111    for line in os.popen("git-rev-list --no-merges %s..%s" % (origin, master)).readlines():
 112        commits.append(line[:-1])
 113    commits.reverse()
 114
 115    config["commits"] = commits
 116
 117    print "Creating temporary p4-sync branch from %s ..." % origin
 118    system("git checkout -f -b p4-sync %s" % origin)
 119
 120#    print "Cleaning index..."
 121#    system("git checkout -f")
 122
 123def prepareLogMessage(template, message):
 124    result = ""
 125
 126    for line in template.split("\n"):
 127        if line.startswith("#"):
 128            result += line + "\n"
 129            continue
 130
 131        substituted = False
 132        for key in logSubstitutions.keys():
 133            if line.find(key) != -1:
 134                value = logSubstitutions[key]
 135                value = value.replace("%log%", message)
 136                if value != "@remove@":
 137                    result += line.replace(key, value) + "\n"
 138                substituted = True
 139                break
 140
 141        if not substituted:
 142            result += line + "\n"
 143
 144    return result
 145
 146def apply(id):
 147    global interactive
 148    print "Applying %s" % (os.popen("git-log --max-count=1 --pretty=oneline %s" % id).read())
 149    diff = os.popen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
 150    filesToAdd = set()
 151    filesToDelete = set()
 152    for line in diff:
 153        modifier = line[0]
 154        path = line[1:].strip()
 155        if modifier == "M":
 156            system("p4 edit %s" % path)
 157        elif modifier == "A":
 158            filesToAdd.add(path)
 159            if path in filesToDelete:
 160                filesToDelete.remove(path)
 161        elif modifier == "D":
 162            filesToDelete.add(path)
 163            if path in filesToAdd:
 164                filesToAdd.remove(path)
 165        else:
 166            die("unknown modifier %s for %s" % (modifier, path))
 167
 168    system("git-diff-files --name-only -z | git-update-index --remove -z --stdin")
 169    system("git cherry-pick --no-commit \"%s\"" % id)
 170    #system("git format-patch --stdout -k \"%s^\"..\"%s\" | git-am -k" % (id, id))
 171    #system("git branch -D tmp")
 172    #system("git checkout -f -b tmp \"%s^\"" % id)
 173
 174    for f in filesToAdd:
 175        system("p4 add %s" % f)
 176    for f in filesToDelete:
 177        system("p4 revert %s" % f)
 178        system("p4 delete %s" % f)
 179
 180    logMessage = ""
 181    foundTitle = False
 182    for log in os.popen("git-cat-file commit %s" % id).readlines():
 183        if not foundTitle:
 184            if len(log) == 1:
 185                foundTitle = 1
 186            continue
 187
 188        if len(logMessage) > 0:
 189            logMessage += "\t"
 190        logMessage += log
 191
 192    template = os.popen("p4 change -o").read()
 193
 194    if interactive:
 195        submitTemplate = prepareLogMessage(template, logMessage)
 196        diff = os.popen("p4 diff -du ...").read()
 197
 198        for newFile in filesToAdd:
 199            diff += "==== new file ====\n"
 200            diff += "--- /dev/null\n"
 201            diff += "+++ %s\n" % newFile
 202            f = open(newFile, "r")
 203            for line in f.readlines():
 204                diff += "+" + line
 205            f.close()
 206
 207        pipe = os.popen("less", "w")
 208        pipe.write(submitTemplate + diff)
 209        pipe.close()
 210
 211        response = "e"
 212        while response == "e":
 213            response = raw_input("Do you want to submit this change (y/e/n)? ")
 214            if response == "e":
 215                [handle, fileName] = tempfile.mkstemp()
 216                tmpFile = os.fdopen(handle, "w+")
 217                tmpFile.write(submitTemplate)
 218                tmpFile.close()
 219                editor = os.environ.get("EDITOR", "vi")
 220                system(editor + " " + fileName)
 221                tmpFile = open(fileName, "r")
 222                submitTemplate = tmpFile.read()
 223                tmpFile.close()
 224                os.remove(fileName)
 225
 226        if response == "y" or response == "yes":
 227           if dryRun:
 228               print submitTemplate
 229               raw_input("Press return to continue...")
 230           else:
 231                pipe = os.popen("p4 submit -i", "w")
 232                pipe.write(submitTemplate)
 233                pipe.close()
 234        else:
 235            print "Not submitting!"
 236            interactive = False
 237    else:
 238        fileName = "submit.txt"
 239        file = open(fileName, "w+")
 240        file.write(prepareLogMessage(template, logMessage))
 241        file.close()
 242        print "Perforce submit template written as %s. Please review/edit and then use p4 submit -i < %s to submit directly!" % (fileName, fileName)
 243
 244check()
 245
 246config = shelve.open(configFile, writeback=True)
 247
 248if firstTime:
 249    start(config)
 250
 251commits = config.get("commits", [])
 252
 253while len(commits) > 0:
 254    firstTime = False
 255    commit = commits[0]
 256    commits = commits[1:]
 257    config["commits"] = commits
 258    apply(commit)
 259    if not interactive:
 260        break
 261
 262config.close()
 263
 264if len(commits) == 0:
 265    if firstTime:
 266        print "No changes found to apply between %s and current HEAD" % origin
 267    else:
 268        print "All changes applied!"
 269        print "Deleting temporary p4-sync branch and going back to %s" % master
 270        system("git checkout %s" % master)
 271        system("git branch -D p4-sync")
 272        print "Cleaning out your perforce checkout by doing p4 edit ... ; p4 revert -a ..."
 273        system("p4 edit ...")
 274        system("p4 revert -a ...")
 275    os.remove(configFile)
 276