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