]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/fast-import/git-p4
Merge branch 'af/maint-install-no-handlink'
[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
SH
710 tmpFile.close()
711 defaultEditor = "vi"
712 if platform.system() == "Windows":
713 defaultEditor = "notepad"
82cea9ff
SB
714 if os.environ.has_key("P4EDITOR"):
715 editor = os.environ.get("P4EDITOR")
716 else:
717 editor = os.environ.get("EDITOR", defaultEditor);
e96e400f
SH
718 system(editor + " " + fileName)
719 tmpFile = open(fileName, "rb")
720 message = tmpFile.read()
721 tmpFile.close()
722 os.remove(fileName)
723 submitTemplate = message[:message.index(separatorLine)]
724 if self.isWindows:
725 submitTemplate = submitTemplate.replace("\r\n", "\n")
726
a7d3ef9d 727 p4_write_pipe("submit -i", submitTemplate)
4f5cf76a
SH
728 else:
729 fileName = "submit.txt"
730 file = open(fileName, "w+")
731 file.write(self.prepareLogMessage(template, logMessage))
732 file.close()
cebdf5af
HWN
733 print ("Perforce submit template written as %s. "
734 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
735 % (fileName, fileName))
4f5cf76a
SH
736
737 def run(self, args):
c9b50e63
SH
738 if len(args) == 0:
739 self.master = currentGitBranch()
4280e533 740 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
c9b50e63
SH
741 die("Detecting current git branch failed!")
742 elif len(args) == 1:
743 self.master = args[0]
744 else:
745 return False
746
4c2d5d72
JX
747 allowSubmit = gitConfig("git-p4.allowSubmit")
748 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
749 die("%s is not in git-p4.allowSubmit" % self.master)
750
27d2d811 751 [upstream, settings] = findUpstreamBranchPoint()
ea99c3ae 752 self.depotPath = settings['depot-paths'][0]
27d2d811
SH
753 if len(self.origin) == 0:
754 self.origin = upstream
a3fdd579
SH
755
756 if self.verbose:
757 print "Origin branch is " + self.origin
9512497b 758
ea99c3ae 759 if len(self.depotPath) == 0:
9512497b
SH
760 print "Internal error: cannot locate perforce depot path from existing branches"
761 sys.exit(128)
762
ea99c3ae 763 self.clientPath = p4Where(self.depotPath)
9512497b 764
51a2640a 765 if len(self.clientPath) == 0:
ea99c3ae 766 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
9512497b
SH
767 sys.exit(128)
768
ea99c3ae 769 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
7944f142 770 self.oldWorkingDirectory = os.getcwd()
c1b296b9 771
053fd0c1 772 chdir(self.clientPath)
31f9ec12 773 print "Syncronizing p4 checkout..."
87b611d5 774 p4_system("sync ...")
9512497b 775
4f5cf76a 776 self.check()
4f5cf76a 777
4c750c0d
SH
778 commits = []
779 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
780 commits.append(line.strip())
781 commits.reverse()
4f5cf76a
SH
782
783 while len(commits) > 0:
4f5cf76a
SH
784 commit = commits[0]
785 commits = commits[1:]
7cb5cbef 786 self.applyCommit(commit)
4f5cf76a
SH
787 if not self.interactive:
788 break
789
4f5cf76a 790 if len(commits) == 0:
4c750c0d 791 print "All changes applied!"
053fd0c1 792 chdir(self.oldWorkingDirectory)
14594f4b 793
4c750c0d
SH
794 sync = P4Sync()
795 sync.run([])
14594f4b 796
4c750c0d
SH
797 rebase = P4Rebase()
798 rebase.rebase()
4f5cf76a 799
b984733c
SH
800 return True
801
711544b0 802class P4Sync(Command):
b984733c
SH
803 def __init__(self):
804 Command.__init__(self)
805 self.options = [
806 optparse.make_option("--branch", dest="branch"),
807 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
808 optparse.make_option("--changesfile", dest="changesFile"),
809 optparse.make_option("--silent", dest="silent", action="store_true"),
ef48f909 810 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
a028a98e 811 optparse.make_option("--verbose", dest="verbose", action="store_true"),
d2c6dd30
HWN
812 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
813 help="Import into refs/heads/ , not refs/remotes"),
8b41a97f 814 optparse.make_option("--max-changes", dest="maxChanges"),
86dff6b6 815 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
3a70cdfa
TAL
816 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
817 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
818 help="Only sync files that are included in the Perforce Client Spec")
b984733c
SH
819 ]
820 self.description = """Imports from Perforce into a git repository.\n
821 example:
822 //depot/my/project/ -- to import the current head
823 //depot/my/project/@all -- to import everything
824 //depot/my/project/@1,6 -- to import only from revision 1 to 6
825
826 (a ... is not needed in the path p4 specification, it's added implicitly)"""
827
828 self.usage += " //depot/path[@revRange]"
b984733c 829 self.silent = False
b984733c
SH
830 self.createdBranches = Set()
831 self.committedChanges = Set()
569d1bd4 832 self.branch = ""
b984733c 833 self.detectBranches = False
cb53e1f8 834 self.detectLabels = False
b984733c 835 self.changesFile = ""
01265103 836 self.syncWithOrigin = True
4b97ffb1 837 self.verbose = False
a028a98e 838 self.importIntoRemotes = True
01a9c9c5 839 self.maxChanges = ""
c1f9197f 840 self.isWindows = (platform.system() == "Windows")
8b41a97f 841 self.keepRepoPath = False
6326aa58 842 self.depotPaths = None
3c699645 843 self.p4BranchesInGit = []
354081d5 844 self.cloneExclude = []
3a70cdfa
TAL
845 self.useClientSpec = False
846 self.clientSpecDirs = []
b984733c 847
01265103
SH
848 if gitConfig("git-p4.syncFromOrigin") == "false":
849 self.syncWithOrigin = False
850
b984733c 851 def extractFilesFromCommit(self, commit):
354081d5
TT
852 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
853 for path in self.cloneExclude]
b984733c
SH
854 files = []
855 fnum = 0
856 while commit.has_key("depotFile%s" % fnum):
857 path = commit["depotFile%s" % fnum]
6326aa58 858
354081d5
TT
859 if [p for p in self.cloneExclude
860 if path.startswith (p)]:
861 found = False
862 else:
863 found = [p for p in self.depotPaths
864 if path.startswith (p)]
6326aa58 865 if not found:
b984733c
SH
866 fnum = fnum + 1
867 continue
868
869 file = {}
870 file["path"] = path
871 file["rev"] = commit["rev%s" % fnum]
872 file["action"] = commit["action%s" % fnum]
873 file["type"] = commit["type%s" % fnum]
874 files.append(file)
875 fnum = fnum + 1
876 return files
877
6326aa58 878 def stripRepoPath(self, path, prefixes):
8b41a97f 879 if self.keepRepoPath:
6326aa58
HWN
880 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
881
882 for p in prefixes:
883 if path.startswith(p):
884 path = path[len(p):]
8b41a97f 885
6326aa58 886 return path
6754a299 887
71b112d4 888 def splitFilesIntoBranches(self, commit):
d5904674 889 branches = {}
71b112d4
SH
890 fnum = 0
891 while commit.has_key("depotFile%s" % fnum):
892 path = commit["depotFile%s" % fnum]
6326aa58
HWN
893 found = [p for p in self.depotPaths
894 if path.startswith (p)]
895 if not found:
71b112d4
SH
896 fnum = fnum + 1
897 continue
898
899 file = {}
900 file["path"] = path
901 file["rev"] = commit["rev%s" % fnum]
902 file["action"] = commit["action%s" % fnum]
903 file["type"] = commit["type%s" % fnum]
904 fnum = fnum + 1
905
6326aa58 906 relPath = self.stripRepoPath(path, self.depotPaths)
b984733c 907
4b97ffb1 908 for branch in self.knownBranches.keys():
6754a299
HWN
909
910 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
911 if relPath.startswith(branch + "/"):
d5904674
SH
912 if branch not in branches:
913 branches[branch] = []
71b112d4 914 branches[branch].append(file)
6555b2cc 915 break
b984733c
SH
916
917 return branches
918
6a49f8e2
HWN
919 ## Should move this out, doesn't use SELF.
920 def readP4Files(self, files):
30b5940b
SH
921 filesForCommit = []
922 filesToRead = []
923
3a70cdfa 924 for f in files:
30b5940b 925 includeFile = True
3a70cdfa
TAL
926 for val in self.clientSpecDirs:
927 if f['path'].startswith(val[0]):
30b5940b
SH
928 if val[1] <= 0:
929 includeFile = False
3a70cdfa
TAL
930 break
931
30b5940b
SH
932 if includeFile:
933 filesForCommit.append(f)
934 if f['action'] != 'delete':
935 filesToRead.append(f)
6a49f8e2 936
30b5940b
SH
937 filedata = []
938 if len(filesToRead) > 0:
939 filedata = p4CmdList('-x - print',
940 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
941 for f in filesToRead]),
942 stdin_mode='w+')
f2eda79f 943
30b5940b
SH
944 if "p4ExitCode" in filedata[0]:
945 die("Problems executing p4. Error: [%d]."
946 % (filedata[0]['p4ExitCode']));
6a49f8e2 947
d2c6dd30
HWN
948 j = 0;
949 contents = {}
b1ce9447 950 while j < len(filedata):
d2c6dd30 951 stat = filedata[j]
b1ce9447 952 j += 1
8ff45f2a 953 text = [];
f3e9512b 954 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
8ff45f2a 955 text.append(filedata[j]['data'])
b1ce9447 956 j += 1
8ff45f2a 957 text = ''.join(text)
1b9a4684
HWN
958
959 if not stat.has_key('depotFile'):
960 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
961 continue
962
8ff45f2a
MSO
963 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
964 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
965 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
2d71bde2 966 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
8ff45f2a 967
b1ce9447 968 contents[stat['depotFile']] = text
6a49f8e2 969
30b5940b
SH
970 for f in filesForCommit:
971 path = f['path']
972 if contents.has_key(path):
973 f['data'] = contents[path]
974
975 return filesForCommit
6a49f8e2 976
6326aa58 977 def commit(self, details, files, branch, branchPrefixes, parent = ""):
b984733c
SH
978 epoch = details["time"]
979 author = details["user"]
980
4b97ffb1
SH
981 if self.verbose:
982 print "commit into %s" % branch
983
96e07dd2
HWN
984 # start with reading files; if that fails, we should not
985 # create a commit.
986 new_files = []
987 for f in files:
988 if [p for p in branchPrefixes if f['path'].startswith(p)]:
989 new_files.append (f)
990 else:
991 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
3a70cdfa 992 files = self.readP4Files(new_files)
96e07dd2 993
b984733c 994 self.gitStream.write("commit %s\n" % branch)
6a49f8e2 995# gitStream.write("mark :%s\n" % details["change"])
b984733c
SH
996 self.committedChanges.add(int(details["change"]))
997 committer = ""
b607e71e
SH
998 if author not in self.users:
999 self.getUserMapFromPerforceServer()
b984733c 1000 if author in self.users:
0828ab14 1001 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
b984733c 1002 else:
0828ab14 1003 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
b984733c
SH
1004
1005 self.gitStream.write("committer %s\n" % committer)
1006
1007 self.gitStream.write("data <<EOT\n")
1008 self.gitStream.write(details["desc"])
6581de09
SH
1009 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1010 % (','.join (branchPrefixes), details["change"]))
1011 if len(details['options']) > 0:
1012 self.gitStream.write(": options = %s" % details['options'])
1013 self.gitStream.write("]\nEOT\n\n")
b984733c
SH
1014
1015 if len(parent) > 0:
4b97ffb1
SH
1016 if self.verbose:
1017 print "parent %s" % parent
b984733c
SH
1018 self.gitStream.write("from %s\n" % parent)
1019
6a49f8e2 1020 for file in files:
b984733c 1021 if file["type"] == "apple":
6a49f8e2 1022 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
b984733c
SH
1023 continue
1024
6a49f8e2
HWN
1025 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1026 if file["action"] == "delete":
b984733c
SH
1027 self.gitStream.write("D %s\n" % relPath)
1028 else:
6a49f8e2 1029 data = file['data']
b984733c 1030
74276ec6 1031 mode = "644"
b9fc6ea9 1032 if isP4Exec(file["type"]):
74276ec6
SH
1033 mode = "755"
1034 elif file["type"] == "symlink":
1035 mode = "120000"
1036 # p4 print on a symlink contains "target\n", so strip it off
1037 data = data[:-1]
1038
c1f9197f
MSO
1039 if self.isWindows and file["type"].endswith("text"):
1040 data = data.replace("\r\n", "\n")
1041
74276ec6 1042 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
b984733c
SH
1043 self.gitStream.write("data %s\n" % len(data))
1044 self.gitStream.write(data)
1045 self.gitStream.write("\n")
1046
1047 self.gitStream.write("\n")
1048
1f4ba1cb
SH
1049 change = int(details["change"])
1050
9bda3a85 1051 if self.labels.has_key(change):
1f4ba1cb
SH
1052 label = self.labels[change]
1053 labelDetails = label[0]
1054 labelRevisions = label[1]
71b112d4
SH
1055 if self.verbose:
1056 print "Change %s is labelled %s" % (change, labelDetails)
1f4ba1cb 1057
6326aa58
HWN
1058 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1059 for p in branchPrefixes]))
1f4ba1cb
SH
1060
1061 if len(files) == len(labelRevisions):
1062
1063 cleanedFiles = {}
1064 for info in files:
1065 if info["action"] == "delete":
1066 continue
1067 cleanedFiles[info["depotFile"]] = info["rev"]
1068
1069 if cleanedFiles == labelRevisions:
1070 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1071 self.gitStream.write("from %s\n" % branch)
1072
1073 owner = labelDetails["Owner"]
1074 tagger = ""
1075 if author in self.users:
1076 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1077 else:
1078 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1079 self.gitStream.write("tagger %s\n" % tagger)
1080 self.gitStream.write("data <<EOT\n")
1081 self.gitStream.write(labelDetails["Description"])
1082 self.gitStream.write("EOT\n\n")
1083
1084 else:
a46668fa 1085 if not self.silent:
cebdf5af
HWN
1086 print ("Tag %s does not match with change %s: files do not match."
1087 % (labelDetails["label"], change))
1f4ba1cb
SH
1088
1089 else:
a46668fa 1090 if not self.silent:
cebdf5af
HWN
1091 print ("Tag %s does not match with change %s: file count is different."
1092 % (labelDetails["label"], change))
b984733c 1093
183b8ef8 1094 def getUserCacheFilename(self):
b2d2d16a
SH
1095 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1096 return home + "/.gitp4-usercache.txt"
183b8ef8 1097
b607e71e 1098 def getUserMapFromPerforceServer(self):
ebd81168
SH
1099 if self.userMapFromPerforceServer:
1100 return
b984733c
SH
1101 self.users = {}
1102
1103 for output in p4CmdList("users"):
1104 if not output.has_key("User"):
1105 continue
1106 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1107
183b8ef8
HWN
1108
1109 s = ''
1110 for (key, val) in self.users.items():
1111 s += "%s\t%s\n" % (key, val)
1112
1113 open(self.getUserCacheFilename(), "wb").write(s)
ebd81168 1114 self.userMapFromPerforceServer = True
b607e71e
SH
1115
1116 def loadUserMapFromCache(self):
1117 self.users = {}
ebd81168 1118 self.userMapFromPerforceServer = False
b607e71e 1119 try:
183b8ef8 1120 cache = open(self.getUserCacheFilename(), "rb")
b607e71e
SH
1121 lines = cache.readlines()
1122 cache.close()
1123 for line in lines:
b25b2065 1124 entry = line.strip().split("\t")
b607e71e
SH
1125 self.users[entry[0]] = entry[1]
1126 except IOError:
1127 self.getUserMapFromPerforceServer()
1128
1f4ba1cb
SH
1129 def getLabels(self):
1130 self.labels = {}
1131
6326aa58 1132 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
10c3211b 1133 if len(l) > 0 and not self.silent:
183f8436 1134 print "Finding files belonging to labels in %s" % `self.depotPaths`
01ce1fe9
SH
1135
1136 for output in l:
1f4ba1cb
SH
1137 label = output["label"]
1138 revisions = {}
1139 newestChange = 0
71b112d4
SH
1140 if self.verbose:
1141 print "Querying files for label %s" % label
6326aa58
HWN
1142 for file in p4CmdList("files "
1143 + ' '.join (["%s...@%s" % (p, label)
1144 for p in self.depotPaths])):
1f4ba1cb
SH
1145 revisions[file["depotFile"]] = file["rev"]
1146 change = int(file["change"])
1147 if change > newestChange:
1148 newestChange = change
1149
9bda3a85
SH
1150 self.labels[newestChange] = [output, revisions]
1151
1152 if self.verbose:
1153 print "Label changes: %s" % self.labels.keys()
1f4ba1cb 1154
86dff6b6
HWN
1155 def guessProjectName(self):
1156 for p in self.depotPaths:
6e5295c4
SH
1157 if p.endswith("/"):
1158 p = p[:-1]
1159 p = p[p.strip().rfind("/") + 1:]
1160 if not p.endswith("/"):
1161 p += "/"
1162 return p
86dff6b6 1163
4b97ffb1 1164 def getBranchMapping(self):
6555b2cc
SH
1165 lostAndFoundBranches = set()
1166
4b97ffb1
SH
1167 for info in p4CmdList("branches"):
1168 details = p4Cmd("branch -o %s" % info["branch"])
1169 viewIdx = 0
1170 while details.has_key("View%s" % viewIdx):
1171 paths = details["View%s" % viewIdx].split(" ")
1172 viewIdx = viewIdx + 1
1173 # require standard //depot/foo/... //depot/bar/... mapping
1174 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1175 continue
1176 source = paths[0]
1177 destination = paths[1]
6509e19c
SH
1178 ## HACK
1179 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1180 source = source[len(self.depotPaths[0]):-4]
1181 destination = destination[len(self.depotPaths[0]):-4]
6555b2cc 1182
1a2edf4e
SH
1183 if destination in self.knownBranches:
1184 if not self.silent:
1185 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1186 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1187 continue
1188
6555b2cc
SH
1189 self.knownBranches[destination] = source
1190
1191 lostAndFoundBranches.discard(destination)
1192
29bdbac1 1193 if source not in self.knownBranches:
6555b2cc
SH
1194 lostAndFoundBranches.add(source)
1195
1196
1197 for branch in lostAndFoundBranches:
1198 self.knownBranches[branch] = branch
29bdbac1 1199
38f9f5ec
SH
1200 def getBranchMappingFromGitBranches(self):
1201 branches = p4BranchesInGit(self.importIntoRemotes)
1202 for branch in branches.keys():
1203 if branch == "master":
1204 branch = "main"
1205 else:
1206 branch = branch[len(self.projectName):]
1207 self.knownBranches[branch] = branch
1208
29bdbac1 1209 def listExistingP4GitBranches(self):
144ff46b
SH
1210 # branches holds mapping from name to commit
1211 branches = p4BranchesInGit(self.importIntoRemotes)
1212 self.p4BranchesInGit = branches.keys()
1213 for branch in branches.keys():
1214 self.initialParents[self.refPrefix + branch] = branches[branch]
4b97ffb1 1215
bb6e09b2
HWN
1216 def updateOptionDict(self, d):
1217 option_keys = {}
1218 if self.keepRepoPath:
1219 option_keys['keepRepoPath'] = 1
1220
1221 d["options"] = ' '.join(sorted(option_keys.keys()))
1222
1223 def readOptions(self, d):
1224 self.keepRepoPath = (d.has_key('options')
1225 and ('keepRepoPath' in d['options']))
6326aa58 1226
8134f69c
SH
1227 def gitRefForBranch(self, branch):
1228 if branch == "main":
1229 return self.refPrefix + "master"
1230
1231 if len(branch) <= 0:
1232 return branch
1233
1234 return self.refPrefix + self.projectName + branch
1235
1ca3d710
SH
1236 def gitCommitByP4Change(self, ref, change):
1237 if self.verbose:
1238 print "looking in ref " + ref + " for change %s using bisect..." % change
1239
1240 earliestCommit = ""
1241 latestCommit = parseRevision(ref)
1242
1243 while True:
1244 if self.verbose:
1245 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1246 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1247 if len(next) == 0:
1248 if self.verbose:
1249 print "argh"
1250 return ""
1251 log = extractLogMessageFromGitCommit(next)
1252 settings = extractSettingsGitLog(log)
1253 currentChange = int(settings['change'])
1254 if self.verbose:
1255 print "current change %s" % currentChange
1256
1257 if currentChange == change:
1258 if self.verbose:
1259 print "found %s" % next
1260 return next
1261
1262 if currentChange < change:
1263 earliestCommit = "^%s" % next
1264 else:
1265 latestCommit = "%s" % next
1266
1267 return ""
1268
1269 def importNewBranch(self, branch, maxChange):
1270 # make fast-import flush all changes to disk and update the refs using the checkpoint
1271 # command so that we can try to find the branch parent in the git history
1272 self.gitStream.write("checkpoint\n\n");
1273 self.gitStream.flush();
1274 branchPrefix = self.depotPaths[0] + branch + "/"
1275 range = "@1,%s" % maxChange
1276 #print "prefix" + branchPrefix
1277 changes = p4ChangesForPaths([branchPrefix], range)
1278 if len(changes) <= 0:
1279 return False
1280 firstChange = changes[0]
1281 #print "first change in branch: %s" % firstChange
1282 sourceBranch = self.knownBranches[branch]
1283 sourceDepotPath = self.depotPaths[0] + sourceBranch
1284 sourceRef = self.gitRefForBranch(sourceBranch)
1285 #print "source " + sourceBranch
1286
1287 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1288 #print "branch parent: %s" % branchParentChange
1289 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1290 if len(gitParent) > 0:
1291 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1292 #print "parent git commit: %s" % gitParent
1293
1294 self.importChanges(changes)
1295 return True
1296
e87f37ae
SH
1297 def importChanges(self, changes):
1298 cnt = 1
1299 for change in changes:
1300 description = p4Cmd("describe %s" % change)
1301 self.updateOptionDict(description)
1302
1303 if not self.silent:
1304 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1305 sys.stdout.flush()
1306 cnt = cnt + 1
1307
1308 try:
1309 if self.detectBranches:
1310 branches = self.splitFilesIntoBranches(description)
1311 for branch in branches.keys():
1312 ## HACK --hwn
1313 branchPrefix = self.depotPaths[0] + branch + "/"
1314
1315 parent = ""
1316
1317 filesForCommit = branches[branch]
1318
1319 if self.verbose:
1320 print "branch is %s" % branch
1321
1322 self.updatedBranches.add(branch)
1323
1324 if branch not in self.createdBranches:
1325 self.createdBranches.add(branch)
1326 parent = self.knownBranches[branch]
1327 if parent == branch:
1328 parent = ""
1ca3d710
SH
1329 else:
1330 fullBranch = self.projectName + branch
1331 if fullBranch not in self.p4BranchesInGit:
1332 if not self.silent:
1333 print("\n Importing new branch %s" % fullBranch);
1334 if self.importNewBranch(branch, change - 1):
1335 parent = ""
1336 self.p4BranchesInGit.append(fullBranch)
1337 if not self.silent:
1338 print("\n Resuming with change %s" % change);
1339
1340 if self.verbose:
1341 print "parent determined through known branches: %s" % parent
e87f37ae 1342
8134f69c
SH
1343 branch = self.gitRefForBranch(branch)
1344 parent = self.gitRefForBranch(parent)
e87f37ae
SH
1345
1346 if self.verbose:
1347 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1348
1349 if len(parent) == 0 and branch in self.initialParents:
1350 parent = self.initialParents[branch]
1351 del self.initialParents[branch]
1352
1353 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1354 else:
1355 files = self.extractFilesFromCommit(description)
1356 self.commit(description, files, self.branch, self.depotPaths,
1357 self.initialParent)
1358 self.initialParent = ""
1359 except IOError:
1360 print self.gitError.read()
1361 sys.exit(1)
1362
c208a243
SH
1363 def importHeadRevision(self, revision):
1364 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1365
1366 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1367 details["desc"] = ("Initial import of %s from the state at revision %s"
1368 % (' '.join(self.depotPaths), revision))
1369 details["change"] = revision
1370 newestRevision = 0
1371
1372 fileCnt = 0
1373 for info in p4CmdList("files "
1374 + ' '.join(["%s...%s"
1375 % (p, revision)
1376 for p in self.depotPaths])):
1377
1378 if info['code'] == 'error':
1379 sys.stderr.write("p4 returned an error: %s\n"
1380 % info['data'])
1381 sys.exit(1)
1382
1383
1384 change = int(info["change"])
1385 if change > newestRevision:
1386 newestRevision = change
1387
1388 if info["action"] == "delete":
1389 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1390 #fileCnt = fileCnt + 1
1391 continue
1392
1393 for prop in ["depotFile", "rev", "action", "type" ]:
1394 details["%s%s" % (prop, fileCnt)] = info[prop]
1395
1396 fileCnt = fileCnt + 1
1397
1398 details["change"] = newestRevision
1399 self.updateOptionDict(details)
1400 try:
1401 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1402 except IOError:
1403 print "IO error with git fast-import. Is your git version recent enough?"
1404 print self.gitError.read()
1405
1406
3a70cdfa
TAL
1407 def getClientSpec(self):
1408 specList = p4CmdList( "client -o" )
1409 temp = {}
1410 for entry in specList:
1411 for k,v in entry.iteritems():
1412 if k.startswith("View"):
1413 if v.startswith('"'):
1414 start = 1
1415 else:
1416 start = 0
1417 index = v.find("...")
1418 v = v[start:index]
1419 if v.startswith("-"):
1420 v = v[1:]
1421 temp[v] = -len(v)
1422 else:
1423 temp[v] = len(v)
1424 self.clientSpecDirs = temp.items()
1425 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1426
b984733c 1427 def run(self, args):
6326aa58 1428 self.depotPaths = []
179caebf
SH
1429 self.changeRange = ""
1430 self.initialParent = ""
6326aa58 1431 self.previousDepotPaths = []
ce6f33c8 1432
29bdbac1
SH
1433 # map from branch depot path to parent branch
1434 self.knownBranches = {}
1435 self.initialParents = {}
5ca44617 1436 self.hasOrigin = originP4BranchesExist()
a43ff00c
SH
1437 if not self.syncWithOrigin:
1438 self.hasOrigin = False
29bdbac1 1439
a028a98e
SH
1440 if self.importIntoRemotes:
1441 self.refPrefix = "refs/remotes/p4/"
1442 else:
db775559 1443 self.refPrefix = "refs/heads/p4/"
a028a98e 1444
cebdf5af
HWN
1445 if self.syncWithOrigin and self.hasOrigin:
1446 if not self.silent:
1447 print "Syncing with origin first by calling git fetch origin"
1448 system("git fetch origin")
10f880f8 1449
569d1bd4 1450 if len(self.branch) == 0:
db775559 1451 self.branch = self.refPrefix + "master"
a028a98e 1452 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
48df6fd8 1453 system("git update-ref %s refs/heads/p4" % self.branch)
48df6fd8 1454 system("git branch -D p4");
faf1bd20 1455 # create it /after/ importing, when master exists
0058a33a 1456 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
a3c55c09 1457 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
967f72e2 1458
3cafb7d8 1459 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
3a70cdfa
TAL
1460 self.getClientSpec()
1461
6a49f8e2
HWN
1462 # TODO: should always look at previous commits,
1463 # merge with previous imports, if possible.
1464 if args == []:
d414c74a 1465 if self.hasOrigin:
5ca44617 1466 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
abcd790f
SH
1467 self.listExistingP4GitBranches()
1468
1469 if len(self.p4BranchesInGit) > 1:
1470 if not self.silent:
1471 print "Importing from/into multiple branches"
1472 self.detectBranches = True
967f72e2 1473
29bdbac1
SH
1474 if self.verbose:
1475 print "branches: %s" % self.p4BranchesInGit
1476
1477 p4Change = 0
1478 for branch in self.p4BranchesInGit:
cebdf5af 1479 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
bb6e09b2
HWN
1480
1481 settings = extractSettingsGitLog(logMsg)
29bdbac1 1482
bb6e09b2
HWN
1483 self.readOptions(settings)
1484 if (settings.has_key('depot-paths')
1485 and settings.has_key ('change')):
1486 change = int(settings['change']) + 1
29bdbac1
SH
1487 p4Change = max(p4Change, change)
1488
bb6e09b2
HWN
1489 depotPaths = sorted(settings['depot-paths'])
1490 if self.previousDepotPaths == []:
6326aa58 1491 self.previousDepotPaths = depotPaths
29bdbac1 1492 else:
6326aa58
HWN
1493 paths = []
1494 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
583e1707 1495 for i in range(0, min(len(cur), len(prev))):
6326aa58 1496 if cur[i] <> prev[i]:
583e1707 1497 i = i - 1
6326aa58
HWN
1498 break
1499
583e1707 1500 paths.append (cur[:i + 1])
6326aa58
HWN
1501
1502 self.previousDepotPaths = paths
29bdbac1
SH
1503
1504 if p4Change > 0:
bb6e09b2 1505 self.depotPaths = sorted(self.previousDepotPaths)
d5904674 1506 self.changeRange = "@%s,#head" % p4Change
330f53b8
SH
1507 if not self.detectBranches:
1508 self.initialParent = parseRevision(self.branch)
341dc1c1 1509 if not self.silent and not self.detectBranches:
967f72e2 1510 print "Performing incremental import into %s git branch" % self.branch
569d1bd4 1511
f9162f6a
SH
1512 if not self.branch.startswith("refs/"):
1513 self.branch = "refs/heads/" + self.branch
179caebf 1514
6326aa58 1515 if len(args) == 0 and self.depotPaths:
b984733c 1516 if not self.silent:
6326aa58 1517 print "Depot paths: %s" % ' '.join(self.depotPaths)
b984733c 1518 else:
6326aa58 1519 if self.depotPaths and self.depotPaths != args:
cebdf5af 1520 print ("previous import used depot path %s and now %s was specified. "
6326aa58
HWN
1521 "This doesn't work!" % (' '.join (self.depotPaths),
1522 ' '.join (args)))
b984733c 1523 sys.exit(1)
6326aa58 1524
bb6e09b2 1525 self.depotPaths = sorted(args)
b984733c 1526
1c49fc19 1527 revision = ""
b984733c 1528 self.users = {}
b984733c 1529
6326aa58
HWN
1530 newPaths = []
1531 for p in self.depotPaths:
1532 if p.find("@") != -1:
1533 atIdx = p.index("@")
1534 self.changeRange = p[atIdx:]
1535 if self.changeRange == "@all":
1536 self.changeRange = ""
6a49f8e2 1537 elif ',' not in self.changeRange:
1c49fc19 1538 revision = self.changeRange
6326aa58 1539 self.changeRange = ""
7fcff9de 1540 p = p[:atIdx]
6326aa58
HWN
1541 elif p.find("#") != -1:
1542 hashIdx = p.index("#")
1c49fc19 1543 revision = p[hashIdx:]
7fcff9de 1544 p = p[:hashIdx]
6326aa58 1545 elif self.previousDepotPaths == []:
1c49fc19 1546 revision = "#head"
6326aa58
HWN
1547
1548 p = re.sub ("\.\.\.$", "", p)
1549 if not p.endswith("/"):
1550 p += "/"
1551
1552 newPaths.append(p)
1553
1554 self.depotPaths = newPaths
1555
b984733c 1556
b607e71e 1557 self.loadUserMapFromCache()
cb53e1f8
SH
1558 self.labels = {}
1559 if self.detectLabels:
1560 self.getLabels();
b984733c 1561
4b97ffb1 1562 if self.detectBranches:
df450923
SH
1563 ## FIXME - what's a P4 projectName ?
1564 self.projectName = self.guessProjectName()
1565
38f9f5ec
SH
1566 if self.hasOrigin:
1567 self.getBranchMappingFromGitBranches()
1568 else:
1569 self.getBranchMapping()
29bdbac1
SH
1570 if self.verbose:
1571 print "p4-git branches: %s" % self.p4BranchesInGit
1572 print "initial parents: %s" % self.initialParents
1573 for b in self.p4BranchesInGit:
1574 if b != "master":
6326aa58
HWN
1575
1576 ## FIXME
29bdbac1
SH
1577 b = b[len(self.projectName):]
1578 self.createdBranches.add(b)
4b97ffb1 1579
f291b4e3 1580 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
b984733c 1581
cebdf5af 1582 importProcess = subprocess.Popen(["git", "fast-import"],
6326aa58
HWN
1583 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1584 stderr=subprocess.PIPE);
08483580
SH
1585 self.gitOutput = importProcess.stdout
1586 self.gitStream = importProcess.stdin
1587 self.gitError = importProcess.stderr
b984733c 1588
1c49fc19 1589 if revision:
c208a243 1590 self.importHeadRevision(revision)
b984733c
SH
1591 else:
1592 changes = []
1593
0828ab14 1594 if len(self.changesFile) > 0:
b984733c
SH
1595 output = open(self.changesFile).readlines()
1596 changeSet = Set()
1597 for line in output:
1598 changeSet.add(int(line))
1599
1600 for change in changeSet:
1601 changes.append(change)
1602
1603 changes.sort()
1604 else:
29bdbac1 1605 if self.verbose:
86dff6b6 1606 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
6326aa58 1607 self.changeRange)
4f6432d8 1608 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
b984733c 1609
01a9c9c5 1610 if len(self.maxChanges) > 0:
7fcff9de 1611 changes = changes[:min(int(self.maxChanges), len(changes))]
01a9c9c5 1612
b984733c 1613 if len(changes) == 0:
0828ab14 1614 if not self.silent:
341dc1c1 1615 print "No changes to import!"
1f52af6c 1616 return True
b984733c 1617
a9d1a27a
SH
1618 if not self.silent and not self.detectBranches:
1619 print "Import destination: %s" % self.branch
1620
341dc1c1
SH
1621 self.updatedBranches = set()
1622
e87f37ae 1623 self.importChanges(changes)
b984733c 1624
341dc1c1
SH
1625 if not self.silent:
1626 print ""
1627 if len(self.updatedBranches) > 0:
1628 sys.stdout.write("Updated branches: ")
1629 for b in self.updatedBranches:
1630 sys.stdout.write("%s " % b)
1631 sys.stdout.write("\n")
b984733c 1632
b984733c 1633 self.gitStream.close()
29bdbac1
SH
1634 if importProcess.wait() != 0:
1635 die("fast-import failed: %s" % self.gitError.read())
b984733c
SH
1636 self.gitOutput.close()
1637 self.gitError.close()
1638
b984733c
SH
1639 return True
1640
01ce1fe9
SH
1641class P4Rebase(Command):
1642 def __init__(self):
1643 Command.__init__(self)
01265103 1644 self.options = [ ]
cebdf5af
HWN
1645 self.description = ("Fetches the latest revision from perforce and "
1646 + "rebases the current work (branch) against it")
68c42153 1647 self.verbose = False
01ce1fe9
SH
1648
1649 def run(self, args):
1650 sync = P4Sync()
1651 sync.run([])
d7e3868c 1652
14594f4b
SH
1653 return self.rebase()
1654
1655 def rebase(self):
36ee4ee4
SH
1656 if os.system("git update-index --refresh") != 0:
1657 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.");
1658 if len(read_pipe("git diff-index HEAD --")) > 0:
1659 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1660
d7e3868c
SH
1661 [upstream, settings] = findUpstreamBranchPoint()
1662 if len(upstream) == 0:
1663 die("Cannot find upstream branchpoint for rebase")
1664
1665 # the branchpoint may be p4/foo~3, so strip off the parent
1666 upstream = re.sub("~[0-9]+$", "", upstream)
1667
1668 print "Rebasing the current branch onto %s" % upstream
b25b2065 1669 oldHead = read_pipe("git rev-parse HEAD").strip()
d7e3868c 1670 system("git rebase %s" % upstream)
1f52af6c 1671 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
01ce1fe9
SH
1672 return True
1673
f9a3a4f7
SH
1674class P4Clone(P4Sync):
1675 def __init__(self):
1676 P4Sync.__init__(self)
1677 self.description = "Creates a new git repository and imports from Perforce into it"
bb6e09b2 1678 self.usage = "usage: %prog [options] //depot/path[@revRange]"
354081d5 1679 self.options += [
bb6e09b2
HWN
1680 optparse.make_option("--destination", dest="cloneDestination",
1681 action='store', default=None,
354081d5
TT
1682 help="where to leave result of the clone"),
1683 optparse.make_option("-/", dest="cloneExclude",
1684 action="append", type="string",
1685 help="exclude depot path")
1686 ]
bb6e09b2 1687 self.cloneDestination = None
f9a3a4f7 1688 self.needsGit = False
f9a3a4f7 1689
354081d5
TT
1690 # This is required for the "append" cloneExclude action
1691 def ensure_value(self, attr, value):
1692 if not hasattr(self, attr) or getattr(self, attr) is None:
1693 setattr(self, attr, value)
1694 return getattr(self, attr)
1695
6a49f8e2
HWN
1696 def defaultDestination(self, args):
1697 ## TODO: use common prefix of args?
1698 depotPath = args[0]
1699 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1700 depotDir = re.sub("(#[^#]*)$", "", depotDir)
053d9e43 1701 depotDir = re.sub(r"\.\.\.$", "", depotDir)
6a49f8e2
HWN
1702 depotDir = re.sub(r"/$", "", depotDir)
1703 return os.path.split(depotDir)[1]
1704
f9a3a4f7
SH
1705 def run(self, args):
1706 if len(args) < 1:
1707 return False
bb6e09b2
HWN
1708
1709 if self.keepRepoPath and not self.cloneDestination:
1710 sys.stderr.write("Must specify destination for --keep-path\n")
1711 sys.exit(1)
f9a3a4f7 1712
6326aa58 1713 depotPaths = args
5e100b5c
SH
1714
1715 if not self.cloneDestination and len(depotPaths) > 1:
1716 self.cloneDestination = depotPaths[-1]
1717 depotPaths = depotPaths[:-1]
1718
354081d5 1719 self.cloneExclude = ["/"+p for p in self.cloneExclude]
6326aa58
HWN
1720 for p in depotPaths:
1721 if not p.startswith("//"):
1722 return False
f9a3a4f7 1723
bb6e09b2 1724 if not self.cloneDestination:
98ad4faf 1725 self.cloneDestination = self.defaultDestination(args)
f9a3a4f7 1726
86dff6b6 1727 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
c3bf3f13
KG
1728 if not os.path.exists(self.cloneDestination):
1729 os.makedirs(self.cloneDestination)
053fd0c1 1730 chdir(self.cloneDestination)
f9a3a4f7 1731 system("git init")
b86f7378 1732 self.gitdir = os.getcwd() + "/.git"
6326aa58 1733 if not P4Sync.run(self, depotPaths):
f9a3a4f7 1734 return False
f9a3a4f7 1735 if self.branch != "master":
8f9b2e08
SH
1736 if gitBranchExists("refs/remotes/p4/master"):
1737 system("git branch master refs/remotes/p4/master")
1738 system("git checkout -f")
1739 else:
1740 print "Could not detect main branch. No checkout/master branch created."
86dff6b6 1741
f9a3a4f7
SH
1742 return True
1743
09d89de2
SH
1744class P4Branches(Command):
1745 def __init__(self):
1746 Command.__init__(self)
1747 self.options = [ ]
1748 self.description = ("Shows the git branches that hold imports and their "
1749 + "corresponding perforce depot paths")
1750 self.verbose = False
1751
1752 def run(self, args):
5ca44617
SH
1753 if originP4BranchesExist():
1754 createOrUpdateBranchesFromOrigin()
1755
09d89de2
SH
1756 cmdline = "git rev-parse --symbolic "
1757 cmdline += " --remotes"
1758
1759 for line in read_pipe_lines(cmdline):
1760 line = line.strip()
1761
1762 if not line.startswith('p4/') or line == "p4/HEAD":
1763 continue
1764 branch = line
1765
1766 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1767 settings = extractSettingsGitLog(log)
1768
1769 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1770 return True
1771
b984733c
SH
1772class HelpFormatter(optparse.IndentedHelpFormatter):
1773 def __init__(self):
1774 optparse.IndentedHelpFormatter.__init__(self)
1775
1776 def format_description(self, description):
1777 if description:
1778 return description + "\n"
1779 else:
1780 return ""
4f5cf76a 1781
86949eef
SH
1782def printUsage(commands):
1783 print "usage: %s <command> [options]" % sys.argv[0]
1784 print ""
1785 print "valid commands: %s" % ", ".join(commands)
1786 print ""
1787 print "Try %s <command> --help for command specific help." % sys.argv[0]
1788 print ""
1789
1790commands = {
b86f7378
HWN
1791 "debug" : P4Debug,
1792 "submit" : P4Submit,
a9834f58 1793 "commit" : P4Submit,
b86f7378
HWN
1794 "sync" : P4Sync,
1795 "rebase" : P4Rebase,
1796 "clone" : P4Clone,
09d89de2
SH
1797 "rollback" : P4RollBack,
1798 "branches" : P4Branches
86949eef
SH
1799}
1800
86949eef 1801
bb6e09b2
HWN
1802def main():
1803 if len(sys.argv[1:]) == 0:
1804 printUsage(commands.keys())
1805 sys.exit(2)
4f5cf76a 1806
bb6e09b2
HWN
1807 cmd = ""
1808 cmdName = sys.argv[1]
1809 try:
b86f7378
HWN
1810 klass = commands[cmdName]
1811 cmd = klass()
bb6e09b2
HWN
1812 except KeyError:
1813 print "unknown command %s" % cmdName
1814 print ""
1815 printUsage(commands.keys())
1816 sys.exit(2)
1817
1818 options = cmd.options
b86f7378 1819 cmd.gitdir = os.environ.get("GIT_DIR", None)
bb6e09b2
HWN
1820
1821 args = sys.argv[2:]
1822
1823 if len(options) > 0:
1824 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1825
1826 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1827 options,
1828 description = cmd.description,
1829 formatter = HelpFormatter())
1830
1831 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1832 global verbose
1833 verbose = cmd.verbose
1834 if cmd.needsGit:
b86f7378
HWN
1835 if cmd.gitdir == None:
1836 cmd.gitdir = os.path.abspath(".git")
1837 if not isValidGitDir(cmd.gitdir):
1838 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1839 if os.path.exists(cmd.gitdir):
bb6e09b2
HWN
1840 cdup = read_pipe("git rev-parse --show-cdup").strip()
1841 if len(cdup) > 0:
053fd0c1 1842 chdir(cdup);
e20a9e53 1843
b86f7378
HWN
1844 if not isValidGitDir(cmd.gitdir):
1845 if isValidGitDir(cmd.gitdir + "/.git"):
1846 cmd.gitdir += "/.git"
bb6e09b2 1847 else:
b86f7378 1848 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
e20a9e53 1849
b86f7378 1850 os.environ["GIT_DIR"] = cmd.gitdir
86949eef 1851
bb6e09b2
HWN
1852 if not cmd.run(args):
1853 parser.print_help()
4f5cf76a 1854
4f5cf76a 1855
bb6e09b2
HWN
1856if __name__ == '__main__':
1857 main()