Skip to content

Commit 0c71e4c

Browse files
committed
refspec: fix transforming nested stars
When we transform a refspec with a component containing a glob, then we simply copy over the component until the next separator from the matching ref. E.g. if we have a ref "refs/heads/foo/bar" and a refspec "refs/heads/*/bar:refs/remotes/origin/*/bar", we: 1. Copy over everything until hitting the glob from the <dst> part: "refs/remotes/origin/". 2. Strip the common prefix of ref and <src> part until the glob, which is "refs/heads/". This leaves us with a ref of "foo/bar". 3. Copy from the ref until the next "/" separator, resulting in "refs/remotes/origin/foo". 4. Copy over the remaining part of the <dst> spec, which is "bar": "refs/remotes/origin/foo/bar". This worked just fine in a world where globs in refspecs were restricted such that a globbing component may only contain a single "*", only. But this restriction has been lifted, so that a glob component may be nested between other characters, causing the above algorithm to fail. Most notably the third step, where we copy until hitting the next "/" separator, might result in a wrong transformation. Given e.g. a ref "refs/gbranchg/head" and a refspec "refs/g*g/head:refs/remotes/origin/*", we'd also be copying the "g" between "branch" and "/" and end up with the wrong transformed ref "refs/remotes/origin/branchg". Instead of copying until the next component separator, we should copy until we hit the pattern after the "*". So in the above example, we'd copy until hitting the string "g/head".
1 parent 51214b8 commit 0c71e4c

File tree

2 files changed

+10
-12
lines changed

2 files changed

+10
-12
lines changed

src/refspec.c

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@ static int refspec_transform(
228228
git_buf *out, const char *from, const char *to, const char *name)
229229
{
230230
const char *from_star, *to_star;
231-
const char *name_slash, *from_slash;
232231
size_t replacement_len, star_offset;
233232

234233
git_buf_sanitize(out);
@@ -251,17 +250,11 @@ static int refspec_transform(
251250
/* the first half is copied over */
252251
git_buf_put(out, to, to_star - to);
253252

254-
/* then we copy over the replacement, from the star's offset to the next slash in 'name' */
255-
name_slash = strchr(name + star_offset, '/');
256-
if (!name_slash)
257-
name_slash = strrchr(name, '\0');
258-
259-
/* if there is no slash after the star in 'from', we want to copy everything over */
260-
from_slash = strchr(from + star_offset, '/');
261-
if (!from_slash)
262-
name_slash = strrchr(name, '\0');
263-
264-
replacement_len = (name_slash - name) - star_offset;
253+
/*
254+
* Copy over the name, but exclude the trailing part in "from" starting
255+
* after the glob
256+
*/
257+
replacement_len = strlen(name + star_offset) - strlen(from_star + 1);
265258
git_buf_put(out, name + star_offset, replacement_len);
266259

267260
return git_buf_puts(out, to_star + 1);

tests/network/refspecs.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ void test_network_refspecs__transform_loosened_star(void)
120120
assert_valid_transform("refs/heads/branch-*/head:refs/remotes/origin/branch-*/head", "refs/heads/branch-a/head", "refs/remotes/origin/branch-a/head");
121121
}
122122

123+
void test_network_refspecs__transform_nested_star(void)
124+
{
125+
assert_valid_transform("refs/heads/x*x/for-linus:refs/remotes/mine/*", "refs/heads/xbranchx/for-linus", "refs/remotes/mine/branch");
126+
}
127+
123128
void test_network_refspecs__no_dst(void)
124129
{
125130
assert_valid_transform("refs/heads/master:", "refs/heads/master", "");

0 commit comments

Comments
 (0)