Merge branch 'va/p4'
authorJunio C Hamano <gitster@pobox.com>
Mon, 28 Feb 2011 05:58:30 +0000 (21:58 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 28 Feb 2011 05:58:30 +0000 (21:58 -0800)
* va/p4:
git-p4: Add copy detection support
git-p4: Improve rename detection support

1  2 
contrib/fast-import/git-p4
index e35c674e1c26bad06b1cb39cf05816dff82756f6,f36973b2fc9fe6efd3b273ba0509f30447de0cfd..a4f440d11696caa6ee4756108bec78a85d4b04f7
@@@ -543,13 -543,13 +543,13 @@@ class P4Submit(Command)
          self.options = [
                  optparse.make_option("--verbose", dest="verbose", action="store_true"),
                  optparse.make_option("--origin", dest="origin"),
-                 optparse.make_option("-M", dest="detectRename", action="store_true"),
+                 optparse.make_option("-M", dest="detectRenames", action="store_true"),
          ]
          self.description = "Submit changes from git to the perforce depot."
          self.usage += " [name of git branch to submit into perforce depot]"
          self.interactive = True
          self.origin = ""
-         self.detectRename = False
+         self.detectRenames = False
          self.verbose = False
          self.isWindows = (platform.system() == "Windows")
  
  
      def applyCommit(self, id):
          print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
-         diffOpts = ("", "-M")[self.detectRename]
+         if not self.detectRenames:
+             # If not explicitly set check the config variable
+             self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+         if self.detectRenames:
+             diffOpts = "-M"
+         else:
+             diffOpts = ""
+         if gitConfig("git-p4.detectCopies").lower() == "true":
+             diffOpts += " -C"
+         if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+             diffOpts += " --find-copies-harder"
          diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
          filesToAdd = set()
          filesToDelete = set()
                  filesToDelete.add(path)
                  if path in filesToAdd:
                      filesToAdd.remove(path)
+             elif modifier == "C":
+                 src, dest = diff['src'], diff['dst']
+                 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                 if diff['src_sha1'] != diff['dst_sha1']:
+                     p4_system("edit \"%s\"" % (dest))
+                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+                     p4_system("edit \"%s\"" % (dest))
+                     filesToChangeExecBit[dest] = diff['dst_mode']
+                 os.unlink(dest)
+                 editedFiles.add(dest)
              elif modifier == "R":
                  src, dest = diff['src'], diff['dst']
                  p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
-                 p4_system("edit \"%s\"" % (dest))
+                 if diff['src_sha1'] != diff['dst_sha1']:
+                     p4_system("edit \"%s\"" % (dest))
                  if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+                     p4_system("edit \"%s\"" % (dest))
                      filesToChangeExecBit[dest] = diff['dst_mode']
                  os.unlink(dest)
                  editedFiles.add(dest)
          return True
  
  class P4Sync(Command):
 +    delete_actions = ( "delete", "move/delete", "purge" )
 +
      def __init__(self):
          Command.__init__(self)
          self.options = [
          if gitConfig("git-p4.syncFromOrigin") == "false":
              self.syncWithOrigin = False
  
 +    #
 +    # P4 wildcards are not allowed in filenames.  P4 complains
 +    # if you simply add them, but you can force it with "-f", in
 +    # which case it translates them into %xx encoding internally.
 +    # Search for and fix just these four characters.  Do % last so
 +    # that fixing it does not inadvertently create new %-escapes.
 +    #
 +    def wildcard_decode(self, path):
 +        # Cannot have * in a filename in windows; untested as to
 +        # what p4 would do in such a case.
 +        if not self.isWindows:
 +            path = path.replace("%2A", "*")
 +        path = path.replace("%23", "#") \
 +                   .replace("%40", "@") \
 +                   .replace("%25", "%")
 +        return path
 +
      def extractFilesFromCommit(self, commit):
          self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
                               for path in self.cloneExclude]
          return files
  
      def stripRepoPath(self, path, prefixes):
 +        if self.useClientSpec:
 +
 +            # if using the client spec, we use the output directory
 +            # specified in the client.  For example, a view
 +            #   //depot/foo/branch/... //client/branch/foo/...
 +            # will end up putting all foo/branch files into
 +            #  branch/foo/
 +            for val in self.clientSpecDirs:
 +                if path.startswith(val[0]):
 +                    # replace the depot path with the client path
 +                    path = path.replace(val[0], val[1][1])
 +                    # now strip out the client (//client/...)
 +                    path = re.sub("^(//[^/]+/)", '', path)
 +                    # the rest is all path
 +                    return path
 +
          if self.keepRepoPath:
              prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
  
            return
  
          relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
 +        relPath = self.wildcard_decode(relPath)
          if verbose:
              sys.stderr.write("%s\n" % relPath)
  
              includeFile = True
              for val in self.clientSpecDirs:
                  if f['path'].startswith(val[0]):
 -                    if val[1] <= 0:
 +                    if val[1][0] <= 0:
                          includeFile = False
                      break
  
              if includeFile:
                  filesForCommit.append(f)
 -                if f['action'] not in ('delete', 'move/delete', 'purge'):
 -                    filesToRead.append(f)
 -                else:
 +                if f['action'] in self.delete_actions:
                      filesToDelete.append(f)
 +                else:
 +                    filesToRead.append(f)
  
          # deleted files...
          for f in filesToDelete:
  
                  cleanedFiles = {}
                  for info in files:
 -                    if info["action"] in ("delete", "purge"):
 +                    if info["action"] in self.delete_actions:
                          continue
                      cleanedFiles[info["depotFile"]] = info["rev"]
  
          print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
  
          details = { "user" : "git perforce import user", "time" : int(time.time()) }
 -        details["desc"] = ("Initial import of %s from the state at revision %s"
 +        details["desc"] = ("Initial import of %s from the state at revision %s\n"
                             % (' '.join(self.depotPaths), revision))
          details["change"] = revision
          newestRevision = 0
                                             % (p, revision)
                                             for p in self.depotPaths])):
  
 -            if info['code'] == 'error':
 +            if 'code' in info and info['code'] == 'error':
                  sys.stderr.write("p4 returned an error: %s\n"
                                   % info['data'])
 +                if info['data'].find("must refer to client") >= 0:
 +                    sys.stderr.write("This particular p4 error is misleading.\n")
 +                    sys.stderr.write("Perhaps the depot path was misspelled.\n");
 +                    sys.stderr.write("Depot path:  %s\n" % " ".join(self.depotPaths))
 +                sys.exit(1)
 +            if 'p4ExitCode' in info:
 +                sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
                  sys.exit(1)
  
  
              if change > newestRevision:
                  newestRevision = change
  
 -            if info["action"] in ("delete", "purge"):
 +            if info["action"] in self.delete_actions:
                  # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
                  #fileCnt = fileCnt + 1
                  continue
          for entry in specList:
              for k,v in entry.iteritems():
                  if k.startswith("View"):
 +
 +                    # p4 has these %%1 to %%9 arguments in specs to
 +                    # reorder paths; which we can't handle (yet :)
 +                    if re.match('%%\d', v) != None:
 +                        print "Sorry, can't handle %%n arguments in client specs"
 +                        sys.exit(1)
 +
                      if v.startswith('"'):
                          start = 1
                      else:
                          start = 0
                      index = v.find("...")
 +
 +                    # save the "client view"; i.e the RHS of the view
 +                    # line that tells the client where to put the
 +                    # files for this view.
 +                    cv = v[index+3:].strip() # +3 to remove previous '...'
 +
 +                    # if the client view doesn't end with a
 +                    # ... wildcard, then we're going to mess up the
 +                    # output directory, so fail gracefully.
 +                    if not cv.endswith('...'):
 +                        print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
 +                        sys.exit(1)
 +                    cv=cv[:-3]
 +
 +                    # now save the view; +index means included, -index
 +                    # means it should be filtered out.
                      v = v[start:index]
                      if v.startswith("-"):
                          v = v[1:]
 -                        temp[v] = -len(v)
 +                        include = -len(v)
                      else:
 -                        temp[v] = len(v)
 +                        include = len(v)
 +
 +                    temp[v] = (include, cv)
 +
          self.clientSpecDirs = temp.items()
 -        self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
 +        self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
  
      def run(self, args):
          self.depotPaths = []
  
                  changes.sort()
              else:
 +                if not self.p4BranchesInGit:
 +                    die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.");
                  if self.verbose:
                      print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
                                                                self.changeRange)
