blob: 6b847c4cb8edb51808b38fd80754535b0ad728b3 [file] [log] [blame]
Simon Hausmann86949ee2007-03-19 20:59:12 +01001#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
Simon Hausmannc8cbbee2007-05-28 14:43:25 +02005# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
Simon Hausmann83dce552007-03-19 22:26:36 +01007# 2007 Trolltech ASA
Simon Hausmann86949ee2007-03-19 20:59:12 +01008# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
10
Reilly Grant1d7367d2009-09-10 00:02:38 -070011import optparse, sys, os, marshal, subprocess, shelve
12import tempfile, getopt, os.path, time, platform
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -030013import re
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030014
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030015verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +010016
Anand Kumria21a50752008-08-10 19:26:28 +010017
18def p4_build_cmd(cmd):
19 """Build a suitable p4 command line.
20
21 This consolidates building and returning a p4 command line into one
22 location. It means that hooking into the environment, or other configuration
23 can be done more easily.
24 """
Anand Kumriaabcaf072008-08-10 19:26:31 +010025 real_cmd = "%s " % "p4"
26
27 user = gitConfig("git-p4.user")
28 if len(user) > 0:
29 real_cmd += "-u %s " % user
30
31 password = gitConfig("git-p4.password")
32 if len(password) > 0:
33 real_cmd += "-P %s " % password
34
35 port = gitConfig("git-p4.port")
36 if len(port) > 0:
37 real_cmd += "-p %s " % port
38
39 host = gitConfig("git-p4.host")
40 if len(host) > 0:
41 real_cmd += "-h %s " % host
42
43 client = gitConfig("git-p4.client")
44 if len(client) > 0:
45 real_cmd += "-c %s " % client
46
47 real_cmd += "%s" % (cmd)
Anand Kumriaee064272008-08-10 19:26:29 +010048 if verbose:
49 print real_cmd
Anand Kumria21a50752008-08-10 19:26:28 +010050 return real_cmd
51
Robert Blum053fd0c2008-08-01 12:50:03 -070052def chdir(dir):
53 if os.name == 'nt':
54 os.environ['PWD']=dir
55 os.chdir(dir)
56
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030057def die(msg):
58 if verbose:
59 raise Exception(msg)
60 else:
61 sys.stderr.write(msg + "\n")
62 sys.exit(1)
63
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030064def write_pipe(c, str):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030065 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030066 sys.stderr.write('Writing pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030067
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030068 pipe = os.popen(c, 'w')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030069 val = pipe.write(str)
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030070 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030071 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030072
73 return val
74
Anand Kumriad9429192008-08-14 23:40:38 +010075def p4_write_pipe(c, str):
76 real_cmd = p4_build_cmd(c)
Tor Arvid Lund893d3402008-08-21 23:11:40 +020077 return write_pipe(real_cmd, str)
Anand Kumriad9429192008-08-14 23:40:38 +010078
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030079def read_pipe(c, ignore_error=False):
80 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030081 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030082
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030083 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030084 val = pipe.read()
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030085 if pipe.close() and not ignore_error:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030086 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030087
88 return val
89
Anand Kumriad9429192008-08-14 23:40:38 +010090def p4_read_pipe(c, ignore_error=False):
91 real_cmd = p4_build_cmd(c)
92 return read_pipe(real_cmd, ignore_error)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030093
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030094def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030095 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030096 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030097 ## todo: check return status
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030098 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030099 val = pipe.readlines()
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -0300100 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300101 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300102
103 return val
Simon Hausmanncaace112007-05-15 14:57:57 +0200104
Anand Kumria23181212008-08-10 19:26:24 +0100105def p4_read_pipe_lines(c):
106 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +0100107 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +0100108 return read_pipe_lines(real_cmd)
109
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300110def system(cmd):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300111 if verbose:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300112 sys.stderr.write("executing %s\n" % cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300113 if os.system(cmd) != 0:
114 die("command failed: %s" % cmd)
115
Anand Kumriabf9320f2008-08-10 19:26:26 +0100116def p4_system(cmd):
117 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +0100118 real_cmd = p4_build_cmd(cmd)
Anand Kumriabf9320f2008-08-10 19:26:26 +0100119 return system(real_cmd)
120
David Brownb9fc6ea2007-09-19 13:12:48 -0700121def isP4Exec(kind):
122 """Determine if a Perforce 'kind' should have execute permission
123
124 'p4 help filetypes' gives a list of the types. If it starts with 'x',
125 or x follows one of a few letters. Otherwise, if there is an 'x' after
126 a plus sign, it is also executable"""
127 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
128
Chris Pettittc65b6702007-11-01 20:43:14 -0700129def setP4ExecBit(file, mode):
130 # Reopens an already open file and changes the execute bit to match
131 # the execute bit setting in the passed in mode.
132
133 p4Type = "+x"
134
135 if not isModeExec(mode):
136 p4Type = getP4OpenedType(file)
137 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
138 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
139 if p4Type[-1] == "+":
140 p4Type = p4Type[0:-1]
141
Anand Kumria87b611d2008-08-10 19:26:27 +0100142 p4_system("reopen -t %s %s" % (p4Type, file))
Chris Pettittc65b6702007-11-01 20:43:14 -0700143
144def getP4OpenedType(file):
145 # Returns the perforce file type for the given file.
146
Anand Kumriaa7d3ef92008-08-14 23:40:39 +0100147 result = p4_read_pipe("opened %s" % file)
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100148 match = re.match(".*\((.+)\)\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700149 if match:
150 return match.group(1)
151 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100152 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700153
Chris Pettittb43b0a32007-11-01 20:43:13 -0700154def diffTreePattern():
155 # This is a simple generator for the diff tree regex pattern. This could be
156 # a class variable if this and parseDiffTreeEntry were a part of a class.
157 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
158 while True:
159 yield pattern
160
161def parseDiffTreeEntry(entry):
162 """Parses a single diff tree entry into its component elements.
163
164 See git-diff-tree(1) manpage for details about the format of the diff
165 output. This method returns a dictionary with the following elements:
166
167 src_mode - The mode of the source file
168 dst_mode - The mode of the destination file
169 src_sha1 - The sha1 for the source file
170 dst_sha1 - The sha1 fr the destination file
171 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
172 status_score - The score for the status (applicable for 'C' and 'R'
173 statuses). This is None if there is no score.
174 src - The path for the source file.
175 dst - The path for the destination file. This is only present for
176 copy or renames. If it is not present, this is None.
177
178 If the pattern is not matched, None is returned."""
179
180 match = diffTreePattern().next().match(entry)
181 if match:
182 return {
183 'src_mode': match.group(1),
184 'dst_mode': match.group(2),
185 'src_sha1': match.group(3),
186 'dst_sha1': match.group(4),
187 'status': match.group(5),
188 'status_score': match.group(6),
189 'src': match.group(7),
190 'dst': match.group(10)
191 }
192 return None
193
Chris Pettittc65b6702007-11-01 20:43:14 -0700194def isModeExec(mode):
195 # Returns True if the given git mode represents an executable file,
196 # otherwise False.
197 return mode[-3:] == "755"
198
199def isModeExecChanged(src_mode, dst_mode):
200 return isModeExec(src_mode) != isModeExec(dst_mode)
201
Luke Diamandb9327052009-07-30 00:13:46 +0100202def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
Anand Kumria155af832008-08-10 19:26:30 +0100203 cmd = p4_build_cmd("-G %s" % (cmd))
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300204 if verbose:
205 sys.stderr.write("Opening pipe: %s\n" % cmd)
Scott Lamb9f90c732007-07-15 20:58:10 -0700206
207 # Use a temporary file to avoid deadlocks without
208 # subprocess.communicate(), which would put another copy
209 # of stdout into memory.
210 stdin_file = None
211 if stdin is not None:
212 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
213 stdin_file.write(stdin)
214 stdin_file.flush()
215 stdin_file.seek(0)
216
217 p4 = subprocess.Popen(cmd, shell=True,
218 stdin=stdin_file,
219 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100220
221 result = []
222 try:
223 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700224 entry = marshal.load(p4.stdout)
Luke Diamandb9327052009-07-30 00:13:46 +0100225 if cb is not None:
226 cb(entry)
227 else:
228 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100229 except EOFError:
230 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700231 exitCode = p4.wait()
232 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200233 entry = {}
234 entry["p4ExitCode"] = exitCode
235 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100236
237 return result
238
239def p4Cmd(cmd):
240 list = p4CmdList(cmd)
241 result = {}
242 for entry in list:
243 result.update(entry)
244 return result;
245
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100246def p4Where(depotPath):
247 if not depotPath.endswith("/"):
248 depotPath += "/"
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100249 depotPath = depotPath + "..."
250 outputList = p4CmdList("where %s" % depotPath)
251 output = None
252 for entry in outputList:
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100253 if "depotFile" in entry:
254 if entry["depotFile"] == depotPath:
255 output = entry
256 break
257 elif "data" in entry:
258 data = entry.get("data")
259 space = data.find(" ")
260 if data[:space] == depotPath:
261 output = entry
262 break
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100263 if output == None:
264 return ""
Simon Hausmanndc524032007-05-21 09:34:56 +0200265 if output["code"] == "error":
266 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100267 clientPath = ""
268 if "path" in output:
269 clientPath = output.get("path")
270 elif "data" in output:
271 data = output.get("data")
272 lastSpace = data.rfind(" ")
273 clientPath = data[lastSpace + 1:]
274
275 if clientPath.endswith("..."):
276 clientPath = clientPath[:-3]
277 return clientPath
278
Simon Hausmann86949ee2007-03-19 20:59:12 +0100279def currentGitBranch():
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300280 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100281
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100282def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300283 if (os.path.exists(path + "/HEAD")
284 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100285 return True;
286 return False
287
Simon Hausmann463e8af2007-05-17 09:13:54 +0200288def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300289 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200290
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100291def extractLogMessageFromGitCommit(commit):
292 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300293
294 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100295 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300296 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100297 if not foundTitle:
298 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200299 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100300 continue
301
302 logMessage += log
303 return logMessage
304
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300305def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100306 values = {}
307 for line in log.split("\n"):
308 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300309 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
310 if not m:
311 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100312
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300313 assignments = m.group(1).split (':')
314 for a in assignments:
315 vals = a.split ('=')
316 key = vals[0].strip()
317 val = ('='.join (vals[1:])).strip()
318 if val.endswith ('\"') and val.startswith('"'):
319 val = val[1:-1]
320
321 values[key] = val
322
Simon Hausmann845b42c2007-06-07 09:19:34 +0200323 paths = values.get("depot-paths")
324 if not paths:
325 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200326 if paths:
327 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300328 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100329
Simon Hausmann8136a632007-03-22 21:27:14 +0100330def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300331 proc = subprocess.Popen(["git", "rev-parse", branch],
332 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200333 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100334
John Chapman36bd8442008-11-08 14:22:49 +1100335_gitConfig = {}
Simon Hausmann01265102007-05-25 10:36:10 +0200336def gitConfig(key):
John Chapman36bd8442008-11-08 14:22:49 +1100337 if not _gitConfig.has_key(key):
338 _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
339 return _gitConfig[key]
Simon Hausmann01265102007-05-25 10:36:10 +0200340
Simon Hausmann062410b2007-07-18 10:56:31 +0200341def p4BranchesInGit(branchesAreInRemotes = True):
342 branches = {}
343
344 cmdline = "git rev-parse --symbolic "
345 if branchesAreInRemotes:
346 cmdline += " --remotes"
347 else:
348 cmdline += " --branches"
349
350 for line in read_pipe_lines(cmdline):
351 line = line.strip()
352
353 ## only import to p4/
354 if not line.startswith('p4/') or line == "p4/HEAD":
355 continue
356 branch = line
357
358 # strip off p4
359 branch = re.sub ("^p4/", "", line)
360
361 branches[branch] = parseRevision(line)
362 return branches
363
Simon Hausmann9ceab362007-06-22 00:01:57 +0200364def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200365 branches = p4BranchesInGit()
366 # map from depot-path to branch name
367 branchByDepotPath = {}
368 for branch in branches.keys():
369 tip = branches[branch]
370 log = extractLogMessageFromGitCommit(tip)
371 settings = extractSettingsGitLog(log)
372 if settings.has_key("depot-paths"):
373 paths = ",".join(settings["depot-paths"])
374 branchByDepotPath[paths] = "remotes/p4/" + branch
375
Simon Hausmann27d2d812007-06-12 14:31:59 +0200376 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200377 parent = 0
378 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200379 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200380 log = extractLogMessageFromGitCommit(commit)
381 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200382 if settings.has_key("depot-paths"):
383 paths = ",".join(settings["depot-paths"])
384 if branchByDepotPath.has_key(paths):
385 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200386
Simon Hausmann86506fe2007-07-18 12:40:12 +0200387 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200388
Simon Hausmann86506fe2007-07-18 12:40:12 +0200389 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200390
Simon Hausmann5ca44612007-08-24 17:44:16 +0200391def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
392 if not silent:
393 print ("Creating/updating branch(es) in %s based on origin branch(es)"
394 % localRefPrefix)
395
396 originPrefix = "origin/p4/"
397
398 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
399 line = line.strip()
400 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
401 continue
402
403 headName = line[len(originPrefix):]
404 remoteHead = localRefPrefix + headName
405 originHead = line
406
407 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
408 if (not original.has_key('depot-paths')
409 or not original.has_key('change')):
410 continue
411
412 update = False
413 if not gitBranchExists(remoteHead):
414 if verbose:
415 print "creating %s" % remoteHead
416 update = True
417 else:
418 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
419 if settings.has_key('change') > 0:
420 if settings['depot-paths'] == original['depot-paths']:
421 originP4Change = int(original['change'])
422 p4Change = int(settings['change'])
423 if originP4Change > p4Change:
424 print ("%s (%s) is newer than %s (%s). "
425 "Updating p4 branch from origin."
426 % (originHead, originP4Change,
427 remoteHead, p4Change))
428 update = True
429 else:
430 print ("Ignoring: %s was imported from %s while "
431 "%s was imported from %s"
432 % (originHead, ','.join(original['depot-paths']),
433 remoteHead, ','.join(settings['depot-paths'])))
434
435 if update:
436 system("git update-ref %s %s" % (remoteHead, originHead))
437
438def originP4BranchesExist():
439 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
440
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200441def p4ChangesForPaths(depotPaths, changeRange):
442 assert depotPaths
Anand Kumriab340fa42008-08-10 19:26:25 +0100443 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200444 for p in depotPaths]))
445
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500446 changes = {}
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200447 for line in output:
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500448 changeNum = int(line.split(" ")[1])
449 changes[changeNum] = True
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200450
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500451 changelist = changes.keys()
452 changelist.sort()
453 return changelist
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200454
Simon Hausmannb9847332007-03-20 20:54:23 +0100455class Command:
456 def __init__(self):
457 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200458 self.needsGit = True
Simon Hausmannb9847332007-03-20 20:54:23 +0100459
460class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100461 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100462 Command.__init__(self)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100463 self.options = [
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300464 optparse.make_option("--verbose", dest="verbose", action="store_true",
465 default=False),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300466 ]
Simon Hausmannc8c39112007-03-19 21:02:30 +0100467 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200468 self.needsGit = False
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300469 self.verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100470
471 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300472 j = 0
Simon Hausmann86949ee2007-03-19 20:59:12 +0100473 for output in p4CmdList(" ".join(args)):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300474 print 'Element: %d' % j
475 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100476 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100477 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100478
Simon Hausmann58346842007-05-21 22:57:06 +0200479class P4RollBack(Command):
480 def __init__(self):
481 Command.__init__(self)
482 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +0200483 optparse.make_option("--verbose", dest="verbose", action="store_true"),
484 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +0200485 ]
486 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann52102d42007-05-21 23:44:24 +0200487 self.verbose = False
Simon Hausmann0c66a782007-05-23 20:07:57 +0200488 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +0200489
490 def run(self, args):
491 if len(args) != 1:
492 return False
493 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +0200494
Simon Hausmannad192f22007-05-23 23:44:19 +0200495 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +0200496 die("Problems executing p4");
497
Simon Hausmann0c66a782007-05-23 20:07:57 +0200498 if self.rollbackLocalBranches:
499 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300500 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200501 else:
502 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300503 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200504
505 for line in lines:
506 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300507 line = line.strip()
508 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +0200509 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300510 settings = extractSettingsGitLog(log)
511
512 depotPaths = settings['depot-paths']
513 change = settings['change']
514
Simon Hausmann58346842007-05-21 22:57:06 +0200515 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +0200516
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300517 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
518 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +0200519 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
520 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
521 continue
522
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300523 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +0200524 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +0200525 if self.verbose:
526 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +0200527 system("git update-ref %s \"%s^\"" % (ref, ref))
528 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300529 settings = extractSettingsGitLog(log)
530
531
532 depotPaths = settings['depot-paths']
533 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +0200534
535 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +0200536 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +0200537
538 return True
539
Simon Hausmann711544b2007-04-01 15:40:46 +0200540class P4Submit(Command):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100541 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +0100542 Command.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100543 self.options = [
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300544 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100545 optparse.make_option("--origin", dest="origin"),
Chris Pettittd9a5f252007-10-15 22:15:06 -0700546 optparse.make_option("-M", dest="detectRename", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100547 ]
548 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200549 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100550 self.interactive = True
Simon Hausmann95124972007-03-23 09:16:07 +0100551 self.origin = ""
Chris Pettittd9a5f252007-10-15 22:15:06 -0700552 self.detectRename = False
Simon Hausmannb0d10df2007-06-07 13:09:14 +0200553 self.verbose = False
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200554 self.isWindows = (platform.system() == "Windows")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100555
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100556 def check(self):
557 if len(p4CmdList("opened ...")) > 0:
558 die("You have files opened with perforce! Close them before starting the sync.")
559
Simon Hausmannedae1e22008-02-19 09:29:06 +0100560 # replaces everything between 'Description:' and the next P4 submit template field with the
561 # commit message
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100562 def prepareLogMessage(self, template, message):
563 result = ""
564
Simon Hausmannedae1e22008-02-19 09:29:06 +0100565 inDescriptionSection = False
566
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100567 for line in template.split("\n"):
568 if line.startswith("#"):
569 result += line + "\n"
570 continue
571
Simon Hausmannedae1e22008-02-19 09:29:06 +0100572 if inDescriptionSection:
573 if line.startswith("Files:"):
574 inDescriptionSection = False
575 else:
576 continue
577 else:
578 if line.startswith("Description:"):
579 inDescriptionSection = True
580 line += "\n"
581 for messageLine in message.split("\n"):
582 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100583
Simon Hausmannedae1e22008-02-19 09:29:06 +0100584 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100585
586 return result
587
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200588 def prepareSubmitTemplate(self):
589 # remove lines in the Files section that show changes to files outside the depot path we're committing into
590 template = ""
591 inFilesSection = False
Anand Kumriab340fa42008-08-10 19:26:25 +0100592 for line in p4_read_pipe_lines("change -o"):
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100593 if line.endswith("\r\n"):
594 line = line[:-2] + "\n"
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200595 if inFilesSection:
596 if line.startswith("\t"):
597 # path starts and ends with a tab
598 path = line[1:]
599 lastTab = path.rfind("\t")
600 if lastTab != -1:
601 path = path[:lastTab]
602 if not path.startswith(self.depotPath):
603 continue
604 else:
605 inFilesSection = False
606 else:
607 if line.startswith("Files:"):
608 inFilesSection = True
609
610 template += line
611
612 return template
613
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300614 def applyCommit(self, id):
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100615 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
616 diffOpts = ("", "-M")[self.detectRename]
617 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100618 filesToAdd = set()
619 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +0200620 editedFiles = set()
Chris Pettittc65b6702007-11-01 20:43:14 -0700621 filesToChangeExecBit = {}
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100622 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -0700623 diff = parseDiffTreeEntry(line)
624 modifier = diff['status']
625 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100626 if modifier == "M":
Anand Kumria87b611d2008-08-10 19:26:27 +0100627 p4_system("edit \"%s\"" % path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700628 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
629 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +0200630 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100631 elif modifier == "A":
632 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700633 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100634 if path in filesToDelete:
635 filesToDelete.remove(path)
636 elif modifier == "D":
637 filesToDelete.add(path)
638 if path in filesToAdd:
639 filesToAdd.remove(path)
Chris Pettittd9a5f252007-10-15 22:15:06 -0700640 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -0700641 src, dest = diff['src'], diff['dst']
Anand Kumria87b611d2008-08-10 19:26:27 +0100642 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
643 p4_system("edit \"%s\"" % (dest))
Chris Pettittc65b6702007-11-01 20:43:14 -0700644 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
645 filesToChangeExecBit[dest] = diff['dst_mode']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700646 os.unlink(dest)
647 editedFiles.add(dest)
648 filesToDelete.add(src)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100649 else:
650 die("unknown modifier %s for %s" % (modifier, path))
651
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100652 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
Simon Hausmann47a130b2007-05-20 16:33:21 +0200653 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200654 tryPatchCmd = patchcmd + "--check -"
655 applyPatchCmd = patchcmd + "--check --apply -"
Simon Hausmann51a26402007-04-15 09:59:56 +0200656
Simon Hausmann47a130b2007-05-20 16:33:21 +0200657 if os.system(tryPatchCmd) != 0:
Simon Hausmann51a26402007-04-15 09:59:56 +0200658 print "Unfortunately applying the change failed!"
659 print "What do you want to do?"
660 response = "x"
661 while response != "s" and response != "a" and response != "w":
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300662 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
663 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
Simon Hausmann51a26402007-04-15 09:59:56 +0200664 if response == "s":
665 print "Skipping! Good luck with the next patches..."
Simon Hausmann20947142007-09-13 22:10:18 +0200666 for f in editedFiles:
Anand Kumria87b611d2008-08-10 19:26:27 +0100667 p4_system("revert \"%s\"" % f);
Simon Hausmann20947142007-09-13 22:10:18 +0200668 for f in filesToAdd:
669 system("rm %s" %f)
Simon Hausmann51a26402007-04-15 09:59:56 +0200670 return
671 elif response == "a":
Simon Hausmann47a130b2007-05-20 16:33:21 +0200672 os.system(applyPatchCmd)
Simon Hausmann51a26402007-04-15 09:59:56 +0200673 if len(filesToAdd) > 0:
674 print "You may also want to call p4 add on the following files:"
675 print " ".join(filesToAdd)
676 if len(filesToDelete):
677 print "The following files should be scheduled for deletion with p4 delete:"
678 print " ".join(filesToDelete)
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300679 die("Please resolve and submit the conflict manually and "
680 + "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200681 elif response == "w":
682 system(diffcmd + " > patch.txt")
683 print "Patch saved to patch.txt in %s !" % self.clientPath
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300684 die("Please resolve and submit the conflict manually and "
685 "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200686
Simon Hausmann47a130b2007-05-20 16:33:21 +0200687 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100688
689 for f in filesToAdd:
Anand Kumria87b611d2008-08-10 19:26:27 +0100690 p4_system("add \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100691 for f in filesToDelete:
Anand Kumria87b611d2008-08-10 19:26:27 +0100692 p4_system("revert \"%s\"" % f)
693 p4_system("delete \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100694
Chris Pettittc65b6702007-11-01 20:43:14 -0700695 # Set/clear executable bits
696 for f in filesToChangeExecBit.keys():
697 mode = filesToChangeExecBit[f]
698 setP4ExecBit(f, mode)
699
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100700 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100701 logMessage = logMessage.strip()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100702
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200703 template = self.prepareSubmitTemplate()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100704
705 if self.interactive:
706 submitTemplate = self.prepareLogMessage(template, logMessage)
Shawn Bohrer67abd412008-03-12 19:03:23 -0500707 if os.environ.has_key("P4DIFF"):
708 del(os.environ["P4DIFF"])
Andrew Waters8b130262010-10-22 13:26:02 +0100709 diff = ""
710 for editedFile in editedFiles:
711 diff += p4_read_pipe("diff -du %r" % editedFile)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100712
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100713 newdiff = ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100714 for newFile in filesToAdd:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100715 newdiff += "==== new file ====\n"
716 newdiff += "--- /dev/null\n"
717 newdiff += "+++ %s\n" % newFile
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100718 f = open(newFile, "r")
719 for line in f.readlines():
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100720 newdiff += "+" + line
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100721 f.close()
722
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100723 separatorLine = "######## everything below this line is just the diff #######\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100724
Simon Hausmanne96e4002008-01-04 14:27:55 +0100725 [handle, fileName] = tempfile.mkstemp()
726 tmpFile = os.fdopen(handle, "w+")
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100727 if self.isWindows:
728 submitTemplate = submitTemplate.replace("\n", "\r\n")
729 separatorLine = separatorLine.replace("\n", "\r\n")
730 newdiff = newdiff.replace("\n", "\r\n")
731 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
Simon Hausmanne96e4002008-01-04 14:27:55 +0100732 tmpFile.close()
Simon Hausmanncdc7e382008-08-27 09:30:29 +0200733 mtime = os.stat(fileName).st_mtime
Shawn Bohrer82cea9f2008-03-12 19:03:24 -0500734 if os.environ.has_key("P4EDITOR"):
735 editor = os.environ.get("P4EDITOR")
736 else:
Nicolas Pitre8b187e62010-01-22 00:55:15 -0500737 editor = read_pipe("git var GIT_EDITOR").strip()
Simon Hausmanne96e4002008-01-04 14:27:55 +0100738 system(editor + " " + fileName)
Simon Hausmanncb4f1282007-05-25 22:34:30 +0200739
Simon Hausmanncdc7e382008-08-27 09:30:29 +0200740 response = "y"
741 if os.stat(fileName).st_mtime <= mtime:
742 response = "x"
743 while response != "y" and response != "n":
744 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
745
746 if response == "y":
747 tmpFile = open(fileName, "rb")
748 message = tmpFile.read()
749 tmpFile.close()
750 submitTemplate = message[:message.index(separatorLine)]
751 if self.isWindows:
752 submitTemplate = submitTemplate.replace("\r\n", "\n")
753 p4_write_pipe("submit -i", submitTemplate)
754 else:
755 for f in editedFiles:
756 p4_system("revert \"%s\"" % f);
757 for f in filesToAdd:
758 p4_system("revert \"%s\"" % f);
759 system("rm %s" %f)
760
761 os.remove(fileName)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100762 else:
763 fileName = "submit.txt"
764 file = open(fileName, "w+")
765 file.write(self.prepareLogMessage(template, logMessage))
766 file.close()
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300767 print ("Perforce submit template written as %s. "
768 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
769 % (fileName, fileName))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100770
771 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200772 if len(args) == 0:
773 self.master = currentGitBranch()
Simon Hausmann4280e532007-05-25 08:49:18 +0200774 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200775 die("Detecting current git branch failed!")
776 elif len(args) == 1:
777 self.master = args[0]
778 else:
779 return False
780
Jing Xue4c2d5d72008-06-22 14:12:39 -0400781 allowSubmit = gitConfig("git-p4.allowSubmit")
782 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
783 die("%s is not in git-p4.allowSubmit" % self.master)
784
Simon Hausmann27d2d812007-06-12 14:31:59 +0200785 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200786 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200787 if len(self.origin) == 0:
788 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200789
790 if self.verbose:
791 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +0100792
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200793 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +0100794 print "Internal error: cannot locate perforce depot path from existing branches"
795 sys.exit(128)
796
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200797 self.clientPath = p4Where(self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +0100798
Simon Hausmann51a26402007-04-15 09:59:56 +0200799 if len(self.clientPath) == 0:
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200800 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
Simon Hausmann95124972007-03-23 09:16:07 +0100801 sys.exit(128)
802
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200803 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +0200804 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200805
Robert Blum053fd0c2008-08-01 12:50:03 -0700806 chdir(self.clientPath)
Benjamin C Meyer6a012982010-03-19 00:39:10 -0400807 print "Synchronizing p4 checkout..."
Anand Kumria87b611d2008-08-10 19:26:27 +0100808 p4_system("sync ...")
Simon Hausmann95124972007-03-23 09:16:07 +0100809
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100810 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100811
Simon Hausmann4c750c02008-02-19 09:37:16 +0100812 commits = []
813 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
814 commits.append(line.strip())
815 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100816
817 while len(commits) > 0:
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100818 commit = commits[0]
819 commits = commits[1:]
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300820 self.applyCommit(commit)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100821 if not self.interactive:
822 break
823
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100824 if len(commits) == 0:
Simon Hausmann4c750c02008-02-19 09:37:16 +0100825 print "All changes applied!"
Robert Blum053fd0c2008-08-01 12:50:03 -0700826 chdir(self.oldWorkingDirectory)
Simon Hausmann14594f42007-08-22 09:07:15 +0200827
Simon Hausmann4c750c02008-02-19 09:37:16 +0100828 sync = P4Sync()
829 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +0200830
Simon Hausmann4c750c02008-02-19 09:37:16 +0100831 rebase = P4Rebase()
832 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100833
Simon Hausmannb9847332007-03-20 20:54:23 +0100834 return True
835
Simon Hausmann711544b2007-04-01 15:40:46 +0200836class P4Sync(Command):
Pete Wyckoff56c09342011-02-19 08:17:57 -0500837 delete_actions = ( "delete", "move/delete", "purge" )
838
Simon Hausmannb9847332007-03-20 20:54:23 +0100839 def __init__(self):
840 Command.__init__(self)
841 self.options = [
842 optparse.make_option("--branch", dest="branch"),
843 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
844 optparse.make_option("--changesfile", dest="changesFile"),
845 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +0200846 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Simon Hausmanna028a982007-05-23 00:03:08 +0200847 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300848 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
849 help="Import into refs/heads/ , not refs/remotes"),
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300850 optparse.make_option("--max-changes", dest="maxChanges"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300851 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100852 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
853 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
854 help="Only sync files that are included in the Perforce Client Spec")
Simon Hausmannb9847332007-03-20 20:54:23 +0100855 ]
856 self.description = """Imports from Perforce into a git repository.\n
857 example:
858 //depot/my/project/ -- to import the current head
859 //depot/my/project/@all -- to import everything
860 //depot/my/project/@1,6 -- to import only from revision 1 to 6
861
862 (a ... is not needed in the path p4 specification, it's added implicitly)"""
863
864 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +0100865 self.silent = False
Reilly Grant1d7367d2009-09-10 00:02:38 -0700866 self.createdBranches = set()
867 self.committedChanges = set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +0100868 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +0100869 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +0200870 self.detectLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100871 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +0200872 self.syncWithOrigin = True
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200873 self.verbose = False
Simon Hausmanna028a982007-05-23 00:03:08 +0200874 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +0200875 self.maxChanges = ""
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +0200876 self.isWindows = (platform.system() == "Windows")
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300877 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300878 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +0200879 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -0800880 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100881 self.useClientSpec = False
882 self.clientSpecDirs = []
Simon Hausmannb9847332007-03-20 20:54:23 +0100883
Simon Hausmann01265102007-05-25 10:36:10 +0200884 if gitConfig("git-p4.syncFromOrigin") == "false":
885 self.syncWithOrigin = False
886
Simon Hausmannb9847332007-03-20 20:54:23 +0100887 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -0800888 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
889 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +0100890 files = []
891 fnum = 0
892 while commit.has_key("depotFile%s" % fnum):
893 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300894
Tommy Thorn354081d2008-02-03 10:38:51 -0800895 if [p for p in self.cloneExclude
896 if path.startswith (p)]:
897 found = False
898 else:
899 found = [p for p in self.depotPaths
900 if path.startswith (p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300901 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +0100902 fnum = fnum + 1
903 continue
904
905 file = {}
906 file["path"] = path
907 file["rev"] = commit["rev%s" % fnum]
908 file["action"] = commit["action%s" % fnum]
909 file["type"] = commit["type%s" % fnum]
910 files.append(file)
911 fnum = fnum + 1
912 return files
913
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300914 def stripRepoPath(self, path, prefixes):
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300915 if self.keepRepoPath:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300916 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300917
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300918 for p in prefixes:
919 if path.startswith(p):
920 path = path[len(p):]
921
922 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300923
Simon Hausmann71b112d2007-05-19 11:54:11 +0200924 def splitFilesIntoBranches(self, commit):
Simon Hausmannd5904672007-05-19 11:07:32 +0200925 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +0200926 fnum = 0
927 while commit.has_key("depotFile%s" % fnum):
928 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300929 found = [p for p in self.depotPaths
930 if path.startswith (p)]
931 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +0200932 fnum = fnum + 1
933 continue
934
935 file = {}
936 file["path"] = path
937 file["rev"] = commit["rev%s" % fnum]
938 file["action"] = commit["action%s" % fnum]
939 file["type"] = commit["type%s" % fnum]
940 fnum = fnum + 1
941
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300942 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +0100943
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200944 for branch in self.knownBranches.keys():
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300945
946 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
947 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +0200948 if branch not in branches:
949 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +0200950 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +0200951 break
Simon Hausmannb9847332007-03-20 20:54:23 +0100952
953 return branches
954
Luke Diamandb9327052009-07-30 00:13:46 +0100955 # output one file from the P4 stream
956 # - helper for streamP4Files
957
958 def streamOneP4File(self, file, contents):
959 if file["type"] == "apple":
960 print "\nfile %s is a strange apple file that forks. Ignoring" % \
961 file['depotFile']
962 return
963
964 relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
965 if verbose:
966 sys.stderr.write("%s\n" % relPath)
967
968 mode = "644"
969 if isP4Exec(file["type"]):
970 mode = "755"
971 elif file["type"] == "symlink":
972 mode = "120000"
973 # p4 print on a symlink contains "target\n", so strip it off
Evan Powersb39c3612010-02-16 00:44:08 -0800974 data = ''.join(contents)
975 contents = [data[:-1]]
Luke Diamandb9327052009-07-30 00:13:46 +0100976
977 if self.isWindows and file["type"].endswith("text"):
978 mangled = []
979 for data in contents:
980 data = data.replace("\r\n", "\n")
981 mangled.append(data)
982 contents = mangled
983
984 if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
985 contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents)
986 elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
987 contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents)
988
989 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
990
991 # total length...
992 length = 0
993 for d in contents:
994 length = length + len(d)
995
996 self.gitStream.write("data %d\n" % length)
997 for d in contents:
998 self.gitStream.write(d)
999 self.gitStream.write("\n")
1000
1001 def streamOneP4Deletion(self, file):
1002 relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
1003 if verbose:
1004 sys.stderr.write("delete %s\n" % relPath)
1005 self.gitStream.write("D %s\n" % relPath)
1006
1007 # handle another chunk of streaming data
1008 def streamP4FilesCb(self, marshalled):
1009
1010 if marshalled.has_key('depotFile') and self.stream_have_file_info:
1011 # start of a new file - output the old one first
1012 self.streamOneP4File(self.stream_file, self.stream_contents)
1013 self.stream_file = {}
1014 self.stream_contents = []
1015 self.stream_have_file_info = False
1016
1017 # pick up the new file information... for the
1018 # 'data' field we need to append to our array
1019 for k in marshalled.keys():
1020 if k == 'data':
1021 self.stream_contents.append(marshalled['data'])
1022 else:
1023 self.stream_file[k] = marshalled[k]
1024
1025 self.stream_have_file_info = True
1026
1027 # Stream directly from "p4 files" into "git fast-import"
1028 def streamP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +01001029 filesForCommit = []
1030 filesToRead = []
Luke Diamandb9327052009-07-30 00:13:46 +01001031 filesToDelete = []
Simon Hausmann30b59402008-03-03 11:55:48 +01001032
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001033 for f in files:
Simon Hausmann30b59402008-03-03 11:55:48 +01001034 includeFile = True
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001035 for val in self.clientSpecDirs:
1036 if f['path'].startswith(val[0]):
Simon Hausmann30b59402008-03-03 11:55:48 +01001037 if val[1] <= 0:
1038 includeFile = False
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001039 break
1040
Simon Hausmann30b59402008-03-03 11:55:48 +01001041 if includeFile:
1042 filesForCommit.append(f)
Pete Wyckoff56c09342011-02-19 08:17:57 -05001043 if f['action'] in self.delete_actions:
Luke Diamandb9327052009-07-30 00:13:46 +01001044 filesToDelete.append(f)
Pete Wyckoff56c09342011-02-19 08:17:57 -05001045 else:
1046 filesToRead.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001047
Luke Diamandb9327052009-07-30 00:13:46 +01001048 # deleted files...
1049 for f in filesToDelete:
1050 self.streamOneP4Deletion(f)
1051
Simon Hausmann30b59402008-03-03 11:55:48 +01001052 if len(filesToRead) > 0:
Luke Diamandb9327052009-07-30 00:13:46 +01001053 self.stream_file = {}
1054 self.stream_contents = []
1055 self.stream_have_file_info = False
1056
1057 # curry self argument
1058 def streamP4FilesCbSelf(entry):
1059 self.streamP4FilesCb(entry)
1060
1061 p4CmdList("-x - print",
1062 '\n'.join(['%s#%s' % (f['path'], f['rev'])
Simon Hausmann30b59402008-03-03 11:55:48 +01001063 for f in filesToRead]),
Luke Diamandb9327052009-07-30 00:13:46 +01001064 cb=streamP4FilesCbSelf)
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -03001065
Luke Diamandb9327052009-07-30 00:13:46 +01001066 # do the last chunk
1067 if self.stream_file.has_key('depotFile'):
1068 self.streamOneP4File(self.stream_file, self.stream_contents)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001069
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001070 def commit(self, details, files, branch, branchPrefixes, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +01001071 epoch = details["time"]
1072 author = details["user"]
Luke Diamandb9327052009-07-30 00:13:46 +01001073 self.branchPrefixes = branchPrefixes
Simon Hausmannb9847332007-03-20 20:54:23 +01001074
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001075 if self.verbose:
1076 print "commit into %s" % branch
1077
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03001078 # start with reading files; if that fails, we should not
1079 # create a commit.
1080 new_files = []
1081 for f in files:
1082 if [p for p in branchPrefixes if f['path'].startswith(p)]:
1083 new_files.append (f)
1084 else:
1085 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03001086
Simon Hausmannb9847332007-03-20 20:54:23 +01001087 self.gitStream.write("commit %s\n" % branch)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001088# gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +01001089 self.committedChanges.add(int(details["change"]))
1090 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +02001091 if author not in self.users:
1092 self.getUserMapFromPerforceServer()
Simon Hausmannb9847332007-03-20 20:54:23 +01001093 if author in self.users:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001094 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01001095 else:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001096 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01001097
1098 self.gitStream.write("committer %s\n" % committer)
1099
1100 self.gitStream.write("data <<EOT\n")
1101 self.gitStream.write(details["desc"])
Simon Hausmann6581de02007-06-11 10:01:58 +02001102 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1103 % (','.join (branchPrefixes), details["change"]))
1104 if len(details['options']) > 0:
1105 self.gitStream.write(": options = %s" % details['options'])
1106 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001107
1108 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001109 if self.verbose:
1110 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +01001111 self.gitStream.write("from %s\n" % parent)
1112
Luke Diamandb9327052009-07-30 00:13:46 +01001113 self.streamP4Files(new_files)
Simon Hausmannb9847332007-03-20 20:54:23 +01001114 self.gitStream.write("\n")
1115
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001116 change = int(details["change"])
1117
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001118 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001119 label = self.labels[change]
1120 labelDetails = label[0]
1121 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02001122 if self.verbose:
1123 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001124
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001125 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1126 for p in branchPrefixes]))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001127
1128 if len(files) == len(labelRevisions):
1129
1130 cleanedFiles = {}
1131 for info in files:
Pete Wyckoff56c09342011-02-19 08:17:57 -05001132 if info["action"] in self.delete_actions:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001133 continue
1134 cleanedFiles[info["depotFile"]] = info["rev"]
1135
1136 if cleanedFiles == labelRevisions:
1137 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1138 self.gitStream.write("from %s\n" % branch)
1139
1140 owner = labelDetails["Owner"]
1141 tagger = ""
1142 if author in self.users:
1143 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1144 else:
1145 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1146 self.gitStream.write("tagger %s\n" % tagger)
1147 self.gitStream.write("data <<EOT\n")
1148 self.gitStream.write(labelDetails["Description"])
1149 self.gitStream.write("EOT\n\n")
1150
1151 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001152 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001153 print ("Tag %s does not match with change %s: files do not match."
1154 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001155
1156 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001157 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001158 print ("Tag %s does not match with change %s: file count is different."
1159 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01001160
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001161 def getUserCacheFilename(self):
Simon Hausmannb2d2d162007-07-25 09:31:38 +02001162 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1163 return home + "/.gitp4-usercache.txt"
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001164
Simon Hausmannb607e712007-05-20 10:55:54 +02001165 def getUserMapFromPerforceServer(self):
Simon Hausmannebd81162007-05-24 00:24:52 +02001166 if self.userMapFromPerforceServer:
1167 return
Simon Hausmannb9847332007-03-20 20:54:23 +01001168 self.users = {}
1169
1170 for output in p4CmdList("users"):
1171 if not output.has_key("User"):
1172 continue
1173 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1174
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001175
1176 s = ''
1177 for (key, val) in self.users.items():
Pete Wyckoff3b167392009-02-27 10:53:59 -08001178 s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001179
1180 open(self.getUserCacheFilename(), "wb").write(s)
Simon Hausmannebd81162007-05-24 00:24:52 +02001181 self.userMapFromPerforceServer = True
Simon Hausmannb607e712007-05-20 10:55:54 +02001182
1183 def loadUserMapFromCache(self):
1184 self.users = {}
Simon Hausmannebd81162007-05-24 00:24:52 +02001185 self.userMapFromPerforceServer = False
Simon Hausmannb607e712007-05-20 10:55:54 +02001186 try:
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001187 cache = open(self.getUserCacheFilename(), "rb")
Simon Hausmannb607e712007-05-20 10:55:54 +02001188 lines = cache.readlines()
1189 cache.close()
1190 for line in lines:
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001191 entry = line.strip().split("\t")
Simon Hausmannb607e712007-05-20 10:55:54 +02001192 self.users[entry[0]] = entry[1]
1193 except IOError:
1194 self.getUserMapFromPerforceServer()
1195
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001196 def getLabels(self):
1197 self.labels = {}
1198
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001199 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
Simon Hausmann10c32112007-04-08 10:15:47 +02001200 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08001201 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001202
1203 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001204 label = output["label"]
1205 revisions = {}
1206 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02001207 if self.verbose:
1208 print "Querying files for label %s" % label
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001209 for file in p4CmdList("files "
1210 + ' '.join (["%s...@%s" % (p, label)
1211 for p in self.depotPaths])):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001212 revisions[file["depotFile"]] = file["rev"]
1213 change = int(file["change"])
1214 if change > newestChange:
1215 newestChange = change
1216
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001217 self.labels[newestChange] = [output, revisions]
1218
1219 if self.verbose:
1220 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001221
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001222 def guessProjectName(self):
1223 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02001224 if p.endswith("/"):
1225 p = p[:-1]
1226 p = p[p.strip().rfind("/") + 1:]
1227 if not p.endswith("/"):
1228 p += "/"
1229 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001230
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001231 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001232 lostAndFoundBranches = set()
1233
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001234 for info in p4CmdList("branches"):
1235 details = p4Cmd("branch -o %s" % info["branch"])
1236 viewIdx = 0
1237 while details.has_key("View%s" % viewIdx):
1238 paths = details["View%s" % viewIdx].split(" ")
1239 viewIdx = viewIdx + 1
1240 # require standard //depot/foo/... //depot/bar/... mapping
1241 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1242 continue
1243 source = paths[0]
1244 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02001245 ## HACK
1246 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1247 source = source[len(self.depotPaths[0]):-4]
1248 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001249
Simon Hausmann1a2edf42007-06-17 15:10:24 +02001250 if destination in self.knownBranches:
1251 if not self.silent:
1252 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1253 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1254 continue
1255
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001256 self.knownBranches[destination] = source
1257
1258 lostAndFoundBranches.discard(destination)
1259
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001260 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001261 lostAndFoundBranches.add(source)
1262
1263
1264 for branch in lostAndFoundBranches:
1265 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001266
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001267 def getBranchMappingFromGitBranches(self):
1268 branches = p4BranchesInGit(self.importIntoRemotes)
1269 for branch in branches.keys():
1270 if branch == "master":
1271 branch = "main"
1272 else:
1273 branch = branch[len(self.projectName):]
1274 self.knownBranches[branch] = branch
1275
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001276 def listExistingP4GitBranches(self):
Simon Hausmann144ff462007-07-18 17:27:50 +02001277 # branches holds mapping from name to commit
1278 branches = p4BranchesInGit(self.importIntoRemotes)
1279 self.p4BranchesInGit = branches.keys()
1280 for branch in branches.keys():
1281 self.initialParents[self.refPrefix + branch] = branches[branch]
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001282
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001283 def updateOptionDict(self, d):
1284 option_keys = {}
1285 if self.keepRepoPath:
1286 option_keys['keepRepoPath'] = 1
1287
1288 d["options"] = ' '.join(sorted(option_keys.keys()))
1289
1290 def readOptions(self, d):
1291 self.keepRepoPath = (d.has_key('options')
1292 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001293
Simon Hausmann8134f692007-08-26 16:44:55 +02001294 def gitRefForBranch(self, branch):
1295 if branch == "main":
1296 return self.refPrefix + "master"
1297
1298 if len(branch) <= 0:
1299 return branch
1300
1301 return self.refPrefix + self.projectName + branch
1302
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001303 def gitCommitByP4Change(self, ref, change):
1304 if self.verbose:
1305 print "looking in ref " + ref + " for change %s using bisect..." % change
1306
1307 earliestCommit = ""
1308 latestCommit = parseRevision(ref)
1309
1310 while True:
1311 if self.verbose:
1312 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1313 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1314 if len(next) == 0:
1315 if self.verbose:
1316 print "argh"
1317 return ""
1318 log = extractLogMessageFromGitCommit(next)
1319 settings = extractSettingsGitLog(log)
1320 currentChange = int(settings['change'])
1321 if self.verbose:
1322 print "current change %s" % currentChange
1323
1324 if currentChange == change:
1325 if self.verbose:
1326 print "found %s" % next
1327 return next
1328
1329 if currentChange < change:
1330 earliestCommit = "^%s" % next
1331 else:
1332 latestCommit = "%s" % next
1333
1334 return ""
1335
1336 def importNewBranch(self, branch, maxChange):
1337 # make fast-import flush all changes to disk and update the refs using the checkpoint
1338 # command so that we can try to find the branch parent in the git history
1339 self.gitStream.write("checkpoint\n\n");
1340 self.gitStream.flush();
1341 branchPrefix = self.depotPaths[0] + branch + "/"
1342 range = "@1,%s" % maxChange
1343 #print "prefix" + branchPrefix
1344 changes = p4ChangesForPaths([branchPrefix], range)
1345 if len(changes) <= 0:
1346 return False
1347 firstChange = changes[0]
1348 #print "first change in branch: %s" % firstChange
1349 sourceBranch = self.knownBranches[branch]
1350 sourceDepotPath = self.depotPaths[0] + sourceBranch
1351 sourceRef = self.gitRefForBranch(sourceBranch)
1352 #print "source " + sourceBranch
1353
1354 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1355 #print "branch parent: %s" % branchParentChange
1356 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1357 if len(gitParent) > 0:
1358 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1359 #print "parent git commit: %s" % gitParent
1360
1361 self.importChanges(changes)
1362 return True
1363
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001364 def importChanges(self, changes):
1365 cnt = 1
1366 for change in changes:
1367 description = p4Cmd("describe %s" % change)
1368 self.updateOptionDict(description)
1369
1370 if not self.silent:
1371 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1372 sys.stdout.flush()
1373 cnt = cnt + 1
1374
1375 try:
1376 if self.detectBranches:
1377 branches = self.splitFilesIntoBranches(description)
1378 for branch in branches.keys():
1379 ## HACK --hwn
1380 branchPrefix = self.depotPaths[0] + branch + "/"
1381
1382 parent = ""
1383
1384 filesForCommit = branches[branch]
1385
1386 if self.verbose:
1387 print "branch is %s" % branch
1388
1389 self.updatedBranches.add(branch)
1390
1391 if branch not in self.createdBranches:
1392 self.createdBranches.add(branch)
1393 parent = self.knownBranches[branch]
1394 if parent == branch:
1395 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001396 else:
1397 fullBranch = self.projectName + branch
1398 if fullBranch not in self.p4BranchesInGit:
1399 if not self.silent:
1400 print("\n Importing new branch %s" % fullBranch);
1401 if self.importNewBranch(branch, change - 1):
1402 parent = ""
1403 self.p4BranchesInGit.append(fullBranch)
1404 if not self.silent:
1405 print("\n Resuming with change %s" % change);
1406
1407 if self.verbose:
1408 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001409
Simon Hausmann8134f692007-08-26 16:44:55 +02001410 branch = self.gitRefForBranch(branch)
1411 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001412
1413 if self.verbose:
1414 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1415
1416 if len(parent) == 0 and branch in self.initialParents:
1417 parent = self.initialParents[branch]
1418 del self.initialParents[branch]
1419
1420 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1421 else:
1422 files = self.extractFilesFromCommit(description)
1423 self.commit(description, files, self.branch, self.depotPaths,
1424 self.initialParent)
1425 self.initialParent = ""
1426 except IOError:
1427 print self.gitError.read()
1428 sys.exit(1)
1429
Simon Hausmannc208a242007-08-26 16:07:18 +02001430 def importHeadRevision(self, revision):
1431 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1432
1433 details = { "user" : "git perforce import user", "time" : int(time.time()) }
Pete Wyckoff1494fcb2011-02-19 08:17:56 -05001434 details["desc"] = ("Initial import of %s from the state at revision %s\n"
Simon Hausmannc208a242007-08-26 16:07:18 +02001435 % (' '.join(self.depotPaths), revision))
1436 details["change"] = revision
1437 newestRevision = 0
1438
1439 fileCnt = 0
1440 for info in p4CmdList("files "
1441 + ' '.join(["%s...%s"
1442 % (p, revision)
1443 for p in self.depotPaths])):
1444
Pete Wyckoff68b28592011-02-19 08:17:55 -05001445 if 'code' in info and info['code'] == 'error':
Simon Hausmannc208a242007-08-26 16:07:18 +02001446 sys.stderr.write("p4 returned an error: %s\n"
1447 % info['data'])
Pete Wyckoffd88e7072011-02-19 08:17:58 -05001448 if info['data'].find("must refer to client") >= 0:
1449 sys.stderr.write("This particular p4 error is misleading.\n")
1450 sys.stderr.write("Perhaps the depot path was misspelled.\n");
1451 sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
Simon Hausmannc208a242007-08-26 16:07:18 +02001452 sys.exit(1)
Pete Wyckoff68b28592011-02-19 08:17:55 -05001453 if 'p4ExitCode' in info:
1454 sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
1455 sys.exit(1)
Simon Hausmannc208a242007-08-26 16:07:18 +02001456
1457
1458 change = int(info["change"])
1459 if change > newestRevision:
1460 newestRevision = change
1461
Pete Wyckoff56c09342011-02-19 08:17:57 -05001462 if info["action"] in self.delete_actions:
Simon Hausmannc208a242007-08-26 16:07:18 +02001463 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1464 #fileCnt = fileCnt + 1
1465 continue
1466
1467 for prop in ["depotFile", "rev", "action", "type" ]:
1468 details["%s%s" % (prop, fileCnt)] = info[prop]
1469
1470 fileCnt = fileCnt + 1
1471
1472 details["change"] = newestRevision
1473 self.updateOptionDict(details)
1474 try:
1475 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1476 except IOError:
1477 print "IO error with git fast-import. Is your git version recent enough?"
1478 print self.gitError.read()
1479
1480
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001481 def getClientSpec(self):
1482 specList = p4CmdList( "client -o" )
1483 temp = {}
1484 for entry in specList:
1485 for k,v in entry.iteritems():
1486 if k.startswith("View"):
1487 if v.startswith('"'):
1488 start = 1
1489 else:
1490 start = 0
1491 index = v.find("...")
1492 v = v[start:index]
1493 if v.startswith("-"):
1494 v = v[1:]
1495 temp[v] = -len(v)
1496 else:
1497 temp[v] = len(v)
1498 self.clientSpecDirs = temp.items()
1499 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1500
Simon Hausmannb9847332007-03-20 20:54:23 +01001501 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001502 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01001503 self.changeRange = ""
1504 self.initialParent = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001505 self.previousDepotPaths = []
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03001506
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001507 # map from branch depot path to parent branch
1508 self.knownBranches = {}
1509 self.initialParents = {}
Simon Hausmann5ca44612007-08-24 17:44:16 +02001510 self.hasOrigin = originP4BranchesExist()
Simon Hausmanna43ff002007-06-11 09:59:27 +02001511 if not self.syncWithOrigin:
1512 self.hasOrigin = False
Simon Hausmann179caeb2007-03-22 22:17:42 +01001513
Simon Hausmanna028a982007-05-23 00:03:08 +02001514 if self.importIntoRemotes:
1515 self.refPrefix = "refs/remotes/p4/"
1516 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001517 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02001518
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001519 if self.syncWithOrigin and self.hasOrigin:
1520 if not self.silent:
1521 print "Syncing with origin first by calling git fetch origin"
1522 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02001523
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001524 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001525 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02001526 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001527 system("git update-ref %s refs/heads/p4" % self.branch)
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001528 system("git branch -D p4");
Simon Hausmannfaf1bd22007-05-21 10:05:30 +02001529 # create it /after/ importing, when master exists
Simon Hausmann0058a332007-08-24 17:46:16 +02001530 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
Simon Hausmanna3c55c02007-05-27 15:48:01 +02001531 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
Simon Hausmann179caeb2007-03-22 22:17:42 +01001532
Anand Kumria3cafb7d2008-08-10 19:26:32 +01001533 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001534 self.getClientSpec()
1535
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001536 # TODO: should always look at previous commits,
1537 # merge with previous imports, if possible.
1538 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02001539 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02001540 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Simon Hausmannabcd7902007-05-24 22:25:36 +02001541 self.listExistingP4GitBranches()
1542
1543 if len(self.p4BranchesInGit) > 1:
1544 if not self.silent:
1545 print "Importing from/into multiple branches"
1546 self.detectBranches = True
Simon Hausmann967f72e2007-03-23 09:30:41 +01001547
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001548 if self.verbose:
1549 print "branches: %s" % self.p4BranchesInGit
1550
1551 p4Change = 0
1552 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001553 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001554
1555 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001556
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001557 self.readOptions(settings)
1558 if (settings.has_key('depot-paths')
1559 and settings.has_key ('change')):
1560 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001561 p4Change = max(p4Change, change)
1562
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001563 depotPaths = sorted(settings['depot-paths'])
1564 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001565 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001566 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001567 paths = []
1568 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Simon Hausmann583e1702007-06-07 09:37:13 +02001569 for i in range(0, min(len(cur), len(prev))):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001570 if cur[i] <> prev[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02001571 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001572 break
1573
Simon Hausmann583e1702007-06-07 09:37:13 +02001574 paths.append (cur[:i + 1])
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001575
1576 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001577
1578 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001579 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02001580 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann330f53b2007-06-07 09:39:51 +02001581 if not self.detectBranches:
1582 self.initialParent = parseRevision(self.branch)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001583 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01001584 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001585
Simon Hausmannf9162f62007-05-17 09:02:45 +02001586 if not self.branch.startswith("refs/"):
1587 self.branch = "refs/heads/" + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01001588
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001589 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01001590 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001591 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01001592 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001593 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001594 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001595 "This doesn't work!" % (' '.join (self.depotPaths),
1596 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01001597 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001598
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001599 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01001600
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001601 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01001602 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01001603
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001604 newPaths = []
1605 for p in self.depotPaths:
1606 if p.find("@") != -1:
1607 atIdx = p.index("@")
1608 self.changeRange = p[atIdx:]
1609 if self.changeRange == "@all":
1610 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001611 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001612 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001613 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001614 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001615 elif p.find("#") != -1:
1616 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001617 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001618 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001619 elif self.previousDepotPaths == []:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001620 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01001621
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001622 p = re.sub ("\.\.\.$", "", p)
1623 if not p.endswith("/"):
1624 p += "/"
1625
1626 newPaths.append(p)
1627
1628 self.depotPaths = newPaths
1629
Simon Hausmannb9847332007-03-20 20:54:23 +01001630
Simon Hausmannb607e712007-05-20 10:55:54 +02001631 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02001632 self.labels = {}
1633 if self.detectLabels:
1634 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01001635
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001636 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02001637 ## FIXME - what's a P4 projectName ?
1638 self.projectName = self.guessProjectName()
1639
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001640 if self.hasOrigin:
1641 self.getBranchMappingFromGitBranches()
1642 else:
1643 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001644 if self.verbose:
1645 print "p4-git branches: %s" % self.p4BranchesInGit
1646 print "initial parents: %s" % self.initialParents
1647 for b in self.p4BranchesInGit:
1648 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001649
1650 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001651 b = b[len(self.projectName):]
1652 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001653
Simon Hausmannf291b4e2007-04-14 11:21:50 +02001654 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01001655
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001656 importProcess = subprocess.Popen(["git", "fast-import"],
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001657 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1658 stderr=subprocess.PIPE);
Simon Hausmann08483582007-05-15 14:31:06 +02001659 self.gitOutput = importProcess.stdout
1660 self.gitStream = importProcess.stdin
1661 self.gitError = importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01001662
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001663 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02001664 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01001665 else:
1666 changes = []
1667
Simon Hausmann0828ab12007-03-20 20:59:30 +01001668 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01001669 output = open(self.changesFile).readlines()
Reilly Grant1d7367d2009-09-10 00:02:38 -07001670 changeSet = set()
Simon Hausmannb9847332007-03-20 20:54:23 +01001671 for line in output:
1672 changeSet.add(int(line))
1673
1674 for change in changeSet:
1675 changes.append(change)
1676
1677 changes.sort()
1678 else:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001679 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001680 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001681 self.changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001682 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
Simon Hausmannb9847332007-03-20 20:54:23 +01001683
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001684 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001685 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001686
Simon Hausmannb9847332007-03-20 20:54:23 +01001687 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001688 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001689 print "No changes to import!"
Simon Hausmann1f52af62007-04-08 00:07:02 +02001690 return True
Simon Hausmannb9847332007-03-20 20:54:23 +01001691
Simon Hausmanna9d1a272007-06-11 23:28:03 +02001692 if not self.silent and not self.detectBranches:
1693 print "Import destination: %s" % self.branch
1694
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001695 self.updatedBranches = set()
1696
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001697 self.importChanges(changes)
Simon Hausmannb9847332007-03-20 20:54:23 +01001698
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001699 if not self.silent:
1700 print ""
1701 if len(self.updatedBranches) > 0:
1702 sys.stdout.write("Updated branches: ")
1703 for b in self.updatedBranches:
1704 sys.stdout.write("%s " % b)
1705 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001706
Simon Hausmannb9847332007-03-20 20:54:23 +01001707 self.gitStream.close()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001708 if importProcess.wait() != 0:
1709 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01001710 self.gitOutput.close()
1711 self.gitError.close()
1712
Simon Hausmannb9847332007-03-20 20:54:23 +01001713 return True
1714
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001715class P4Rebase(Command):
1716 def __init__(self):
1717 Command.__init__(self)
Simon Hausmann01265102007-05-25 10:36:10 +02001718 self.options = [ ]
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001719 self.description = ("Fetches the latest revision from perforce and "
1720 + "rebases the current work (branch) against it")
Simon Hausmann68c42152007-06-07 12:51:03 +02001721 self.verbose = False
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001722
1723 def run(self, args):
1724 sync = P4Sync()
1725 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02001726
Simon Hausmann14594f42007-08-22 09:07:15 +02001727 return self.rebase()
1728
1729 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01001730 if os.system("git update-index --refresh") != 0:
1731 die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
1732 if len(read_pipe("git diff-index HEAD --")) > 0:
1733 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1734
Simon Hausmannd7e38682007-06-12 14:34:46 +02001735 [upstream, settings] = findUpstreamBranchPoint()
1736 if len(upstream) == 0:
1737 die("Cannot find upstream branchpoint for rebase")
1738
1739 # the branchpoint may be p4/foo~3, so strip off the parent
1740 upstream = re.sub("~[0-9]+$", "", upstream)
1741
1742 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001743 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02001744 system("git rebase %s" % upstream)
Simon Hausmann1f52af62007-04-08 00:07:02 +02001745 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001746 return True
1747
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001748class P4Clone(P4Sync):
1749 def __init__(self):
1750 P4Sync.__init__(self)
1751 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001752 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08001753 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001754 optparse.make_option("--destination", dest="cloneDestination",
1755 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08001756 help="where to leave result of the clone"),
1757 optparse.make_option("-/", dest="cloneExclude",
1758 action="append", type="string",
1759 help="exclude depot path")
1760 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001761 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001762 self.needsGit = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001763
Tommy Thorn354081d2008-02-03 10:38:51 -08001764 # This is required for the "append" cloneExclude action
1765 def ensure_value(self, attr, value):
1766 if not hasattr(self, attr) or getattr(self, attr) is None:
1767 setattr(self, attr, value)
1768 return getattr(self, attr)
1769
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001770 def defaultDestination(self, args):
1771 ## TODO: use common prefix of args?
1772 depotPath = args[0]
1773 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1774 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13001775 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001776 depotDir = re.sub(r"/$", "", depotDir)
1777 return os.path.split(depotDir)[1]
1778
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001779 def run(self, args):
1780 if len(args) < 1:
1781 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001782
1783 if self.keepRepoPath and not self.cloneDestination:
1784 sys.stderr.write("Must specify destination for --keep-path\n")
1785 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001786
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001787 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02001788
1789 if not self.cloneDestination and len(depotPaths) > 1:
1790 self.cloneDestination = depotPaths[-1]
1791 depotPaths = depotPaths[:-1]
1792
Tommy Thorn354081d2008-02-03 10:38:51 -08001793 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001794 for p in depotPaths:
1795 if not p.startswith("//"):
1796 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001797
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001798 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02001799 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001800
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001801 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Kevin Greenc3bf3f12007-06-11 16:48:07 -04001802 if not os.path.exists(self.cloneDestination):
1803 os.makedirs(self.cloneDestination)
Robert Blum053fd0c2008-08-01 12:50:03 -07001804 chdir(self.cloneDestination)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001805 system("git init")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001806 self.gitdir = os.getcwd() + "/.git"
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001807 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001808 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001809 if self.branch != "master":
Tor Arvid Lunde9905012008-08-28 00:36:12 +02001810 if self.importIntoRemotes:
1811 masterbranch = "refs/remotes/p4/master"
1812 else:
1813 masterbranch = "refs/heads/p4/master"
1814 if gitBranchExists(masterbranch):
1815 system("git branch master %s" % masterbranch)
Simon Hausmann8f9b2e02007-05-18 22:13:26 +02001816 system("git checkout -f")
1817 else:
1818 print "Could not detect main branch. No checkout/master branch created."
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001819
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001820 return True
1821
Simon Hausmann09d89de2007-06-20 23:10:28 +02001822class P4Branches(Command):
1823 def __init__(self):
1824 Command.__init__(self)
1825 self.options = [ ]
1826 self.description = ("Shows the git branches that hold imports and their "
1827 + "corresponding perforce depot paths")
1828 self.verbose = False
1829
1830 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02001831 if originP4BranchesExist():
1832 createOrUpdateBranchesFromOrigin()
1833
Simon Hausmann09d89de2007-06-20 23:10:28 +02001834 cmdline = "git rev-parse --symbolic "
1835 cmdline += " --remotes"
1836
1837 for line in read_pipe_lines(cmdline):
1838 line = line.strip()
1839
1840 if not line.startswith('p4/') or line == "p4/HEAD":
1841 continue
1842 branch = line
1843
1844 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1845 settings = extractSettingsGitLog(log)
1846
1847 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1848 return True
1849
Simon Hausmannb9847332007-03-20 20:54:23 +01001850class HelpFormatter(optparse.IndentedHelpFormatter):
1851 def __init__(self):
1852 optparse.IndentedHelpFormatter.__init__(self)
1853
1854 def format_description(self, description):
1855 if description:
1856 return description + "\n"
1857 else:
1858 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001859
Simon Hausmann86949ee2007-03-19 20:59:12 +01001860def printUsage(commands):
1861 print "usage: %s <command> [options]" % sys.argv[0]
1862 print ""
1863 print "valid commands: %s" % ", ".join(commands)
1864 print ""
1865 print "Try %s <command> --help for command specific help." % sys.argv[0]
1866 print ""
1867
1868commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001869 "debug" : P4Debug,
1870 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02001871 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001872 "sync" : P4Sync,
1873 "rebase" : P4Rebase,
1874 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02001875 "rollback" : P4RollBack,
1876 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01001877}
1878
Simon Hausmann86949ee2007-03-19 20:59:12 +01001879
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001880def main():
1881 if len(sys.argv[1:]) == 0:
1882 printUsage(commands.keys())
1883 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001884
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001885 cmd = ""
1886 cmdName = sys.argv[1]
1887 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001888 klass = commands[cmdName]
1889 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001890 except KeyError:
1891 print "unknown command %s" % cmdName
1892 print ""
1893 printUsage(commands.keys())
1894 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001895
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001896 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001897 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001898
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001899 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001900
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001901 if len(options) > 0:
1902 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001903
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001904 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1905 options,
1906 description = cmd.description,
1907 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01001908
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001909 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1910 global verbose
1911 verbose = cmd.verbose
1912 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001913 if cmd.gitdir == None:
1914 cmd.gitdir = os.path.abspath(".git")
1915 if not isValidGitDir(cmd.gitdir):
1916 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1917 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001918 cdup = read_pipe("git rev-parse --show-cdup").strip()
1919 if len(cdup) > 0:
Robert Blum053fd0c2008-08-01 12:50:03 -07001920 chdir(cdup);
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001921
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001922 if not isValidGitDir(cmd.gitdir):
1923 if isValidGitDir(cmd.gitdir + "/.git"):
1924 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001925 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001926 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02001927
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001928 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001929
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001930 if not cmd.run(args):
1931 parser.print_help()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001932
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001933
1934if __name__ == '__main__':
1935 main()