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