316
results = p4CmdList(["changes", "-m", "1"], skip_info=True)
317
return int(results[0]['change'])
318
319
-def p4_describe(change):
319
+def p4_describe(change, shelved=False):
320
"""Make sure it returns a valid result by checking for
321
the presence of field "time". Return a dict of the
322
results."""
323
324
- ds = p4CmdList(["describe", "-s", str(change)], skip_info=True)
324
+ cmd = ["describe", "-s"]
325
+ if shelved:
326
+ cmd += ["-S"]
327
+ cmd += [str(change)]
328
+
329
+ ds = p4CmdList(cmd, skip_info=True)
330
if len(ds) != 1:
331
die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
332
667
stderr=subprocess.PIPE, stdout=subprocess.PIPE);
668
return proc.wait() == 0;
669
670
+def gitUpdateRef(ref, newvalue):
671
+ subprocess.check_call(["git", "update-ref", ref, newvalue])
672
+
673
+def gitDeleteRef(ref):
674
+ subprocess.check_call(["git", "update-ref", "-d", ref])
675
+
676
_gitConfig = {}
677
678
def gitConfig(key, typeSpecifier=None):
2422
self.tempBranches = []
2423
self.tempBranchLocation = "refs/git-p4-tmp"
2424
self.largeFileSystem = None
2425
+ self.suppress_meta_comment = False
2426
2427
if gitConfig('git-p4.largeFileSystem'):
2428
largeFileSystemConstructor = globals()[gitConfig('git-p4.largeFileSystem')]
2433
if gitConfig("git-p4.syncFromOrigin") == "false":
2434
self.syncWithOrigin = False
2435
2436
+ self.depotPaths = []
2437
+ self.changeRange = ""
2438
+ self.previousDepotPaths = []
2439
+ self.hasOrigin = False
2440
+
2441
+ # map from branch depot path to parent branch
2442
+ self.knownBranches = {}
2443
+ self.initialParents = {}
2444
+
2445
+ self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
2446
+ self.labels = {}
2447
+
2448
# Force a checkpoint in fast-import and wait for it to finish
2449
def checkpoint(self):
2450
self.gitStream.write("checkpoint\n\n")
2453
if self.verbose:
2454
print "checkpoint finished: " + out
2455
2432
- def extractFilesFromCommit(self, commit):
2456
+ def cmp_shelved(self, path, filerev, revision):
2457
+ """ Determine if a path at revision #filerev is the same as the file
2458
+ at revision @revision for a shelved changelist. If they don't match,
2459
+ unshelving won't be safe (we will get other changes mixed in).
2460
+
2461
+ This is comparing the revision that the shelved changelist is *based* on, not
2462
+ the shelved changelist itself.
2463
+ """
2464
+ ret = p4Cmd(["diff2", "{0}#{1}".format(path, filerev), "{0}@{1}".format(path, revision)])
2465
+ if verbose:
2466
+ print("p4 diff2 path %s filerev %s revision %s => %s" % (path, filerev, revision, ret))
2467
+ return ret["status"] == "identical"
2468
+
2469
+ def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0, origin_revision = 0):
2470
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
2471
for path in self.cloneExclude]
2472
files = []
2489
file["rev"] = commit["rev%s" % fnum]
2490
file["action"] = commit["action%s" % fnum]
2491
file["type"] = commit["type%s" % fnum]
2492
+ if shelved:
2493
+ file["shelved_cl"] = int(shelved_cl)
2494
+
2495
+ # For shelved changelists, check that the revision of each file that the
2496
+ # shelve was based on matches the revision that we are using for the
2497
+ # starting point for git-fast-import (self.initialParent). Otherwise
2498
+ # the resulting diff will contain deltas from multiple commits.
2499
+
2500
+ if file["action"] != "add" and \
2501
+ not self.cmp_shelved(path, file["rev"], origin_revision):
2502
+ sys.exit("change {0} not based on {1} for {2}, cannot unshelve".format(
2503
+ commit["change"], self.initialParent, path))
2504
+
2505
files.append(file)
2506
fnum = fnum + 1
2507
return files
2793
def streamP4FilesCbSelf(entry):
2794
self.streamP4FilesCb(entry)
2795
2746
- fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
2796
+ fileArgs = []
2797
+ for f in filesToRead:
2798
+ if 'shelved_cl' in f:
2799
+ # Handle shelved CLs using the "p4 print file@=N" syntax to print
2800
+ # the contents
2801
+ fileArg = '%s@=%d' % (f['path'], f['shelved_cl'])
2802
+ else:
2803
+ fileArg = '%s#%s' % (f['path'], f['rev'])
2804
+
2805
+ fileArgs.append(fileArg)
2806
2807
p4CmdList(["-x", "-", "print"],
2808
stdin=fileArgs,
2903
self.gitStream.write(details["desc"])
2904
if len(jobs) > 0:
2905
self.gitStream.write("\nJobs: %s" % (' '.join(jobs)))
2847
- self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
2848
- (','.join(self.branchPrefixes), details["change"]))
2849
- if len(details['options']) > 0:
2850
- self.gitStream.write(": options = %s" % details['options'])
2851
- self.gitStream.write("]\nEOT\n\n")
2906
+
2907
+ if not self.suppress_meta_comment:
2908
+ self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
2909
+ (','.join(self.branchPrefixes), details["change"]))
2910
+ if len(details['options']) > 0:
2911
+ self.gitStream.write(": options = %s" % details['options'])
2912
+ self.gitStream.write("]\n")
2913
+
2914
+ self.gitStream.write("EOT\n\n")
2915
2916
if len(parent) > 0:
2917
if self.verbose:
3225
else:
3226
return None
3227
3165
- def importChanges(self, changes):
3228
+ def importChanges(self, changes, shelved=False, origin_revision=0):
3229
cnt = 1
3230
for change in changes:
3168
- description = p4_describe(change)
3231
+ description = p4_describe(change, shelved)
3232
self.updateOptionDict(description)
3233
3234
if not self.silent:
3298
print "Parent of %s not found. Committing into head of %s" % (branch, parent)
3299
self.commit(description, filesForCommit, branch, parent)
3300
else:
3238
- files = self.extractFilesFromCommit(description)
3301
+ files = self.extractFilesFromCommit(description, shelved, change, origin_revision)
3302
self.commit(description, files, self.branch,
3303
self.initialParent)
3304
# only needed once, to connect to the previous commit
3363
print "IO error with git fast-import. Is your git version recent enough?"
3364
print self.gitError.read()
3365
3366
+ def openStreams(self):
3367
+ self.importProcess = subprocess.Popen(["git", "fast-import"],
3368
+ stdin=subprocess.PIPE,
3369
+ stdout=subprocess.PIPE,
3370
+ stderr=subprocess.PIPE);
3371
+ self.gitOutput = self.importProcess.stdout
3372
+ self.gitStream = self.importProcess.stdin
3373
+ self.gitError = self.importProcess.stderr
3374
3304
- def run(self, args):
3305
- self.depotPaths = []
3306
- self.changeRange = ""
3307
- self.previousDepotPaths = []
3308
- self.hasOrigin = False
3309
-
3310
- # map from branch depot path to parent branch
3311
- self.knownBranches = {}
3312
- self.initialParents = {}
3375
+ def closeStreams(self):
3376
+ self.gitStream.close()
3377
+ if self.importProcess.wait() != 0:
3378
+ die("fast-import failed: %s" % self.gitError.read())
3379
+ self.gitOutput.close()
3380
+ self.gitError.close()
3381
3382
+ def run(self, args):
3383
if self.importIntoRemotes:
3384
self.refPrefix = "refs/remotes/p4/"
3385
else:
3566
b = b[len(self.projectName):]
3567
self.createdBranches.add(b)
3568
3500
- self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
3501
-
3502
- self.importProcess = subprocess.Popen(["git", "fast-import"],
3503
- stdin=subprocess.PIPE,
3504
- stdout=subprocess.PIPE,
3505
- stderr=subprocess.PIPE);
3506
- self.gitOutput = self.importProcess.stdout
3507
- self.gitStream = self.importProcess.stdin
3508
- self.gitError = self.importProcess.stderr
3569
+ self.openStreams()
3570
3571
if revision:
3572
self.importHeadRevision(revision)
3646
missingP4Labels = p4Labels - gitTags
3647
self.importP4Labels(self.gitStream, missingP4Labels)
3648
3588
- self.gitStream.close()
3589
- if self.importProcess.wait() != 0:
3590
- die("fast-import failed: %s" % self.gitError.read())
3591
- self.gitOutput.close()
3592
- self.gitError.close()
3649
+ self.closeStreams()
3650
3651
# Cleanup temporary branches created during import
3652
if self.tempBranches != []:
3778
3779
return True
3780
3781
+class P4Unshelve(Command):
3782
+ def __init__(self):
3783
+ Command.__init__(self)
3784
+ self.options = []
3785
+ self.origin = "HEAD"
3786
+ self.description = "Unshelve a P4 changelist into a git commit"
3787
+ self.usage = "usage: %prog [options] changelist"
3788
+ self.options += [
3789
+ optparse.make_option("--origin", dest="origin",
3790
+ help="Use this base revision instead of the default (%s)" % self.origin),
3791
+ ]
3792
+ self.verbose = False
3793
+ self.noCommit = False
3794
+ self.destbranch = "refs/remotes/p4/unshelved"
3795
+
3796
+ def renameBranch(self, branch_name):
3797
+ """ Rename the existing branch to branch_name.N
3798
+ """
3799
+
3800
+ found = True
3801
+ for i in range(0,1000):
3802
+ backup_branch_name = "{0}.{1}".format(branch_name, i)
3803
+ if not gitBranchExists(backup_branch_name):
3804
+ gitUpdateRef(backup_branch_name, branch_name) # copy ref to backup
3805
+ gitDeleteRef(branch_name)
3806
+ found = True
3807
+ print("renamed old unshelve branch to {0}".format(backup_branch_name))
3808
+ break
3809
+
3810
+ if not found:
3811
+ sys.exit("gave up trying to rename existing branch {0}".format(sync.branch))
3812
+
3813
+ def findLastP4Revision(self, starting_point):
3814
+ """ Look back from starting_point for the first commit created by git-p4
3815
+ to find the P4 commit we are based on, and the depot-paths.
3816
+ """
3817
+
3818
+ for parent in (range(65535)):
3819
+ log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent))
3820
+ settings = extractSettingsGitLog(log)
3821
+ if settings.has_key('change'):
3822
+ return settings
3823
+
3824
+ sys.exit("could not find git-p4 commits in {0}".format(self.origin))
3825
+
3826
+ def run(self, args):
3827
+ if len(args) != 1:
3828
+ return False
3829
+
3830
+ if not gitBranchExists(self.origin):
3831
+ sys.exit("origin branch {0} does not exist".format(self.origin))
3832
+
3833
+ sync = P4Sync()
3834
+ changes = args
3835
+ sync.initialParent = self.origin
3836
+
3837
+ # use the first change in the list to construct the branch to unshelve into
3838
+ change = changes[0]
3839
+
3840
+ # if the target branch already exists, rename it
3841
+ branch_name = "{0}/{1}".format(self.destbranch, change)
3842
+ if gitBranchExists(branch_name):
3843
+ self.renameBranch(branch_name)
3844
+ sync.branch = branch_name
3845
+
3846
+ sync.verbose = self.verbose
3847
+ sync.suppress_meta_comment = True
3848
+
3849
+ settings = self.findLastP4Revision(self.origin)
3850
+ origin_revision = settings['change']
3851
+ sync.depotPaths = settings['depot-paths']
3852
+ sync.branchPrefixes = sync.depotPaths
3853
+
3854
+ sync.openStreams()
3855
+ sync.loadUserMapFromCache()
3856
+ sync.silent = True
3857
+ sync.importChanges(changes, shelved=True, origin_revision=origin_revision)
3858
+ sync.closeStreams()
3859
+
3860
+ print("unshelved changelist {0} into {1}".format(change, branch_name))
3861
+
3862
+ return True
3863
+
3864
class P4Branches(Command):
3865
def __init__(self):
3866
Command.__init__(self)
3915
"rebase" : P4Rebase,
3916
"clone" : P4Clone,
3917
"rollback" : P4RollBack,
3778
- "branches" : P4Branches
3918
+ "branches" : P4Branches,
3919
+ "unshelve" : P4Unshelve,
3920
}
3921
3922