diff options
Diffstat (limited to 'git-p4.py')
-rwxr-xr-x | git-p4.py | 262 |
1 files changed, 228 insertions, 34 deletions
@@ -14,6 +14,8 @@ import re, shutil verbose = False +# Only labels/tags matching this will be imported/exported +defaultLabelRegexp = r'[A-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. @@ -792,6 +814,7 @@ class P4Submit(Command, P4UserMap): 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]" @@ -801,6 +824,7 @@ class P4Submit(Command, P4UserMap): 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: @@ -1228,6 +1252,73 @@ 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): + validTagRegexp = gitConfig("git-p4.validTagRegexp") + if len(validTagRegexp) == 0: + validTagRegexp = defaultLabelRegexp + m = re.compile(validTagRegexp) + commit_re = re.compile(r'\s*\[git-p4:.*change = (\d+)\s*\]') + + 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 + changelist = None + for l in read_pipe_lines(["git", "log", "--max-count=1", name]): + match = commit_re.match(l) + if match: + changelist = match.group(1) + + if not changelist: + # a tag pointing to something not sent to p4; ignore + if verbose: + print "git tag %s does not give a p4 commit" % name + continue + + # 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 +1408,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,6 +1645,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("--import-labels", dest="importLabels", action="store_true"), optparse.make_option("--verbose", dest="verbose", action="store_true"), optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false", help="Import into refs/heads/ , not refs/remotes"), @@ -1568,6 +1670,7 @@ class P4Sync(Command, P4UserMap): self.branch = "" self.detectBranches = False self.detectLabels = False + self.importLabels = False self.changesFile = "" self.syncWithOrigin = True self.verbose = False @@ -1829,6 +1932,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 +2028,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 +2040,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 +2067,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.validLabelRegexp") + 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 +2606,6 @@ class P4Sync(Command, P4UserMap): self.depotPaths = newPaths - self.loadUserMapFromCache() self.labels = {} if self.detectLabels: @@ -2489,22 +2669,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 +2712,18 @@ 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"), + optparse.make_option("--verbose", dest="verbose", action="store_true"), + ] + self.verbose = False + 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() |