@@ -1169,6 +1169,218 @@ class P4Submit(Command, P4UserMap):
1169
1169
1170
1170
return True
1171
1171
1172
+ class View (object ):
1173
+ """Represent a p4 view ("p4 help views"), and map files in a
1174
+ repo according to the view."""
1175
+
1176
+ class Path (object ):
1177
+ """A depot or client path, possibly containing wildcards.
1178
+ The only one supported is ... at the end, currently.
1179
+ Initialize with the full path, with //depot or //client."""
1180
+
1181
+ def __init__ (self , path , is_depot ):
1182
+ self .path = path
1183
+ self .is_depot = is_depot
1184
+ self .find_wildcards ()
1185
+ # remember the prefix bit, useful for relative mappings
1186
+ m = re .match ("(//[^/]+/)" , self .path )
1187
+ if not m :
1188
+ die ("Path %s does not start with //prefix/" % self .path )
1189
+ prefix = m .group (1 )
1190
+ if not self .is_depot :
1191
+ # strip //client/ on client paths
1192
+ self .path = self .path [len (prefix ):]
1193
+
1194
+ def find_wildcards (self ):
1195
+ """Make sure wildcards are valid, and set up internal
1196
+ variables."""
1197
+
1198
+ self .ends_triple_dot = False
1199
+ # There are three wildcards allowed in p4 views
1200
+ # (see "p4 help views"). This code knows how to
1201
+ # handle "..." (only at the end), but cannot deal with
1202
+ # "%%n" or "*". Only check the depot_side, as p4 should
1203
+ # validate that the client_side matches too.
1204
+ if re .search (r'%%[1-9]' , self .path ):
1205
+ die ("Can't handle %%n wildcards in view: %s" % self .path )
1206
+ if self .path .find ("*" ) >= 0 :
1207
+ die ("Can't handle * wildcards in view: %s" % self .path )
1208
+ triple_dot_index = self .path .find ("..." )
1209
+ if triple_dot_index >= 0 :
1210
+ if not self .path .endswith ("..." ):
1211
+ die ("Can handle ... wildcard only at end of path: %s" %
1212
+ self .path )
1213
+ self .ends_triple_dot = True
1214
+
1215
+ def ensure_compatible (self , other_path ):
1216
+ """Make sure the wildcards agree."""
1217
+ if self .ends_triple_dot != other_path .ends_triple_dot :
1218
+ die ("Both paths must end with ... if either does;\n " +
1219
+ "paths: %s %s" % (self .path , other_path .path ))
1220
+
1221
+ def match_wildcards (self , test_path ):
1222
+ """See if this test_path matches us, and fill in the value
1223
+ of the wildcards if so. Returns a tuple of
1224
+ (True|False, wildcards[]). For now, only the ... at end
1225
+ is supported, so at most one wildcard."""
1226
+ if self .ends_triple_dot :
1227
+ dotless = self .path [:- 3 ]
1228
+ if test_path .startswith (dotless ):
1229
+ wildcard = test_path [len (dotless ):]
1230
+ return (True , [ wildcard ])
1231
+ else :
1232
+ if test_path == self .path :
1233
+ return (True , [])
1234
+ return (False , [])
1235
+
1236
+ def match (self , test_path ):
1237
+ """Just return if it matches; don't bother with the wildcards."""
1238
+ b , _ = self .match_wildcards (test_path )
1239
+ return b
1240
+
1241
+ def fill_in_wildcards (self , wildcards ):
1242
+ """Return the relative path, with the wildcards filled in
1243
+ if there are any."""
1244
+ if self .ends_triple_dot :
1245
+ return self .path [:- 3 ] + wildcards [0 ]
1246
+ else :
1247
+ return self .path
1248
+
1249
+ class Mapping (object ):
1250
+ def __init__ (self , depot_side , client_side , overlay , exclude ):
1251
+ # depot_side is without the trailing /... if it had one
1252
+ self .depot_side = View .Path (depot_side , is_depot = True )
1253
+ self .client_side = View .Path (client_side , is_depot = False )
1254
+ self .overlay = overlay # started with "+"
1255
+ self .exclude = exclude # started with "-"
1256
+ assert not (self .overlay and self .exclude )
1257
+ self .depot_side .ensure_compatible (self .client_side )
1258
+
1259
+ def __str__ (self ):
1260
+ c = " "
1261
+ if self .overlay :
1262
+ c = "+"
1263
+ if self .exclude :
1264
+ c = "-"
1265
+ return "View.Mapping: %s%s -> %s" % \
1266
+ (c , self .depot_side , self .client_side )
1267
+
1268
+ def map_depot_to_client (self , depot_path ):
1269
+ """Calculate the client path if using this mapping on the
1270
+ given depot path; does not consider the effect of other
1271
+ mappings in a view. Even excluded mappings are returned."""
1272
+ matches , wildcards = self .depot_side .match_wildcards (depot_path )
1273
+ if not matches :
1274
+ return ""
1275
+ client_path = self .client_side .fill_in_wildcards (wildcards )
1276
+ return client_path
1277
+
1278
+ #
1279
+ # View methods
1280
+ #
1281
+ def __init__ (self ):
1282
+ self .mappings = []
1283
+
1284
+ def append (self , view_line ):
1285
+ """Parse a view line, splitting it into depot and client
1286
+ sides. Append to self.mappings, preserving order."""
1287
+
1288
+ # Split the view line into exactly two words. P4 enforces
1289
+ # structure on these lines that simplifies this quite a bit.
1290
+ #
1291
+ # Either or both words may be double-quoted.
1292
+ # Single quotes do not matter.
1293
+ # Double-quote marks cannot occur inside the words.
1294
+ # A + or - prefix is also inside the quotes.
1295
+ # There are no quotes unless they contain a space.
1296
+ # The line is already white-space stripped.
1297
+ # The two words are separated by a single space.
1298
+ #
1299
+ if view_line [0 ] == '"' :
1300
+ # First word is double quoted. Find its end.
1301
+ close_quote_index = view_line .find ('"' , 1 )
1302
+ if close_quote_index <= 0 :
1303
+ die ("No first-word closing quote found: %s" % view_line )
1304
+ depot_side = view_line [1 :close_quote_index ]
1305
+ # skip closing quote and space
1306
+ rhs_index = close_quote_index + 1 + 1
1307
+ else :
1308
+ space_index = view_line .find (" " )
1309
+ if space_index <= 0 :
1310
+ die ("No word-splitting space found: %s" % view_line )
1311
+ depot_side = view_line [0 :space_index ]
1312
+ rhs_index = space_index + 1
1313
+
1314
+ if view_line [rhs_index ] == '"' :
1315
+ # Second word is double quoted. Make sure there is a
1316
+ # double quote at the end too.
1317
+ if not view_line .endswith ('"' ):
1318
+ die ("View line with rhs quote should end with one: %s" %
1319
+ view_line )
1320
+ # skip the quotes
1321
+ client_side = view_line [rhs_index + 1 :- 1 ]
1322
+ else :
1323
+ client_side = view_line [rhs_index :]
1324
+
1325
+ # prefix + means overlay on previous mapping
1326
+ overlay = False
1327
+ if depot_side .startswith ("+" ):
1328
+ overlay = True
1329
+ depot_side = depot_side [1 :]
1330
+
1331
+ # prefix - means exclude this path
1332
+ exclude = False
1333
+ if depot_side .startswith ("-" ):
1334
+ exclude = True
1335
+ depot_side = depot_side [1 :]
1336
+
1337
+ m = View .Mapping (depot_side , client_side , overlay , exclude )
1338
+ self .mappings .append (m )
1339
+
1340
+ def map_in_client (self , depot_path ):
1341
+ """Return the relative location in the client where this
1342
+ depot file should live. Returns "" if the file should
1343
+ not be mapped in the client."""
1344
+
1345
+ paths_filled = []
1346
+ client_path = ""
1347
+
1348
+ # look at later entries first
1349
+ for m in self .mappings [::- 1 ]:
1350
+
1351
+ # see where will this path end up in the client
1352
+ p = m .map_depot_to_client (depot_path )
1353
+
1354
+ if p == "" :
1355
+ # Depot path does not belong in client. Must remember
1356
+ # this, as previous items should not cause files to
1357
+ # exist in this path either. Remember that the list is
1358
+ # being walked from the end, which has higher precedence.
1359
+ # Overlap mappings do not exclude previous mappings.
1360
+ if not m .overlay :
1361
+ paths_filled .append (m .client_side )
1362
+
1363
+ else :
1364
+ # This mapping matched; no need to search any further.
1365
+ # But, the mapping could be rejected if the client path
1366
+ # has already been claimed by an earlier mapping.
1367
+ already_mapped_in_client = False
1368
+ for f in paths_filled :
1369
+ # this is View.Path.match
1370
+ if f .match (p ):
1371
+ already_mapped_in_client = True
1372
+ break
1373
+ if not already_mapped_in_client :
1374
+ # Include this file, unless it is from a line that
1375
+ # explicitly said to exclude it.
1376
+ if not m .exclude :
1377
+ client_path = p
1378
+
1379
+ # a match, even if rejected, always stops the search
1380
+ break
1381
+
1382
+ return client_path
1383
+
1172
1384
class P4Sync (Command , P4UserMap ):
1173
1385
delete_actions = ( "delete" , "move/delete" , "purge" )
1174
1386
@@ -1216,8 +1428,7 @@ class P4Sync(Command, P4UserMap):
1216
1428
self .p4BranchesInGit = []
1217
1429
self .cloneExclude = []
1218
1430
self .useClientSpec = False
1219
- self .clientSpecDirs = []
1220
- self .haveSingleFileClientViews = False
1431
+ self .clientSpecDirs = None
1221
1432
1222
1433
if gitConfig ("git-p4.syncFromOrigin" ) == "false" :
1223
1434
self .syncWithOrigin = False
@@ -1268,30 +1479,7 @@ class P4Sync(Command, P4UserMap):
1268
1479
1269
1480
def stripRepoPath (self , path , prefixes ):
1270
1481
if self .useClientSpec :
1271
-
1272
- # if using the client spec, we use the output directory
1273
- # specified in the client. For example, a view
1274
- # //depot/foo/branch/... //client/branch/foo/...
1275
- # will end up putting all foo/branch files into
1276
- # branch/foo/
1277
- for val in self .clientSpecDirs :
1278
- if self .haveSingleFileClientViews and len (path ) == abs (val [1 ][0 ]) and path == val [0 ]:
1279
- # since 'path' is a depot file path, if it matches the LeftMap,
1280
- # then the View is a single file mapping, so use the entire rightMap
1281
- # first two tests above avoid the last == test for common cases
1282
- path = val [1 ][1 ]
1283
- # now strip out the client (//client/...)
1284
- path = re .sub ("^(//[^/]+/)" , '' , path )
1285
- # the rest is local client path
1286
- return path
1287
-
1288
- if path .startswith (val [0 ]):
1289
- # replace the depot path with the client path
1290
- path = path .replace (val [0 ], val [1 ][1 ])
1291
- # now strip out the client (//client/...)
1292
- path = re .sub ("^(//[^/]+/)" , '' , path )
1293
- # the rest is all path
1294
- return path
1482
+ return self .clientSpecDirs .map_in_client (path )
1295
1483
1296
1484
if self .keepRepoPath :
1297
1485
prefixes = [re .sub ("^(//[^/]+/).*" , r'\1' , prefixes [0 ])]
@@ -1441,19 +1629,17 @@ class P4Sync(Command, P4UserMap):
1441
1629
filesToDelete = []
1442
1630
1443
1631
for f in files :
1444
- includeFile = True
1445
- for val in self .clientSpecDirs :
1446
- if f ['path' ].startswith (val [0 ]):
1447
- if val [1 ][0 ] <= 0 :
1448
- includeFile = False
1449
- break
1632
+ # if using a client spec, only add the files that have
1633
+ # a path in the client
1634
+ if self .clientSpecDirs :
1635
+ if self .clientSpecDirs .map_in_client (f ['path' ]) == "" :
1636
+ continue
1450
1637
1451
- if includeFile :
1452
- filesForCommit .append (f )
1453
- if f ['action' ] in self .delete_actions :
1454
- filesToDelete .append (f )
1455
- else :
1456
- filesToRead .append (f )
1638
+ filesForCommit .append (f )
1639
+ if f ['action' ] in self .delete_actions :
1640
+ filesToDelete .append (f )
1641
+ else :
1642
+ filesToRead .append (f )
1457
1643
1458
1644
# deleted files...
1459
1645
for f in filesToDelete :
@@ -1892,60 +2078,31 @@ class P4Sync(Command, P4UserMap):
1892
2078
1893
2079
1894
2080
def getClientSpec (self ):
1895
- specList = p4CmdList ( "client -o" )
1896
- temp = {}
1897
- for entry in specList :
1898
- for k ,v in entry .iteritems ():
1899
- if k .startswith ("View" ):
1900
-
1901
- # p4 has these %%1 to %%9 arguments in specs to
1902
- # reorder paths; which we can't handle (yet :)
1903
- if re .search ('%%\d' , v ) != None :
1904
- print "Sorry, can't handle %%n arguments in client specs"
1905
- sys .exit (1 )
1906
- if re .search ('\*' , v ) != None :
1907
- print "Sorry, can't handle * mappings in client specs"
1908
- sys .exit (1 )
1909
-
1910
- if v .startswith ('"' ):
1911
- start = 1
1912
- else :
1913
- start = 0
1914
- index = v .find ("..." )
1915
-
1916
- # save the "client view"; i.e the RHS of the view
1917
- # line that tells the client where to put the
1918
- # files for this view.
1919
-
1920
- # check for individual file mappings - those which have no '...'
1921
- if index < 0 :
1922
- v ,cv = v .strip ().split ()
1923
- v = v [start :]
1924
- self .haveSingleFileClientViews = True
1925
- else :
1926
- cv = v [index + 3 :].strip () # +3 to remove previous '...'
1927
- cv = cv [:- 3 ]
1928
- v = v [start :index ]
1929
-
1930
- # now save the view; +index means included, -index
1931
- # means it should be filtered out.
1932
- if v .startswith ("-" ):
1933
- v = v [1 :]
1934
- include = - len (v )
1935
- else :
1936
- include = len (v )
2081
+ specList = p4CmdList ("client -o" )
2082
+ if len (specList ) != 1 :
2083
+ die ('Output from "client -o" is %d lines, expecting 1' %
2084
+ len (specList ))
2085
+
2086
+ # dictionary of all client parameters
2087
+ entry = specList [0 ]
2088
+
2089
+ # just the keys that start with "View"
2090
+ view_keys = [ k for k in entry .keys () if k .startswith ("View" ) ]
2091
+
2092
+ # hold this new View
2093
+ view = View ()
1937
2094
1938
- # store the View #number for sorting
1939
- # and the View string itself (this last for documentation)
1940
- temp [v ] = (include , cv , int (k [4 :]),k )
2095
+ # append the lines, in order, to the view
2096
+ for view_num in range (len (view_keys )):
2097
+ k = "View%d" % view_num
2098
+ if k not in view_keys :
2099
+ die ("Expected view key %s missing" % k )
2100
+ view .append (entry [k ])
1941
2101
1942
- self .clientSpecDirs = temp .items ()
1943
- # Perforce ViewNN with higher #numbers override those with lower
1944
- # reverse sort on the View #number
1945
- self .clientSpecDirs .sort ( lambda x , y : y [1 ][2 ] - x [1 ][2 ] )
2102
+ self .clientSpecDirs = view
1946
2103
if self .verbose :
1947
- for val in self .clientSpecDirs :
1948
- print "clientSpecDirs: %s %s" % (val [ 0 ], val [ 1 ] )
2104
+ for i , m in enumerate ( self .clientSpecDirs . mappings ) :
2105
+ print "clientSpecDirs %d: %s" % (i , str ( m ) )
1949
2106
1950
2107
def run (self , args ):
1951
2108
self .depotPaths = []
0 commit comments