blob: 2b6ea74d1c6224f8745bb4dd99770430f38ad247 [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 """
27 real_cmd = "%s %s" % ("p4", cmd)
Anand Kumriaee064272008-08-10 19:26:29 +010028 if verbose:
29 print real_cmd
Anand Kumria21a50752008-08-10 19:26:28 +010030 return real_cmd
31
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030032def die(msg):
33 if verbose:
34 raise Exception(msg)
35 else:
36 sys.stderr.write(msg + "\n")
37 sys.exit(1)
38
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030039def write_pipe(c, str):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030040 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030041 sys.stderr.write('Writing pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030042
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030043 pipe = os.popen(c, 'w')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030044 val = pipe.write(str)
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030045 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030046 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030047
48 return val
49
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030050def read_pipe(c, ignore_error=False):
51 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030052 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030053
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030054 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030055 val = pipe.read()
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030056 if pipe.close() and not ignore_error:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030057 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030058
59 return val
60
61
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030062def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030063 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030064 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030065 ## todo: check return status
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030066 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030067 val = pipe.readlines()
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030068 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030069 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030070
71 return val
Simon Hausmanncaace112007-05-15 14:57:57 +020072
Anand Kumria23181212008-08-10 19:26:24 +010073def p4_read_pipe_lines(c):
74 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +010075 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +010076 return read_pipe_lines(real_cmd)
77
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -030078def system(cmd):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030079 if verbose:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -030080 sys.stderr.write("executing %s\n" % cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -030081 if os.system(cmd) != 0:
82 die("command failed: %s" % cmd)
83
Anand Kumriabf9320f2008-08-10 19:26:26 +010084def p4_system(cmd):
85 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +010086 real_cmd = p4_build_cmd(cmd)
Anand Kumriabf9320f2008-08-10 19:26:26 +010087 return system(real_cmd)
88
David Brownb9fc6ea2007-09-19 13:12:48 -070089def isP4Exec(kind):
90 """Determine if a Perforce 'kind' should have execute permission
91
92 'p4 help filetypes' gives a list of the types. If it starts with 'x',
93 or x follows one of a few letters. Otherwise, if there is an 'x' after
94 a plus sign, it is also executable"""
95 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
96
Chris Pettittc65b6702007-11-01 20:43:14 -070097def setP4ExecBit(file, mode):
98 # Reopens an already open file and changes the execute bit to match
99 # the execute bit setting in the passed in mode.
100
101 p4Type = "+x"
102
103 if not isModeExec(mode):
104 p4Type = getP4OpenedType(file)
105 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
106 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
107 if p4Type[-1] == "+":
108 p4Type = p4Type[0:-1]
109
Anand Kumria87b611d2008-08-10 19:26:27 +0100110 p4_system("reopen -t %s %s" % (p4Type, file))
Chris Pettittc65b6702007-11-01 20:43:14 -0700111
112def getP4OpenedType(file):
113 # Returns the perforce file type for the given file.
114
115 result = read_pipe("p4 opened %s" % file)
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100116 match = re.match(".*\((.+)\)\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700117 if match:
118 return match.group(1)
119 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100120 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700121
Chris Pettittb43b0a32007-11-01 20:43:13 -0700122def diffTreePattern():
123 # This is a simple generator for the diff tree regex pattern. This could be
124 # a class variable if this and parseDiffTreeEntry were a part of a class.
125 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
126 while True:
127 yield pattern
128
129def parseDiffTreeEntry(entry):
130 """Parses a single diff tree entry into its component elements.
131
132 See git-diff-tree(1) manpage for details about the format of the diff
133 output. This method returns a dictionary with the following elements:
134
135 src_mode - The mode of the source file
136 dst_mode - The mode of the destination file
137 src_sha1 - The sha1 for the source file
138 dst_sha1 - The sha1 fr the destination file
139 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
140 status_score - The score for the status (applicable for 'C' and 'R'
141 statuses). This is None if there is no score.
142 src - The path for the source file.
143 dst - The path for the destination file. This is only present for
144 copy or renames. If it is not present, this is None.
145
146 If the pattern is not matched, None is returned."""
147
148 match = diffTreePattern().next().match(entry)
149 if match:
150 return {
151 'src_mode': match.group(1),
152 'dst_mode': match.group(2),
153 'src_sha1': match.group(3),
154 'dst_sha1': match.group(4),
155 'status': match.group(5),
156 'status_score': match.group(6),
157 'src': match.group(7),
158 'dst': match.group(10)
159 }
160 return None
161
Chris Pettittc65b6702007-11-01 20:43:14 -0700162def isModeExec(mode):
163 # Returns True if the given git mode represents an executable file,
164 # otherwise False.
165 return mode[-3:] == "755"
166
167def isModeExecChanged(src_mode, dst_mode):
168 return isModeExec(src_mode) != isModeExec(dst_mode)
169
Scott Lamb9f90c732007-07-15 20:58:10 -0700170def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
Anand Kumria155af832008-08-10 19:26:30 +0100171 cmd = p4_build_cmd("-G %s" % (cmd))
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300172 if verbose:
173 sys.stderr.write("Opening pipe: %s\n" % cmd)
Scott Lamb9f90c732007-07-15 20:58:10 -0700174
175 # Use a temporary file to avoid deadlocks without
176 # subprocess.communicate(), which would put another copy
177 # of stdout into memory.
178 stdin_file = None
179 if stdin is not None:
180 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
181 stdin_file.write(stdin)
182 stdin_file.flush()
183 stdin_file.seek(0)
184
185 p4 = subprocess.Popen(cmd, shell=True,
186 stdin=stdin_file,
187 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100188
189 result = []
190 try:
191 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700192 entry = marshal.load(p4.stdout)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100193 result.append(entry)
194 except EOFError:
195 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700196 exitCode = p4.wait()
197 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200198 entry = {}
199 entry["p4ExitCode"] = exitCode
200 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100201
202 return result
203
204def p4Cmd(cmd):
205 list = p4CmdList(cmd)
206 result = {}
207 for entry in list:
208 result.update(entry)
209 return result;
210
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100211def p4Where(depotPath):
212 if not depotPath.endswith("/"):
213 depotPath += "/"
214 output = p4Cmd("where %s..." % depotPath)
Simon Hausmanndc524032007-05-21 09:34:56 +0200215 if output["code"] == "error":
216 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100217 clientPath = ""
218 if "path" in output:
219 clientPath = output.get("path")
220 elif "data" in output:
221 data = output.get("data")
222 lastSpace = data.rfind(" ")
223 clientPath = data[lastSpace + 1:]
224
225 if clientPath.endswith("..."):
226 clientPath = clientPath[:-3]
227 return clientPath
228
Simon Hausmann86949ee2007-03-19 20:59:12 +0100229def currentGitBranch():
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300230 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100231
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100232def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300233 if (os.path.exists(path + "/HEAD")
234 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100235 return True;
236 return False
237
Simon Hausmann463e8af2007-05-17 09:13:54 +0200238def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300239 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200240
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100241def extractLogMessageFromGitCommit(commit):
242 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300243
244 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100245 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300246 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100247 if not foundTitle:
248 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200249 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100250 continue
251
252 logMessage += log
253 return logMessage
254
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300255def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100256 values = {}
257 for line in log.split("\n"):
258 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300259 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
260 if not m:
261 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100262
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300263 assignments = m.group(1).split (':')
264 for a in assignments:
265 vals = a.split ('=')
266 key = vals[0].strip()
267 val = ('='.join (vals[1:])).strip()
268 if val.endswith ('\"') and val.startswith('"'):
269 val = val[1:-1]
270
271 values[key] = val
272
Simon Hausmann845b42c2007-06-07 09:19:34 +0200273 paths = values.get("depot-paths")
274 if not paths:
275 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200276 if paths:
277 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300278 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100279
Simon Hausmann8136a632007-03-22 21:27:14 +0100280def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300281 proc = subprocess.Popen(["git", "rev-parse", branch],
282 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200283 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100284
Simon Hausmann01265102007-05-25 10:36:10 +0200285def gitConfig(key):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300286 return read_pipe("git config %s" % key, ignore_error=True).strip()
Simon Hausmann01265102007-05-25 10:36:10 +0200287
Simon Hausmann062410b2007-07-18 10:56:31 +0200288def p4BranchesInGit(branchesAreInRemotes = True):
289 branches = {}
290
291 cmdline = "git rev-parse --symbolic "
292 if branchesAreInRemotes:
293 cmdline += " --remotes"
294 else:
295 cmdline += " --branches"
296
297 for line in read_pipe_lines(cmdline):
298 line = line.strip()
299
300 ## only import to p4/
301 if not line.startswith('p4/') or line == "p4/HEAD":
302 continue
303 branch = line
304
305 # strip off p4
306 branch = re.sub ("^p4/", "", line)
307
308 branches[branch] = parseRevision(line)
309 return branches
310
Simon Hausmann9ceab362007-06-22 00:01:57 +0200311def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200312 branches = p4BranchesInGit()
313 # map from depot-path to branch name
314 branchByDepotPath = {}
315 for branch in branches.keys():
316 tip = branches[branch]
317 log = extractLogMessageFromGitCommit(tip)
318 settings = extractSettingsGitLog(log)
319 if settings.has_key("depot-paths"):
320 paths = ",".join(settings["depot-paths"])
321 branchByDepotPath[paths] = "remotes/p4/" + branch
322
Simon Hausmann27d2d812007-06-12 14:31:59 +0200323 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200324 parent = 0
325 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200326 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200327 log = extractLogMessageFromGitCommit(commit)
328 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200329 if settings.has_key("depot-paths"):
330 paths = ",".join(settings["depot-paths"])
331 if branchByDepotPath.has_key(paths):
332 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200333
Simon Hausmann86506fe2007-07-18 12:40:12 +0200334 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200335
Simon Hausmann86506fe2007-07-18 12:40:12 +0200336 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200337
Simon Hausmann5ca44612007-08-24 17:44:16 +0200338def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
339 if not silent:
340 print ("Creating/updating branch(es) in %s based on origin branch(es)"
341 % localRefPrefix)
342
343 originPrefix = "origin/p4/"
344
345 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
346 line = line.strip()
347 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
348 continue
349
350 headName = line[len(originPrefix):]
351 remoteHead = localRefPrefix + headName
352 originHead = line
353
354 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
355 if (not original.has_key('depot-paths')
356 or not original.has_key('change')):
357 continue
358
359 update = False
360 if not gitBranchExists(remoteHead):
361 if verbose:
362 print "creating %s" % remoteHead
363 update = True
364 else:
365 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
366 if settings.has_key('change') > 0:
367 if settings['depot-paths'] == original['depot-paths']:
368 originP4Change = int(original['change'])
369 p4Change = int(settings['change'])
370 if originP4Change > p4Change:
371 print ("%s (%s) is newer than %s (%s). "
372 "Updating p4 branch from origin."
373 % (originHead, originP4Change,
374 remoteHead, p4Change))
375 update = True
376 else:
377 print ("Ignoring: %s was imported from %s while "
378 "%s was imported from %s"
379 % (originHead, ','.join(original['depot-paths']),
380 remoteHead, ','.join(settings['depot-paths'])))
381
382 if update:
383 system("git update-ref %s %s" % (remoteHead, originHead))
384
385def originP4BranchesExist():
386 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
387
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200388def p4ChangesForPaths(depotPaths, changeRange):
389 assert depotPaths
Anand Kumriab340fa42008-08-10 19:26:25 +0100390 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200391 for p in depotPaths]))
392
393 changes = []
394 for line in output:
395 changeNum = line.split(" ")[1]
396 changes.append(int(changeNum))
397
398 changes.sort()
399 return changes
400
Simon Hausmannb9847332007-03-20 20:54:23 +0100401class Command:
402 def __init__(self):
403 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200404 self.needsGit = True
Simon Hausmannb9847332007-03-20 20:54:23 +0100405
406class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100407 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100408 Command.__init__(self)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100409 self.options = [
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300410 optparse.make_option("--verbose", dest="verbose", action="store_true",
411 default=False),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300412 ]
Simon Hausmannc8c39112007-03-19 21:02:30 +0100413 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200414 self.needsGit = False
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300415 self.verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100416
417 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300418 j = 0
Simon Hausmann86949ee2007-03-19 20:59:12 +0100419 for output in p4CmdList(" ".join(args)):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300420 print 'Element: %d' % j
421 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100422 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100423 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100424
Simon Hausmann58346842007-05-21 22:57:06 +0200425class P4RollBack(Command):
426 def __init__(self):
427 Command.__init__(self)
428 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +0200429 optparse.make_option("--verbose", dest="verbose", action="store_true"),
430 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +0200431 ]
432 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann52102d42007-05-21 23:44:24 +0200433 self.verbose = False
Simon Hausmann0c66a782007-05-23 20:07:57 +0200434 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +0200435
436 def run(self, args):
437 if len(args) != 1:
438 return False
439 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +0200440
Simon Hausmannad192f22007-05-23 23:44:19 +0200441 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +0200442 die("Problems executing p4");
443
Simon Hausmann0c66a782007-05-23 20:07:57 +0200444 if self.rollbackLocalBranches:
445 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300446 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200447 else:
448 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300449 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200450
451 for line in lines:
452 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300453 line = line.strip()
454 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +0200455 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300456 settings = extractSettingsGitLog(log)
457
458 depotPaths = settings['depot-paths']
459 change = settings['change']
460
Simon Hausmann58346842007-05-21 22:57:06 +0200461 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +0200462
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300463 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
464 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +0200465 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
466 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
467 continue
468
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300469 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +0200470 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +0200471 if self.verbose:
472 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +0200473 system("git update-ref %s \"%s^\"" % (ref, ref))
474 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300475 settings = extractSettingsGitLog(log)
476
477
478 depotPaths = settings['depot-paths']
479 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +0200480
481 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +0200482 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +0200483
484 return True
485
Simon Hausmann711544b2007-04-01 15:40:46 +0200486class P4Submit(Command):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100487 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +0100488 Command.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100489 self.options = [
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300490 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100491 optparse.make_option("--origin", dest="origin"),
Chris Pettittd9a5f252007-10-15 22:15:06 -0700492 optparse.make_option("-M", dest="detectRename", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100493 ]
494 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200495 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100496 self.interactive = True
Simon Hausmann95124972007-03-23 09:16:07 +0100497 self.origin = ""
Chris Pettittd9a5f252007-10-15 22:15:06 -0700498 self.detectRename = False
Simon Hausmannb0d10df2007-06-07 13:09:14 +0200499 self.verbose = False
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200500 self.isWindows = (platform.system() == "Windows")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100501
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100502 def check(self):
503 if len(p4CmdList("opened ...")) > 0:
504 die("You have files opened with perforce! Close them before starting the sync.")
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
Anand Kumriab340fa42008-08-10 19:26:25 +0100538 for line in p4_read_pipe_lines("change -o"):
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100539 if line.endswith("\r\n"):
540 line = line[:-2] + "\n"
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200541 if inFilesSection:
542 if line.startswith("\t"):
543 # path starts and ends with a tab
544 path = line[1:]
545 lastTab = path.rfind("\t")
546 if lastTab != -1:
547 path = path[:lastTab]
548 if not path.startswith(self.depotPath):
549 continue
550 else:
551 inFilesSection = False
552 else:
553 if line.startswith("Files:"):
554 inFilesSection = True
555
556 template += line
557
558 return template
559
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300560 def applyCommit(self, id):
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100561 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
562 diffOpts = ("", "-M")[self.detectRename]
563 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100564 filesToAdd = set()
565 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +0200566 editedFiles = set()
Chris Pettittc65b6702007-11-01 20:43:14 -0700567 filesToChangeExecBit = {}
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100568 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -0700569 diff = parseDiffTreeEntry(line)
570 modifier = diff['status']
571 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100572 if modifier == "M":
Anand Kumria87b611d2008-08-10 19:26:27 +0100573 p4_system("edit \"%s\"" % path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700574 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
575 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +0200576 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100577 elif modifier == "A":
578 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700579 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100580 if path in filesToDelete:
581 filesToDelete.remove(path)
582 elif modifier == "D":
583 filesToDelete.add(path)
584 if path in filesToAdd:
585 filesToAdd.remove(path)
Chris Pettittd9a5f252007-10-15 22:15:06 -0700586 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -0700587 src, dest = diff['src'], diff['dst']
Anand Kumria87b611d2008-08-10 19:26:27 +0100588 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
589 p4_system("edit \"%s\"" % (dest))
Chris Pettittc65b6702007-11-01 20:43:14 -0700590 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
591 filesToChangeExecBit[dest] = diff['dst_mode']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700592 os.unlink(dest)
593 editedFiles.add(dest)
594 filesToDelete.add(src)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100595 else:
596 die("unknown modifier %s for %s" % (modifier, path))
597
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100598 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
Simon Hausmann47a130b2007-05-20 16:33:21 +0200599 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200600 tryPatchCmd = patchcmd + "--check -"
601 applyPatchCmd = patchcmd + "--check --apply -"
Simon Hausmann51a26402007-04-15 09:59:56 +0200602
Simon Hausmann47a130b2007-05-20 16:33:21 +0200603 if os.system(tryPatchCmd) != 0:
Simon Hausmann51a26402007-04-15 09:59:56 +0200604 print "Unfortunately applying the change failed!"
605 print "What do you want to do?"
606 response = "x"
607 while response != "s" and response != "a" and response != "w":
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300608 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
609 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
Simon Hausmann51a26402007-04-15 09:59:56 +0200610 if response == "s":
611 print "Skipping! Good luck with the next patches..."
Simon Hausmann20947142007-09-13 22:10:18 +0200612 for f in editedFiles:
Anand Kumria87b611d2008-08-10 19:26:27 +0100613 p4_system("revert \"%s\"" % f);
Simon Hausmann20947142007-09-13 22:10:18 +0200614 for f in filesToAdd:
615 system("rm %s" %f)
Simon Hausmann51a26402007-04-15 09:59:56 +0200616 return
617 elif response == "a":
Simon Hausmann47a130b2007-05-20 16:33:21 +0200618 os.system(applyPatchCmd)
Simon Hausmann51a26402007-04-15 09:59:56 +0200619 if len(filesToAdd) > 0:
620 print "You may also want to call p4 add on the following files:"
621 print " ".join(filesToAdd)
622 if len(filesToDelete):
623 print "The following files should be scheduled for deletion with p4 delete:"
624 print " ".join(filesToDelete)
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300625 die("Please resolve and submit the conflict manually and "
626 + "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200627 elif response == "w":
628 system(diffcmd + " > patch.txt")
629 print "Patch saved to patch.txt in %s !" % self.clientPath
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
Simon Hausmann47a130b2007-05-20 16:33:21 +0200633 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100634
635 for f in filesToAdd:
Anand Kumria87b611d2008-08-10 19:26:27 +0100636 p4_system("add \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100637 for f in filesToDelete:
Anand Kumria87b611d2008-08-10 19:26:27 +0100638 p4_system("revert \"%s\"" % f)
639 p4_system("delete \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100640
Chris Pettittc65b6702007-11-01 20:43:14 -0700641 # Set/clear executable bits
642 for f in filesToChangeExecBit.keys():
643 mode = filesToChangeExecBit[f]
644 setP4ExecBit(f, mode)
645
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100646 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100647 logMessage = logMessage.strip()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100648
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200649 template = self.prepareSubmitTemplate()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100650
651 if self.interactive:
652 submitTemplate = self.prepareLogMessage(template, logMessage)
Shawn Bohrer67abd412008-03-12 19:03:23 -0500653 if os.environ.has_key("P4DIFF"):
654 del(os.environ["P4DIFF"])
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300655 diff = read_pipe("p4 diff -du ...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100656
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100657 newdiff = ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100658 for newFile in filesToAdd:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100659 newdiff += "==== new file ====\n"
660 newdiff += "--- /dev/null\n"
661 newdiff += "+++ %s\n" % newFile
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100662 f = open(newFile, "r")
663 for line in f.readlines():
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100664 newdiff += "+" + line
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100665 f.close()
666
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100667 separatorLine = "######## everything below this line is just the diff #######\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100668
Simon Hausmanne96e4002008-01-04 14:27:55 +0100669 [handle, fileName] = tempfile.mkstemp()
670 tmpFile = os.fdopen(handle, "w+")
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100671 if self.isWindows:
672 submitTemplate = submitTemplate.replace("\n", "\r\n")
673 separatorLine = separatorLine.replace("\n", "\r\n")
674 newdiff = newdiff.replace("\n", "\r\n")
675 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
Simon Hausmanne96e4002008-01-04 14:27:55 +0100676 tmpFile.close()
677 defaultEditor = "vi"
678 if platform.system() == "Windows":
679 defaultEditor = "notepad"
Shawn Bohrer82cea9f2008-03-12 19:03:24 -0500680 if os.environ.has_key("P4EDITOR"):
681 editor = os.environ.get("P4EDITOR")
682 else:
683 editor = os.environ.get("EDITOR", defaultEditor);
Simon Hausmanne96e4002008-01-04 14:27:55 +0100684 system(editor + " " + fileName)
685 tmpFile = open(fileName, "rb")
686 message = tmpFile.read()
687 tmpFile.close()
688 os.remove(fileName)
689 submitTemplate = message[:message.index(separatorLine)]
690 if self.isWindows:
691 submitTemplate = submitTemplate.replace("\r\n", "\n")
Simon Hausmanncb4f1282007-05-25 22:34:30 +0200692
Simon Hausmanne96e4002008-01-04 14:27:55 +0100693 write_pipe("p4 submit -i", submitTemplate)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100694 else:
695 fileName = "submit.txt"
696 file = open(fileName, "w+")
697 file.write(self.prepareLogMessage(template, logMessage))
698 file.close()
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300699 print ("Perforce submit template written as %s. "
700 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
701 % (fileName, fileName))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100702
703 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200704 if len(args) == 0:
705 self.master = currentGitBranch()
Simon Hausmann4280e532007-05-25 08:49:18 +0200706 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200707 die("Detecting current git branch failed!")
708 elif len(args) == 1:
709 self.master = args[0]
710 else:
711 return False
712
Jing Xue4c2d5d72008-06-22 14:12:39 -0400713 allowSubmit = gitConfig("git-p4.allowSubmit")
714 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
715 die("%s is not in git-p4.allowSubmit" % self.master)
716
Simon Hausmann27d2d812007-06-12 14:31:59 +0200717 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200718 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200719 if len(self.origin) == 0:
720 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200721
722 if self.verbose:
723 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +0100724
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200725 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +0100726 print "Internal error: cannot locate perforce depot path from existing branches"
727 sys.exit(128)
728
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200729 self.clientPath = p4Where(self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +0100730
Simon Hausmann51a26402007-04-15 09:59:56 +0200731 if len(self.clientPath) == 0:
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200732 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
Simon Hausmann95124972007-03-23 09:16:07 +0100733 sys.exit(128)
734
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200735 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +0200736 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200737
Simon Hausmann51a26402007-04-15 09:59:56 +0200738 os.chdir(self.clientPath)
Simon Hausmann31f9ec12007-08-21 11:53:02 +0200739 print "Syncronizing p4 checkout..."
Anand Kumria87b611d2008-08-10 19:26:27 +0100740 p4_system("sync ...")
Simon Hausmann95124972007-03-23 09:16:07 +0100741
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100742 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100743
Simon Hausmann4c750c02008-02-19 09:37:16 +0100744 commits = []
745 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
746 commits.append(line.strip())
747 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100748
749 while len(commits) > 0:
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100750 commit = commits[0]
751 commits = commits[1:]
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300752 self.applyCommit(commit)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100753 if not self.interactive:
754 break
755
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100756 if len(commits) == 0:
Simon Hausmann4c750c02008-02-19 09:37:16 +0100757 print "All changes applied!"
758 os.chdir(self.oldWorkingDirectory)
Simon Hausmann14594f42007-08-22 09:07:15 +0200759
Simon Hausmann4c750c02008-02-19 09:37:16 +0100760 sync = P4Sync()
761 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +0200762
Simon Hausmann4c750c02008-02-19 09:37:16 +0100763 rebase = P4Rebase()
764 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100765
Simon Hausmannb9847332007-03-20 20:54:23 +0100766 return True
767
Simon Hausmann711544b2007-04-01 15:40:46 +0200768class P4Sync(Command):
Simon Hausmannb9847332007-03-20 20:54:23 +0100769 def __init__(self):
770 Command.__init__(self)
771 self.options = [
772 optparse.make_option("--branch", dest="branch"),
773 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
774 optparse.make_option("--changesfile", dest="changesFile"),
775 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +0200776 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Simon Hausmanna028a982007-05-23 00:03:08 +0200777 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300778 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
779 help="Import into refs/heads/ , not refs/remotes"),
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300780 optparse.make_option("--max-changes", dest="maxChanges"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300781 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100782 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
783 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
784 help="Only sync files that are included in the Perforce Client Spec")
Simon Hausmannb9847332007-03-20 20:54:23 +0100785 ]
786 self.description = """Imports from Perforce into a git repository.\n
787 example:
788 //depot/my/project/ -- to import the current head
789 //depot/my/project/@all -- to import everything
790 //depot/my/project/@1,6 -- to import only from revision 1 to 6
791
792 (a ... is not needed in the path p4 specification, it's added implicitly)"""
793
794 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +0100795 self.silent = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100796 self.createdBranches = Set()
797 self.committedChanges = Set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +0100798 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +0100799 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +0200800 self.detectLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100801 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +0200802 self.syncWithOrigin = True
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200803 self.verbose = False
Simon Hausmanna028a982007-05-23 00:03:08 +0200804 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +0200805 self.maxChanges = ""
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +0200806 self.isWindows = (platform.system() == "Windows")
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300807 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300808 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +0200809 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -0800810 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100811 self.useClientSpec = False
812 self.clientSpecDirs = []
Simon Hausmannb9847332007-03-20 20:54:23 +0100813
Simon Hausmann01265102007-05-25 10:36:10 +0200814 if gitConfig("git-p4.syncFromOrigin") == "false":
815 self.syncWithOrigin = False
816
Simon Hausmannb9847332007-03-20 20:54:23 +0100817 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -0800818 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
819 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +0100820 files = []
821 fnum = 0
822 while commit.has_key("depotFile%s" % fnum):
823 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300824
Tommy Thorn354081d2008-02-03 10:38:51 -0800825 if [p for p in self.cloneExclude
826 if path.startswith (p)]:
827 found = False
828 else:
829 found = [p for p in self.depotPaths
830 if path.startswith (p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300831 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +0100832 fnum = fnum + 1
833 continue
834
835 file = {}
836 file["path"] = path
837 file["rev"] = commit["rev%s" % fnum]
838 file["action"] = commit["action%s" % fnum]
839 file["type"] = commit["type%s" % fnum]
840 files.append(file)
841 fnum = fnum + 1
842 return files
843
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300844 def stripRepoPath(self, path, prefixes):
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300845 if self.keepRepoPath:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300846 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300847
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300848 for p in prefixes:
849 if path.startswith(p):
850 path = path[len(p):]
851
852 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300853
Simon Hausmann71b112d2007-05-19 11:54:11 +0200854 def splitFilesIntoBranches(self, commit):
Simon Hausmannd5904672007-05-19 11:07:32 +0200855 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +0200856 fnum = 0
857 while commit.has_key("depotFile%s" % fnum):
858 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300859 found = [p for p in self.depotPaths
860 if path.startswith (p)]
861 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +0200862 fnum = fnum + 1
863 continue
864
865 file = {}
866 file["path"] = path
867 file["rev"] = commit["rev%s" % fnum]
868 file["action"] = commit["action%s" % fnum]
869 file["type"] = commit["type%s" % fnum]
870 fnum = fnum + 1
871
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300872 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +0100873
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200874 for branch in self.knownBranches.keys():
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300875
876 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
877 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +0200878 if branch not in branches:
879 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +0200880 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +0200881 break
Simon Hausmannb9847332007-03-20 20:54:23 +0100882
883 return branches
884
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300885 ## Should move this out, doesn't use SELF.
886 def readP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +0100887 filesForCommit = []
888 filesToRead = []
889
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100890 for f in files:
Simon Hausmann30b59402008-03-03 11:55:48 +0100891 includeFile = True
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100892 for val in self.clientSpecDirs:
893 if f['path'].startswith(val[0]):
Simon Hausmann30b59402008-03-03 11:55:48 +0100894 if val[1] <= 0:
895 includeFile = False
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100896 break
897
Simon Hausmann30b59402008-03-03 11:55:48 +0100898 if includeFile:
899 filesForCommit.append(f)
900 if f['action'] != 'delete':
901 filesToRead.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300902
Simon Hausmann30b59402008-03-03 11:55:48 +0100903 filedata = []
904 if len(filesToRead) > 0:
905 filedata = p4CmdList('-x - print',
906 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
907 for f in filesToRead]),
908 stdin_mode='w+')
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -0300909
Simon Hausmann30b59402008-03-03 11:55:48 +0100910 if "p4ExitCode" in filedata[0]:
911 die("Problems executing p4. Error: [%d]."
912 % (filedata[0]['p4ExitCode']));
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300913
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300914 j = 0;
915 contents = {}
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300916 while j < len(filedata):
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300917 stat = filedata[j]
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300918 j += 1
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100919 text = [];
Jason McMullanf3e95122007-12-05 12:16:56 -0500920 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100921 text.append(filedata[j]['data'])
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300922 j += 1
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100923 text = ''.join(text)
Han-Wen Nienhuys1b9a4682007-05-23 18:49:35 -0300924
925 if not stat.has_key('depotFile'):
926 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
927 continue
928
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100929 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
930 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
931 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
Daniel Barkalow2d71bde2008-07-22 12:48:57 -0400932 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
Marius Storm-Olsen8ff45f22008-03-03 13:42:47 +0100933
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300934 contents[stat['depotFile']] = text
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300935
Simon Hausmann30b59402008-03-03 11:55:48 +0100936 for f in filesForCommit:
937 path = f['path']
938 if contents.has_key(path):
939 f['data'] = contents[path]
940
941 return filesForCommit
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300942
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300943 def commit(self, details, files, branch, branchPrefixes, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +0100944 epoch = details["time"]
945 author = details["user"]
946
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200947 if self.verbose:
948 print "commit into %s" % branch
949
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -0300950 # start with reading files; if that fails, we should not
951 # create a commit.
952 new_files = []
953 for f in files:
954 if [p for p in branchPrefixes if f['path'].startswith(p)]:
955 new_files.append (f)
956 else:
957 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100958 files = self.readP4Files(new_files)
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -0300959
Simon Hausmannb9847332007-03-20 20:54:23 +0100960 self.gitStream.write("commit %s\n" % branch)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300961# gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +0100962 self.committedChanges.add(int(details["change"]))
963 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +0200964 if author not in self.users:
965 self.getUserMapFromPerforceServer()
Simon Hausmannb9847332007-03-20 20:54:23 +0100966 if author in self.users:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100967 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100968 else:
Simon Hausmann0828ab12007-03-20 20:59:30 +0100969 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +0100970
971 self.gitStream.write("committer %s\n" % committer)
972
973 self.gitStream.write("data <<EOT\n")
974 self.gitStream.write(details["desc"])
Simon Hausmann6581de02007-06-11 10:01:58 +0200975 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
976 % (','.join (branchPrefixes), details["change"]))
977 if len(details['options']) > 0:
978 self.gitStream.write(": options = %s" % details['options'])
979 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +0100980
981 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200982 if self.verbose:
983 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +0100984 self.gitStream.write("from %s\n" % parent)
985
Simon Hausmannb9847332007-03-20 20:54:23 +0100986 for file in files:
Simon Hausmannb9847332007-03-20 20:54:23 +0100987 if file["type"] == "apple":
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300988 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
Simon Hausmannb9847332007-03-20 20:54:23 +0100989 continue
990
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300991 relPath = self.stripRepoPath(file['path'], branchPrefixes)
992 if file["action"] == "delete":
Simon Hausmannb9847332007-03-20 20:54:23 +0100993 self.gitStream.write("D %s\n" % relPath)
994 else:
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300995 data = file['data']
Simon Hausmannb9847332007-03-20 20:54:23 +0100996
Simon Hausmann74276ec2007-08-07 12:28:00 +0200997 mode = "644"
David Brownb9fc6ea2007-09-19 13:12:48 -0700998 if isP4Exec(file["type"]):
Simon Hausmann74276ec2007-08-07 12:28:00 +0200999 mode = "755"
1000 elif file["type"] == "symlink":
1001 mode = "120000"
1002 # p4 print on a symlink contains "target\n", so strip it off
1003 data = data[:-1]
1004
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +02001005 if self.isWindows and file["type"].endswith("text"):
1006 data = data.replace("\r\n", "\n")
1007
Simon Hausmann74276ec2007-08-07 12:28:00 +02001008 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
Simon Hausmannb9847332007-03-20 20:54:23 +01001009 self.gitStream.write("data %s\n" % len(data))
1010 self.gitStream.write(data)
1011 self.gitStream.write("\n")
1012
1013 self.gitStream.write("\n")
1014
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001015 change = int(details["change"])
1016
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001017 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001018 label = self.labels[change]
1019 labelDetails = label[0]
1020 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02001021 if self.verbose:
1022 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001023
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001024 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1025 for p in branchPrefixes]))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001026
1027 if len(files) == len(labelRevisions):
1028
1029 cleanedFiles = {}
1030 for info in files:
1031 if info["action"] == "delete":
1032 continue
1033 cleanedFiles[info["depotFile"]] = info["rev"]
1034
1035 if cleanedFiles == labelRevisions:
1036 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1037 self.gitStream.write("from %s\n" % branch)
1038
1039 owner = labelDetails["Owner"]
1040 tagger = ""
1041 if author in self.users:
1042 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1043 else:
1044 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1045 self.gitStream.write("tagger %s\n" % tagger)
1046 self.gitStream.write("data <<EOT\n")
1047 self.gitStream.write(labelDetails["Description"])
1048 self.gitStream.write("EOT\n\n")
1049
1050 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001051 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001052 print ("Tag %s does not match with change %s: files do not match."
1053 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001054
1055 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001056 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001057 print ("Tag %s does not match with change %s: file count is different."
1058 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01001059
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001060 def getUserCacheFilename(self):
Simon Hausmannb2d2d162007-07-25 09:31:38 +02001061 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1062 return home + "/.gitp4-usercache.txt"
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001063
Simon Hausmannb607e712007-05-20 10:55:54 +02001064 def getUserMapFromPerforceServer(self):
Simon Hausmannebd81162007-05-24 00:24:52 +02001065 if self.userMapFromPerforceServer:
1066 return
Simon Hausmannb9847332007-03-20 20:54:23 +01001067 self.users = {}
1068
1069 for output in p4CmdList("users"):
1070 if not output.has_key("User"):
1071 continue
1072 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1073
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001074
1075 s = ''
1076 for (key, val) in self.users.items():
1077 s += "%s\t%s\n" % (key, val)
1078
1079 open(self.getUserCacheFilename(), "wb").write(s)
Simon Hausmannebd81162007-05-24 00:24:52 +02001080 self.userMapFromPerforceServer = True
Simon Hausmannb607e712007-05-20 10:55:54 +02001081
1082 def loadUserMapFromCache(self):
1083 self.users = {}
Simon Hausmannebd81162007-05-24 00:24:52 +02001084 self.userMapFromPerforceServer = False
Simon Hausmannb607e712007-05-20 10:55:54 +02001085 try:
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001086 cache = open(self.getUserCacheFilename(), "rb")
Simon Hausmannb607e712007-05-20 10:55:54 +02001087 lines = cache.readlines()
1088 cache.close()
1089 for line in lines:
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001090 entry = line.strip().split("\t")
Simon Hausmannb607e712007-05-20 10:55:54 +02001091 self.users[entry[0]] = entry[1]
1092 except IOError:
1093 self.getUserMapFromPerforceServer()
1094
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001095 def getLabels(self):
1096 self.labels = {}
1097
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001098 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
Simon Hausmann10c32112007-04-08 10:15:47 +02001099 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08001100 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001101
1102 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001103 label = output["label"]
1104 revisions = {}
1105 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02001106 if self.verbose:
1107 print "Querying files for label %s" % label
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001108 for file in p4CmdList("files "
1109 + ' '.join (["%s...@%s" % (p, label)
1110 for p in self.depotPaths])):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001111 revisions[file["depotFile"]] = file["rev"]
1112 change = int(file["change"])
1113 if change > newestChange:
1114 newestChange = change
1115
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001116 self.labels[newestChange] = [output, revisions]
1117
1118 if self.verbose:
1119 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001120
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001121 def guessProjectName(self):
1122 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02001123 if p.endswith("/"):
1124 p = p[:-1]
1125 p = p[p.strip().rfind("/") + 1:]
1126 if not p.endswith("/"):
1127 p += "/"
1128 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001129
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001130 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001131 lostAndFoundBranches = set()
1132
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001133 for info in p4CmdList("branches"):
1134 details = p4Cmd("branch -o %s" % info["branch"])
1135 viewIdx = 0
1136 while details.has_key("View%s" % viewIdx):
1137 paths = details["View%s" % viewIdx].split(" ")
1138 viewIdx = viewIdx + 1
1139 # require standard //depot/foo/... //depot/bar/... mapping
1140 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1141 continue
1142 source = paths[0]
1143 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02001144 ## HACK
1145 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1146 source = source[len(self.depotPaths[0]):-4]
1147 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001148
Simon Hausmann1a2edf42007-06-17 15:10:24 +02001149 if destination in self.knownBranches:
1150 if not self.silent:
1151 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1152 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1153 continue
1154
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001155 self.knownBranches[destination] = source
1156
1157 lostAndFoundBranches.discard(destination)
1158
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001159 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001160 lostAndFoundBranches.add(source)
1161
1162
1163 for branch in lostAndFoundBranches:
1164 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001165
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001166 def getBranchMappingFromGitBranches(self):
1167 branches = p4BranchesInGit(self.importIntoRemotes)
1168 for branch in branches.keys():
1169 if branch == "master":
1170 branch = "main"
1171 else:
1172 branch = branch[len(self.projectName):]
1173 self.knownBranches[branch] = branch
1174
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001175 def listExistingP4GitBranches(self):
Simon Hausmann144ff462007-07-18 17:27:50 +02001176 # branches holds mapping from name to commit
1177 branches = p4BranchesInGit(self.importIntoRemotes)
1178 self.p4BranchesInGit = branches.keys()
1179 for branch in branches.keys():
1180 self.initialParents[self.refPrefix + branch] = branches[branch]
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001181
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001182 def updateOptionDict(self, d):
1183 option_keys = {}
1184 if self.keepRepoPath:
1185 option_keys['keepRepoPath'] = 1
1186
1187 d["options"] = ' '.join(sorted(option_keys.keys()))
1188
1189 def readOptions(self, d):
1190 self.keepRepoPath = (d.has_key('options')
1191 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001192
Simon Hausmann8134f692007-08-26 16:44:55 +02001193 def gitRefForBranch(self, branch):
1194 if branch == "main":
1195 return self.refPrefix + "master"
1196
1197 if len(branch) <= 0:
1198 return branch
1199
1200 return self.refPrefix + self.projectName + branch
1201
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001202 def gitCommitByP4Change(self, ref, change):
1203 if self.verbose:
1204 print "looking in ref " + ref + " for change %s using bisect..." % change
1205
1206 earliestCommit = ""
1207 latestCommit = parseRevision(ref)
1208
1209 while True:
1210 if self.verbose:
1211 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1212 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1213 if len(next) == 0:
1214 if self.verbose:
1215 print "argh"
1216 return ""
1217 log = extractLogMessageFromGitCommit(next)
1218 settings = extractSettingsGitLog(log)
1219 currentChange = int(settings['change'])
1220 if self.verbose:
1221 print "current change %s" % currentChange
1222
1223 if currentChange == change:
1224 if self.verbose:
1225 print "found %s" % next
1226 return next
1227
1228 if currentChange < change:
1229 earliestCommit = "^%s" % next
1230 else:
1231 latestCommit = "%s" % next
1232
1233 return ""
1234
1235 def importNewBranch(self, branch, maxChange):
1236 # make fast-import flush all changes to disk and update the refs using the checkpoint
1237 # command so that we can try to find the branch parent in the git history
1238 self.gitStream.write("checkpoint\n\n");
1239 self.gitStream.flush();
1240 branchPrefix = self.depotPaths[0] + branch + "/"
1241 range = "@1,%s" % maxChange
1242 #print "prefix" + branchPrefix
1243 changes = p4ChangesForPaths([branchPrefix], range)
1244 if len(changes) <= 0:
1245 return False
1246 firstChange = changes[0]
1247 #print "first change in branch: %s" % firstChange
1248 sourceBranch = self.knownBranches[branch]
1249 sourceDepotPath = self.depotPaths[0] + sourceBranch
1250 sourceRef = self.gitRefForBranch(sourceBranch)
1251 #print "source " + sourceBranch
1252
1253 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1254 #print "branch parent: %s" % branchParentChange
1255 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1256 if len(gitParent) > 0:
1257 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1258 #print "parent git commit: %s" % gitParent
1259
1260 self.importChanges(changes)
1261 return True
1262
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001263 def importChanges(self, changes):
1264 cnt = 1
1265 for change in changes:
1266 description = p4Cmd("describe %s" % change)
1267 self.updateOptionDict(description)
1268
1269 if not self.silent:
1270 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1271 sys.stdout.flush()
1272 cnt = cnt + 1
1273
1274 try:
1275 if self.detectBranches:
1276 branches = self.splitFilesIntoBranches(description)
1277 for branch in branches.keys():
1278 ## HACK --hwn
1279 branchPrefix = self.depotPaths[0] + branch + "/"
1280
1281 parent = ""
1282
1283 filesForCommit = branches[branch]
1284
1285 if self.verbose:
1286 print "branch is %s" % branch
1287
1288 self.updatedBranches.add(branch)
1289
1290 if branch not in self.createdBranches:
1291 self.createdBranches.add(branch)
1292 parent = self.knownBranches[branch]
1293 if parent == branch:
1294 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001295 else:
1296 fullBranch = self.projectName + branch
1297 if fullBranch not in self.p4BranchesInGit:
1298 if not self.silent:
1299 print("\n Importing new branch %s" % fullBranch);
1300 if self.importNewBranch(branch, change - 1):
1301 parent = ""
1302 self.p4BranchesInGit.append(fullBranch)
1303 if not self.silent:
1304 print("\n Resuming with change %s" % change);
1305
1306 if self.verbose:
1307 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001308
Simon Hausmann8134f692007-08-26 16:44:55 +02001309 branch = self.gitRefForBranch(branch)
1310 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001311
1312 if self.verbose:
1313 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1314
1315 if len(parent) == 0 and branch in self.initialParents:
1316 parent = self.initialParents[branch]
1317 del self.initialParents[branch]
1318
1319 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1320 else:
1321 files = self.extractFilesFromCommit(description)
1322 self.commit(description, files, self.branch, self.depotPaths,
1323 self.initialParent)
1324 self.initialParent = ""
1325 except IOError:
1326 print self.gitError.read()
1327 sys.exit(1)
1328
Simon Hausmannc208a242007-08-26 16:07:18 +02001329 def importHeadRevision(self, revision):
1330 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1331
1332 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1333 details["desc"] = ("Initial import of %s from the state at revision %s"
1334 % (' '.join(self.depotPaths), revision))
1335 details["change"] = revision
1336 newestRevision = 0
1337
1338 fileCnt = 0
1339 for info in p4CmdList("files "
1340 + ' '.join(["%s...%s"
1341 % (p, revision)
1342 for p in self.depotPaths])):
1343
1344 if info['code'] == 'error':
1345 sys.stderr.write("p4 returned an error: %s\n"
1346 % info['data'])
1347 sys.exit(1)
1348
1349
1350 change = int(info["change"])
1351 if change > newestRevision:
1352 newestRevision = change
1353
1354 if info["action"] == "delete":
1355 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1356 #fileCnt = fileCnt + 1
1357 continue
1358
1359 for prop in ["depotFile", "rev", "action", "type" ]:
1360 details["%s%s" % (prop, fileCnt)] = info[prop]
1361
1362 fileCnt = fileCnt + 1
1363
1364 details["change"] = newestRevision
1365 self.updateOptionDict(details)
1366 try:
1367 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1368 except IOError:
1369 print "IO error with git fast-import. Is your git version recent enough?"
1370 print self.gitError.read()
1371
1372
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001373 def getClientSpec(self):
1374 specList = p4CmdList( "client -o" )
1375 temp = {}
1376 for entry in specList:
1377 for k,v in entry.iteritems():
1378 if k.startswith("View"):
1379 if v.startswith('"'):
1380 start = 1
1381 else:
1382 start = 0
1383 index = v.find("...")
1384 v = v[start:index]
1385 if v.startswith("-"):
1386 v = v[1:]
1387 temp[v] = -len(v)
1388 else:
1389 temp[v] = len(v)
1390 self.clientSpecDirs = temp.items()
1391 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1392
Simon Hausmannb9847332007-03-20 20:54:23 +01001393 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001394 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01001395 self.changeRange = ""
1396 self.initialParent = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001397 self.previousDepotPaths = []
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03001398
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001399 # map from branch depot path to parent branch
1400 self.knownBranches = {}
1401 self.initialParents = {}
Simon Hausmann5ca44612007-08-24 17:44:16 +02001402 self.hasOrigin = originP4BranchesExist()
Simon Hausmanna43ff002007-06-11 09:59:27 +02001403 if not self.syncWithOrigin:
1404 self.hasOrigin = False
Simon Hausmann179caeb2007-03-22 22:17:42 +01001405
Simon Hausmanna028a982007-05-23 00:03:08 +02001406 if self.importIntoRemotes:
1407 self.refPrefix = "refs/remotes/p4/"
1408 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001409 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02001410
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001411 if self.syncWithOrigin and self.hasOrigin:
1412 if not self.silent:
1413 print "Syncing with origin first by calling git fetch origin"
1414 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02001415
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001416 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001417 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02001418 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001419 system("git update-ref %s refs/heads/p4" % self.branch)
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001420 system("git branch -D p4");
Simon Hausmannfaf1bd22007-05-21 10:05:30 +02001421 # create it /after/ importing, when master exists
Simon Hausmann0058a332007-08-24 17:46:16 +02001422 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
Simon Hausmanna3c55c02007-05-27 15:48:01 +02001423 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
Simon Hausmann179caeb2007-03-22 22:17:42 +01001424
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001425 if self.useClientSpec or gitConfig("p4.useclientspec") == "true":
1426 self.getClientSpec()
1427
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001428 # TODO: should always look at previous commits,
1429 # merge with previous imports, if possible.
1430 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02001431 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02001432 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Simon Hausmannabcd7902007-05-24 22:25:36 +02001433 self.listExistingP4GitBranches()
1434
1435 if len(self.p4BranchesInGit) > 1:
1436 if not self.silent:
1437 print "Importing from/into multiple branches"
1438 self.detectBranches = True
Simon Hausmann967f72e2007-03-23 09:30:41 +01001439
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001440 if self.verbose:
1441 print "branches: %s" % self.p4BranchesInGit
1442
1443 p4Change = 0
1444 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001445 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001446
1447 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001448
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001449 self.readOptions(settings)
1450 if (settings.has_key('depot-paths')
1451 and settings.has_key ('change')):
1452 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001453 p4Change = max(p4Change, change)
1454
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001455 depotPaths = sorted(settings['depot-paths'])
1456 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001457 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001458 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001459 paths = []
1460 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Simon Hausmann583e1702007-06-07 09:37:13 +02001461 for i in range(0, min(len(cur), len(prev))):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001462 if cur[i] <> prev[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02001463 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001464 break
1465
Simon Hausmann583e1702007-06-07 09:37:13 +02001466 paths.append (cur[:i + 1])
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001467
1468 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001469
1470 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001471 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02001472 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann330f53b2007-06-07 09:39:51 +02001473 if not self.detectBranches:
1474 self.initialParent = parseRevision(self.branch)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001475 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01001476 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001477
Simon Hausmannf9162f62007-05-17 09:02:45 +02001478 if not self.branch.startswith("refs/"):
1479 self.branch = "refs/heads/" + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01001480
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001481 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01001482 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001483 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01001484 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001485 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001486 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001487 "This doesn't work!" % (' '.join (self.depotPaths),
1488 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01001489 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001490
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001491 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01001492
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001493 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01001494 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01001495
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001496 newPaths = []
1497 for p in self.depotPaths:
1498 if p.find("@") != -1:
1499 atIdx = p.index("@")
1500 self.changeRange = p[atIdx:]
1501 if self.changeRange == "@all":
1502 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001503 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001504 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001505 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001506 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001507 elif p.find("#") != -1:
1508 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001509 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001510 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001511 elif self.previousDepotPaths == []:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001512 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01001513
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001514 p = re.sub ("\.\.\.$", "", p)
1515 if not p.endswith("/"):
1516 p += "/"
1517
1518 newPaths.append(p)
1519
1520 self.depotPaths = newPaths
1521
Simon Hausmannb9847332007-03-20 20:54:23 +01001522
Simon Hausmannb607e712007-05-20 10:55:54 +02001523 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02001524 self.labels = {}
1525 if self.detectLabels:
1526 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01001527
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001528 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02001529 ## FIXME - what's a P4 projectName ?
1530 self.projectName = self.guessProjectName()
1531
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001532 if self.hasOrigin:
1533 self.getBranchMappingFromGitBranches()
1534 else:
1535 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001536 if self.verbose:
1537 print "p4-git branches: %s" % self.p4BranchesInGit
1538 print "initial parents: %s" % self.initialParents
1539 for b in self.p4BranchesInGit:
1540 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001541
1542 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001543 b = b[len(self.projectName):]
1544 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001545
Simon Hausmannf291b4e2007-04-14 11:21:50 +02001546 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01001547
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001548 importProcess = subprocess.Popen(["git", "fast-import"],
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001549 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1550 stderr=subprocess.PIPE);
Simon Hausmann08483582007-05-15 14:31:06 +02001551 self.gitOutput = importProcess.stdout
1552 self.gitStream = importProcess.stdin
1553 self.gitError = importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01001554
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001555 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02001556 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01001557 else:
1558 changes = []
1559
Simon Hausmann0828ab12007-03-20 20:59:30 +01001560 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01001561 output = open(self.changesFile).readlines()
1562 changeSet = Set()
1563 for line in output:
1564 changeSet.add(int(line))
1565
1566 for change in changeSet:
1567 changes.append(change)
1568
1569 changes.sort()
1570 else:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001571 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001572 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001573 self.changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001574 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
Simon Hausmannb9847332007-03-20 20:54:23 +01001575
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001576 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001577 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001578
Simon Hausmannb9847332007-03-20 20:54:23 +01001579 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001580 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001581 print "No changes to import!"
Simon Hausmann1f52af62007-04-08 00:07:02 +02001582 return True
Simon Hausmannb9847332007-03-20 20:54:23 +01001583
Simon Hausmanna9d1a272007-06-11 23:28:03 +02001584 if not self.silent and not self.detectBranches:
1585 print "Import destination: %s" % self.branch
1586
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001587 self.updatedBranches = set()
1588
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001589 self.importChanges(changes)
Simon Hausmannb9847332007-03-20 20:54:23 +01001590
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001591 if not self.silent:
1592 print ""
1593 if len(self.updatedBranches) > 0:
1594 sys.stdout.write("Updated branches: ")
1595 for b in self.updatedBranches:
1596 sys.stdout.write("%s " % b)
1597 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001598
Simon Hausmannb9847332007-03-20 20:54:23 +01001599 self.gitStream.close()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001600 if importProcess.wait() != 0:
1601 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01001602 self.gitOutput.close()
1603 self.gitError.close()
1604
Simon Hausmannb9847332007-03-20 20:54:23 +01001605 return True
1606
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001607class P4Rebase(Command):
1608 def __init__(self):
1609 Command.__init__(self)
Simon Hausmann01265102007-05-25 10:36:10 +02001610 self.options = [ ]
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001611 self.description = ("Fetches the latest revision from perforce and "
1612 + "rebases the current work (branch) against it")
Simon Hausmann68c42152007-06-07 12:51:03 +02001613 self.verbose = False
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001614
1615 def run(self, args):
1616 sync = P4Sync()
1617 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02001618
Simon Hausmann14594f42007-08-22 09:07:15 +02001619 return self.rebase()
1620
1621 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01001622 if os.system("git update-index --refresh") != 0:
1623 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.");
1624 if len(read_pipe("git diff-index HEAD --")) > 0:
1625 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1626
Simon Hausmannd7e38682007-06-12 14:34:46 +02001627 [upstream, settings] = findUpstreamBranchPoint()
1628 if len(upstream) == 0:
1629 die("Cannot find upstream branchpoint for rebase")
1630
1631 # the branchpoint may be p4/foo~3, so strip off the parent
1632 upstream = re.sub("~[0-9]+$", "", upstream)
1633
1634 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001635 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02001636 system("git rebase %s" % upstream)
Simon Hausmann1f52af62007-04-08 00:07:02 +02001637 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001638 return True
1639
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001640class P4Clone(P4Sync):
1641 def __init__(self):
1642 P4Sync.__init__(self)
1643 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001644 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08001645 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001646 optparse.make_option("--destination", dest="cloneDestination",
1647 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08001648 help="where to leave result of the clone"),
1649 optparse.make_option("-/", dest="cloneExclude",
1650 action="append", type="string",
1651 help="exclude depot path")
1652 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001653 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001654 self.needsGit = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001655
Tommy Thorn354081d2008-02-03 10:38:51 -08001656 # This is required for the "append" cloneExclude action
1657 def ensure_value(self, attr, value):
1658 if not hasattr(self, attr) or getattr(self, attr) is None:
1659 setattr(self, attr, value)
1660 return getattr(self, attr)
1661
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001662 def defaultDestination(self, args):
1663 ## TODO: use common prefix of args?
1664 depotPath = args[0]
1665 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1666 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13001667 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001668 depotDir = re.sub(r"/$", "", depotDir)
1669 return os.path.split(depotDir)[1]
1670
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001671 def run(self, args):
1672 if len(args) < 1:
1673 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001674
1675 if self.keepRepoPath and not self.cloneDestination:
1676 sys.stderr.write("Must specify destination for --keep-path\n")
1677 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001678
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001679 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02001680
1681 if not self.cloneDestination and len(depotPaths) > 1:
1682 self.cloneDestination = depotPaths[-1]
1683 depotPaths = depotPaths[:-1]
1684
Tommy Thorn354081d2008-02-03 10:38:51 -08001685 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001686 for p in depotPaths:
1687 if not p.startswith("//"):
1688 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001689
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001690 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02001691 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001692
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001693 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Kevin Greenc3bf3f12007-06-11 16:48:07 -04001694 if not os.path.exists(self.cloneDestination):
1695 os.makedirs(self.cloneDestination)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001696 os.chdir(self.cloneDestination)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001697 system("git init")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001698 self.gitdir = os.getcwd() + "/.git"
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001699 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001700 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001701 if self.branch != "master":
Simon Hausmann8f9b2e02007-05-18 22:13:26 +02001702 if gitBranchExists("refs/remotes/p4/master"):
1703 system("git branch master refs/remotes/p4/master")
1704 system("git checkout -f")
1705 else:
1706 print "Could not detect main branch. No checkout/master branch created."
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001707
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001708 return True
1709
Simon Hausmann09d89de2007-06-20 23:10:28 +02001710class P4Branches(Command):
1711 def __init__(self):
1712 Command.__init__(self)
1713 self.options = [ ]
1714 self.description = ("Shows the git branches that hold imports and their "
1715 + "corresponding perforce depot paths")
1716 self.verbose = False
1717
1718 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02001719 if originP4BranchesExist():
1720 createOrUpdateBranchesFromOrigin()
1721
Simon Hausmann09d89de2007-06-20 23:10:28 +02001722 cmdline = "git rev-parse --symbolic "
1723 cmdline += " --remotes"
1724
1725 for line in read_pipe_lines(cmdline):
1726 line = line.strip()
1727
1728 if not line.startswith('p4/') or line == "p4/HEAD":
1729 continue
1730 branch = line
1731
1732 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1733 settings = extractSettingsGitLog(log)
1734
1735 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1736 return True
1737
Simon Hausmannb9847332007-03-20 20:54:23 +01001738class HelpFormatter(optparse.IndentedHelpFormatter):
1739 def __init__(self):
1740 optparse.IndentedHelpFormatter.__init__(self)
1741
1742 def format_description(self, description):
1743 if description:
1744 return description + "\n"
1745 else:
1746 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001747
Simon Hausmann86949ee2007-03-19 20:59:12 +01001748def printUsage(commands):
1749 print "usage: %s <command> [options]" % sys.argv[0]
1750 print ""
1751 print "valid commands: %s" % ", ".join(commands)
1752 print ""
1753 print "Try %s <command> --help for command specific help." % sys.argv[0]
1754 print ""
1755
1756commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001757 "debug" : P4Debug,
1758 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02001759 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001760 "sync" : P4Sync,
1761 "rebase" : P4Rebase,
1762 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02001763 "rollback" : P4RollBack,
1764 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01001765}
1766
Simon Hausmann86949ee2007-03-19 20:59:12 +01001767
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001768def main():
1769 if len(sys.argv[1:]) == 0:
1770 printUsage(commands.keys())
1771 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001772
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001773 cmd = ""
1774 cmdName = sys.argv[1]
1775 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001776 klass = commands[cmdName]
1777 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001778 except KeyError:
1779 print "unknown command %s" % cmdName
1780 print ""
1781 printUsage(commands.keys())
1782 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001783
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001784 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001785 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001786
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001787 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001788
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001789 if len(options) > 0:
1790 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001791
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001792 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1793 options,
1794 description = cmd.description,
1795 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01001796
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001797 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1798 global verbose
1799 verbose = cmd.verbose
1800 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001801 if cmd.gitdir == None:
1802 cmd.gitdir = os.path.abspath(".git")
1803 if not isValidGitDir(cmd.gitdir):
1804 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1805 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001806 cdup = read_pipe("git rev-parse --show-cdup").strip()
1807 if len(cdup) > 0:
1808 os.chdir(cdup);
1809
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001810 if not isValidGitDir(cmd.gitdir):
1811 if isValidGitDir(cmd.gitdir + "/.git"):
1812 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001813 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001814 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02001815
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001816 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001817
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001818 if not cmd.run(args):
1819 parser.print_help()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001820
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001821
1822if __name__ == '__main__':
1823 main()