blob: 3e9df70f29dcfa16ab263d5ace42d8d6acf9aecd [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
Simon Hausmann08483582007-05-15 14:31:06 +020011import optparse, sys, os, marshal, popen2, subprocess, shelve
Simon Hausmann25df95c2007-05-15 15:15:39 +020012import tempfile, getopt, sha, os.path, time, platform
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -030013import re
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030014
Simon Hausmannb9847332007-03-20 20:54:23 +010015from sets import Set;
Simon Hausmann4f5cf762007-03-19 22:25:17 +010016
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030017verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +010018
Anand Kumria21a50752008-08-10 19:26:28 +010019
20def p4_build_cmd(cmd):
21 """Build a suitable p4 command line.
22
23 This consolidates building and returning a p4 command line into one
24 location. It means that hooking into the environment, or other configuration
25 can be done more easily.
26 """
Anand Kumriaabcaf072008-08-10 19:26:31 +010027 real_cmd = "%s " % "p4"
28
29 user = gitConfig("git-p4.user")
30 if len(user) > 0:
31 real_cmd += "-u %s " % user
32
33 password = gitConfig("git-p4.password")
34 if len(password) > 0:
35 real_cmd += "-P %s " % password
36
37 port = gitConfig("git-p4.port")
38 if len(port) > 0:
39 real_cmd += "-p %s " % port
40
41 host = gitConfig("git-p4.host")
42 if len(host) > 0:
43 real_cmd += "-h %s " % host
44
45 client = gitConfig("git-p4.client")
46 if len(client) > 0:
47 real_cmd += "-c %s " % client
48
49 real_cmd += "%s" % (cmd)
Anand Kumriaee064272008-08-10 19:26:29 +010050 if verbose:
51 print real_cmd
Anand Kumria21a50752008-08-10 19:26:28 +010052 return real_cmd
53
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030054def die(msg):
55 if verbose:
56 raise Exception(msg)
57 else:
58 sys.stderr.write(msg + "\n")
59 sys.exit(1)
60
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030061def write_pipe(c, str):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030062 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030063 sys.stderr.write('Writing pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030064
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030065 pipe = os.popen(c, 'w')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030066 val = pipe.write(str)
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030067 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030068 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030069
70 return val
71
Anand Kumriad9429192008-08-14 23:40:38 +010072def p4_write_pipe(c, str):
73 real_cmd = p4_build_cmd(c)
74 return write_pipe(c, str)
75
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030076def read_pipe(c, ignore_error=False):
77 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030078 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030079
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030080 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030081 val = pipe.read()
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030082 if pipe.close() and not ignore_error:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030083 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030084
85 return val
86
Anand Kumriad9429192008-08-14 23:40:38 +010087def p4_read_pipe(c, ignore_error=False):
88 real_cmd = p4_build_cmd(c)
89 return read_pipe(real_cmd, ignore_error)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030090
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030091def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030092 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030093 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030094 ## todo: check return status
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030095 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030096 val = pipe.readlines()
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030097 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030098 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030099
100 return val
Simon Hausmanncaace112007-05-15 14:57:57 +0200101
Anand Kumria23181212008-08-10 19:26:24 +0100102def p4_read_pipe_lines(c):
103 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +0100104 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +0100105 return read_pipe_lines(real_cmd)
106
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300107def system(cmd):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300108 if verbose:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300109 sys.stderr.write("executing %s\n" % cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300110 if os.system(cmd) != 0:
111 die("command failed: %s" % cmd)
112
Anand Kumriabf9320f2008-08-10 19:26:26 +0100113def p4_system(cmd):
114 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +0100115 real_cmd = p4_build_cmd(cmd)
Anand Kumriabf9320f2008-08-10 19:26:26 +0100116 return system(real_cmd)
117
David Brownb9fc6ea2007-09-19 13:12:48 -0700118def isP4Exec(kind):
119 """Determine if a Perforce 'kind' should have execute permission
120
121 'p4 help filetypes' gives a list of the types. If it starts with 'x',
122 or x follows one of a few letters. Otherwise, if there is an 'x' after
123 a plus sign, it is also executable"""
124 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
125
Chris Pettittc65b6702007-11-01 20:43:14 -0700126def setP4ExecBit(file, mode):
127 # Reopens an already open file and changes the execute bit to match
128 # the execute bit setting in the passed in mode.
129
130 p4Type = "+x"
131
132 if not isModeExec(mode):
133 p4Type = getP4OpenedType(file)
134 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
135 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
136 if p4Type[-1] == "+":
137 p4Type = p4Type[0:-1]
138
Anand Kumria87b611d2008-08-10 19:26:27 +0100139 p4_system("reopen -t %s %s" % (p4Type, file))
Chris Pettittc65b6702007-11-01 20:43:14 -0700140
141def getP4OpenedType(file):
142 # Returns the perforce file type for the given file.
143
144 result = read_pipe("p4 opened %s" % file)
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100145 match = re.match(".*\((.+)\)\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700146 if match:
147 return match.group(1)
148 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100149 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700150
Chris Pettittb43b0a32007-11-01 20:43:13 -0700151def diffTreePattern():
152 # This is a simple generator for the diff tree regex pattern. This could be
153 # a class variable if this and parseDiffTreeEntry were a part of a class.
154 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
155 while True:
156 yield pattern
157
158def parseDiffTreeEntry(entry):
159 """Parses a single diff tree entry into its component elements.
160
161 See git-diff-tree(1) manpage for details about the format of the diff
162 output. This method returns a dictionary with the following elements:
163
164 src_mode - The mode of the source file
165 dst_mode - The mode of the destination file
166 src_sha1 - The sha1 for the source file
167 dst_sha1 - The sha1 fr the destination file
168 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
169 status_score - The score for the status (applicable for 'C' and 'R'
170 statuses). This is None if there is no score.
171 src - The path for the source file.
172 dst - The path for the destination file. This is only present for
173 copy or renames. If it is not present, this is None.
174
175 If the pattern is not matched, None is returned."""
176
177 match = diffTreePattern().next().match(entry)
178 if match:
179 return {
180 'src_mode': match.group(1),
181 'dst_mode': match.group(2),
182 'src_sha1': match.group(3),
183 'dst_sha1': match.group(4),
184 'status': match.group(5),
185 'status_score': match.group(6),
186 'src': match.group(7),
187 'dst': match.group(10)
188 }
189 return None
190
Chris Pettittc65b6702007-11-01 20:43:14 -0700191def isModeExec(mode):
192 # Returns True if the given git mode represents an executable file,
193 # otherwise False.
194 return mode[-3:] == "755"
195
196def isModeExecChanged(src_mode, dst_mode):
197 return isModeExec(src_mode) != isModeExec(dst_mode)
198
Scott Lamb9f90c732007-07-15 20:58:10 -0700199def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
Anand Kumria155af832008-08-10 19:26:30 +0100200 cmd = p4_build_cmd("-G %s" % (cmd))
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300201 if verbose:
202 sys.stderr.write("Opening pipe: %s\n" % cmd)
Scott Lamb9f90c732007-07-15 20:58:10 -0700203
204 # Use a temporary file to avoid deadlocks without
205 # subprocess.communicate(), which would put another copy
206 # of stdout into memory.
207 stdin_file = None
208 if stdin is not None:
209 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
210 stdin_file.write(stdin)
211 stdin_file.flush()
212 stdin_file.seek(0)
213
214 p4 = subprocess.Popen(cmd, shell=True,
215 stdin=stdin_file,
216 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100217
218 result = []
219 try:
220 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700221 entry = marshal.load(p4.stdout)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100222 result.append(entry)
223 except EOFError:
224 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700225 exitCode = p4.wait()
226 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200227 entry = {}
228 entry["p4ExitCode"] = exitCode
229 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100230
231 return result
232
233def p4Cmd(cmd):
234 list = p4CmdList(cmd)
235 result = {}
236 for entry in list:
237 result.update(entry)
238 return result;
239
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100240def p4Where(depotPath):
241 if not depotPath.endswith("/"):
242 depotPath += "/"
243 output = p4Cmd("where %s..." % depotPath)
Simon Hausmanndc524032007-05-21 09:34:56 +0200244 if output["code"] == "error":
245 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100246 clientPath = ""
247 if "path" in output:
248 clientPath = output.get("path")
249 elif "data" in output:
250 data = output.get("data")
251 lastSpace = data.rfind(" ")
252 clientPath = data[lastSpace + 1:]
253
254 if clientPath.endswith("..."):
255 clientPath = clientPath[:-3]
256 return clientPath
257
Simon Hausmann86949ee2007-03-19 20:59:12 +0100258def currentGitBranch():
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300259 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100260
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100261def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300262 if (os.path.exists(path + "/HEAD")
263 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100264 return True;
265 return False
266
Simon Hausmann463e8af2007-05-17 09:13:54 +0200267def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300268 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200269
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100270def extractLogMessageFromGitCommit(commit):
271 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300272
273 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100274 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300275 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100276 if not foundTitle:
277 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200278 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100279 continue
280
281 logMessage += log
282 return logMessage
283
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300284def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100285 values = {}
286 for line in log.split("\n"):
287 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300288 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
289 if not m:
290 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100291
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300292 assignments = m.group(1).split (':')
293 for a in assignments:
294 vals = a.split ('=')
295 key = vals[0].strip()
296 val = ('='.join (vals[1:])).strip()
297 if val.endswith ('\"') and val.startswith('"'):
298 val = val[1:-1]
299
300 values[key] = val
301
Simon Hausmann845b42c2007-06-07 09:19:34 +0200302 paths = values.get("depot-paths")
303 if not paths:
304 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200305 if paths:
306 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300307 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100308
Simon Hausmann8136a632007-03-22 21:27:14 +0100309def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300310 proc = subprocess.Popen(["git", "rev-parse", branch],
311 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200312 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100313
Simon Hausmann01265102007-05-25 10:36:10 +0200314def gitConfig(key):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300315 return read_pipe("git config %s" % key, ignore_error=True).strip()
Simon Hausmann01265102007-05-25 10:36:10 +0200316
Simon Hausmann062410b2007-07-18 10:56:31 +0200317def p4BranchesInGit(branchesAreInRemotes = True):
318 branches = {}
319
320 cmdline = "git rev-parse --symbolic "
321 if branchesAreInRemotes:
322 cmdline += " --remotes"
323 else:
324 cmdline += " --branches"
325
326 for line in read_pipe_lines(cmdline):
327 line = line.strip()
328
329 ## only import to p4/
330 if not line.startswith('p4/') or line == "p4/HEAD":
331 continue
332 branch = line
333
334 # strip off p4
335 branch = re.sub ("^p4/", "", line)
336
337 branches[branch] = parseRevision(line)
338 return branches
339
Simon Hausmann9ceab362007-06-22 00:01:57 +0200340def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200341 branches = p4BranchesInGit()
342 # map from depot-path to branch name
343 branchByDepotPath = {}
344 for branch in branches.keys():
345 tip = branches[branch]
346 log = extractLogMessageFromGitCommit(tip)
347 settings = extractSettingsGitLog(log)
348 if settings.has_key("depot-paths"):
349 paths = ",".join(settings["depot-paths"])
350 branchByDepotPath[paths] = "remotes/p4/" + branch
351
Simon Hausmann27d2d812007-06-12 14:31:59 +0200352 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200353 parent = 0
354 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200355 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200356 log = extractLogMessageFromGitCommit(commit)
357 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200358 if settings.has_key("depot-paths"):
359 paths = ",".join(settings["depot-paths"])
360 if branchByDepotPath.has_key(paths):
361 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200362
Simon Hausmann86506fe2007-07-18 12:40:12 +0200363 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200364
Simon Hausmann86506fe2007-07-18 12:40:12 +0200365 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200366
Simon Hausmann5ca44612007-08-24 17:44:16 +0200367def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
368 if not silent:
369 print ("Creating/updating branch(es) in %s based on origin branch(es)"
370 % localRefPrefix)
371
372 originPrefix = "origin/p4/"
373
374 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
375 line = line.strip()
376 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
377 continue
378
379 headName = line[len(originPrefix):]
380 remoteHead = localRefPrefix + headName
381 originHead = line
382
383 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
384 if (not original.has_key('depot-paths')
385 or not original.has_key('change')):
386 continue
387
388 update = False
389 if not gitBranchExists(remoteHead):
390 if verbose:
391 print "creating %s" % remoteHead
392 update = True
393 else:
394 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
395 if settings.has_key('change') > 0:
396 if settings['depot-paths'] == original['depot-paths']:
397 originP4Change = int(original['change'])
398 p4Change = int(settings['change'])
399 if originP4Change > p4Change:
400 print ("%s (%s) is newer than %s (%s). "
401 "Updating p4 branch from origin."
402 % (originHead, originP4Change,
403 remoteHead, p4Change))
404 update = True
405 else:
406 print ("Ignoring: %s was imported from %s while "
407 "%s was imported from %s"
408 % (originHead, ','.join(original['depot-paths']),
409 remoteHead, ','.join(settings['depot-paths'])))
410
411 if update:
412 system("git update-ref %s %s" % (remoteHead, originHead))
413
414def originP4BranchesExist():
415 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
416
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200417def p4ChangesForPaths(depotPaths, changeRange):
418 assert depotPaths
Anand Kumriab340fa42008-08-10 19:26:25 +0100419 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200420 for p in depotPaths]))
421
422 changes = []
423 for line in output:
424 changeNum = line.split(" ")[1]
425 changes.append(int(changeNum))
426
427 changes.sort()
428 return changes
429
Simon Hausmannb9847332007-03-20 20:54:23 +0100430class Command:
431 def __init__(self):
432 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200433 self.needsGit = True
Simon Hausmannb9847332007-03-20 20:54:23 +0100434
435class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100436 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100437 Command.__init__(self)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100438 self.options = [
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300439 optparse.make_option("--verbose", dest="verbose", action="store_true",
440 default=False),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300441 ]
Simon Hausmannc8c39112007-03-19 21:02:30 +0100442 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200443 self.needsGit = False
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300444 self.verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100445
446 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300447 j = 0
Simon Hausmann86949ee2007-03-19 20:59:12 +0100448 for output in p4CmdList(" ".join(args)):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300449 print 'Element: %d' % j
450 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100451 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100452 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100453
Simon Hausmann58346842007-05-21 22:57:06 +0200454class P4RollBack(Command):
455 def __init__(self):
456 Command.__init__(self)
457 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +0200458 optparse.make_option("--verbose", dest="verbose", action="store_true"),
459 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +0200460 ]
461 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann52102d42007-05-21 23:44:24 +0200462 self.verbose = False
Simon Hausmann0c66a782007-05-23 20:07:57 +0200463 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +0200464
465 def run(self, args):
466 if len(args) != 1:
467 return False
468 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +0200469
Simon Hausmannad192f22007-05-23 23:44:19 +0200470 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +0200471 die("Problems executing p4");
472
Simon Hausmann0c66a782007-05-23 20:07:57 +0200473 if self.rollbackLocalBranches:
474 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300475 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200476 else:
477 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300478 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200479
480 for line in lines:
481 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300482 line = line.strip()
483 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +0200484 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300485 settings = extractSettingsGitLog(log)
486
487 depotPaths = settings['depot-paths']
488 change = settings['change']
489
Simon Hausmann58346842007-05-21 22:57:06 +0200490 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +0200491
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300492 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
493 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +0200494 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
495 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
496 continue
497
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300498 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +0200499 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +0200500 if self.verbose:
501 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +0200502 system("git update-ref %s \"%s^\"" % (ref, ref))
503 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300504 settings = extractSettingsGitLog(log)
505
506
507 depotPaths = settings['depot-paths']
508 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +0200509
510 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +0200511 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +0200512
513 return True
514
Simon Hausmann711544b2007-04-01 15:40:46 +0200515class P4Submit(Command):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100516 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +0100517 Command.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100518 self.options = [
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300519 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100520 optparse.make_option("--origin", dest="origin"),
Chris Pettittd9a5f252007-10-15 22:15:06 -0700521 optparse.make_option("-M", dest="detectRename", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100522 ]
523 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200524 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100525 self.interactive = True
Simon Hausmann95124972007-03-23 09:16:07 +0100526 self.origin = ""
Chris Pettittd9a5f252007-10-15 22:15:06 -0700527 self.detectRename = False
Simon Hausmannb0d10df2007-06-07 13:09:14 +0200528 self.verbose = False
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200529 self.isWindows = (platform.system() == "Windows")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100530
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100531 def check(self):
532 if len(p4CmdList("opened ...")) > 0:
533 die("You have files opened with perforce! Close them before starting the sync.")
534
Simon Hausmannedae1e22008-02-19 09:29:06 +0100535 # replaces everything between 'Description:' and the next P4 submit template field with the
536 # commit message
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100537 def prepareLogMessage(self, template, message):
538 result = ""
539
Simon Hausmannedae1e22008-02-19 09:29:06 +0100540 inDescriptionSection = False
541
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100542 for line in template.split("\n"):
543 if line.startswith("#"):
544 result += line + "\n"
545 continue
546
Simon Hausmannedae1e22008-02-19 09:29:06 +0100547 if inDescriptionSection:
548 if line.startswith("Files:"):
549 inDescriptionSection = False
550 else:
551 continue
552 else:
553 if line.startswith("Description:"):
554 inDescriptionSection = True
555 line += "\n"
556 for messageLine in message.split("\n"):
557 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100558
Simon Hausmannedae1e22008-02-19 09:29:06 +0100559 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100560
561 return result
562
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200563 def prepareSubmitTemplate(self):
564 # remove lines in the Files section that show changes to files outside the depot path we're committing into
565 template = ""
566 inFilesSection = False
Anand Kumriab340fa42008-08-10 19:26:25 +0100567 for line in p4_read_pipe_lines("change -o"):
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100568 if line.endswith("\r\n"):
569 line = line[:-2] + "\n"
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200570 if inFilesSection:
571 if line.startswith("\t"):
572 # path starts and ends with a tab
573 path = line[1:]
574 lastTab = path.rfind("\t")
575 if lastTab != -1:
576 path = path[:lastTab]
577 if not path.startswith(self.depotPath):
578 continue
579 else:
580 inFilesSection = False
581 else:
582 if line.startswith("Files:"):
583 inFilesSection = True
584
585 template += line
586
587 return template
588
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300589 def applyCommit(self, id):
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100590 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
591 diffOpts = ("", "-M")[self.detectRename]
592 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100593 filesToAdd = set()
594 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +0200595 editedFiles = set()
Chris Pettittc65b6702007-11-01 20:43:14 -0700596 filesToChangeExecBit = {}
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100597 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -0700598 diff = parseDiffTreeEntry(line)
599 modifier = diff['status']
600 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100601 if modifier == "M":
Anand Kumria87b611d2008-08-10 19:26:27 +0100602 p4_system("edit \"%s\"" % path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700603 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
604 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +0200605 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100606 elif modifier == "A":
607 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700608 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100609 if path in filesToDelete:
610 filesToDelete.remove(path)
611 elif modifier == "D":
612 filesToDelete.add(path)
613 if path in filesToAdd:
614 filesToAdd.remove(path)
Chris Pettittd9a5f252007-10-15 22:15:06 -0700615 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -0700616 src, dest = diff['src'], diff['dst']
Anand Kumria87b611d2008-08-10 19:26:27 +0100617 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
618 p4_system("edit \"%s\"" % (dest))
Chris Pettittc65b6702007-11-01 20:43:14 -0700619 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
620 filesToChangeExecBit[dest] = diff['dst_mode']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700621 os.unlink(dest)
622 editedFiles.add(dest)
623 filesToDelete.add(src)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100624 else:
625 die("unknown modifier %s for %s" % (modifier, path))
626
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100627 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
Simon Hausmann47a130b2007-05-20 16:33:21 +0200628 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200629 tryPatchCmd = patchcmd + "--check -"
630 applyPatchCmd = patchcmd + "--check --apply -"
Simon Hausmann51a26402007-04-15 09:59:56 +0200631
Simon Hausmann47a130b2007-05-20 16:33:21 +0200632 if os.system(tryPatchCmd) != 0:
Simon Hausmann51a26402007-04-15 09:59:56 +0200633 print "Unfortunately applying the change failed!"
634 print "What do you want to do?"
635 response = "x"
636 while response != "s" and response != "a" and response != "w":
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300637 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
638 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
Simon Hausmann51a26402007-04-15 09:59:56 +0200639 if response == "s":
640 print "Skipping! Good luck with the next patches..."
Simon Hausmann20947142007-09-13 22:10:18 +0200641 for f in editedFiles:
Anand Kumria87b611d2008-08-10 19:26:27 +0100642 p4_system("revert \"%s\"" % f);
Simon Hausmann20947142007-09-13 22:10:18 +0200643 for f in filesToAdd:
644 system("rm %s" %f)
Simon Hausmann51a26402007-04-15 09:59:56 +0200645 return
646 elif response == "a":
Simon Hausmann47a130b2007-05-20 16:33:21 +0200647 os.system(applyPatchCmd)
Simon Hausmann51a26402007-04-15 09:59:56 +0200648 if len(filesToAdd) > 0:
649 print "You may also want to call p4 add on the following files:"
650 print " ".join(filesToAdd)
651 if len(filesToDelete):
652 print "The following files should be scheduled for deletion with p4 delete:"
653 print " ".join(filesToDelete)
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300654 die("Please resolve and submit the conflict manually and "
655 + "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200656 elif response == "w":
657 system(diffcmd + " > patch.txt")
658 print "Patch saved to patch.txt in %s !" % self.clientPath
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300659 die("Please resolve and submit the conflict manually and "
660 "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200661
Simon Hausmann47a130b2007-05-20 16:33:21 +0200662 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100663
664 for f in filesToAdd:
Anand Kumria87b611d2008-08-10 19:26:27 +0100665 p4_system("add \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100666 for f in filesToDelete:
Anand Kumria87b611d2008-08-10 19:26:27 +0100667 p4_system("revert \"%s\"" % f)
668 p4_system("delete \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100669
Chris Pettittc65b6702007-11-01 20:43:14 -0700670 # Set/clear executable bits
671 for f in filesToChangeExecBit.keys():
672 mode = filesToChangeExecBit[f]
673 setP4ExecBit(f, mode)
674
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100675 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100676 logMessage = logMessage.strip()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100677
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200678 template = self.prepareSubmitTemplate()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100679
680 if self.interactive:
681 submitTemplate = self.prepareLogMessage(template, logMessage)
Shawn Bohrer67abd412008-03-12 19:03:23 -0500682 if os.environ.has_key("P4DIFF"):
683 del(os.environ["P4DIFF"])
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300684 diff = read_pipe("p4 diff -du ...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100685
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100686 newdiff = ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100687 for newFile in filesToAdd:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100688 newdiff += "==== new file ====\n"
689 newdiff += "--- /dev/null\n"
690 newdiff += "+++ %s\n" % newFile
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100691 f = open(newFile, "r")
692 for line in f.readlines():
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100693 newdiff += "+" + line
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100694 f.close()
695
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100696 separatorLine = "######## everything below this line is just the diff #######\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100697
Simon Hausmanne96e4002008-01-04 14:27:55 +0100698 [handle, fileName] = tempfile.mkstemp()
699 tmpFile = os.fdopen(handle, "w+")
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100700 if self.isWindows:
701 submitTemplate = submitTemplate.replace("\n", "\r\n")
702 separatorLine = separatorLine.replace("\n", "\r\n")
703 newdiff = newdiff.replace("\n", "\r\n")
704 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
Simon Hausmanne96e4002008-01-04 14:27:55 +0100705 tmpFile.close()
706 defaultEditor = "vi"
707 if platform.system() == "Windows":
708 defaultEditor = "notepad"
Shawn Bohrer82cea9f2008-03-12 19:03:24 -0500709 if os.environ.has_key("P4EDITOR"):
710 editor = os.environ.get("P4EDITOR")
711 else:
712 editor = os.environ.get("EDITOR", defaultEditor);
Simon Hausmanne96e4002008-01-04 14:27:55 +0100713 system(editor + " " + fileName)
714 tmpFile = open(fileName, "rb")
715 message = tmpFile.read()
716 tmpFile.close()
717 os.remove(fileName)
718 submitTemplate = message[:message.index(separatorLine)]
719 if self.isWindows:
720 submitTemplate = submitTemplate.replace("\r\n", "\n")
Simon Hausmanncb4f1282007-05-25 22:34:30 +0200721
Simon Hausmanne96e4002008-01-04 14:27:55 +0100722 write_pipe("p4 submit -i", submitTemplate)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100723 else:
724 fileName = "submit.txt"
725 file = open(fileName, "w+")
726 file.write(self.prepareLogMessage(template, logMessage))
727 file.close()
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300728 print ("Perforce submit template written as %s. "
729 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
730 % (fileName, fileName))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100731
732 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200733 if len(args) == 0:
734 self.master = currentGitBranch()
Simon Hausmann4280e532007-05-25 08:49:18 +0200735 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200736 die("Detecting current git branch failed!")
737 elif len(args) == 1:
738 self.master = args[0]
739 else:
740 return False
741
Jing Xue4c2d5d72008-06-22 14:12:39 -0400742 allowSubmit = gitConfig("git-p4.allowSubmit")
743 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
744 die("%s is not in git-p4.allowSubmit" % self.master)
745
Simon Hausmann27d2d812007-06-12 14:31:59 +0200746 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200747 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200748 if len(self.origin) == 0:
749 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200750
751 if self.verbose:
752 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +0100753
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200754 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +0100755 print "Internal error: cannot locate perforce depot path from existing branches"
756 sys.exit(128)
757
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200758 self.clientPath = p4Where(self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +0100759
Simon Hausmann51a26402007-04-15 09:59:56 +0200760 if len(self.clientPath) == 0:
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200761 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
Simon Hausmann95124972007-03-23 09:16:07 +0100762 sys.exit(128)
763
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200764 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +0200765 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200766
Simon Hausmann51a26402007-04-15 09:59:56 +0200767 os.chdir(self.clientPath)
Simon Hausmann31f9ec12007-08-21 11:53:02 +0200768 print "Syncronizing p4 checkout..."
Anand Kumria87b611d2008-08-10 19:26:27 +0100769 p4_system("sync ...")
Simon Hausmann95124972007-03-23 09:16:07 +0100770
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100771 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100772
Simon Hausmann4c750c02008-02-19 09:37:16 +0100773 commits = []
774 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
775 commits.append(line.strip())
776 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100777
778 while len(commits) > 0:
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100779 commit = commits[0]
780 commits = commits[1:]
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300781 self.applyCommit(commit)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100782 if not self.interactive:
783 break
784
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100785 if len(commits) == 0:
Simon Hausmann4c750c02008-02-19 09:37:16 +0100786 print "All changes applied!"
787 os.chdir(self.oldWorkingDirectory)
Simon Hausmann14594f42007-08-22 09:07:15 +0200788
Simon Hausmann4c750c02008-02-19 09:37:16 +0100789 sync = P4Sync()
790 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +0200791
Simon Hausmann4c750c02008-02-19 09:37:16 +0100792 rebase = P4Rebase()
793 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100794
Simon Hausmannb9847332007-03-20 20:54:23 +0100795 return True
796
Simon Hausmann711544b2007-04-01 15:40:46 +0200797class P4Sync(Command):
Simon Hausmannb9847332007-03-20 20:54:23 +0100798 def __init__(self):
799 Command.__init__(self)
800 self.options = [
801 optparse.make_option("--branch", dest="branch"),
802 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
803 optparse.make_option("--changesfile", dest="changesFile"),
804 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +0200805 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Simon Hausmanna028a982007-05-23 00:03:08 +0200806 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300807 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
808 help="Import into refs/heads/ , not refs/remotes"),
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300809 optparse.make_option("--max-changes", dest="maxChanges"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300810 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100811 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
812 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
813 help="Only sync files that are included in the Perforce Client Spec")
Simon Hausmannb9847332007-03-20 20:54:23 +0100814 ]
815 self.description = """Imports from Perforce into a git repository.\n
816 example:
817 //depot/my/project/ -- to import the current head
818 //depot/my/project/@all -- to import everything
819 //depot/my/project/@1,6 -- to import only from revision 1 to 6
820
821 (a ... is not needed in the path p4 specification, it's added implicitly)"""
822
823 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +0100824 self.silent = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100825 self.createdBranches = Set()
826 self.committedChanges = Set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +0100827 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +0100828 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +0200829 self.detectLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100830 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +0200831 self.syncWithOrigin = True
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200832 self.verbose = False
Simon Hausmanna028a982007-05-23 00:03:08 +0200833 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +0200834 self.maxChanges = ""
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +0200835 self.isWindows = (platform.system() == "Windows")
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300836 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300837 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +0200838 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -0800839 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100840 self.useClientSpec = False
841 self.clientSpecDirs = []
Simon Hausmannb9847332007-03-20 20:54:23 +0100842
Simon Hausmann01265102007-05-25 10:36:10 +0200843 if gitConfig("git-p4.syncFromOrigin") == "false":
844 self.syncWithOrigin = False
845
Simon Hausmannb9847332007-03-20 20:54:23 +0100846 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -0800847 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
848 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +0100849 files = []
850 fnum = 0
851 while commit.has_key("depotFile%s" % fnum):
852 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300853
Tommy Thorn354081d2008-02-03 10:38:51 -0800854 if [p for p in self.cloneExclude
855 if path.startswith (p)]:
856 found = False
857 else:
858 found = [p for p in self.depotPaths
859 if path.startswith (p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300860 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +0100861 fnum = fnum + 1
862 continue
863
864 file = {}
865 file["path"] = path
866 file["rev"] = commit["rev%s" % fnum]
867 file["action"] = commit["action%s" % fnum]
868 file["type"] = commit["type%s" % fnum]
869 files.append(file)
870 fnum = fnum + 1
871 return files
872
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300873 def stripRepoPath(self, path, prefixes):
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300874 if self.keepRepoPath:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300875 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300876
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300877 for p in prefixes:
878 if path.startswith(p):
879 path = path[len(p):]
880
881 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300882
Simon Hausmann71b112d2007-05-19 11:54:11 +0200883 def splitFilesIntoBranches(self, commit):
Simon Hausmannd5904672007-05-19 11:07:32 +0200884 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +0200885 fnum = 0
886 while commit.has_key("depotFile%s" % fnum):
887 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300888 found = [p for p in self.depotPaths
889 if path.startswith (p)]
890 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +0200891 fnum = fnum + 1
892 continue
893
894 file = {}
895 file["path"] = path
896 file["rev"] = commit["rev%s" % fnum]
897 file["action"] = commit["action%s" % fnum]
898 file["type"] = commit["type%s" % fnum]
899 fnum = fnum + 1
900
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300901 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +0100902
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200903 for branch in self.knownBranches.keys():
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300904
905 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
906 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +0200907 if branch not in branches:
908 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +0200909 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +0200910 break
Simon Hausmannb9847332007-03-20 20:54:23 +0100911
912 return branches
913
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300914 ## Should move this out, doesn't use SELF.
915 def readP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +0100916 filesForCommit = []
917 filesToRead = []
918
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100919 for f in files:
Simon Hausmann30b59402008-03-03 11:55:48 +0100920 includeFile = True
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100921 for val in self.clientSpecDirs:
922 if f['path'].startswith(val[0]):
Simon Hausmann30b59402008-03-03 11:55:48 +0100923 if val[1] <= 0:
924 includeFile = False
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100925 break
926
Simon Hausmann30b59402008-03-03 11:55:48 +0100927 if includeFile:
928 filesForCommit.append(f)
929 if f['action'] != 'delete':
930 filesToRead.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300931
Simon Hausmann30b59402008-03-03 11:55:48 +0100932 filedata = []
933 if len(filesToRead) > 0:
934 filedata = p4CmdList('-x - print',
935 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
936 for f in filesToRead]),
937 stdin_mode='w+')
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -0300938
Simon Hausmann30b59402008-03-03 11:55:48 +0100939 if "p4ExitCode" in filedata[0]:
940 die("Problems executing p4. Error: [%d]."
941 % (filedata[0]['p4ExitCode']));
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300942
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300943 j = 0;
944 contents = {}
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300945 while j < len(filedata):
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300946 stat = filedata[j]
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300947 j += 1
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100948 text = [];
Jason McMullanf3e95122007-12-05 12:16:56 -0500949 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100950 text.append(filedata[j]['data'])
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300951 j += 1
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100952 text = ''.join(text)
Han-Wen Nienhuys1b9a4682007-05-23 18:49:35 -0300953
954 if not stat.has_key('depotFile'):
955 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
956 continue
957
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100958 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
959 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
960 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
Daniel Barkalow2d71bde2008-07-22 12:48:57 -0400961 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100962
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300963 contents[stat['depotFile']] = text
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300964
Simon Hausmann30b59402008-03-03 11:55:48 +0100965 for f in filesForCommit:
966 path = f['path']
967 if contents.has_key(path):
968 f['data'] = contents[path]
969
970 return filesForCommit
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300971
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300972 def commit(self, details, files, branch, branchPrefixes, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +0100973 epoch = details["time"]
974 author = details["user"]
975
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200976 if self.verbose:
977 print "commit into %s" % branch
978
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -0300979 # start with reading files; if that fails, we should not
980 # create a commit.
981 new_files = []
982 for f in files:
983 if [p for p in branchPrefixes if f['path'].startswith(p)]:
984 new_files.append (f)
985 else:
986 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100987 files = self.readP4Files(new_files)
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -0300988
Simon Hausmannb9847332007-03-20 20:54:23 +0100989 self.gitStream.write("commit %s\n" % branch)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300990# gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +0100991 self.committedChanges.add(int(details["change"]))
992 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +0200993 if author not in self.users:
994 self.getUserMapFromPerforceServer()
Simon Hausmannb9847332007-03-20 20:54:23 +0100995 if author in self.users:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100996 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100997 else:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100998 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100999
1000 self.gitStream.write("committer %s\n" % committer)
1001
1002 self.gitStream.write("data <<EOT\n")
1003 self.gitStream.write(details["desc"])
Simon Hausmann6581de02007-06-11 10:01:58 +02001004 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1005 % (','.join (branchPrefixes), details["change"]))
1006 if len(details['options']) > 0:
1007 self.gitStream.write(": options = %s" % details['options'])
1008 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001009
1010 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001011 if self.verbose:
1012 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +01001013 self.gitStream.write("from %s\n" % parent)
1014
Simon Hausmannb9847332007-03-20 20:54:23 +01001015 for file in files:
Simon Hausmannb9847332007-03-20 20:54:23 +01001016 if file["type"] == "apple":
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001017 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
Simon Hausmannb9847332007-03-20 20:54:23 +01001018 continue
1019
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001020 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1021 if file["action"] == "delete":
Simon Hausmannb9847332007-03-20 20:54:23 +01001022 self.gitStream.write("D %s\n" % relPath)
1023 else:
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001024 data = file['data']
Simon Hausmannb9847332007-03-20 20:54:23 +01001025
Simon Hausmann74276ec2007-08-07 12:28:00 +02001026 mode = "644"
David Brownb9fc6ea2007-09-19 13:12:48 -07001027 if isP4Exec(file["type"]):
Simon Hausmann74276ec2007-08-07 12:28:00 +02001028 mode = "755"
1029 elif file["type"] == "symlink":
1030 mode = "120000"
1031 # p4 print on a symlink contains "target\n", so strip it off
1032 data = data[:-1]
1033
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +02001034 if self.isWindows and file["type"].endswith("text"):
1035 data = data.replace("\r\n", "\n")
1036
Simon Hausmann74276ec2007-08-07 12:28:00 +02001037 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
Simon Hausmannb9847332007-03-20 20:54:23 +01001038 self.gitStream.write("data %s\n" % len(data))
1039 self.gitStream.write(data)
1040 self.gitStream.write("\n")
1041
1042 self.gitStream.write("\n")
1043
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001044 change = int(details["change"])
1045
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001046 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001047 label = self.labels[change]
1048 labelDetails = label[0]
1049 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02001050 if self.verbose:
1051 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001052
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001053 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1054 for p in branchPrefixes]))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001055
1056 if len(files) == len(labelRevisions):
1057
1058 cleanedFiles = {}
1059 for info in files:
1060 if info["action"] == "delete":
1061 continue
1062 cleanedFiles[info["depotFile"]] = info["rev"]
1063
1064 if cleanedFiles == labelRevisions:
1065 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1066 self.gitStream.write("from %s\n" % branch)
1067
1068 owner = labelDetails["Owner"]
1069 tagger = ""
1070 if author in self.users:
1071 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1072 else:
1073 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1074 self.gitStream.write("tagger %s\n" % tagger)
1075 self.gitStream.write("data <<EOT\n")
1076 self.gitStream.write(labelDetails["Description"])
1077 self.gitStream.write("EOT\n\n")
1078
1079 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001080 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001081 print ("Tag %s does not match with change %s: files do not match."
1082 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001083
1084 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001085 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001086 print ("Tag %s does not match with change %s: file count is different."
1087 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01001088
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001089 def getUserCacheFilename(self):
Simon Hausmannb2d2d162007-07-25 09:31:38 +02001090 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1091 return home + "/.gitp4-usercache.txt"
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001092
Simon Hausmannb607e712007-05-20 10:55:54 +02001093 def getUserMapFromPerforceServer(self):
Simon Hausmannebd81162007-05-24 00:24:52 +02001094 if self.userMapFromPerforceServer:
1095 return
Simon Hausmannb9847332007-03-20 20:54:23 +01001096 self.users = {}
1097
1098 for output in p4CmdList("users"):
1099 if not output.has_key("User"):
1100 continue
1101 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1102
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001103
1104 s = ''
1105 for (key, val) in self.users.items():
1106 s += "%s\t%s\n" % (key, val)
1107
1108 open(self.getUserCacheFilename(), "wb").write(s)
Simon Hausmannebd81162007-05-24 00:24:52 +02001109 self.userMapFromPerforceServer = True
Simon Hausmannb607e712007-05-20 10:55:54 +02001110
1111 def loadUserMapFromCache(self):
1112 self.users = {}
Simon Hausmannebd81162007-05-24 00:24:52 +02001113 self.userMapFromPerforceServer = False
Simon Hausmannb607e712007-05-20 10:55:54 +02001114 try:
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001115 cache = open(self.getUserCacheFilename(), "rb")
Simon Hausmannb607e712007-05-20 10:55:54 +02001116 lines = cache.readlines()
1117 cache.close()
1118 for line in lines:
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001119 entry = line.strip().split("\t")
Simon Hausmannb607e712007-05-20 10:55:54 +02001120 self.users[entry[0]] = entry[1]
1121 except IOError:
1122 self.getUserMapFromPerforceServer()
1123
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001124 def getLabels(self):
1125 self.labels = {}
1126
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001127 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
Simon Hausmann10c32112007-04-08 10:15:47 +02001128 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08001129 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001130
1131 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001132 label = output["label"]
1133 revisions = {}
1134 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02001135 if self.verbose:
1136 print "Querying files for label %s" % label
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001137 for file in p4CmdList("files "
1138 + ' '.join (["%s...@%s" % (p, label)
1139 for p in self.depotPaths])):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001140 revisions[file["depotFile"]] = file["rev"]
1141 change = int(file["change"])
1142 if change > newestChange:
1143 newestChange = change
1144
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001145 self.labels[newestChange] = [output, revisions]
1146
1147 if self.verbose:
1148 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001149
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001150 def guessProjectName(self):
1151 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02001152 if p.endswith("/"):
1153 p = p[:-1]
1154 p = p[p.strip().rfind("/") + 1:]
1155 if not p.endswith("/"):
1156 p += "/"
1157 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001158
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001159 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001160 lostAndFoundBranches = set()
1161
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001162 for info in p4CmdList("branches"):
1163 details = p4Cmd("branch -o %s" % info["branch"])
1164 viewIdx = 0
1165 while details.has_key("View%s" % viewIdx):
1166 paths = details["View%s" % viewIdx].split(" ")
1167 viewIdx = viewIdx + 1
1168 # require standard //depot/foo/... //depot/bar/... mapping
1169 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1170 continue
1171 source = paths[0]
1172 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02001173 ## HACK
1174 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1175 source = source[len(self.depotPaths[0]):-4]
1176 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001177
Simon Hausmann1a2edf42007-06-17 15:10:24 +02001178 if destination in self.knownBranches:
1179 if not self.silent:
1180 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1181 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1182 continue
1183
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001184 self.knownBranches[destination] = source
1185
1186 lostAndFoundBranches.discard(destination)
1187
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001188 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001189 lostAndFoundBranches.add(source)
1190
1191
1192 for branch in lostAndFoundBranches:
1193 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001194
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001195 def getBranchMappingFromGitBranches(self):
1196 branches = p4BranchesInGit(self.importIntoRemotes)
1197 for branch in branches.keys():
1198 if branch == "master":
1199 branch = "main"
1200 else:
1201 branch = branch[len(self.projectName):]
1202 self.knownBranches[branch] = branch
1203
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001204 def listExistingP4GitBranches(self):
Simon Hausmann144ff462007-07-18 17:27:50 +02001205 # branches holds mapping from name to commit
1206 branches = p4BranchesInGit(self.importIntoRemotes)
1207 self.p4BranchesInGit = branches.keys()
1208 for branch in branches.keys():
1209 self.initialParents[self.refPrefix + branch] = branches[branch]
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001210
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001211 def updateOptionDict(self, d):
1212 option_keys = {}
1213 if self.keepRepoPath:
1214 option_keys['keepRepoPath'] = 1
1215
1216 d["options"] = ' '.join(sorted(option_keys.keys()))
1217
1218 def readOptions(self, d):
1219 self.keepRepoPath = (d.has_key('options')
1220 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001221
Simon Hausmann8134f692007-08-26 16:44:55 +02001222 def gitRefForBranch(self, branch):
1223 if branch == "main":
1224 return self.refPrefix + "master"
1225
1226 if len(branch) <= 0:
1227 return branch
1228
1229 return self.refPrefix + self.projectName + branch
1230
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001231 def gitCommitByP4Change(self, ref, change):
1232 if self.verbose:
1233 print "looking in ref " + ref + " for change %s using bisect..." % change
1234
1235 earliestCommit = ""
1236 latestCommit = parseRevision(ref)
1237
1238 while True:
1239 if self.verbose:
1240 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1241 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1242 if len(next) == 0:
1243 if self.verbose:
1244 print "argh"
1245 return ""
1246 log = extractLogMessageFromGitCommit(next)
1247 settings = extractSettingsGitLog(log)
1248 currentChange = int(settings['change'])
1249 if self.verbose:
1250 print "current change %s" % currentChange
1251
1252 if currentChange == change:
1253 if self.verbose:
1254 print "found %s" % next
1255 return next
1256
1257 if currentChange < change:
1258 earliestCommit = "^%s" % next
1259 else:
1260 latestCommit = "%s" % next
1261
1262 return ""
1263
1264 def importNewBranch(self, branch, maxChange):
1265 # make fast-import flush all changes to disk and update the refs using the checkpoint
1266 # command so that we can try to find the branch parent in the git history
1267 self.gitStream.write("checkpoint\n\n");
1268 self.gitStream.flush();
1269 branchPrefix = self.depotPaths[0] + branch + "/"
1270 range = "@1,%s" % maxChange
1271 #print "prefix" + branchPrefix
1272 changes = p4ChangesForPaths([branchPrefix], range)
1273 if len(changes) <= 0:
1274 return False
1275 firstChange = changes[0]
1276 #print "first change in branch: %s" % firstChange
1277 sourceBranch = self.knownBranches[branch]
1278 sourceDepotPath = self.depotPaths[0] + sourceBranch
1279 sourceRef = self.gitRefForBranch(sourceBranch)
1280 #print "source " + sourceBranch
1281
1282 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1283 #print "branch parent: %s" % branchParentChange
1284 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1285 if len(gitParent) > 0:
1286 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1287 #print "parent git commit: %s" % gitParent
1288
1289 self.importChanges(changes)
1290 return True
1291
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001292 def importChanges(self, changes):
1293 cnt = 1
1294 for change in changes:
1295 description = p4Cmd("describe %s" % change)
1296 self.updateOptionDict(description)
1297
1298 if not self.silent:
1299 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1300 sys.stdout.flush()
1301 cnt = cnt + 1
1302
1303 try:
1304 if self.detectBranches:
1305 branches = self.splitFilesIntoBranches(description)
1306 for branch in branches.keys():
1307 ## HACK --hwn
1308 branchPrefix = self.depotPaths[0] + branch + "/"
1309
1310 parent = ""
1311
1312 filesForCommit = branches[branch]
1313
1314 if self.verbose:
1315 print "branch is %s" % branch
1316
1317 self.updatedBranches.add(branch)
1318
1319 if branch not in self.createdBranches:
1320 self.createdBranches.add(branch)
1321 parent = self.knownBranches[branch]
1322 if parent == branch:
1323 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001324 else:
1325 fullBranch = self.projectName + branch
1326 if fullBranch not in self.p4BranchesInGit:
1327 if not self.silent:
1328 print("\n Importing new branch %s" % fullBranch);
1329 if self.importNewBranch(branch, change - 1):
1330 parent = ""
1331 self.p4BranchesInGit.append(fullBranch)
1332 if not self.silent:
1333 print("\n Resuming with change %s" % change);
1334
1335 if self.verbose:
1336 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001337
Simon Hausmann8134f692007-08-26 16:44:55 +02001338 branch = self.gitRefForBranch(branch)
1339 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001340
1341 if self.verbose:
1342 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1343
1344 if len(parent) == 0 and branch in self.initialParents:
1345 parent = self.initialParents[branch]
1346 del self.initialParents[branch]
1347
1348 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1349 else:
1350 files = self.extractFilesFromCommit(description)
1351 self.commit(description, files, self.branch, self.depotPaths,
1352 self.initialParent)
1353 self.initialParent = ""
1354 except IOError:
1355 print self.gitError.read()
1356 sys.exit(1)
1357
Simon Hausmannc208a242007-08-26 16:07:18 +02001358 def importHeadRevision(self, revision):
1359 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1360
1361 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1362 details["desc"] = ("Initial import of %s from the state at revision %s"
1363 % (' '.join(self.depotPaths), revision))
1364 details["change"] = revision
1365 newestRevision = 0
1366
1367 fileCnt = 0
1368 for info in p4CmdList("files "
1369 + ' '.join(["%s...%s"
1370 % (p, revision)
1371 for p in self.depotPaths])):
1372
1373 if info['code'] == 'error':
1374 sys.stderr.write("p4 returned an error: %s\n"
1375 % info['data'])
1376 sys.exit(1)
1377
1378
1379 change = int(info["change"])
1380 if change > newestRevision:
1381 newestRevision = change
1382
1383 if info["action"] == "delete":
1384 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1385 #fileCnt = fileCnt + 1
1386 continue
1387
1388 for prop in ["depotFile", "rev", "action", "type" ]:
1389 details["%s%s" % (prop, fileCnt)] = info[prop]
1390
1391 fileCnt = fileCnt + 1
1392
1393 details["change"] = newestRevision
1394 self.updateOptionDict(details)
1395 try:
1396 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1397 except IOError:
1398 print "IO error with git fast-import. Is your git version recent enough?"
1399 print self.gitError.read()
1400
1401
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001402 def getClientSpec(self):
1403 specList = p4CmdList( "client -o" )
1404 temp = {}
1405 for entry in specList:
1406 for k,v in entry.iteritems():
1407 if k.startswith("View"):
1408 if v.startswith('"'):
1409 start = 1
1410 else:
1411 start = 0
1412 index = v.find("...")
1413 v = v[start:index]
1414 if v.startswith("-"):
1415 v = v[1:]
1416 temp[v] = -len(v)
1417 else:
1418 temp[v] = len(v)
1419 self.clientSpecDirs = temp.items()
1420 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1421
Simon Hausmannb9847332007-03-20 20:54:23 +01001422 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001423 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01001424 self.changeRange = ""
1425 self.initialParent = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001426 self.previousDepotPaths = []
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03001427
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001428 # map from branch depot path to parent branch
1429 self.knownBranches = {}
1430 self.initialParents = {}
Simon Hausmann5ca44612007-08-24 17:44:16 +02001431 self.hasOrigin = originP4BranchesExist()
Simon Hausmanna43ff002007-06-11 09:59:27 +02001432 if not self.syncWithOrigin:
1433 self.hasOrigin = False
Simon Hausmann179caeb2007-03-22 22:17:42 +01001434
Simon Hausmanna028a982007-05-23 00:03:08 +02001435 if self.importIntoRemotes:
1436 self.refPrefix = "refs/remotes/p4/"
1437 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001438 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02001439
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001440 if self.syncWithOrigin and self.hasOrigin:
1441 if not self.silent:
1442 print "Syncing with origin first by calling git fetch origin"
1443 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02001444
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001445 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001446 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02001447 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001448 system("git update-ref %s refs/heads/p4" % self.branch)
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001449 system("git branch -D p4");
Simon Hausmannfaf1bd22007-05-21 10:05:30 +02001450 # create it /after/ importing, when master exists
Simon Hausmann0058a332007-08-24 17:46:16 +02001451 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
Simon Hausmanna3c55c02007-05-27 15:48:01 +02001452 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
Simon Hausmann179caeb2007-03-22 22:17:42 +01001453
Anand Kumria3cafb7d2008-08-10 19:26:32 +01001454 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001455 self.getClientSpec()
1456
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001457 # TODO: should always look at previous commits,
1458 # merge with previous imports, if possible.
1459 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02001460 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02001461 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Simon Hausmannabcd7902007-05-24 22:25:36 +02001462 self.listExistingP4GitBranches()
1463
1464 if len(self.p4BranchesInGit) > 1:
1465 if not self.silent:
1466 print "Importing from/into multiple branches"
1467 self.detectBranches = True
Simon Hausmann967f72e2007-03-23 09:30:41 +01001468
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001469 if self.verbose:
1470 print "branches: %s" % self.p4BranchesInGit
1471
1472 p4Change = 0
1473 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001474 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001475
1476 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001477
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001478 self.readOptions(settings)
1479 if (settings.has_key('depot-paths')
1480 and settings.has_key ('change')):
1481 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001482 p4Change = max(p4Change, change)
1483
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001484 depotPaths = sorted(settings['depot-paths'])
1485 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001486 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001487 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001488 paths = []
1489 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Simon Hausmann583e1702007-06-07 09:37:13 +02001490 for i in range(0, min(len(cur), len(prev))):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001491 if cur[i] <> prev[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02001492 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001493 break
1494
Simon Hausmann583e1702007-06-07 09:37:13 +02001495 paths.append (cur[:i + 1])
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001496
1497 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001498
1499 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001500 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02001501 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann330f53b2007-06-07 09:39:51 +02001502 if not self.detectBranches:
1503 self.initialParent = parseRevision(self.branch)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001504 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01001505 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001506
Simon Hausmannf9162f62007-05-17 09:02:45 +02001507 if not self.branch.startswith("refs/"):
1508 self.branch = "refs/heads/" + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01001509
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001510 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01001511 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001512 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01001513 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001514 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001515 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001516 "This doesn't work!" % (' '.join (self.depotPaths),
1517 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01001518 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001519
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001520 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01001521
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001522 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01001523 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01001524
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001525 newPaths = []
1526 for p in self.depotPaths:
1527 if p.find("@") != -1:
1528 atIdx = p.index("@")
1529 self.changeRange = p[atIdx:]
1530 if self.changeRange == "@all":
1531 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001532 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001533 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001534 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001535 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001536 elif p.find("#") != -1:
1537 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001538 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001539 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001540 elif self.previousDepotPaths == []:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001541 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01001542
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001543 p = re.sub ("\.\.\.$", "", p)
1544 if not p.endswith("/"):
1545 p += "/"
1546
1547 newPaths.append(p)
1548
1549 self.depotPaths = newPaths
1550
Simon Hausmannb9847332007-03-20 20:54:23 +01001551
Simon Hausmannb607e712007-05-20 10:55:54 +02001552 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02001553 self.labels = {}
1554 if self.detectLabels:
1555 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01001556
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001557 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02001558 ## FIXME - what's a P4 projectName ?
1559 self.projectName = self.guessProjectName()
1560
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001561 if self.hasOrigin:
1562 self.getBranchMappingFromGitBranches()
1563 else:
1564 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001565 if self.verbose:
1566 print "p4-git branches: %s" % self.p4BranchesInGit
1567 print "initial parents: %s" % self.initialParents
1568 for b in self.p4BranchesInGit:
1569 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001570
1571 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001572 b = b[len(self.projectName):]
1573 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001574
Simon Hausmannf291b4e2007-04-14 11:21:50 +02001575 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01001576
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001577 importProcess = subprocess.Popen(["git", "fast-import"],
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001578 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1579 stderr=subprocess.PIPE);
Simon Hausmann08483582007-05-15 14:31:06 +02001580 self.gitOutput = importProcess.stdout
1581 self.gitStream = importProcess.stdin
1582 self.gitError = importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01001583
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001584 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02001585 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01001586 else:
1587 changes = []
1588
Simon Hausmann0828ab12007-03-20 20:59:30 +01001589 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01001590 output = open(self.changesFile).readlines()
1591 changeSet = Set()
1592 for line in output:
1593 changeSet.add(int(line))
1594
1595 for change in changeSet:
1596 changes.append(change)
1597
1598 changes.sort()
1599 else:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001600 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001601 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001602 self.changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001603 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
Simon Hausmannb9847332007-03-20 20:54:23 +01001604
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001605 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001606 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001607
Simon Hausmannb9847332007-03-20 20:54:23 +01001608 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001609 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001610 print "No changes to import!"
Simon Hausmann1f52af62007-04-08 00:07:02 +02001611 return True
Simon Hausmannb9847332007-03-20 20:54:23 +01001612
Simon Hausmanna9d1a272007-06-11 23:28:03 +02001613 if not self.silent and not self.detectBranches:
1614 print "Import destination: %s" % self.branch
1615
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001616 self.updatedBranches = set()
1617
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001618 self.importChanges(changes)
Simon Hausmannb9847332007-03-20 20:54:23 +01001619
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001620 if not self.silent:
1621 print ""
1622 if len(self.updatedBranches) > 0:
1623 sys.stdout.write("Updated branches: ")
1624 for b in self.updatedBranches:
1625 sys.stdout.write("%s " % b)
1626 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001627
Simon Hausmannb9847332007-03-20 20:54:23 +01001628 self.gitStream.close()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001629 if importProcess.wait() != 0:
1630 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01001631 self.gitOutput.close()
1632 self.gitError.close()
1633
Simon Hausmannb9847332007-03-20 20:54:23 +01001634 return True
1635
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001636class P4Rebase(Command):
1637 def __init__(self):
1638 Command.__init__(self)
Simon Hausmann01265102007-05-25 10:36:10 +02001639 self.options = [ ]
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001640 self.description = ("Fetches the latest revision from perforce and "
1641 + "rebases the current work (branch) against it")
Simon Hausmann68c42152007-06-07 12:51:03 +02001642 self.verbose = False
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001643
1644 def run(self, args):
1645 sync = P4Sync()
1646 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02001647
Simon Hausmann14594f42007-08-22 09:07:15 +02001648 return self.rebase()
1649
1650 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01001651 if os.system("git update-index --refresh") != 0:
1652 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.");
1653 if len(read_pipe("git diff-index HEAD --")) > 0:
1654 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1655
Simon Hausmannd7e38682007-06-12 14:34:46 +02001656 [upstream, settings] = findUpstreamBranchPoint()
1657 if len(upstream) == 0:
1658 die("Cannot find upstream branchpoint for rebase")
1659
1660 # the branchpoint may be p4/foo~3, so strip off the parent
1661 upstream = re.sub("~[0-9]+$", "", upstream)
1662
1663 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001664 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02001665 system("git rebase %s" % upstream)
Simon Hausmann1f52af62007-04-08 00:07:02 +02001666 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001667 return True
1668
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001669class P4Clone(P4Sync):
1670 def __init__(self):
1671 P4Sync.__init__(self)
1672 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001673 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08001674 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001675 optparse.make_option("--destination", dest="cloneDestination",
1676 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08001677 help="where to leave result of the clone"),
1678 optparse.make_option("-/", dest="cloneExclude",
1679 action="append", type="string",
1680 help="exclude depot path")
1681 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001682 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001683 self.needsGit = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001684
Tommy Thorn354081d2008-02-03 10:38:51 -08001685 # This is required for the "append" cloneExclude action
1686 def ensure_value(self, attr, value):
1687 if not hasattr(self, attr) or getattr(self, attr) is None:
1688 setattr(self, attr, value)
1689 return getattr(self, attr)
1690
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001691 def defaultDestination(self, args):
1692 ## TODO: use common prefix of args?
1693 depotPath = args[0]
1694 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1695 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13001696 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001697 depotDir = re.sub(r"/$", "", depotDir)
1698 return os.path.split(depotDir)[1]
1699
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001700 def run(self, args):
1701 if len(args) < 1:
1702 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001703
1704 if self.keepRepoPath and not self.cloneDestination:
1705 sys.stderr.write("Must specify destination for --keep-path\n")
1706 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001707
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001708 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02001709
1710 if not self.cloneDestination and len(depotPaths) > 1:
1711 self.cloneDestination = depotPaths[-1]
1712 depotPaths = depotPaths[:-1]
1713
Tommy Thorn354081d2008-02-03 10:38:51 -08001714 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001715 for p in depotPaths:
1716 if not p.startswith("//"):
1717 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001718
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001719 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02001720 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001721
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001722 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Kevin Greenc3bf3f12007-06-11 16:48:07 -04001723 if not os.path.exists(self.cloneDestination):
1724 os.makedirs(self.cloneDestination)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001725 os.chdir(self.cloneDestination)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001726 system("git init")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001727 self.gitdir = os.getcwd() + "/.git"
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001728 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001729 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001730 if self.branch != "master":
Simon Hausmann8f9b2e02007-05-18 22:13:26 +02001731 if gitBranchExists("refs/remotes/p4/master"):
1732 system("git branch master refs/remotes/p4/master")
1733 system("git checkout -f")
1734 else:
1735 print "Could not detect main branch. No checkout/master branch created."
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001736
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001737 return True
1738
Simon Hausmann09d89de2007-06-20 23:10:28 +02001739class P4Branches(Command):
1740 def __init__(self):
1741 Command.__init__(self)
1742 self.options = [ ]
1743 self.description = ("Shows the git branches that hold imports and their "
1744 + "corresponding perforce depot paths")
1745 self.verbose = False
1746
1747 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02001748 if originP4BranchesExist():
1749 createOrUpdateBranchesFromOrigin()
1750
Simon Hausmann09d89de2007-06-20 23:10:28 +02001751 cmdline = "git rev-parse --symbolic "
1752 cmdline += " --remotes"
1753
1754 for line in read_pipe_lines(cmdline):
1755 line = line.strip()
1756
1757 if not line.startswith('p4/') or line == "p4/HEAD":
1758 continue
1759 branch = line
1760
1761 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1762 settings = extractSettingsGitLog(log)
1763
1764 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1765 return True
1766
Simon Hausmannb9847332007-03-20 20:54:23 +01001767class HelpFormatter(optparse.IndentedHelpFormatter):
1768 def __init__(self):
1769 optparse.IndentedHelpFormatter.__init__(self)
1770
1771 def format_description(self, description):
1772 if description:
1773 return description + "\n"
1774 else:
1775 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001776
Simon Hausmann86949ee2007-03-19 20:59:12 +01001777def printUsage(commands):
1778 print "usage: %s <command> [options]" % sys.argv[0]
1779 print ""
1780 print "valid commands: %s" % ", ".join(commands)
1781 print ""
1782 print "Try %s <command> --help for command specific help." % sys.argv[0]
1783 print ""
1784
1785commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001786 "debug" : P4Debug,
1787 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02001788 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001789 "sync" : P4Sync,
1790 "rebase" : P4Rebase,
1791 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02001792 "rollback" : P4RollBack,
1793 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01001794}
1795
Simon Hausmann86949ee2007-03-19 20:59:12 +01001796
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001797def main():
1798 if len(sys.argv[1:]) == 0:
1799 printUsage(commands.keys())
1800 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001801
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001802 cmd = ""
1803 cmdName = sys.argv[1]
1804 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001805 klass = commands[cmdName]
1806 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001807 except KeyError:
1808 print "unknown command %s" % cmdName
1809 print ""
1810 printUsage(commands.keys())
1811 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001812
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001813 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001814 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001815
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001816 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001817
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001818 if len(options) > 0:
1819 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001820
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001821 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1822 options,
1823 description = cmd.description,
1824 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01001825
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001826 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1827 global verbose
1828 verbose = cmd.verbose
1829 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001830 if cmd.gitdir == None:
1831 cmd.gitdir = os.path.abspath(".git")
1832 if not isValidGitDir(cmd.gitdir):
1833 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1834 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001835 cdup = read_pipe("git rev-parse --show-cdup").strip()
1836 if len(cdup) > 0:
1837 os.chdir(cdup);
1838
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001839 if not isValidGitDir(cmd.gitdir):
1840 if isValidGitDir(cmd.gitdir + "/.git"):
1841 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001842 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001843 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02001844
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001845 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001846
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001847 if not cmd.run(args):
1848 parser.print_help()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001849
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001850
1851if __name__ == '__main__':
1852 main()