git-p4: support explicit sync of arbitrary existing git-p4 refs

With the --branch argument of the "sync" subcommand, git-p4 enables you to import a perforce branch/path to an arbitrary git ref, using a full ref path, or to refs/remotes/p4/* or refs/heads/p4/*, depending on --import-local, using a short ref name. However, when you later want to explicitly sync such a given ref to pick up subsequent p4 changes, it only works if the ref was placed in the p4 path *and* has only one path component (no "/"). This limitation results from a bad assumption in the existing-branch sync logic, and also means you cannot individually sync branches detected by --detect-branches, as these also get a "/" in their names. Fix "git p4 sync --branch", when called with an existing ref, so that it works correctly regardless of whether the ref is in the p4 path or not, and (in the case of refs in the p4 path) regardless of whether it has a "/" in its short name or not. Also add tests to validate that these branch-specific syncs work as expected. Signed-off-by: Tao Klerks <tao@klerks.biz> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tao Klerks committed Apr 4, 2022 at 05:10 UTC 17f273ffbad8724278e0fa4da592a7ec7c6abc83
3 files changed +133 -14
git-p4.py
+38 -14
@@ -893,6 +893,36 @@ def gitConfigList(key):
893 _gitConfig[key] = []
894 return _gitConfig[key]
895
896 +def fullP4Ref(incomingRef, importIntoRemotes=True):
897 + """Standardize a given provided p4 ref value to a full git ref:
898 + refs/foo/bar/branch -> use it exactly
899 + p4/branch -> prepend refs/remotes/ or refs/heads/
900 + branch -> prepend refs/remotes/p4/ or refs/heads/p4/"""
901 + if incomingRef.startswith("refs/"):
902 + return incomingRef
903 + if importIntoRemotes:
904 + prepend = "refs/remotes/"
905 + else:
906 + prepend = "refs/heads/"
907 + if not incomingRef.startswith("p4/"):
908 + prepend += "p4/"
909 + return prepend + incomingRef
910 +
911 +def shortP4Ref(incomingRef, importIntoRemotes=True):
912 + """Standardize to a "short ref" if possible:
913 + refs/foo/bar/branch -> ignore
914 + refs/remotes/p4/branch or refs/heads/p4/branch -> shorten
915 + p4/branch -> shorten"""
916 + if importIntoRemotes:
917 + longprefix = "refs/remotes/p4/"
918 + else:
919 + longprefix = "refs/heads/p4/"
920 + if incomingRef.startswith(longprefix):
921 + return incomingRef[len(longprefix):]
922 + if incomingRef.startswith("p4/"):
923 + return incomingRef[3:]
924 + return incomingRef
925 +
926 def p4BranchesInGit(branchesAreInRemotes=True):
927 """Find all the branches whose names start with "p4/", looking
928 in remotes or heads as specified by the argument. Return
@@ -3750,9 +3780,13 @@ class P4Sync(Command, P4UserMap):
3780
3781 # restrict to just this one, disabling detect-branches
3782 if branch_arg_given:
3753 - short = self.branch.split("/")[-1]
3783 + short = shortP4Ref(self.branch, self.importIntoRemotes)
3784 if short in branches:
3785 self.p4BranchesInGit = [ short ]
3786 + elif self.branch.startswith('refs/') and \
3787 + branchExists(self.branch) and \
3788 + '[git-p4:' in extractLogMessageFromGitCommit(self.branch):
3789 + self.p4BranchesInGit = [ self.branch ]
3790 else:
3791 self.p4BranchesInGit = branches.keys()
3792
@@ -3769,7 +3803,8 @@ class P4Sync(Command, P4UserMap):
3803
3804 p4Change = 0
3805 for branch in self.p4BranchesInGit:
3772 - logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
3806 + logMsg = extractLogMessageFromGitCommit(fullP4Ref(branch,
3807 + self.importIntoRemotes))
3808
3809 settings = extractSettingsGitLog(logMsg)
3810
@@ -3802,18 +3837,7 @@ class P4Sync(Command, P4UserMap):
3837 if not self.silent and not self.detectBranches:
3838 print("Performing incremental import into %s git branch" % self.branch)
3839
3805 - # accept multiple ref name abbreviations:
3806 - # refs/foo/bar/branch -> use it exactly
3807 - # p4/branch -> prepend refs/remotes/ or refs/heads/
3808 - # branch -> prepend refs/remotes/p4/ or refs/heads/p4/
3809 - if not self.branch.startswith("refs/"):
3810 - if self.importIntoRemotes:
3811 - prepend = "refs/remotes/"
3812 - else:
3813 - prepend = "refs/heads/"
3814 - if not self.branch.startswith("p4/"):
3815 - prepend += "p4/"
3816 - self.branch = prepend + self.branch
3840 + self.branch = fullP4Ref(self.branch, self.importIntoRemotes)
3841
3842 if len(args) == 0 and self.depotPaths:
3843 if not self.silent:
t/t9800-git-p4-basic.sh
+85
@@ -74,6 +74,91 @@ test_expect_success 'git p4 sync new branch' '
74 )
75 '
76
77 +#
78 +# Setup as before, and then explicitly sync imported branch, using a
79 +# different ref format.
80 +#
81 +test_expect_success 'git p4 sync existing branch without changes' '
82 + test_create_repo "$git" &&
83 + test_when_finished cleanup_git &&
84 + (
85 + cd "$git" &&
86 + test_commit head &&
87 + git p4 sync --branch=depot //depot@all &&
88 + git p4 sync --branch=refs/remotes/p4/depot >out &&
89 + test_i18ngrep "No changes to import!" out
90 + )
91 +'
92 +
93 +#
94 +# Same as before, relative branch name.
95 +#
96 +test_expect_success 'git p4 sync existing branch with relative name' '
97 + test_create_repo "$git" &&
98 + test_when_finished cleanup_git &&
99 + (
100 + cd "$git" &&
101 + test_commit head &&
102 + git p4 sync --branch=branch1 //depot@all &&
103 + git p4 sync --branch=p4/branch1 >out &&
104 + test_i18ngrep "No changes to import!" out
105 + )
106 +'
107 +
108 +#
109 +# Same as before, with a nested branch path, referenced different ways.
110 +#
111 +test_expect_success 'git p4 sync existing branch with nested path' '
112 + test_create_repo "$git" &&
113 + test_when_finished cleanup_git &&
114 + (
115 + cd "$git" &&
116 + test_commit head &&
117 + git p4 sync --branch=p4/some/path //depot@all &&
118 + git p4 sync --branch=some/path >out &&
119 + test_i18ngrep "No changes to import!" out
120 + )
121 +'
122 +
123 +#
124 +# Same as before, with a full ref path outside the p4/* namespace.
125 +#
126 +test_expect_success 'git p4 sync branch explicit ref without p4 in path' '
127 + test_create_repo "$git" &&
128 + test_when_finished cleanup_git &&
129 + (
130 + cd "$git" &&
131 + test_commit head &&
132 + git p4 sync --branch=refs/remotes/someremote/depot //depot@all &&
133 + git p4 sync --branch=refs/remotes/someremote/depot >out &&
134 + test_i18ngrep "No changes to import!" out
135 + )
136 +'
137 +
138 +test_expect_success 'git p4 sync nonexistent ref' '
139 + test_create_repo "$git" &&
140 + test_when_finished cleanup_git &&
141 + (
142 + cd "$git" &&
143 + test_commit head &&
144 + git p4 sync --branch=depot //depot@all &&
145 + test_must_fail git p4 sync --branch=depot2 2>errs &&
146 + test_i18ngrep "Perhaps you never did" errs
147 + )
148 +'
149 +
150 +test_expect_success 'git p4 sync existing non-p4-imported ref' '
151 + test_create_repo "$git" &&
152 + test_when_finished cleanup_git &&
153 + (
154 + cd "$git" &&
155 + test_commit head &&
156 + git p4 sync --branch=depot //depot@all &&
157 + test_must_fail git p4 sync --branch=refs/heads/master 2>errs &&
158 + test_i18ngrep "Perhaps you never did" errs
159 + )
160 +'
161 +
162 test_expect_success 'clone two dirs' '
163 (
164 cd "$cli" &&
t/t9801-git-p4-branch.sh
+10
@@ -129,6 +129,16 @@ test_expect_success 'import depot, branch detection' '
129 )
130 '
131
132 +test_expect_success 'sync specific detected branch' '
133 + test_when_finished cleanup_git &&
134 + git p4 clone --dest="$git" --detect-branches //depot@all &&
135 + (
136 + cd "$git" &&
137 + git p4 sync --branch=depot/branch2 >out &&
138 + test_i18ngrep "No changes to import!" out
139 + )
140 +'
141 +
142 test_expect_success 'import depot, branch detection, branchList branch definition' '
143 test_when_finished cleanup_git &&
144 test_create_repo "$git" &&