Merge pull request #6962 from ryan-ph/ryanpham/negative-refspec/remote

remote: Handle fetching negative refspecs
This commit is contained in:
Edward Thomson
2024-12-24 23:55:06 +00:00
committed by GitHub
6 changed files with 115 additions and 4 deletions

View File

@@ -78,6 +78,15 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
*/
GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);
/**
* Check if a refspec's source descriptor matches a negative reference
*
* @param refspec the refspec
* @param refname the name of the reference to check
* @return 1 if the refspec matches, 0 otherwise
*/
GIT_EXTERN(int) git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname);
/**
* Check if a refspec's source descriptor matches a reference
*

View File

@@ -22,6 +22,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
const char *lhs, *rhs;
int valid = 0;
unsigned int flags;
bool is_neg_refspec = false;
GIT_ASSERT_ARG(refspec);
GIT_ASSERT_ARG(input);
@@ -34,6 +35,9 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
refspec->force = 1;
lhs++;
}
if (*lhs == '^') {
is_neg_refspec = true;
}
rhs = strrchr(lhs, ':');
@@ -62,7 +66,14 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
if (1 <= llen && memchr(lhs, '*', llen)) {
if ((rhs && !is_glob) || (!rhs && is_fetch))
/*
* If the lefthand side contains a glob, then one of the following must be
* true, otherwise the spec is invalid
* 1) the rhs exists and also contains a glob
* 2) it is a negative refspec (i.e. no rhs)
* 3) the rhs doesn't exist and we're fetching
*/
if ((rhs && !is_glob) || (rhs && is_neg_refspec) || (!rhs && is_fetch && !is_neg_refspec))
goto invalid;
is_glob = 1;
} else if (rhs && is_glob)
@@ -225,6 +236,14 @@ int git_refspec_force(const git_refspec *refspec)
return refspec->force;
}
int git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->src == NULL || !git_refspec_is_negative(refspec))
return false;
return (wildmatch(refspec->src + 1, refname, 0) == 0);
}
int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
{
if (refspec == NULL || refspec->src == NULL)
@@ -340,6 +359,14 @@ int git_refspec_is_wildcard(const git_refspec *spec)
return (spec->src[strlen(spec->src) - 1] == '*');
}
int git_refspec_is_negative(const git_refspec *spec)
{
GIT_ASSERT_ARG(spec);
GIT_ASSERT_ARG(spec->src);
return (spec->src[0] == '^' && spec->dst == NULL);
}
git_direction git_refspec_direction(const git_refspec *spec)
{
GIT_ASSERT_ARG(spec);

View File

@@ -45,6 +45,14 @@ int git_refspec__serialize(git_str *out, const git_refspec *refspec);
*/
int git_refspec_is_wildcard(const git_refspec *spec);
/**
* Determines if a refspec is a negative refspec.
*
* @param spec the refspec
* @return 1 if the refspec is a negative, 0 otherwise
*/
int git_refspec_is_negative(const git_refspec *spec);
/**
* DWIM `spec` with `refs` existing on the remote, append the dwim'ed
* result in `out`.

View File

@@ -2628,17 +2628,21 @@ done:
git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
{
git_refspec *spec;
git_refspec *match = NULL;
size_t i;
git_vector_foreach(&remote->active_refspecs, i, spec) {
if (spec->push)
continue;
if (git_refspec_src_matches(spec, refname))
return spec;
if (git_refspec_src_matches_negative(spec, refname))
return NULL;
if (git_refspec_src_matches(spec, refname) && match == NULL)
match = spec;
}
return NULL;
return match;
}
git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)

View File

@@ -402,6 +402,8 @@ void test_refs_normalize__negative_refspec_pattern(void)
{
ensure_refname_normalized(
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "^foo/bar", "^foo/bar");
ensure_refname_normalized(
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "^foo/bar/*", "^foo/bar/*");
ensure_refname_invalid(
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/^bar");
}

View File

@@ -10,8 +10,11 @@ static char* repo2_path;
static const char *REPO1_REFNAME = "refs/heads/main";
static const char *REPO2_REFNAME = "refs/remotes/repo1/main";
static const char *REPO1_UNDERSCORE_REFNAME = "refs/heads/_branch";
static const char *REPO2_UNDERSCORE_REFNAME = "refs/remotes/repo1/_branch";
static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main";
static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main";
static char *NEGATIVE_SPEC = "^refs/heads/_*";
void test_remote_fetch__initialize(void) {
git_config *c;
@@ -170,3 +173,61 @@ void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) {
git_reference_free(ref);
}
/**
* This checks that negative refspecs are respected when fetching. We create a
* repository with a '_' prefixed reference. A second repository is configured
* with a negative refspec to ignore any refs prefixed with '_' and fetch the
* first repository into the second.
*
* @param commit1id A pointer to an OID which will be populated with the first
* commit.
*/
static void do_fetch_repo_with_ref_matching_negative_refspec(git_oid *commit1id) {
/* create a commit in repo 1 and a reference to it with '_' prefix */
{
git_oid empty_tree_id;
git_tree *empty_tree;
git_signature *sig;
git_treebuilder *tb;
cl_git_pass(git_treebuilder_new(&tb, repo1, NULL));
cl_git_pass(git_treebuilder_write(&empty_tree_id, tb));
cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id));
cl_git_pass(git_signature_default(&sig, repo1));
cl_git_pass(git_commit_create(commit1id, repo1, REPO1_UNDERSCORE_REFNAME, sig,
sig, NULL, "one", empty_tree, 0, NULL));
git_tree_free(empty_tree);
git_signature_free(sig);
git_treebuilder_free(tb);
}
/* fetch the remote with negative refspec for references prefixed with '_' */
{
char *refspec_strs = { NEGATIVE_SPEC };
git_strarray refspecs = { &refspec_strs, 1 };
git_remote *remote;
cl_git_pass(git_remote_create_anonymous(&remote, repo2,
git_repository_path(repo1)));
cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message"));
git_remote_free(remote);
}
}
void test_remote_fetch__skip_negative_refspec_match(void) {
git_oid commit1id;
git_reference *ref1;
git_reference *ref2;
do_fetch_repo_with_ref_matching_negative_refspec(&commit1id);
/* assert that the reference in exists in repo1 but not in repo2 */
cl_git_pass(git_reference_lookup(&ref1, repo1, REPO1_UNDERSCORE_REFNAME));
cl_assert_equal_b(git_reference_lookup(&ref2, repo2, REPO2_UNDERSCORE_REFNAME), GIT_ENOTFOUND);
git_reference_free(ref1);
git_reference_free(ref2);
}