Merge branch 'cl/p4-use-diff-tree' into maint
authorJunio C Hamano <gitster@pobox.com>
Thu, 8 May 2014 17:01:32 +0000 (10:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 May 2014 17:01:32 +0000 (10:01 -0700)
"git p4" dealing with changes in binary files were broken by a
change in 1.9 release.

* cl/p4-use-diff-tree:
git-p4: format-patch to diff-tree change breaks binary patches

1  2 
git-p4.py
diff --combined git-p4.py
index cdfa2df5d8e0add0363f3e246362b437b72c4d58,17760c73e5628ed197eb7841a6cfc2a8fd1b3137..4ee673994be6d4dca177dc054cb8a6bc6ed1f253
+++ b/git-p4.py
@@@ -310,8 -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,14 -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)):
@@@ -1220,7 -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.
              else:
                  die("unknown modifier %s for %s" % (modifier, path))
  
-         diffcmd = "git diff-tree -p \"%s\"" % (id)
+         diffcmd = "git diff-tree --full-index -p \"%s\"" % (id)
          patchcmd = diffcmd + " | git apply "
          tryPatchCmd = patchcmd + "--check -"
          applyPatchCmd = patchcmd + "--check --apply -"
              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
  
                  # 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
@@@ -1799,16 -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.
              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."""
 +
 +        if depot_path in self.client_spec_path_cache:
 +            return self.client_spec_path_cache[depot_path]
  
 -        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" )
          """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):
              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
              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"]))