@@@ -1818,13 -1774,10 +1845,13 @@@ class P4Clone(P4Sync)
                                   help="where to leave result of the clone"),
              optparse.make_option("-/", dest="cloneExclude",
                                   action="append", type="string",
 -                                 help="exclude depot path")
 +                                 help="exclude depot path"),
 +            optparse.make_option("--bare", dest="cloneBare",
 +                                 action="store_true", default=False),
          ]
          self.cloneDestination = None
          self.needsGit = False
 +        self.cloneBare = False
  
      # This is required for the "append" cloneExclude action
      def ensure_value(self, attr, value):
              self.cloneDestination = self.defaultDestination(args)
  
          print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
 +
          if not os.path.exists(self.cloneDestination):
              os.makedirs(self.cloneDestination)
          chdir(self.cloneDestination)
 -        system("git init")
 -        self.gitdir = os.getcwd() + "/.git"
 +
 +        init_cmd = [ "git", "init" ]
 +        if self.cloneBare:
 +            init_cmd.append("--bare")
 +        subprocess.check_call(init_cmd)
 +
          if not P4Sync.run(self, depotPaths):
              return False
          if self.branch != "master":
                  masterbranch = "refs/heads/p4/master"
              if gitBranchExists(masterbranch):
                  system("git branch master %s" % masterbranch)
 -                system("git checkout -f")
 +                if not self.cloneBare:
 +                    system("git checkout -f")
              else:
                  print "Could not detect main branch. No checkout/master branch created."