0c0f629a1d6898147a862b08cbd0bafc94b6da89
   1#!/usr/bin/python
   2#
   3# p4-git-sync.py
   4#
   5# Author: Simon Hausmann <hausmann@kde.org>
   6# License: MIT <http://www.opensource.org/licenses/mit-license.php>
   7#
   8
   9import os, string, shelve, stat
  10import getopt, sys, marshal
  11
  12def p4CmdList(cmd):
  13    cmd = "p4 -G %s" % cmd
  14    pipe = os.popen(cmd, "rb")
  15
  16    result = []
  17    try:
  18        while True:
  19            entry = marshal.load(pipe)
  20            result.append(entry)
  21    except EOFError:
  22        pass
  23    pipe.close()
  24
  25    return result
  26
  27def p4Cmd(cmd):
  28    list = p4CmdList(cmd)
  29    result = {}
  30    for entry in list:
  31        result.update(entry)
  32    return result;
  33
  34try:
  35    opts, args = getopt.getopt(sys.argv[1:], "", [ "continue", "git-dir=", "origin=", "reset", "master=",
  36                                                   "submit-log-subst=", "log-substitutions=" ])
  37except getopt.GetoptError:
  38    print "fixme, syntax error"
  39    sys.exit(1)
  40
  41logSubstitutions = {}
  42logSubstitutions["<enter description here>"] = "%log%"
  43logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
  44gitdir = os.environ.get("GIT_DIR", "")
  45origin = "origin"
  46master = "master"
  47firstTime = True
  48reset = False
  49
  50for o, a in opts:
  51    if o == "--git-dir":
  52        gitdir = a
  53    elif o == "--origin":
  54        origin = a
  55    elif o == "--master":
  56        master = a
  57    elif o == "--continue":
  58        firstTime = False
  59    elif o == "--reset":
  60        reset = True
  61        firstTime = True
  62    elif o == "--submit-log-subst":
  63        key = a.split("%")[0]
  64        value = a.split("%")[1]
  65        logSubstitutions[key] = value
  66    elif o == "--log-substitutions":
  67        for line in open(a, "r").readlines():
  68            tokens = line[:-1].split("=")
  69            logSubstitutions[tokens[0]] = tokens[1]
  70
  71if len(gitdir) == 0:
  72    gitdir = ".git"
  73else:
  74    os.environ["GIT_DIR"] = gitdir
  75
  76configFile = gitdir + "/p4-git-sync.cfg"
  77
  78origin = "origin"
  79if len(args) == 1:
  80    origin = args[0]
  81
  82def die(msg):
  83    sys.stderr.write(msg + "\n")
  84    sys.exit(1)
  85
  86def system(cmd):
  87    if os.system(cmd) != 0:
  88        die("command failed: %s" % cmd)
  89
  90def check():
  91    return
  92    if len(p4CmdList("opened ...")) > 0:
  93        die("You have files opened with perforce! Close them before starting the sync.")
  94
  95def start(config):
  96    if len(config) > 0 and not reset:
  97        die("Cannot start sync. Previous sync config found at %s" % configFile)
  98
  99    #if len(os.popen("git-update-index --refresh").read()) > 0:
 100    #    die("Your working tree is not clean. Check with git status!")
 101
 102    commits = []
 103    for line in os.popen("git-rev-list --no-merges %s..%s" % (origin, master)).readlines():
 104        commits.append(line[:-1])
 105    commits.reverse()
 106
 107    config["commits"] = commits
 108
 109#    print "Cleaning index..."
 110#    system("git checkout -f")
 111
 112def prepareLogMessage(template, message):
 113    result = ""
 114
 115    substs = logSubstitutions
 116    for k in substs.keys():
 117        substs[k] = substs[k].replace("%log%", message)
 118
 119    for line in template.split("\n"):
 120        if line.startswith("#"):
 121            result += line + "\n"
 122            continue
 123
 124        substituted = False
 125        for key in substs.keys():
 126            if line.find(key) != -1:
 127                value = substs[key]
 128                if value != "@remove@":
 129                    result += line.replace(key, value) + "\n"
 130                substituted = True
 131                break
 132
 133        if not substituted:
 134            result += line + "\n"
 135
 136    return result
 137
 138def apply(id):
 139    print "Applying %s" % (os.popen("git-log --max-count=1 --pretty=oneline %s" % id).read())
 140    diff = os.popen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
 141    filesToAdd = set()
 142    filesToDelete = set()
 143    for line in diff:
 144        modifier = line[0]
 145        path = line[1:].strip()
 146        if modifier == "M":
 147            system("p4 edit %s" % path)
 148        elif modifier == "A":
 149            filesToAdd.add(path)
 150            if path in filesToDelete:
 151                filesToDelete.remove(path)
 152        elif modifier == "D":
 153            filesToDelete.add(path)
 154            if path in filesToAdd:
 155                filesToAdd.remove(path)
 156        else:
 157            die("unknown modifier %s for %s" % (modifier, path))
 158
 159    system("git-diff-files --name-only -z | git-update-index --remove -z --stdin")
 160    system("git cherry-pick --no-commit \"%s\"" % id)
 161
 162    for f in filesToAdd:
 163        system("p4 add %s" % f)
 164    for f in filesToDelete:
 165        system("p4 revert %s" % f)
 166        system("p4 delete %s" % f)
 167
 168    logMessage = ""
 169    foundTitle = False
 170    for log in os.popen("git-cat-file commit %s" % id).readlines():
 171        log = log[:-1]
 172        if not foundTitle:
 173            if len(log) == 0:
 174                foundTitle = 1
 175            continue
 176
 177        if len(logMessage) > 0:
 178            logMessage += "\t"
 179        logMessage += log + "\n"
 180
 181    template = os.popen("p4 change -o").read()
 182    fileName = "submit.txt"
 183    file = open(fileName, "w+")
 184    file.write(prepareLogMessage(template, logMessage))
 185    file.close()
 186    print "Perforce submit template written as %s. Please review/edit and then use p4 submit -i < %s to submit directly!" % (fileName, fileName)
 187
 188check()
 189
 190config = shelve.open(configFile, writeback=True)
 191
 192if firstTime:
 193    start(config)
 194
 195commits = config.get("commits", [])
 196
 197if len(commits) > 0:
 198    firstTime = False
 199    commit = commits[0]
 200    commits = commits[1:]
 201    config["commits"] = commits
 202    apply(commit)
 203
 204config.close()
 205
 206if len(commits) == 0:
 207    if firstTime:
 208        print "No changes found to apply between %s and current HEAD" % origin
 209    else:
 210        print "All changes applied!"
 211    os.remove(configFile)
 212