blob: 38438f3c4a548f8d45d66c9c207acd67bd598454 [file] [log] [blame]
Simon Hausmann86949ee2007-03-19 20:59:12 +01001#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
Simon Hausmannc8cbbee2007-05-28 14:43:25 +02005# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
Simon Hausmann83dce552007-03-19 22:26:36 +01007# 2007 Trolltech ASA
Simon Hausmann86949ee2007-03-19 20:59:12 +01008# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
10
Simon Hausmann08483582007-05-15 14:31:06 +020011import optparse, sys, os, marshal, popen2, subprocess, shelve
Simon Hausmann25df95c2007-05-15 15:15:39 +020012import tempfile, getopt, sha, os.path, time, platform
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -030013import re
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030014
Simon Hausmannb9847332007-03-20 20:54:23 +010015from sets import Set;
Simon Hausmann4f5cf762007-03-19 22:25:17 +010016
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030017verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +010018
Anand Kumria21a50752008-08-10 19:26:28 +010019
20def p4_build_cmd(cmd):
21 """Build a suitable p4 command line.
22
23 This consolidates building and returning a p4 command line into one
24 location. It means that hooking into the environment, or other configuration
25 can be done more easily.
26 """
Anand Kumriaabcaf072008-08-10 19:26:31 +010027 real_cmd = "%s " % "p4"
28
29 user = gitConfig("git-p4.user")
30 if len(user) > 0:
31 real_cmd += "-u %s " % user
32
33 password = gitConfig("git-p4.password")
34 if len(password) > 0:
35 real_cmd += "-P %s " % password
36
37 port = gitConfig("git-p4.port")
38 if len(port) > 0:
39 real_cmd += "-p %s " % port
40
41 host = gitConfig("git-p4.host")
42 if len(host) > 0:
43 real_cmd += "-h %s " % host
44
45 client = gitConfig("git-p4.client")
46 if len(client) > 0:
47 real_cmd += "-c %s " % client
48
49 real_cmd += "%s" % (cmd)
Anand Kumriaee064272008-08-10 19:26:29 +010050 if verbose:
51 print real_cmd
Anand Kumria21a50752008-08-10 19:26:28 +010052 return real_cmd
53
Robert Blum053fd0c2008-08-01 12:50:03 -070054def chdir(dir):
55 if os.name == 'nt':
56 os.environ['PWD']=dir
57 os.chdir(dir)
58
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030059def die(msg):
60 if verbose:
61 raise Exception(msg)
62 else:
63 sys.stderr.write(msg + "\n")
64 sys.exit(1)
65
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030066def write_pipe(c, str):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030067 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030068 sys.stderr.write('Writing pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030069
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030070 pipe = os.popen(c, 'w')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030071 val = pipe.write(str)
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030072 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030073 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030074
75 return val
76
Anand Kumriad9429192008-08-14 23:40:38 +010077def p4_write_pipe(c, str):
78 real_cmd = p4_build_cmd(c)
Tor Arvid Lund893d3402008-08-21 23:11:40 +020079 return write_pipe(real_cmd, str)
Anand Kumriad9429192008-08-14 23:40:38 +010080
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030081def read_pipe(c, ignore_error=False):
82 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030083 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030084
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030085 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030086 val = pipe.read()
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030087 if pipe.close() and not ignore_error:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030088 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030089
90 return val
91
Anand Kumriad9429192008-08-14 23:40:38 +010092def p4_read_pipe(c, ignore_error=False):
93 real_cmd = p4_build_cmd(c)
94 return read_pipe(real_cmd, ignore_error)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030095
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -030096def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030097 if verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -030098 sys.stderr.write('Reading pipe: %s\n' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -030099 ## todo: check return status
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -0300100 pipe = os.popen(c, 'rb')
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300101 val = pipe.readlines()
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -0300102 if pipe.close():
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300103 die('Command failed: %s' % c)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300104
105 return val
Simon Hausmanncaace112007-05-15 14:57:57 +0200106
Anand Kumria23181212008-08-10 19:26:24 +0100107def p4_read_pipe_lines(c):
108 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +0100109 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +0100110 return read_pipe_lines(real_cmd)
111
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300112def system(cmd):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300113 if verbose:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300114 sys.stderr.write("executing %s\n" % cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300115 if os.system(cmd) != 0:
116 die("command failed: %s" % cmd)
117
Anand Kumriabf9320f2008-08-10 19:26:26 +0100118def p4_system(cmd):
119 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +0100120 real_cmd = p4_build_cmd(cmd)
Anand Kumriabf9320f2008-08-10 19:26:26 +0100121 return system(real_cmd)
122
David Brownb9fc6ea2007-09-19 13:12:48 -0700123def isP4Exec(kind):
124 """Determine if a Perforce 'kind' should have execute permission
125
126 'p4 help filetypes' gives a list of the types. If it starts with 'x',
127 or x follows one of a few letters. Otherwise, if there is an 'x' after
128 a plus sign, it is also executable"""
129 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
130
Chris Pettittc65b6702007-11-01 20:43:14 -0700131def setP4ExecBit(file, mode):
132 # Reopens an already open file and changes the execute bit to match
133 # the execute bit setting in the passed in mode.
134
135 p4Type = "+x"
136
137 if not isModeExec(mode):
138 p4Type = getP4OpenedType(file)
139 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
140 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
141 if p4Type[-1] == "+":
142 p4Type = p4Type[0:-1]
143
Anand Kumria87b611d2008-08-10 19:26:27 +0100144 p4_system("reopen -t %s %s" % (p4Type, file))
Chris Pettittc65b6702007-11-01 20:43:14 -0700145
146def getP4OpenedType(file):
147 # Returns the perforce file type for the given file.
148
Anand Kumriaa7d3ef92008-08-14 23:40:39 +0100149 result = p4_read_pipe("opened %s" % file)
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100150 match = re.match(".*\((.+)\)\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700151 if match:
152 return match.group(1)
153 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100154 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700155
Chris Pettittb43b0a32007-11-01 20:43:13 -0700156def diffTreePattern():
157 # This is a simple generator for the diff tree regex pattern. This could be
158 # a class variable if this and parseDiffTreeEntry were a part of a class.
159 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
160 while True:
161 yield pattern
162
163def parseDiffTreeEntry(entry):
164 """Parses a single diff tree entry into its component elements.
165
166 See git-diff-tree(1) manpage for details about the format of the diff
167 output. This method returns a dictionary with the following elements:
168
169 src_mode - The mode of the source file
170 dst_mode - The mode of the destination file
171 src_sha1 - The sha1 for the source file
172 dst_sha1 - The sha1 fr the destination file
173 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
174 status_score - The score for the status (applicable for 'C' and 'R'
175 statuses). This is None if there is no score.
176 src - The path for the source file.
177 dst - The path for the destination file. This is only present for
178 copy or renames. If it is not present, this is None.
179
180 If the pattern is not matched, None is returned."""
181
182 match = diffTreePattern().next().match(entry)
183 if match:
184 return {
185 'src_mode': match.group(1),
186 'dst_mode': match.group(2),
187 'src_sha1': match.group(3),
188 'dst_sha1': match.group(4),
189 'status': match.group(5),
190 'status_score': match.group(6),
191 'src': match.group(7),
192 'dst': match.group(10)
193 }
194 return None
195
Chris Pettittc65b6702007-11-01 20:43:14 -0700196def isModeExec(mode):
197 # Returns True if the given git mode represents an executable file,
198 # otherwise False.
199 return mode[-3:] == "755"
200
201def isModeExecChanged(src_mode, dst_mode):
202 return isModeExec(src_mode) != isModeExec(dst_mode)
203
Luke Diamandb9327052009-07-30 00:13:46 +0100204def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
Anand Kumria155af832008-08-10 19:26:30 +0100205 cmd = p4_build_cmd("-G %s" % (cmd))
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300206 if verbose:
207 sys.stderr.write("Opening pipe: %s\n" % cmd)
Scott Lamb9f90c732007-07-15 20:58:10 -0700208
209 # Use a temporary file to avoid deadlocks without
210 # subprocess.communicate(), which would put another copy
211 # of stdout into memory.
212 stdin_file = None
213 if stdin is not None:
214 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
215 stdin_file.write(stdin)
216 stdin_file.flush()
217 stdin_file.seek(0)
218
219 p4 = subprocess.Popen(cmd, shell=True,
220 stdin=stdin_file,
221 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100222
223 result = []
224 try:
225 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700226 entry = marshal.load(p4.stdout)
Luke Diamandb9327052009-07-30 00:13:46 +0100227 if cb is not None:
228 cb(entry)
229 else:
230 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100231 except EOFError:
232 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700233 exitCode = p4.wait()
234 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200235 entry = {}
236 entry["p4ExitCode"] = exitCode
237 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100238
239 return result
240
241def p4Cmd(cmd):
242 list = p4CmdList(cmd)
243 result = {}
244 for entry in list:
245 result.update(entry)
246 return result;
247
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100248def p4Where(depotPath):
249 if not depotPath.endswith("/"):
250 depotPath += "/"
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100251 depotPath = depotPath + "..."
252 outputList = p4CmdList("where %s" % depotPath)
253 output = None
254 for entry in outputList:
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100255 if "depotFile" in entry:
256 if entry["depotFile"] == depotPath:
257 output = entry
258 break
259 elif "data" in entry:
260 data = entry.get("data")
261 space = data.find(" ")
262 if data[:space] == depotPath:
263 output = entry
264 break
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100265 if output == None:
266 return ""
Simon Hausmanndc524032007-05-21 09:34:56 +0200267 if output["code"] == "error":
268 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100269 clientPath = ""
270 if "path" in output:
271 clientPath = output.get("path")
272 elif "data" in output:
273 data = output.get("data")
274 lastSpace = data.rfind(" ")
275 clientPath = data[lastSpace + 1:]
276
277 if clientPath.endswith("..."):
278 clientPath = clientPath[:-3]
279 return clientPath
280
Simon Hausmann86949ee2007-03-19 20:59:12 +0100281def currentGitBranch():
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300282 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100283
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100284def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300285 if (os.path.exists(path + "/HEAD")
286 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100287 return True;
288 return False
289
Simon Hausmann463e8af2007-05-17 09:13:54 +0200290def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300291 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200292
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100293def extractLogMessageFromGitCommit(commit):
294 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300295
296 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100297 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300298 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100299 if not foundTitle:
300 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200301 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100302 continue
303
304 logMessage += log
305 return logMessage
306
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300307def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100308 values = {}
309 for line in log.split("\n"):
310 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300311 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
312 if not m:
313 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100314
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300315 assignments = m.group(1).split (':')
316 for a in assignments:
317 vals = a.split ('=')
318 key = vals[0].strip()
319 val = ('='.join (vals[1:])).strip()
320 if val.endswith ('\"') and val.startswith('"'):
321 val = val[1:-1]
322
323 values[key] = val
324
Simon Hausmann845b42c2007-06-07 09:19:34 +0200325 paths = values.get("depot-paths")
326 if not paths:
327 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200328 if paths:
329 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300330 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100331
Simon Hausmann8136a632007-03-22 21:27:14 +0100332def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300333 proc = subprocess.Popen(["git", "rev-parse", branch],
334 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200335 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100336
John Chapman36bd8442008-11-08 14:22:49 +1100337_gitConfig = {}
Simon Hausmann01265102007-05-25 10:36:10 +0200338def gitConfig(key):
John Chapman36bd8442008-11-08 14:22:49 +1100339 if not _gitConfig.has_key(key):
340 _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
341 return _gitConfig[key]
Simon Hausmann01265102007-05-25 10:36:10 +0200342
Simon Hausmann062410b2007-07-18 10:56:31 +0200343def p4BranchesInGit(branchesAreInRemotes = True):
344 branches = {}
345
346 cmdline = "git rev-parse --symbolic "
347 if branchesAreInRemotes:
348 cmdline += " --remotes"
349 else:
350 cmdline += " --branches"
351
352 for line in read_pipe_lines(cmdline):
353 line = line.strip()
354
355 ## only import to p4/
356 if not line.startswith('p4/') or line == "p4/HEAD":
357 continue
358 branch = line
359
360 # strip off p4
361 branch = re.sub ("^p4/", "", line)
362
363 branches[branch] = parseRevision(line)
364 return branches
365
Simon Hausmann9ceab362007-06-22 00:01:57 +0200366def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200367 branches = p4BranchesInGit()
368 # map from depot-path to branch name
369 branchByDepotPath = {}
370 for branch in branches.keys():
371 tip = branches[branch]
372 log = extractLogMessageFromGitCommit(tip)
373 settings = extractSettingsGitLog(log)
374 if settings.has_key("depot-paths"):
375 paths = ",".join(settings["depot-paths"])
376 branchByDepotPath[paths] = "remotes/p4/" + branch
377
Simon Hausmann27d2d812007-06-12 14:31:59 +0200378 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200379 parent = 0
380 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200381 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200382 log = extractLogMessageFromGitCommit(commit)
383 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200384 if settings.has_key("depot-paths"):
385 paths = ",".join(settings["depot-paths"])
386 if branchByDepotPath.has_key(paths):
387 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200388
Simon Hausmann86506fe2007-07-18 12:40:12 +0200389 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200390
Simon Hausmann86506fe2007-07-18 12:40:12 +0200391 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200392
Simon Hausmann5ca44612007-08-24 17:44:16 +0200393def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
394 if not silent:
395 print ("Creating/updating branch(es) in %s based on origin branch(es)"
396 % localRefPrefix)
397
398 originPrefix = "origin/p4/"
399
400 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
401 line = line.strip()
402 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
403 continue
404
405 headName = line[len(originPrefix):]
406 remoteHead = localRefPrefix + headName
407 originHead = line
408
409 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
410 if (not original.has_key('depot-paths')
411 or not original.has_key('change')):
412 continue
413
414 update = False
415 if not gitBranchExists(remoteHead):
416 if verbose:
417 print "creating %s" % remoteHead
418 update = True
419 else:
420 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
421 if settings.has_key('change') > 0:
422 if settings['depot-paths'] == original['depot-paths']:
423 originP4Change = int(original['change'])
424 p4Change = int(settings['change'])
425 if originP4Change > p4Change:
426 print ("%s (%s) is newer than %s (%s). "
427 "Updating p4 branch from origin."
428 % (originHead, originP4Change,
429 remoteHead, p4Change))
430 update = True
431 else:
432 print ("Ignoring: %s was imported from %s while "
433 "%s was imported from %s"
434 % (originHead, ','.join(original['depot-paths']),
435 remoteHead, ','.join(settings['depot-paths'])))
436
437 if update:
438 system("git update-ref %s %s" % (remoteHead, originHead))
439
440def originP4BranchesExist():
441 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
442
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200443def p4ChangesForPaths(depotPaths, changeRange):
444 assert depotPaths
Anand Kumriab340fa42008-08-10 19:26:25 +0100445 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200446 for p in depotPaths]))
447
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500448 changes = {}
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200449 for line in output:
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500450 changeNum = int(line.split(" ")[1])
451 changes[changeNum] = True
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200452
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500453 changelist = changes.keys()
454 changelist.sort()
455 return changelist
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200456
Simon Hausmannb9847332007-03-20 20:54:23 +0100457class Command:
458 def __init__(self):
459 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200460 self.needsGit = True
Simon Hausmannb9847332007-03-20 20:54:23 +0100461
462class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100463 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100464 Command.__init__(self)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100465 self.options = [
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300466 optparse.make_option("--verbose", dest="verbose", action="store_true",
467 default=False),
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300468 ]
Simon Hausmannc8c39112007-03-19 21:02:30 +0100469 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200470 self.needsGit = False
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300471 self.verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100472
473 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300474 j = 0
Simon Hausmann86949ee2007-03-19 20:59:12 +0100475 for output in p4CmdList(" ".join(args)):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300476 print 'Element: %d' % j
477 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100478 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100479 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100480
Simon Hausmann58346842007-05-21 22:57:06 +0200481class P4RollBack(Command):
482 def __init__(self):
483 Command.__init__(self)
484 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +0200485 optparse.make_option("--verbose", dest="verbose", action="store_true"),
486 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +0200487 ]
488 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann52102d42007-05-21 23:44:24 +0200489 self.verbose = False
Simon Hausmann0c66a782007-05-23 20:07:57 +0200490 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +0200491
492 def run(self, args):
493 if len(args) != 1:
494 return False
495 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +0200496
Simon Hausmannad192f22007-05-23 23:44:19 +0200497 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +0200498 die("Problems executing p4");
499
Simon Hausmann0c66a782007-05-23 20:07:57 +0200500 if self.rollbackLocalBranches:
501 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300502 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200503 else:
504 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300505 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +0200506
507 for line in lines:
508 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300509 line = line.strip()
510 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +0200511 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300512 settings = extractSettingsGitLog(log)
513
514 depotPaths = settings['depot-paths']
515 change = settings['change']
516
Simon Hausmann58346842007-05-21 22:57:06 +0200517 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +0200518
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300519 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
520 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +0200521 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
522 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
523 continue
524
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300525 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +0200526 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +0200527 if self.verbose:
528 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +0200529 system("git update-ref %s \"%s^\"" % (ref, ref))
530 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300531 settings = extractSettingsGitLog(log)
532
533
534 depotPaths = settings['depot-paths']
535 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +0200536
537 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +0200538 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +0200539
540 return True
541
Simon Hausmann711544b2007-04-01 15:40:46 +0200542class P4Submit(Command):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100543 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +0100544 Command.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100545 self.options = [
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300546 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100547 optparse.make_option("--origin", dest="origin"),
Chris Pettittd9a5f252007-10-15 22:15:06 -0700548 optparse.make_option("-M", dest="detectRename", action="store_true"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100549 ]
550 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200551 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100552 self.interactive = True
Simon Hausmann95124972007-03-23 09:16:07 +0100553 self.origin = ""
Chris Pettittd9a5f252007-10-15 22:15:06 -0700554 self.detectRename = False
Simon Hausmannb0d10df2007-06-07 13:09:14 +0200555 self.verbose = False
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +0200556 self.isWindows = (platform.system() == "Windows")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100557
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100558 def check(self):
559 if len(p4CmdList("opened ...")) > 0:
560 die("You have files opened with perforce! Close them before starting the sync.")
561
Simon Hausmannedae1e22008-02-19 09:29:06 +0100562 # replaces everything between 'Description:' and the next P4 submit template field with the
563 # commit message
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100564 def prepareLogMessage(self, template, message):
565 result = ""
566
Simon Hausmannedae1e22008-02-19 09:29:06 +0100567 inDescriptionSection = False
568
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100569 for line in template.split("\n"):
570 if line.startswith("#"):
571 result += line + "\n"
572 continue
573
Simon Hausmannedae1e22008-02-19 09:29:06 +0100574 if inDescriptionSection:
575 if line.startswith("Files:"):
576 inDescriptionSection = False
577 else:
578 continue
579 else:
580 if line.startswith("Description:"):
581 inDescriptionSection = True
582 line += "\n"
583 for messageLine in message.split("\n"):
584 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100585
Simon Hausmannedae1e22008-02-19 09:29:06 +0100586 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100587
588 return result
589
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200590 def prepareSubmitTemplate(self):
591 # remove lines in the Files section that show changes to files outside the depot path we're committing into
592 template = ""
593 inFilesSection = False
Anand Kumriab340fa42008-08-10 19:26:25 +0100594 for line in p4_read_pipe_lines("change -o"):
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100595 if line.endswith("\r\n"):
596 line = line[:-2] + "\n"
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200597 if inFilesSection:
598 if line.startswith("\t"):
599 # path starts and ends with a tab
600 path = line[1:]
601 lastTab = path.rfind("\t")
602 if lastTab != -1:
603 path = path[:lastTab]
604 if not path.startswith(self.depotPath):
605 continue
606 else:
607 inFilesSection = False
608 else:
609 if line.startswith("Files:"):
610 inFilesSection = True
611
612 template += line
613
614 return template
615
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300616 def applyCommit(self, id):
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100617 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
618 diffOpts = ("", "-M")[self.detectRename]
619 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100620 filesToAdd = set()
621 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +0200622 editedFiles = set()
Chris Pettittc65b6702007-11-01 20:43:14 -0700623 filesToChangeExecBit = {}
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100624 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -0700625 diff = parseDiffTreeEntry(line)
626 modifier = diff['status']
627 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100628 if modifier == "M":
Anand Kumria87b611d2008-08-10 19:26:27 +0100629 p4_system("edit \"%s\"" % path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700630 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
631 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +0200632 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100633 elif modifier == "A":
634 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -0700635 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100636 if path in filesToDelete:
637 filesToDelete.remove(path)
638 elif modifier == "D":
639 filesToDelete.add(path)
640 if path in filesToAdd:
641 filesToAdd.remove(path)
Chris Pettittd9a5f252007-10-15 22:15:06 -0700642 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -0700643 src, dest = diff['src'], diff['dst']
Anand Kumria87b611d2008-08-10 19:26:27 +0100644 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
645 p4_system("edit \"%s\"" % (dest))
Chris Pettittc65b6702007-11-01 20:43:14 -0700646 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
647 filesToChangeExecBit[dest] = diff['dst_mode']
Chris Pettittd9a5f252007-10-15 22:15:06 -0700648 os.unlink(dest)
649 editedFiles.add(dest)
650 filesToDelete.add(src)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100651 else:
652 die("unknown modifier %s for %s" % (modifier, path))
653
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100654 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
Simon Hausmann47a130b2007-05-20 16:33:21 +0200655 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200656 tryPatchCmd = patchcmd + "--check -"
657 applyPatchCmd = patchcmd + "--check --apply -"
Simon Hausmann51a26402007-04-15 09:59:56 +0200658
Simon Hausmann47a130b2007-05-20 16:33:21 +0200659 if os.system(tryPatchCmd) != 0:
Simon Hausmann51a26402007-04-15 09:59:56 +0200660 print "Unfortunately applying the change failed!"
661 print "What do you want to do?"
662 response = "x"
663 while response != "s" and response != "a" and response != "w":
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300664 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
665 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
Simon Hausmann51a26402007-04-15 09:59:56 +0200666 if response == "s":
667 print "Skipping! Good luck with the next patches..."
Simon Hausmann20947142007-09-13 22:10:18 +0200668 for f in editedFiles:
Anand Kumria87b611d2008-08-10 19:26:27 +0100669 p4_system("revert \"%s\"" % f);
Simon Hausmann20947142007-09-13 22:10:18 +0200670 for f in filesToAdd:
671 system("rm %s" %f)
Simon Hausmann51a26402007-04-15 09:59:56 +0200672 return
673 elif response == "a":
Simon Hausmann47a130b2007-05-20 16:33:21 +0200674 os.system(applyPatchCmd)
Simon Hausmann51a26402007-04-15 09:59:56 +0200675 if len(filesToAdd) > 0:
676 print "You may also want to call p4 add on the following files:"
677 print " ".join(filesToAdd)
678 if len(filesToDelete):
679 print "The following files should be scheduled for deletion with p4 delete:"
680 print " ".join(filesToDelete)
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300681 die("Please resolve and submit the conflict manually and "
682 + "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200683 elif response == "w":
684 system(diffcmd + " > patch.txt")
685 print "Patch saved to patch.txt in %s !" % self.clientPath
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300686 die("Please resolve and submit the conflict manually and "
687 "continue afterwards with git-p4 submit --continue")
Simon Hausmann51a26402007-04-15 09:59:56 +0200688
Simon Hausmann47a130b2007-05-20 16:33:21 +0200689 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100690
691 for f in filesToAdd:
Anand Kumria87b611d2008-08-10 19:26:27 +0100692 p4_system("add \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100693 for f in filesToDelete:
Anand Kumria87b611d2008-08-10 19:26:27 +0100694 p4_system("revert \"%s\"" % f)
695 p4_system("delete \"%s\"" % f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100696
Chris Pettittc65b6702007-11-01 20:43:14 -0700697 # Set/clear executable bits
698 for f in filesToChangeExecBit.keys():
699 mode = filesToChangeExecBit[f]
700 setP4ExecBit(f, mode)
701
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100702 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +0100703 logMessage = logMessage.strip()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100704
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200705 template = self.prepareSubmitTemplate()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100706
707 if self.interactive:
708 submitTemplate = self.prepareLogMessage(template, logMessage)
Shawn Bohrer67abd412008-03-12 19:03:23 -0500709 if os.environ.has_key("P4DIFF"):
710 del(os.environ["P4DIFF"])
Anand Kumriaa7d3ef92008-08-14 23:40:39 +0100711 diff = p4_read_pipe("diff -du ...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100712
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100713 newdiff = ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100714 for newFile in filesToAdd:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100715 newdiff += "==== new file ====\n"
716 newdiff += "--- /dev/null\n"
717 newdiff += "+++ %s\n" % newFile
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100718 f = open(newFile, "r")
719 for line in f.readlines():
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100720 newdiff += "+" + line
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100721 f.close()
722
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100723 separatorLine = "######## everything below this line is just the diff #######\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100724
Simon Hausmanne96e4002008-01-04 14:27:55 +0100725 [handle, fileName] = tempfile.mkstemp()
726 tmpFile = os.fdopen(handle, "w+")
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100727 if self.isWindows:
728 submitTemplate = submitTemplate.replace("\n", "\r\n")
729 separatorLine = separatorLine.replace("\n", "\r\n")
730 newdiff = newdiff.replace("\n", "\r\n")
731 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
Simon Hausmanne96e4002008-01-04 14:27:55 +0100732 tmpFile.close()
Simon Hausmanncdc7e382008-08-27 09:30:29 +0200733 mtime = os.stat(fileName).st_mtime
Simon Hausmanne96e4002008-01-04 14:27:55 +0100734 defaultEditor = "vi"
735 if platform.system() == "Windows":
736 defaultEditor = "notepad"
Shawn Bohrer82cea9f2008-03-12 19:03:24 -0500737 if os.environ.has_key("P4EDITOR"):
738 editor = os.environ.get("P4EDITOR")
739 else:
740 editor = os.environ.get("EDITOR", defaultEditor);
Simon Hausmanne96e4002008-01-04 14:27:55 +0100741 system(editor + " " + fileName)
Simon Hausmanncb4f1282007-05-25 22:34:30 +0200742
Simon Hausmanncdc7e382008-08-27 09:30:29 +0200743 response = "y"
744 if os.stat(fileName).st_mtime <= mtime:
745 response = "x"
746 while response != "y" and response != "n":
747 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
748
749 if response == "y":
750 tmpFile = open(fileName, "rb")
751 message = tmpFile.read()
752 tmpFile.close()
753 submitTemplate = message[:message.index(separatorLine)]
754 if self.isWindows:
755 submitTemplate = submitTemplate.replace("\r\n", "\n")
756 p4_write_pipe("submit -i", submitTemplate)
757 else:
758 for f in editedFiles:
759 p4_system("revert \"%s\"" % f);
760 for f in filesToAdd:
761 p4_system("revert \"%s\"" % f);
762 system("rm %s" %f)
763
764 os.remove(fileName)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100765 else:
766 fileName = "submit.txt"
767 file = open(fileName, "w+")
768 file.write(self.prepareLogMessage(template, logMessage))
769 file.close()
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -0300770 print ("Perforce submit template written as %s. "
771 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
772 % (fileName, fileName))
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100773
774 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200775 if len(args) == 0:
776 self.master = currentGitBranch()
Simon Hausmann4280e532007-05-25 08:49:18 +0200777 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
Simon Hausmannc9b50e62007-03-29 19:15:24 +0200778 die("Detecting current git branch failed!")
779 elif len(args) == 1:
780 self.master = args[0]
781 else:
782 return False
783
Jing Xue4c2d5d72008-06-22 14:12:39 -0400784 allowSubmit = gitConfig("git-p4.allowSubmit")
785 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
786 die("%s is not in git-p4.allowSubmit" % self.master)
787
Simon Hausmann27d2d812007-06-12 14:31:59 +0200788 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200789 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200790 if len(self.origin) == 0:
791 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200792
793 if self.verbose:
794 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +0100795
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200796 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +0100797 print "Internal error: cannot locate perforce depot path from existing branches"
798 sys.exit(128)
799
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200800 self.clientPath = p4Where(self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +0100801
Simon Hausmann51a26402007-04-15 09:59:56 +0200802 if len(self.clientPath) == 0:
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200803 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
Simon Hausmann95124972007-03-23 09:16:07 +0100804 sys.exit(128)
805
Simon Hausmannea99c3a2007-08-08 17:06:55 +0200806 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +0200807 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +0200808
Robert Blum053fd0c2008-08-01 12:50:03 -0700809 chdir(self.clientPath)
Simon Hausmann31f9ec12007-08-21 11:53:02 +0200810 print "Syncronizing p4 checkout..."
Anand Kumria87b611d2008-08-10 19:26:27 +0100811 p4_system("sync ...")
Simon Hausmann95124972007-03-23 09:16:07 +0100812
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100813 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100814
Simon Hausmann4c750c02008-02-19 09:37:16 +0100815 commits = []
816 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
817 commits.append(line.strip())
818 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100819
820 while len(commits) > 0:
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100821 commit = commits[0]
822 commits = commits[1:]
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -0300823 self.applyCommit(commit)
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100824 if not self.interactive:
825 break
826
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100827 if len(commits) == 0:
Simon Hausmann4c750c02008-02-19 09:37:16 +0100828 print "All changes applied!"
Robert Blum053fd0c2008-08-01 12:50:03 -0700829 chdir(self.oldWorkingDirectory)
Simon Hausmann14594f42007-08-22 09:07:15 +0200830
Simon Hausmann4c750c02008-02-19 09:37:16 +0100831 sync = P4Sync()
832 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +0200833
Simon Hausmann4c750c02008-02-19 09:37:16 +0100834 rebase = P4Rebase()
835 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100836
Simon Hausmannb9847332007-03-20 20:54:23 +0100837 return True
838
Simon Hausmann711544b2007-04-01 15:40:46 +0200839class P4Sync(Command):
Simon Hausmannb9847332007-03-20 20:54:23 +0100840 def __init__(self):
841 Command.__init__(self)
842 self.options = [
843 optparse.make_option("--branch", dest="branch"),
844 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
845 optparse.make_option("--changesfile", dest="changesFile"),
846 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +0200847 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Simon Hausmanna028a982007-05-23 00:03:08 +0200848 optparse.make_option("--verbose", dest="verbose", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -0300849 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
850 help="Import into refs/heads/ , not refs/remotes"),
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300851 optparse.make_option("--max-changes", dest="maxChanges"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300852 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100853 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
854 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
855 help="Only sync files that are included in the Perforce Client Spec")
Simon Hausmannb9847332007-03-20 20:54:23 +0100856 ]
857 self.description = """Imports from Perforce into a git repository.\n
858 example:
859 //depot/my/project/ -- to import the current head
860 //depot/my/project/@all -- to import everything
861 //depot/my/project/@1,6 -- to import only from revision 1 to 6
862
863 (a ... is not needed in the path p4 specification, it's added implicitly)"""
864
865 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +0100866 self.silent = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100867 self.createdBranches = Set()
868 self.committedChanges = Set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +0100869 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +0100870 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +0200871 self.detectLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100872 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +0200873 self.syncWithOrigin = True
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200874 self.verbose = False
Simon Hausmanna028a982007-05-23 00:03:08 +0200875 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +0200876 self.maxChanges = ""
Marius Storm-Olsenc1f91972007-05-24 14:07:55 +0200877 self.isWindows = (platform.system() == "Windows")
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300878 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300879 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +0200880 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -0800881 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +0100882 self.useClientSpec = False
883 self.clientSpecDirs = []
Simon Hausmannb9847332007-03-20 20:54:23 +0100884
Simon Hausmann01265102007-05-25 10:36:10 +0200885 if gitConfig("git-p4.syncFromOrigin") == "false":
886 self.syncWithOrigin = False
887
Simon Hausmannb9847332007-03-20 20:54:23 +0100888 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -0800889 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
890 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +0100891 files = []
892 fnum = 0
893 while commit.has_key("depotFile%s" % fnum):
894 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300895
Tommy Thorn354081d2008-02-03 10:38:51 -0800896 if [p for p in self.cloneExclude
897 if path.startswith (p)]:
898 found = False
899 else:
900 found = [p for p in self.depotPaths
901 if path.startswith (p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300902 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +0100903 fnum = fnum + 1
904 continue
905
906 file = {}
907 file["path"] = path
908 file["rev"] = commit["rev%s" % fnum]
909 file["action"] = commit["action%s" % fnum]
910 file["type"] = commit["type%s" % fnum]
911 files.append(file)
912 fnum = fnum + 1
913 return files
914
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300915 def stripRepoPath(self, path, prefixes):
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300916 if self.keepRepoPath:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300917 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300918
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300919 for p in prefixes:
920 if path.startswith(p):
921 path = path[len(p):]
922
923 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300924
Simon Hausmann71b112d2007-05-19 11:54:11 +0200925 def splitFilesIntoBranches(self, commit):
Simon Hausmannd5904672007-05-19 11:07:32 +0200926 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +0200927 fnum = 0
928 while commit.has_key("depotFile%s" % fnum):
929 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300930 found = [p for p in self.depotPaths
931 if path.startswith (p)]
932 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +0200933 fnum = fnum + 1
934 continue
935
936 file = {}
937 file["path"] = path
938 file["rev"] = commit["rev%s" % fnum]
939 file["action"] = commit["action%s" % fnum]
940 file["type"] = commit["type%s" % fnum]
941 fnum = fnum + 1
942
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300943 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +0100944
Simon Hausmann4b97ffb2007-05-18 21:45:23 +0200945 for branch in self.knownBranches.keys():
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300946
947 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
948 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +0200949 if branch not in branches:
950 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +0200951 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +0200952 break
Simon Hausmannb9847332007-03-20 20:54:23 +0100953
954 return branches
955
Luke Diamandb9327052009-07-30 00:13:46 +0100956 # output one file from the P4 stream
957 # - helper for streamP4Files
958
959 def streamOneP4File(self, file, contents):
960 if file["type"] == "apple":
961 print "\nfile %s is a strange apple file that forks. Ignoring" % \
962 file['depotFile']
963 return
964
965 relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
966 if verbose:
967 sys.stderr.write("%s\n" % relPath)
968
969 mode = "644"
970 if isP4Exec(file["type"]):
971 mode = "755"
972 elif file["type"] == "symlink":
973 mode = "120000"
974 # p4 print on a symlink contains "target\n", so strip it off
975 last = contents.pop()
976 last = last[:-1]
977 contents.append(last)
978
979 if self.isWindows and file["type"].endswith("text"):
980 mangled = []
981 for data in contents:
982 data = data.replace("\r\n", "\n")
983 mangled.append(data)
984 contents = mangled
985
986 if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
987 contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents)
988 elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
989 contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents)
990
991 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
992
993 # total length...
994 length = 0
995 for d in contents:
996 length = length + len(d)
997
998 self.gitStream.write("data %d\n" % length)
999 for d in contents:
1000 self.gitStream.write(d)
1001 self.gitStream.write("\n")
1002
1003 def streamOneP4Deletion(self, file):
1004 relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
1005 if verbose:
1006 sys.stderr.write("delete %s\n" % relPath)
1007 self.gitStream.write("D %s\n" % relPath)
1008
1009 # handle another chunk of streaming data
1010 def streamP4FilesCb(self, marshalled):
1011
1012 if marshalled.has_key('depotFile') and self.stream_have_file_info:
1013 # start of a new file - output the old one first
1014 self.streamOneP4File(self.stream_file, self.stream_contents)
1015 self.stream_file = {}
1016 self.stream_contents = []
1017 self.stream_have_file_info = False
1018
1019 # pick up the new file information... for the
1020 # 'data' field we need to append to our array
1021 for k in marshalled.keys():
1022 if k == 'data':
1023 self.stream_contents.append(marshalled['data'])
1024 else:
1025 self.stream_file[k] = marshalled[k]
1026
1027 self.stream_have_file_info = True
1028
1029 # Stream directly from "p4 files" into "git fast-import"
1030 def streamP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +01001031 filesForCommit = []
1032 filesToRead = []
Luke Diamandb9327052009-07-30 00:13:46 +01001033 filesToDelete = []
Simon Hausmann30b59402008-03-03 11:55:48 +01001034
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001035 for f in files:
Simon Hausmann30b59402008-03-03 11:55:48 +01001036 includeFile = True
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001037 for val in self.clientSpecDirs:
1038 if f['path'].startswith(val[0]):
Simon Hausmann30b59402008-03-03 11:55:48 +01001039 if val[1] <= 0:
1040 includeFile = False
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001041 break
1042
Simon Hausmann30b59402008-03-03 11:55:48 +01001043 if includeFile:
1044 filesForCommit.append(f)
John Chapman7f96e2e2008-11-08 14:22:48 +11001045 if f['action'] not in ('delete', 'purge'):
Simon Hausmann30b59402008-03-03 11:55:48 +01001046 filesToRead.append(f)
Luke Diamandb9327052009-07-30 00:13:46 +01001047 else:
1048 filesToDelete.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001049
Luke Diamandb9327052009-07-30 00:13:46 +01001050 # deleted files...
1051 for f in filesToDelete:
1052 self.streamOneP4Deletion(f)
1053
Simon Hausmann30b59402008-03-03 11:55:48 +01001054 if len(filesToRead) > 0:
Luke Diamandb9327052009-07-30 00:13:46 +01001055 self.stream_file = {}
1056 self.stream_contents = []
1057 self.stream_have_file_info = False
1058
1059 # curry self argument
1060 def streamP4FilesCbSelf(entry):
1061 self.streamP4FilesCb(entry)
1062
1063 p4CmdList("-x - print",
1064 '\n'.join(['%s#%s' % (f['path'], f['rev'])
Simon Hausmann30b59402008-03-03 11:55:48 +01001065 for f in filesToRead]),
Luke Diamandb9327052009-07-30 00:13:46 +01001066 cb=streamP4FilesCbSelf)
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -03001067
Luke Diamandb9327052009-07-30 00:13:46 +01001068 # do the last chunk
1069 if self.stream_file.has_key('depotFile'):
1070 self.streamOneP4File(self.stream_file, self.stream_contents)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001071
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001072 def commit(self, details, files, branch, branchPrefixes, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +01001073 epoch = details["time"]
1074 author = details["user"]
Luke Diamandb9327052009-07-30 00:13:46 +01001075 self.branchPrefixes = branchPrefixes
Simon Hausmannb9847332007-03-20 20:54:23 +01001076
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001077 if self.verbose:
1078 print "commit into %s" % branch
1079
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03001080 # start with reading files; if that fails, we should not
1081 # create a commit.
1082 new_files = []
1083 for f in files:
1084 if [p for p in branchPrefixes if f['path'].startswith(p)]:
1085 new_files.append (f)
1086 else:
1087 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03001088
Simon Hausmannb9847332007-03-20 20:54:23 +01001089 self.gitStream.write("commit %s\n" % branch)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001090# gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +01001091 self.committedChanges.add(int(details["change"]))
1092 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +02001093 if author not in self.users:
1094 self.getUserMapFromPerforceServer()
Simon Hausmannb9847332007-03-20 20:54:23 +01001095 if author in self.users:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001096 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01001097 else:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001098 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01001099
1100 self.gitStream.write("committer %s\n" % committer)
1101
1102 self.gitStream.write("data <<EOT\n")
1103 self.gitStream.write(details["desc"])
Simon Hausmann6581de02007-06-11 10:01:58 +02001104 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1105 % (','.join (branchPrefixes), details["change"]))
1106 if len(details['options']) > 0:
1107 self.gitStream.write(": options = %s" % details['options'])
1108 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001109
1110 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001111 if self.verbose:
1112 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +01001113 self.gitStream.write("from %s\n" % parent)
1114
Luke Diamandb9327052009-07-30 00:13:46 +01001115 self.streamP4Files(new_files)
Simon Hausmannb9847332007-03-20 20:54:23 +01001116 self.gitStream.write("\n")
1117
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001118 change = int(details["change"])
1119
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001120 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001121 label = self.labels[change]
1122 labelDetails = label[0]
1123 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02001124 if self.verbose:
1125 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001126
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001127 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1128 for p in branchPrefixes]))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001129
1130 if len(files) == len(labelRevisions):
1131
1132 cleanedFiles = {}
1133 for info in files:
John Chapman7f96e2e2008-11-08 14:22:48 +11001134 if info["action"] in ("delete", "purge"):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001135 continue
1136 cleanedFiles[info["depotFile"]] = info["rev"]
1137
1138 if cleanedFiles == labelRevisions:
1139 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1140 self.gitStream.write("from %s\n" % branch)
1141
1142 owner = labelDetails["Owner"]
1143 tagger = ""
1144 if author in self.users:
1145 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1146 else:
1147 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1148 self.gitStream.write("tagger %s\n" % tagger)
1149 self.gitStream.write("data <<EOT\n")
1150 self.gitStream.write(labelDetails["Description"])
1151 self.gitStream.write("EOT\n\n")
1152
1153 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001154 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001155 print ("Tag %s does not match with change %s: files do not match."
1156 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001157
1158 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02001159 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001160 print ("Tag %s does not match with change %s: file count is different."
1161 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01001162
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001163 def getUserCacheFilename(self):
Simon Hausmannb2d2d162007-07-25 09:31:38 +02001164 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1165 return home + "/.gitp4-usercache.txt"
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001166
Simon Hausmannb607e712007-05-20 10:55:54 +02001167 def getUserMapFromPerforceServer(self):
Simon Hausmannebd81162007-05-24 00:24:52 +02001168 if self.userMapFromPerforceServer:
1169 return
Simon Hausmannb9847332007-03-20 20:54:23 +01001170 self.users = {}
1171
1172 for output in p4CmdList("users"):
1173 if not output.has_key("User"):
1174 continue
1175 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1176
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001177
1178 s = ''
1179 for (key, val) in self.users.items():
Pete Wyckoff3b167392009-02-27 10:53:59 -08001180 s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001181
1182 open(self.getUserCacheFilename(), "wb").write(s)
Simon Hausmannebd81162007-05-24 00:24:52 +02001183 self.userMapFromPerforceServer = True
Simon Hausmannb607e712007-05-20 10:55:54 +02001184
1185 def loadUserMapFromCache(self):
1186 self.users = {}
Simon Hausmannebd81162007-05-24 00:24:52 +02001187 self.userMapFromPerforceServer = False
Simon Hausmannb607e712007-05-20 10:55:54 +02001188 try:
Han-Wen Nienhuys183b8ef2007-05-23 18:49:35 -03001189 cache = open(self.getUserCacheFilename(), "rb")
Simon Hausmannb607e712007-05-20 10:55:54 +02001190 lines = cache.readlines()
1191 cache.close()
1192 for line in lines:
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001193 entry = line.strip().split("\t")
Simon Hausmannb607e712007-05-20 10:55:54 +02001194 self.users[entry[0]] = entry[1]
1195 except IOError:
1196 self.getUserMapFromPerforceServer()
1197
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001198 def getLabels(self):
1199 self.labels = {}
1200
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001201 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
Simon Hausmann10c32112007-04-08 10:15:47 +02001202 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08001203 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001204
1205 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001206 label = output["label"]
1207 revisions = {}
1208 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02001209 if self.verbose:
1210 print "Querying files for label %s" % label
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001211 for file in p4CmdList("files "
1212 + ' '.join (["%s...@%s" % (p, label)
1213 for p in self.depotPaths])):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001214 revisions[file["depotFile"]] = file["rev"]
1215 change = int(file["change"])
1216 if change > newestChange:
1217 newestChange = change
1218
Simon Hausmann9bda3a82007-05-19 12:05:40 +02001219 self.labels[newestChange] = [output, revisions]
1220
1221 if self.verbose:
1222 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02001223
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001224 def guessProjectName(self):
1225 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02001226 if p.endswith("/"):
1227 p = p[:-1]
1228 p = p[p.strip().rfind("/") + 1:]
1229 if not p.endswith("/"):
1230 p += "/"
1231 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001232
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001233 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001234 lostAndFoundBranches = set()
1235
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001236 for info in p4CmdList("branches"):
1237 details = p4Cmd("branch -o %s" % info["branch"])
1238 viewIdx = 0
1239 while details.has_key("View%s" % viewIdx):
1240 paths = details["View%s" % viewIdx].split(" ")
1241 viewIdx = viewIdx + 1
1242 # require standard //depot/foo/... //depot/bar/... mapping
1243 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1244 continue
1245 source = paths[0]
1246 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02001247 ## HACK
1248 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1249 source = source[len(self.depotPaths[0]):-4]
1250 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001251
Simon Hausmann1a2edf42007-06-17 15:10:24 +02001252 if destination in self.knownBranches:
1253 if not self.silent:
1254 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1255 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1256 continue
1257
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001258 self.knownBranches[destination] = source
1259
1260 lostAndFoundBranches.discard(destination)
1261
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001262 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02001263 lostAndFoundBranches.add(source)
1264
1265
1266 for branch in lostAndFoundBranches:
1267 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001268
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001269 def getBranchMappingFromGitBranches(self):
1270 branches = p4BranchesInGit(self.importIntoRemotes)
1271 for branch in branches.keys():
1272 if branch == "master":
1273 branch = "main"
1274 else:
1275 branch = branch[len(self.projectName):]
1276 self.knownBranches[branch] = branch
1277
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001278 def listExistingP4GitBranches(self):
Simon Hausmann144ff462007-07-18 17:27:50 +02001279 # branches holds mapping from name to commit
1280 branches = p4BranchesInGit(self.importIntoRemotes)
1281 self.p4BranchesInGit = branches.keys()
1282 for branch in branches.keys():
1283 self.initialParents[self.refPrefix + branch] = branches[branch]
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001284
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001285 def updateOptionDict(self, d):
1286 option_keys = {}
1287 if self.keepRepoPath:
1288 option_keys['keepRepoPath'] = 1
1289
1290 d["options"] = ' '.join(sorted(option_keys.keys()))
1291
1292 def readOptions(self, d):
1293 self.keepRepoPath = (d.has_key('options')
1294 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001295
Simon Hausmann8134f692007-08-26 16:44:55 +02001296 def gitRefForBranch(self, branch):
1297 if branch == "main":
1298 return self.refPrefix + "master"
1299
1300 if len(branch) <= 0:
1301 return branch
1302
1303 return self.refPrefix + self.projectName + branch
1304
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001305 def gitCommitByP4Change(self, ref, change):
1306 if self.verbose:
1307 print "looking in ref " + ref + " for change %s using bisect..." % change
1308
1309 earliestCommit = ""
1310 latestCommit = parseRevision(ref)
1311
1312 while True:
1313 if self.verbose:
1314 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1315 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1316 if len(next) == 0:
1317 if self.verbose:
1318 print "argh"
1319 return ""
1320 log = extractLogMessageFromGitCommit(next)
1321 settings = extractSettingsGitLog(log)
1322 currentChange = int(settings['change'])
1323 if self.verbose:
1324 print "current change %s" % currentChange
1325
1326 if currentChange == change:
1327 if self.verbose:
1328 print "found %s" % next
1329 return next
1330
1331 if currentChange < change:
1332 earliestCommit = "^%s" % next
1333 else:
1334 latestCommit = "%s" % next
1335
1336 return ""
1337
1338 def importNewBranch(self, branch, maxChange):
1339 # make fast-import flush all changes to disk and update the refs using the checkpoint
1340 # command so that we can try to find the branch parent in the git history
1341 self.gitStream.write("checkpoint\n\n");
1342 self.gitStream.flush();
1343 branchPrefix = self.depotPaths[0] + branch + "/"
1344 range = "@1,%s" % maxChange
1345 #print "prefix" + branchPrefix
1346 changes = p4ChangesForPaths([branchPrefix], range)
1347 if len(changes) <= 0:
1348 return False
1349 firstChange = changes[0]
1350 #print "first change in branch: %s" % firstChange
1351 sourceBranch = self.knownBranches[branch]
1352 sourceDepotPath = self.depotPaths[0] + sourceBranch
1353 sourceRef = self.gitRefForBranch(sourceBranch)
1354 #print "source " + sourceBranch
1355
1356 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1357 #print "branch parent: %s" % branchParentChange
1358 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1359 if len(gitParent) > 0:
1360 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1361 #print "parent git commit: %s" % gitParent
1362
1363 self.importChanges(changes)
1364 return True
1365
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001366 def importChanges(self, changes):
1367 cnt = 1
1368 for change in changes:
1369 description = p4Cmd("describe %s" % change)
1370 self.updateOptionDict(description)
1371
1372 if not self.silent:
1373 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1374 sys.stdout.flush()
1375 cnt = cnt + 1
1376
1377 try:
1378 if self.detectBranches:
1379 branches = self.splitFilesIntoBranches(description)
1380 for branch in branches.keys():
1381 ## HACK --hwn
1382 branchPrefix = self.depotPaths[0] + branch + "/"
1383
1384 parent = ""
1385
1386 filesForCommit = branches[branch]
1387
1388 if self.verbose:
1389 print "branch is %s" % branch
1390
1391 self.updatedBranches.add(branch)
1392
1393 if branch not in self.createdBranches:
1394 self.createdBranches.add(branch)
1395 parent = self.knownBranches[branch]
1396 if parent == branch:
1397 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02001398 else:
1399 fullBranch = self.projectName + branch
1400 if fullBranch not in self.p4BranchesInGit:
1401 if not self.silent:
1402 print("\n Importing new branch %s" % fullBranch);
1403 if self.importNewBranch(branch, change - 1):
1404 parent = ""
1405 self.p4BranchesInGit.append(fullBranch)
1406 if not self.silent:
1407 print("\n Resuming with change %s" % change);
1408
1409 if self.verbose:
1410 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001411
Simon Hausmann8134f692007-08-26 16:44:55 +02001412 branch = self.gitRefForBranch(branch)
1413 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001414
1415 if self.verbose:
1416 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1417
1418 if len(parent) == 0 and branch in self.initialParents:
1419 parent = self.initialParents[branch]
1420 del self.initialParents[branch]
1421
1422 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1423 else:
1424 files = self.extractFilesFromCommit(description)
1425 self.commit(description, files, self.branch, self.depotPaths,
1426 self.initialParent)
1427 self.initialParent = ""
1428 except IOError:
1429 print self.gitError.read()
1430 sys.exit(1)
1431
Simon Hausmannc208a242007-08-26 16:07:18 +02001432 def importHeadRevision(self, revision):
1433 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1434
1435 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1436 details["desc"] = ("Initial import of %s from the state at revision %s"
1437 % (' '.join(self.depotPaths), revision))
1438 details["change"] = revision
1439 newestRevision = 0
1440
1441 fileCnt = 0
1442 for info in p4CmdList("files "
1443 + ' '.join(["%s...%s"
1444 % (p, revision)
1445 for p in self.depotPaths])):
1446
1447 if info['code'] == 'error':
1448 sys.stderr.write("p4 returned an error: %s\n"
1449 % info['data'])
1450 sys.exit(1)
1451
1452
1453 change = int(info["change"])
1454 if change > newestRevision:
1455 newestRevision = change
1456
John Chapman7f96e2e2008-11-08 14:22:48 +11001457 if info["action"] in ("delete", "purge"):
Simon Hausmannc208a242007-08-26 16:07:18 +02001458 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1459 #fileCnt = fileCnt + 1
1460 continue
1461
1462 for prop in ["depotFile", "rev", "action", "type" ]:
1463 details["%s%s" % (prop, fileCnt)] = info[prop]
1464
1465 fileCnt = fileCnt + 1
1466
1467 details["change"] = newestRevision
1468 self.updateOptionDict(details)
1469 try:
1470 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1471 except IOError:
1472 print "IO error with git fast-import. Is your git version recent enough?"
1473 print self.gitError.read()
1474
1475
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001476 def getClientSpec(self):
1477 specList = p4CmdList( "client -o" )
1478 temp = {}
1479 for entry in specList:
1480 for k,v in entry.iteritems():
1481 if k.startswith("View"):
1482 if v.startswith('"'):
1483 start = 1
1484 else:
1485 start = 0
1486 index = v.find("...")
1487 v = v[start:index]
1488 if v.startswith("-"):
1489 v = v[1:]
1490 temp[v] = -len(v)
1491 else:
1492 temp[v] = len(v)
1493 self.clientSpecDirs = temp.items()
1494 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1495
Simon Hausmannb9847332007-03-20 20:54:23 +01001496 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001497 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01001498 self.changeRange = ""
1499 self.initialParent = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001500 self.previousDepotPaths = []
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03001501
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001502 # map from branch depot path to parent branch
1503 self.knownBranches = {}
1504 self.initialParents = {}
Simon Hausmann5ca44612007-08-24 17:44:16 +02001505 self.hasOrigin = originP4BranchesExist()
Simon Hausmanna43ff002007-06-11 09:59:27 +02001506 if not self.syncWithOrigin:
1507 self.hasOrigin = False
Simon Hausmann179caeb2007-03-22 22:17:42 +01001508
Simon Hausmanna028a982007-05-23 00:03:08 +02001509 if self.importIntoRemotes:
1510 self.refPrefix = "refs/remotes/p4/"
1511 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001512 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02001513
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001514 if self.syncWithOrigin and self.hasOrigin:
1515 if not self.silent:
1516 print "Syncing with origin first by calling git fetch origin"
1517 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02001518
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001519 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02001520 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02001521 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001522 system("git update-ref %s refs/heads/p4" % self.branch)
Simon Hausmann48df6fd2007-05-17 21:18:53 +02001523 system("git branch -D p4");
Simon Hausmannfaf1bd22007-05-21 10:05:30 +02001524 # create it /after/ importing, when master exists
Simon Hausmann0058a332007-08-24 17:46:16 +02001525 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
Simon Hausmanna3c55c02007-05-27 15:48:01 +02001526 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
Simon Hausmann179caeb2007-03-22 22:17:42 +01001527
Anand Kumria3cafb7d2008-08-10 19:26:32 +01001528 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01001529 self.getClientSpec()
1530
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001531 # TODO: should always look at previous commits,
1532 # merge with previous imports, if possible.
1533 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02001534 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02001535 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Simon Hausmannabcd7902007-05-24 22:25:36 +02001536 self.listExistingP4GitBranches()
1537
1538 if len(self.p4BranchesInGit) > 1:
1539 if not self.silent:
1540 print "Importing from/into multiple branches"
1541 self.detectBranches = True
Simon Hausmann967f72e2007-03-23 09:30:41 +01001542
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001543 if self.verbose:
1544 print "branches: %s" % self.p4BranchesInGit
1545
1546 p4Change = 0
1547 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001548 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001549
1550 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001551
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001552 self.readOptions(settings)
1553 if (settings.has_key('depot-paths')
1554 and settings.has_key ('change')):
1555 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001556 p4Change = max(p4Change, change)
1557
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001558 depotPaths = sorted(settings['depot-paths'])
1559 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001560 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001561 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001562 paths = []
1563 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Simon Hausmann583e1702007-06-07 09:37:13 +02001564 for i in range(0, min(len(cur), len(prev))):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001565 if cur[i] <> prev[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02001566 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001567 break
1568
Simon Hausmann583e1702007-06-07 09:37:13 +02001569 paths.append (cur[:i + 1])
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001570
1571 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001572
1573 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001574 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02001575 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann330f53b2007-06-07 09:39:51 +02001576 if not self.detectBranches:
1577 self.initialParent = parseRevision(self.branch)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001578 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01001579 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01001580
Simon Hausmannf9162f62007-05-17 09:02:45 +02001581 if not self.branch.startswith("refs/"):
1582 self.branch = "refs/heads/" + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01001583
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001584 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01001585 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001586 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01001587 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001588 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001589 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001590 "This doesn't work!" % (' '.join (self.depotPaths),
1591 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01001592 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001593
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001594 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01001595
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001596 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01001597 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01001598
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001599 newPaths = []
1600 for p in self.depotPaths:
1601 if p.find("@") != -1:
1602 atIdx = p.index("@")
1603 self.changeRange = p[atIdx:]
1604 if self.changeRange == "@all":
1605 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001606 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001607 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001608 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001609 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001610 elif p.find("#") != -1:
1611 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001612 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001613 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001614 elif self.previousDepotPaths == []:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001615 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01001616
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001617 p = re.sub ("\.\.\.$", "", p)
1618 if not p.endswith("/"):
1619 p += "/"
1620
1621 newPaths.append(p)
1622
1623 self.depotPaths = newPaths
1624
Simon Hausmannb9847332007-03-20 20:54:23 +01001625
Simon Hausmannb607e712007-05-20 10:55:54 +02001626 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02001627 self.labels = {}
1628 if self.detectLabels:
1629 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01001630
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001631 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02001632 ## FIXME - what's a P4 projectName ?
1633 self.projectName = self.guessProjectName()
1634
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01001635 if self.hasOrigin:
1636 self.getBranchMappingFromGitBranches()
1637 else:
1638 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001639 if self.verbose:
1640 print "p4-git branches: %s" % self.p4BranchesInGit
1641 print "initial parents: %s" % self.initialParents
1642 for b in self.p4BranchesInGit:
1643 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001644
1645 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001646 b = b[len(self.projectName):]
1647 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02001648
Simon Hausmannf291b4e2007-04-14 11:21:50 +02001649 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01001650
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001651 importProcess = subprocess.Popen(["git", "fast-import"],
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001652 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1653 stderr=subprocess.PIPE);
Simon Hausmann08483582007-05-15 14:31:06 +02001654 self.gitOutput = importProcess.stdout
1655 self.gitStream = importProcess.stdin
1656 self.gitError = importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01001657
Simon Hausmann1c49fc12007-08-26 16:04:34 +02001658 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02001659 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01001660 else:
1661 changes = []
1662
Simon Hausmann0828ab12007-03-20 20:59:30 +01001663 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01001664 output = open(self.changesFile).readlines()
1665 changeSet = Set()
1666 for line in output:
1667 changeSet.add(int(line))
1668
1669 for change in changeSet:
1670 changes.append(change)
1671
1672 changes.sort()
1673 else:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001674 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001675 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001676 self.changeRange)
Simon Hausmann4f6432d2007-08-26 15:56:36 +02001677 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
Simon Hausmannb9847332007-03-20 20:54:23 +01001678
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001679 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07001680 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02001681
Simon Hausmannb9847332007-03-20 20:54:23 +01001682 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01001683 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001684 print "No changes to import!"
Simon Hausmann1f52af62007-04-08 00:07:02 +02001685 return True
Simon Hausmannb9847332007-03-20 20:54:23 +01001686
Simon Hausmanna9d1a272007-06-11 23:28:03 +02001687 if not self.silent and not self.detectBranches:
1688 print "Import destination: %s" % self.branch
1689
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001690 self.updatedBranches = set()
1691
Simon Hausmanne87f37a2007-08-26 16:00:52 +02001692 self.importChanges(changes)
Simon Hausmannb9847332007-03-20 20:54:23 +01001693
Simon Hausmann341dc1c2007-05-21 00:39:16 +02001694 if not self.silent:
1695 print ""
1696 if len(self.updatedBranches) > 0:
1697 sys.stdout.write("Updated branches: ")
1698 for b in self.updatedBranches:
1699 sys.stdout.write("%s " % b)
1700 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01001701
Simon Hausmannb9847332007-03-20 20:54:23 +01001702 self.gitStream.close()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02001703 if importProcess.wait() != 0:
1704 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01001705 self.gitOutput.close()
1706 self.gitError.close()
1707
Simon Hausmannb9847332007-03-20 20:54:23 +01001708 return True
1709
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001710class P4Rebase(Command):
1711 def __init__(self):
1712 Command.__init__(self)
Simon Hausmann01265102007-05-25 10:36:10 +02001713 self.options = [ ]
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03001714 self.description = ("Fetches the latest revision from perforce and "
1715 + "rebases the current work (branch) against it")
Simon Hausmann68c42152007-06-07 12:51:03 +02001716 self.verbose = False
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001717
1718 def run(self, args):
1719 sync = P4Sync()
1720 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02001721
Simon Hausmann14594f42007-08-22 09:07:15 +02001722 return self.rebase()
1723
1724 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01001725 if os.system("git update-index --refresh") != 0:
1726 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.");
1727 if len(read_pipe("git diff-index HEAD --")) > 0:
1728 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1729
Simon Hausmannd7e38682007-06-12 14:34:46 +02001730 [upstream, settings] = findUpstreamBranchPoint()
1731 if len(upstream) == 0:
1732 die("Cannot find upstream branchpoint for rebase")
1733
1734 # the branchpoint may be p4/foo~3, so strip off the parent
1735 upstream = re.sub("~[0-9]+$", "", upstream)
1736
1737 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001738 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02001739 system("git rebase %s" % upstream)
Simon Hausmann1f52af62007-04-08 00:07:02 +02001740 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02001741 return True
1742
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001743class P4Clone(P4Sync):
1744 def __init__(self):
1745 P4Sync.__init__(self)
1746 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001747 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08001748 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001749 optparse.make_option("--destination", dest="cloneDestination",
1750 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08001751 help="where to leave result of the clone"),
1752 optparse.make_option("-/", dest="cloneExclude",
1753 action="append", type="string",
1754 help="exclude depot path")
1755 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001756 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001757 self.needsGit = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001758
Tommy Thorn354081d2008-02-03 10:38:51 -08001759 # This is required for the "append" cloneExclude action
1760 def ensure_value(self, attr, value):
1761 if not hasattr(self, attr) or getattr(self, attr) is None:
1762 setattr(self, attr, value)
1763 return getattr(self, attr)
1764
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001765 def defaultDestination(self, args):
1766 ## TODO: use common prefix of args?
1767 depotPath = args[0]
1768 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1769 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13001770 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03001771 depotDir = re.sub(r"/$", "", depotDir)
1772 return os.path.split(depotDir)[1]
1773
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001774 def run(self, args):
1775 if len(args) < 1:
1776 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001777
1778 if self.keepRepoPath and not self.cloneDestination:
1779 sys.stderr.write("Must specify destination for --keep-path\n")
1780 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001781
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001782 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02001783
1784 if not self.cloneDestination and len(depotPaths) > 1:
1785 self.cloneDestination = depotPaths[-1]
1786 depotPaths = depotPaths[:-1]
1787
Tommy Thorn354081d2008-02-03 10:38:51 -08001788 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001789 for p in depotPaths:
1790 if not p.startswith("//"):
1791 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001792
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001793 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02001794 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001795
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001796 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Kevin Greenc3bf3f12007-06-11 16:48:07 -04001797 if not os.path.exists(self.cloneDestination):
1798 os.makedirs(self.cloneDestination)
Robert Blum053fd0c2008-08-01 12:50:03 -07001799 chdir(self.cloneDestination)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001800 system("git init")
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001801 self.gitdir = os.getcwd() + "/.git"
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001802 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001803 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001804 if self.branch != "master":
Tor Arvid Lunde9905012008-08-28 00:36:12 +02001805 if self.importIntoRemotes:
1806 masterbranch = "refs/remotes/p4/master"
1807 else:
1808 masterbranch = "refs/heads/p4/master"
1809 if gitBranchExists(masterbranch):
1810 system("git branch master %s" % masterbranch)
Simon Hausmann8f9b2e02007-05-18 22:13:26 +02001811 system("git checkout -f")
1812 else:
1813 print "Could not detect main branch. No checkout/master branch created."
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03001814
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02001815 return True
1816
Simon Hausmann09d89de2007-06-20 23:10:28 +02001817class P4Branches(Command):
1818 def __init__(self):
1819 Command.__init__(self)
1820 self.options = [ ]
1821 self.description = ("Shows the git branches that hold imports and their "
1822 + "corresponding perforce depot paths")
1823 self.verbose = False
1824
1825 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02001826 if originP4BranchesExist():
1827 createOrUpdateBranchesFromOrigin()
1828
Simon Hausmann09d89de2007-06-20 23:10:28 +02001829 cmdline = "git rev-parse --symbolic "
1830 cmdline += " --remotes"
1831
1832 for line in read_pipe_lines(cmdline):
1833 line = line.strip()
1834
1835 if not line.startswith('p4/') or line == "p4/HEAD":
1836 continue
1837 branch = line
1838
1839 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1840 settings = extractSettingsGitLog(log)
1841
1842 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1843 return True
1844
Simon Hausmannb9847332007-03-20 20:54:23 +01001845class HelpFormatter(optparse.IndentedHelpFormatter):
1846 def __init__(self):
1847 optparse.IndentedHelpFormatter.__init__(self)
1848
1849 def format_description(self, description):
1850 if description:
1851 return description + "\n"
1852 else:
1853 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001854
Simon Hausmann86949ee2007-03-19 20:59:12 +01001855def printUsage(commands):
1856 print "usage: %s <command> [options]" % sys.argv[0]
1857 print ""
1858 print "valid commands: %s" % ", ".join(commands)
1859 print ""
1860 print "Try %s <command> --help for command specific help." % sys.argv[0]
1861 print ""
1862
1863commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001864 "debug" : P4Debug,
1865 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02001866 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001867 "sync" : P4Sync,
1868 "rebase" : P4Rebase,
1869 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02001870 "rollback" : P4RollBack,
1871 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01001872}
1873
Simon Hausmann86949ee2007-03-19 20:59:12 +01001874
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001875def main():
1876 if len(sys.argv[1:]) == 0:
1877 printUsage(commands.keys())
1878 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001879
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001880 cmd = ""
1881 cmdName = sys.argv[1]
1882 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001883 klass = commands[cmdName]
1884 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001885 except KeyError:
1886 print "unknown command %s" % cmdName
1887 print ""
1888 printUsage(commands.keys())
1889 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001890
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001891 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001892 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01001893
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001894 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001895
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001896 if len(options) > 0:
1897 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02001898
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001899 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1900 options,
1901 description = cmd.description,
1902 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01001903
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001904 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1905 global verbose
1906 verbose = cmd.verbose
1907 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001908 if cmd.gitdir == None:
1909 cmd.gitdir = os.path.abspath(".git")
1910 if not isValidGitDir(cmd.gitdir):
1911 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1912 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001913 cdup = read_pipe("git rev-parse --show-cdup").strip()
1914 if len(cdup) > 0:
Robert Blum053fd0c2008-08-01 12:50:03 -07001915 chdir(cdup);
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001916
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001917 if not isValidGitDir(cmd.gitdir):
1918 if isValidGitDir(cmd.gitdir + "/.git"):
1919 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001920 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001921 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02001922
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03001923 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001924
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001925 if not cmd.run(args):
1926 parser.print_help()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001927
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001928
1929if __name__ == '__main__':
1930 main()