diff options
Diffstat (limited to 'git-p4.py')
-rwxr-xr-x | git-p4.py | 301 |
1 files changed, 105 insertions, 196 deletions
@@ -310,8 +310,8 @@ def split_p4_type(p4type): # # return the raw p4 type of a file (text, text+ko, etc) # -def p4_type(file): - results = p4CmdList(["fstat", "-T", "headType", file]) +def p4_type(f): + results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)]) return results[0]['headType'] # @@ -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)): @@ -1217,7 +1220,7 @@ class P4Submit(Command, P4UserMap): editor = os.environ.get("P4EDITOR") else: editor = read_pipe("git var GIT_EDITOR").strip() - system(editor + " " + template_file) + system([editor, template_file]) # If the file was not saved, prompt to see if this patch should # be skipped. But skip this verification step if configured so. @@ -1235,6 +1238,28 @@ class P4Submit(Command, P4UserMap): if response == 'n': return False + def get_diff_description(self, editedFiles): + # diff + if os.environ.has_key("P4DIFF"): + del(os.environ["P4DIFF"]) + diff = "" + for editedFile in editedFiles: + diff += p4_read_pipe(['diff', '-du', + wildcard_encode(editedFile)]) + + # new file diff + newdiff = "" + for newFile in filesToAdd: + newdiff += "==== new file ====\n" + newdiff += "--- /dev/null\n" + newdiff += "+++ %s\n" % newFile + f = open(newFile, "r") + for line in f.readlines(): + newdiff += "+" + line + f.close() + + return diff + newdiff + def applyCommit(self, id): """Apply one commit, return True if it succeeded.""" @@ -1308,7 +1333,7 @@ class P4Submit(Command, P4UserMap): else: die("unknown modifier %s for %s" % (modifier, path)) - diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id) + diffcmd = "git diff-tree --full-index -p \"%s\"" % (id) patchcmd = diffcmd + " | git apply " tryPatchCmd = patchcmd + "--check -" applyPatchCmd = patchcmd + "--check --apply -" @@ -1395,34 +1420,15 @@ class P4Submit(Command, P4UserMap): submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n" separatorLine = "######## everything below this line is just the diff #######\n" + if not self.prepare_p4_only: + submitTemplate += separatorLine + submitTemplate += self.get_diff_description(editedFiles) - # diff - if os.environ.has_key("P4DIFF"): - del(os.environ["P4DIFF"]) - diff = "" - for editedFile in editedFiles: - diff += p4_read_pipe(['diff', '-du', - wildcard_encode(editedFile)]) - - # new file diff - newdiff = "" - for newFile in filesToAdd: - newdiff += "==== new file ====\n" - newdiff += "--- /dev/null\n" - newdiff += "+++ %s\n" % newFile - f = open(newFile, "r") - for line in f.readlines(): - newdiff += "+" + line - f.close() - - # change description file: submitTemplate, separatorLine, diff, newdiff (handle, fileName) = tempfile.mkstemp() tmpFile = os.fdopen(handle, "w+") if self.isWindows: submitTemplate = submitTemplate.replace("\n", "\r\n") - separatorLine = separatorLine.replace("\n", "\r\n") - newdiff = newdiff.replace("\n", "\r\n") - tmpFile.write(submitTemplate + separatorLine + diff + newdiff) + tmpFile.write(submitTemplate) tmpFile.close() if self.prepare_p4_only: @@ -1555,8 +1561,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 +1574,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 @@ -1786,7 +1792,7 @@ class P4Submit(Command, P4UserMap): missingGitTags = gitTags - p4Labels self.exportGitTags(missingGitTags) - # exit with error unless everything applied perfecly + # exit with error unless everything applied perfectly if len(commits) != len(applied): sys.exit(1) @@ -1796,117 +1802,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 +1839,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 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.""" + 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):] - paths_filled = [] - client_path = "" + def update_client_spec_path_cache(self, files): + """ Caching file paths by "p4 where" batch query """ - # look at later entries first - for m in self.mappings[::-1]: + # 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] - # see where will this path end up in the client - p = m.map_depot_to_client(depot_path) + if len(fileArgs) == 0: + return # All files in cache - 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) + 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 in 'p4 where' output") + 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"]) - 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 + # 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] = "" - # a match, even if rejected, always stops the search - break + 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.""" - return client_path + if depot_path in self.client_spec_path_cache: + return self.client_spec_path_cache[depot_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 +2021,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): @@ -2180,9 +2075,20 @@ class P4Sync(Command, P4UserMap): git_mode = "100755" if type_base == "symlink": git_mode = "120000" - # p4 print on a symlink contains "target\n"; remove the newline + # p4 print on a symlink sometimes contains "target\n"; + # if it does, remove the newline data = ''.join(contents) - contents = [data[:-1]] + if not data: + # Some version of p4 allowed creating a symlink that pointed + # to nothing. This causes p4 errors when checking out such + # a change, and errors here too. Work around it by ignoring + # the bad symlink; hopefully a future change fixes it. + print "\nIgnoring empty symlink in %s" % file['depotFile'] + return + elif data[-1] == '\n': + contents = [data[:-1]] + else: + contents = [data] if type_base == "utf16": # p4 delivers different text in the python output to -G @@ -2379,6 +2285,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"])) @@ -3168,7 +3077,7 @@ class P4Rebase(Command): if os.system("git update-index --refresh") != 0: die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash."); if len(read_pipe("git diff-index HEAD --")) > 0: - die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash."); + die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash."); [upstream, settings] = findUpstreamBranchPoint() if len(upstream) == 0: @@ -3180,7 +3089,7 @@ class P4Rebase(Command): print "Rebasing the current branch onto %s" % upstream oldHead = read_pipe("git rev-parse HEAD").strip() system("git rebase %s" % upstream) - system("git diff-tree --stat --summary -M %s HEAD" % oldHead) + system("git diff-tree --stat --summary -M %s HEAD --" % oldHead) return True class P4Clone(P4Sync): |