Merge branch 'ks/p4-view-spec'

* ks/p4-view-spec:
  git p4: implement view spec wildcards with "p4 where"
  git p4 test: sanitize P4CHARSET
This commit is contained in:
Junio C Hamano 2013-09-18 11:44:50 -07:00
commit 34022ba21a
2 changed files with 62 additions and 166 deletions

225
git-p4.py
View File

@ -780,11 +780,14 @@ def getClientSpec():
# dictionary of all client parameters
entry = specList[0]
# the //client/ name
client_name = entry["Client"]
# just the keys that start with "View"
view_keys = [ k for k in entry.keys() if k.startswith("View") ]
# hold this new View
view = View()
view = View(client_name)
# append the lines, in order, to the view
for view_num in range(len(view_keys)):
@ -1555,8 +1558,8 @@ class P4Submit(Command, P4UserMap):
for b in body:
labelTemplate += "\t" + b + "\n"
labelTemplate += "View:\n"
for mapping in clientSpec.mappings:
labelTemplate += "\t%s\n" % mapping.depot_side.path
for depot_side in clientSpec.mappings:
labelTemplate += "\t%s\n" % depot_side
if self.dry_run:
print "Would create p4 label %s for tag" % name
@ -1568,7 +1571,7 @@ class P4Submit(Command, P4UserMap):
# Use the label
p4_system(["tag", "-l", name] +
["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
["%s@%s" % (depot_side, changelist) for depot_side in clientSpec.mappings])
if verbose:
print "created p4 label for tag %s" % name
@ -1796,117 +1799,16 @@ class View(object):
"""Represent a p4 view ("p4 help views"), and map files in a
repo according to the view."""
class Path(object):
"""A depot or client path, possibly containing wildcards.
The only one supported is ... at the end, currently.
Initialize with the full path, with //depot or //client."""
def __init__(self, path, is_depot):
self.path = path
self.is_depot = is_depot
self.find_wildcards()
# remember the prefix bit, useful for relative mappings
m = re.match("(//[^/]+/)", self.path)
if not m:
die("Path %s does not start with //prefix/" % self.path)
prefix = m.group(1)
if not self.is_depot:
# strip //client/ on client paths
self.path = self.path[len(prefix):]
def find_wildcards(self):
"""Make sure wildcards are valid, and set up internal
variables."""
self.ends_triple_dot = False
# There are three wildcards allowed in p4 views
# (see "p4 help views"). This code knows how to
# handle "..." (only at the end), but cannot deal with
# "%%n" or "*". Only check the depot_side, as p4 should
# validate that the client_side matches too.
if re.search(r'%%[1-9]', self.path):
die("Can't handle %%n wildcards in view: %s" % self.path)
if self.path.find("*") >= 0:
die("Can't handle * wildcards in view: %s" % self.path)
triple_dot_index = self.path.find("...")
if triple_dot_index >= 0:
if triple_dot_index != len(self.path) - 3:
die("Can handle only single ... wildcard, at end: %s" %
self.path)
self.ends_triple_dot = True
def ensure_compatible(self, other_path):
"""Make sure the wildcards agree."""
if self.ends_triple_dot != other_path.ends_triple_dot:
die("Both paths must end with ... if either does;\n" +
"paths: %s %s" % (self.path, other_path.path))
def match_wildcards(self, test_path):
"""See if this test_path matches us, and fill in the value
of the wildcards if so. Returns a tuple of
(True|False, wildcards[]). For now, only the ... at end
is supported, so at most one wildcard."""
if self.ends_triple_dot:
dotless = self.path[:-3]
if test_path.startswith(dotless):
wildcard = test_path[len(dotless):]
return (True, [ wildcard ])
else:
if test_path == self.path:
return (True, [])
return (False, [])
def match(self, test_path):
"""Just return if it matches; don't bother with the wildcards."""
b, _ = self.match_wildcards(test_path)
return b
def fill_in_wildcards(self, wildcards):
"""Return the relative path, with the wildcards filled in
if there are any."""
if self.ends_triple_dot:
return self.path[:-3] + wildcards[0]
else:
return self.path
class Mapping(object):
def __init__(self, depot_side, client_side, overlay, exclude):
# depot_side is without the trailing /... if it had one
self.depot_side = View.Path(depot_side, is_depot=True)
self.client_side = View.Path(client_side, is_depot=False)
self.overlay = overlay # started with "+"
self.exclude = exclude # started with "-"
assert not (self.overlay and self.exclude)
self.depot_side.ensure_compatible(self.client_side)
def __str__(self):
c = " "
if self.overlay:
c = "+"
if self.exclude:
c = "-"
return "View.Mapping: %s%s -> %s" % \
(c, self.depot_side.path, self.client_side.path)
def map_depot_to_client(self, depot_path):
"""Calculate the client path if using this mapping on the
given depot path; does not consider the effect of other
mappings in a view. Even excluded mappings are returned."""
matches, wildcards = self.depot_side.match_wildcards(depot_path)
if not matches:
return ""
client_path = self.client_side.fill_in_wildcards(wildcards)
return client_path
#
# View methods
#
def __init__(self):
def __init__(self, client_name):
self.mappings = []
self.client_prefix = "//%s/" % client_name
# cache results of "p4 where" to lookup client file locations
self.client_spec_path_cache = {}
def append(self, view_line):
"""Parse a view line, splitting it into depot and client
sides. Append to self.mappings, preserving order."""
sides. Append to self.mappings, preserving order. This
is only needed for tag creation."""
# Split the view line into exactly two words. P4 enforces
# structure on these lines that simplifies this quite a bit.
@ -1934,76 +1836,62 @@ class View(object):
depot_side = view_line[0:space_index]
rhs_index = space_index + 1
if view_line[rhs_index] == '"':
# Second word is double quoted. Make sure there is a
# double quote at the end too.
if not view_line.endswith('"'):
die("View line with rhs quote should end with one: %s" %
view_line)
# skip the quotes
client_side = view_line[rhs_index+1:-1]
else:
client_side = view_line[rhs_index:]
# prefix + means overlay on previous mapping
overlay = False
if depot_side.startswith("+"):
overlay = True
depot_side = depot_side[1:]
# prefix - means exclude this path
# prefix - means exclude this path, leave out of mappings
exclude = False
if depot_side.startswith("-"):
exclude = True
depot_side = depot_side[1:]
m = View.Mapping(depot_side, client_side, overlay, exclude)
self.mappings.append(m)
if not exclude:
self.mappings.append(depot_side)
def convert_client_path(self, clientFile):
# chop off //client/ part to make it relative
if not clientFile.startswith(self.client_prefix):
die("No prefix '%s' on clientFile '%s'" %
(self.client_prefix, clientFile))
return clientFile[len(self.client_prefix):]
def update_client_spec_path_cache(self, files):
""" Caching file paths by "p4 where" batch query """
# List depot file paths exclude that already cached
fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
if len(fileArgs) == 0:
return # All files in cache
where_result = p4CmdList(["-x", "-", "where"], stdin=fileArgs)
for res in where_result:
if "code" in res and res["code"] == "error":
# assume error is "... file(s) not in client view"
continue
if "clientFile" not in res:
die("No clientFile from 'p4 where %s'" % depot_path)
if "unmap" in res:
# it will list all of them, but only one not unmap-ped
continue
self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
# not found files or unmap files set to ""
for depotFile in fileArgs:
if depotFile not in self.client_spec_path_cache:
self.client_spec_path_cache[depotFile] = ""
def map_in_client(self, depot_path):
"""Return the relative location in the client where this
depot file should live. Returns "" if the file should
not be mapped in the client."""
paths_filled = []
client_path = ""
if depot_path in self.client_spec_path_cache:
return self.client_spec_path_cache[depot_path]
# look at later entries first
for m in self.mappings[::-1]:
# see where will this path end up in the client
p = m.map_depot_to_client(depot_path)
if p == "":
# Depot path does not belong in client. Must remember
# this, as previous items should not cause files to
# exist in this path either. Remember that the list is
# being walked from the end, which has higher precedence.
# Overlap mappings do not exclude previous mappings.
if not m.overlay:
paths_filled.append(m.client_side)
else:
# This mapping matched; no need to search any further.
# But, the mapping could be rejected if the client path
# has already been claimed by an earlier mapping (i.e.
# one later in the list, which we are walking backwards).
already_mapped_in_client = False
for f in paths_filled:
# this is View.Path.match
if f.match(p):
already_mapped_in_client = True
break
if not already_mapped_in_client:
# Include this file, unless it is from a line that
# explicitly said to exclude it.
if not m.exclude:
client_path = p
# a match, even if rejected, always stops the search
break
return client_path
die( "Error: %s is not found in client spec path" % depot_path )
return ""
class P4Sync(Command, P4UserMap):
delete_actions = ( "delete", "move/delete", "purge" )
@ -2130,6 +2018,10 @@ class P4Sync(Command, P4UserMap):
"""Look at each depotFile in the commit to figure out to what
branch it belongs."""
if self.clientSpecDirs:
files = self.extractFilesFromCommit(commit)
self.clientSpecDirs.update_client_spec_path_cache(files)
branches = {}
fnum = 0
while commit.has_key("depotFile%s" % fnum):
@ -2383,6 +2275,9 @@ class P4Sync(Command, P4UserMap):
else:
sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
if self.clientSpecDirs:
self.clientSpecDirs.update_client_spec_path_cache(files)
self.gitStream.write("commit %s\n" % branch)
# gitStream.write("mark :%s\n" % details["change"])
self.committedChanges.add(int(details["change"]))

View File

@ -48,7 +48,8 @@ P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
P4PORT=localhost:$P4DPORT
P4CLIENT=client
P4EDITOR=:
export P4PORT P4CLIENT P4EDITOR
unset P4CHARSET
export P4PORT P4CLIENT P4EDITOR P4CHARSET
db="$TRASH_DIRECTORY/db"
cli="$TRASH_DIRECTORY/cli"