mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
Merge pull request #6962 from ryan-ph/ryanpham/negative-refspec/remote
remote: Handle fetching negative refspecs
This commit is contained in:
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user