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 " % "p4"
29 user = gitConfig("git-p4.user")
31 real_cmd += "-u %s " % user
33 password = gitConfig("git-p4.password")
35 real_cmd += "-P %s " % password
37 port = gitConfig("git-p4.port")
39 real_cmd += "-p %s " % port
41 host = gitConfig("git-p4.host")
43 real_cmd += "-h %s " % host
45 client = gitConfig("git-p4.client")
47 real_cmd += "-c %s " % client
49 real_cmd += "%s" % (cmd)
63 sys.stderr.write(msg + "\n")
66 def write_pipe(c, str):
68 sys.stderr.write('Writing pipe: %s\n' % c)
70 pipe = os.popen(c, 'w')
73 die('Command failed: %s' % c)
77 def p4_write_pipe(c, str):
78 real_cmd = p4_build_cmd(c)
79 return write_pipe(real_cmd, str)
81 def read_pipe(c, ignore_error=False):
83 sys.stderr.write('Reading pipe: %s\n' % c)
85 pipe = os.popen(c, 'rb')
87 if pipe.close() and not ignore_error:
88 die('Command failed: %s' % c)
92 def p4_read_pipe(c, ignore_error=False):
93 real_cmd = p4_build_cmd(c)
94 return read_pipe(real_cmd, ignore_error)
96 def read_pipe_lines(c):
98 sys.stderr.write('Reading pipe: %s\n' % c)
99 ## todo: check return status
100 pipe = os.popen(c, 'rb')
101 val = pipe.readlines()
103 die('Command failed: %s' % c)
107 def p4_read_pipe_lines(c):
108 """Specifically invoke p4 on the command supplied. """
109 real_cmd = p4_build_cmd(c)
110 return read_pipe_lines(real_cmd)
114 sys.stderr.write("executing %s\n" % cmd)
115 if os.system(cmd) != 0:
116 die("command failed: %s" % cmd)
119 """Specifically invoke p4 as the system command. """
120 real_cmd = p4_build_cmd(cmd)
121 return system(real_cmd)
124 """Determine if a Perforce 'kind' should have execute permission
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)
131 def 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.
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]
144 p4_system("reopen -t %s %s" % (p4Type, file))
146 def getP4OpenedType(file):
147 # Returns the perforce file type for the given file.
149 result = p4_read_pipe("opened %s" % file)
150 match = re.match(".*\((.+)\)\r?$", result)
152 return match.group(1)
154 die("Could not determine file type for %s (result: '%s')" % (file, result))
156 def 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(.*))|$)')
163 def parseDiffTreeEntry(entry):
164 """Parses a single diff tree entry into its component elements.
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:
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.
180 If the pattern is not matched, None is returned."""
182 match = diffTreePattern().next().match(entry)
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)
196 def isModeExec(mode):
197 # Returns True if the given git mode represents an executable file,
199 return mode[-3:] == "755"
201 def isModeExecChanged(src_mode, dst_mode):
202 return isModeExec(src_mode) != isModeExec(dst_mode)
204 def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
205 cmd = p4_build_cmd("-G %s" % (cmd))
207 sys.stderr.write("Opening pipe: %s\n" % cmd)
209 # Use a temporary file to avoid deadlocks without
210 # subprocess.communicate(), which would put another copy
211 # of stdout into memory.
213 if stdin is not None:
214 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
215 stdin_file.write(stdin)
219 p4 = subprocess.Popen(cmd, shell=True,
221 stdout=subprocess.PIPE)
226 entry = marshal.load(p4.stdout)
233 entry["p4ExitCode"] = exitCode
239 list = p4CmdList(cmd)
245 def p4Where(depotPath):
246 if not depotPath.endswith("/"):
248 depotPath = depotPath + "..."
249 outputList = p4CmdList("where %s" % depotPath)
251 for entry in outputList:
252 if entry["depotFile"] == depotPath:
257 if output["code"] == "error":
261 clientPath = output.get("path")
262 elif "data" in output:
263 data = output.get("data")
264 lastSpace = data.rfind(" ")
265 clientPath = data[lastSpace + 1:]
267 if clientPath.endswith("..."):
268 clientPath = clientPath[:-3]
271 def currentGitBranch():
272 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
274 def isValidGitDir(path):
275 if (os.path.exists(path + "/HEAD")
276 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
280 def parseRevision(ref):
281 return read_pipe("git rev-parse %s" % ref).strip()
283 def extractLogMessageFromGitCommit(commit):
286 ## fixme: title is first line of commit, not 1st paragraph.
288 for log in read_pipe_lines("git cat-file commit %s" % commit):
297 def extractSettingsGitLog(log):
299 for line in log.split("\n"):
301 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
305 assignments = m.group(1).split (':')
306 for a in assignments:
308 key = vals[0].strip()
309 val = ('='.join (vals[1:])).strip()
310 if val.endswith ('\"') and val.startswith('"'):
315 paths = values.get("depot-paths")
317 paths = values.get("depot-path")
319 values['depot-paths'] = paths.split(',')
322 def gitBranchExists(branch):
323 proc = subprocess.Popen(["git", "rev-parse", branch],
324 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
325 return proc.wait() == 0;
329 if not _gitConfig.has_key(key):
330 _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
331 return _gitConfig[key]
333 def p4BranchesInGit(branchesAreInRemotes = True):
336 cmdline = "git rev-parse --symbolic "
337 if branchesAreInRemotes:
338 cmdline += " --remotes"
340 cmdline += " --branches"
342 for line in read_pipe_lines(cmdline):
345 ## only import to p4/
346 if not line.startswith('p4/') or line == "p4/HEAD":
351 branch = re.sub ("^p4/", "", line)
353 branches[branch] = parseRevision(line)
356 def findUpstreamBranchPoint(head = "HEAD"):
357 branches = p4BranchesInGit()
358 # map from depot-path to branch name
359 branchByDepotPath = {}
360 for branch in branches.keys():
361 tip = branches[branch]
362 log = extractLogMessageFromGitCommit(tip)
363 settings = extractSettingsGitLog(log)
364 if settings.has_key("depot-paths"):
365 paths = ",".join(settings["depot-paths"])
366 branchByDepotPath[paths] = "remotes/p4/" + branch
370 while parent < 65535:
371 commit = head + "~%s" % parent
372 log = extractLogMessageFromGitCommit(commit)
373 settings = extractSettingsGitLog(log)
374 if settings.has_key("depot-paths"):
375 paths = ",".join(settings["depot-paths"])
376 if branchByDepotPath.has_key(paths):
377 return [branchByDepotPath[paths], settings]
381 return ["", settings]
383 def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
385 print ("Creating/updating branch(es) in %s based on origin branch(es)"
388 originPrefix = "origin/p4/"
390 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
392 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
395 headName = line[len(originPrefix):]
396 remoteHead = localRefPrefix + headName
399 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
400 if (not original.has_key('depot-paths')
401 or not original.has_key('change')):
405 if not gitBranchExists(remoteHead):
407 print "creating %s" % remoteHead
410 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
411 if settings.has_key('change') > 0:
412 if settings['depot-paths'] == original['depot-paths']:
413 originP4Change = int(original['change'])
414 p4Change = int(settings['change'])
415 if originP4Change > p4Change:
416 print ("%s (%s) is newer than %s (%s). "
417 "Updating p4 branch from origin."
418 % (originHead, originP4Change,
419 remoteHead, p4Change))
422 print ("Ignoring: %s was imported from %s while "
423 "%s was imported from %s"
424 % (originHead, ','.join(original['depot-paths']),
425 remoteHead, ','.join(settings['depot-paths'])))
428 system("git update-ref %s %s" % (remoteHead, originHead))
430 def originP4BranchesExist():
431 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
433 def p4ChangesForPaths(depotPaths, changeRange):
435 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
436 for p in depotPaths]))
440 changeNum = line.split(" ")[1]
441 changes.append(int(changeNum))
448 self.usage = "usage: %prog [options]"
451 class P4Debug(Command):
453 Command.__init__(self)
455 optparse.make_option("--verbose", dest="verbose", action="store_true",
458 self.description = "A tool to debug the output of p4 -G."
459 self.needsGit = False
464 for output in p4CmdList(" ".join(args)):
465 print 'Element: %d' % j
470 class P4RollBack(Command):
472 Command.__init__(self)
474 optparse.make_option("--verbose", dest="verbose", action="store_true"),
475 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
477 self.description = "A tool to debug the multi-branch import. Don't use :)"
479 self.rollbackLocalBranches = False
484 maxChange = int(args[0])
486 if "p4ExitCode" in p4Cmd("changes -m 1"):
487 die("Problems executing p4");
489 if self.rollbackLocalBranches:
490 refPrefix = "refs/heads/"
491 lines = read_pipe_lines("git rev-parse --symbolic --branches")
493 refPrefix = "refs/remotes/"
494 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
497 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
499 ref = refPrefix + line
500 log = extractLogMessageFromGitCommit(ref)
501 settings = extractSettingsGitLog(log)
503 depotPaths = settings['depot-paths']
504 change = settings['change']
508 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
509 for p in depotPaths]))) == 0:
510 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
511 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
514 while change and int(change) > maxChange:
517 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
518 system("git update-ref %s \"%s^\"" % (ref, ref))
519 log = extractLogMessageFromGitCommit(ref)
520 settings = extractSettingsGitLog(log)
523 depotPaths = settings['depot-paths']
524 change = settings['change']
527 print "%s rewound to %s" % (ref, change)
531 class P4Submit(Command):
533 Command.__init__(self)
535 optparse.make_option("--verbose", dest="verbose", action="store_true"),
536 optparse.make_option("--origin", dest="origin"),
537 optparse.make_option("-M", dest="detectRename", action="store_true"),
539 self.description = "Submit changes from git to the perforce depot."
540 self.usage += " [name of git branch to submit into perforce depot]"
541 self.interactive = True
543 self.detectRename = False
545 self.isWindows = (platform.system() == "Windows")
548 if len(p4CmdList("opened ...")) > 0:
549 die("You have files opened with perforce! Close them before starting the sync.")
551 # replaces everything between 'Description:' and the next P4 submit template field with the
553 def prepareLogMessage(self, template, message):
556 inDescriptionSection = False
558 for line in template.split("\n"):
559 if line.startswith("#"):
560 result += line + "\n"
563 if inDescriptionSection:
564 if line.startswith("Files:"):
565 inDescriptionSection = False
569 if line.startswith("Description:"):
570 inDescriptionSection = True
572 for messageLine in message.split("\n"):
573 line += "\t" + messageLine + "\n"
575 result += line + "\n"
579 def prepareSubmitTemplate(self):
580 # remove lines in the Files section that show changes to files outside the depot path we're committing into
582 inFilesSection = False
583 for line in p4_read_pipe_lines("change -o"):
584 if line.endswith("\r\n"):
585 line = line[:-2] + "\n"
587 if line.startswith("\t"):
588 # path starts and ends with a tab
590 lastTab = path.rfind("\t")
592 path = path[:lastTab]
593 if not path.startswith(self.depotPath):
596 inFilesSection = False
598 if line.startswith("Files:"):
599 inFilesSection = True
605 def applyCommit(self, id):
606 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
607 diffOpts = ("", "-M")[self.detectRename]
608 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
610 filesToDelete = set()
612 filesToChangeExecBit = {}
614 diff = parseDiffTreeEntry(line)
615 modifier = diff['status']
618 p4_system("edit \"%s\"" % path)
619 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
620 filesToChangeExecBit[path] = diff['dst_mode']
621 editedFiles.add(path)
622 elif modifier == "A":
624 filesToChangeExecBit[path] = diff['dst_mode']
625 if path in filesToDelete:
626 filesToDelete.remove(path)
627 elif modifier == "D":
628 filesToDelete.add(path)
629 if path in filesToAdd:
630 filesToAdd.remove(path)
631 elif modifier == "R":
632 src, dest = diff['src'], diff['dst']
633 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
634 p4_system("edit \"%s\"" % (dest))
635 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
636 filesToChangeExecBit[dest] = diff['dst_mode']
638 editedFiles.add(dest)
639 filesToDelete.add(src)
641 die("unknown modifier %s for %s" % (modifier, path))
643 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
644 patchcmd = diffcmd + " | git apply "
645 tryPatchCmd = patchcmd + "--check -"
646 applyPatchCmd = patchcmd + "--check --apply -"
648 if os.system(tryPatchCmd) != 0:
649 print "Unfortunately applying the change failed!"
650 print "What do you want to do?"
652 while response != "s" and response != "a" and response != "w":
653 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
654 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
656 print "Skipping! Good luck with the next patches..."
657 for f in editedFiles:
658 p4_system("revert \"%s\"" % f);
662 elif response == "a":
663 os.system(applyPatchCmd)
664 if len(filesToAdd) > 0:
665 print "You may also want to call p4 add on the following files:"
666 print " ".join(filesToAdd)
667 if len(filesToDelete):
668 print "The following files should be scheduled for deletion with p4 delete:"
669 print " ".join(filesToDelete)
670 die("Please resolve and submit the conflict manually and "
671 + "continue afterwards with git-p4 submit --continue")
672 elif response == "w":
673 system(diffcmd + " > patch.txt")
674 print "Patch saved to patch.txt in %s !" % self.clientPath
675 die("Please resolve and submit the conflict manually and "
676 "continue afterwards with git-p4 submit --continue")
678 system(applyPatchCmd)
681 p4_system("add \"%s\"" % f)
682 for f in filesToDelete:
683 p4_system("revert \"%s\"" % f)
684 p4_system("delete \"%s\"" % f)
686 # Set/clear executable bits
687 for f in filesToChangeExecBit.keys():
688 mode = filesToChangeExecBit[f]
689 setP4ExecBit(f, mode)
691 logMessage = extractLogMessageFromGitCommit(id)
692 logMessage = logMessage.strip()
694 template = self.prepareSubmitTemplate()
697 submitTemplate = self.prepareLogMessage(template, logMessage)
698 if os.environ.has_key("P4DIFF"):
699 del(os.environ["P4DIFF"])
700 diff = p4_read_pipe("diff -du ...")
703 for newFile in filesToAdd:
704 newdiff += "==== new file ====\n"
705 newdiff += "--- /dev/null\n"
706 newdiff += "+++ %s\n" % newFile
707 f = open(newFile, "r")
708 for line in f.readlines():
709 newdiff += "+" + line
712 separatorLine = "######## everything below this line is just the diff #######\n"
714 [handle, fileName] = tempfile.mkstemp()
715 tmpFile = os.fdopen(handle, "w+")
717 submitTemplate = submitTemplate.replace("\n", "\r\n")
718 separatorLine = separatorLine.replace("\n", "\r\n")
719 newdiff = newdiff.replace("\n", "\r\n")
720 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
722 mtime = os.stat(fileName).st_mtime
724 if platform.system() == "Windows":
725 defaultEditor = "notepad"
726 if os.environ.has_key("P4EDITOR"):
727 editor = os.environ.get("P4EDITOR")
729 editor = os.environ.get("EDITOR", defaultEditor);
730 system(editor + " " + fileName)
733 if os.stat(fileName).st_mtime <= mtime:
735 while response != "y" and response != "n":
736 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
739 tmpFile = open(fileName, "rb")
740 message = tmpFile.read()
742 submitTemplate = message[:message.index(separatorLine)]
744 submitTemplate = submitTemplate.replace("\r\n", "\n")
745 p4_write_pipe("submit -i", submitTemplate)
747 for f in editedFiles:
748 p4_system("revert \"%s\"" % f);
750 p4_system("revert \"%s\"" % f);
755 fileName = "submit.txt"
756 file = open(fileName, "w+")
757 file.write(self.prepareLogMessage(template, logMessage))
759 print ("Perforce submit template written as %s. "
760 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
761 % (fileName, fileName))
765 self.master = currentGitBranch()
766 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
767 die("Detecting current git branch failed!")
769 self.master = args[0]
773 allowSubmit = gitConfig("git-p4.allowSubmit")
774 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
775 die("%s is not in git-p4.allowSubmit" % self.master)
777 [upstream, settings] = findUpstreamBranchPoint()
778 self.depotPath = settings['depot-paths'][0]
779 if len(self.origin) == 0:
780 self.origin = upstream
783 print "Origin branch is " + self.origin
785 if len(self.depotPath) == 0:
786 print "Internal error: cannot locate perforce depot path from existing branches"
789 self.clientPath = p4Where(self.depotPath)
791 if len(self.clientPath) == 0:
792 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
795 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
796 self.oldWorkingDirectory = os.getcwd()
798 chdir(self.clientPath)
799 print "Syncronizing p4 checkout..."
800 p4_system("sync ...")
805 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
806 commits.append(line.strip())
809 while len(commits) > 0:
811 commits = commits[1:]
812 self.applyCommit(commit)
813 if not self.interactive:
816 if len(commits) == 0:
817 print "All changes applied!"
818 chdir(self.oldWorkingDirectory)
828 class P4Sync(Command):
830 Command.__init__(self)
832 optparse.make_option("--branch", dest="branch"),
833 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
834 optparse.make_option("--changesfile", dest="changesFile"),
835 optparse.make_option("--silent", dest="silent", action="store_true"),
836 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
837 optparse.make_option("--verbose", dest="verbose", action="store_true"),
838 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
839 help="Import into refs/heads/ , not refs/remotes"),
840 optparse.make_option("--max-changes", dest="maxChanges"),
841 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
842 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
843 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
844 help="Only sync files that are included in the Perforce Client Spec")
846 self.description = """Imports from Perforce into a git repository.\n
848 //depot/my/project/ -- to import the current head
849 //depot/my/project/@all -- to import everything
850 //depot/my/project/@1,6 -- to import only from revision 1 to 6
852 (a ... is not needed in the path p4 specification, it's added implicitly)"""
854 self.usage += " //depot/path[@revRange]"
856 self.createdBranches = Set()
857 self.committedChanges = Set()
859 self.detectBranches = False
860 self.detectLabels = False
861 self.changesFile = ""
862 self.syncWithOrigin = True
864 self.importIntoRemotes = True
866 self.isWindows = (platform.system() == "Windows")
867 self.keepRepoPath = False
868 self.depotPaths = None
869 self.p4BranchesInGit = []
870 self.cloneExclude = []
871 self.useClientSpec = False
872 self.clientSpecDirs = []
874 if gitConfig("git-p4.syncFromOrigin") == "false":
875 self.syncWithOrigin = False
877 def extractFilesFromCommit(self, commit):
878 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
879 for path in self.cloneExclude]
882 while commit.has_key("depotFile%s" % fnum):
883 path = commit["depotFile%s" % fnum]
885 if [p for p in self.cloneExclude
886 if path.startswith (p)]:
889 found = [p for p in self.depotPaths
890 if path.startswith (p)]
897 file["rev"] = commit["rev%s" % fnum]
898 file["action"] = commit["action%s" % fnum]
899 file["type"] = commit["type%s" % fnum]
904 def stripRepoPath(self, path, prefixes):
905 if self.keepRepoPath:
906 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
909 if path.startswith(p):
914 def splitFilesIntoBranches(self, commit):
917 while commit.has_key("depotFile%s" % fnum):
918 path = commit["depotFile%s" % fnum]
919 found = [p for p in self.depotPaths
920 if path.startswith (p)]
927 file["rev"] = commit["rev%s" % fnum]
928 file["action"] = commit["action%s" % fnum]
929 file["type"] = commit["type%s" % fnum]
932 relPath = self.stripRepoPath(path, self.depotPaths)
934 for branch in self.knownBranches.keys():
936 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
937 if relPath.startswith(branch + "/"):
938 if branch not in branches:
939 branches[branch] = []
940 branches[branch].append(file)
945 ## Should move this out, doesn't use SELF.
946 def readP4Files(self, files):
952 for val in self.clientSpecDirs:
953 if f['path'].startswith(val[0]):
959 filesForCommit.append(f)
960 if f['action'] not in ('delete', 'purge'):
961 filesToRead.append(f)
964 if len(filesToRead) > 0:
965 filedata = p4CmdList('-x - print',
966 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
967 for f in filesToRead]),
970 if "p4ExitCode" in filedata[0]:
971 die("Problems executing p4. Error: [%d]."
972 % (filedata[0]['p4ExitCode']));
976 while j < len(filedata):
980 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
981 text += filedata[j]['data']
982 del filedata[j]['data']
985 if not stat.has_key('depotFile'):
986 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
989 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
990 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
991 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
992 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text)
994 contents[stat['depotFile']] = text
996 for f in filesForCommit:
998 if contents.has_key(path):
999 f['data'] = contents[path]
1001 return filesForCommit
1003 def commit(self, details, files, branch, branchPrefixes, parent = ""):
1004 epoch = details["time"]
1005 author = details["user"]
1008 print "commit into %s" % branch
1010 # start with reading files; if that fails, we should not
1014 if [p for p in branchPrefixes if f['path'].startswith(p)]:
1015 new_files.append (f)
1017 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
1018 files = self.readP4Files(new_files)
1020 self.gitStream.write("commit %s\n" % branch)
1021 # gitStream.write("mark :%s\n" % details["change"])
1022 self.committedChanges.add(int(details["change"]))
1024 if author not in self.users:
1025 self.getUserMapFromPerforceServer()
1026 if author in self.users:
1027 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
1029 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
1031 self.gitStream.write("committer %s\n" % committer)
1033 self.gitStream.write("data <<EOT\n")
1034 self.gitStream.write(details["desc"])
1035 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1036 % (','.join (branchPrefixes), details["change"]))
1037 if len(details['options']) > 0:
1038 self.gitStream.write(": options = %s" % details['options'])
1039 self.gitStream.write("]\nEOT\n\n")
1043 print "parent %s" % parent
1044 self.gitStream.write("from %s\n" % parent)
1047 if file["type"] == "apple":
1048 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
1051 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1052 if file["action"] in ("delete", "purge"):
1053 self.gitStream.write("D %s\n" % relPath)
1058 if isP4Exec(file["type"]):
1060 elif file["type"] == "symlink":
1062 # p4 print on a symlink contains "target\n", so strip it off
1065 if self.isWindows and file["type"].endswith("text"):
1066 data = data.replace("\r\n", "\n")
1068 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
1069 self.gitStream.write("data %s\n" % len(data))
1070 self.gitStream.write(data)
1071 self.gitStream.write("\n")
1073 self.gitStream.write("\n")
1075 change = int(details["change"])
1077 if self.labels.has_key(change):
1078 label = self.labels[change]
1079 labelDetails = label[0]
1080 labelRevisions = label[1]
1082 print "Change %s is labelled %s" % (change, labelDetails)
1084 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1085 for p in branchPrefixes]))
1087 if len(files) == len(labelRevisions):
1091 if info["action"] in ("delete", "purge"):
1093 cleanedFiles[info["depotFile"]] = info["rev"]
1095 if cleanedFiles == labelRevisions:
1096 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1097 self.gitStream.write("from %s\n" % branch)
1099 owner = labelDetails["Owner"]
1101 if author in self.users:
1102 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1104 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1105 self.gitStream.write("tagger %s\n" % tagger)
1106 self.gitStream.write("data <<EOT\n")
1107 self.gitStream.write(labelDetails["Description"])
1108 self.gitStream.write("EOT\n\n")
1112 print ("Tag %s does not match with change %s: files do not match."
1113 % (labelDetails["label"], change))
1117 print ("Tag %s does not match with change %s: file count is different."
1118 % (labelDetails["label"], change))
1120 def getUserCacheFilename(self):
1121 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1122 return home + "/.gitp4-usercache.txt"
1124 def getUserMapFromPerforceServer(self):
1125 if self.userMapFromPerforceServer:
1129 for output in p4CmdList("users"):
1130 if not output.has_key("User"):
1132 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1136 for (key, val) in self.users.items():
1137 s += "%s\t%s\n" % (key, val)
1139 open(self.getUserCacheFilename(), "wb").write(s)
1140 self.userMapFromPerforceServer = True
1142 def loadUserMapFromCache(self):
1144 self.userMapFromPerforceServer = False
1146 cache = open(self.getUserCacheFilename(), "rb")
1147 lines = cache.readlines()
1150 entry = line.strip().split("\t")
1151 self.users[entry[0]] = entry[1]
1153 self.getUserMapFromPerforceServer()
1155 def getLabels(self):
1158 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
1159 if len(l) > 0 and not self.silent:
1160 print "Finding files belonging to labels in %s" % `self.depotPaths`
1163 label = output["label"]
1167 print "Querying files for label %s" % label
1168 for file in p4CmdList("files "
1169 + ' '.join (["%s...@%s" % (p, label)
1170 for p in self.depotPaths])):
1171 revisions[file["depotFile"]] = file["rev"]
1172 change = int(file["change"])
1173 if change > newestChange:
1174 newestChange = change
1176 self.labels[newestChange] = [output, revisions]
1179 print "Label changes: %s" % self.labels.keys()
1181 def guessProjectName(self):
1182 for p in self.depotPaths:
1185 p = p[p.strip().rfind("/") + 1:]
1186 if not p.endswith("/"):
1190 def getBranchMapping(self):
1191 lostAndFoundBranches = set()
1193 for info in p4CmdList("branches"):
1194 details = p4Cmd("branch -o %s" % info["branch"])
1196 while details.has_key("View%s" % viewIdx):
1197 paths = details["View%s" % viewIdx].split(" ")
1198 viewIdx = viewIdx + 1
1199 # require standard //depot/foo/... //depot/bar/... mapping
1200 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1203 destination = paths[1]
1205 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1206 source = source[len(self.depotPaths[0]):-4]
1207 destination = destination[len(self.depotPaths[0]):-4]
1209 if destination in self.knownBranches:
1211 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1212 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1215 self.knownBranches[destination] = source
1217 lostAndFoundBranches.discard(destination)
1219 if source not in self.knownBranches:
1220 lostAndFoundBranches.add(source)
1223 for branch in lostAndFoundBranches:
1224 self.knownBranches[branch] = branch
1226 def getBranchMappingFromGitBranches(self):
1227 branches = p4BranchesInGit(self.importIntoRemotes)
1228 for branch in branches.keys():
1229 if branch == "master":
1232 branch = branch[len(self.projectName):]
1233 self.knownBranches[branch] = branch
1235 def listExistingP4GitBranches(self):
1236 # branches holds mapping from name to commit
1237 branches = p4BranchesInGit(self.importIntoRemotes)
1238 self.p4BranchesInGit = branches.keys()
1239 for branch in branches.keys():
1240 self.initialParents[self.refPrefix + branch] = branches[branch]
1242 def updateOptionDict(self, d):
1244 if self.keepRepoPath:
1245 option_keys['keepRepoPath'] = 1
1247 d["options"] = ' '.join(sorted(option_keys.keys()))
1249 def readOptions(self, d):
1250 self.keepRepoPath = (d.has_key('options')
1251 and ('keepRepoPath' in d['options']))
1253 def gitRefForBranch(self, branch):
1254 if branch == "main":
1255 return self.refPrefix + "master"
1257 if len(branch) <= 0:
1260 return self.refPrefix + self.projectName + branch
1262 def gitCommitByP4Change(self, ref, change):
1264 print "looking in ref " + ref + " for change %s using bisect..." % change
1267 latestCommit = parseRevision(ref)
1271 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1272 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1277 log = extractLogMessageFromGitCommit(next)
1278 settings = extractSettingsGitLog(log)
1279 currentChange = int(settings['change'])
1281 print "current change %s" % currentChange
1283 if currentChange == change:
1285 print "found %s" % next
1288 if currentChange < change:
1289 earliestCommit = "^%s" % next
1291 latestCommit = "%s" % next
1295 def importNewBranch(self, branch, maxChange):
1296 # make fast-import flush all changes to disk and update the refs using the checkpoint
1297 # command so that we can try to find the branch parent in the git history
1298 self.gitStream.write("checkpoint\n\n");
1299 self.gitStream.flush();
1300 branchPrefix = self.depotPaths[0] + branch + "/"
1301 range = "@1,%s" % maxChange
1302 #print "prefix" + branchPrefix
1303 changes = p4ChangesForPaths([branchPrefix], range)
1304 if len(changes) <= 0:
1306 firstChange = changes[0]
1307 #print "first change in branch: %s" % firstChange
1308 sourceBranch = self.knownBranches[branch]
1309 sourceDepotPath = self.depotPaths[0] + sourceBranch
1310 sourceRef = self.gitRefForBranch(sourceBranch)
1311 #print "source " + sourceBranch
1313 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1314 #print "branch parent: %s" % branchParentChange
1315 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1316 if len(gitParent) > 0:
1317 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1318 #print "parent git commit: %s" % gitParent
1320 self.importChanges(changes)
1323 def importChanges(self, changes):
1325 for change in changes:
1326 description = p4Cmd("describe %s" % change)
1327 self.updateOptionDict(description)
1330 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1335 if self.detectBranches:
1336 branches = self.splitFilesIntoBranches(description)
1337 for branch in branches.keys():
1339 branchPrefix = self.depotPaths[0] + branch + "/"
1343 filesForCommit = branches[branch]
1346 print "branch is %s" % branch
1348 self.updatedBranches.add(branch)
1350 if branch not in self.createdBranches:
1351 self.createdBranches.add(branch)
1352 parent = self.knownBranches[branch]
1353 if parent == branch:
1356 fullBranch = self.projectName + branch
1357 if fullBranch not in self.p4BranchesInGit:
1359 print("\n Importing new branch %s" % fullBranch);
1360 if self.importNewBranch(branch, change - 1):
1362 self.p4BranchesInGit.append(fullBranch)
1364 print("\n Resuming with change %s" % change);
1367 print "parent determined through known branches: %s" % parent
1369 branch = self.gitRefForBranch(branch)
1370 parent = self.gitRefForBranch(parent)
1373 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1375 if len(parent) == 0 and branch in self.initialParents:
1376 parent = self.initialParents[branch]
1377 del self.initialParents[branch]
1379 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1381 files = self.extractFilesFromCommit(description)
1382 self.commit(description, files, self.branch, self.depotPaths,
1384 self.initialParent = ""
1386 print self.gitError.read()
1389 def importHeadRevision(self, revision):
1390 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1392 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1393 details["desc"] = ("Initial import of %s from the state at revision %s"
1394 % (' '.join(self.depotPaths), revision))
1395 details["change"] = revision
1399 for info in p4CmdList("files "
1400 + ' '.join(["%s...%s"
1402 for p in self.depotPaths])):
1404 if info['code'] == 'error':
1405 sys.stderr.write("p4 returned an error: %s\n"
1410 change = int(info["change"])
1411 if change > newestRevision:
1412 newestRevision = change
1414 if info["action"] in ("delete", "purge"):
1415 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1416 #fileCnt = fileCnt + 1
1419 for prop in ["depotFile", "rev", "action", "type" ]:
1420 details["%s%s" % (prop, fileCnt)] = info[prop]
1422 fileCnt = fileCnt + 1
1424 details["change"] = newestRevision
1425 self.updateOptionDict(details)
1427 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1429 print "IO error with git fast-import. Is your git version recent enough?"
1430 print self.gitError.read()
1433 def getClientSpec(self):
1434 specList = p4CmdList( "client -o" )
1436 for entry in specList:
1437 for k,v in entry.iteritems():
1438 if k.startswith("View"):
1439 if v.startswith('"'):
1443 index = v.find("...")
1445 if v.startswith("-"):
1450 self.clientSpecDirs = temp.items()
1451 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1453 def run(self, args):
1454 self.depotPaths = []
1455 self.changeRange = ""
1456 self.initialParent = ""
1457 self.previousDepotPaths = []
1459 # map from branch depot path to parent branch
1460 self.knownBranches = {}
1461 self.initialParents = {}
1462 self.hasOrigin = originP4BranchesExist()
1463 if not self.syncWithOrigin:
1464 self.hasOrigin = False
1466 if self.importIntoRemotes:
1467 self.refPrefix = "refs/remotes/p4/"
1469 self.refPrefix = "refs/heads/p4/"
1471 if self.syncWithOrigin and self.hasOrigin:
1473 print "Syncing with origin first by calling git fetch origin"
1474 system("git fetch origin")
1476 if len(self.branch) == 0:
1477 self.branch = self.refPrefix + "master"
1478 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
1479 system("git update-ref %s refs/heads/p4" % self.branch)
1480 system("git branch -D p4");
1481 # create it /after/ importing, when master exists
1482 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
1483 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
1485 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
1486 self.getClientSpec()
1488 # TODO: should always look at previous commits,
1489 # merge with previous imports, if possible.
1492 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
1493 self.listExistingP4GitBranches()
1495 if len(self.p4BranchesInGit) > 1:
1497 print "Importing from/into multiple branches"
1498 self.detectBranches = True
1501 print "branches: %s" % self.p4BranchesInGit
1504 for branch in self.p4BranchesInGit:
1505 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
1507 settings = extractSettingsGitLog(logMsg)
1509 self.readOptions(settings)
1510 if (settings.has_key('depot-paths')
1511 and settings.has_key ('change')):
1512 change = int(settings['change']) + 1
1513 p4Change = max(p4Change, change)
1515 depotPaths = sorted(settings['depot-paths'])
1516 if self.previousDepotPaths == []:
1517 self.previousDepotPaths = depotPaths
1520 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
1521 for i in range(0, min(len(cur), len(prev))):
1522 if cur[i] <> prev[i]:
1526 paths.append (cur[:i + 1])
1528 self.previousDepotPaths = paths
1531 self.depotPaths = sorted(self.previousDepotPaths)
1532 self.changeRange = "@%s,#head" % p4Change
1533 if not self.detectBranches:
1534 self.initialParent = parseRevision(self.branch)
1535 if not self.silent and not self.detectBranches:
1536 print "Performing incremental import into %s git branch" % self.branch
1538 if not self.branch.startswith("refs/"):
1539 self.branch = "refs/heads/" + self.branch
1541 if len(args) == 0 and self.depotPaths:
1543 print "Depot paths: %s" % ' '.join(self.depotPaths)
1545 if self.depotPaths and self.depotPaths != args:
1546 print ("previous import used depot path %s and now %s was specified. "
1547 "This doesn't work!" % (' '.join (self.depotPaths),
1551 self.depotPaths = sorted(args)
1557 for p in self.depotPaths:
1558 if p.find("@") != -1:
1559 atIdx = p.index("@")
1560 self.changeRange = p[atIdx:]
1561 if self.changeRange == "@all":
1562 self.changeRange = ""
1563 elif ',' not in self.changeRange:
1564 revision = self.changeRange
1565 self.changeRange = ""
1567 elif p.find("#") != -1:
1568 hashIdx = p.index("#")
1569 revision = p[hashIdx:]
1571 elif self.previousDepotPaths == []:
1574 p = re.sub ("\.\.\.$", "", p)
1575 if not p.endswith("/"):
1580 self.depotPaths = newPaths
1583 self.loadUserMapFromCache()
1585 if self.detectLabels:
1588 if self.detectBranches:
1589 ## FIXME - what's a P4 projectName ?
1590 self.projectName = self.guessProjectName()
1593 self.getBranchMappingFromGitBranches()
1595 self.getBranchMapping()
1597 print "p4-git branches: %s" % self.p4BranchesInGit
1598 print "initial parents: %s" % self.initialParents
1599 for b in self.p4BranchesInGit:
1603 b = b[len(self.projectName):]
1604 self.createdBranches.add(b)
1606 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
1608 importProcess = subprocess.Popen(["git", "fast-import"],
1609 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1610 stderr=subprocess.PIPE);
1611 self.gitOutput = importProcess.stdout
1612 self.gitStream = importProcess.stdin
1613 self.gitError = importProcess.stderr
1616 self.importHeadRevision(revision)
1620 if len(self.changesFile) > 0:
1621 output = open(self.changesFile).readlines()
1624 changeSet.add(int(line))
1626 for change in changeSet:
1627 changes.append(change)
1632 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
1634 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
1636 if len(self.maxChanges) > 0:
1637 changes = changes[:min(int(self.maxChanges), len(changes))]
1639 if len(changes) == 0:
1641 print "No changes to import!"
1644 if not self.silent and not self.detectBranches:
1645 print "Import destination: %s" % self.branch
1647 self.updatedBranches = set()
1649 self.importChanges(changes)
1653 if len(self.updatedBranches) > 0:
1654 sys.stdout.write("Updated branches: ")
1655 for b in self.updatedBranches:
1656 sys.stdout.write("%s " % b)
1657 sys.stdout.write("\n")
1659 self.gitStream.close()
1660 if importProcess.wait() != 0:
1661 die("fast-import failed: %s" % self.gitError.read())
1662 self.gitOutput.close()
1663 self.gitError.close()
1667 class P4Rebase(Command):
1669 Command.__init__(self)
1671 self.description = ("Fetches the latest revision from perforce and "
1672 + "rebases the current work (branch) against it")
1673 self.verbose = False
1675 def run(self, args):
1679 return self.rebase()
1682 if os.system("git update-index --refresh") != 0:
1683 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.");
1684 if len(read_pipe("git diff-index HEAD --")) > 0:
1685 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1687 [upstream, settings] = findUpstreamBranchPoint()
1688 if len(upstream) == 0:
1689 die("Cannot find upstream branchpoint for rebase")
1691 # the branchpoint may be p4/foo~3, so strip off the parent
1692 upstream = re.sub("~[0-9]+$", "", upstream)
1694 print "Rebasing the current branch onto %s" % upstream
1695 oldHead = read_pipe("git rev-parse HEAD").strip()
1696 system("git rebase %s" % upstream)
1697 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
1700 class P4Clone(P4Sync):
1702 P4Sync.__init__(self)
1703 self.description = "Creates a new git repository and imports from Perforce into it"
1704 self.usage = "usage: %prog [options] //depot/path[@revRange]"
1706 optparse.make_option("--destination", dest="cloneDestination",
1707 action='store', default=None,
1708 help="where to leave result of the clone"),
1709 optparse.make_option("-/", dest="cloneExclude",
1710 action="append", type="string",
1711 help="exclude depot path")
1713 self.cloneDestination = None
1714 self.needsGit = False
1716 # This is required for the "append" cloneExclude action
1717 def ensure_value(self, attr, value):
1718 if not hasattr(self, attr) or getattr(self, attr) is None:
1719 setattr(self, attr, value)
1720 return getattr(self, attr)
1722 def defaultDestination(self, args):
1723 ## TODO: use common prefix of args?
1725 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1726 depotDir = re.sub("(#[^#]*)$", "", depotDir)
1727 depotDir = re.sub(r"\.\.\.$", "", depotDir)
1728 depotDir = re.sub(r"/$", "", depotDir)
1729 return os.path.split(depotDir)[1]
1731 def run(self, args):
1735 if self.keepRepoPath and not self.cloneDestination:
1736 sys.stderr.write("Must specify destination for --keep-path\n")
1741 if not self.cloneDestination and len(depotPaths) > 1:
1742 self.cloneDestination = depotPaths[-1]
1743 depotPaths = depotPaths[:-1]
1745 self.cloneExclude = ["/"+p for p in self.cloneExclude]
1746 for p in depotPaths:
1747 if not p.startswith("//"):
1750 if not self.cloneDestination:
1751 self.cloneDestination = self.defaultDestination(args)
1753 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
1754 if not os.path.exists(self.cloneDestination):
1755 os.makedirs(self.cloneDestination)
1756 chdir(self.cloneDestination)
1758 self.gitdir = os.getcwd() + "/.git"
1759 if not P4Sync.run(self, depotPaths):
1761 if self.branch != "master":
1762 if self.importIntoRemotes:
1763 masterbranch = "refs/remotes/p4/master"
1765 masterbranch = "refs/heads/p4/master"
1766 if gitBranchExists(masterbranch):
1767 system("git branch master %s" % masterbranch)
1768 system("git checkout -f")
1770 print "Could not detect main branch. No checkout/master branch created."
1774 class P4Branches(Command):
1776 Command.__init__(self)
1778 self.description = ("Shows the git branches that hold imports and their "
1779 + "corresponding perforce depot paths")
1780 self.verbose = False
1782 def run(self, args):
1783 if originP4BranchesExist():
1784 createOrUpdateBranchesFromOrigin()
1786 cmdline = "git rev-parse --symbolic "
1787 cmdline += " --remotes"
1789 for line in read_pipe_lines(cmdline):
1792 if not line.startswith('p4/') or line == "p4/HEAD":
1796 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1797 settings = extractSettingsGitLog(log)
1799 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1802 class HelpFormatter(optparse.IndentedHelpFormatter):
1804 optparse.IndentedHelpFormatter.__init__(self)
1806 def format_description(self, description):
1808 return description + "\n"
1812 def printUsage(commands):
1813 print "usage: %s <command> [options]" % sys.argv[0]
1815 print "valid commands: %s" % ", ".join(commands)
1817 print "Try %s <command> --help for command specific help." % sys.argv[0]
1822 "submit" : P4Submit,
1823 "commit" : P4Submit,
1825 "rebase" : P4Rebase,
1827 "rollback" : P4RollBack,
1828 "branches" : P4Branches
1833 if len(sys.argv[1:]) == 0:
1834 printUsage(commands.keys())
1838 cmdName = sys.argv[1]
1840 klass = commands[cmdName]
1843 print "unknown command %s" % cmdName
1845 printUsage(commands.keys())
1848 options = cmd.options
1849 cmd.gitdir = os.environ.get("GIT_DIR", None)
1853 if len(options) > 0:
1854 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1856 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1858 description = cmd.description,
1859 formatter = HelpFormatter())
1861 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1863 verbose = cmd.verbose
1865 if cmd.gitdir == None:
1866 cmd.gitdir = os.path.abspath(".git")
1867 if not isValidGitDir(cmd.gitdir):
1868 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1869 if os.path.exists(cmd.gitdir):
1870 cdup = read_pipe("git rev-parse --show-cdup").strip()
1874 if not isValidGitDir(cmd.gitdir):
1875 if isValidGitDir(cmd.gitdir + "/.git"):
1876 cmd.gitdir += "/.git"
1878 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
1880 os.environ["GIT_DIR"] = cmd.gitdir
1882 if not cmd.run(args):
1886 if __name__ == '__main__':