Code

Pass the right number of arguments to commit, fixes single-branch imports.
[git.git] / contrib / fast-import / p4-git-sync.py
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 #
11 import os, string, shelve, stat
12 import getopt, sys, marshal, tempfile
14 def p4CmdList(cmd):
15     cmd = "p4 -G %s" % cmd
16     pipe = os.popen(cmd, "rb")
18     result = []
19     try:
20         while True:
21             entry = marshal.load(pipe)
22             result.append(entry)
23     except EOFError:
24         pass
25     pipe.close()
27     return result
29 def p4Cmd(cmd):
30     list = p4CmdList(cmd)
31     result = {}
32     for entry in list:
33         result.update(entry)
34     return result;
36 def die(msg):
37     sys.stderr.write(msg + "\n")
38     sys.exit(1)
40 def 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
45 try:
46     opts, args = getopt.getopt(sys.argv[1:], "", [ "continue", "git-dir=", "origin=", "reset", "master=",
47                                                    "submit-log-subst=", "log-substitutions=", "noninteractive",
48                                                    "dry-run" ])
49 except getopt.GetoptError:
50     print "fixme, syntax error"
51     sys.exit(1)
53 logSubstitutions = {}
54 logSubstitutions["<enter description here>"] = "%log%"
55 logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
56 gitdir = os.environ.get("GIT_DIR", "")
57 origin = "origin"
58 master = ""
59 firstTime = True
60 reset = False
61 interactive = True
62 dryRun = False
64 for 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
89 if len(gitdir) == 0:
90     gitdir = ".git"
91 else:
92     os.environ["GIT_DIR"] = gitdir
94 if 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)
102 configFile = gitdir + "/p4-git-sync.cfg"
104 origin = "origin"
105 if len(args) == 1:
106     origin = args[0]
108 if 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)
115 def system(cmd):
116     if os.system(cmd) != 0:
117         die("command failed: %s" % cmd)
119 def check():
120     if len(p4CmdList("opened ...")) > 0:
121         die("You have files opened with perforce! Close them before starting the sync.")
123 def start(config):
124     if len(config) > 0 and not reset:
125         die("Cannot start sync. Previous sync config found at %s" % configFile)
127     #if len(os.popen("git-update-index --refresh").read()) > 0:
128     #    die("Your working tree is not clean. Check with git status!")
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()
135     config["commits"] = commits
137     print "Creating temporary p4-sync branch from %s ..." % origin
138     system("git checkout -f -b p4-sync %s" % origin)
140 #    print "Cleaning index..."
141 #    system("git checkout -f")
143 def prepareLogMessage(template, message):
144     result = ""
146     for line in template.split("\n"):
147         if line.startswith("#"):
148             result += line + "\n"
149             continue
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
161         if not substituted:
162             result += line + "\n"
164     return result
166 def 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))
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)
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)
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
208         if len(logMessage) > 0:
209             logMessage += "\t"
210         logMessage += log
212     template = os.popen("p4 change -o").read()
214     if interactive:
215         submitTemplate = prepareLogMessage(template, logMessage)
216         diff = os.popen("p4 diff -du ...").read()
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()
227         pipe = os.popen("less", "w")
228         pipe.write(submitTemplate + diff)
229         pipe.close()
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)
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)
264 check()
266 config = shelve.open(configFile, writeback=True)
268 if firstTime:
269     start(config)
271 commits = config.get("commits", [])
273 while 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
282 config.close()
284 if 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)