14
15
verbose = False
16
17
+# Only labels/tags matching this will be imported/exported
18
+defaultLabelRegexp = r'[A-Z0-9_\-.]+$'
19
20
def p4_build_cmd(cmd):
21
"""Build a suitable p4 command line.
255
else:
256
die("Could not determine file type for %s (result: '%s')" % (file, result))
257
258
+# Return the set of all p4 labels
259
+def getP4Labels(depotPaths):
260
+ labels = set()
261
+ if isinstance(depotPaths,basestring):
262
+ depotPaths = [depotPaths]
263
+
264
+ for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
265
+ label = l['label']
266
+ labels.add(label)
267
+
268
+ return labels
269
+
270
+# Return the set of all git tags
271
+def getGitTags():
272
+ gitTags = set()
273
+ for line in read_pipe_lines(["git", "tag"]):
274
+ tag = line.strip()
275
+ gitTags.add(tag)
276
+ return gitTags
277
+
278
def diffTreePattern():
279
# This is a simple generator for the diff tree regex pattern. This could be
280
# a class variable if this and parseDiffTreeEntry were a part of a class.
814
optparse.make_option("-M", dest="detectRenames", action="store_true"),
815
# preserve the user, requires relevant p4 permissions
816
optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
817
+ optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
818
]
819
self.description = "Submit changes from git to the perforce depot."
820
self.usage += " [name of git branch to submit into perforce depot]"
824
self.verbose = False
825
self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
826
self.isWindows = (platform.system() == "Windows")
827
+ self.exportLabels = False
828
829
def check(self):
830
if len(p4CmdList("opened ...")) > 0:
1252
+ "Please review/edit and then use p4 submit -i < %s to submit directly!"
1253
% (fileName, fileName))
1254
1255
+ # Export git tags as p4 labels. Create a p4 label and then tag
1256
+ # with that.
1257
+ def exportGitTags(self, gitTags):
1258
+ validTagRegexp = gitConfig("git-p4.validTagRegexp")
1259
+ if len(validTagRegexp) == 0:
1260
+ validTagRegexp = defaultLabelRegexp
1261
+ m = re.compile(validTagRegexp)
1262
+ commit_re = re.compile(r'\s*\[git-p4:.*change = (\d+)\s*\]')
1263
+
1264
+ for name in gitTags:
1265
+
1266
+ if not m.match(name):
1267
+ if verbose:
1268
+ print "tag %s does not match regexp %s" % (name, validTagRegexp)
1269
+ continue
1270
+
1271
+ # Get the p4 commit this corresponds to
1272
+ changelist = None
1273
+ for l in read_pipe_lines(["git", "log", "--max-count=1", name]):
1274
+ match = commit_re.match(l)
1275
+ if match:
1276
+ changelist = match.group(1)
1277
+
1278
+ if not changelist:
1279
+ # a tag pointing to something not sent to p4; ignore
1280
+ if verbose:
1281
+ print "git tag %s does not give a p4 commit" % name
1282
+ continue
1283
+
1284
+ # Get the tag details.
1285
+ inHeader = True
1286
+ isAnnotated = False
1287
+ body = []
1288
+ for l in read_pipe_lines(["git", "cat-file", "-p", name]):
1289
+ l = l.strip()
1290
+ if inHeader:
1291
+ if re.match(r'tag\s+', l):
1292
+ isAnnotated = True
1293
+ elif re.match(r'\s*$', l):
1294
+ inHeader = False
1295
+ continue
1296
+ else:
1297
+ body.append(l)
1298
+
1299
+ if not isAnnotated:
1300
+ body = ["lightweight tag imported by git p4\n"]
1301
+
1302
+ # Create the label - use the same view as the client spec we are using
1303
+ clientSpec = getClientSpec()
1304
+
1305
+ labelTemplate = "Label: %s\n" % name
1306
+ labelTemplate += "Description:\n"
1307
+ for b in body:
1308
+ labelTemplate += "\t" + b + "\n"
1309
+ labelTemplate += "View:\n"
1310
+ for mapping in clientSpec.mappings:
1311
+ labelTemplate += "\t%s\n" % mapping.depot_side.path
1312
+
1313
+ p4_write_pipe(["label", "-i"], labelTemplate)
1314
+
1315
+ # Use the label
1316
+ p4_system(["tag", "-l", name] +
1317
+ ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
1318
+
1319
+ if verbose:
1320
+ print "created p4 label for tag %s" % name
1321
+
1322
def run(self, args):
1323
if len(args) == 0:
1324
self.master = currentGitBranch()
1408
rebase = P4Rebase()
1409
rebase.rebase()
1410
1411
+ if gitConfig("git-p4.exportLabels", "--bool") == "true":
1412
+ self.exportLabels = true
1413
+
1414
+ if self.exportLabels:
1415
+ p4Labels = getP4Labels(self.depotPath)
1416
+ gitTags = getGitTags()
1417
+
1418
+ missingGitTags = gitTags - p4Labels
1419
+ self.exportGitTags(missingGitTags)
1420
+
1421
return True
1422
1423
class View(object):
1645
optparse.make_option("--changesfile", dest="changesFile"),
1646
optparse.make_option("--silent", dest="silent", action="store_true"),
1647
optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
1648
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
1649
optparse.make_option("--verbose", dest="verbose", action="store_true"),
1650
optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
1651
help="Import into refs/heads/ , not refs/remotes"),
1670
self.branch = ""
1671
self.detectBranches = False
1672
self.detectLabels = False
1673
+ self.importLabels = False
1674
self.changesFile = ""
1675
self.syncWithOrigin = True
1676
self.verbose = False
1932
else:
1933
return "%s <a@b>" % userid
1934
1935
+ # Stream a p4 tag
1936
+ def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
1937
+ if verbose:
1938
+ print "writing tag %s for commit %s" % (labelName, commit)
1939
+ gitStream.write("tag %s\n" % labelName)
1940
+ gitStream.write("from %s\n" % commit)
1941
+
1942
+ if labelDetails.has_key('Owner'):
1943
+ owner = labelDetails["Owner"]
1944
+ else:
1945
+ owner = None
1946
+
1947
+ # Try to use the owner of the p4 label, or failing that,
1948
+ # the current p4 user id.
1949
+ if owner:
1950
+ email = self.make_email(owner)
1951
+ else:
1952
+ email = self.make_email(self.p4UserId())
1953
+ tagger = "%s %s %s" % (email, epoch, self.tz)
1954
+
1955
+ gitStream.write("tagger %s\n" % tagger)
1956
+
1957
+ print "labelDetails=",labelDetails
1958
+ if labelDetails.has_key('Description'):
1959
+ description = labelDetails['Description']
1960
+ else:
1961
+ description = 'Label from git p4'
1962
+
1963
+ gitStream.write("data %d\n" % len(description))
1964
+ gitStream.write(description)
1965
+ gitStream.write("\n")
1966
+
1967
def commit(self, details, files, branch, branchPrefixes, parent = ""):
1968
epoch = details["time"]
1969
author = details["user"]
2028
cleanedFiles[info["depotFile"]] = info["rev"]
2029
2030
if cleanedFiles == labelRevisions:
1896
- self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1897
- self.gitStream.write("from %s\n" % branch)
1898
-
1899
- owner = labelDetails["Owner"]
1900
-
1901
- # Try to use the owner of the p4 label, or failing that,
1902
- # the current p4 user id.
1903
- if owner:
1904
- email = self.make_email(owner)
1905
- else:
1906
- email = self.make_email(self.p4UserId())
1907
- tagger = "%s %s %s" % (email, epoch, self.tz)
1908
-
1909
- self.gitStream.write("tagger %s\n" % tagger)
1910
-
1911
- description = labelDetails["Description"]
1912
- self.gitStream.write("data %d\n" % len(description))
1913
- self.gitStream.write(description)
1914
- self.gitStream.write("\n")
2031
+ self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
2032
2033
else:
2034
if not self.silent:
2040
print ("Tag %s does not match with change %s: file count is different."
2041
% (labelDetails["label"], change))
2042
2043
+ # Build a dictionary of changelists and labels, for "detect-labels" option.
2044
def getLabels(self):
2045
self.labels = {}
2046
2067
if self.verbose:
2068
print "Label changes: %s" % self.labels.keys()
2069
2070
+ # Import p4 labels as git tags. A direct mapping does not
2071
+ # exist, so assume that if all the files are at the same revision
2072
+ # then we can use that, or it's something more complicated we should
2073
+ # just ignore.
2074
+ def importP4Labels(self, stream, p4Labels):
2075
+ if verbose:
2076
+ print "import p4 labels: " + ' '.join(p4Labels)
2077
+
2078
+ ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
2079
+ validLabelRegexp = gitConfig("git-p4.validLabelRegexp")
2080
+ if len(validLabelRegexp) == 0:
2081
+ validLabelRegexp = defaultLabelRegexp
2082
+ m = re.compile(validLabelRegexp)
2083
+
2084
+ for name in p4Labels:
2085
+ commitFound = False
2086
+
2087
+ if not m.match(name):
2088
+ if verbose:
2089
+ print "label %s does not match regexp %s" % (name,validLabelRegexp)
2090
+ continue
2091
+
2092
+ if name in ignoredP4Labels:
2093
+ continue
2094
+
2095
+ labelDetails = p4CmdList(['label', "-o", name])[0]
2096
+
2097
+ # get the most recent changelist for each file in this label
2098
+ change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
2099
+ for p in self.depotPaths])
2100
+
2101
+ if change.has_key('change'):
2102
+ # find the corresponding git commit; take the oldest commit
2103
+ changelist = int(change['change'])
2104
+ gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
2105
+ "--reverse", ":/\[git-p4:.*change = %d\]" % changelist])
2106
+ if len(gitCommit) == 0:
2107
+ print "could not find git commit for changelist %d" % changelist
2108
+ else:
2109
+ gitCommit = gitCommit.strip()
2110
+ commitFound = True
2111
+ # Convert from p4 time format
2112
+ try:
2113
+ tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
2114
+ except ValueError:
2115
+ print "Could not convert label time %s" % labelDetail['Update']
2116
+ tmwhen = 1
2117
+
2118
+ when = int(time.mktime(tmwhen))
2119
+ self.streamTag(stream, name, labelDetails, gitCommit, when)
2120
+ if verbose:
2121
+ print "p4 label %s mapped to git commit %s" % (name, gitCommit)
2122
+ else:
2123
+ if verbose:
2124
+ print "Label %s has no changelists - possibly deleted?" % name
2125
+
2126
+ if not commitFound:
2127
+ # We can't import this label; don't try again as it will get very
2128
+ # expensive repeatedly fetching all the files for labels that will
2129
+ # never be imported. If the label is moved in the future, the
2130
+ # ignore will need to be removed manually.
2131
+ system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
2132
+
2133
def guessProjectName(self):
2134
for p in self.depotPaths:
2135
if p.endswith("/"):
2606
2607
self.depotPaths = newPaths
2608
2428
-
2609
self.loadUserMapFromCache()
2610
self.labels = {}
2611
if self.detectLabels:
2669
if len(changes) == 0:
2670
if not self.silent:
2671
print "No changes to import!"
2492
- return True
2672
+ else:
2673
+ if not self.silent and not self.detectBranches:
2674
+ print "Import destination: %s" % self.branch
2675
2494
- if not self.silent and not self.detectBranches:
2495
- print "Import destination: %s" % self.branch
2676
+ self.updatedBranches = set()
2677
2497
- self.updatedBranches = set()
2678
+ self.importChanges(changes)
2679
2499
- self.importChanges(changes)
2680
+ if not self.silent:
2681
+ print ""
2682
+ if len(self.updatedBranches) > 0:
2683
+ sys.stdout.write("Updated branches: ")
2684
+ for b in self.updatedBranches:
2685
+ sys.stdout.write("%s " % b)
2686
+ sys.stdout.write("\n")
2687
2501
- if not self.silent:
2502
- print ""
2503
- if len(self.updatedBranches) > 0:
2504
- sys.stdout.write("Updated branches: ")
2505
- for b in self.updatedBranches:
2506
- sys.stdout.write("%s " % b)
2507
- sys.stdout.write("\n")
2688
+ if gitConfig("git-p4.importLabels", "--bool") == "true":
2689
+ self.importLabels = true
2690
+
2691
+ if self.importLabels:
2692
+ p4Labels = getP4Labels(self.depotPaths)
2693
+ gitTags = getGitTags()
2694
+
2695
+ missingP4Labels = p4Labels - gitTags
2696
+ self.importP4Labels(self.gitStream, missingP4Labels)
2697
2698
self.gitStream.close()
2699
if importProcess.wait() != 0:
2712
class P4Rebase(Command):
2713
def __init__(self):
2714
Command.__init__(self)
2526
- self.options = [ ]
2715
+ self.options = [
2716
+ optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
2717
+ optparse.make_option("--verbose", dest="verbose", action="store_true"),
2718
+ ]
2719
+ self.verbose = False
2720
+ self.importLabels = False
2721
self.description = ("Fetches the latest revision from perforce and "
2722
+ "rebases the current work (branch) against it")
2529
- self.verbose = False
2723
2724
def run(self, args):
2725
sync = P4Sync()
2726
+ sync.importLabels = self.importLabels
2727
sync.run([])
2728
2729
return self.rebase()