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