@@ -316,12 +316,17 @@ def p4_last_change():
316316 results = p4CmdList (["changes" , "-m" , "1" ], skip_info = True )
317317 return int (results [0 ]['change' ])
318318
319- def p4_describe (change ):
319+ def p4_describe (change , shelved = False ):
320320 """Make sure it returns a valid result by checking for
321321 the presence of field "time". Return a dict of the
322322 results."""
323323
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 )
325330 if len (ds ) != 1 :
326331 die ("p4 describe -s %d did not return 1 result: %s" % (change , str (ds )))
327332
@@ -662,6 +667,12 @@ def gitBranchExists(branch):
662667 stderr = subprocess .PIPE , stdout = subprocess .PIPE );
663668 return proc .wait () == 0 ;
664669
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+
665676_gitConfig = {}
666677
667678def gitConfig (key , typeSpecifier = None ):
@@ -2411,6 +2422,7 @@ def __init__(self):
24112422 self .tempBranches = []
24122423 self .tempBranchLocation = "refs/git-p4-tmp"
24132424 self .largeFileSystem = None
2425+ self .suppress_meta_comment = False
24142426
24152427 if gitConfig ('git-p4.largeFileSystem' ):
24162428 largeFileSystemConstructor = globals ()[gitConfig ('git-p4.largeFileSystem' )]
@@ -2421,6 +2433,18 @@ def __init__(self):
24212433 if gitConfig ("git-p4.syncFromOrigin" ) == "false" :
24222434 self .syncWithOrigin = False
24232435
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+
24242448 # Force a checkpoint in fast-import and wait for it to finish
24252449 def checkpoint (self ):
24262450 self .gitStream .write ("checkpoint\n \n " )
@@ -2429,7 +2453,20 @@ def checkpoint(self):
24292453 if self .verbose :
24302454 print "checkpoint finished: " + out
24312455
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 ):
24332470 self .cloneExclude = [re .sub (r"\.\.\.$" , "" , path )
24342471 for path in self .cloneExclude ]
24352472 files = []
@@ -2452,6 +2489,19 @@ def extractFilesFromCommit(self, commit):
24522489 file ["rev" ] = commit ["rev%s" % fnum ]
24532490 file ["action" ] = commit ["action%s" % fnum ]
24542491 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+
24552505 files .append (file )
24562506 fnum = fnum + 1
24572507 return files
@@ -2743,7 +2793,16 @@ def streamP4Files(self, files):
27432793 def streamP4FilesCbSelf (entry ):
27442794 self .streamP4FilesCb (entry )
27452795
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 )
27472806
27482807 p4CmdList (["-x" , "-" , "print" ],
27492808 stdin = fileArgs ,
@@ -2844,11 +2903,15 @@ def commit(self, details, files, branch, parent = ""):
28442903 self .gitStream .write (details ["desc" ])
28452904 if len (jobs ) > 0 :
28462905 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 " )
28522915
28532916 if len (parent ) > 0 :
28542917 if self .verbose :
@@ -3162,10 +3225,10 @@ def searchParent(self, parent, branch, target):
31623225 else :
31633226 return None
31643227
3165- def importChanges (self , changes ):
3228+ def importChanges (self , changes , shelved = False , origin_revision = 0 ):
31663229 cnt = 1
31673230 for change in changes :
3168- description = p4_describe (change )
3231+ description = p4_describe (change , shelved )
31693232 self .updateOptionDict (description )
31703233
31713234 if not self .silent :
@@ -3235,7 +3298,7 @@ def importChanges(self, changes):
32353298 print "Parent of %s not found. Committing into head of %s" % (branch , parent )
32363299 self .commit (description , filesForCommit , branch , parent )
32373300 else :
3238- files = self .extractFilesFromCommit (description )
3301+ files = self .extractFilesFromCommit (description , shelved , change , origin_revision )
32393302 self .commit (description , files , self .branch ,
32403303 self .initialParent )
32413304 # only needed once, to connect to the previous commit
@@ -3300,17 +3363,23 @@ def importHeadRevision(self, revision):
33003363 print "IO error with git fast-import. Is your git version recent enough?"
33013364 print self .gitError .read ()
33023365
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
33033374
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 ()
33133381
3382+ def run (self , args ):
33143383 if self .importIntoRemotes :
33153384 self .refPrefix = "refs/remotes/p4/"
33163385 else :
@@ -3497,15 +3566,7 @@ def run(self, args):
34973566 b = b [len (self .projectName ):]
34983567 self .createdBranches .add (b )
34993568
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 ()
35093570
35103571 if revision :
35113572 self .importHeadRevision (revision )
@@ -3585,11 +3646,7 @@ def run(self, args):
35853646 missingP4Labels = p4Labels - gitTags
35863647 self .importP4Labels (self .gitStream , missingP4Labels )
35873648
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 ()
35933650
35943651 # Cleanup temporary branches created during import
35953652 if self .tempBranches != []:
@@ -3721,6 +3778,89 @@ def run(self, args):
37213778
37223779 return True
37233780
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+
37243864class P4Branches (Command ):
37253865 def __init__ (self ):
37263866 Command .__init__ (self )
@@ -3775,7 +3915,8 @@ def printUsage(commands):
37753915 "rebase" : P4Rebase ,
37763916 "clone" : P4Clone ,
37773917 "rollback" : P4RollBack ,
3778- "branches" : P4Branches
3918+ "branches" : P4Branches ,
3919+ "unshelve" : P4Unshelve ,
37793920}
37803921
37813922
0 commit comments