Commit 0c71e4cb by Patrick Steinhardt

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".
parent 51214b85
......@@ -228,7 +228,6 @@ static int refspec_transform(
git_buf *out, const char *from, const char *to, const char *name)
{
const char *from_star, *to_star;
const char *name_slash, *from_slash;
size_t replacement_len, star_offset;
git_buf_sanitize(out);
......@@ -251,17 +250,11 @@ static int refspec_transform(
/* the first half is copied over */
git_buf_put(out, to, to_star - to);
/* then we copy over the replacement, from the star's offset to the next slash in 'name' */
name_slash = strchr(name + star_offset, '/');
if (!name_slash)
name_slash = strrchr(name, '\0');
/* if there is no slash after the star in 'from', we want to copy everything over */
from_slash = strchr(from + star_offset, '/');
if (!from_slash)
name_slash = strrchr(name, '\0');
replacement_len = (name_slash - name) - star_offset;
/*
* Copy over the name, but exclude the trailing part in "from" starting
* after the glob
*/
replacement_len = strlen(name + star_offset) - strlen(from_star + 1);
git_buf_put(out, name + star_offset, replacement_len);
return git_buf_puts(out, to_star + 1);
......
......@@ -120,6 +120,11 @@ void test_network_refspecs__transform_loosened_star(void)
assert_valid_transform("refs/heads/branch-*/head:refs/remotes/origin/branch-*/head", "refs/heads/branch-a/head", "refs/remotes/origin/branch-a/head");
}
void test_network_refspecs__transform_nested_star(void)
{
assert_valid_transform("refs/heads/x*x/for-linus:refs/remotes/mine/*", "refs/heads/xbranchx/for-linus", "refs/remotes/mine/branch");
}
void test_network_refspecs__no_dst(void)
{
assert_valid_transform("refs/heads/master:", "refs/heads/master", "");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment