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 "depotFile" in entry:
253 if entry["depotFile"] == depotPath:
256 elif "data" in entry:
257 data = entry.get("data")
258 space = data.find(" ")
259 if data[:space] == depotPath:
264 if output["code"] == "error":
268 clientPath = output.get("path")
269 elif "data" in output:
270 data = output.get("data")
271 lastSpace = data.rfind(" ")
272 clientPath = data[lastSpace + 1:]
274 if clientPath.endswith("..."):
275 clientPath = clientPath[:-3]
278 def currentGitBranch():
279 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
281 def isValidGitDir(path):
282 if (os.path.exists(path + "/HEAD")
283 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
287 def parseRevision(ref):
288 return read_pipe("git rev-parse %s" % ref).strip()
290 def extractLogMessageFromGitCommit(commit):
293 ## fixme: title is first line of commit, not 1st paragraph.
295 for log in read_pipe_lines("git cat-file commit %s" % commit):
304 def extractSettingsGitLog(log):
306 for line in log.split("\n"):
308 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
312 assignments = m.group(1).split (':')
313 for a in assignments:
315 key = vals[0].strip()
316 val = ('='.join (vals[1:])).strip()
317 if val.endswith ('\"') and val.startswith('"'):
322 paths = values.get("depot-paths")
324 paths = values.get("depot-path")
326 values['depot-paths'] = paths.split(',')
329 def gitBranchExists(branch):
330 proc = subprocess.Popen(["git", "rev-parse", branch],
331 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
332 return proc.wait() == 0;
336 if not _gitConfig.has_key(key):
337 _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
338 return _gitConfig[key]
340 def p4BranchesInGit(branchesAreInRemotes = True):
343 cmdline = "git rev-parse --symbolic "
344 if branchesAreInRemotes:
345 cmdline += " --remotes"
347 cmdline += " --branches"
349 for line in read_pipe_lines(cmdline):
352 ## only import to p4/
353 if not line.startswith('p4/') or line == "p4/HEAD":
358 branch = re.sub ("^p4/", "", line)
360 branches[branch] = parseRevision(line)
363 def findUpstreamBranchPoint(head = "HEAD"):
364 branches = p4BranchesInGit()
365 # map from depot-path to branch name
366 branchByDepotPath = {}
367 for branch in branches.keys():
368 tip = branches[branch]
369 log = extractLogMessageFromGitCommit(tip)
370 settings = extractSettingsGitLog(log)
371 if settings.has_key("depot-paths"):
372 paths = ",".join(settings["depot-paths"])
373 branchByDepotPath[paths] = "remotes/p4/" + branch
377 while parent < 65535:
378 commit = head + "~%s" % parent
379 log = extractLogMessageFromGitCommit(commit)
380 settings = extractSettingsGitLog(log)
381 if settings.has_key("depot-paths"):
382 paths = ",".join(settings["depot-paths"])
383 if branchByDepotPath.has_key(paths):
384 return [branchByDepotPath[paths], settings]
388 return ["", settings]
390 def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
392 print ("Creating/updating branch(es) in %s based on origin branch(es)"
395 originPrefix = "origin/p4/"
397 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
399 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
402 headName = line[len(originPrefix):]
403 remoteHead = localRefPrefix + headName
406 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
407 if (not original.has_key('depot-paths')
408 or not original.has_key('change')):
412 if not gitBranchExists(remoteHead):
414 print "creating %s" % remoteHead
417 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
418 if settings.has_key('change') > 0:
419 if settings['depot-paths'] == original['depot-paths']:
420 originP4Change = int(original['change'])
421 p4Change = int(settings['change'])
422 if originP4Change > p4Change:
423 print ("%s (%s) is newer than %s (%s). "
424 "Updating p4 branch from origin."
425 % (originHead, originP4Change,
426 remoteHead, p4Change))
429 print ("Ignoring: %s was imported from %s while "
430 "%s was imported from %s"
431 % (originHead, ','.join(original['depot-paths']),
432 remoteHead, ','.join(settings['depot-paths'])))
435 system("git update-ref %s %s" % (remoteHead, originHead))
437 def originP4BranchesExist():
438 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
440 def p4ChangesForPaths(depotPaths, changeRange):
442 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
443 for p in depotPaths]))
447 changeNum = line.split(" ")[1]
448 changes.append(int(changeNum))
455 self.usage = "usage: %prog [options]"
458 class P4Debug(Command):
460 Command.__init__(self)
462 optparse.make_option("--verbose", dest="verbose", action="store_true",
465 self.description = "A tool to debug the output of p4 -G."
466 self.needsGit = False
471 for output in p4CmdList(" ".join(args)):
472 print 'Element: %d' % j
477 class P4RollBack(Command):
479 Command.__init__(self)
481 optparse.make_option("--verbose", dest="verbose", action="store_true"),
482 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
484 self.description = "A tool to debug the multi-branch import. Don't use :)"
486 self.rollbackLocalBranches = False
491 maxChange = int(args[0])
493 if "p4ExitCode" in p4Cmd("changes -m 1"):
494 die("Problems executing p4");
496 if self.rollbackLocalBranches:
497 refPrefix = "refs/heads/"
498 lines = read_pipe_lines("git rev-parse --symbolic --branches")
500 refPrefix = "refs/remotes/"
501 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
504 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
506 ref = refPrefix + line
507 log = extractLogMessageFromGitCommit(ref)
508 settings = extractSettingsGitLog(log)
510 depotPaths = settings['depot-paths']
511 change = settings['change']
515 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
516 for p in depotPaths]))) == 0:
517 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
518 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
521 while change and int(change) > maxChange:
524 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
525 system("git update-ref %s \"%s^\"" % (ref, ref))
526 log = extractLogMessageFromGitCommit(ref)
527 settings = extractSettingsGitLog(log)
530 depotPaths = settings['depot-paths']
531 change = settings['change']
534 print "%s rewound to %s" % (ref, change)
538 class P4Submit(Command):
540 Command.__init__(self)
542 optparse.make_option("--verbose", dest="verbose", action="store_true"),
543 optparse.make_option("--origin", dest="origin"),
544 optparse.make_option("-M", dest="detectRename", action="store_true"),
546 self.description = "Submit changes from git to the perforce depot."
547 self.usage += " [name of git branch to submit into perforce depot]"
548 self.interactive = True
550 self.detectRename = False
552 self.isWindows = (platform.system() == "Windows")
555 if len(p4CmdList("opened ...")) > 0:
556 die("You have files opened with perforce! Close them before starting the sync.")
558 # replaces everything between 'Description:' and the next P4 submit template field with the
560 def prepareLogMessage(self, template, message):
563 inDescriptionSection = False
565 for line in template.split("\n"):
566 if line.startswith("#"):
567 result += line + "\n"
570 if inDescriptionSection:
571 if line.startswith("Files:"):
572 inDescriptionSection = False
576 if line.startswith("Description:"):
577 inDescriptionSection = True
579 for messageLine in message.split("\n"):
580 line += "\t" + messageLine + "\n"
582 result += line + "\n"
586 def prepareSubmitTemplate(self):
587 # remove lines in the Files section that show changes to files outside the depot path we're committing into
589 inFilesSection = False
590 for line in p4_read_pipe_lines("change -o"):
591 if line.endswith("\r\n"):
592 line = line[:-2] + "\n"
594 if line.startswith("\t"):
595 # path starts and ends with a tab
597 lastTab = path.rfind("\t")
599 path = path[:lastTab]
600 if not path.startswith(self.depotPath):
603 inFilesSection = False
605 if line.startswith("Files:"):
606 inFilesSection = True
612 def applyCommit(self, id):
613 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
614 diffOpts = ("", "-M")[self.detectRename]
615 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
617 filesToDelete = set()
619 filesToChangeExecBit = {}
621 diff = parseDiffTreeEntry(line)
622 modifier = diff['status']
625 p4_system("edit \"%s\"" % path)
626 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
627 filesToChangeExecBit[path] = diff['dst_mode']
628 editedFiles.add(path)
629 elif modifier == "A":
631 filesToChangeExecBit[path] = diff['dst_mode']
632 if path in filesToDelete:
633 filesToDelete.remove(path)
634 elif modifier == "D":
635 filesToDelete.add(path)
636 if path in filesToAdd:
637 filesToAdd.remove(path)
638 elif modifier == "R":
639 src, dest = diff['src'], diff['dst']
640 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
641 p4_system("edit \"%s\"" % (dest))
642 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
643 filesToChangeExecBit[dest] = diff['dst_mode']
645 editedFiles.add(dest)
646 filesToDelete.add(src)
648 die("unknown modifier %s for %s" % (modifier, path))
650 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
651 patchcmd = diffcmd + " | git apply "
652 tryPatchCmd = patchcmd + "--check -"
653 applyPatchCmd = patchcmd + "--check --apply -"
655 if os.system(tryPatchCmd) != 0:
656 print "Unfortunately applying the change failed!"
657 print "What do you want to do?"
659 while response != "s" and response != "a" and response != "w":
660 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
661 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
663 print "Skipping! Good luck with the next patches..."
664 for f in editedFiles:
665 p4_system("revert \"%s\"" % f);
669 elif response == "a":
670 os.system(applyPatchCmd)
671 if len(filesToAdd) > 0:
672 print "You may also want to call p4 add on the following files:"
673 print " ".join(filesToAdd)
674 if len(filesToDelete):
675 print "The following files should be scheduled for deletion with p4 delete:"
676 print " ".join(filesToDelete)
677 die("Please resolve and submit the conflict manually and "
678 + "continue afterwards with git-p4 submit --continue")
679 elif response == "w":
680 system(diffcmd + " > patch.txt")
681 print "Patch saved to patch.txt in %s !" % self.clientPath
682 die("Please resolve and submit the conflict manually and "
683 "continue afterwards with git-p4 submit --continue")
685 system(applyPatchCmd)
688 p4_system("add \"%s\"" % f)
689 for f in filesToDelete:
690 p4_system("revert \"%s\"" % f)
691 p4_system("delete \"%s\"" % f)
693 # Set/clear executable bits
694 for f in filesToChangeExecBit.keys():
695 mode = filesToChangeExecBit[f]
696 setP4ExecBit(f, mode)
698 logMessage = extractLogMessageFromGitCommit(id)
699 logMessage = logMessage.strip()
701 template = self.prepareSubmitTemplate()
704 submitTemplate = self.prepareLogMessage(template, logMessage)
705 if os.environ.has_key("P4DIFF"):
706 del(os.environ["P4DIFF"])
707 diff = p4_read_pipe("diff -du ...")
710 for newFile in filesToAdd:
711 newdiff += "==== new file ====\n"
712 newdiff += "--- /dev/null\n"
713 newdiff += "+++ %s\n" % newFile
714 f = open(newFile, "r")
715 for line in f.readlines():
716 newdiff += "+" + line
719 separatorLine = "######## everything below this line is just the diff #######\n"
721 [handle, fileName] = tempfile.mkstemp()
722 tmpFile = os.fdopen(handle, "w+")
724 submitTemplate = submitTemplate.replace("\n", "\r\n")
725 separatorLine = separatorLine.replace("\n", "\r\n")
726 newdiff = newdiff.replace("\n", "\r\n")
727 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
729 mtime = os.stat(fileName).st_mtime
731 if platform.system() == "Windows":
732 defaultEditor = "notepad"
733 if os.environ.has_key("P4EDITOR"):
734 editor = os.environ.get("P4EDITOR")
736 editor = os.environ.get("EDITOR", defaultEditor);
737 system(editor + " " + fileName)
740 if os.stat(fileName).st_mtime <= mtime:
742 while response != "y" and response != "n":
743 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
746 tmpFile = open(fileName, "rb")
747 message = tmpFile.read()
749 submitTemplate = message[:message.index(separatorLine)]
751 submitTemplate = submitTemplate.replace("\r\n", "\n")
752 p4_write_pipe("submit -i", submitTemplate)
754 for f in editedFiles:
755 p4_system("revert \"%s\"" % f);
757 p4_system("revert \"%s\"" % f);
762 fileName = "submit.txt"
763 file = open(fileName, "w+")
764 file.write(self.prepareLogMessage(template, logMessage))
766 print ("Perforce submit template written as %s. "
767 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
768 % (fileName, fileName))
772 self.master = currentGitBranch()
773 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
774 die("Detecting current git branch failed!")
776 self.master = args[0]
780 allowSubmit = gitConfig("git-p4.allowSubmit")
781 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
782 die("%s is not in git-p4.allowSubmit" % self.master)
784 [upstream, settings] = findUpstreamBranchPoint()
785 self.depotPath = settings['depot-paths'][0]
786 if len(self.origin) == 0:
787 self.origin = upstream
790 print "Origin branch is " + self.origin
792 if len(self.depotPath) == 0:
793 print "Internal error: cannot locate perforce depot path from existing branches"
796 self.clientPath = p4Where(self.depotPath)
798 if len(self.clientPath) == 0:
799 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
802 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
803 self.oldWorkingDirectory = os.getcwd()
805 chdir(self.clientPath)
806 print "Syncronizing p4 checkout..."
807 p4_system("sync ...")
812 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
813 commits.append(line.strip())
816 while len(commits) > 0:
818 commits = commits[1:]
819 self.applyCommit(commit)
820 if not self.interactive:
823 if len(commits) == 0:
824 print "All changes applied!"
825 chdir(self.oldWorkingDirectory)
835 class P4Sync(Command):
837 Command.__init__(self)
839 optparse.make_option("--branch", dest="branch"),
840 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
841 optparse.make_option("--changesfile", dest="changesFile"),
842 optparse.make_option("--silent", dest="silent", action="store_true"),
843 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
844 optparse.make_option("--verbose", dest="verbose", action="store_true"),
845 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
846 help="Import into refs/heads/ , not refs/remotes"),
847 optparse.make_option("--max-changes", dest="maxChanges"),
848 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
849 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
850 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
851 help="Only sync files that are included in the Perforce Client Spec")
853 self.description = """Imports from Perforce into a git repository.\n
855 //depot/my/project/ -- to import the current head
856 //depot/my/project/@all -- to import everything
857 //depot/my/project/@1,6 -- to import only from revision 1 to 6
859 (a ... is not needed in the path p4 specification, it's added implicitly)"""
861 self.usage += " //depot/path[@revRange]"
863 self.createdBranches = Set()
864 self.committedChanges = Set()
866 self.detectBranches = False
867 self.detectLabels = False
868 self.changesFile = ""
869 self.syncWithOrigin = True
871 self.importIntoRemotes = True
873 self.isWindows = (platform.system() == "Windows")
874 self.keepRepoPath = False
875 self.depotPaths = None
876 self.p4BranchesInGit = []
877 self.cloneExclude = []
878 self.useClientSpec = False
879 self.clientSpecDirs = []
881 if gitConfig("git-p4.syncFromOrigin") == "false":
882 self.syncWithOrigin = False
884 def extractFilesFromCommit(self, commit):
885 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
886 for path in self.cloneExclude]
889 while commit.has_key("depotFile%s" % fnum):
890 path = commit["depotFile%s" % fnum]
892 if [p for p in self.cloneExclude
893 if path.startswith (p)]:
896 found = [p for p in self.depotPaths
897 if path.startswith (p)]
904 file["rev"] = commit["rev%s" % fnum]
905 file["action"] = commit["action%s" % fnum]
906 file["type"] = commit["type%s" % fnum]
911 def stripRepoPath(self, path, prefixes):
912 if self.keepRepoPath:
913 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
916 if path.startswith(p):
921 def splitFilesIntoBranches(self, commit):
924 while commit.has_key("depotFile%s" % fnum):
925 path = commit["depotFile%s" % fnum]
926 found = [p for p in self.depotPaths
927 if path.startswith (p)]
934 file["rev"] = commit["rev%s" % fnum]
935 file["action"] = commit["action%s" % fnum]
936 file["type"] = commit["type%s" % fnum]
939 relPath = self.stripRepoPath(path, self.depotPaths)
941 for branch in self.knownBranches.keys():
943 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
944 if relPath.startswith(branch + "/"):
945 if branch not in branches:
946 branches[branch] = []
947 branches[branch].append(file)
952 ## Should move this out, doesn't use SELF.
953 def readP4Files(self, files):
959 for val in self.clientSpecDirs:
960 if f['path'].startswith(val[0]):
966 filesForCommit.append(f)
967 if f['action'] not in ('delete', 'purge'):
968 filesToRead.append(f)
971 if len(filesToRead) > 0:
972 filedata = p4CmdList('-x - print',
973 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
974 for f in filesToRead]),
977 if "p4ExitCode" in filedata[0]:
978 die("Problems executing p4. Error: [%d]."
979 % (filedata[0]['p4ExitCode']));
983 while j < len(filedata):
987 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
988 text += filedata[j]['data']
989 del filedata[j]['data']
992 if not stat.has_key('depotFile'):
993 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
996 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
997 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
998 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
999 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text)
1001 contents[stat['depotFile']] = text
1003 for f in filesForCommit:
1005 if contents.has_key(path):
1006 f['data'] = contents[path]
1008 return filesForCommit
1010 def commit(self, details, files, branch, branchPrefixes, parent = ""):
1011 epoch = details["time"]
1012 author = details["user"]
1015 print "commit into %s" % branch
1017 # start with reading files; if that fails, we should not
1021 if [p for p in branchPrefixes if f['path'].startswith(p)]:
1022 new_files.append (f)
1024 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
1025 files = self.readP4Files(new_files)
1027 self.gitStream.write("commit %s\n" % branch)
1028 # gitStream.write("mark :%s\n" % details["change"])
1029 self.committedChanges.add(int(details["change"]))
1031 if author not in self.users:
1032 self.getUserMapFromPerforceServer()
1033 if author in self.users:
1034 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
1036 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
1038 self.gitStream.write("committer %s\n" % committer)
1040 self.gitStream.write("data <<EOT\n")
1041 self.gitStream.write(details["desc"])
1042 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
1043 % (','.join (branchPrefixes), details["change"]))
1044 if len(details['options']) > 0:
1045 self.gitStream.write(": options = %s" % details['options'])
1046 self.gitStream.write("]\nEOT\n\n")
1050 print "parent %s" % parent
1051 self.gitStream.write("from %s\n" % parent)
1054 if file["type"] == "apple":
1055 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
1058 relPath = self.stripRepoPath(file['path'], branchPrefixes)
1059 if file["action"] in ("delete", "purge"):
1060 self.gitStream.write("D %s\n" % relPath)
1065 if isP4Exec(file["type"]):
1067 elif file["type"] == "symlink":
1069 # p4 print on a symlink contains "target\n", so strip it off
1072 if self.isWindows and file["type"].endswith("text"):
1073 data = data.replace("\r\n", "\n")
1075 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
1076 self.gitStream.write("data %s\n" % len(data))
1077 self.gitStream.write(data)
1078 self.gitStream.write("\n")
1080 self.gitStream.write("\n")
1082 change = int(details["change"])
1084 if self.labels.has_key(change):
1085 label = self.labels[change]
1086 labelDetails = label[0]
1087 labelRevisions = label[1]
1089 print "Change %s is labelled %s" % (change, labelDetails)
1091 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1092 for p in branchPrefixes]))
1094 if len(files) == len(labelRevisions):
1098 if info["action"] in ("delete", "purge"):
1100 cleanedFiles[info["depotFile"]] = info["rev"]
1102 if cleanedFiles == labelRevisions:
1103 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1104 self.gitStream.write("from %s\n" % branch)
1106 owner = labelDetails["Owner"]
1108 if author in self.users:
1109 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1111 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1112 self.gitStream.write("tagger %s\n" % tagger)
1113 self.gitStream.write("data <<EOT\n")
1114 self.gitStream.write(labelDetails["Description"])
1115 self.gitStream.write("EOT\n\n")
1119 print ("Tag %s does not match with change %s: files do not match."
1120 % (labelDetails["label"], change))
1124 print ("Tag %s does not match with change %s: file count is different."
1125 % (labelDetails["label"], change))
1127 def getUserCacheFilename(self):
1128 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1129 return home + "/.gitp4-usercache.txt"
1131 def getUserMapFromPerforceServer(self):
1132 if self.userMapFromPerforceServer:
1136 for output in p4CmdList("users"):
1137 if not output.has_key("User"):
1139 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1143 for (key, val) in self.users.items():
1144 s += "%s\t%s\n" % (key, val)
1146 open(self.getUserCacheFilename(), "wb").write(s)
1147 self.userMapFromPerforceServer = True
1149 def loadUserMapFromCache(self):
1151 self.userMapFromPerforceServer = False
1153 cache = open(self.getUserCacheFilename(), "rb")
1154 lines = cache.readlines()
1157 entry = line.strip().split("\t")
1158 self.users[entry[0]] = entry[1]
1160 self.getUserMapFromPerforceServer()
1162 def getLabels(self):
1165 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
1166 if len(l) > 0 and not self.silent:
1167 print "Finding files belonging to labels in %s" % `self.depotPaths`
1170 label = output["label"]
1174 print "Querying files for label %s" % label
1175 for file in p4CmdList("files "
1176 + ' '.join (["%s...@%s" % (p, label)
1177 for p in self.depotPaths])):
1178 revisions[file["depotFile"]] = file["rev"]
1179 change = int(file["change"])
1180 if change > newestChange:
1181 newestChange = change
1183 self.labels[newestChange] = [output, revisions]
1186 print "Label changes: %s" % self.labels.keys()
1188 def guessProjectName(self):
1189 for p in self.depotPaths:
1192 p = p[p.strip().rfind("/") + 1:]
1193 if not p.endswith("/"):
1197 def getBranchMapping(self):
1198 lostAndFoundBranches = set()
1200 for info in p4CmdList("branches"):
1201 details = p4Cmd("branch -o %s" % info["branch"])
1203 while details.has_key("View%s" % viewIdx):
1204 paths = details["View%s" % viewIdx].split(" ")
1205 viewIdx = viewIdx + 1
1206 # require standard //depot/foo/... //depot/bar/... mapping
1207 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1210 destination = paths[1]
1212 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1213 source = source[len(self.depotPaths[0]):-4]
1214 destination = destination[len(self.depotPaths[0]):-4]
1216 if destination in self.knownBranches:
1218 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1219 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1222 self.knownBranches[destination] = source
1224 lostAndFoundBranches.discard(destination)
1226 if source not in self.knownBranches:
1227 lostAndFoundBranches.add(source)
1230 for branch in lostAndFoundBranches:
1231 self.knownBranches[branch] = branch
1233 def getBranchMappingFromGitBranches(self):
1234 branches = p4BranchesInGit(self.importIntoRemotes)
1235 for branch in branches.keys():
1236 if branch == "master":
1239 branch = branch[len(self.projectName):]
1240 self.knownBranches[branch] = branch
1242 def listExistingP4GitBranches(self):
1243 # branches holds mapping from name to commit
1244 branches = p4BranchesInGit(self.importIntoRemotes)
1245 self.p4BranchesInGit = branches.keys()
1246 for branch in branches.keys():
1247 self.initialParents[self.refPrefix + branch] = branches[branch]
1249 def updateOptionDict(self, d):
1251 if self.keepRepoPath:
1252 option_keys['keepRepoPath'] = 1
1254 d["options"] = ' '.join(sorted(option_keys.keys()))
1256 def readOptions(self, d):
1257 self.keepRepoPath = (d.has_key('options')
1258 and ('keepRepoPath' in d['options']))
1260 def gitRefForBranch(self, branch):
1261 if branch == "main":
1262 return self.refPrefix + "master"
1264 if len(branch) <= 0:
1267 return self.refPrefix + self.projectName + branch
1269 def gitCommitByP4Change(self, ref, change):
1271 print "looking in ref " + ref + " for change %s using bisect..." % change
1274 latestCommit = parseRevision(ref)
1278 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1279 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1284 log = extractLogMessageFromGitCommit(next)
1285 settings = extractSettingsGitLog(log)
1286 currentChange = int(settings['change'])
1288 print "current change %s" % currentChange
1290 if currentChange == change:
1292 print "found %s" % next
1295 if currentChange < change:
1296 earliestCommit = "^%s" % next
1298 latestCommit = "%s" % next
1302 def importNewBranch(self, branch, maxChange):
1303 # make fast-import flush all changes to disk and update the refs using the checkpoint
1304 # command so that we can try to find the branch parent in the git history
1305 self.gitStream.write("checkpoint\n\n");
1306 self.gitStream.flush();
1307 branchPrefix = self.depotPaths[0] + branch + "/"
1308 range = "@1,%s" % maxChange
1309 #print "prefix" + branchPrefix
1310 changes = p4ChangesForPaths([branchPrefix], range)
1311 if len(changes) <= 0:
1313 firstChange = changes[0]
1314 #print "first change in branch: %s" % firstChange
1315 sourceBranch = self.knownBranches[branch]
1316 sourceDepotPath = self.depotPaths[0] + sourceBranch
1317 sourceRef = self.gitRefForBranch(sourceBranch)
1318 #print "source " + sourceBranch
1320 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1321 #print "branch parent: %s" % branchParentChange
1322 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1323 if len(gitParent) > 0:
1324 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1325 #print "parent git commit: %s" % gitParent
1327 self.importChanges(changes)
1330 def importChanges(self, changes):
1332 for change in changes:
1333 description = p4Cmd("describe %s" % change)
1334 self.updateOptionDict(description)
1337 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1342 if self.detectBranches:
1343 branches = self.splitFilesIntoBranches(description)
1344 for branch in branches.keys():
1346 branchPrefix = self.depotPaths[0] + branch + "/"
1350 filesForCommit = branches[branch]
1353 print "branch is %s" % branch
1355 self.updatedBranches.add(branch)
1357 if branch not in self.createdBranches:
1358 self.createdBranches.add(branch)
1359 parent = self.knownBranches[branch]
1360 if parent == branch:
1363 fullBranch = self.projectName + branch
1364 if fullBranch not in self.p4BranchesInGit:
1366 print("\n Importing new branch %s" % fullBranch);
1367 if self.importNewBranch(branch, change - 1):
1369 self.p4BranchesInGit.append(fullBranch)
1371 print("\n Resuming with change %s" % change);
1374 print "parent determined through known branches: %s" % parent
1376 branch = self.gitRefForBranch(branch)
1377 parent = self.gitRefForBranch(parent)
1380 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1382 if len(parent) == 0 and branch in self.initialParents:
1383 parent = self.initialParents[branch]
1384 del self.initialParents[branch]
1386 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1388 files = self.extractFilesFromCommit(description)
1389 self.commit(description, files, self.branch, self.depotPaths,
1391 self.initialParent = ""
1393 print self.gitError.read()
1396 def importHeadRevision(self, revision):
1397 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1399 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1400 details["desc"] = ("Initial import of %s from the state at revision %s"
1401 % (' '.join(self.depotPaths), revision))
1402 details["change"] = revision
1406 for info in p4CmdList("files "
1407 + ' '.join(["%s...%s"
1409 for p in self.depotPaths])):
1411 if info['code'] == 'error':
1412 sys.stderr.write("p4 returned an error: %s\n"
1417 change = int(info["change"])
1418 if change > newestRevision:
1419 newestRevision = change
1421 if info["action"] in ("delete", "purge"):
1422 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1423 #fileCnt = fileCnt + 1
1426 for prop in ["depotFile", "rev", "action", "type" ]:
1427 details["%s%s" % (prop, fileCnt)] = info[prop]
1429 fileCnt = fileCnt + 1
1431 details["change"] = newestRevision
1432 self.updateOptionDict(details)
1434 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1436 print "IO error with git fast-import. Is your git version recent enough?"
1437 print self.gitError.read()
1440 def getClientSpec(self):
1441 specList = p4CmdList( "client -o" )
1443 for entry in specList:
1444 for k,v in entry.iteritems():
1445 if k.startswith("View"):
1446 if v.startswith('"'):
1450 index = v.find("...")
1452 if v.startswith("-"):
1457 self.clientSpecDirs = temp.items()
1458 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1460 def run(self, args):
1461 self.depotPaths = []
1462 self.changeRange = ""
1463 self.initialParent = ""
1464 self.previousDepotPaths = []
1466 # map from branch depot path to parent branch
1467 self.knownBranches = {}
1468 self.initialParents = {}
1469 self.hasOrigin = originP4BranchesExist()
1470 if not self.syncWithOrigin:
1471 self.hasOrigin = False
1473 if self.importIntoRemotes:
1474 self.refPrefix = "refs/remotes/p4/"
1476 self.refPrefix = "refs/heads/p4/"
1478 if self.syncWithOrigin and self.hasOrigin:
1480 print "Syncing with origin first by calling git fetch origin"
1481 system("git fetch origin")
1483 if len(self.branch) == 0:
1484 self.branch = self.refPrefix + "master"
1485 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
1486 system("git update-ref %s refs/heads/p4" % self.branch)
1487 system("git branch -D p4");
1488 # create it /after/ importing, when master exists
1489 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
1490 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
1492 if self.useClientSpec or gitConfig("git-p4.useclientspec") == "true":
1493 self.getClientSpec()
1495 # TODO: should always look at previous commits,
1496 # merge with previous imports, if possible.
1499 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
1500 self.listExistingP4GitBranches()
1502 if len(self.p4BranchesInGit) > 1:
1504 print "Importing from/into multiple branches"
1505 self.detectBranches = True
1508 print "branches: %s" % self.p4BranchesInGit
1511 for branch in self.p4BranchesInGit:
1512 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
1514 settings = extractSettingsGitLog(logMsg)
1516 self.readOptions(settings)
1517 if (settings.has_key('depot-paths')
1518 and settings.has_key ('change')):
1519 change = int(settings['change']) + 1
1520 p4Change = max(p4Change, change)
1522 depotPaths = sorted(settings['depot-paths'])
1523 if self.previousDepotPaths == []:
1524 self.previousDepotPaths = depotPaths
1527 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
1528 for i in range(0, min(len(cur), len(prev))):
1529 if cur[i] <> prev[i]:
1533 paths.append (cur[:i + 1])
1535 self.previousDepotPaths = paths
1538 self.depotPaths = sorted(self.previousDepotPaths)
1539 self.changeRange = "@%s,#head" % p4Change
1540 if not self.detectBranches:
1541 self.initialParent = parseRevision(self.branch)
1542 if not self.silent and not self.detectBranches:
1543 print "Performing incremental import into %s git branch" % self.branch
1545 if not self.branch.startswith("refs/"):
1546 self.branch = "refs/heads/" + self.branch
1548 if len(args) == 0 and self.depotPaths:
1550 print "Depot paths: %s" % ' '.join(self.depotPaths)
1552 if self.depotPaths and self.depotPaths != args:
1553 print ("previous import used depot path %s and now %s was specified. "
1554 "This doesn't work!" % (' '.join (self.depotPaths),
1558 self.depotPaths = sorted(args)
1564 for p in self.depotPaths:
1565 if p.find("@") != -1:
1566 atIdx = p.index("@")
1567 self.changeRange = p[atIdx:]
1568 if self.changeRange == "@all":
1569 self.changeRange = ""
1570 elif ',' not in self.changeRange:
1571 revision = self.changeRange
1572 self.changeRange = ""
1574 elif p.find("#") != -1:
1575 hashIdx = p.index("#")
1576 revision = p[hashIdx:]
1578 elif self.previousDepotPaths == []:
1581 p = re.sub ("\.\.\.$", "", p)
1582 if not p.endswith("/"):
1587 self.depotPaths = newPaths
1590 self.loadUserMapFromCache()
1592 if self.detectLabels:
1595 if self.detectBranches:
1596 ## FIXME - what's a P4 projectName ?
1597 self.projectName = self.guessProjectName()
1600 self.getBranchMappingFromGitBranches()
1602 self.getBranchMapping()
1604 print "p4-git branches: %s" % self.p4BranchesInGit
1605 print "initial parents: %s" % self.initialParents
1606 for b in self.p4BranchesInGit:
1610 b = b[len(self.projectName):]
1611 self.createdBranches.add(b)
1613 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
1615 importProcess = subprocess.Popen(["git", "fast-import"],
1616 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1617 stderr=subprocess.PIPE);
1618 self.gitOutput = importProcess.stdout
1619 self.gitStream = importProcess.stdin
1620 self.gitError = importProcess.stderr
1623 self.importHeadRevision(revision)
1627 if len(self.changesFile) > 0:
1628 output = open(self.changesFile).readlines()
1631 changeSet.add(int(line))
1633 for change in changeSet:
1634 changes.append(change)
1639 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
1641 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
1643 if len(self.maxChanges) > 0:
1644 changes = changes[:min(int(self.maxChanges), len(changes))]
1646 if len(changes) == 0:
1648 print "No changes to import!"
1651 if not self.silent and not self.detectBranches:
1652 print "Import destination: %s" % self.branch
1654 self.updatedBranches = set()
1656 self.importChanges(changes)
1660 if len(self.updatedBranches) > 0:
1661 sys.stdout.write("Updated branches: ")
1662 for b in self.updatedBranches:
1663 sys.stdout.write("%s " % b)
1664 sys.stdout.write("\n")
1666 self.gitStream.close()
1667 if importProcess.wait() != 0:
1668 die("fast-import failed: %s" % self.gitError.read())
1669 self.gitOutput.close()
1670 self.gitError.close()
1674 class P4Rebase(Command):
1676 Command.__init__(self)
1678 self.description = ("Fetches the latest revision from perforce and "
1679 + "rebases the current work (branch) against it")
1680 self.verbose = False
1682 def run(self, args):
1686 return self.rebase()
1689 if os.system("git update-index --refresh") != 0:
1690 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.");
1691 if len(read_pipe("git diff-index HEAD --")) > 0:
1692 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1694 [upstream, settings] = findUpstreamBranchPoint()
1695 if len(upstream) == 0:
1696 die("Cannot find upstream branchpoint for rebase")
1698 # the branchpoint may be p4/foo~3, so strip off the parent
1699 upstream = re.sub("~[0-9]+$", "", upstream)
1701 print "Rebasing the current branch onto %s" % upstream
1702 oldHead = read_pipe("git rev-parse HEAD").strip()
1703 system("git rebase %s" % upstream)
1704 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
1707 class P4Clone(P4Sync):
1709 P4Sync.__init__(self)
1710 self.description = "Creates a new git repository and imports from Perforce into it"
1711 self.usage = "usage: %prog [options] //depot/path[@revRange]"
1713 optparse.make_option("--destination", dest="cloneDestination",
1714 action='store', default=None,
1715 help="where to leave result of the clone"),
1716 optparse.make_option("-/", dest="cloneExclude",
1717 action="append", type="string",
1718 help="exclude depot path")
1720 self.cloneDestination = None
1721 self.needsGit = False
1723 # This is required for the "append" cloneExclude action
1724 def ensure_value(self, attr, value):
1725 if not hasattr(self, attr) or getattr(self, attr) is None:
1726 setattr(self, attr, value)
1727 return getattr(self, attr)
1729 def defaultDestination(self, args):
1730 ## TODO: use common prefix of args?
1732 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1733 depotDir = re.sub("(#[^#]*)$", "", depotDir)
1734 depotDir = re.sub(r"\.\.\.$", "", depotDir)
1735 depotDir = re.sub(r"/$", "", depotDir)
1736 return os.path.split(depotDir)[1]
1738 def run(self, args):
1742 if self.keepRepoPath and not self.cloneDestination:
1743 sys.stderr.write("Must specify destination for --keep-path\n")
1748 if not self.cloneDestination and len(depotPaths) > 1:
1749 self.cloneDestination = depotPaths[-1]
1750 depotPaths = depotPaths[:-1]
1752 self.cloneExclude = ["/"+p for p in self.cloneExclude]
1753 for p in depotPaths:
1754 if not p.startswith("//"):
1757 if not self.cloneDestination:
1758 self.cloneDestination = self.defaultDestination(args)
1760 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
1761 if not os.path.exists(self.cloneDestination):
1762 os.makedirs(self.cloneDestination)
1763 chdir(self.cloneDestination)
1765 self.gitdir = os.getcwd() + "/.git"
1766 if not P4Sync.run(self, depotPaths):
1768 if self.branch != "master":
1769 if self.importIntoRemotes:
1770 masterbranch = "refs/remotes/p4/master"
1772 masterbranch = "refs/heads/p4/master"
1773 if gitBranchExists(masterbranch):
1774 system("git branch master %s" % masterbranch)
1775 system("git checkout -f")
1777 print "Could not detect main branch. No checkout/master branch created."
1781 class P4Branches(Command):
1783 Command.__init__(self)
1785 self.description = ("Shows the git branches that hold imports and their "
1786 + "corresponding perforce depot paths")
1787 self.verbose = False
1789 def run(self, args):
1790 if originP4BranchesExist():
1791 createOrUpdateBranchesFromOrigin()
1793 cmdline = "git rev-parse --symbolic "
1794 cmdline += " --remotes"
1796 for line in read_pipe_lines(cmdline):
1799 if not line.startswith('p4/') or line == "p4/HEAD":
1803 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1804 settings = extractSettingsGitLog(log)
1806 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1809 class HelpFormatter(optparse.IndentedHelpFormatter):
1811 optparse.IndentedHelpFormatter.__init__(self)
1813 def format_description(self, description):
1815 return description + "\n"
1819 def printUsage(commands):
1820 print "usage: %s <command> [options]" % sys.argv[0]
1822 print "valid commands: %s" % ", ".join(commands)
1824 print "Try %s <command> --help for command specific help." % sys.argv[0]
1829 "submit" : P4Submit,
1830 "commit" : P4Submit,
1832 "rebase" : P4Rebase,
1834 "rollback" : P4RollBack,
1835 "branches" : P4Branches
1840 if len(sys.argv[1:]) == 0:
1841 printUsage(commands.keys())
1845 cmdName = sys.argv[1]
1847 klass = commands[cmdName]
1850 print "unknown command %s" % cmdName
1852 printUsage(commands.keys())
1855 options = cmd.options
1856 cmd.gitdir = os.environ.get("GIT_DIR", None)
1860 if len(options) > 0:
1861 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1863 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1865 description = cmd.description,
1866 formatter = HelpFormatter())
1868 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1870 verbose = cmd.verbose
1872 if cmd.gitdir == None:
1873 cmd.gitdir = os.path.abspath(".git")
1874 if not isValidGitDir(cmd.gitdir):
1875 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1876 if os.path.exists(cmd.gitdir):
1877 cdup = read_pipe("git rev-parse --show-cdup").strip()
1881 if not isValidGitDir(cmd.gitdir):
1882 if isValidGitDir(cmd.gitdir + "/.git"):
1883 cmd.gitdir += "/.git"
1885 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
1887 os.environ["GIT_DIR"] = cmd.gitdir
1889 if not cmd.run(args):
1893 if __name__ == '__main__':