@@ -47,8 +47,10 @@ def __str__(self):
47
47
# Only labels/tags matching this will be imported/exported
48
48
defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
49
49
50
- # Grab changes in blocks of this many revisions, unless otherwise requested
51
- defaultBlockSize = 512
50
+ # The block size is reduced automatically if required
51
+ defaultBlockSize = 1 << 20
52
+
53
+ p4_access_checked = False
52
54
53
55
def p4_build_cmd (cmd ):
54
56
"""Build a suitable p4 command line.
@@ -91,6 +93,13 @@ def p4_build_cmd(cmd):
91
93
real_cmd = ' ' .join (real_cmd ) + ' ' + cmd
92
94
else :
93
95
real_cmd += cmd
96
+
97
+ # now check that we can actually talk to the server
98
+ global p4_access_checked
99
+ if not p4_access_checked :
100
+ p4_access_checked = True # suppress access checks in p4_check_access itself
101
+ p4_check_access ()
102
+
94
103
return real_cmd
95
104
96
105
def git_dir (path ):
@@ -264,6 +273,52 @@ def p4_system(cmd):
264
273
if retcode :
265
274
raise CalledProcessError (retcode , real_cmd )
266
275
276
+ def die_bad_access (s ):
277
+ die ("failure accessing depot: {0}" .format (s .rstrip ()))
278
+
279
+ def p4_check_access (min_expiration = 1 ):
280
+ """ Check if we can access Perforce - account still logged in
281
+ """
282
+ results = p4CmdList (["login" , "-s" ])
283
+
284
+ if len (results ) == 0 :
285
+ # should never get here: always get either some results, or a p4ExitCode
286
+ assert ("could not parse response from perforce" )
287
+
288
+ result = results [0 ]
289
+
290
+ if 'p4ExitCode' in result :
291
+ # p4 returned non-zero status, e.g. P4PORT invalid, or p4 not in path
292
+ die_bad_access ("could not run p4" )
293
+
294
+ code = result .get ("code" )
295
+ if not code :
296
+ # we get here if we couldn't connect and there was nothing to unmarshal
297
+ die_bad_access ("could not connect" )
298
+
299
+ elif code == "stat" :
300
+ expiry = result .get ("TicketExpiration" )
301
+ if expiry :
302
+ expiry = int (expiry )
303
+ if expiry > min_expiration :
304
+ # ok to carry on
305
+ return
306
+ else :
307
+ die_bad_access ("perforce ticket expires in {0} seconds" .format (expiry ))
308
+
309
+ else :
310
+ # account without a timeout - all ok
311
+ return
312
+
313
+ elif code == "error" :
314
+ data = result .get ("data" )
315
+ if data :
316
+ die_bad_access ("p4 error: {0}" .format (data ))
317
+ else :
318
+ die_bad_access ("unknown error" )
319
+ else :
320
+ die_bad_access ("unknown error code {0}" .format (code ))
321
+
267
322
_p4_version_string = None
268
323
def p4_version_string ():
269
324
"""Read the version string, showing just the last line, which
@@ -511,10 +566,30 @@ def isModeExec(mode):
511
566
# otherwise False.
512
567
return mode [- 3 :] == "755"
513
568
569
+ class P4Exception (Exception ):
570
+ """ Base class for exceptions from the p4 client """
571
+ def __init__ (self , exit_code ):
572
+ self .p4ExitCode = exit_code
573
+
574
+ class P4ServerException (P4Exception ):
575
+ """ Base class for exceptions where we get some kind of marshalled up result from the server """
576
+ def __init__ (self , exit_code , p4_result ):
577
+ super (P4ServerException , self ).__init__ (exit_code )
578
+ self .p4_result = p4_result
579
+ self .code = p4_result [0 ]['code' ]
580
+ self .data = p4_result [0 ]['data' ]
581
+
582
+ class P4RequestSizeException (P4ServerException ):
583
+ """ One of the maxresults or maxscanrows errors """
584
+ def __init__ (self , exit_code , p4_result , limit ):
585
+ super (P4RequestSizeException , self ).__init__ (exit_code , p4_result )
586
+ self .limit = limit
587
+
514
588
def isModeExecChanged (src_mode , dst_mode ):
515
589
return isModeExec (src_mode ) != isModeExec (dst_mode )
516
590
517
- def p4CmdList (cmd , stdin = None , stdin_mode = 'w+b' , cb = None , skip_info = False ):
591
+ def p4CmdList (cmd , stdin = None , stdin_mode = 'w+b' , cb = None , skip_info = False ,
592
+ errors_as_exceptions = False ):
518
593
519
594
if isinstance (cmd ,basestring ):
520
595
cmd = "-G " + cmd
@@ -561,9 +636,25 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False):
561
636
pass
562
637
exitCode = p4 .wait ()
563
638
if exitCode != 0 :
564
- entry = {}
565
- entry ["p4ExitCode" ] = exitCode
566
- result .append (entry )
639
+ if errors_as_exceptions :
640
+ if len (result ) > 0 :
641
+ data = result [0 ].get ('data' )
642
+ if data :
643
+ m = re .search ('Too many rows scanned \(over (\d+)\)' , data )
644
+ if not m :
645
+ m = re .search ('Request too large \(over (\d+)\)' , data )
646
+
647
+ if m :
648
+ limit = int (m .group (1 ))
649
+ raise P4RequestSizeException (exitCode , result , limit )
650
+
651
+ raise P4ServerException (exitCode , result )
652
+ else :
653
+ raise P4Exception (exitCode )
654
+ else :
655
+ entry = {}
656
+ entry ["p4ExitCode" ] = exitCode
657
+ result .append (entry )
567
658
568
659
return result
569
660
@@ -868,7 +959,7 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
868
959
try :
869
960
(changeStart , changeEnd ) = p4ParseNumericChangeRange (parts )
870
961
block_size = chooseBlockSize (requestedBlockSize )
871
- except :
962
+ except ValueError :
872
963
changeStart = parts [0 ][1 :]
873
964
changeEnd = parts [1 ]
874
965
if requestedBlockSize :
@@ -878,7 +969,8 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
878
969
changes = set ()
879
970
880
971
# Retrieve changes a block at a time, to prevent running
881
- # into a MaxResults/MaxScanRows error from the server.
972
+ # into a MaxResults/MaxScanRows error from the server. If
973
+ # we _do_ hit one of those errors, turn down the block size
882
974
883
975
while True :
884
976
cmd = ['changes' ]
@@ -892,10 +984,24 @@ def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
892
984
for p in depotPaths :
893
985
cmd += ["%s...@%s" % (p , revisionRange )]
894
986
987
+ # fetch the changes
988
+ try :
989
+ result = p4CmdList (cmd , errors_as_exceptions = True )
990
+ except P4RequestSizeException as e :
991
+ if not block_size :
992
+ block_size = e .limit
993
+ elif block_size > e .limit :
994
+ block_size = e .limit
995
+ else :
996
+ block_size = max (2 , block_size // 2 )
997
+
998
+ if verbose : print ("block size error, retrying with block size {0}" .format (block_size ))
999
+ continue
1000
+ except P4Exception as e :
1001
+ die ('Error retrieving changes description ({0})' .format (e .p4ExitCode ))
1002
+
895
1003
# Insert changes in chronological order
896
- for entry in reversed (p4CmdList (cmd )):
897
- if entry .has_key ('p4ExitCode' ):
898
- die ('Error retrieving changes descriptions ({})' .format (entry ['p4ExitCode' ]))
1004
+ for entry in reversed (result ):
899
1005
if not entry .has_key ('change' ):
900
1006
continue
901
1007
changes .add (int (entry ['change' ]))
@@ -1363,7 +1469,14 @@ def __init__(self):
1363
1469
optparse .make_option ("--update-shelve" , dest = "update_shelve" , action = "append" , type = "int" ,
1364
1470
metavar = "CHANGELIST" ,
1365
1471
help = "update an existing shelved changelist, implies --shelve, "
1366
- "repeat in-order for multiple shelved changelists" )
1472
+ "repeat in-order for multiple shelved changelists" ),
1473
+ optparse .make_option ("--commit" , dest = "commit" , metavar = "COMMIT" ,
1474
+ help = "submit only the specified commit(s), one commit or xxx..xxx" ),
1475
+ optparse .make_option ("--disable-rebase" , dest = "disable_rebase" , action = "store_true" ,
1476
+ help = "Disable rebase after submit is completed. Can be useful if you "
1477
+ "work from a local git branch that is not master" ),
1478
+ optparse .make_option ("--disable-p4sync" , dest = "disable_p4sync" , action = "store_true" ,
1479
+ help = "Skip Perforce sync of p4/master after submit or shelve" ),
1367
1480
]
1368
1481
self .description = "Submit changes from git to the perforce depot."
1369
1482
self .usage += " [name of git branch to submit into perforce depot]"
@@ -1373,6 +1486,9 @@ def __init__(self):
1373
1486
self .dry_run = False
1374
1487
self .shelve = False
1375
1488
self .update_shelve = list ()
1489
+ self .commit = ""
1490
+ self .disable_rebase = gitConfigBool ("git-p4.disableRebase" )
1491
+ self .disable_p4sync = gitConfigBool ("git-p4.disableP4Sync" )
1376
1492
self .prepare_p4_only = False
1377
1493
self .conflict_behavior = None
1378
1494
self .isWindows = (platform .system () == "Windows" )
@@ -2114,9 +2230,18 @@ def run(self, args):
2114
2230
else :
2115
2231
committish = 'HEAD'
2116
2232
2117
- for line in read_pipe_lines (["git" , "rev-list" , "--no-merges" , "%s..%s" % (self .origin , committish )]):
2118
- commits .append (line .strip ())
2119
- commits .reverse ()
2233
+ if self .commit != "" :
2234
+ if self .commit .find (".." ) != - 1 :
2235
+ limits_ish = self .commit .split (".." )
2236
+ for line in read_pipe_lines (["git" , "rev-list" , "--no-merges" , "%s..%s" % (limits_ish [0 ], limits_ish [1 ])]):
2237
+ commits .append (line .strip ())
2238
+ commits .reverse ()
2239
+ else :
2240
+ commits .append (self .commit )
2241
+ else :
2242
+ for line in read_pipe_lines (["git" , "rev-list" , "--no-merges" , "%s..%s" % (self .origin , committish )]):
2243
+ commits .append (line .strip ())
2244
+ commits .reverse ()
2120
2245
2121
2246
if self .preserveUser or gitConfigBool ("git-p4.skipUserNameCheck" ):
2122
2247
self .checkAuthorship = False
@@ -2224,10 +2349,14 @@ def run(self, args):
2224
2349
sync = P4Sync ()
2225
2350
if self .branch :
2226
2351
sync .branch = self .branch
2227
- sync .run ([])
2352
+ if self .disable_p4sync :
2353
+ sync .sync_origin_only ()
2354
+ else :
2355
+ sync .run ([])
2228
2356
2229
- rebase = P4Rebase ()
2230
- rebase .rebase ()
2357
+ if not self .disable_rebase :
2358
+ rebase = P4Rebase ()
2359
+ rebase .rebase ()
2231
2360
2232
2361
else :
2233
2362
if len (applied ) == 0 :
@@ -3307,6 +3436,14 @@ def importChanges(self, changes, shelved=False, origin_revision=0):
3307
3436
print self .gitError .read ()
3308
3437
sys .exit (1 )
3309
3438
3439
+ def sync_origin_only (self ):
3440
+ if self .syncWithOrigin :
3441
+ self .hasOrigin = originP4BranchesExist ()
3442
+ if self .hasOrigin :
3443
+ if not self .silent :
3444
+ print 'Syncing with origin first, using "git fetch origin"'
3445
+ system ("git fetch origin" )
3446
+
3310
3447
def importHeadRevision (self , revision ):
3311
3448
print "Doing initial import of %s from revision %s into %s" % (' ' .join (self .depotPaths ), revision , self .branch )
3312
3449
@@ -3385,12 +3522,7 @@ def run(self, args):
3385
3522
else :
3386
3523
self .refPrefix = "refs/heads/p4/"
3387
3524
3388
- if self .syncWithOrigin :
3389
- self .hasOrigin = originP4BranchesExist ()
3390
- if self .hasOrigin :
3391
- if not self .silent :
3392
- print 'Syncing with origin first, using "git fetch origin"'
3393
- system ("git fetch origin" )
3525
+ self .sync_origin_only ()
3394
3526
3395
3527
branch_arg_given = bool (self .branch )
3396
3528
if len (self .branch ) == 0 :
0 commit comments