diff options
Diffstat (limited to 'git-p4.py')
-rwxr-xr-x | git-p4.py | 289 |
1 files changed, 235 insertions, 54 deletions
@@ -14,6 +14,8 @@ import re, shutil verbose = False +# Only labels/tags matching this will be imported/exported +defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$' def p4_build_cmd(cmd): """Build a suitable p4 command line. @@ -253,6 +255,26 @@ def getP4OpenedType(file): else: die("Could not determine file type for %s (result: '%s')" % (file, result)) +# Return the set of all p4 labels +def getP4Labels(depotPaths): + labels = set() + if isinstance(depotPaths,basestring): + depotPaths = [depotPaths] + + for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]): + label = l['label'] + labels.add(label) + + return labels + +# Return the set of all git tags +def getGitTags(): + gitTags = set() + for line in read_pipe_lines(["git", "tag"]): + tag = line.strip() + gitTags.add(tag) + return gitTags + def diffTreePattern(): # This is a simple generator for the diff tree regex pattern. This could be # a class variable if this and parseDiffTreeEntry were a part of a class. @@ -640,6 +662,7 @@ class Command: def __init__(self): self.usage = "usage: %prog [options]" self.needsGit = True + self.verbose = False class P4UserMap: def __init__(self): @@ -705,13 +728,9 @@ class P4UserMap: class P4Debug(Command): def __init__(self): Command.__init__(self) - self.options = [ - optparse.make_option("--verbose", dest="verbose", action="store_true", - default=False), - ] + self.options = [] self.description = "A tool to debug the output of p4 -G." self.needsGit = False - self.verbose = False def run(self, args): j = 0 @@ -725,11 +744,9 @@ class P4RollBack(Command): def __init__(self): Command.__init__(self) self.options = [ - optparse.make_option("--verbose", dest="verbose", action="store_true"), optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true") ] self.description = "A tool to debug the multi-branch import. Don't use :)" - self.verbose = False self.rollbackLocalBranches = False def run(self, args): @@ -787,20 +804,20 @@ class P4Submit(Command, P4UserMap): Command.__init__(self) P4UserMap.__init__(self) self.options = [ - optparse.make_option("--verbose", dest="verbose", action="store_true"), optparse.make_option("--origin", dest="origin"), optparse.make_option("-M", dest="detectRenames", action="store_true"), # preserve the user, requires relevant p4 permissions optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"), + optparse.make_option("--export-labels", dest="exportLabels", action="store_true"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" self.interactive = True self.origin = "" self.detectRenames = False - self.verbose = False self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true" self.isWindows = (platform.system() == "Windows") + self.exportLabels = False def check(self): if len(p4CmdList("opened ...")) > 0: @@ -970,7 +987,7 @@ class P4Submit(Command, P4UserMap): mtime = os.stat(template_file).st_mtime # invoke the editor - if os.environ.has_key("P4EDITOR"): + if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""): editor = os.environ.get("P4EDITOR") else: editor = read_pipe("git var GIT_EDITOR").strip() @@ -1228,6 +1245,71 @@ class P4Submit(Command, P4UserMap): + "Please review/edit and then use p4 submit -i < %s to submit directly!" % (fileName, fileName)) + # Export git tags as p4 labels. Create a p4 label and then tag + # with that. + def exportGitTags(self, gitTags): + validLabelRegexp = gitConfig("git-p4.labelExportRegexp") + if len(validLabelRegexp) == 0: + validLabelRegexp = defaultLabelRegexp + m = re.compile(validLabelRegexp) + + for name in gitTags: + + if not m.match(name): + if verbose: + print "tag %s does not match regexp %s" % (name, validTagRegexp) + continue + + # Get the p4 commit this corresponds to + logMessage = extractLogMessageFromGitCommit(name) + values = extractSettingsGitLog(logMessage) + + if not values.has_key('change'): + # a tag pointing to something not sent to p4; ignore + if verbose: + print "git tag %s does not give a p4 commit" % name + continue + else: + changelist = values['change'] + + # Get the tag details. + inHeader = True + isAnnotated = False + body = [] + for l in read_pipe_lines(["git", "cat-file", "-p", name]): + l = l.strip() + if inHeader: + if re.match(r'tag\s+', l): + isAnnotated = True + elif re.match(r'\s*$', l): + inHeader = False + continue + else: + body.append(l) + + if not isAnnotated: + body = ["lightweight tag imported by git p4\n"] + + # Create the label - use the same view as the client spec we are using + clientSpec = getClientSpec() + + labelTemplate = "Label: %s\n" % name + labelTemplate += "Description:\n" + for b in body: + labelTemplate += "\t" + b + "\n" + labelTemplate += "View:\n" + for mapping in clientSpec.mappings: + labelTemplate += "\t%s\n" % mapping.depot_side.path + + p4_write_pipe(["label", "-i"], labelTemplate) + + # Use the label + p4_system(["tag", "-l", name] + + ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings]) + + if verbose: + print "created p4 label for tag %s" % name + def run(self, args): if len(args) == 0: self.master = currentGitBranch() @@ -1317,6 +1399,16 @@ class P4Submit(Command, P4UserMap): rebase = P4Rebase() rebase.rebase() + if gitConfig("git-p4.exportLabels", "--bool") == "true": + self.exportLabels = true + + if self.exportLabels: + p4Labels = getP4Labels(self.depotPath) + gitTags = getGitTags() + + missingGitTags = gitTags - p4Labels + self.exportGitTags(missingGitTags) + return True class View(object): @@ -1544,7 +1636,7 @@ class P4Sync(Command, P4UserMap): optparse.make_option("--changesfile", dest="changesFile"), optparse.make_option("--silent", dest="silent", action="store_true"), optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"), - optparse.make_option("--verbose", dest="verbose", action="store_true"), + optparse.make_option("--import-labels", dest="importLabels", action="store_true"), optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false", help="Import into refs/heads/ , not refs/remotes"), optparse.make_option("--max-changes", dest="maxChanges"), @@ -1568,9 +1660,9 @@ class P4Sync(Command, P4UserMap): self.branch = "" self.detectBranches = False self.detectLabels = False + self.importLabels = False self.changesFile = "" self.syncWithOrigin = True - self.verbose = False self.importIntoRemotes = True self.maxChanges = "" self.isWindows = (platform.system() == "Windows") @@ -1829,6 +1921,38 @@ class P4Sync(Command, P4UserMap): else: return "%s <a@b>" % userid + # Stream a p4 tag + def streamTag(self, gitStream, labelName, labelDetails, commit, epoch): + if verbose: + print "writing tag %s for commit %s" % (labelName, commit) + gitStream.write("tag %s\n" % labelName) + gitStream.write("from %s\n" % commit) + + if labelDetails.has_key('Owner'): + owner = labelDetails["Owner"] + else: + owner = None + + # Try to use the owner of the p4 label, or failing that, + # the current p4 user id. + if owner: + email = self.make_email(owner) + else: + email = self.make_email(self.p4UserId()) + tagger = "%s %s %s" % (email, epoch, self.tz) + + gitStream.write("tagger %s\n" % tagger) + + print "labelDetails=",labelDetails + if labelDetails.has_key('Description'): + description = labelDetails['Description'] + else: + description = 'Label from git p4' + + gitStream.write("data %d\n" % len(description)) + gitStream.write(description) + gitStream.write("\n") + def commit(self, details, files, branch, branchPrefixes, parent = ""): epoch = details["time"] author = details["user"] @@ -1893,25 +2017,7 @@ class P4Sync(Command, P4UserMap): cleanedFiles[info["depotFile"]] = info["rev"] if cleanedFiles == labelRevisions: - self.gitStream.write("tag tag_%s\n" % labelDetails["label"]) - self.gitStream.write("from %s\n" % branch) - - owner = labelDetails["Owner"] - - # Try to use the owner of the p4 label, or failing that, - # the current p4 user id. - if owner: - email = self.make_email(owner) - else: - email = self.make_email(self.p4UserId()) - tagger = "%s %s %s" % (email, epoch, self.tz) - - self.gitStream.write("tagger %s\n" % tagger) - - description = labelDetails["Description"] - self.gitStream.write("data %d\n" % len(description)) - self.gitStream.write(description) - self.gitStream.write("\n") + self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch) else: if not self.silent: @@ -1923,6 +2029,7 @@ class P4Sync(Command, P4UserMap): print ("Tag %s does not match with change %s: file count is different." % (labelDetails["label"], change)) + # Build a dictionary of changelists and labels, for "detect-labels" option. def getLabels(self): self.labels = {} @@ -1949,6 +2056,69 @@ class P4Sync(Command, P4UserMap): if self.verbose: print "Label changes: %s" % self.labels.keys() + # Import p4 labels as git tags. A direct mapping does not + # exist, so assume that if all the files are at the same revision + # then we can use that, or it's something more complicated we should + # just ignore. + def importP4Labels(self, stream, p4Labels): + if verbose: + print "import p4 labels: " + ' '.join(p4Labels) + + ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels") + validLabelRegexp = gitConfig("git-p4.labelImportRegexp") + if len(validLabelRegexp) == 0: + validLabelRegexp = defaultLabelRegexp + m = re.compile(validLabelRegexp) + + for name in p4Labels: + commitFound = False + + if not m.match(name): + if verbose: + print "label %s does not match regexp %s" % (name,validLabelRegexp) + continue + + if name in ignoredP4Labels: + continue + + labelDetails = p4CmdList(['label', "-o", name])[0] + + # get the most recent changelist for each file in this label + change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name) + for p in self.depotPaths]) + + if change.has_key('change'): + # find the corresponding git commit; take the oldest commit + changelist = int(change['change']) + gitCommit = read_pipe(["git", "rev-list", "--max-count=1", + "--reverse", ":/\[git-p4:.*change = %d\]" % changelist]) + if len(gitCommit) == 0: + print "could not find git commit for changelist %d" % changelist + else: + gitCommit = gitCommit.strip() + commitFound = True + # Convert from p4 time format + try: + tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S") + except ValueError: + print "Could not convert label time %s" % labelDetail['Update'] + tmwhen = 1 + + when = int(time.mktime(tmwhen)) + self.streamTag(stream, name, labelDetails, gitCommit, when) + if verbose: + print "p4 label %s mapped to git commit %s" % (name, gitCommit) + else: + if verbose: + print "Label %s has no changelists - possibly deleted?" % name + + if not commitFound: + # We can't import this label; don't try again as it will get very + # expensive repeatedly fetching all the files for labels that will + # never be imported. If the label is moved in the future, the + # ignore will need to be removed manually. + system(["git", "config", "--add", "git-p4.ignoredP4Labels", name]) + def guessProjectName(self): for p in self.depotPaths: if p.endswith("/"): @@ -2425,7 +2595,6 @@ class P4Sync(Command, P4UserMap): self.depotPaths = newPaths - self.loadUserMapFromCache() self.labels = {} if self.detectLabels: @@ -2489,22 +2658,31 @@ class P4Sync(Command, P4UserMap): if len(changes) == 0: if not self.silent: print "No changes to import!" - return True + else: + if not self.silent and not self.detectBranches: + print "Import destination: %s" % self.branch - if not self.silent and not self.detectBranches: - print "Import destination: %s" % self.branch + self.updatedBranches = set() - self.updatedBranches = set() + self.importChanges(changes) - self.importChanges(changes) + if not self.silent: + print "" + if len(self.updatedBranches) > 0: + sys.stdout.write("Updated branches: ") + for b in self.updatedBranches: + sys.stdout.write("%s " % b) + sys.stdout.write("\n") - if not self.silent: - print "" - if len(self.updatedBranches) > 0: - sys.stdout.write("Updated branches: ") - for b in self.updatedBranches: - sys.stdout.write("%s " % b) - sys.stdout.write("\n") + if gitConfig("git-p4.importLabels", "--bool") == "true": + self.importLabels = true + + if self.importLabels: + p4Labels = getP4Labels(self.depotPaths) + gitTags = getGitTags() + + missingP4Labels = p4Labels - gitTags + self.importP4Labels(self.gitStream, missingP4Labels) self.gitStream.close() if importProcess.wait() != 0: @@ -2523,13 +2701,16 @@ class P4Sync(Command, P4UserMap): class P4Rebase(Command): def __init__(self): Command.__init__(self) - self.options = [ ] + self.options = [ + optparse.make_option("--import-labels", dest="importLabels", action="store_true"), + ] + self.importLabels = False self.description = ("Fetches the latest revision from perforce and " + "rebases the current work (branch) against it") - self.verbose = False def run(self, args): sync = P4Sync() + sync.importLabels = self.importLabels sync.run([]) return self.rebase() @@ -2719,16 +2900,16 @@ def main(): args = sys.argv[2:] - if len(options) > 0: - if cmd.needsGit: - options.append(optparse.make_option("--git-dir", dest="gitdir")) + options.append(optparse.make_option("--verbose", dest="verbose", action="store_true")) + if cmd.needsGit: + options.append(optparse.make_option("--git-dir", dest="gitdir")) - parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName), - options, - description = cmd.description, - formatter = HelpFormatter()) + parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName), + options, + description = cmd.description, + formatter = HelpFormatter()) - (cmd, args) = parser.parse_args(sys.argv[2:], cmd); + (cmd, args) = parser.parse_args(sys.argv[2:], cmd); global verbose verbose = cmd.verbose if cmd.needsGit: |