3 # git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
5 # Author: Simon Hausmann <simon@lst.de>
6 # Copyright: 2007 Simon Hausmann <simon@lst.de>
8 # License: MIT <http://www.opensource.org/licenses/mit-license.php>
11 import optparse, sys, os, marshal, popen2, subprocess, shelve
12 import tempfile, getopt, sha, os.path, time, platform
20 def p4_build_cmd(cmd):
21 """Build a suitable p4 command line.
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.
27 real_cmd = "%s %s" % ("p4", cmd)
36 sys.stderr.write(msg + "\n")
39 def write_pipe(c, str):
41 sys.stderr.write('Writing pipe: %s\n' % c)
43 pipe = os.popen(c, 'w')
46 die('Command failed: %s' % c)
50 def read_pipe(c, ignore_error=False):
52 sys.stderr.write('Reading pipe: %s\n' % c)
54 pipe = os.popen(c, 'rb')
56 if pipe.close() and not ignore_error:
57 die('Command failed: %s' % c)
62 def read_pipe_lines(c):
64 sys.stderr.write('Reading pipe: %s\n' % c)
65 ## todo: check return status
66 pipe = os.popen(c, 'rb')
67 val = pipe.readlines()
69 die('Command failed: %s' % c)
73 def p4_read_pipe_lines(c):
74 """Specifically invoke p4 on the command supplied. """
75 real_cmd = p4_build_cmd(c)
76 return read_pipe_lines(real_cmd)
80 sys.stderr.write("executing %s\n" % cmd)
81 if os.system(cmd) != 0:
82 die("command failed: %s" % cmd)
85 """Specifically invoke p4 as the system command. """
86 real_cmd = p4_build_cmd(cmd)
87 return system(real_cmd)
90 """Determine if a Perforce 'kind' should have execute permission
92 'p4 help filetypes' gives a list of the types. If it starts with 'x',
93 or x follows one of a few letters. Otherwise, if there is an 'x' after
94 a plus sign, it is also executable"""
95 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
97 def setP4ExecBit(file, mode):
98 # Reopens an already open file and changes the execute bit to match
99 # the execute bit setting in the passed in mode.
103 if not isModeExec(mode):
104 p4Type = getP4OpenedType(file)
105 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
106 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
107 if p4Type[-1] == "+":
108 p4Type = p4Type[0:-1]
110 p4_system("reopen -t %s %s" % (p4Type, file))
112 def getP4OpenedType(file):
113 # Returns the perforce file type for the given file.
115 result = read_pipe("p4 opened %s" % file)
116 match = re.match(".*\((.+)\)\r?$", result)
118 return match.group(1)
120 die("Could not determine file type for %s (result: '%s')" % (file, result))
122 def diffTreePattern():
123 # This is a simple generator for the diff tree regex pattern. This could be
124 # a class variable if this and parseDiffTreeEntry were a part of a class.
125 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
129 def parseDiffTreeEntry(entry):
130 """Parses a single diff tree entry into its component elements.
132 See git-diff-tree(1) manpage for details about the format of the diff
133 output. This method returns a dictionary with the following elements:
135 src_mode - The mode of the source file
136 dst_mode - The mode of the destination file
137 src_sha1 - The sha1 for the source file
138 dst_sha1 - The sha1 fr the destination file
139 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
140 status_score - The score for the status (applicable for 'C' and 'R'
141 statuses). This is None if there is no score.
142 src - The path for the source file.
143 dst - The path for the destination file. This is only present for
144 copy or renames. If it is not present, this is None.
146 If the pattern is not matched, None is returned."""
148 match = diffTreePattern().next().match(entry)
151 'src_mode': match.group(1),
152 'dst_mode': match.group(2),
153 'src_sha1': match.group(3),
154 'dst_sha1': match.group(4),
155 'status': match.group(5),
156 'status_score': match.group(6),
157 'src': match.group(7),
158 'dst': match.group(10)
162 def isModeExec(mode):
163 # Returns True if the given git mode represents an executable file,
165 return mode[-3:] == "755"
167 def isModeExecChanged(src_mode, dst_mode):
168 return isModeExec(src_mode) != isModeExec(dst_mode)
170 def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
171 cmd = p4_build_cmd("-G %s" % (cmd))
173 sys.stderr.write("Opening pipe: %s\n" % cmd)
175 # Use a temporary file to avoid deadlocks without
176 # subprocess.communicate(), which would put another copy
177 # of stdout into memory.
179 if stdin is not None:
180 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
181 stdin_file.write(stdin)
185 p4 = subprocess.Popen(cmd, shell=True,
187 stdout=subprocess.PIPE)
192 entry = marshal.load(p4.stdout)
199 entry["p4ExitCode"] = exitCode
205 list = p4CmdList(cmd)
211 def p4Where(depotPath):
212 if not depotPath.endswith("/"):
214 output = p4Cmd("where %s..." % depotPath)
215 if output["code"] == "error":
219 clientPath = output.get("path")
220 elif "data" in output:
221 data = output.get("data")
222 lastSpace = data.rfind(" ")
223 clientPath = data[lastSpace + 1:]
225 if clientPath.endswith("..."):
226 clientPath = clientPath[:-3]
229 def currentGitBranch():
230 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
232 def isValidGitDir(path):
233 if (os.path.exists(path + "/HEAD")
234 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
238 def parseRevision(ref):
239 return read_pipe("git rev-parse %s" % ref).strip()
241 def extractLogMessageFromGitCommit(commit):
244 ## fixme: title is first line of commit, not 1st paragraph.
246 for log in read_pipe_lines("git cat-file commit %s" % commit):
255 def extractSettingsGitLog(log):
257 for line in log.split("\n"):
259 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
263 assignments = m.group(1).split (':')
264 for a in assignments:
266 key = vals[0].strip()
267 val = ('='.join (vals[1:])).strip()
268 if val.endswith ('\"') and val.startswith('"'):
273 paths = values.get("depot-paths")
275 paths = values.get("depot-path")
277 values['depot-paths'] = paths.split(',')
280 def gitBranchExists(branch):
281 proc = subprocess.Popen(["git", "rev-parse", branch],
282 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
283 return proc.wait() == 0;
286 return read_pipe("git config %s" % key, ignore_error=True).strip()
288 def p4BranchesInGit(branchesAreInRemotes = True):
291 cmdline = "git rev-parse --symbolic "
292 if branchesAreInRemotes:
293 cmdline += " --remotes"
295 cmdline += " --branches"
297 for line in read_pipe_lines(cmdline):
300 ## only import to p4/
301 if not line.startswith('p4/') or line == "p4/HEAD":
306 branch = re.sub ("^p4/", "", line)
308 branches[branch] = parseRevision(line)
311 def findUpstreamBranchPoint(head = "HEAD"):
312 branches = p4BranchesInGit()
313 # map from depot-path to branch name
314 branchByDepotPath = {}
315 for branch in branches.keys():
316 tip = branches[branch]
317 log = extractLogMessageFromGitCommit(tip)
318 settings = extractSettingsGitLog(log)
319 if settings.has_key("depot-paths"):
320 paths = ",".join(settings["depot-paths"])
321 branchByDepotPath[paths] = "remotes/p4/" + branch
325 while parent < 65535:
326 commit = head + "~%s" % parent
327 log = extractLogMessageFromGitCommit(commit)
328 settings = extractSettingsGitLog(log)
329 if settings.has_key("depot-paths"):
330 paths = ",".join(settings["depot-paths"])
331 if branchByDepotPath.has_key(paths):
332 return [branchByDepotPath[paths], settings]
336 return ["", settings]
338 def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
340 print ("Creating/updating branch(es) in %s based on origin branch(es)"
343 originPrefix = "origin/p4/"
345 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
347 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
350 headName = line[len(originPrefix):]
351 remoteHead = localRefPrefix + headName
354 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
355 if (not original.has_key('depot-paths')
356 or not original.has_key('change')):
360 if not gitBranchExists(remoteHead):
362 print "creating %s" % remoteHead
365 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
366 if settings.has_key('change') > 0:
367 if settings['depot-paths'] == original['depot-paths']:
368 originP4Change = int(original['change'])
369 p4Change = int(settings['change'])
370 if originP4Change > p4Change:
371 print ("%s (%s) is newer than %s (%s). "
372 "Updating p4 branch from origin."
373 % (originHead, originP4Change,
374 remoteHead, p4Change))
377 print ("Ignoring: %s was imported from %s while "
378 "%s was imported from %s"
379 % (originHead, ','.join(original['depot-paths']),
380 remoteHead, ','.join(settings['depot-paths'])))
383 system("git update-ref %s %s" % (remoteHead, originHead))
385 def originP4BranchesExist():
386 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
388 def p4ChangesForPaths(depotPaths, changeRange):
390 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
391 for p in depotPaths]))
395 changeNum = line.split(" ")[1]
396 changes.append(int(changeNum))
403 self.usage = "usage: %prog [options]"
406 class P4Debug(Command):
408 Command.__init__(self)
410 optparse.make_option("--verbose", dest="verbose", action="store_true",
413 self.description = "A tool to debug the output of p4 -G."
414 self.needsGit = False
419 for output in p4CmdList(" ".join(args)):
420 print 'Element: %d' % j
425 class P4RollBack(Command):
427 Command.__init__(self)
429 optparse.make_option("--verbose", dest="verbose", action="store_true"),
430 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
432 self.description = "A tool to debug the multi-branch import. Don't use :)"
434 self.rollbackLocalBranches = False
439 maxChange = int(args[0])
441 if "p4ExitCode" in p4Cmd("changes -m 1"):
442 die("Problems executing p4");
444 if self.rollbackLocalBranches:
445 refPrefix = "refs/heads/"
446 lines = read_pipe_lines("git rev-parse --symbolic --branches")
448 refPrefix = "refs/remotes/"
449 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
452 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
454 ref = refPrefix + line
455 log = extractLogMessageFromGitCommit(ref)
456 settings = extractSettingsGitLog(log)
458 depotPaths = settings['depot-paths']
459 change = settings['change']
463 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
464 for p in depotPaths]))) == 0:
465 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
466 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
469 while change and int(change) > maxChange:
472 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
473 system("git update-ref %s \"%s^\"" % (ref, ref))
474 log = extractLogMessageFromGitCommit(ref)
475 settings = extractSettingsGitLog(log)
478 depotPaths = settings['depot-paths']
479 change = settings['change']
482 print "%s rewound to %s" % (ref, change)
486 class P4Submit(Command):
488 Command.__init__(self)
490 optparse.make_option("--verbose", dest="verbose", action="store_true"),
491 optparse.make_option("--origin", dest="origin"),
492 optparse.make_option("-M", dest="detectRename", action="store_true"),
494 self.description = "Submit changes from git to the perforce depot."
495 self.usage += " [name of git branch to submit into perforce depot]"
496 self.interactive = True
498 self.detectRename = False
500 self.isWindows = (platform.system() == "Windows")
503 if len(p4CmdList("opened ...")) > 0:
504 die("You have files opened with perforce! Close them before starting the sync.")
506 # replaces everything between 'Description:' and the next P4 submit template field with the
508 def prepareLogMessage(self, template, message):
511 inDescriptionSection = False
513 for line in template.split("\n"):
514 if line.startswith("#"):
515 result += line + "\n"
518 if inDescriptionSection:
519 if line.startswith("Files:"):
520 inDescriptionSection = False
524 if line.startswith("Description:"):
525 inDescriptionSection = True
527 for messageLine in message.split("\n"):
528 line += "\t" + messageLine + "\n"
530 result += line + "\n"
534 def prepareSubmitTemplate(self):
535 # remove lines in the Files section that show changes to files outside the depot path we're committing into
537 inFilesSection = False
538 for line in p4_read_pipe_lines("change -o"):
539 if line.endswith("\r\n"):
540 line = line[:-2] + "\n"
542 if line.startswith("\t"):
543 # path starts and ends with a tab
545 lastTab = path.rfind("\t")
547 path = path[:lastTab]
548 if not path.startswith(self.depotPath):
551 inFilesSection = False
553 if line.startswith("Files:"):
554 inFilesSection = True
560 def applyCommit(self, id):
561 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
562 diffOpts = ("", "-M")[self.detectRename]
563 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
565 filesToDelete = set()
567 filesToChangeExecBit = {}
569 diff = parseDiffTreeEntry(line)
570 modifier = diff['status']
573 p4_system("edit \"%s\"" % path)
574 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
575 filesToChangeExecBit[path] = diff['dst_mode']
576 editedFiles.add(path)
577 elif modifier == "A":
579 filesToChangeExecBit[path] = diff['dst_mode']
580 if path in filesToDelete:
581 filesToDelete.remove(path)
582 elif modifier == "D":
583 filesToDelete.add(path)
584 if path in filesToAdd:
585 filesToAdd.remove(path)
586 elif modifier == "R":
587 src, dest = diff['src'], diff['dst']
588 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
589 p4_system("edit \"%s\"" % (dest))
590 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
591 filesToChangeExecBit[dest] = diff['dst_mode']
593 editedFiles.add(dest)
594 filesToDelete.add(src)
596 die("unknown modifier %s for %s" % (modifier, path))
598 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
599 patchcmd = diffcmd + " | git apply "
600 tryPatchCmd = patchcmd + "--check -"
601 applyPatchCmd = patchcmd + "--check --apply -"
603 if os.system(tryPatchCmd) != 0:
604 print "Unfortunately applying the change failed!"
605 print "What do you want to do?"
607 while response != "s" and response != "a" and response != "w":
608 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
609 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
611 print "Skipping! Good luck with the next patches..."
612 for f in editedFiles:
613 p4_system("revert \"%s\"" % f);
617 elif response == "a":
618 os.system(applyPatchCmd)
619 if len(filesToAdd) > 0:
620 print "You may also want to call p4 add on the following files:"
621 print " ".join(filesToAdd)
622 if len(filesToDelete):
623 print "The following files should be scheduled for deletion with p4 delete:"
624 print " ".join(filesToDelete)
625 die("Please resolve and submit the conflict manually and "
626 + "continue afterwards with git-p4 submit --continue")
627 elif response == "w":
628 system(diffcmd + " > patch.txt")
629 print "Patch saved to patch.txt in %s !" % self.clientPath
630 die("Please resolve and submit the conflict manually and "
631 "continue afterwards with git-p4 submit --continue")
633 system(applyPatchCmd)
636 p4_system("add \"%s\"" % f)
637 for f in filesToDelete:
638 p4_system("revert \"%s\"" % f)
639 p4_system("delete \"%s\"" % f)
641 # Set/clear executable bits
642 for f in filesToChangeExecBit.keys():
643 mode = filesToChangeExecBit[f]
644 setP4ExecBit(f, mode)
646 logMessage = extractLogMessageFromGitCommit(id)
647 logMessage = logMessage.strip()
649 template = self.prepareSubmitTemplate()
652 submitTemplate = self.prepareLogMessage(template, logMessage)
653 if os.environ.has_key("P4DIFF"):
654 del(os.environ["P4DIFF"])
655 diff = read_pipe("p4 diff -du ...")
658 for newFile in filesToAdd:
659 newdiff += "==== new file ====\n"
660 newdiff += "--- /dev/null\n"
661 newdiff += "+++ %s\n" % newFile
662 f = open(newFile, "r")
663 for line in f.readlines():
664 newdiff += "+" + line
667 separatorLine = "######## everything below this line is just the diff #######\n"
669 [handle, fileName] = tempfile.mkstemp()
670 tmpFile = os.fdopen(handle, "w+")
672 submitTemplate = submitTemplate.replace("\n", "\r\n")
673 separatorLine = separatorLine.replace("\n", "\r\n")
674 newdiff = newdiff.replace("\n", "\r\n")
675 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
678 if platform.system() == "Windows":
679 defaultEditor = "notepad"
680 if os.environ.has_key("P4EDITOR"):
681 editor = os.environ.get("P4EDITOR")
683 editor = os.environ.get("EDITOR", defaultEditor);
684 system(editor + " " + fileName)
685 tmpFile = open(fileName, "rb")
686 message = tmpFile.read()
689 submitTemplate = message[:message.index(separatorLine)]
691 submitTemplate = submitTemplate.replace("\r\n", "\n")
693 write_pipe("p4 submit -i", submitTemplate)
695 fileName = "submit.txt"
696 file = open(fileName, "w+")
697 file.write(self.prepareLogMessage(template, logMessage))
699 print ("Perforce submit template written as %s. "
700 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
701 % (fileName, fileName))
705 self.master = currentGitBranch()
706 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
707 die("Detecting current git branch failed!")
709 self.master = args[0]
713 allowSubmit = gitConfig("git-p4.allowSubmit")
714 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
715 die("%s is not in git-p4.allowSubmit" % self.master)
717 [upstream, settings] = findUpstreamBranchPoint()
718 self.depotPath = settings['depot-paths'][0]
719 if len(self.origin) == 0:
720 self.origin = upstream
723 print "Origin branch is " + self.origin
725 if len(self.depotPath) == 0:
726 print "Internal error: cannot locate perforce depot path from existing branches"
729 self.clientPath = p4Where(self.depotPath)
731 if len(self.clientPath) == 0:
732 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
735 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
736 self.oldWorkingDirectory = os.getcwd()
738 os.chdir(self.clientPath)
739 print "Syncronizing p4 checkout..."
740 p4_system("sync ...")
745 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
746 commits.append(line.strip())
749 while len(commits) > 0:
751 commits = commits[1:]
752 self.applyCommit(commit)
753 if not self.interactive:
756 if len(commits) == 0:
757 print "All changes applied!"
758 os.chdir(self.oldWorkingDirectory)
768 class P4Sync(Command):
770 Command.__init__(self)
772 optparse.make_option("--branch", dest="branch"),
773 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
774 optparse.make_option("--changesfile", dest="changesFile"),
775 optparse.make_option("--silent", dest="silent", action="store_true"),
776 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
777 optparse.make_option("--verbose", dest="verbose", action="store_true"),
778 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
779 help="Import into refs/heads/ , not refs/remotes"),
780 optparse.make_option("--max-changes", dest="maxChanges"),
781 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
782 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
783 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
784 help="Only sync files that are included in the Perforce Client Spec")
786 self.description = """Imports from Perforce into a git repository.\n
788 //depot/my/project/ -- to import the current head
789 //depot/my/project/@all -- to import everything
790 //depot/my/project/@1,6 -- to import only from revision 1 to 6
792 (a ... is not needed in the path p4 specification, it's added implicitly)"""
794 self.usage += " //depot/path[@revRange]"
796 self.createdBranches = Set()
797 self.committedChanges = Set()
799 self.detectBranches = False
800 self.detectLabels = False
801 self.changesFile = ""
802 self.syncWithOrigin = True
804 self.importIntoRemotes = True
806 self.isWindows = (platform.system() == "Windows")
807 self.keepRepoPath = False
808 self.depotPaths = None
809 self.p4BranchesInGit = []
810 self.cloneExclude = []
811 self.useClientSpec = False
812 self.clientSpecDirs = []
814 if gitConfig("git-p4.syncFromOrigin") == "false":
815 self.syncWithOrigin = False
817 def extractFilesFromCommit(self, commit):
818 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
819 for path in self.cloneExclude]
822 while commit.has_key("depotFile%s" % fnum):
823 path = commit["depotFile%s" % fnum]
825 if [p for p in self.cloneExclude
826 if path.startswith (p)]:
829 found = [p for p in self.depotPaths
830 if path.startswith (p)]
837 file["rev"] = commit["rev%s" % fnum]
838 file["action"] = commit["action%s" % fnum]
839 file["type"] = commit["type%s" % fnum]
844 def stripRepoPath(self, path, prefixes):
845 if self.keepRepoPath:
846 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
849 if path.startswith(p):
854 def splitFilesIntoBranches(self, commit):
857 while commit.has_key("depotFile%s" % fnum):
858 path = commit["depotFile%s" % fnum]
859 found = [p for p in self.depotPaths
860 if path.startswith (p)]
867 file["rev"] = commit["rev%s" % fnum]
868 file["action"] = commit["action%s" % fnum]
869 file["type"] = commit["type%s" % fnum]
872 relPath = self.stripRepoPath(path, self.depotPaths)
874 for branch in self.knownBranches.keys():
876 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
877 if relPath.startswith(branch + "/"):
878 if branch not in branches:
879 branches[branch] = []
880 branches[branch].append(file)
885 ## Should move this out, doesn't use SELF.
886 def readP4Files(self, files):
892 for val in self.clientSpecDirs:
893 if f['path'].startswith(val[0]):
899 filesForCommit.append(f)
900 if f['action'] != 'delete':
901 filesToRead.append(f)
904 if len(filesToRead) > 0:
905 filedata = p4CmdList('-x - print',
906 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
907 for f in filesToRead]),
910 if "p4ExitCode" in filedata[0]:
911 die("Problems executing p4. Error: [%d]."
912 % (filedata[0]['p4ExitCode']));
916 while j < len(filedata):
920 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
921 text.append(filedata[j]['data'])
925 if not stat.has_key('depotFile'):
926 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
929 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
930 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
931 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
932 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
934 contents[stat['depotFile']] = text
936 for f in filesForCommit:
938 if contents.has_key(path):
939 f['data'] = contents[path]
941 return filesForCommit
943 def commit(self, details, files, branch, branchPrefixes, parent = ""):
944 epoch = details["time"]
945 author = details["user"]
948 print "commit into %s" % branch
950 # start with reading files; if that fails, we should not
954 if [p for p in branchPrefixes if f['path'].startswith(p)]:
957 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
958 files = self.readP4Files(new_files)
960 self.gitStream.write("commit %s\n" % branch)
961 # gitStream.write("mark :%s\n" % details["change"])
962 self.committedChanges.add(int(details["change"]))
964 if author not in self.users:
965 self.getUserMapFromPerforceServer()
966 if author in self.users:
967 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
969 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
971 self.gitStream.write("committer %s\n" % committer)
973 self.gitStream.write("data <<EOT\n")
974 self.gitStream.write(details["desc"])
975 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
976 % (','.join (branchPrefixes), details["change"]))
977 if len(details['options']) > 0:
978 self.gitStream.write(": options = %s" % details['options'])
979 self.gitStream.write("]\nEOT\n\n")
983 print "parent %s" % parent
984 self.gitStream.write("from %s\n" % parent)
987 if file["type"] == "apple":
988 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
991 relPath = self.stripRepoPath(file['path'], branchPrefixes)
992 if file["action"] == "delete":
993 self.gitStream.write("D %s\n" % relPath)
998 if isP4Exec(file["type"]):
1000 elif file["type"] == "symlink":
1002 # p4 print on a symlink contains "target\n", so strip it off
1005 if self.isWindows and file["type"].endswith("text"):
1006 data = data.replace("\r\n", "\n")
1008 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
1009 self.gitStream.write("data %s\n" % len(data))
1010 self.gitStream.write(data)
1011 self.gitStream.write("\n")
1013 self.gitStream.write("\n")
1015 change = int(details["change"])
1017 if self.labels.has_key(change):
1018 label = self.labels[change]
1019 labelDetails = label[0]
1020 labelRevisions = label[1]
1022 print "Change %s is labelled %s" % (change, labelDetails)
1024 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1025 for p in branchPrefixes]))
1027 if len(files) == len(labelRevisions):
1031 if info["action"] == "delete":
1033 cleanedFiles[info["depotFile"]] = info["rev"]
1035 if cleanedFiles == labelRevisions:
1036 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1037 self.gitStream.write("from %s\n" % branch)
1039 owner = labelDetails["Owner"]
1041 if author in self.users:
1042 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1044 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1045 self.gitStream.write("tagger %s\n" % tagger)
1046 self.gitStream.write("data <<EOT\n")
1047 self.gitStream.write(labelDetails["Description"])
1048 self.gitStream.write("EOT\n\n")
1052 print ("Tag %s does not match with change %s: files do not match."
1053 % (labelDetails["label"], change))
1057 print ("Tag %s does not match with change %s: file count is different."
1058 % (labelDetails["label"], change))
1060 def getUserCacheFilename(self):
1061 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1062 return home + "/.gitp4-usercache.txt"
1064 def getUserMapFromPerforceServer(self):
1065 if self.userMapFromPerforceServer:
1069 for output in p4CmdList("users"):
1070 if not output.has_key("User"):
1072 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1076 for (key, val) in self.users.items():
1077 s += "%s\t%s\n" % (key, val)
1079 open(self.getUserCacheFilename(), "wb").write(s)
1080 self.userMapFromPerforceServer = True
1082 def loadUserMapFromCache(self):
1084 self.userMapFromPerforceServer = False
1086 cache = open(self.getUserCacheFilename(), "rb")
1087 lines = cache.readlines()
1090 entry = line.strip().split("\t")
1091 self.users[entry[0]] = entry[1]
1093 self.getUserMapFromPerforceServer()
1095 def getLabels(self):
1098 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
1099 if len(l) > 0 and not self.silent:
1100 print "Finding files belonging to labels in %s" % `self.depotPaths`
1103 label = output["label"]
1107 print "Querying files for label %s" % label
1108 for file in p4CmdList("files "
1109 + ' '.join (["%s...@%s" % (p, label)
1110 for p in self.depotPaths])):
1111 revisions[file["depotFile"]] = file["rev"]
1112 change = int(file["change"])
1113 if change > newestChange:
1114 newestChange = change
1116 self.labels[newestChange] = [output, revisions]
1119 print "Label changes: %s" % self.labels.keys()
1121 def guessProjectName(self):
1122 for p in self.depotPaths:
1125 p = p[p.strip().rfind("/") + 1:]
1126 if not p.endswith("/"):
1130 def getBranchMapping(self):
1131 lostAndFoundBranches = set()
1133 for info in p4CmdList("branches"):
1134 details = p4Cmd("branch -o %s" % info["branch"])
1136 while details.has_key("View%s" % viewIdx):
1137 paths = details["View%s" % viewIdx].split(" ")
1138 viewIdx = viewIdx + 1
1139 # require standard //depot/foo/... //depot/bar/... mapping
1140 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1143 destination = paths[1]
1145 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1146 source = source[len(self.depotPaths[0]):-4]
1147 destination = destination[len(self.depotPaths[0]):-4]
1149 if destination in self.knownBranches:
1151 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1152 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1155 self.knownBranches[destination] = source
1157 lostAndFoundBranches.discard(destination)
1159 if source not in self.knownBranches:
1160 lostAndFoundBranches.add(source)
1163 for branch in lostAndFoundBranches:
1164 self.knownBranches[branch] = branch
1166 def getBranchMappingFromGitBranches(self):
1167 branches = p4BranchesInGit(self.importIntoRemotes)
1168 for branch in branches.keys():
1169 if branch == "master":
1172 branch = branch[len(self.projectName):]
1173 self.knownBranches[branch] = branch
1175 def listExistingP4GitBranches(self):
1176 # branches holds mapping from name to commit
1177 branches = p4BranchesInGit(self.importIntoRemotes)
1178 self.p4BranchesInGit = branches.keys()
1179 for branch in branches.keys():
1180 self.initialParents[self.refPrefix + branch] = branches[branch]
1182 def updateOptionDict(self, d):
1184 if self.keepRepoPath:
1185 option_keys['keepRepoPath'] = 1
1187 d["options"] = ' '.join(sorted(option_keys.keys()))
1189 def readOptions(self, d):
1190 self.keepRepoPath = (d.has_key('options')
1191 and ('keepRepoPath' in d['options']))
1193 def gitRefForBranch(self, branch):
1194 if branch == "main":
1195 return self.refPrefix + "master"
1197 if len(branch) <= 0:
1200 return self.refPrefix + self.projectName + branch
1202 def gitCommitByP4Change(self, ref, change):
1204 print "looking in ref " + ref + " for change %s using bisect..." % change
1207 latestCommit = parseRevision(ref)
1211 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1212 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1217 log = extractLogMessageFromGitCommit(next)
1218 settings = extractSettingsGitLog(log)
1219 currentChange = int(settings['change'])
1221 print "current change %s" % currentChange
1223 if currentChange == change:
1225 print "found %s" % next
1228 if currentChange < change:
1229 earliestCommit = "^%s" % next
1231 latestCommit = "%s" % next
1235 def importNewBranch(self, branch, maxChange):
1236 # make fast-import flush all changes to disk and update the refs using the checkpoint
1237 # command so that we can try to find the branch parent in the git history
1238 self.gitStream.write("checkpoint\n\n");
1239 self.gitStream.flush();
1240 branchPrefix = self.depotPaths[0] + branch + "/"
1241 range = "@1,%s" % maxChange
1242 #print "prefix" + branchPrefix
1243 changes = p4ChangesForPaths([branchPrefix], range)
1244 if len(changes) <= 0:
1246 firstChange = changes[0]
1247 #print "first change in branch: %s" % firstChange
1248 sourceBranch = self.knownBranches[branch]
1249 sourceDepotPath = self.depotPaths[0] + sourceBranch
1250 sourceRef = self.gitRefForBranch(sourceBranch)
1251 #print "source " + sourceBranch
1253 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1254 #print "branch parent: %s" % branchParentChange
1255 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1256 if len(gitParent) > 0:
1257 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1258 #print "parent git commit: %s" % gitParent
1260 self.importChanges(changes)
1263 def importChanges(self, changes):
1265 for change in changes:
1266 description = p4Cmd("describe %s" % change)
1267 self.updateOptionDict(description)
1270 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1275 if self.detectBranches:
1276 branches = self.splitFilesIntoBranches(description)
1277 for branch in branches.keys():
1279 branchPrefix = self.depotPaths[0] + branch + "/"
1283 filesForCommit = branches[branch]
1286 print "branch is %s" % branch
1288 self.updatedBranches.add(branch)
1290 if branch not in self.createdBranches:
1291 self.createdBranches.add(branch)
1292 parent = self.knownBranches[branch]
1293 if parent == branch:
1296 fullBranch = self.projectName + branch
1297 if fullBranch not in self.p4BranchesInGit:
1299 print("\n Importing new branch %s" % fullBranch);
1300 if self.importNewBranch(branch, change - 1):
1302 self.p4BranchesInGit.append(fullBranch)
1304 print("\n Resuming with change %s" % change);
1307 print "parent determined through known branches: %s" % parent
1309 branch = self.gitRefForBranch(branch)
1310 parent = self.gitRefForBranch(parent)
1313 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1315 if len(parent) == 0 and branch in self.initialParents:
1316 parent = self.initialParents[branch]
1317 del self.initialParents[branch]
1319 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1321 files = self.extractFilesFromCommit(description)
1322 self.commit(description, files, self.branch, self.depotPaths,
1324 self.initialParent = ""
1326 print self.gitError.read()
1329 def importHeadRevision(self, revision):
1330 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1332 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1333 details["desc"] = ("Initial import of %s from the state at revision %s"
1334 % (' '.join(self.depotPaths), revision))
1335 details["change"] = revision
1339 for info in p4CmdList("files "
1340 + ' '.join(["%s...%s"
1342 for p in self.depotPaths])):
1344 if info['code'] == 'error':
1345 sys.stderr.write("p4 returned an error: %s\n"
1350 change = int(info["change"])
1351 if change > newestRevision:
1352 newestRevision = change
1354 if info["action"] == "delete":
1355 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1356 #fileCnt = fileCnt + 1
1359 for prop in ["depotFile", "rev", "action", "type" ]:
1360 details["%s%s" % (prop, fileCnt)] = info[prop]
1362 fileCnt = fileCnt + 1
1364 details["change"] = newestRevision
1365 self.updateOptionDict(details)
1367 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1369 print "IO error with git fast-import. Is your git version recent enough?"
1370 print self.gitError.read()
1373 def getClientSpec(self):
1374 specList = p4CmdList( "client -o" )
1376 for entry in specList:
1377 for k,v in entry.iteritems():
1378 if k.startswith("View"):
1379 if v.startswith('"'):
1383 index = v.find("...")
1385 if v.startswith("-"):
1390 self.clientSpecDirs = temp.items()
1391 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1393 def run(self, args):
1394 self.depotPaths = []
1395 self.changeRange = ""
1396 self.initialParent = ""
1397 self.previousDepotPaths = []
1399 # map from branch depot path to parent branch
1400 self.knownBranches = {}
1401 self.initialParents = {}
1402 self.hasOrigin = originP4BranchesExist()
1403 if not self.syncWithOrigin:
1404 self.hasOrigin = False
1406 if self.importIntoRemotes:
1407 self.refPrefix = "refs/remotes/p4/"
1409 self.refPrefix = "refs/heads/p4/"
1411 if self.syncWithOrigin and self.hasOrigin:
1413 print "Syncing with origin first by calling git fetch origin"
1414 system("git fetch origin")
1416 if len(self.branch) == 0:
1417 self.branch = self.refPrefix + "master"
1418 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
1419 system("git update-ref %s refs/heads/p4" % self.branch)
1420 system("git branch -D p4");
1421 # create it /after/ importing, when master exists
1422 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
1423 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
1425 if self.useClientSpec or gitConfig("p4.useclientspec") == "true":
1426 self.getClientSpec()
1428 # TODO: should always look at previous commits,
1429 # merge with previous imports, if possible.
1432 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
1433 self.listExistingP4GitBranches()
1435 if len(self.p4BranchesInGit) > 1:
1437 print "Importing from/into multiple branches"
1438 self.detectBranches = True
1441 print "branches: %s" % self.p4BranchesInGit
1444 for branch in self.p4BranchesInGit:
1445 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
1447 settings = extractSettingsGitLog(logMsg)
1449 self.readOptions(settings)
1450 if (settings.has_key('depot-paths')
1451 and settings.has_key ('change')):
1452 change = int(settings['change']) + 1
1453 p4Change = max(p4Change, change)
1455 depotPaths = sorted(settings['depot-paths'])
1456 if self.previousDepotPaths == []:
1457 self.previousDepotPaths = depotPaths
1460 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
1461 for i in range(0, min(len(cur), len(prev))):
1462 if cur[i] <> prev[i]:
1466 paths.append (cur[:i + 1])
1468 self.previousDepotPaths = paths
1471 self.depotPaths = sorted(self.previousDepotPaths)
1472 self.changeRange = "@%s,#head" % p4Change
1473 if not self.detectBranches:
1474 self.initialParent = parseRevision(self.branch)
1475 if not self.silent and not self.detectBranches:
1476 print "Performing incremental import into %s git branch" % self.branch
1478 if not self.branch.startswith("refs/"):
1479 self.branch = "refs/heads/" + self.branch
1481 if len(args) == 0 and self.depotPaths:
1483 print "Depot paths: %s" % ' '.join(self.depotPaths)
1485 if self.depotPaths and self.depotPaths != args:
1486 print ("previous import used depot path %s and now %s was specified. "
1487 "This doesn't work!" % (' '.join (self.depotPaths),
1491 self.depotPaths = sorted(args)
1497 for p in self.depotPaths:
1498 if p.find("@") != -1:
1499 atIdx = p.index("@")
1500 self.changeRange = p[atIdx:]
1501 if self.changeRange == "@all":
1502 self.changeRange = ""
1503 elif ',' not in self.changeRange:
1504 revision = self.changeRange
1505 self.changeRange = ""
1507 elif p.find("#") != -1:
1508 hashIdx = p.index("#")
1509 revision = p[hashIdx:]
1511 elif self.previousDepotPaths == []:
1514 p = re.sub ("\.\.\.$", "", p)
1515 if not p.endswith("/"):
1520 self.depotPaths = newPaths
1523 self.loadUserMapFromCache()
1525 if self.detectLabels:
1528 if self.detectBranches:
1529 ## FIXME - what's a P4 projectName ?
1530 self.projectName = self.guessProjectName()
1533 self.getBranchMappingFromGitBranches()
1535 self.getBranchMapping()
1537 print "p4-git branches: %s" % self.p4BranchesInGit
1538 print "initial parents: %s" % self.initialParents
1539 for b in self.p4BranchesInGit:
1543 b = b[len(self.projectName):]
1544 self.createdBranches.add(b)
1546 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
1548 importProcess = subprocess.Popen(["git", "fast-import"],
1549 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1550 stderr=subprocess.PIPE);
1551 self.gitOutput = importProcess.stdout
1552 self.gitStream = importProcess.stdin
1553 self.gitError = importProcess.stderr
1556 self.importHeadRevision(revision)
1560 if len(self.changesFile) > 0:
1561 output = open(self.changesFile).readlines()
1564 changeSet.add(int(line))
1566 for change in changeSet:
1567 changes.append(change)
1572 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
1574 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
1576 if len(self.maxChanges) > 0:
1577 changes = changes[:min(int(self.maxChanges), len(changes))]
1579 if len(changes) == 0:
1581 print "No changes to import!"
1584 if not self.silent and not self.detectBranches:
1585 print "Import destination: %s" % self.branch
1587 self.updatedBranches = set()
1589 self.importChanges(changes)
1593 if len(self.updatedBranches) > 0:
1594 sys.stdout.write("Updated branches: ")
1595 for b in self.updatedBranches:
1596 sys.stdout.write("%s " % b)
1597 sys.stdout.write("\n")
1599 self.gitStream.close()
1600 if importProcess.wait() != 0:
1601 die("fast-import failed: %s" % self.gitError.read())
1602 self.gitOutput.close()
1603 self.gitError.close()
1607 class P4Rebase(Command):
1609 Command.__init__(self)
1611 self.description = ("Fetches the latest revision from perforce and "
1612 + "rebases the current work (branch) against it")
1613 self.verbose = False
1615 def run(self, args):
1619 return self.rebase()
1622 if os.system("git update-index --refresh") != 0:
1623 die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
1624 if len(read_pipe("git diff-index HEAD --")) > 0:
1625 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1627 [upstream, settings] = findUpstreamBranchPoint()
1628 if len(upstream) == 0:
1629 die("Cannot find upstream branchpoint for rebase")
1631 # the branchpoint may be p4/foo~3, so strip off the parent
1632 upstream = re.sub("~[0-9]+$", "", upstream)
1634 print "Rebasing the current branch onto %s" % upstream
1635 oldHead = read_pipe("git rev-parse HEAD").strip()
1636 system("git rebase %s" % upstream)
1637 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
1640 class P4Clone(P4Sync):
1642 P4Sync.__init__(self)
1643 self.description = "Creates a new git repository and imports from Perforce into it"
1644 self.usage = "usage: %prog [options] //depot/path[@revRange]"
1646 optparse.make_option("--destination", dest="cloneDestination",
1647 action='store', default=None,
1648 help="where to leave result of the clone"),
1649 optparse.make_option("-/", dest="cloneExclude",
1650 action="append", type="string",
1651 help="exclude depot path")
1653 self.cloneDestination = None
1654 self.needsGit = False
1656 # This is required for the "append" cloneExclude action
1657 def ensure_value(self, attr, value):
1658 if not hasattr(self, attr) or getattr(self, attr) is None:
1659 setattr(self, attr, value)
1660 return getattr(self, attr)
1662 def defaultDestination(self, args):
1663 ## TODO: use common prefix of args?
1665 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1666 depotDir = re.sub("(#[^#]*)$", "", depotDir)
1667 depotDir = re.sub(r"\.\.\.$", "", depotDir)
1668 depotDir = re.sub(r"/$", "", depotDir)
1669 return os.path.split(depotDir)[1]
1671 def run(self, args):
1675 if self.keepRepoPath and not self.cloneDestination:
1676 sys.stderr.write("Must specify destination for --keep-path\n")
1681 if not self.cloneDestination and len(depotPaths) > 1:
1682 self.cloneDestination = depotPaths[-1]
1683 depotPaths = depotPaths[:-1]
1685 self.cloneExclude = ["/"+p for p in self.cloneExclude]
1686 for p in depotPaths:
1687 if not p.startswith("//"):
1690 if not self.cloneDestination:
1691 self.cloneDestination = self.defaultDestination(args)
1693 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
1694 if not os.path.exists(self.cloneDestination):
1695 os.makedirs(self.cloneDestination)
1696 os.chdir(self.cloneDestination)
1698 self.gitdir = os.getcwd() + "/.git"
1699 if not P4Sync.run(self, depotPaths):
1701 if self.branch != "master":
1702 if gitBranchExists("refs/remotes/p4/master"):
1703 system("git branch master refs/remotes/p4/master")
1704 system("git checkout -f")
1706 print "Could not detect main branch. No checkout/master branch created."
1710 class P4Branches(Command):
1712 Command.__init__(self)
1714 self.description = ("Shows the git branches that hold imports and their "
1715 + "corresponding perforce depot paths")
1716 self.verbose = False
1718 def run(self, args):
1719 if originP4BranchesExist():
1720 createOrUpdateBranchesFromOrigin()
1722 cmdline = "git rev-parse --symbolic "
1723 cmdline += " --remotes"
1725 for line in read_pipe_lines(cmdline):
1728 if not line.startswith('p4/') or line == "p4/HEAD":
1732 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1733 settings = extractSettingsGitLog(log)
1735 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1738 class HelpFormatter(optparse.IndentedHelpFormatter):
1740 optparse.IndentedHelpFormatter.__init__(self)
1742 def format_description(self, description):
1744 return description + "\n"
1748 def printUsage(commands):
1749 print "usage: %s <command> [options]" % sys.argv[0]
1751 print "valid commands: %s" % ", ".join(commands)
1753 print "Try %s <command> --help for command specific help." % sys.argv[0]
1758 "submit" : P4Submit,
1759 "commit" : P4Submit,
1761 "rebase" : P4Rebase,
1763 "rollback" : P4RollBack,
1764 "branches" : P4Branches
1769 if len(sys.argv[1:]) == 0:
1770 printUsage(commands.keys())
1774 cmdName = sys.argv[1]
1776 klass = commands[cmdName]
1779 print "unknown command %s" % cmdName
1781 printUsage(commands.keys())
1784 options = cmd.options
1785 cmd.gitdir = os.environ.get("GIT_DIR", None)
1789 if len(options) > 0:
1790 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1792 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1794 description = cmd.description,
1795 formatter = HelpFormatter())
1797 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1799 verbose = cmd.verbose
1801 if cmd.gitdir == None:
1802 cmd.gitdir = os.path.abspath(".git")
1803 if not isValidGitDir(cmd.gitdir):
1804 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1805 if os.path.exists(cmd.gitdir):
1806 cdup = read_pipe("git rev-parse --show-cdup").strip()
1810 if not isValidGitDir(cmd.gitdir):
1811 if isValidGitDir(cmd.gitdir + "/.git"):
1812 cmd.gitdir += "/.git"
1814 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
1816 os.environ["GIT_DIR"] = cmd.gitdir
1818 if not cmd.run(args):
1822 if __name__ == '__main__':