]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/fast-import/git-p4
git-p4: Support purged files and optimize memory usage
[thirdparty/git.git] / contrib / fast-import / git-p4
CommitLineData
86949eef
SH
1#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
c8cbbee9
SH
5# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
83dce55a 7# 2007 Trolltech ASA
86949eef
SH
8# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
10
08483580 11import optparse, sys, os, marshal, popen2, subprocess, shelve
25df95cc 12import tempfile, getopt, sha, os.path, time, platform
ce6f33c8 13import re
8b41a97f 14
b984733c 15from sets import Set;
4f5cf76a 16
4addad22 17verbose = False
86949eef 18
21a50753
AK
19
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 """
abcaf073
AK
27 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)
ee06427a
AK
50 if verbose:
51 print real_cmd
21a50753
AK
52 return real_cmd
53
053fd0c1
RB
54def chdir(dir):
55 if os.name == 'nt':
56 os.environ['PWD']=dir
57 os.chdir(dir)
58
86dff6b6
HWN
59def die(msg):
60 if verbose:
61 raise Exception(msg)
62 else:
63 sys.stderr.write(msg + "\n")
64 sys.exit(1)
65
bce4c5fc 66def write_pipe(c, str):
4addad22 67 if verbose:
86dff6b6 68 sys.stderr.write('Writing pipe: %s\n' % c)
b016d397 69
bce4c5fc 70 pipe = os.popen(c, 'w')
b016d397 71 val = pipe.write(str)
bce4c5fc 72 if pipe.close():
86dff6b6 73 die('Command failed: %s' % c)
b016d397
HWN
74
75 return val
76
d9429194
AK
77def p4_write_pipe(c, str):
78 real_cmd = p4_build_cmd(c)
893d340f 79 return write_pipe(real_cmd, str)
d9429194 80
4addad22
HWN
81def read_pipe(c, ignore_error=False):
82 if verbose:
86dff6b6 83 sys.stderr.write('Reading pipe: %s\n' % c)
8b41a97f 84
bce4c5fc 85 pipe = os.popen(c, 'rb')
b016d397 86 val = pipe.read()
4addad22 87 if pipe.close() and not ignore_error:
86dff6b6 88 die('Command failed: %s' % c)
b016d397
HWN
89
90 return val
91
d9429194
AK
92def p4_read_pipe(c, ignore_error=False):
93 real_cmd = p4_build_cmd(c)
94 return read_pipe(real_cmd, ignore_error)
b016d397 95
bce4c5fc 96def read_pipe_lines(c):
4addad22 97 if verbose:
86dff6b6 98 sys.stderr.write('Reading pipe: %s\n' % c)
b016d397 99 ## todo: check return status
bce4c5fc 100 pipe = os.popen(c, 'rb')
b016d397 101 val = pipe.readlines()
bce4c5fc 102 if pipe.close():
86dff6b6 103 die('Command failed: %s' % c)
b016d397
HWN
104
105 return val
caace111 106
2318121b
AK
107def p4_read_pipe_lines(c):
108 """Specifically invoke p4 on the command supplied. """
155af834 109 real_cmd = p4_build_cmd(c)
2318121b
AK
110 return read_pipe_lines(real_cmd)
111
6754a299 112def system(cmd):
4addad22 113 if verbose:
bb6e09b2 114 sys.stderr.write("executing %s\n" % cmd)
6754a299
HWN
115 if os.system(cmd) != 0:
116 die("command failed: %s" % cmd)
117
bf9320f1
AK
118def p4_system(cmd):
119 """Specifically invoke p4 as the system command. """
155af834 120 real_cmd = p4_build_cmd(cmd)
bf9320f1
AK
121 return system(real_cmd)
122
b9fc6ea9
DB
123def 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
c65b670e
CP
131def 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
87b611d5 144 p4_system("reopen -t %s %s" % (p4Type, file))
c65b670e
CP
145
146def getP4OpenedType(file):
147 # Returns the perforce file type for the given file.
148
a7d3ef9d 149 result = p4_read_pipe("opened %s" % file)
f3e5ae4f 150 match = re.match(".*\((.+)\)\r?$", result)
c65b670e
CP
151 if match:
152 return match.group(1)
153 else:
f3e5ae4f 154 die("Could not determine file type for %s (result: '%s')" % (file, result))
c65b670e 155
b43b0a3c
CP
156def 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
c65b670e
CP
196def 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
9f90c733 204def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
155af834 205 cmd = p4_build_cmd("-G %s" % (cmd))
6a49f8e2
HWN
206 if verbose:
207 sys.stderr.write("Opening pipe: %s\n" % cmd)
9f90c733
SL
208
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)
86949eef
SH
222
223 result = []
224 try:
225 while True:
9f90c733 226 entry = marshal.load(p4.stdout)
86949eef
SH
227 result.append(entry)
228 except EOFError:
229 pass
9f90c733
SL
230 exitCode = p4.wait()
231 if exitCode != 0:
ac3e0d79
SH
232 entry = {}
233 entry["p4ExitCode"] = exitCode
234 result.append(entry)
86949eef
SH
235
236 return result
237
238def p4Cmd(cmd):
239 list = p4CmdList(cmd)
240 result = {}
241 for entry in list:
242 result.update(entry)
243 return result;
244
cb2c9db5
SH
245def p4Where(depotPath):
246 if not depotPath.endswith("/"):
247 depotPath += "/"
248 output = p4Cmd("where %s..." % depotPath)
dc524036
SH
249 if output["code"] == "error":
250 return ""
cb2c9db5
SH
251 clientPath = ""
252 if "path" in output:
253 clientPath = output.get("path")
254 elif "data" in output:
255 data = output.get("data")
256 lastSpace = data.rfind(" ")
257 clientPath = data[lastSpace + 1:]
258
259 if clientPath.endswith("..."):
260 clientPath = clientPath[:-3]
261 return clientPath
262
86949eef 263def currentGitBranch():
b25b2065 264 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
86949eef 265
4f5cf76a 266def isValidGitDir(path):
bb6e09b2
HWN
267 if (os.path.exists(path + "/HEAD")
268 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
4f5cf76a
SH
269 return True;
270 return False
271
463e8af6 272def parseRevision(ref):
b25b2065 273 return read_pipe("git rev-parse %s" % ref).strip()
463e8af6 274
6ae8de88
SH
275def extractLogMessageFromGitCommit(commit):
276 logMessage = ""
b016d397
HWN
277
278 ## fixme: title is first line of commit, not 1st paragraph.
6ae8de88 279 foundTitle = False
b016d397 280 for log in read_pipe_lines("git cat-file commit %s" % commit):
6ae8de88
SH
281 if not foundTitle:
282 if len(log) == 1:
1c094184 283 foundTitle = True
6ae8de88
SH
284 continue
285
286 logMessage += log
287 return logMessage
288
bb6e09b2 289def extractSettingsGitLog(log):
6ae8de88
SH
290 values = {}
291 for line in log.split("\n"):
292 line = line.strip()
6326aa58
HWN
293 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
294 if not m:
295 continue
296
297 assignments = m.group(1).split (':')
298 for a in assignments:
299 vals = a.split ('=')
300 key = vals[0].strip()
301 val = ('='.join (vals[1:])).strip()
302 if val.endswith ('\"') and val.startswith('"'):
303 val = val[1:-1]
304
305 values[key] = val
306
845b42cb
SH
307 paths = values.get("depot-paths")
308 if not paths:
309 paths = values.get("depot-path")
a3fdd579
SH
310 if paths:
311 values['depot-paths'] = paths.split(',')
bb6e09b2 312 return values
6ae8de88 313
8136a639 314def gitBranchExists(branch):
bb6e09b2
HWN
315 proc = subprocess.Popen(["git", "rev-parse", branch],
316 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
caace111 317 return proc.wait() == 0;
8136a639 318
01265103 319def gitConfig(key):
4addad22 320 return read_pipe("git config %s" % key, ignore_error=True).strip()
01265103 321
062410bb
SH
322def p4BranchesInGit(branchesAreInRemotes = True):
323 branches = {}
324
325 cmdline = "git rev-parse --symbolic "
326 if branchesAreInRemotes:
327 cmdline += " --remotes"
328 else:
329 cmdline += " --branches"
330
331 for line in read_pipe_lines(cmdline):
332 line = line.strip()
333
334 ## only import to p4/
335 if not line.startswith('p4/') or line == "p4/HEAD":
336 continue
337 branch = line
338
339 # strip off p4
340 branch = re.sub ("^p4/", "", line)
341
342 branches[branch] = parseRevision(line)
343 return branches
344
9ceab363 345def findUpstreamBranchPoint(head = "HEAD"):
86506fe5
SH
346 branches = p4BranchesInGit()
347 # map from depot-path to branch name
348 branchByDepotPath = {}
349 for branch in branches.keys():
350 tip = branches[branch]
351 log = extractLogMessageFromGitCommit(tip)
352 settings = extractSettingsGitLog(log)
353 if settings.has_key("depot-paths"):
354 paths = ",".join(settings["depot-paths"])
355 branchByDepotPath[paths] = "remotes/p4/" + branch
356
27d2d811 357 settings = None
27d2d811
SH
358 parent = 0
359 while parent < 65535:
9ceab363 360 commit = head + "~%s" % parent
27d2d811
SH
361 log = extractLogMessageFromGitCommit(commit)
362 settings = extractSettingsGitLog(log)
86506fe5
SH
363 if settings.has_key("depot-paths"):
364 paths = ",".join(settings["depot-paths"])
365 if branchByDepotPath.has_key(paths):
366 return [branchByDepotPath[paths], settings]
27d2d811 367
86506fe5 368 parent = parent + 1
27d2d811 369
86506fe5 370 return ["", settings]
27d2d811 371
5ca44617
SH
372def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
373 if not silent:
374 print ("Creating/updating branch(es) in %s based on origin branch(es)"
375 % localRefPrefix)
376
377 originPrefix = "origin/p4/"
378
379 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
380 line = line.strip()
381 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
382 continue
383
384 headName = line[len(originPrefix):]
385 remoteHead = localRefPrefix + headName
386 originHead = line
387
388 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
389 if (not original.has_key('depot-paths')
390 or not original.has_key('change')):
391 continue
392
393 update = False
394 if not gitBranchExists(remoteHead):
395 if verbose:
396 print "creating %s" % remoteHead
397 update = True
398 else:
399 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
400 if settings.has_key('change') > 0:
401 if settings['depot-paths'] == original['depot-paths']:
402 originP4Change = int(original['change'])
403 p4Change = int(settings['change'])
404 if originP4Change > p4Change:
405 print ("%s (%s) is newer than %s (%s). "
406 "Updating p4 branch from origin."
407 % (originHead, originP4Change,
408 remoteHead, p4Change))
409 update = True
410 else:
411 print ("Ignoring: %s was imported from %s while "
412 "%s was imported from %s"
413 % (originHead, ','.join(original['depot-paths']),
414 remoteHead, ','.join(settings['depot-paths'])))
415
416 if update:
417 system("git update-ref %s %s" % (remoteHead, originHead))
418
419def originP4BranchesExist():
420 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
421
4f6432d8
SH
422def p4ChangesForPaths(depotPaths, changeRange):
423 assert depotPaths
b340fa43 424 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
4f6432d8
SH
425 for p in depotPaths]))
426
427 changes = []
428 for line in output:
429 changeNum = line.split(" ")[1]
430 changes.append(int(changeNum))
431
432 changes.sort()
433 return changes
434
b984733c
SH
435class Command:
436 def __init__(self):
437 self.usage = "usage: %prog [options]"
8910ac0e 438 self.needsGit = True
b984733c
SH
439
440class P4Debug(Command):
86949eef 441 def __init__(self):
6ae8de88 442 Command.__init__(self)
86949eef 443 self.options = [
b1ce9447
HWN
444 optparse.make_option("--verbose", dest="verbose", action="store_true",
445 default=False),
4addad22 446 ]
c8c39116 447 self.description = "A tool to debug the output of p4 -G."
8910ac0e 448 self.needsGit = False
b1ce9447 449 self.verbose = False
86949eef
SH
450
451 def run(self, args):
b1ce9447 452 j = 0
86949eef 453 for output in p4CmdList(" ".join(args)):
b1ce9447
HWN
454 print 'Element: %d' % j
455 j += 1
86949eef 456 print output
b984733c 457 return True
86949eef 458
5834684d
SH
459class P4RollBack(Command):
460 def __init__(self):
461 Command.__init__(self)
462 self.options = [
0c66a783
SH
463 optparse.make_option("--verbose", dest="verbose", action="store_true"),
464 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
5834684d
SH
465 ]
466 self.description = "A tool to debug the multi-branch import. Don't use :)"
52102d47 467 self.verbose = False
0c66a783 468 self.rollbackLocalBranches = False
5834684d
SH
469
470 def run(self, args):
471 if len(args) != 1:
472 return False
473 maxChange = int(args[0])
0c66a783 474
ad192f28 475 if "p4ExitCode" in p4Cmd("changes -m 1"):
66a2f523
SH
476 die("Problems executing p4");
477
0c66a783
SH
478 if self.rollbackLocalBranches:
479 refPrefix = "refs/heads/"
b016d397 480 lines = read_pipe_lines("git rev-parse --symbolic --branches")
0c66a783
SH
481 else:
482 refPrefix = "refs/remotes/"
b016d397 483 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
0c66a783
SH
484
485 for line in lines:
486 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
b25b2065
HWN
487 line = line.strip()
488 ref = refPrefix + line
5834684d 489 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
490 settings = extractSettingsGitLog(log)
491
492 depotPaths = settings['depot-paths']
493 change = settings['change']
494
5834684d 495 changed = False
52102d47 496
6326aa58
HWN
497 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
498 for p in depotPaths]))) == 0:
52102d47
SH
499 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
500 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
501 continue
502
bb6e09b2 503 while change and int(change) > maxChange:
5834684d 504 changed = True
52102d47
SH
505 if self.verbose:
506 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
5834684d
SH
507 system("git update-ref %s \"%s^\"" % (ref, ref))
508 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
509 settings = extractSettingsGitLog(log)
510
511
512 depotPaths = settings['depot-paths']
513 change = settings['change']
5834684d
SH
514
515 if changed:
52102d47 516 print "%s rewound to %s" % (ref, change)
5834684d
SH
517
518 return True
519
711544b0 520class P4Submit(Command):
4f5cf76a 521 def __init__(self):
b984733c 522 Command.__init__(self)
4f5cf76a 523 self.options = [
4addad22 524 optparse.make_option("--verbose", dest="verbose", action="store_true"),
4f5cf76a 525 optparse.make_option("--origin", dest="origin"),
d9a5f25b 526 optparse.make_option("-M", dest="detectRename", action="store_true"),
4f5cf76a
SH
527 ]
528 self.description = "Submit changes from git to the perforce depot."
c9b50e63 529 self.usage += " [name of git branch to submit into perforce depot]"
4f5cf76a 530 self.interactive = True
9512497b 531 self.origin = ""
d9a5f25b 532 self.detectRename = False
b0d10df7 533 self.verbose = False
f7baba8b 534 self.isWindows = (platform.system() == "Windows")
4f5cf76a 535
4f5cf76a
SH
536 def check(self):
537 if len(p4CmdList("opened ...")) > 0:
538 die("You have files opened with perforce! Close them before starting the sync.")
539
edae1e2f
SH
540 # replaces everything between 'Description:' and the next P4 submit template field with the
541 # commit message
4f5cf76a
SH
542 def prepareLogMessage(self, template, message):
543 result = ""
544
edae1e2f
SH
545 inDescriptionSection = False
546
4f5cf76a
SH
547 for line in template.split("\n"):
548 if line.startswith("#"):
549 result += line + "\n"
550 continue
551
edae1e2f
SH
552 if inDescriptionSection:
553 if line.startswith("Files:"):
554 inDescriptionSection = False
555 else:
556 continue
557 else:
558 if line.startswith("Description:"):
559 inDescriptionSection = True
560 line += "\n"
561 for messageLine in message.split("\n"):
562 line += "\t" + messageLine + "\n"
563
564 result += line + "\n"
4f5cf76a
SH
565
566 return result
567
ea99c3ae
SH
568 def prepareSubmitTemplate(self):
569 # remove lines in the Files section that show changes to files outside the depot path we're committing into
570 template = ""
571 inFilesSection = False
b340fa43 572 for line in p4_read_pipe_lines("change -o"):
f3e5ae4f
MSO
573 if line.endswith("\r\n"):
574 line = line[:-2] + "\n"
ea99c3ae
SH
575 if inFilesSection:
576 if line.startswith("\t"):
577 # path starts and ends with a tab
578 path = line[1:]
579 lastTab = path.rfind("\t")
580 if lastTab != -1:
581 path = path[:lastTab]
582 if not path.startswith(self.depotPath):
583 continue
584 else:
585 inFilesSection = False
586 else:
587 if line.startswith("Files:"):
588 inFilesSection = True
589
590 template += line
591
592 return template
593
7cb5cbef 594 def applyCommit(self, id):
0e36f2d7
SH
595 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
596 diffOpts = ("", "-M")[self.detectRename]
597 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
4f5cf76a
SH
598 filesToAdd = set()
599 filesToDelete = set()
d336c158 600 editedFiles = set()
c65b670e 601 filesToChangeExecBit = {}
4f5cf76a 602 for line in diff:
b43b0a3c
CP
603 diff = parseDiffTreeEntry(line)
604 modifier = diff['status']
605 path = diff['src']
4f5cf76a 606 if modifier == "M":
87b611d5 607 p4_system("edit \"%s\"" % path)
c65b670e
CP
608 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
609 filesToChangeExecBit[path] = diff['dst_mode']
d336c158 610 editedFiles.add(path)
4f5cf76a
SH
611 elif modifier == "A":
612 filesToAdd.add(path)
c65b670e 613 filesToChangeExecBit[path] = diff['dst_mode']
4f5cf76a
SH
614 if path in filesToDelete:
615 filesToDelete.remove(path)
616 elif modifier == "D":
617 filesToDelete.add(path)
618 if path in filesToAdd:
619 filesToAdd.remove(path)
d9a5f25b 620 elif modifier == "R":
b43b0a3c 621 src, dest = diff['src'], diff['dst']
87b611d5
AK
622 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
623 p4_system("edit \"%s\"" % (dest))
c65b670e
CP
624 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
625 filesToChangeExecBit[dest] = diff['dst_mode']
d9a5f25b
CP
626 os.unlink(dest)
627 editedFiles.add(dest)
628 filesToDelete.add(src)
4f5cf76a
SH
629 else:
630 die("unknown modifier %s for %s" % (modifier, path))
631
0e36f2d7 632 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
47a130b7 633 patchcmd = diffcmd + " | git apply "
c1b296b9
SH
634 tryPatchCmd = patchcmd + "--check -"
635 applyPatchCmd = patchcmd + "--check --apply -"
51a2640a 636
47a130b7 637 if os.system(tryPatchCmd) != 0:
51a2640a
SH
638 print "Unfortunately applying the change failed!"
639 print "What do you want to do?"
640 response = "x"
641 while response != "s" and response != "a" and response != "w":
cebdf5af
HWN
642 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
643 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
51a2640a
SH
644 if response == "s":
645 print "Skipping! Good luck with the next patches..."
20947149 646 for f in editedFiles:
87b611d5 647 p4_system("revert \"%s\"" % f);
20947149
SH
648 for f in filesToAdd:
649 system("rm %s" %f)
51a2640a
SH
650 return
651 elif response == "a":
47a130b7 652 os.system(applyPatchCmd)
51a2640a
SH
653 if len(filesToAdd) > 0:
654 print "You may also want to call p4 add on the following files:"
655 print " ".join(filesToAdd)
656 if len(filesToDelete):
657 print "The following files should be scheduled for deletion with p4 delete:"
658 print " ".join(filesToDelete)
cebdf5af
HWN
659 die("Please resolve and submit the conflict manually and "
660 + "continue afterwards with git-p4 submit --continue")
51a2640a
SH
661 elif response == "w":
662 system(diffcmd + " > patch.txt")
663 print "Patch saved to patch.txt in %s !" % self.clientPath
cebdf5af
HWN
664 die("Please resolve and submit the conflict manually and "
665 "continue afterwards with git-p4 submit --continue")
51a2640a 666
47a130b7 667 system(applyPatchCmd)
4f5cf76a
SH
668
669 for f in filesToAdd:
87b611d5 670 p4_system("add \"%s\"" % f)
4f5cf76a 671 for f in filesToDelete:
87b611d5
AK
672 p4_system("revert \"%s\"" % f)
673 p4_system("delete \"%s\"" % f)
4f5cf76a 674
c65b670e
CP
675 # Set/clear executable bits
676 for f in filesToChangeExecBit.keys():
677 mode = filesToChangeExecBit[f]
678 setP4ExecBit(f, mode)
679
0e36f2d7 680 logMessage = extractLogMessageFromGitCommit(id)
0e36f2d7 681 logMessage = logMessage.strip()
4f5cf76a 682
ea99c3ae 683 template = self.prepareSubmitTemplate()
4f5cf76a
SH
684
685 if self.interactive:
686 submitTemplate = self.prepareLogMessage(template, logMessage)
67abd417
SB
687 if os.environ.has_key("P4DIFF"):
688 del(os.environ["P4DIFF"])
a7d3ef9d 689 diff = p4_read_pipe("diff -du ...")
4f5cf76a 690
f3e5ae4f 691 newdiff = ""
4f5cf76a 692 for newFile in filesToAdd:
f3e5ae4f
MSO
693 newdiff += "==== new file ====\n"
694 newdiff += "--- /dev/null\n"
695 newdiff += "+++ %s\n" % newFile
4f5cf76a
SH
696 f = open(newFile, "r")
697 for line in f.readlines():
f3e5ae4f 698 newdiff += "+" + line
4f5cf76a
SH
699 f.close()
700
f3e5ae4f 701 separatorLine = "######## everything below this line is just the diff #######\n"
4f5cf76a 702
e96e400f
SH
703 [handle, fileName] = tempfile.mkstemp()
704 tmpFile = os.fdopen(handle, "w+")
f3e5ae4f
MSO
705 if self.isWindows:
706 submitTemplate = submitTemplate.replace("\n", "\r\n")
707 separatorLine = separatorLine.replace("\n", "\r\n")
708 newdiff = newdiff.replace("\n", "\r\n")
709 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
e96e400f 710 tmpFile.close()
cdc7e388 711 mtime = os.stat(fileName).st_mtime
e96e400f
SH
712 defaultEditor = "vi"
713 if platform.system() == "Windows":
714 defaultEditor = "notepad"
82cea9ff
SB
715 if os.environ.has_key("P4EDITOR"):
716 editor = os.environ.get("P4EDITOR")
717 else:
718 editor = os.environ.get("EDITOR", defaultEditor);
e96e400f 719 system(editor + " " + fileName)
e96e400f 720
cdc7e388
SH
721 response = "y"
722 if os.stat(fileName).st_mtime <= mtime:
723 response = "x"
724 while response != "y" and response != "n":
725 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
726
727 if response == "y":
728 tmpFile = open(fileName, "rb")
729 message = tmpFile.read()
730 tmpFile.close()
731 submitTemplate = message[:message.index(separatorLine)]
732 if self.isWindows:
733 submitTemplate = submitTemplate.replace("\r\n", "\n")
734 p4_write_pipe("submit -i", submitTemplate)
735 else:
736 for f in editedFiles:
737 p4_system("revert \"%s\"" % f);
738 for f in filesToAdd:
739 p4_system("revert \"%s\"" % f);
740 system("rm %s" %f)
741
742 os.remove(fileName)
4f5cf76a
SH
743 else:
744 fileName = "submit.txt"
745 file = open(fileName, "w+")
746 file.write(self.prepareLogMessage(template, logMessage))
747 file.close()
cebdf5af
HWN
748 print ("Perforce submit template written as %s. "
749 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
750 % (fileName, fileName))
4f5cf76a
SH
751
752 def run(self, args):
c9b50e63
SH
753 if len(args) == 0:
754 self.master = currentGitBranch()
4280e533 755 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
c9b50e63
SH
756 die("Detecting current git branch failed!")
757 elif len(args) == 1:
758 self.master = args[0]
759 else:
760 return False
761
4c2d5d72
JX
762 allowSubmit = gitConfig("git-p4.allowSubmit")
763 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
764 die("%s is not in git-p4.allowSubmit" % self.master)
765
27d2d811 766 [upstream, settings] = findUpstreamBranchPoint()
ea99c3ae 767 self.depotPath = settings['depot-paths'][0]
27d2d811
SH
768 if len(self.origin) == 0:
769 self.origin = upstream
a3fdd579
SH
770
771 if self.verbose:
772 print "Origin branch is " + self.origin
9512497b 773
ea99c3ae 774 if len(self.depotPath) == 0:
9512497b
SH
775 print "Internal error: cannot locate perforce depot path from existing branches"
776 sys.exit(128)
777
ea99c3ae 778 self.clientPath = p4Where(self.depotPath)
9512497b 779
51a2640a 780 if len(self.clientPath) == 0:
ea99c3ae 781 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
9512497b
SH
782 sys.exit(128)
783
ea99c3ae 784 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
7944f142 785 self.oldWorkingDirectory = os.getcwd()
c1b296b9 786
053fd0c1 787 chdir(self.clientPath)
31f9ec12 788 print "Syncronizing p4 checkout..."
87b611d5 789 p4_system("sync ...")
9512497b 790
4f5cf76a 791 self.check()
4f5cf76a 792
4c750c0d
SH
793 commits = []
794 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
795 commits.append(line.strip())
796 commits.reverse()
4f5cf76a
SH
797
798 while len(commits) > 0:
4f5cf76a
SH
799 commit = commits[0]
800 commits = commits[1:]
7cb5cbef 801 self.applyCommit(commit)
4f5cf76a
SH
802 if not self.interactive:
803 break
804
4f5cf76a 805 if len(commits) == 0:
4c750c0d 806 print "All changes applied!"
053fd0c1 807 chdir(self.oldWorkingDirectory)
14594f4b 808
4c750c0d
SH
809 sync = P4Sync()
810 sync.run([])
14594f4b 811
4c750c0d
SH
812 rebase = P4Rebase()
813 rebase.rebase()
4f5cf76a 814
b984733c
SH
815 return True
816
711544b0 817class P4Sync(Command):
b984733c
SH
818 def __init__(self):
819 Command.__init__(self)
820 self.options = [
821 optparse.make_option("--branch", dest="branch"),
822 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
823 optparse.make_option("--changesfile", dest="changesFile"),
824 optparse.make_option("--silent", dest="silent", action="store_true"),
ef48f909 825 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
a028a98e 826 optparse.make_option("--verbose", dest="verbose", action="store_true"),
d2c6dd30
HWN
827 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
828 help="Import into refs/heads/ , not refs/remotes"),
8b41a97f 829 optparse.make_option("--max-changes", dest="maxChanges"),
86dff6b6 830 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
3a70cdfa
TAL
831 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
832 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
833 help="Only sync files that are included in the Perforce Client Spec")
b984733c
SH
834 ]
835 self.description = """Imports from Perforce into a git repository.\n
836 example:
837 //depot/my/project/ -- to import the current head
838 //depot/my/project/@all -- to import everything
839 //depot/my/project/@1,6 -- to import only from revision 1 to 6
840
841 (a ... is not needed in the path p4 specification, it's added implicitly)"""
842
843 self.usage += " //depot/path[@revRange]"
b984733c 844 self.silent = False
b984733c
SH
845 self.createdBranches = Set()
846 self.committedChanges = Set()
569d1bd4 847 self.branch = ""
b984733c 848 self.detectBranches = False
cb53e1f8 849 self.detectLabels = False
b984733c 850 self.changesFile = ""
01265103 851 self.syncWithOrigin = True
4b97ffb1 852 self.verbose = False
a028a98e 853 self.importIntoRemotes = True
01a9c9c5 854 self.maxChanges = ""
c1f9197f 855 self.isWindows = (platform.system() == "Windows")
8b41a97f 856 self.keepRepoPath = False
6326aa58 857 self.depotPaths = None
3c699645 858 self.p4BranchesInGit = []
354081d5 859 self.cloneExclude = []
3a70cdfa
TAL
860 self.useClientSpec = False
861 self.clientSpecDirs = []
b984733c 862
01265103
SH
863 if gitConfig("git-p4.syncFromOrigin") == "false":
864 self.syncWithOrigin = False
865
b984733c 866 def extractFilesFromCommit(self, commit):
354081d5
TT
867 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
868 for path in self.cloneExclude]
b984733c
SH
869 files = []
870 fnum = 0
871 while commit.has_key("depotFile%s" % fnum):
872 path = commit["depotFile%s" % fnum]
6326aa58 873
354081d5
TT
874 if [p for p in self.cloneExclude
875 if path.startswith (p)]:
876 found = False
877 else:
878 found = [p for p in self.depotPaths
879 if path.startswith (p)]
6326aa58 880 if not found:
b984733c
SH
881 fnum = fnum + 1
882 continue
883
884 file = {}
885 file["path"] = path
886 file["rev"] = commit["rev%s" % fnum]
887 file["action"] = commit["action%s" % fnum]
888 file["type"] = commit["type%s" % fnum]
889 files.append(file)
890 fnum = fnum + 1
891 return files
892
6326aa58 893 def stripRepoPath(self, path, prefixes):
8b41a97f 894 if self.keepRepoPath:
6326aa58
HWN
895 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
896
897 for p in prefixes:
898 if path.startswith(p):
899 path = path[len(p):]
8b41a97f 900
6326aa58 901 return path
6754a299 902
71b112d4 903 def splitFilesIntoBranches(self, commit):
d5904674 904 branches = {}
71b112d4
SH
905 fnum = 0
906 while commit.has_key("depotFile%s" % fnum):
907 path = commit["depotFile%s" % fnum]
6326aa58
HWN
908 found = [p for p in self.depotPaths
909 if path.startswith (p)]
910 if not found:
71b112d4
SH
911 fnum = fnum + 1
912 continue
913
914 file = {}
915 file["path"] = path
916 file["rev"] = commit["rev%s" % fnum]
917 file["action"] = commit["action%s" % fnum]
918 file["type"] = commit["type%s" % fnum]
919 fnum = fnum + 1
920
6326aa58 921 relPath = self.stripRepoPath(path, self.depotPaths)
b984733c 922
4b97ffb1 923 for branch in self.knownBranches.keys():
6754a299
HWN
924
925 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
926 if relPath.startswith(branch + "/"):
d5904674
SH
927 if branch not in branches:
928 branches[branch] = []
71b112d4 929 branches[branch].append(file)
6555b2cc 930 break
b984733c
SH
931
932 return branches
933
6a49f8e2
HWN
934 ## Should move this out, doesn't use SELF.
935 def readP4Files(self, files):
30b5940b
SH
936 filesForCommit = []
937 filesToRead = []
938
3a70cdfa 939 for f in files:
30b5940b 940 includeFile = True
3a70cdfa
TAL
941 for val in self.clientSpecDirs:
942 if f['path'].startswith(val[0]):
30b5940b
SH
943 if val[1] <= 0:
944 includeFile = False
3a70cdfa
TAL
945 break
946
30b5940b
SH
947 if includeFile:
948 filesForCommit.append(f)
7f96e2e2 949 if f['action'] not in ('delete', 'purge'):
30b5940b 950 filesToRead.append(f)
6a49f8e2 951
30b5940b
SH
952 filedata = []
953 if len(filesToRead) > 0:
954 filedata = p4CmdList('-x - print',
955 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
956 for f in filesToRead]),
957 stdin_mode='w+')
f2eda79f 958
30b5940b
SH
959 if "p4ExitCode" in filedata[0]:
960 die("Problems executing p4. Error: [%d]."
961 % (filedata[0]['p4ExitCode']));
6a49f8e2 962
d2c6dd30
HWN
963 j = 0;
964 contents = {}
b1ce9447 965 while j < len(filedata):
d2c6dd30 966 stat = filedata[j]
b1ce9447 967 j += 1
7f96e2e2 968 text = ''
f3e9512b 969 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
7f96e2e2
JC
970 text += filedata[j]['data']
971 del filedata[j]['data']
b1ce9447 972 j += 1
1b9a4684
HWN
973
974 if not stat.has_key('depotFile'):
975 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
976 continue
977
8ff45f2a
MSO
978 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
979 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
980 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
2d71bde2 981 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
8ff45f2a 982
b1ce9447 983 contents[stat['depotFile']] = text
6a49f8e2 984
30b5940b
SH
985 for f in filesForCommit:
986 path = f['path']
987 if contents.has_key(path):
988 f['data'] = contents[path]
989
990 return filesForCommit
6a49f8e2 991
6326aa58 992 def commit(self, details, files, branch, branchPrefixes, parent = ""):
b984733c
SH
993 epoch = details["time"]
994 author = details["user"]
995
4b97ffb1
SH
996 if self.verbose:
997 print "commit into %s" % branch
998
96e07dd2
HWN
999 # start with reading files; if that fails, we should not
1000 # create a commit.
1001 new_files = []
1002 for f in files:
1003 if [p for p in branchPrefixes if f['path'].startswith(p)]:
1004 new_files.append (f)
1005 else:
1006 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
3a70cdfa 1007 files = self.readP4Files(new_files)
96e07dd2 1008
b984733c 1009 self.gitStream.write("commit %s\n" % branch)
6a49f8e2 1010# gitStream.write("mark :%s\n" % details["change"])
b984733c
SH
1011 self.committedChanges.add(int(details["change"]))
1012 committer = ""
b607e71e
SH
1013 if author not in self.users:
1014 self.getUserMapFromPerforceServer()
b984733c 1015 if author in self.users:
0828ab14 1016 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
b984733c 1017 else:
0828ab14 1018 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
b984733c
SH
1019
1020 self.gitStream.write("committer %s\n" % committer)
1021
1022 self.gitStream.write("data <<EOT\n")
1023 self.gitStream.write(details["desc"])
6581de09
SH
1024 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1025 % (','.join (branchPrefixes), details["change"]))
1026 if len(details['options']) > 0:
1027 self.gitStream.write(": options = %s" % details['options'])
1028 self.gitStream.write("]\nEOT\n\n")
b984733c
SH
1029
1030 if len(parent) > 0:
4b97ffb1
SH
1031 if self.verbose:
1032 print "parent %s" % parent
b984733c
SH
1033 self.gitStream.write("from %s\n" % parent)
1034
6a49f8e2 1035 for file in files:
b984733c 1036 if file["type"] == "apple":
6a49f8e2 1037 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
b984733c
SH
1038 continue
1039
6a49f8e2 1040 relPath = self.stripRepoPath(file['path'], branchPrefixes)
7f96e2e2 1041 if file["action"] in ("delete", "purge"):
b984733c
SH
1042 self.gitStream.write("D %s\n" % relPath)
1043 else:
6a49f8e2 1044 data = file['data']
b984733c 1045
74276ec6 1046 mode = "644"
b9fc6ea9 1047 if isP4Exec(file["type"]):
74276ec6
SH
1048 mode = "755"
1049 elif file["type"] == "symlink":
1050 mode = "120000"
1051 # p4 print on a symlink contains "target\n", so strip it off
1052 data = data[:-1]
1053
c1f9197f
MSO
1054 if self.isWindows and file["type"].endswith("text"):
1055 data = data.replace("\r\n", "\n")
1056
74276ec6 1057 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
b984733c
SH
1058 self.gitStream.write("data %s\n" % len(data))
1059 self.gitStream.write(data)
1060 self.gitStream.write("\n")
1061
1062 self.gitStream.write("\n")
1063
1f4ba1cb
SH
1064 change = int(details["change"])
1065
9bda3a85 1066 if self.labels.has_key(change):
1f4ba1cb
SH
1067 label = self.labels[change]
1068 labelDetails = label[0]
1069 labelRevisions = label[1]
71b112d4
SH
1070 if self.verbose:
1071 print "Change %s is labelled %s" % (change, labelDetails)
1f4ba1cb 1072
6326aa58
HWN
1073 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1074 for p in branchPrefixes]))
1f4ba1cb
SH
1075
1076 if len(files) == len(labelRevisions):
1077
1078 cleanedFiles = {}
1079 for info in files:
7f96e2e2 1080 if info["action"] in ("delete", "purge"):
1f4ba1cb
SH
1081 continue
1082 cleanedFiles[info["depotFile"]] = info["rev"]
1083
1084 if cleanedFiles == labelRevisions:
1085 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1086 self.gitStream.write("from %s\n" % branch)
1087
1088 owner = labelDetails["Owner"]
1089 tagger = ""
1090 if author in self.users:
1091 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1092 else:
1093 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1094 self.gitStream.write("tagger %s\n" % tagger)
1095 self.gitStream.write("data <<EOT\n")
1096 self.gitStream.write(labelDetails["Description"])
1097 self.gitStream.write("EOT\n\n")
1098
1099 else:
a46668fa 1100 if not self.silent:
cebdf5af
HWN
1101 print ("Tag %s does not match with change %s: files do not match."
1102 % (labelDetails["label"], change))
1f4ba1cb
SH
1103
1104 else:
a46668fa 1105 if not self.silent:
cebdf5af
HWN
1106 print ("Tag %s does not match with change %s: file count is different."
1107 % (labelDetails["label"], change))
b984733c 1108
183b8ef8 1109 def getUserCacheFilename(self):
b2d2d16a
SH
1110 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1111 return home + "/.gitp4-usercache.txt"
183b8ef8 1112
b607e71e 1113 def getUserMapFromPerforceServer(self):
ebd81168
SH
1114 if self.userMapFromPerforceServer:
1115 return
b984733c
SH
1116 self.users = {}
1117
1118 for output in p4CmdList("users"):
1119 if not output.has_key("User"):
1120 continue
1121 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1122
183b8ef8
HWN
1123
1124 s = ''
1125 for (key, val) in self.users.items():
1126 s += "%s\t%s\n" % (key, val)
1127
1128 open(self.getUserCacheFilename(), "wb").write(s)
ebd81168 1129 self.userMapFromPerforceServer = True
b607e71e
SH
1130
1131 def loadUserMapFromCache(self):
1132 self.users = {}
ebd81168 1133 self.userMapFromPerforceServer = False
b607e71e 1134 try:
183b8ef8 1135 cache = open(self.getUserCacheFilename(), "rb")
b607e71e
SH
1136 lines = cache.readlines()
1137 cache.close()
1138 for line in lines:
b25b2065 1139 entry = line.strip().split("\t")
b607e71e
SH
1140 self.users[entry[0]] = entry[1]
1141 except IOError:
1142 self.getUserMapFromPerforceServer()
1143
1f4ba1cb
SH
1144 def getLabels(self):
1145 self.labels = {}
1146
6326aa58 1147 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
10c3211b 1148 if len(l) > 0 and not self.silent:
183f8436 1149 print "Finding files belonging to labels in %s" % `self.depotPaths`
01ce1fe9
SH
1150
1151 for output in l:
1f4ba1cb
SH
1152 label = output["label"]
1153 revisions = {}
1154 newestChange = 0
71b112d4
SH
1155 if self.verbose:
1156 print "Querying files for label %s" % label
6326aa58
HWN
1157 for file in p4CmdList("files "
1158 + ' '.join (["%s...@%s" % (p, label)
1159 for p in self.depotPaths])):
1f4ba1cb
SH
1160 revisions[file["depotFile"]] = file["rev"]
1161 change = int(file["change"])
1162 if change > newestChange:
1163 newestChange = change
1164
9bda3a85
SH
1165 self.labels[newestChange] = [output, revisions]
1166
1167 if self.verbose:
1168 print "Label changes: %s" % self.labels.keys()
1f4ba1cb 1169
86dff6b6
HWN
1170 def guessProjectName(self):
1171 for p in self.depotPaths:
6e5295c4
SH
1172 if p.endswith("/"):
1173 p = p[:-1]
1174 p = p[p.strip().rfind("/") + 1:]
1175 if not p.endswith("/"):
1176 p += "/"
1177 return p
86dff6b6 1178
4b97ffb1 1179 def getBranchMapping(self):
6555b2cc
SH
1180 lostAndFoundBranches = set()
1181
4b97ffb1
SH
1182 for info in p4CmdList("branches"):
1183 details = p4Cmd("branch -o %s" % info["branch"])
1184 viewIdx = 0
1185 while details.has_key("View%s" % viewIdx):
1186 paths = details["View%s" % viewIdx].split(" ")
1187 viewIdx = viewIdx + 1
1188 # require standard //depot/foo/... //depot/bar/... mapping
1189 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1190 continue
1191 source = paths[0]
1192 destination = paths[1]
6509e19c
SH
1193 ## HACK
1194 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1195 source = source[len(self.depotPaths[0]):-4]
1196 destination = destination[len(self.depotPaths[0]):-4]
6555b2cc 1197
1a2edf4e
SH
1198 if destination in self.knownBranches:
1199 if not self.silent:
1200 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1201 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1202 continue
1203
6555b2cc
SH
1204 self.knownBranches[destination] = source
1205
1206 lostAndFoundBranches.discard(destination)
1207
29bdbac1 1208 if source not in self.knownBranches:
6555b2cc
SH
1209 lostAndFoundBranches.add(source)
1210
1211
1212 for branch in lostAndFoundBranches:
1213 self.knownBranches[branch] = branch
29bdbac1 1214
38f9f5ec
SH
1215 def getBranchMappingFromGitBranches(self):
1216 branches = p4BranchesInGit(self.importIntoRemotes)
1217 for branch in branches.keys():
1218 if branch == "master":
1219 branch = "main"
1220 else:
1221 branch = branch[len(self.projectName):]
1222 self.knownBranches[branch] = branch
1223
29bdbac1 1224 def listExistingP4GitBranches(self):
144ff46b
SH
1225 # branches holds mapping from name to commit
1226 branches = p4BranchesInGit(self.importIntoRemotes)
1227 self.p4BranchesInGit = branches.keys()
1228 for branch in branches.keys():
1229 self.initialParents[self.refPrefix + branch] = branches[branch]
4b97ffb1 1230
bb6e09b2
HWN
1231 def updateOptionDict(self, d):
1232 option_keys = {}
1233 if self.keepRepoPath:
1234 option_keys['keepRepoPath'] = 1
1235
1236 d["options"] = ' '.join(sorted(option_keys.keys()))
1237
1238 def readOptions(self, d):
1239 self.keepRepoPath = (d.has_key('options')
1240 and ('keepRepoPath' in d['options']))
6326aa58 1241
8134f69c
SH
1242 def gitRefForBranch(self, branch):
1243 if branch == "main":
1244 return self.refPrefix + "master"
1245
1246 if len(branch) <= 0:
1247 return branch
1248
1249 return self.refPrefix + self.projectName + branch
1250
1ca3d710
SH
1251 def gitCommitByP4Change(self, ref, change):
1252 if self.verbose:
1253 print "looking in ref " + ref + " for change %s using bisect..." % change
1254
1255 earliestCommit = ""
1256 latestCommit = parseRevision(ref)
1257
1258 while True:
1259 if self.verbose:
1260 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1261 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1262 if len(next) == 0:
1263 if self.verbose:
1264 print "argh"
1265 return ""
1266 log = extractLogMessageFromGitCommit(next)
1267 settings = extractSettingsGitLog(log)
1268 currentChange = int(settings['change'])
1269 if self.verbose:
1270 print "current change %s" % currentChange
1271
1272 if currentChange == change:
1273 if self.verbose:
1274 print "found %s" % next
1275 return next
1276
1277 if currentChange < change:
1278 earliestCommit = "^%s" % next
1279 else:
1280 latestCommit = "%s" % next
1281
1282 return ""
1283
1284 def importNewBranch(self, branch, maxChange):
1285 # make fast-import flush all changes to disk and update the refs using the checkpoint
1286 # command so that we can try to find the branch parent in the git history
1287 self.gitStream.write("checkpoint\n\n");
1288 self.gitStream.flush();
1289 branchPrefix = self.depotPaths[0] + branch + "/"
1290 range = "@1,%s" % maxChange
1291 #print "prefix" + branchPrefix
1292 changes = p4ChangesForPaths([branchPrefix], range)
1293 if len(changes) <= 0:
1294 return False
1295 firstChange = changes[0]
1296 #print "first change in branch: %s" % firstChange
1297 sourceBranch = self.knownBranches[branch]
1298 sourceDepotPath = self.depotPaths[0] + sourceBranch
1299 sourceRef = self.gitRefForBranch(sourceBranch)
1300 #print "source " + sourceBranch
1301
1302 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1303 #print "branch parent: %s" % branchParentChange
1304 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1305 if len(gitParent) > 0:
1306 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1307 #print "parent git commit: %s" % gitParent
1308
1309 self.importChanges(changes)
1310 return True
1311
e87f37ae
SH
1312 def importChanges(self, changes):
1313 cnt = 1
1314 for change in changes:
1315 description = p4Cmd("describe %s" % change)
1316 self.updateOptionDict(description)
1317
1318 if not self.silent:
1319 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1320 sys.stdout.flush()
1321 cnt = cnt + 1
1322
1323 try:
1324 if self.detectBranches:
1325 branches = self.splitFilesIntoBranches(description)
1326 for branch in branches.keys():
1327 ## HACK --hwn
1328 branchPrefix = self.depotPaths[0] + branch + "/"
1329
1330 parent = ""
1331
1332 filesForCommit = branches[branch]
1333
1334 if self.verbose:
1335 print "branch is %s" % branch
1336
1337 self.updatedBranches.add(branch)
1338
1339 if branch not in self.createdBranches:
1340 self.createdBranches.add(branch)
1341 parent = self.knownBranches[branch]
1342 if parent == branch:
1343 parent = ""
1ca3d710
SH
1344 else:
1345 fullBranch = self.projectName + branch
1346 if fullBranch not in self.p4BranchesInGit:
1347 if not self.silent:
1348 print("\n Importing new branch %s" % fullBranch);
1349 if self.importNewBranch(branch, change - 1):
1350 parent = ""
1351 self.p4BranchesInGit.append(fullBranch)
1352 if not self.silent:
1353 print("\n Resuming with change %s" % change);
1354
1355 if self.verbose:
1356 print "parent determined through known branches: %s" % parent
e87f37ae 1357
8134f69c
SH
1358 branch = self.gitRefForBranch(branch)
1359 parent = self.gitRefForBranch(parent)
e87f37ae
SH
1360
1361 if self.verbose:
1362 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1363
1364 if len(parent) == 0 and branch in self.initialParents:
1365 parent = self.initialParents[branch]
1366 del self.initialParents[branch]
1367
1368 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1369 else:
1370 files = self.extractFilesFromCommit(description)
1371 self.commit(description, files, self.branch, self.depotPaths,
1372 self.initialParent)
1373 self.initialParent = ""
1374 except IOError:
1375 print self.gitError.read()
1376 sys.exit(1)
1377
c208a243
SH
1378 def importHeadRevision(self, revision):
1379 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1380
1381 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1382 details["desc"] = ("Initial import of %s from the state at revision %s"
1383 % (' '.join(self.depotPaths), revision))
1384 details["change"] = revision
1385 newestRevision = 0
1386
1387 fileCnt = 0
1388 for info in p4CmdList("files "
1389 + ' '.join(["%s...%s"
1390 % (p, revision)
1391 for p in self.depotPaths])):
1392
1393 if info['code'] == 'error':
1394 sys.stderr.write("p4 returned an error: %s\n"
1395 % info['data'])
1396 sys.exit(1)
1397
1398
1399 change = int(info["change"])
1400 if change > newestRevision:
1401 newestRevision = change
1402
7f96e2e2 1403 if info["action"] in ("delete", "purge"):
c208a243
SH
1404 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1405 #fileCnt = fileCnt + 1
1406 continue
1407
1408 for prop in ["depotFile", "rev", "action", "type" ]:
1409 details["%s%s" % (prop, fileCnt)] = info[prop]
1410
1411 fileCnt = fileCnt + 1
1412
1413 details["change"] = newestRevision
1414 self.updateOptionDict(details)
1415 try:
1416 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1417 except IOError:
1418 print "IO error with git fast-import. Is your git version recent enough?"
1419 print self.gitError.read()
1420
1421
3a70cdfa
TAL
1422 def getClientSpec(self):
1423 specList = p4CmdList( "client -o" )
1424 temp = {}
1425 for entry in specList:
1426 for k,v in entry.iteritems():
1427 if k.startswith("View"):
1428 if v.startswith('"'):
1429 start = 1
1430 else:
1431 start = 0
1432 index = v.find("...")
1433 v = v[start:index]
1434 if v.startswith("-"):
1435 v = v[1:]
1436 temp[v] = -len(v)
1437 else:
1438 temp[v] = len(v)
1439 self.clientSpecDirs = temp.items()
1440 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1441
b984733c 1442 def run(self, args):
6326aa58 1443 self.depotPaths = []
179caebf
SH
1444 self.changeRange = ""
1445 self.initialParent = ""
6326aa58 1446 self.previousDepotPaths = []
ce6f33c8 1447
29bdbac1
SH
1448 # map from branch depot path to parent branch
1449 self.knownBranches = {}
1450 self.initialParents = {}
5ca44617 1451 self.hasOrigin = originP4BranchesExist()
a43ff00c
SH
1452 if not self.syncWithOrigin:
1453 self.hasOrigin = False
29bdbac1 1454
a028a98e
SH
1455 if self.importIntoRemotes:
1456 self.refPrefix = "refs/remotes/p4/"
1457 else:
db775559 1458 self.refPrefix = "refs/heads/p4/"
a028a98e 1459
cebdf5af
HWN
1460 if self.syncWithOrigin and self.hasOrigin:
1461 if not self.silent:
1462 print "Syncing with origin first by calling git fetch origin"
1463 system("git fetch origin")
10f880f8 1464
569d1bd4 1465 if len(self.branch) == 0:
db775559 1466 self.branch = self.refPrefix + "master"
a028a98e 1467 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
48df6fd8 1468 system("git update-ref %s refs/heads/p4" % self.branch)
48df6fd8 1469 system("git branch -D p4");
faf1bd20 1470 # create it /after/ importing, when master exists
0058a33a 1471 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
a3c55c09 1472 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
967f72e2 1473
3cafb7d8 1474 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
3a70cdfa
TAL
1475 self.getClientSpec()
1476
6a49f8e2
HWN
1477 # TODO: should always look at previous commits,
1478 # merge with previous imports, if possible.
1479 if args == []:
d414c74a 1480 if self.hasOrigin:
5ca44617 1481 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
abcd790f
SH
1482 self.listExistingP4GitBranches()
1483
1484 if len(self.p4BranchesInGit) > 1:
1485 if not self.silent:
1486 print "Importing from/into multiple branches"
1487 self.detectBranches = True
967f72e2 1488
29bdbac1
SH
1489 if self.verbose:
1490 print "branches: %s" % self.p4BranchesInGit
1491
1492 p4Change = 0
1493 for branch in self.p4BranchesInGit:
cebdf5af 1494 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
bb6e09b2
HWN
1495
1496 settings = extractSettingsGitLog(logMsg)
29bdbac1 1497
bb6e09b2
HWN
1498 self.readOptions(settings)
1499 if (settings.has_key('depot-paths')
1500 and settings.has_key ('change')):
1501 change = int(settings['change']) + 1
29bdbac1
SH
1502 p4Change = max(p4Change, change)
1503
bb6e09b2
HWN
1504 depotPaths = sorted(settings['depot-paths'])
1505 if self.previousDepotPaths == []:
6326aa58 1506 self.previousDepotPaths = depotPaths
29bdbac1 1507 else:
6326aa58
HWN
1508 paths = []
1509 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
583e1707 1510 for i in range(0, min(len(cur), len(prev))):
6326aa58 1511 if cur[i] <> prev[i]:
583e1707 1512 i = i - 1
6326aa58
HWN
1513 break
1514
583e1707 1515 paths.append (cur[:i + 1])
6326aa58
HWN
1516
1517 self.previousDepotPaths = paths
29bdbac1
SH
1518
1519 if p4Change > 0:
bb6e09b2 1520 self.depotPaths = sorted(self.previousDepotPaths)
d5904674 1521 self.changeRange = "@%s,#head" % p4Change
330f53b8
SH
1522 if not self.detectBranches:
1523 self.initialParent = parseRevision(self.branch)
341dc1c1 1524 if not self.silent and not self.detectBranches:
967f72e2 1525 print "Performing incremental import into %s git branch" % self.branch
569d1bd4 1526
f9162f6a
SH
1527 if not self.branch.startswith("refs/"):
1528 self.branch = "refs/heads/" + self.branch
179caebf 1529
6326aa58 1530 if len(args) == 0 and self.depotPaths:
b984733c 1531 if not self.silent:
6326aa58 1532 print "Depot paths: %s" % ' '.join(self.depotPaths)
b984733c 1533 else:
6326aa58 1534 if self.depotPaths and self.depotPaths != args:
cebdf5af 1535 print ("previous import used depot path %s and now %s was specified. "
6326aa58
HWN
1536 "This doesn't work!" % (' '.join (self.depotPaths),
1537 ' '.join (args)))
b984733c 1538 sys.exit(1)
6326aa58 1539
bb6e09b2 1540 self.depotPaths = sorted(args)
b984733c 1541
1c49fc19 1542 revision = ""
b984733c 1543 self.users = {}
b984733c 1544
6326aa58
HWN
1545 newPaths = []
1546 for p in self.depotPaths:
1547 if p.find("@") != -1:
1548 atIdx = p.index("@")
1549 self.changeRange = p[atIdx:]
1550 if self.changeRange == "@all":
1551 self.changeRange = ""
6a49f8e2 1552 elif ',' not in self.changeRange:
1c49fc19 1553 revision = self.changeRange
6326aa58 1554 self.changeRange = ""
7fcff9de 1555 p = p[:atIdx]
6326aa58
HWN
1556 elif p.find("#") != -1:
1557 hashIdx = p.index("#")
1c49fc19 1558 revision = p[hashIdx:]
7fcff9de 1559 p = p[:hashIdx]
6326aa58 1560 elif self.previousDepotPaths == []:
1c49fc19 1561 revision = "#head"
6326aa58
HWN
1562
1563 p = re.sub ("\.\.\.$", "", p)
1564 if not p.endswith("/"):
1565 p += "/"
1566
1567 newPaths.append(p)
1568
1569 self.depotPaths = newPaths
1570
b984733c 1571
b607e71e 1572 self.loadUserMapFromCache()
cb53e1f8
SH
1573 self.labels = {}
1574 if self.detectLabels:
1575 self.getLabels();
b984733c 1576
4b97ffb1 1577 if self.detectBranches:
df450923
SH
1578 ## FIXME - what's a P4 projectName ?
1579 self.projectName = self.guessProjectName()
1580
38f9f5ec
SH
1581 if self.hasOrigin:
1582 self.getBranchMappingFromGitBranches()
1583 else:
1584 self.getBranchMapping()
29bdbac1
SH
1585 if self.verbose:
1586 print "p4-git branches: %s" % self.p4BranchesInGit
1587 print "initial parents: %s" % self.initialParents
1588 for b in self.p4BranchesInGit:
1589 if b != "master":
6326aa58
HWN
1590
1591 ## FIXME
29bdbac1
SH
1592 b = b[len(self.projectName):]
1593 self.createdBranches.add(b)
4b97ffb1 1594
f291b4e3 1595 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
b984733c 1596
cebdf5af 1597 importProcess = subprocess.Popen(["git", "fast-import"],
6326aa58
HWN
1598 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1599 stderr=subprocess.PIPE);
08483580
SH
1600 self.gitOutput = importProcess.stdout
1601 self.gitStream = importProcess.stdin
1602 self.gitError = importProcess.stderr
b984733c 1603
1c49fc19 1604 if revision:
c208a243 1605 self.importHeadRevision(revision)
b984733c
SH
1606 else:
1607 changes = []
1608
0828ab14 1609 if len(self.changesFile) > 0:
b984733c
SH
1610 output = open(self.changesFile).readlines()
1611 changeSet = Set()
1612 for line in output:
1613 changeSet.add(int(line))
1614
1615 for change in changeSet:
1616 changes.append(change)
1617
1618 changes.sort()
1619 else:
29bdbac1 1620 if self.verbose:
86dff6b6 1621 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
6326aa58 1622 self.changeRange)
4f6432d8 1623 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
b984733c 1624
01a9c9c5 1625 if len(self.maxChanges) > 0:
7fcff9de 1626 changes = changes[:min(int(self.maxChanges), len(changes))]
01a9c9c5 1627
b984733c 1628 if len(changes) == 0:
0828ab14 1629 if not self.silent:
341dc1c1 1630 print "No changes to import!"
1f52af6c 1631 return True
b984733c 1632
a9d1a27a
SH
1633 if not self.silent and not self.detectBranches:
1634 print "Import destination: %s" % self.branch
1635
341dc1c1
SH
1636 self.updatedBranches = set()
1637
e87f37ae 1638 self.importChanges(changes)
b984733c 1639
341dc1c1
SH
1640 if not self.silent:
1641 print ""
1642 if len(self.updatedBranches) > 0:
1643 sys.stdout.write("Updated branches: ")
1644 for b in self.updatedBranches:
1645 sys.stdout.write("%s " % b)
1646 sys.stdout.write("\n")
b984733c 1647
b984733c 1648 self.gitStream.close()
29bdbac1
SH
1649 if importProcess.wait() != 0:
1650 die("fast-import failed: %s" % self.gitError.read())
b984733c
SH
1651 self.gitOutput.close()
1652 self.gitError.close()
1653
b984733c
SH
1654 return True
1655
01ce1fe9
SH
1656class P4Rebase(Command):
1657 def __init__(self):
1658 Command.__init__(self)
01265103 1659 self.options = [ ]
cebdf5af
HWN
1660 self.description = ("Fetches the latest revision from perforce and "
1661 + "rebases the current work (branch) against it")
68c42153 1662 self.verbose = False
01ce1fe9
SH
1663
1664 def run(self, args):
1665 sync = P4Sync()
1666 sync.run([])
d7e3868c 1667
14594f4b
SH
1668 return self.rebase()
1669
1670 def rebase(self):
36ee4ee4
SH
1671 if os.system("git update-index --refresh") != 0:
1672 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.");
1673 if len(read_pipe("git diff-index HEAD --")) > 0:
1674 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1675
d7e3868c
SH
1676 [upstream, settings] = findUpstreamBranchPoint()
1677 if len(upstream) == 0:
1678 die("Cannot find upstream branchpoint for rebase")
1679
1680 # the branchpoint may be p4/foo~3, so strip off the parent
1681 upstream = re.sub("~[0-9]+$", "", upstream)
1682
1683 print "Rebasing the current branch onto %s" % upstream
b25b2065 1684 oldHead = read_pipe("git rev-parse HEAD").strip()
d7e3868c 1685 system("git rebase %s" % upstream)
1f52af6c 1686 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
01ce1fe9
SH
1687 return True
1688
f9a3a4f7
SH
1689class P4Clone(P4Sync):
1690 def __init__(self):
1691 P4Sync.__init__(self)
1692 self.description = "Creates a new git repository and imports from Perforce into it"
bb6e09b2 1693 self.usage = "usage: %prog [options] //depot/path[@revRange]"
354081d5 1694 self.options += [
bb6e09b2
HWN
1695 optparse.make_option("--destination", dest="cloneDestination",
1696 action='store', default=None,
354081d5
TT
1697 help="where to leave result of the clone"),
1698 optparse.make_option("-/", dest="cloneExclude",
1699 action="append", type="string",
1700 help="exclude depot path")
1701 ]
bb6e09b2 1702 self.cloneDestination = None
f9a3a4f7 1703 self.needsGit = False
f9a3a4f7 1704
354081d5
TT
1705 # This is required for the "append" cloneExclude action
1706 def ensure_value(self, attr, value):
1707 if not hasattr(self, attr) or getattr(self, attr) is None:
1708 setattr(self, attr, value)
1709 return getattr(self, attr)
1710
6a49f8e2
HWN
1711 def defaultDestination(self, args):
1712 ## TODO: use common prefix of args?
1713 depotPath = args[0]
1714 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1715 depotDir = re.sub("(#[^#]*)$", "", depotDir)
053d9e43 1716 depotDir = re.sub(r"\.\.\.$", "", depotDir)
6a49f8e2
HWN
1717 depotDir = re.sub(r"/$", "", depotDir)
1718 return os.path.split(depotDir)[1]
1719
f9a3a4f7
SH
1720 def run(self, args):
1721 if len(args) < 1:
1722 return False
bb6e09b2
HWN
1723
1724 if self.keepRepoPath and not self.cloneDestination:
1725 sys.stderr.write("Must specify destination for --keep-path\n")
1726 sys.exit(1)
f9a3a4f7 1727
6326aa58 1728 depotPaths = args
5e100b5c
SH
1729
1730 if not self.cloneDestination and len(depotPaths) > 1:
1731 self.cloneDestination = depotPaths[-1]
1732 depotPaths = depotPaths[:-1]
1733
354081d5 1734 self.cloneExclude = ["/"+p for p in self.cloneExclude]
6326aa58
HWN
1735 for p in depotPaths:
1736 if not p.startswith("//"):
1737 return False
f9a3a4f7 1738
bb6e09b2 1739 if not self.cloneDestination:
98ad4faf 1740 self.cloneDestination = self.defaultDestination(args)
f9a3a4f7 1741
86dff6b6 1742 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
c3bf3f13
KG
1743 if not os.path.exists(self.cloneDestination):
1744 os.makedirs(self.cloneDestination)
053fd0c1 1745 chdir(self.cloneDestination)
f9a3a4f7 1746 system("git init")
b86f7378 1747 self.gitdir = os.getcwd() + "/.git"
6326aa58 1748 if not P4Sync.run(self, depotPaths):
f9a3a4f7 1749 return False
f9a3a4f7 1750 if self.branch != "master":
e9905013
TAL
1751 if self.importIntoRemotes:
1752 masterbranch = "refs/remotes/p4/master"
1753 else:
1754 masterbranch = "refs/heads/p4/master"
1755 if gitBranchExists(masterbranch):
1756 system("git branch master %s" % masterbranch)
8f9b2e08
SH
1757 system("git checkout -f")
1758 else:
1759 print "Could not detect main branch. No checkout/master branch created."
86dff6b6 1760
f9a3a4f7
SH
1761 return True
1762
09d89de2
SH
1763class P4Branches(Command):
1764 def __init__(self):
1765 Command.__init__(self)
1766 self.options = [ ]
1767 self.description = ("Shows the git branches that hold imports and their "
1768 + "corresponding perforce depot paths")
1769 self.verbose = False
1770
1771 def run(self, args):
5ca44617
SH
1772 if originP4BranchesExist():
1773 createOrUpdateBranchesFromOrigin()
1774
09d89de2
SH
1775 cmdline = "git rev-parse --symbolic "
1776 cmdline += " --remotes"
1777
1778 for line in read_pipe_lines(cmdline):
1779 line = line.strip()
1780
1781 if not line.startswith('p4/') or line == "p4/HEAD":
1782 continue
1783 branch = line
1784
1785 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1786 settings = extractSettingsGitLog(log)
1787
1788 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1789 return True
1790
b984733c
SH
1791class HelpFormatter(optparse.IndentedHelpFormatter):
1792 def __init__(self):
1793 optparse.IndentedHelpFormatter.__init__(self)
1794
1795 def format_description(self, description):
1796 if description:
1797 return description + "\n"
1798 else:
1799 return ""
4f5cf76a 1800
86949eef
SH
1801def printUsage(commands):
1802 print "usage: %s <command> [options]" % sys.argv[0]
1803 print ""
1804 print "valid commands: %s" % ", ".join(commands)
1805 print ""
1806 print "Try %s <command> --help for command specific help." % sys.argv[0]
1807 print ""
1808
1809commands = {
b86f7378
HWN
1810 "debug" : P4Debug,
1811 "submit" : P4Submit,
a9834f58 1812 "commit" : P4Submit,
b86f7378
HWN
1813 "sync" : P4Sync,
1814 "rebase" : P4Rebase,
1815 "clone" : P4Clone,
09d89de2
SH
1816 "rollback" : P4RollBack,
1817 "branches" : P4Branches
86949eef
SH
1818}
1819
86949eef 1820
bb6e09b2
HWN
1821def main():
1822 if len(sys.argv[1:]) == 0:
1823 printUsage(commands.keys())
1824 sys.exit(2)
4f5cf76a 1825
bb6e09b2
HWN
1826 cmd = ""
1827 cmdName = sys.argv[1]
1828 try:
b86f7378
HWN
1829 klass = commands[cmdName]
1830 cmd = klass()
bb6e09b2
HWN
1831 except KeyError:
1832 print "unknown command %s" % cmdName
1833 print ""
1834 printUsage(commands.keys())
1835 sys.exit(2)
1836
1837 options = cmd.options
b86f7378 1838 cmd.gitdir = os.environ.get("GIT_DIR", None)
bb6e09b2
HWN
1839
1840 args = sys.argv[2:]
1841
1842 if len(options) > 0:
1843 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1844
1845 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1846 options,
1847 description = cmd.description,
1848 formatter = HelpFormatter())
1849
1850 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1851 global verbose
1852 verbose = cmd.verbose
1853 if cmd.needsGit:
b86f7378
HWN
1854 if cmd.gitdir == None:
1855 cmd.gitdir = os.path.abspath(".git")
1856 if not isValidGitDir(cmd.gitdir):
1857 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1858 if os.path.exists(cmd.gitdir):
bb6e09b2
HWN
1859 cdup = read_pipe("git rev-parse --show-cdup").strip()
1860 if len(cdup) > 0:
053fd0c1 1861 chdir(cdup);
e20a9e53 1862
b86f7378
HWN
1863 if not isValidGitDir(cmd.gitdir):
1864 if isValidGitDir(cmd.gitdir + "/.git"):
1865 cmd.gitdir += "/.git"
bb6e09b2 1866 else:
b86f7378 1867 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
e20a9e53 1868
b86f7378 1869 os.environ["GIT_DIR"] = cmd.gitdir
86949eef 1870
bb6e09b2
HWN
1871 if not cmd.run(args):
1872 parser.print_help()
4f5cf76a 1873
4f5cf76a 1874
bb6e09b2
HWN
1875if __name__ == '__main__':
1876 main()