Code

First (untested) attempt at migrating p4-git-sync into the final git-p4 script
authorSimon Hausmann <hausmann@kde.org>
Mon, 19 Mar 2007 21:25:17 +0000 (22:25 +0100)
committerSimon Hausmann <hausmann@kde.org>
Mon, 19 Mar 2007 21:25:17 +0000 (22:25 +0100)
Signed-off-by: Simon Hausmann <hausmann@kde.org>
contrib/fast-import/git-p4.py

index 42efc7d9aa29a31e2d45f4d6705e0984dc9fd35c..593bda822fc955098b72bfcd1eeb618391b32668 100755 (executable)
@@ -6,7 +6,10 @@
 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
 #
 
-import optparse, sys, os, marshal, popen2
+import optparse, sys, os, marshal, popen2, shelve
+import tempfile
+
+gitdir = os.environ.get("GIT_DIR", "")
 
 def p4CmdList(cmd):
     cmd = "p4 -G %s" % cmd
@@ -37,6 +40,15 @@ def die(msg):
 def currentGitBranch():
     return os.popen("git-name-rev HEAD").read().split(" ")[1][:-1]
 
+def isValidGitDir(path):
+    if os.path.exists(path + "/HEAD") and os.path.exists(path + "/refs") and os.path.exists(path + "/objects"):
+        return True;
+    return False
+
+def system(cmd):
+    if os.system(cmd) != 0:
+        die("command failed: %s" % cmd)
+
 class P4Debug:
     def __init__(self):
         self.options = [
@@ -83,6 +95,214 @@ class P4CleanTags:
 
         print "%s tags removed." % len(allTags)
 
+class P4Sync:
+    def __init__(self):
+        self.options = [
+                optparse.make_option("--continue", action="store_false", dest="firstTime"),
+                optparse.make_option("--origin", dest="origin"),
+                optparse.make_option("--reset", action="store_true", dest="reset"),
+                optparse.make_option("--master", dest="master"),
+                optparse.make_option("--log-substitutions", dest="substFile"),
+                optparse.make_option("--noninteractive", action="store_false"),
+                optparse.make_option("--dry-run", action="store_true")
+        ]
+        self.description = "Submit changes from git to the perforce depot."
+        self.firstTime = True
+        self.reset = False
+        self.interactive = True
+        self.dryRun = False
+        self.substFile = ""
+        self.firstTime = True
+        self.origin = "origin"
+        self.master = ""
+
+        self.logSubstitutions = {}
+        self.logSubstitutions["<enter description here>"] = "%log%"
+        self.logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
+
+    def check(self):
+        if len(p4CmdList("opened ...")) > 0:
+            die("You have files opened with perforce! Close them before starting the sync.")
+
+    def start(self):
+        if len(self.config) > 0 and not self.reset:
+            die("Cannot start sync. Previous sync config found at %s" % self.configFile)
+
+        commits = []
+        for line in os.popen("git-rev-list --no-merges %s..%s" % (self.origin, self.master)).readlines():
+            commits.append(line[:-1])
+        commits.reverse()
+
+        self.config["commits"] = commits
+
+        print "Creating temporary p4-sync branch from %s ..." % self.origin
+        system("git checkout -f -b p4-sync %s" % self.origin)
+
+    def prepareLogMessage(self, template, message):
+        result = ""
+
+        for line in template.split("\n"):
+            if line.startswith("#"):
+                result += line + "\n"
+                continue
+
+            substituted = False
+            for key in self.logSubstitutions.keys():
+                if line.find(key) != -1:
+                    value = self.logSubstitutions[key]
+                    value = value.replace("%log%", message)
+                    if value != "@remove@":
+                        result += line.replace(key, value) + "\n"
+                    substituted = True
+                    break
+
+            if not substituted:
+                result += line + "\n"
+
+        return result
+
+    def apply(self, id):
+        print "Applying %s" % (os.popen("git-log --max-count=1 --pretty=oneline %s" % id).read())
+        diff = os.popen("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)).readlines()
+        filesToAdd = set()
+        filesToDelete = set()
+        for line in diff:
+            modifier = line[0]
+            path = line[1:].strip()
+            if modifier == "M":
+                system("p4 edit %s" % path)
+            elif modifier == "A":
+                filesToAdd.add(path)
+                if path in filesToDelete:
+                    filesToDelete.remove(path)
+            elif modifier == "D":
+                filesToDelete.add(path)
+                if path in filesToAdd:
+                    filesToAdd.remove(path)
+            else:
+                die("unknown modifier %s for %s" % (modifier, path))
+
+        system("git-diff-files --name-only -z | git-update-index --remove -z --stdin")
+        system("git cherry-pick --no-commit \"%s\"" % id)
+
+        for f in filesToAdd:
+            system("p4 add %s" % f)
+        for f in filesToDelete:
+            system("p4 revert %s" % f)
+            system("p4 delete %s" % f)
+
+        logMessage = ""
+        foundTitle = False
+        for log in os.popen("git-cat-file commit %s" % id).readlines():
+            if not foundTitle:
+                if len(log) == 1:
+                    foundTitle = 1
+                continue
+
+            if len(logMessage) > 0:
+                logMessage += "\t"
+            logMessage += log
+
+        template = os.popen("p4 change -o").read()
+
+        if self.interactive:
+            submitTemplate = self.prepareLogMessage(template, logMessage)
+            diff = os.popen("p4 diff -du ...").read()
+
+            for newFile in filesToAdd:
+                diff += "==== new file ====\n"
+                diff += "--- /dev/null\n"
+                diff += "+++ %s\n" % newFile
+                f = open(newFile, "r")
+                for line in f.readlines():
+                    diff += "+" + line
+                f.close()
+
+            pipe = os.popen("less", "w")
+            pipe.write(submitTemplate + diff)
+            pipe.close()
+
+            response = "e"
+            while response == "e":
+                response = raw_input("Do you want to submit this change (y/e/n)? ")
+                if response == "e":
+                    [handle, fileName] = tempfile.mkstemp()
+                    tmpFile = os.fdopen(handle, "w+")
+                    tmpFile.write(submitTemplate)
+                    tmpFile.close()
+                    editor = os.environ.get("EDITOR", "vi")
+                    system(editor + " " + fileName)
+                    tmpFile = open(fileName, "r")
+                    submitTemplate = tmpFile.read()
+                    tmpFile.close()
+                    os.remove(fileName)
+
+            if response == "y" or response == "yes":
+               if self.dryRun:
+                   print submitTemplate
+                   raw_input("Press return to continue...")
+               else:
+                    pipe = os.popen("p4 submit -i", "w")
+                    pipe.write(submitTemplate)
+                    pipe.close()
+            else:
+                print "Not submitting!"
+                self.interactive = False
+        else:
+            fileName = "submit.txt"
+            file = open(fileName, "w+")
+            file.write(self.prepareLogMessage(template, logMessage))
+            file.close()
+            print "Perforce submit template written as %s. Please review/edit and then use p4 submit -i < %s to submit directly!" % (fileName, fileName)
+
+    def run(self, args):
+        if self.reset:
+            self.firstTime = True
+
+        if len(self.substFile) > 0:
+            for line in open(self.substFile, "r").readlines():
+                tokens = line[:-1].split("=")
+                self.logSubstitutions[tokens[0]] = tokens[1]
+
+        if len(self.master) == 0:
+            self.master = currentGitBranch()
+            if len(self.master) == 0 or not os.path.exists("%s/refs/heads/%s" % (gitdir, self.master)):
+                die("Detecting current git branch failed!")
+
+        self.check()
+        self.configFile = gitdir + "/p4-git-sync.cfg"
+        self.config = shelve.open(self.configFile, writeback=True)
+
+        if self.firstTime:
+            self.start()
+
+        commits = self.config.get("commits", [])
+
+        while len(commits) > 0:
+            self.firstTime = False
+            commit = commits[0]
+            commits = commits[1:]
+            self.config["commits"] = commits
+            self.apply(commit)
+            if not self.interactive:
+                break
+
+        self.config.close()
+
+        if len(commits) == 0:
+            if self.firstTime:
+                print "No changes found to apply between %s and current HEAD" % self.origin
+            else:
+                print "All changes applied!"
+                print "Deleting temporary p4-sync branch and going back to %s" % self.master
+                system("git checkout %s" % self.master)
+                system("git branch -D p4-sync")
+                print "Cleaning out your perforce checkout by doing p4 edit ... ; p4 revert ..."
+                system("p4 edit ... >/dev/null")
+                system("p4 revert ... >/dev/null")
+            os.remove(self.configFile)
+
+
 def printUsage(commands):
     print "usage: %s <command> [options]" % sys.argv[0]
     print ""
@@ -93,7 +313,8 @@ def printUsage(commands):
 
 commands = {
     "debug" : P4Debug(),
-    "clean-tags" : P4CleanTags()
+    "clean-tags" : P4CleanTags(),
+    "sync-to-perforce" : P4Sync()
 }
 
 if len(sys.argv[1:]) == 0:
@@ -110,9 +331,25 @@ except KeyError:
     printUsage(commands.keys())
     sys.exit(2)
 
-parser = optparse.OptionParser("usage: %prog " + cmdName + " [options]", cmd.options,
+options = cmd.options
+cmd.gitdir = gitdir
+options.append(optparse.make_option("--git-dir", dest="gitdir"))
+
+parser = optparse.OptionParser("usage: %prog " + cmdName + " [options]", options,
                                description = cmd.description)
 
 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
 
+gitdir = cmd.gitdir
+if len(gitdir) == 0:
+    gitdir = ".git"
+
+if not isValidGitDir(gitdir):
+    if isValidGitDir(gitdir + "/.git"):
+        gitdir += "/.git"
+    else:
+        dir("fatal: cannot locate git repository at %s" % gitdir)
+
+os.environ["GIT_DIR"] = gitdir
+
 cmd.run(args)