@@ -316,12 +316,17 @@ def p4_last_change():
316
316
results = p4CmdList (["changes" , "-m" , "1" ], skip_info = True )
317
317
return int (results [0 ]['change' ])
318
318
319
- def p4_describe (change ):
319
+ def p4_describe (change , shelved = False ):
320
320
"""Make sure it returns a valid result by checking for
321
321
the presence of field "time". Return a dict of the
322
322
results."""
323
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 )
325
330
if len (ds ) != 1 :
326
331
die ("p4 describe -s %d did not return 1 result: %s" % (change , str (ds )))
327
332
@@ -662,6 +667,12 @@ def gitBranchExists(branch):
662
667
stderr = subprocess .PIPE , stdout = subprocess .PIPE );
663
668
return proc .wait () == 0 ;
664
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
+
665
676
_gitConfig = {}
666
677
667
678
def gitConfig (key , typeSpecifier = None ):
@@ -2411,6 +2422,7 @@ def __init__(self):
2411
2422
self .tempBranches = []
2412
2423
self .tempBranchLocation = "refs/git-p4-tmp"
2413
2424
self .largeFileSystem = None
2425
+ self .suppress_meta_comment = False
2414
2426
2415
2427
if gitConfig ('git-p4.largeFileSystem' ):
2416
2428
largeFileSystemConstructor = globals ()[gitConfig ('git-p4.largeFileSystem' )]
@@ -2421,6 +2433,18 @@ def __init__(self):
2421
2433
if gitConfig ("git-p4.syncFromOrigin" ) == "false" :
2422
2434
self .syncWithOrigin = False
2423
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
+
2424
2448
# Force a checkpoint in fast-import and wait for it to finish
2425
2449
def checkpoint (self ):
2426
2450
self .gitStream .write ("checkpoint\n \n " )
@@ -2429,7 +2453,20 @@ def checkpoint(self):
2429
2453
if self .verbose :
2430
2454
print "checkpoint finished: " + out
2431
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 ):
2433
2470
self .cloneExclude = [re .sub (r"\.\.\.$" , "" , path )
2434
2471
for path in self .cloneExclude ]
2435
2472
files = []
@@ -2452,6 +2489,19 @@ def extractFilesFromCommit(self, commit):
2452
2489
file ["rev" ] = commit ["rev%s" % fnum ]
2453
2490
file ["action" ] = commit ["action%s" % fnum ]
2454
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
+
2455
2505
files .append (file )
2456
2506
fnum = fnum + 1
2457
2507
return files
@@ -2743,7 +2793,16 @@ def streamP4Files(self, files):
2743
2793
def streamP4FilesCbSelf (entry ):
2744
2794
self .streamP4FilesCb (entry )
2745
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 )
2747
2806
2748
2807
p4CmdList (["-x" , "-" , "print" ],
2749
2808
stdin = fileArgs ,
@@ -2844,11 +2903,15 @@ def commit(self, details, files, branch, parent = ""):
2844
2903
self .gitStream .write (details ["desc" ])
2845
2904
if len (jobs ) > 0 :
2846
2905
self .gitStream .write ("\n Jobs: %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 ("]\n EOT\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 " )
2852
2915
2853
2916
if len (parent ) > 0 :
2854
2917
if self .verbose :
@@ -3162,10 +3225,10 @@ def searchParent(self, parent, branch, target):
3162
3225
else :
3163
3226
return None
3164
3227
3165
- def importChanges (self , changes ):
3228
+ def importChanges (self , changes , shelved = False , origin_revision = 0 ):
3166
3229
cnt = 1
3167
3230
for change in changes :
3168
- description = p4_describe (change )
3231
+ description = p4_describe (change , shelved )
3169
3232
self .updateOptionDict (description )
3170
3233
3171
3234
if not self .silent :
@@ -3235,7 +3298,7 @@ def importChanges(self, changes):
3235
3298
print "Parent of %s not found. Committing into head of %s" % (branch , parent )
3236
3299
self .commit (description , filesForCommit , branch , parent )
3237
3300
else :
3238
- files = self .extractFilesFromCommit (description )
3301
+ files = self .extractFilesFromCommit (description , shelved , change , origin_revision )
3239
3302
self .commit (description , files , self .branch ,
3240
3303
self .initialParent )
3241
3304
# only needed once, to connect to the previous commit
@@ -3300,17 +3363,23 @@ def importHeadRevision(self, revision):
3300
3363
print "IO error with git fast-import. Is your git version recent enough?"
3301
3364
print self .gitError .read ()
3302
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
3303
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 ()
3313
3381
3382
+ def run (self , args ):
3314
3383
if self .importIntoRemotes :
3315
3384
self .refPrefix = "refs/remotes/p4/"
3316
3385
else :
@@ -3497,15 +3566,7 @@ def run(self, args):
3497
3566
b = b [len (self .projectName ):]
3498
3567
self .createdBranches .add (b )
3499
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 ()
3509
3570
3510
3571
if revision :
3511
3572
self .importHeadRevision (revision )
@@ -3585,11 +3646,7 @@ def run(self, args):
3585
3646
missingP4Labels = p4Labels - gitTags
3586
3647
self .importP4Labels (self .gitStream , missingP4Labels )
3587
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 ()
3593
3650
3594
3651
# Cleanup temporary branches created during import
3595
3652
if self .tempBranches != []:
@@ -3721,6 +3778,89 @@ def run(self, args):
3721
3778
3722
3779
return True
3723
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
+
3724
3864
class P4Branches (Command ):
3725
3865
def __init__ (self ):
3726
3866
Command .__init__ (self )
@@ -3775,7 +3915,8 @@ def printUsage(commands):
3775
3915
"rebase" : P4Rebase ,
3776
3916
"clone" : P4Clone ,
3777
3917
"rollback" : P4RollBack ,
3778
- "branches" : P4Branches
3918
+ "branches" : P4Branches ,
3919
+ "unshelve" : P4Unshelve ,
3779
3920
}
3780
3921
3781
3922
0 commit comments