Merge branch 'am/p4-branches-excludes'
authorJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:40 +0000 (15:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 9 Jul 2019 22:25:40 +0000 (15:25 -0700)
"git p4" update.

* am/p4-branches-excludes:
git-p4: respect excluded paths when detecting branches
git-p4: add failing test for "git-p4: respect excluded paths when detecting branches"
git-p4: don't exclude other files with same prefix
git-p4: add failing test for "don't exclude other files with same prefix"
git-p4: don't groom exclude path list on every commit
git-p4: match branches case insensitively if configured
git-p4: add failing test for "git-p4: match branches case insensitively if configured"
git-p4: detect/prevent infinite loop in gitCommitByP4Change()

1  2 
git-p4.py
t/t9801-git-p4-branch.sh
t/t9817-git-p4-exclude.sh
diff --combined git-p4.py
index c71a6832e2fa0a632dc93155d0fa2b727c659f5e,96c4b78dc7c8f4ea70975878e8c4f18ab629e82c..3991e7d1a7fc4d7206f8786a0d92ae5961ef0ef4
+++ b/git-p4.py
@@@ -737,7 -737,7 +737,7 @@@ def extractLogMessageFromGitCommit(comm
  
      ## fixme: title is first line of commit, not 1st paragraph.
      foundTitle = False
 -    for log in read_pipe_lines("git cat-file commit %s" % commit):
 +    for log in read_pipe_lines(["git", "cat-file", "commit", commit]):
         if not foundTitle:
             if len(log) == 1:
                 foundTitle = True
@@@ -1309,14 -1309,14 +1309,14 @@@ class GitLFS(LargeFileSystem)
  
  class Command:
      delete_actions = ( "delete", "move/delete", "purge" )
 -    add_actions = ( "add", "move/add" )
 +    add_actions = ( "add", "branch", "move/add" )
  
      def __init__(self):
          self.usage = "usage: %prog [options]"
          self.needsGit = True
          self.verbose = False
  
-     # This is required for the "append" cloneExclude action
+     # This is required for the "append" update_shelve action
      def ensure_value(self, attr, value):
          if not hasattr(self, attr) or getattr(self, attr) is None:
              setattr(self, attr, value)
@@@ -2530,6 -2530,11 +2530,11 @@@ class View(object)
          die( "Error: %s is not found in client spec path" % depot_path )
          return ""
  
+ def cloneExcludeCallback(option, opt_str, value, parser):
+     # prepend "/" because the first "/" was consumed as part of the option itself.
+     # ("-//depot/A/..." becomes "/depot/A/..." after option parsing)
+     parser.values.cloneExclude += ["/" + re.sub(r"\.\.\.$", "", value)]
  class P4Sync(Command, P4UserMap):
  
      def __init__(self):
                  optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
                                       help="Only sync files that are included in the Perforce Client Spec"),
                  optparse.make_option("-/", dest="cloneExclude",
-                                      action="append", type="string",
+                                      action="callback", callback=cloneExcludeCallback, type="string",
                                       help="exclude depot path"),
          ]
          self.description = """Imports from Perforce into a git repository.\n
          if self.verbose:
              print("checkpoint finished: " + out)
  
+     def isPathWanted(self, path):
+         for p in self.cloneExclude:
+             if p.endswith("/"):
+                 if p4PathStartsWith(path, p):
+                     return False
+             # "-//depot/file1" without a trailing "/" should only exclude "file1", but not "file111" or "file1_dir/file2"
+             elif path.lower() == p.lower():
+                 return False
+         for p in self.depotPaths:
+             if p4PathStartsWith(path, p):
+                 return True
+         return False
      def extractFilesFromCommit(self, commit, shelved=False, shelved_cl = 0):
-         self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
-                              for path in self.cloneExclude]
          files = []
          fnum = 0
          while "depotFile%s" % fnum in commit:
              path =  commit["depotFile%s" % fnum]
-             if [p for p in self.cloneExclude
-                 if p4PathStartsWith(path, p)]:
-                 found = False
-             else:
-                 found = [p for p in self.depotPaths
-                          if p4PathStartsWith(path, p)]
+             found = self.isPathWanted(path)
              if not found:
                  fnum = fnum + 1
                  continue
              path = self.clientSpecDirs.map_in_client(path)
              if self.detectBranches:
                  for b in self.knownBranches:
-                     if path.startswith(b + "/"):
+                     if p4PathStartsWith(path, b + "/"):
                          path = path[len(b)+1:]
  
          elif self.keepRepoPath:
          fnum = 0
          while "depotFile%s" % fnum in commit:
              path =  commit["depotFile%s" % fnum]
-             found = [p for p in self.depotPaths
-                      if p4PathStartsWith(path, p)]
+             found = self.isPathWanted(path)
              if not found:
                  fnum = fnum + 1
                  continue
              for branch in self.knownBranches.keys():
                  # add a trailing slash so that a commit into qt/4.2foo
                  # doesn't end up in qt/4.2, e.g.
-                 if relPath.startswith(branch + "/"):
+                 if p4PathStartsWith(relPath, branch + "/"):
                      if branch not in branches:
                          branches[branch] = []
                      branches[branch].append(file)
              if currentChange < change:
                  earliestCommit = "^%s" % next
              else:
-                 latestCommit = "%s" % next
+                 if next == latestCommit:
+                     die("Infinite loop while looking in ref %s for change %s. Check your branch mappings" % (ref, change))
+                 latestCommit = "%s^@" % next
  
          return ""
  
@@@ -3888,7 -3899,6 +3899,6 @@@ class P4Clone(P4Sync)
              self.cloneDestination = depotPaths[-1]
              depotPaths = depotPaths[:-1]
  
-         self.cloneExclude = ["/"+p for p in self.cloneExclude]
          for p in depotPaths:
              if not p.startswith("//"):
                  sys.stderr.write('Depot paths must start with "//": %s\n' % p)
diff --combined t/t9801-git-p4-branch.sh
index 38d6b9043b2039d97cdb634ac86658c45ec36abd,9654362052a80fc2eef8cc4257ad2aea4a883696..67ff2711f5f5fd9ab95cbf05df8f38868e24bbec
@@@ -151,7 -151,7 +151,7 @@@ test_expect_success 'import depot, bran
  '
  
  test_expect_success 'restart p4d' '
 -      kill_p4d &&
 +      stop_and_cleanup_p4d &&
        start_p4d
  '
  
@@@ -411,6 -411,46 +411,46 @@@ test_expect_failure 'git p4 clone file 
        )
  '
  
+ # Check that excluded files are omitted during import
+ test_expect_success 'git p4 clone complex branches with excluded files' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList branch1:branch2 &&
+               git config --add git-p4.branchList branch1:branch3 &&
+               git config --add git-p4.branchList branch1:branch4 &&
+               git config --add git-p4.branchList branch1:branch5 &&
+               git config --add git-p4.branchList branch1:branch6 &&
+               git p4 clone --dest=. --detect-branches -//depot/branch1/file2 -//depot/branch2/file2 -//depot/branch3/file2 -//depot/branch4/file2 -//depot/branch5/file2 -//depot/branch6/file2 //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/depot/branch1 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_file file3 &&
+               git reset --hard p4/depot/branch2 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_missing file3 &&
+               git reset --hard p4/depot/branch3 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_missing file3 &&
+               git reset --hard p4/depot/branch4 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_file file3 &&
+               git reset --hard p4/depot/branch5 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_file file3 &&
+               git reset --hard p4/depot/branch6 &&
+               test_path_is_file file1 &&
+               test_path_is_missing file2 &&
+               test_path_is_missing file3
+       )
+ '
  # From a report in http://stackoverflow.com/questions/11893688
  # where --use-client-spec caused branch prefixes not to be removed;
  # every file in git appeared into a subdirectory of the branch name.
@@@ -505,7 -545,7 +545,7 @@@ test_expect_success 'use-client-spec de
  '
  
  test_expect_success 'restart p4d' '
 -      kill_p4d &&
 +      stop_and_cleanup_p4d &&
        start_p4d
  '
  
@@@ -610,4 -650,100 +650,96 @@@ test_expect_success 'Update a file in g
        )
  '
  
 -      kill_p4d &&
+ test_expect_success 'restart p4d (case folding enabled)' '
 -test_expect_success 'kill p4d' '
 -      kill_p4d
 -'
 -
++      stop_and_cleanup_p4d &&
+       start_p4d -C1
+ '
+ #
+ # 1: //depot/main/mf1
+ # 2: integrate //depot/main/... -> //depot/branch1/...
+ # 3: //depot/main/mf2
+ # 4: //depot/BRANCH1/B1f3
+ # 5: //depot/branch1/b1f4
+ #
+ test_expect_success !CASE_INSENSITIVE_FS 'basic p4 branches for case folding' '
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+               echo mf1 >main/mf1 &&
+               p4 add main/mf1 &&
+               p4 submit -d "main/mf1" &&
+               p4 integrate //depot/main/... //depot/branch1/... &&
+               p4 submit -d "integrate main to branch1" &&
+               echo mf2 >main/mf2 &&
+               p4 add main/mf2 &&
+               p4 submit -d "main/mf2" &&
+               mkdir BRANCH1 &&
+               echo B1f3 >BRANCH1/B1f3 &&
+               p4 add BRANCH1/B1f3 &&
+               p4 submit -d "BRANCH1/B1f3" &&
+               echo b1f4 >branch1/b1f4 &&
+               p4 add branch1/b1f4 &&
+               p4 submit -d "branch1/b1f4"
+       )
+ '
+ # Check that files are properly split across branches when ignorecase is set
+ test_expect_success !CASE_INSENSITIVE_FS 'git p4 clone, branchList branch definition, ignorecase' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList main:branch1 &&
+               git config --type=bool core.ignoreCase true &&
+               git p4 clone --dest=. --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/master &&
+               test_path_is_file mf1 &&
+               test_path_is_file mf2 &&
+               test_path_is_missing B1f3 &&
+               test_path_is_missing b1f4 &&
+               git reset --hard p4/depot/branch1 &&
+               test_path_is_file mf1 &&
+               test_path_is_missing mf2 &&
+               test_path_is_file B1f3 &&
+               test_path_is_file b1f4
+       )
+ '
+ # Check that files are properly split across branches when ignorecase is set, use-client-spec case
+ test_expect_success !CASE_INSENSITIVE_FS 'git p4 clone with client-spec, branchList branch definition, ignorecase' '
+       client_view "//depot/... //client/..." &&
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList main:branch1 &&
+               git config --type=bool core.ignoreCase true &&
+               git p4 clone --dest=. --use-client-spec --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/master &&
+               test_path_is_file mf1 &&
+               test_path_is_file mf2 &&
+               test_path_is_missing B1f3 &&
+               test_path_is_missing b1f4 &&
+               git reset --hard p4/depot/branch1 &&
+               test_path_is_file mf1 &&
+               test_path_is_missing mf2 &&
+               test_path_is_file B1f3 &&
+               test_path_is_file b1f4
+       )
+ '
  test_done
index 96d25f0c02ccfb04391487b550a702751bf22a11,275dd304258311703ed596ce25a1b67ef3bcc110..ec3d937c6a73bb8d89de51839af4bba4c3a73910
@@@ -22,7 -22,9 +22,9 @@@ test_expect_success 'create exclude rep
                mkdir -p wanted discard &&
                echo wanted >wanted/foo &&
                echo discard >discard/foo &&
-               p4 add wanted/foo discard/foo &&
+               echo discard_file >discard_file &&
+               echo discard_file_not >discard_file_not &&
+               p4 add wanted/foo discard/foo discard_file discard_file_not &&
                p4 submit -d "initial revision"
        )
  '
@@@ -33,7 -35,9 +35,9 @@@ test_expect_success 'check the repo wa
        (
                cd "$git" &&
                test_path_is_file wanted/foo &&
-               test_path_is_file discard/foo
+               test_path_is_file discard/foo &&
+               test_path_is_file discard_file &&
+               test_path_is_file discard_file_not
        )
  '
  
@@@ -43,7 -47,21 +47,21 @@@ test_expect_success 'clone, excluding p
        (
                cd "$git" &&
                test_path_is_file wanted/foo &&
-               test_path_is_missing discard/foo
+               test_path_is_missing discard/foo &&
+               test_path_is_file discard_file &&
+               test_path_is_file discard_file_not
+       )
+ '
+ test_expect_success 'clone, excluding single file, no trailing /' '
+       test_when_finished cleanup_git &&
+       git p4 clone -//depot/discard_file --dest="$git" //depot/...@all &&
+       (
+               cd "$git" &&
+               test_path_is_file wanted/foo &&
+               test_path_is_file discard/foo &&
+               test_path_is_missing discard_file &&
+               test_path_is_file discard_file_not
        )
  '
  
@@@ -52,16 -70,43 +70,39 @@@ test_expect_success 'clone, then sync w
        git p4 clone -//depot/discard/... --dest="$git" //depot/...@all &&
        (
                cd "$cli" &&
-               p4 edit wanted/foo discard/foo &&
+               p4 edit wanted/foo discard/foo discard_file_not &&
                date >>wanted/foo &&
                date >>discard/foo &&
+               date >>discard_file_not &&
                p4 submit -d "updating" &&
  
                cd "$git" &&
                git p4 sync -//depot/discard/... &&
                test_path_is_file wanted/foo &&
-               test_path_is_missing discard/foo
+               test_path_is_missing discard/foo &&
+               test_path_is_file discard_file &&
+               test_path_is_file discard_file_not
+       )
+ '
+ test_expect_success 'clone, then sync with exclude, no trailing /' '
+       test_when_finished cleanup_git &&
+       git p4 clone -//depot/discard/... -//depot/discard_file --dest="$git" //depot/...@all &&
+       (
+               cd "$cli" &&
+               p4 edit wanted/foo discard/foo discard_file_not &&
+               date >>wanted/foo &&
+               date >>discard/foo &&
+               date >>discard_file_not &&
+               p4 submit -d "updating" &&
+               cd "$git" &&
+               git p4 sync -//depot/discard/... -//depot/discard_file &&
+               test_path_is_file wanted/foo &&
+               test_path_is_missing discard/foo &&
+               test_path_is_missing discard_file &&
+               test_path_is_file discard_file_not
        )
  '
  
 -test_expect_success 'kill p4d' '
 -      kill_p4d
 -'
 -
  test_done