8000 remote: Handle fetching negative refspecs · libgit2/libgit2@f7f30ec · GitHub
[go: up one dir, main page]

Skip to content

Commit f7f30ec

Browse files
committed
remote: Handle fetching negative refspecs
Add support for fetching with negative refspecs. Fetching from the remote with a negative refspec will now validate any references fetched do not match _any_ negative refspecs and at least one non-negative refspec. As we must ensure that fetched references do not match any of the negative refspecs provided, we cannot short-circuit when checking for matching refspecs.
1 parent 3d9f406 commit f7f30ec

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

src/libgit2/refspec.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
2222
const char *lhs, *rhs;
2323
int valid = 0;
2424
unsigned int flags;
25+
bool is_neg_refspec = false;
2526

2627
GIT_ASSERT_ARG(refspec);
2728
GIT_ASSERT_ARG(input);
@@ -34,6 +35,9 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
3435
refspec->force = 1;
3536
lhs++;
3637
}
38+
if (*lhs == '^') {
39+
is_neg_refspec = true;
40+
}
3741

3842
rhs = strrchr(lhs, ':');
3943

@@ -62,7 +66,14 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
6266

6367
llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
6468
if (1 <= llen && memchr(lhs, '*', llen)) {
65-
if ((rhs && !is_glob) || (!rhs && is_fetch))
69+
/*
70+
* If the lefthand side contains a glob, then one of the following must be
71+
* true, otherwise the spec is invalid
72+
* 1) the rhs exists and also contains a glob
73+
* 2) it is a negative refspec (i.e. no rhs)
74+
* 3) the rhs doesn't exist and we're fetching
75+
*/
76+
if ((rhs && !is_glob) || (rhs && is_neg_refspec) || (!rhs && is_fetch && !is_neg_refspec))
6677
goto invalid;
6778
is_glob = 1;
6879
} else if (rhs && is_glob)

src/libgit2/remote.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,17 +2628,21 @@ int git_remote_name_is_valid(int *valid, const char *remote_name)
26282628
git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
26292629
{
26302630
git_refspec *spec;
2631+
git_refspec *match = NULL;
26312632
size_t i;
26322633

26332634
git_vector_foreach(&remote->active_refspecs, i, spec) {
26342635
if (spec->push)
26352636
continue;
26362637

2638+
if (git_refspec_is_negative(< 8000 span class=pl-s1>spec) && git_refspec_src_matches_negative(spec, refname))
2639+
return NULL;
2640+
26372641
if (git_refspec_src_matches(spec, refname))
2638-
return spec;
2642+
match = spec;
26392643
}
26402644

2641-
return NULL;
2645+
return match;
26422646
}
26432647

26442648
git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)

tests/libgit2/refs/normalize.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ void test_refs_normalize__negative_refspec_pattern(void)
402402
{
403403
ensure_refname_normalized(
404404
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "^foo/bar", "^foo/bar");
405+
ensure_refname_normalized(
406+
GIT_REFERENCE_FORMAT_REFS 8000 PEC_PATTERN, "^foo/bar/*", "^foo/bar/*");
405407
ensure_refname_invalid(
406408
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/^bar");
407409
}

tests/libgit2/remote/fetch.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ static char* repo2_path;
1010

1111
static const char *REPO1_REFNAME = "refs/heads/main";
1212
static const char *REPO2_REFNAME = "refs/remotes/repo1/main";
13+
static const char *REPO1_UNDERSCORE_REFNAME = "refs/heads/_branch";
14+
static const char *REPO2_UNDERSCORE_REFNAME = "refs/remotes/repo1/_branch";
1315
static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main";
1416
static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main";
17+
static char *NEGATIVE_SPEC = "^refs/heads/_*";
1518

1619
void test_remote_fetch__initialize(void) {
1720
git_config *c;
@@ -170,3 +173,64 @@ void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) {
170173

171174
git_reference_free(ref);
172175
}
176+
177+
/**
178+
* This checks that negative refspecs are respected when fetching. We create a
179+
* repository with a '_' prefixed reference. A second repository is configured
180+
* with a negative refspec to ignore any refs prefixed with '_' and fetch the
181+
* first repository into the second.
182+
*
183+
* @param commit1id A pointer to an OID which will be populated with the first
184+
* commit.
185+
*/
186+
static void do_fetch_repo_with_ref_matching_negative_refspec(git_oid *commit1id) {
187+
/* create a commit in repo 1 and a reference to it with '_' prefix */
188+
{
189+
git_oid empty_tree_id;
190+
git_tree *empty_tree;
191+
git_signature *sig;
192+
git_treebuilder *tb;
193+
cl_git_pass(git_treebuilder_new(&tb, repo1, NULL));
194+
cl_git_pass(git_treebuilder_write(&empty_tree_id, tb));
195+
cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id));
196+
cl_git_pass(git_signature_default(&sig, repo1));
197+
cl_git_pass(git_commit_create(commit1id, repo1, REPO1_UNDERSCORE_REFNAME, sig,
198+
sig, NULL, "one", empty_tree, 0, NULL));
199+
200+
git_tree_free(empty_tree);
201+
git_signature_free(sig);
202+
git_treebuilder_free(tb);
203+
}
204+
205+
/* fetch the remote with negative refspec for references prefixed with '_' */
206+
{
207+
char *refspec_strs = { NEGATIVE_SPEC };
208+
git_strarray refspecs = {
209+
.count = 1,
210+
.strings = &refspec_strs,
211+
};
212+
213+
git_remote *remote;
214+
215+
cl_git_pass(git_remote_create_anonymous(&remote, repo2,
216+
git_repository_path(repo1)));
217+
cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message"));
218+
219+
git_remote_free(remote);
220+
}
221+
}
222+
223+
void test_remote_fetch__skip_negative_refspec_match(void) {
224+
git_oid commit1id;
225+
git_reference *ref1;
226+
git_reference *ref2;
227+
228+
do_fetch_repo_with_ref_matching_negative_refspec(&commit1id);
229+
230+
/* assert that the reference in exists in repo1 but not in repo2 */
231+
cl_git_pass(git_reference_lookup(&ref1, repo1, REPO1_UNDERSCORE_REFNAME));
232+
cl_assert_equal_b(git_reference_lookup(&ref2, repo2, REPO2_UNDERSCORE_REFNAME), GIT_ENOTFOUND);
233+
234+
git_reference_free(ref1);
235+
git_reference_free(ref2);
236+
}

0 commit comments

Comments
 (0)
0