blob: e55a41b10ed3fd7f93a9c5b60870dc5dc837177c [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
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030019def die(msg):
20 if verbose:
21 raise Exception(msg)
22 else:
23 sys.stderr.write(msg + "\n")
24 sys.exit(1)
25
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030026def write_pipe(c, str):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030027 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030028 sys.stderr.write('Writing pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030029
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030030 pipe = os.popen(c, 'w')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030031 val = pipe.write(str)
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030032 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030033 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030034
35 return val
36
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030037def read_pipe(c, ignore_error=False):
38 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030039 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030040
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030041 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030042 val = pipe.read()
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030043 if pipe.close() and not ignore_error:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030044 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030045
46 return val
47
48
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030049def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030050 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030051 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030052 ## todo: check return status
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030053 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030054 val = pipe.readlines()
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030055 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030056 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030057
58 return val
Simon Hausmanncaace112007-05-15 14:57:57 +020059
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -030060def system(cmd):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030061 if verbose:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -030062 sys.stderr.write("executing %s\n" % cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -030063 if os.system(cmd) != 0:
64 die("command failed: %s" % cmd)
65
David Brownb9fc6ea2007-09-19 13:12:48 -070066def isP4Exec(kind):
67 """Determine if a Perforce 'kind' should have execute permission
68
69 'p4 help filetypes' gives a list of the types. If it starts with 'x',
70 or x follows one of a few letters. Otherwise, if there is an 'x' after
71 a plus sign, it is also executable"""
72 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
73
Chris Pettittc65b6702007-11-01 20:43:14 -070074def setP4ExecBit(file, mode):
75 # Reopens an already open file and changes the execute bit to match
76 # the execute bit setting in the passed in mode.
77
78 p4Type = "+x"
79
80 if not isModeExec(mode):
81 p4Type = getP4OpenedType(file)
82 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
83 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
84 if p4Type[-1] == "+":
85 p4Type = p4Type[0:-1]
86
87 system("p4 reopen -t %s %s" % (p4Type, file))
88
89def getP4OpenedType(file):
90 # Returns the perforce file type for the given file.
91
92 result = read_pipe("p4 opened %s" % file)
93 match = re.match(".*\((.+)\)$", result)
94 if match:
95 return match.group(1)
96 else:
97 die("Could not determine file type for %s" % file)
98
Chris Pettittb43b0a32007-11-01 20:43:13 -070099def diffTreePattern():
100 # This is a simple generator for the diff tree regex pattern. This could be
101 # a class variable if this and parseDiffTreeEntry were a part of a class.
102 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
103 while True:
104 yield pattern
105
106def parseDiffTreeEntry(entry):
107 """Parses a single diff tree entry into its component elements.
108
109 See git-diff-tree(1) manpage for details about the format of the diff
110 output. This method returns a dictionary with the following elements:
111
112 src_mode - The mode of the source file
113 dst_mode - The mode of the destination file
114 src_sha1 - The sha1 for the source file
115 dst_sha1 - The sha1 fr the destination file
116 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
117 status_score - The score for the status (applicable for 'C' and 'R'
118 statuses). This is None if there is no score.
119 src - The path for the source file.
120 dst - The path for the destination file. This is only present for
121 copy or renames. If it is not present, this is None.
122
123 If the pattern is not matched, None is returned."""
124
125 match = diffTreePattern().next().match(entry)
126 if match:
127 return {
128 'src_mode': match.group(1),
129 'dst_mode': match.group(2),
130 'src_sha1': match.group(3),
131 'dst_sha1': match.group(4),
132 'status': match.group(5),
133 'status_score': match.group(6),
134 'src': match.group(7),
135 'dst': match.group(10)
136 }
137 return None
138
Chris Pettittc65b6702007-11-01 20:43:14 -0700139def isModeExec(mode):
140 # Returns True if the given git mode represents an executable file,
141 # otherwise False.
142 return mode[-3:] == "755"
143
144def isModeExecChanged(src_mode, dst_mode):
145 return isModeExec(src_mode) != isModeExec(dst_mode)
146
Scott Lamb9f90c732007-07-15 20:58:10 -0700147def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100148 cmd = "p4 -G %s" % cmd
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300149 if verbose:
150 sys.stderr.write("Opening pipe: %s\n" % cmd)
Scott Lamb9f90c732007-07-15 20:58:10 -0700151
152 # Use a temporary file to avoid deadlocks without
153 # subprocess.communicate(), which would put another copy
154 # of stdout into memory.
155 stdin_file = None
156 if stdin is not None:
157 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
158 stdin_file.write(stdin)
159 stdin_file.flush()
160 stdin_file.seek(0)
161
162 p4 = subprocess.Popen(cmd, shell=True,
163 stdin=stdin_file,
164 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100165
166 result = []
167 try:
168 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700169 entry = marshal.load(p4.stdout)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100170 result.append(entry)
171 except EOFError:
172 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700173 exitCode = p4.wait()
174 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200175 entry = {}
176 entry["p4ExitCode"] = exitCode
177 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100178
179 return result
180
181def p4Cmd(cmd):
182 list = p4CmdList(cmd)
183 result = {}
184 for entry in list:
185 result.update(entry)
186 return result;
187
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100188def p4Where(depotPath):
189 if not depotPath.endswith("/"):
190 depotPath += "/"
191 output = p4Cmd("where %s..." % depotPath)
Simon Hausmanndc524032007-05-21 09:34:56 +0200192 if output["code"] == "error":
193 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100194 clientPath = ""
195 if "path" in output:
196 clientPath = output.get("path")
197 elif "data" in output:
198 data = output.get("data")
199 lastSpace = data.rfind(" ")
200 clientPath = data[lastSpace + 1:]
201
202 if clientPath.endswith("..."):
203 clientPath = clientPath[:-3]
204 return clientPath
205
Simon Hausmann86949ee2007-03-19 20:59:12 +0100206def currentGitBranch():
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300207 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100208
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100209def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300210 if (os.path.exists(path + "/HEAD")
211 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100212 return True;
213 return False
214
Simon Hausmann463e8af2007-05-17 09:13:54 +0200215def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300216 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200217
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100218def extractLogMessageFromGitCommit(commit):
219 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300220
221 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100222 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300223 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100224 if not foundTitle:
225 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200226 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100227 continue
228
229 logMessage += log
230 return logMessage
231
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300232def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100233 values = {}
234 for line in log.split("\n"):
235 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300236 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
237 if not m:
238 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100239
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300240 assignments = m.group(1).split (':')
241 for a in assignments:
242 vals = a.split ('=')
243 key = vals[0].strip()
244 val = ('='.join (vals[1:])).strip()
245 if val.endswith ('\"') and val.startswith('"'):
246 val = val[1:-1]
247
248 values[key] = val
249
Simon Hausmann845b42c2007-06-07 09:19:34 +0200250 paths = values.get("depot-paths")
251 if not paths:
252 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200253 if paths:
254 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300255 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100256
Simon Hausmann8136a632007-03-22 21:27:14 +0100257def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300258 proc = subprocess.Popen(["git", "rev-parse", branch],
259 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200260 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100261
Simon Hausmann01265102007-05-25 10:36:10 +0200262def gitConfig(key):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300263 return read_pipe("git config %s" % key, ignore_error=True).strip()
Simon Hausmann01265102007-05-25 10:36:10 +0200264
Simon Hausmann062410b2007-07-18 10:56:31 +0200265def p4BranchesInGit(branchesAreInRemotes = True):
266 branches = {}
267
268 cmdline = "git rev-parse --symbolic "
269 if branchesAreInRemotes:
270 cmdline += " --remotes"
271 else:
272 cmdline += " --branches"
273
274 for line in read_pipe_lines(cmdline):
275 line = line.strip()
276
277 ## only import to p4/
278 if not line.startswith('p4/') or line == "p4/HEAD":
279 continue
280 branch = line
281
282 # strip off p4
283 branch = re.sub ("^p4/", "", line)
284
285 branches[branch] = parseRevision(line)
286 return branches
287
Simon Hausmann9ceab362007-06-22 00:01:57 +0200288def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200289 branches = p4BranchesInGit()
290 # map from depot-path to branch name
291 branchByDepotPath = {}
292 for branch in branches.keys():
293 tip = branches[branch]
294 log = extractLogMessageFromGitCommit(tip)
295 settings = extractSettingsGitLog(log)
296 if settings.has_key("depot-paths"):
297 paths = ",".join(settings["depot-paths"])
298 branchByDepotPath[paths] = "remotes/p4/" + branch
299
Simon Hausmann27d2d812007-06-12 14:31:59 +0200300 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200301 parent = 0
302 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200303 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200304 log = extractLogMessageFromGitCommit(commit)
305 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200306 if settings.has_key("depot-paths"):
307 paths = ",".join(settings["depot-paths"])
308 if branchByDepotPath.has_key(paths):
309 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200310
Simon Hausmann86506fe2007-07-18 12:40:12 +0200311 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200312
Simon Hausmann86506fe2007-07-18 12:40:12 +0200313 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200314
Simon Hausmann5ca44612007-08-24 17:44:16 +0200315def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
316 if not silent:
317 print ("Creating/updating branch(es) in %s based on origin branch(es)"
318 % localRefPrefix)
319
320 originPrefix = "origin/p4/"
321
322 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
323 line = line.strip()
324 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
325 continue
326
327 headName = line[len(originPrefix):]
328 remoteHead = localRefPrefix + headName
329 originHead = line
330
331 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
332 if (not original.has_key('depot-paths')
333 or not original.has_key('change')):
334 continue
335
336 update = False
337 if not gitBranchExists(remoteHead):
338 if verbose:
339 print "creating %s" % remoteHead
340 update = True
341 else:
342 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
343 if settings.has_key('change') > 0:
344 if settings['depot-paths'] == original['depot-paths']:
345 originP4Change = int(original['change'])
346 p4Change = int(settings['change'])
347 if originP4Change > p4Change:
348 print ("%s (%s) is newer than %s (%s). "
349 "Updating p4 branch from origin."
350 % (originHead, originP4Change,
351 remoteHead, p4Change))
352 update = True
353 else:
354 print ("Ignoring: %s was imported from %s while "
355 "%s was imported from %s"
356 % (originHead, ','.join(original['depot-paths']),
357 remoteHead, ','.join(settings['depot-paths'])))
358
359 if update:
360 system("git update-ref %s %s" % (remoteHead, originHead))
361
362def originP4BranchesExist():
363 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
364
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200365def p4ChangesForPaths(depotPaths, changeRange):
366 assert depotPaths
367 output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange)
368 for p in depotPaths]))
369
370 changes = []
371 for line in output:
372 changeNum = line.split(" ")[1]
373 changes.append(int(changeNum))
374
375 changes.sort()
376 return changes
377
Simon Hausmannb9847332007-03-20 20:54:23 +0100378class Command:
379 def __init__(self):
380 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200381 self.needsGit = True
Simon Hausmannb9847332007-03-20 20:54:23 +0100382
383class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100384 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100385 Command.__init__(self)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100386 self.options = [
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300387 optparse.make_option("--verbose", dest="verbose", action="store_true",
388 default=False),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300389 ]
Simon Hausmannc8c39112007-03-19 21:02:30 +0100390 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200391 self.needsGit = False
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300392 self.verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100393
394 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300395 j = 0
Simon Hausmann86949ee2007-03-19 20:59:12 +0100396 for output in p4CmdList(" ".join(args)):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300397 print 'Element: %d' % j
398 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100399 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100400 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100401
Simon Hausmann58346842007-05-21 22:57:06 +0200402class P4RollBack(Command):
403 def __init__(self):
404 Command.__init__(self)
405 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +0200406 optparse.make_option("--verbose", dest="verbose", action="store_true"),
407 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +0200408 ]
409 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann52102d42007-05-21 23:44:24 +0200410 self.verbose = False
Simon Hausmann0c66a782007-05-23 20:07:57 +0200411 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +0200412
413 def run(self, args):
414 if len(args) != 1:
415 return False
416 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +0200417
Simon Hausmannad192f22007-05-23 23:44:19 +0200418 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +0200419 die("Problems executing p4");
420
Simon Hausmann0c66a782007-05-23 20:07:57 +0200421 if self.rollbackLocalBranches:
422 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300423 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200424 else:
425 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300426 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200427
428 for line in lines:
429 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300430 line = line.strip()
431 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +0200432 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300433 settings = extractSettingsGitLog(log)
434
435 depotPaths = settings['depot-paths']
436 change = settings['change']
437
Simon Hausmann58346842007-05-21 22:57:06 +0200438 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +0200439
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300440 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
441 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +0200442 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
443 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
444 continue
445
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300446 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +0200447 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +0200448 if self.verbose:
449 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +0200450 system("git update-ref %s \"%s^\"" % (ref, ref))
451 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300452 settings = extractSettingsGitLog(log)
453
454
455 depotPaths = settings['depot-paths']
456 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +0200457
458 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +0200459 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +0200460
461 return True
462
Simon Hausmann711544b2007-04-01 15:40:46 +0200463class P4Submit(Command):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100464 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +0100465 Command.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100466 self.options = [
467 optparse.make_option("--continue", action="store_false", dest="firstTime"),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300468 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100469 optparse.make_option("--origin", dest="origin"),
470 optparse.make_option("--reset", action="store_true", dest="reset"),
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200471 optparse.make_option("--direct", dest="directSubmit", action="store_true"),
Chris Pettittd9a5f252007-10-15 22:15:06 -0700472 optparse.make_option("-M", dest="detectRename", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100473 ]
474 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200475 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100476 self.firstTime = True
477 self.reset = False
478 self.interactive = True
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100479 self.firstTime = True
Simon Hausmann95124972007-03-23 09:16:07 +0100480 self.origin = ""
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200481 self.directSubmit = False
Chris Pettittd9a5f252007-10-15 22:15:06 -0700482 self.detectRename = False
Simon Hausmannb0d10df2007-06-07 13:09:14 +0200483 self.verbose = False
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200484 self.isWindows = (platform.system() == "Windows")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100485
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100486 def check(self):
487 if len(p4CmdList("opened ...")) > 0:
488 die("You have files opened with perforce! Close them before starting the sync.")
489
490 def start(self):
491 if len(self.config) > 0 and not self.reset:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300492 die("Cannot start sync. Previous sync config found at %s\n"
493 "If you want to start submitting again from scratch "
494 "maybe you want to call git-p4 submit --reset" % self.configFile)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100495
496 commits = []
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200497 if self.directSubmit:
498 commits.append("0")
499 else:
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300500 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300501 commits.append(line.strip())
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200502 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100503
504 self.config["commits"] = commits
505
Simon Hausmannedae1e22008-02-19 09:29:06 +0100506 # replaces everything between 'Description:' and the next P4 submit template field with the
507 # commit message
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100508 def prepareLogMessage(self, template, message):
509 result = ""
510
Simon Hausmannedae1e22008-02-19 09:29:06 +0100511 inDescriptionSection = False
512
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100513 for line in template.split("\n"):
514 if line.startswith("#"):
515 result += line + "\n"
516 continue
517
Simon Hausmannedae1e22008-02-19 09:29:06 +0100518 if inDescriptionSection:
519 if line.startswith("Files:"):
520 inDescriptionSection = False
521 else:
522 continue
523 else:
524 if line.startswith("Description:"):
525 inDescriptionSection = True
526 line += "\n"
527 for messageLine in message.split("\n"):
528 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100529
Simon Hausmannedae1e22008-02-19 09:29:06 +0100530 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100531
532 return result
533
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200534 def prepareSubmitTemplate(self):
535 # remove lines in the Files section that show changes to files outside the depot path we're committing into
536 template = ""
537 inFilesSection = False
538 for line in read_pipe_lines("p4 change -o"):
539 if inFilesSection:
540 if line.startswith("\t"):
541 # path starts and ends with a tab
542 path = line[1:]
543 lastTab = path.rfind("\t")
544 if lastTab != -1:
545 path = path[:lastTab]
546 if not path.startswith(self.depotPath):
547 continue
548 else:
549 inFilesSection = False
550 else:
551 if line.startswith("Files:"):
552 inFilesSection = True
553
554 template += line
555
556 return template
557
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300558 def applyCommit(self, id):
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200559 if self.directSubmit:
560 print "Applying local change in working directory/index"
561 diff = self.diffStatus
562 else:
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300563 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
Chris Pettittd9a5f252007-10-15 22:15:06 -0700564 diffOpts = ("", "-M")[self.detectRename]
Chris Pettittb43b0a32007-11-01 20:43:13 -0700565 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100566 filesToAdd = set()
567 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +0200568 editedFiles = set()
Chris Pettittc65b6702007-11-01 20:43:14 -0700569 filesToChangeExecBit = {}
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100570 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -0700571 diff = parseDiffTreeEntry(line)
572 modifier = diff['status']
573 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100574 if modifier == "M":
Simon Hausmannd336c152007-05-16 09:41:26 +0200575 system("p4 edit \"%s\"" % path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700576 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
577 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +0200578 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100579 elif modifier == "A":
580 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700581 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100582 if path in filesToDelete:
583 filesToDelete.remove(path)
584 elif modifier == "D":
585 filesToDelete.add(path)
586 if path in filesToAdd:
587 filesToAdd.remove(path)
Chris Pettittd9a5f252007-10-15 22:15:06 -0700588 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -0700589 src, dest = diff['src'], diff['dst']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700590 system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest))
591 system("p4 edit \"%s\"" % (dest))
Chris Pettittc65b6702007-11-01 20:43:14 -0700592 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
593 filesToChangeExecBit[dest] = diff['dst_mode']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700594 os.unlink(dest)
595 editedFiles.add(dest)
596 filesToDelete.add(src)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100597 else:
598 die("unknown modifier %s for %s" % (modifier, path))
599
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200600 if self.directSubmit:
601 diffcmd = "cat \"%s\"" % self.diffFile
602 else:
603 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
Simon Hausmann47a130b2007-05-20 16:33:21 +0200604 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200605 tryPatchCmd = patchcmd + "--check -"
606 applyPatchCmd = patchcmd + "--check --apply -"
Simon Hausmann51a26402007-04-15 09:59:56 +0200607
Simon Hausmann47a130b2007-05-20 16:33:21 +0200608 if os.system(tryPatchCmd) != 0:
Simon Hausmann51a26402007-04-15 09:59:56 +0200609 print "Unfortunately applying the change failed!"
610 print "What do you want to do?"
611 response = "x"
612 while response != "s" and response != "a" and response != "w":
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300613 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
614 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
Simon Hausmann51a26402007-04-15 09:59:56 +0200615 if response == "s":
616 print "Skipping! Good luck with the next patches..."
Simon Hausmann20947142007-09-13 22:10:18 +0200617 for f in editedFiles:
618 system("p4 revert \"%s\"" % f);
619 for f in filesToAdd:
620 system("rm %s" %f)
Simon Hausmann51a26402007-04-15 09:59:56 +0200621 return
622 elif response == "a":
Simon Hausmann47a130b2007-05-20 16:33:21 +0200623 os.system(applyPatchCmd)
Simon Hausmann51a26402007-04-15 09:59:56 +0200624 if len(filesToAdd) > 0:
625 print "You may also want to call p4 add on the following files:"
626 print " ".join(filesToAdd)
627 if len(filesToDelete):
628 print "The following files should be scheduled for deletion with p4 delete:"
629 print " ".join(filesToDelete)
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300630 die("Please resolve and submit the conflict manually and "
631 + "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200632 elif response == "w":
633 system(diffcmd + " > patch.txt")
634 print "Patch saved to patch.txt in %s !" % self.clientPath
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300635 die("Please resolve and submit the conflict manually and "
636 "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200637
Simon Hausmann47a130b2007-05-20 16:33:21 +0200638 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100639
640 for f in filesToAdd:
Simon Hausmanne6b711f2007-06-11 23:40:25 +0200641 system("p4 add \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100642 for f in filesToDelete:
Simon Hausmanne6b711f2007-06-11 23:40:25 +0200643 system("p4 revert \"%s\"" % f)
644 system("p4 delete \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100645
Chris Pettittc65b6702007-11-01 20:43:14 -0700646 # Set/clear executable bits
647 for f in filesToChangeExecBit.keys():
648 mode = filesToChangeExecBit[f]
649 setP4ExecBit(f, mode)
650
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200651 logMessage = ""
652 if not self.directSubmit:
653 logMessage = extractLogMessageFromGitCommit(id)
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200654 if self.isWindows:
655 logMessage = logMessage.replace("\n", "\r\n")
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300656 logMessage = logMessage.strip()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100657
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200658 template = self.prepareSubmitTemplate()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100659
660 if self.interactive:
661 submitTemplate = self.prepareLogMessage(template, logMessage)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300662 diff = read_pipe("p4 diff -du ...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100663
664 for newFile in filesToAdd:
665 diff += "==== new file ====\n"
666 diff += "--- /dev/null\n"
667 diff += "+++ %s\n" % newFile
668 f = open(newFile, "r")
669 for line in f.readlines():
670 diff += "+" + line
671 f.close()
672
Simon Hausmann25df95c2007-05-15 15:15:39 +0200673 separatorLine = "######## everything below this line is just the diff #######"
674 if platform.system() == "Windows":
675 separatorLine += "\r"
676 separatorLine += "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100677
Simon Hausmanne96e4002008-01-04 14:27:55 +0100678 [handle, fileName] = tempfile.mkstemp()
679 tmpFile = os.fdopen(handle, "w+")
680 tmpFile.write(submitTemplate + separatorLine + diff)
681 tmpFile.close()
682 defaultEditor = "vi"
683 if platform.system() == "Windows":
684 defaultEditor = "notepad"
685 editor = os.environ.get("EDITOR", defaultEditor);
686 system(editor + " " + fileName)
687 tmpFile = open(fileName, "rb")
688 message = tmpFile.read()
689 tmpFile.close()
690 os.remove(fileName)
691 submitTemplate = message[:message.index(separatorLine)]
692 if self.isWindows:
693 submitTemplate = submitTemplate.replace("\r\n", "\n")
Simon Hausmanncb4f1282007-05-25 22:34:30 +0200694
Simon Hausmanne96e4002008-01-04 14:27:55 +0100695 if self.directSubmit:
696 print "Submitting to git first"
697 os.chdir(self.oldWorkingDirectory)
698 write_pipe("git commit -a -F -", submitTemplate)
699 os.chdir(self.clientPath)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100700
Simon Hausmanne96e4002008-01-04 14:27:55 +0100701 write_pipe("p4 submit -i", submitTemplate)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100702 else:
703 fileName = "submit.txt"
704 file = open(fileName, "w+")
705 file.write(self.prepareLogMessage(template, logMessage))
706 file.close()
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300707 print ("Perforce submit template written as %s. "
708 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
709 % (fileName, fileName))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100710
711 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200712 if len(args) == 0:
713 self.master = currentGitBranch()
Simon Hausmann4280e532007-05-25 08:49:18 +0200714 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200715 die("Detecting current git branch failed!")
716 elif len(args) == 1:
717 self.master = args[0]
718 else:
719 return False
720
Simon Hausmann27d2d812007-06-12 14:31:59 +0200721 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200722 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200723 if len(self.origin) == 0:
724 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200725
726 if self.verbose:
727 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +0100728
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200729 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +0100730 print "Internal error: cannot locate perforce depot path from existing branches"
731 sys.exit(128)
732
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200733 self.clientPath = p4Where(self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +0100734
Simon Hausmann51a26402007-04-15 09:59:56 +0200735 if len(self.clientPath) == 0:
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200736 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
Simon Hausmann95124972007-03-23 09:16:07 +0100737 sys.exit(128)
738
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200739 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +0200740 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200741
742 if self.directSubmit:
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300743 self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
Simon Hausmanncbf5efa2007-05-21 10:08:11 +0200744 if len(self.diffStatus) == 0:
745 print "No changes in working directory to submit."
746 return True
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300747 patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -0300748 self.diffFile = self.gitdir + "/p4-git-diff"
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200749 f = open(self.diffFile, "wb")
750 f.write(patch)
751 f.close();
752
Simon Hausmann51a26402007-04-15 09:59:56 +0200753 os.chdir(self.clientPath)
Simon Hausmann31f9ec12007-08-21 11:53:02 +0200754 print "Syncronizing p4 checkout..."
755 system("p4 sync ...")
Simon Hausmann95124972007-03-23 09:16:07 +0100756
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100757 if self.reset:
758 self.firstTime = True
759
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100760 self.check()
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -0300761 self.configFile = self.gitdir + "/p4-git-sync.cfg"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100762 self.config = shelve.open(self.configFile, writeback=True)
763
764 if self.firstTime:
765 self.start()
766
767 commits = self.config.get("commits", [])
768
769 while len(commits) > 0:
770 self.firstTime = False
771 commit = commits[0]
772 commits = commits[1:]
773 self.config["commits"] = commits
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300774 self.applyCommit(commit)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100775 if not self.interactive:
776 break
777
778 self.config.close()
779
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200780 if self.directSubmit:
781 os.remove(self.diffFile)
782
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100783 if len(commits) == 0:
784 if self.firstTime:
785 print "No changes found to apply between %s and current HEAD" % self.origin
786 else:
787 print "All changes applied!"
Simon Hausmann7944f142007-05-21 11:04:26 +0200788 os.chdir(self.oldWorkingDirectory)
Simon Hausmann14594f42007-08-22 09:07:15 +0200789
790 sync = P4Sync()
791 sync.run([])
792
Simon Hausmanne96e4002008-01-04 14:27:55 +0100793 rebase = P4Rebase()
794 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100795 os.remove(self.configFile)
796
Simon Hausmannb9847332007-03-20 20:54:23 +0100797 return True
798
Simon Hausmann711544b2007-04-01 15:40:46 +0200799class P4Sync(Command):
Simon Hausmannb9847332007-03-20 20:54:23 +0100800 def __init__(self):
801 Command.__init__(self)
802 self.options = [
803 optparse.make_option("--branch", dest="branch"),
804 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
805 optparse.make_option("--changesfile", dest="changesFile"),
806 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +0200807 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Simon Hausmanna028a982007-05-23 00:03:08 +0200808 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300809 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
810 help="Import into refs/heads/ , not refs/remotes"),
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300811 optparse.make_option("--max-changes", dest="maxChanges"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300812 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
813 help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
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 = []
Simon Hausmannb9847332007-03-20 20:54:23 +0100840
Simon Hausmann01265102007-05-25 10:36:10 +0200841 if gitConfig("git-p4.syncFromOrigin") == "false":
842 self.syncWithOrigin = False
843
Simon Hausmannb9847332007-03-20 20:54:23 +0100844 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -0800845 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
846 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +0100847 files = []
848 fnum = 0
849 while commit.has_key("depotFile%s" % fnum):
850 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300851
Tommy Thorn354081d2008-02-03 10:38:51 -0800852 if [p for p in self.cloneExclude
853 if path.startswith (p)]:
854 found = False
855 else:
856 found = [p for p in self.depotPaths
857 if path.startswith (p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300858 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +0100859 fnum = fnum + 1
860 continue
861
862 file = {}
863 file["path"] = path
864 file["rev"] = commit["rev%s" % fnum]
865 file["action"] = commit["action%s" % fnum]
866 file["type"] = commit["type%s" % fnum]
867 files.append(file)
868 fnum = fnum + 1
869 return files
870
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300871 def stripRepoPath(self, path, prefixes):
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300872 if self.keepRepoPath:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300873 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300874
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300875 for p in prefixes:
876 if path.startswith(p):
877 path = path[len(p):]
878
879 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300880
Simon Hausmann71b112d2007-05-19 11:54:11 +0200881 def splitFilesIntoBranches(self, commit):
Simon Hausmannd5904672007-05-19 11:07:32 +0200882 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +0200883 fnum = 0
884 while commit.has_key("depotFile%s" % fnum):
885 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300886 found = [p for p in self.depotPaths
887 if path.startswith (p)]
888 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +0200889 fnum = fnum + 1
890 continue
891
892 file = {}
893 file["path"] = path
894 file["rev"] = commit["rev%s" % fnum]
895 file["action"] = commit["action%s" % fnum]
896 file["type"] = commit["type%s" % fnum]
897 fnum = fnum + 1
898
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300899 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +0100900
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200901 for branch in self.knownBranches.keys():
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300902
903 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
904 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +0200905 if branch not in branches:
906 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +0200907 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +0200908 break
Simon Hausmannb9847332007-03-20 20:54:23 +0100909
910 return branches
911
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300912 ## Should move this out, doesn't use SELF.
913 def readP4Files(self, files):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300914 files = [f for f in files
Han-Wen Nienhuys982bb8a2007-05-23 18:49:35 -0300915 if f['action'] != 'delete']
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300916
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300917 if not files:
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -0300918 return
919
Scott Lamb78800192007-07-15 20:58:11 -0700920 filedata = p4CmdList('-x - print',
921 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
922 for f in files]),
923 stdin_mode='w+')
924 if "p4ExitCode" in filedata[0]:
925 die("Problems executing p4. Error: [%d]."
926 % (filedata[0]['p4ExitCode']));
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300927
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300928 j = 0;
929 contents = {}
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300930 while j < len(filedata):
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300931 stat = filedata[j]
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300932 j += 1
933 text = ''
Jason McMullanf3e95122007-12-05 12:16:56 -0500934 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
935 tmp = filedata[j]['data']
936 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
937 tmp = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', tmp)
938 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
939 tmp = re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', tmp)
940 text += tmp
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300941 j += 1
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300942
Han-Wen Nienhuys1b9a4682007-05-23 18:49:35 -0300943
944 if not stat.has_key('depotFile'):
945 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
946 continue
947
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300948 contents[stat['depotFile']] = text
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300949
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300950 for f in files:
951 assert not f.has_key('data')
952 f['data'] = contents[f['path']]
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300953
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300954 def commit(self, details, files, branch, branchPrefixes, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +0100955 epoch = details["time"]
956 author = details["user"]
957
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200958 if self.verbose:
959 print "commit into %s" % branch
960
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -0300961 # start with reading files; if that fails, we should not
962 # create a commit.
963 new_files = []
964 for f in files:
965 if [p for p in branchPrefixes if f['path'].startswith(p)]:
966 new_files.append (f)
967 else:
968 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
969 files = new_files
970 self.readP4Files(files)
971
972
973
974
Simon Hausmannb9847332007-03-20 20:54:23 +0100975 self.gitStream.write("commit %s\n" % branch)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300976# gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +0100977 self.committedChanges.add(int(details["change"]))
978 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +0200979 if author not in self.users:
980 self.getUserMapFromPerforceServer()
Simon Hausmannb9847332007-03-20 20:54:23 +0100981 if author in self.users:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100982 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100983 else:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100984 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100985
986 self.gitStream.write("committer %s\n" % committer)
987
988 self.gitStream.write("data <<EOT\n")
989 self.gitStream.write(details["desc"])
Simon Hausmann6581de02007-06-11 10:01:58 +0200990 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
991 % (','.join (branchPrefixes), details["change"]))
992 if len(details['options']) > 0:
993 self.gitStream.write(": options = %s" % details['options'])
994 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +0100995
996 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200997 if self.verbose:
998 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +0100999 self.gitStream.write("from %s\n" % parent)
1000
Simon Hausmannb9847332007-03-20 20:54:23 +01001001 for file in files:
Simon Hausmannb9847332007-03-20 20:54:23 +01001002 if file["type"] == "apple":
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001003 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
Simon Hausmannb9847332007-03-20 20:54:23 +01001004 continue
1005
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001006 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1007 if file["action"] == "delete":
Simon Hausmannb9847332007-03-20 20:54:23 +01001008 self.gitStream.write("D %s\n" % relPath)
1009 else:
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001010 data = file['data']
Simon Hausmannb9847332007-03-20 20:54:23 +01001011
Simon Hausmann74276ec2007-08-07 12:28:00 +02001012 mode = "644"
David Brownb9fc6ea2007-09-19 13:12:48 -07001013 if isP4Exec(file["type"]):
Simon Hausmann74276ec2007-08-07 12:28:00 +02001014 mode = "755"
1015 elif file["type"] == "symlink":
1016 mode = "120000"
1017 # p4 print on a symlink contains "target\n", so strip it off
1018 data = data[:-1]
1019
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +02001020 if self.isWindows and file["type"].endswith("text"):
1021 data = data.replace("\r\n", "\n")
1022
Simon Hausmann74276ec2007-08-07 12:28:00 +02001023 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
Simon Hausmannb9847332007-03-20 20:54:23 +01001024 self.gitStream.write("data %s\n" % len(data))
1025 self.gitStream.write(data)
1026 self.gitStream.write("\n")
1027
1028 self.gitStream.write("\n")
1029
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001030 change = int(details["change"])
1031
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001032 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001033 label = self.labels[change]
1034 labelDetails = label[0]
1035 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02001036 if self.verbose:
1037 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001038
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001039 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1040 for p in branchPrefixes]))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001041
1042 if len(files) == len(labelRevisions):
1043
1044 cleanedFiles = {}
1045 for info in files:
1046 if info["action"] == "delete":
1047 continue
1048 cleanedFiles[info["depotFile"]] = info["rev"]
1049
1050 if cleanedFiles == labelRevisions:
1051 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1052 self.gitStream.write("from %s\n" % branch)
1053
1054 owner = labelDetails["Owner"]
1055 tagger = ""
1056 if author in self.users:
1057 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1058 else:
1059 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1060 self.gitStream.write("tagger %s\n" % tagger)
1061 self.gitStream.write("data <<EOT\n")
1062 self.gitStream.write(labelDetails["Description"])
1063 self.gitStream.write("EOT\n\n")
1064
1065 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001066 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001067 print ("Tag %s does not match with change %s: files do not match."
1068 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001069
1070 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001071 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001072 print ("Tag %s does not match with change %s: file count is different."
1073 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01001074
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001075 def getUserCacheFilename(self):
Simon Hausmannb2d2d162007-07-25 09:31:38 +02001076 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1077 return home + "/.gitp4-usercache.txt"
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001078
Simon Hausmannb607e712007-05-20 10:55:54 +02001079 def getUserMapFromPerforceServer(self):
Simon Hausmannebd81162007-05-24 00:24:52 +02001080 if self.userMapFromPerforceServer:
1081 return
Simon Hausmannb9847332007-03-20 20:54:23 +01001082 self.users = {}
1083
1084 for output in p4CmdList("users"):
1085 if not output.has_key("User"):
1086 continue
1087 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1088
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001089
1090 s = ''
1091 for (key, val) in self.users.items():
1092 s += "%s\t%s\n" % (key, val)
1093
1094 open(self.getUserCacheFilename(), "wb").write(s)
Simon Hausmannebd81162007-05-24 00:24:52 +02001095 self.userMapFromPerforceServer = True
Simon Hausmannb607e712007-05-20 10:55:54 +02001096
1097 def loadUserMapFromCache(self):
1098 self.users = {}
Simon Hausmannebd81162007-05-24 00:24:52 +02001099 self.userMapFromPerforceServer = False
Simon Hausmannb607e712007-05-20 10:55:54 +02001100 try:
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001101 cache = open(self.getUserCacheFilename(), "rb")
Simon Hausmannb607e712007-05-20 10:55:54 +02001102 lines = cache.readlines()
1103 cache.close()
1104 for line in lines:
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001105 entry = line.strip().split("\t")
Simon Hausmannb607e712007-05-20 10:55:54 +02001106 self.users[entry[0]] = entry[1]
1107 except IOError:
1108 self.getUserMapFromPerforceServer()
1109
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001110 def getLabels(self):
1111 self.labels = {}
1112
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001113 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
Simon Hausmann10c32112007-04-08 10:15:47 +02001114 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08001115 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001116
1117 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001118 label = output["label"]
1119 revisions = {}
1120 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02001121 if self.verbose:
1122 print "Querying files for label %s" % label
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001123 for file in p4CmdList("files "
1124 + ' '.join (["%s...@%s" % (p, label)
1125 for p in self.depotPaths])):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001126 revisions[file["depotFile"]] = file["rev"]
1127 change = int(file["change"])
1128 if change > newestChange:
1129 newestChange = change
1130
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001131 self.labels[newestChange] = [output, revisions]
1132
1133 if self.verbose:
1134 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001135
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001136 def guessProjectName(self):
1137 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02001138 if p.endswith("/"):
1139 p = p[:-1]
1140 p = p[p.strip().rfind("/") + 1:]
1141 if not p.endswith("/"):
1142 p += "/"
1143 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001144
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001145 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001146 lostAndFoundBranches = set()
1147
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001148 for info in p4CmdList("branches"):
1149 details = p4Cmd("branch -o %s" % info["branch"])
1150 viewIdx = 0
1151 while details.has_key("View%s" % viewIdx):
1152 paths = details["View%s" % viewIdx].split(" ")
1153 viewIdx = viewIdx + 1
1154 # require standard //depot/foo/... //depot/bar/... mapping
1155 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1156 continue
1157 source = paths[0]
1158 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02001159 ## HACK
1160 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1161 source = source[len(self.depotPaths[0]):-4]
1162 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001163
Simon Hausmann1a2edf42007-06-17 15:10:24 +02001164 if destination in self.knownBranches:
1165 if not self.silent:
1166 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1167 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1168 continue
1169
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001170 self.knownBranches[destination] = source
1171
1172 lostAndFoundBranches.discard(destination)
1173
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001174 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001175 lostAndFoundBranches.add(source)
1176
1177
1178 for branch in lostAndFoundBranches:
1179 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001180
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001181 def getBranchMappingFromGitBranches(self):
1182 branches = p4BranchesInGit(self.importIntoRemotes)
1183 for branch in branches.keys():
1184 if branch == "master":
1185 branch = "main"
1186 else:
1187 branch = branch[len(self.projectName):]
1188 self.knownBranches[branch] = branch
1189
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001190 def listExistingP4GitBranches(self):
Simon Hausmann144ff462007-07-18 17:27:50 +02001191 # branches holds mapping from name to commit
1192 branches = p4BranchesInGit(self.importIntoRemotes)
1193 self.p4BranchesInGit = branches.keys()
1194 for branch in branches.keys():
1195 self.initialParents[self.refPrefix + branch] = branches[branch]
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001196
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001197 def updateOptionDict(self, d):
1198 option_keys = {}
1199 if self.keepRepoPath:
1200 option_keys['keepRepoPath'] = 1
1201
1202 d["options"] = ' '.join(sorted(option_keys.keys()))
1203
1204 def readOptions(self, d):
1205 self.keepRepoPath = (d.has_key('options')
1206 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001207
Simon Hausmann8134f692007-08-26 16:44:55 +02001208 def gitRefForBranch(self, branch):
1209 if branch == "main":
1210 return self.refPrefix + "master"
1211
1212 if len(branch) <= 0:
1213 return branch
1214
1215 return self.refPrefix + self.projectName + branch
1216
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001217 def gitCommitByP4Change(self, ref, change):
1218 if self.verbose:
1219 print "looking in ref " + ref + " for change %s using bisect..." % change
1220
1221 earliestCommit = ""
1222 latestCommit = parseRevision(ref)
1223
1224 while True:
1225 if self.verbose:
1226 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1227 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1228 if len(next) == 0:
1229 if self.verbose:
1230 print "argh"
1231 return ""
1232 log = extractLogMessageFromGitCommit(next)
1233 settings = extractSettingsGitLog(log)
1234 currentChange = int(settings['change'])
1235 if self.verbose:
1236 print "current change %s" % currentChange
1237
1238 if currentChange == change:
1239 if self.verbose:
1240 print "found %s" % next
1241 return next
1242
1243 if currentChange < change:
1244 earliestCommit = "^%s" % next
1245 else:
1246 latestCommit = "%s" % next
1247
1248 return ""
1249
1250 def importNewBranch(self, branch, maxChange):
1251 # make fast-import flush all changes to disk and update the refs using the checkpoint
1252 # command so that we can try to find the branch parent in the git history
1253 self.gitStream.write("checkpoint\n\n");
1254 self.gitStream.flush();
1255 branchPrefix = self.depotPaths[0] + branch + "/"
1256 range = "@1,%s" % maxChange
1257 #print "prefix" + branchPrefix
1258 changes = p4ChangesForPaths([branchPrefix], range)
1259 if len(changes) <= 0:
1260 return False
1261 firstChange = changes[0]
1262 #print "first change in branch: %s" % firstChange
1263 sourceBranch = self.knownBranches[branch]
1264 sourceDepotPath = self.depotPaths[0] + sourceBranch
1265 sourceRef = self.gitRefForBranch(sourceBranch)
1266 #print "source " + sourceBranch
1267
1268 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1269 #print "branch parent: %s" % branchParentChange
1270 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1271 if len(gitParent) > 0:
1272 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1273 #print "parent git commit: %s" % gitParent
1274
1275 self.importChanges(changes)
1276 return True
1277
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001278 def importChanges(self, changes):
1279 cnt = 1
1280 for change in changes:
1281 description = p4Cmd("describe %s" % change)
1282 self.updateOptionDict(description)
1283
1284 if not self.silent:
1285 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1286 sys.stdout.flush()
1287 cnt = cnt + 1
1288
1289 try:
1290 if self.detectBranches:
1291 branches = self.splitFilesIntoBranches(description)
1292 for branch in branches.keys():
1293 ## HACK --hwn
1294 branchPrefix = self.depotPaths[0] + branch + "/"
1295
1296 parent = ""
1297
1298 filesForCommit = branches[branch]
1299
1300 if self.verbose:
1301 print "branch is %s" % branch
1302
1303 self.updatedBranches.add(branch)
1304
1305 if branch not in self.createdBranches:
1306 self.createdBranches.add(branch)
1307 parent = self.knownBranches[branch]
1308 if parent == branch:
1309 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001310 else:
1311 fullBranch = self.projectName + branch
1312 if fullBranch not in self.p4BranchesInGit:
1313 if not self.silent:
1314 print("\n Importing new branch %s" % fullBranch);
1315 if self.importNewBranch(branch, change - 1):
1316 parent = ""
1317 self.p4BranchesInGit.append(fullBranch)
1318 if not self.silent:
1319 print("\n Resuming with change %s" % change);
1320
1321 if self.verbose:
1322 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001323
Simon Hausmann8134f692007-08-26 16:44:55 +02001324 branch = self.gitRefForBranch(branch)
1325 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001326
1327 if self.verbose:
1328 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1329
1330 if len(parent) == 0 and branch in self.initialParents:
1331 parent = self.initialParents[branch]
1332 del self.initialParents[branch]
1333
1334 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1335 else:
1336 files = self.extractFilesFromCommit(description)
1337 self.commit(description, files, self.branch, self.depotPaths,
1338 self.initialParent)
1339 self.initialParent = ""
1340 except IOError:
1341 print self.gitError.read()
1342 sys.exit(1)
1343
Simon Hausmannc208a242007-08-26 16:07:18 +02001344 def importHeadRevision(self, revision):
1345 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1346
1347 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1348 details["desc"] = ("Initial import of %s from the state at revision %s"
1349 % (' '.join(self.depotPaths), revision))
1350 details["change"] = revision
1351 newestRevision = 0
1352
1353 fileCnt = 0
1354 for info in p4CmdList("files "
1355 + ' '.join(["%s...%s"
1356 % (p, revision)
1357 for p in self.depotPaths])):
1358
1359 if info['code'] == 'error':
1360 sys.stderr.write("p4 returned an error: %s\n"
1361 % info['data'])
1362 sys.exit(1)
1363
1364
1365 change = int(info["change"])
1366 if change > newestRevision:
1367 newestRevision = change
1368
1369 if info["action"] == "delete":
1370 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1371 #fileCnt = fileCnt + 1
1372 continue
1373
1374 for prop in ["depotFile", "rev", "action", "type" ]:
1375 details["%s%s" % (prop, fileCnt)] = info[prop]
1376
1377 fileCnt = fileCnt + 1
1378
1379 details["change"] = newestRevision
1380 self.updateOptionDict(details)
1381 try:
1382 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1383 except IOError:
1384 print "IO error with git fast-import. Is your git version recent enough?"
1385 print self.gitError.read()
1386
1387
Simon Hausmannb9847332007-03-20 20:54:23 +01001388 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001389 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01001390 self.changeRange = ""
1391 self.initialParent = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001392 self.previousDepotPaths = []
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03001393
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001394 # map from branch depot path to parent branch
1395 self.knownBranches = {}
1396 self.initialParents = {}
Simon Hausmann5ca44612007-08-24 17:44:16 +02001397 self.hasOrigin = originP4BranchesExist()
Simon Hausmanna43ff002007-06-11 09:59:27 +02001398 if not self.syncWithOrigin:
1399 self.hasOrigin = False
Simon Hausmann179caeb2007-03-22 22:17:42 +01001400
Simon Hausmanna028a982007-05-23 00:03:08 +02001401 if self.importIntoRemotes:
1402 self.refPrefix = "refs/remotes/p4/"
1403 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001404 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02001405
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001406 if self.syncWithOrigin and self.hasOrigin:
1407 if not self.silent:
1408 print "Syncing with origin first by calling git fetch origin"
1409 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02001410
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001411 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001412 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02001413 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001414 system("git update-ref %s refs/heads/p4" % self.branch)
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001415 system("git branch -D p4");
Simon Hausmannfaf1bd22007-05-21 10:05:30 +02001416 # create it /after/ importing, when master exists
Simon Hausmann0058a332007-08-24 17:46:16 +02001417 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
Simon Hausmanna3c55c02007-05-27 15:48:01 +02001418 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
Simon Hausmann179caeb2007-03-22 22:17:42 +01001419
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001420 # TODO: should always look at previous commits,
1421 # merge with previous imports, if possible.
1422 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02001423 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02001424 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Simon Hausmannabcd7902007-05-24 22:25:36 +02001425 self.listExistingP4GitBranches()
1426
1427 if len(self.p4BranchesInGit) > 1:
1428 if not self.silent:
1429 print "Importing from/into multiple branches"
1430 self.detectBranches = True
Simon Hausmann967f72e2007-03-23 09:30:41 +01001431
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001432 if self.verbose:
1433 print "branches: %s" % self.p4BranchesInGit
1434
1435 p4Change = 0
1436 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001437 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001438
1439 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001440
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001441 self.readOptions(settings)
1442 if (settings.has_key('depot-paths')
1443 and settings.has_key ('change')):
1444 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001445 p4Change = max(p4Change, change)
1446
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001447 depotPaths = sorted(settings['depot-paths'])
1448 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001449 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001450 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001451 paths = []
1452 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Simon Hausmann583e1702007-06-07 09:37:13 +02001453 for i in range(0, min(len(cur), len(prev))):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001454 if cur[i] <> prev[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02001455 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001456 break
1457
Simon Hausmann583e1702007-06-07 09:37:13 +02001458 paths.append (cur[:i + 1])
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001459
1460 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001461
1462 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001463 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02001464 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann330f53b2007-06-07 09:39:51 +02001465 if not self.detectBranches:
1466 self.initialParent = parseRevision(self.branch)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001467 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01001468 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001469
Simon Hausmannf9162f62007-05-17 09:02:45 +02001470 if not self.branch.startswith("refs/"):
1471 self.branch = "refs/heads/" + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01001472
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001473 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01001474 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001475 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01001476 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001477 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001478 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001479 "This doesn't work!" % (' '.join (self.depotPaths),
1480 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01001481 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001482
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001483 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01001484
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001485 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01001486 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01001487
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001488 newPaths = []
1489 for p in self.depotPaths:
1490 if p.find("@") != -1:
1491 atIdx = p.index("@")
1492 self.changeRange = p[atIdx:]
1493 if self.changeRange == "@all":
1494 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001495 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001496 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001497 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001498 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001499 elif p.find("#") != -1:
1500 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001501 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001502 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001503 elif self.previousDepotPaths == []:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001504 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01001505
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001506 p = re.sub ("\.\.\.$", "", p)
1507 if not p.endswith("/"):
1508 p += "/"
1509
1510 newPaths.append(p)
1511
1512 self.depotPaths = newPaths
1513
Simon Hausmannb9847332007-03-20 20:54:23 +01001514
Simon Hausmannb607e712007-05-20 10:55:54 +02001515 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02001516 self.labels = {}
1517 if self.detectLabels:
1518 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01001519
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001520 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02001521 ## FIXME - what's a P4 projectName ?
1522 self.projectName = self.guessProjectName()
1523
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001524 if self.hasOrigin:
1525 self.getBranchMappingFromGitBranches()
1526 else:
1527 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001528 if self.verbose:
1529 print "p4-git branches: %s" % self.p4BranchesInGit
1530 print "initial parents: %s" % self.initialParents
1531 for b in self.p4BranchesInGit:
1532 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001533
1534 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001535 b = b[len(self.projectName):]
1536 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001537
Simon Hausmannf291b4e2007-04-14 11:21:50 +02001538 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01001539
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001540 importProcess = subprocess.Popen(["git", "fast-import"],
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001541 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1542 stderr=subprocess.PIPE);
Simon Hausmann08483582007-05-15 14:31:06 +02001543 self.gitOutput = importProcess.stdout
1544 self.gitStream = importProcess.stdin
1545 self.gitError = importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01001546
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001547 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02001548 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01001549 else:
1550 changes = []
1551
Simon Hausmann0828ab12007-03-20 20:59:30 +01001552 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01001553 output = open(self.changesFile).readlines()
1554 changeSet = Set()
1555 for line in output:
1556 changeSet.add(int(line))
1557
1558 for change in changeSet:
1559 changes.append(change)
1560
1561 changes.sort()
1562 else:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001563 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001564 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001565 self.changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001566 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
Simon Hausmannb9847332007-03-20 20:54:23 +01001567
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001568 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001569 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001570
Simon Hausmannb9847332007-03-20 20:54:23 +01001571 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001572 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001573 print "No changes to import!"
Simon Hausmann1f52af62007-04-08 00:07:02 +02001574 return True
Simon Hausmannb9847332007-03-20 20:54:23 +01001575
Simon Hausmanna9d1a272007-06-11 23:28:03 +02001576 if not self.silent and not self.detectBranches:
1577 print "Import destination: %s" % self.branch
1578
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001579 self.updatedBranches = set()
1580
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001581 self.importChanges(changes)
Simon Hausmannb9847332007-03-20 20:54:23 +01001582
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001583 if not self.silent:
1584 print ""
1585 if len(self.updatedBranches) > 0:
1586 sys.stdout.write("Updated branches: ")
1587 for b in self.updatedBranches:
1588 sys.stdout.write("%s " % b)
1589 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001590
Simon Hausmannb9847332007-03-20 20:54:23 +01001591 self.gitStream.close()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001592 if importProcess.wait() != 0:
1593 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01001594 self.gitOutput.close()
1595 self.gitError.close()
1596
Simon Hausmannb9847332007-03-20 20:54:23 +01001597 return True
1598
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001599class P4Rebase(Command):
1600 def __init__(self):
1601 Command.__init__(self)
Simon Hausmann01265102007-05-25 10:36:10 +02001602 self.options = [ ]
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001603 self.description = ("Fetches the latest revision from perforce and "
1604 + "rebases the current work (branch) against it")
Simon Hausmann68c42152007-06-07 12:51:03 +02001605 self.verbose = False
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001606
1607 def run(self, args):
1608 sync = P4Sync()
1609 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02001610
Simon Hausmann14594f42007-08-22 09:07:15 +02001611 return self.rebase()
1612
1613 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01001614 if os.system("git update-index --refresh") != 0:
1615 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.");
1616 if len(read_pipe("git diff-index HEAD --")) > 0:
1617 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1618
Simon Hausmannd7e38682007-06-12 14:34:46 +02001619 [upstream, settings] = findUpstreamBranchPoint()
1620 if len(upstream) == 0:
1621 die("Cannot find upstream branchpoint for rebase")
1622
1623 # the branchpoint may be p4/foo~3, so strip off the parent
1624 upstream = re.sub("~[0-9]+$", "", upstream)
1625
1626 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001627 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02001628 system("git rebase %s" % upstream)
Simon Hausmann1f52af62007-04-08 00:07:02 +02001629 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001630 return True
1631
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001632class P4Clone(P4Sync):
1633 def __init__(self):
1634 P4Sync.__init__(self)
1635 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001636 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08001637 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001638 optparse.make_option("--destination", dest="cloneDestination",
1639 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08001640 help="where to leave result of the clone"),
1641 optparse.make_option("-/", dest="cloneExclude",
1642 action="append", type="string",
1643 help="exclude depot path")
1644 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001645 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001646 self.needsGit = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001647
Tommy Thorn354081d2008-02-03 10:38:51 -08001648 # This is required for the "append" cloneExclude action
1649 def ensure_value(self, attr, value):
1650 if not hasattr(self, attr) or getattr(self, attr) is None:
1651 setattr(self, attr, value)
1652 return getattr(self, attr)
1653
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001654 def defaultDestination(self, args):
1655 ## TODO: use common prefix of args?
1656 depotPath = args[0]
1657 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1658 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13001659 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001660 depotDir = re.sub(r"/$", "", depotDir)
1661 return os.path.split(depotDir)[1]
1662
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001663 def run(self, args):
1664 if len(args) < 1:
1665 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001666
1667 if self.keepRepoPath and not self.cloneDestination:
1668 sys.stderr.write("Must specify destination for --keep-path\n")
1669 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001670
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001671 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02001672
1673 if not self.cloneDestination and len(depotPaths) > 1:
1674 self.cloneDestination = depotPaths[-1]
1675 depotPaths = depotPaths[:-1]
1676
Tommy Thorn354081d2008-02-03 10:38:51 -08001677 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001678 for p in depotPaths:
1679 if not p.startswith("//"):
1680 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001681
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001682 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02001683 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001684
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001685 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Kevin Greenc3bf3f12007-06-11 16:48:07 -04001686 if not os.path.exists(self.cloneDestination):
1687 os.makedirs(self.cloneDestination)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001688 os.chdir(self.cloneDestination)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001689 system("git init")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001690 self.gitdir = os.getcwd() + "/.git"
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001691 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001692 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001693 if self.branch != "master":
Simon Hausmann8f9b2e02007-05-18 22:13:26 +02001694 if gitBranchExists("refs/remotes/p4/master"):
1695 system("git branch master refs/remotes/p4/master")
1696 system("git checkout -f")
1697 else:
1698 print "Could not detect main branch. No checkout/master branch created."
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001699
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001700 return True
1701
Simon Hausmann09d89de2007-06-20 23:10:28 +02001702class P4Branches(Command):
1703 def __init__(self):
1704 Command.__init__(self)
1705 self.options = [ ]
1706 self.description = ("Shows the git branches that hold imports and their "
1707 + "corresponding perforce depot paths")
1708 self.verbose = False
1709
1710 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02001711 if originP4BranchesExist():
1712 createOrUpdateBranchesFromOrigin()
1713
Simon Hausmann09d89de2007-06-20 23:10:28 +02001714 cmdline = "git rev-parse --symbolic "
1715 cmdline += " --remotes"
1716
1717 for line in read_pipe_lines(cmdline):
1718 line = line.strip()
1719
1720 if not line.startswith('p4/') or line == "p4/HEAD":
1721 continue
1722 branch = line
1723
1724 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1725 settings = extractSettingsGitLog(log)
1726
1727 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1728 return True
1729
Simon Hausmannb9847332007-03-20 20:54:23 +01001730class HelpFormatter(optparse.IndentedHelpFormatter):
1731 def __init__(self):
1732 optparse.IndentedHelpFormatter.__init__(self)
1733
1734 def format_description(self, description):
1735 if description:
1736 return description + "\n"
1737 else:
1738 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001739
Simon Hausmann86949ee2007-03-19 20:59:12 +01001740def printUsage(commands):
1741 print "usage: %s <command> [options]" % sys.argv[0]
1742 print ""
1743 print "valid commands: %s" % ", ".join(commands)
1744 print ""
1745 print "Try %s <command> --help for command specific help." % sys.argv[0]
1746 print ""
1747
1748commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001749 "debug" : P4Debug,
1750 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02001751 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001752 "sync" : P4Sync,
1753 "rebase" : P4Rebase,
1754 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02001755 "rollback" : P4RollBack,
1756 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01001757}
1758
Simon Hausmann86949ee2007-03-19 20:59:12 +01001759
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001760def main():
1761 if len(sys.argv[1:]) == 0:
1762 printUsage(commands.keys())
1763 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001764
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001765 cmd = ""
1766 cmdName = sys.argv[1]
1767 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001768 klass = commands[cmdName]
1769 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001770 except KeyError:
1771 print "unknown command %s" % cmdName
1772 print ""
1773 printUsage(commands.keys())
1774 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001775
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001776 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001777 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001778
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001779 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001780
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001781 if len(options) > 0:
1782 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001783
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001784 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1785 options,
1786 description = cmd.description,
1787 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01001788
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001789 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1790 global verbose
1791 verbose = cmd.verbose
1792 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001793 if cmd.gitdir == None:
1794 cmd.gitdir = os.path.abspath(".git")
1795 if not isValidGitDir(cmd.gitdir):
1796 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1797 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001798 cdup = read_pipe("git rev-parse --show-cdup").strip()
1799 if len(cdup) > 0:
1800 os.chdir(cdup);
1801
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001802 if not isValidGitDir(cmd.gitdir):
1803 if isValidGitDir(cmd.gitdir + "/.git"):
1804 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001805 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001806 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02001807
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001808 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001809
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001810 if not cmd.run(args):
1811 parser.print_help()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001812
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001813
1814if __name__ == '__main__':
1815 main()