8000 Merge pull request #6962 from ryan-ph/ryanpham/negative-refspec/remote · libgit2/libgit2@ccc2028 · GitHub
[go: up one dir, main page]

Skip to content

Commit ccc2028

Browse files
authored
Merge pull request #6962 from ryan-ph/ryanpham/negative-refspec/remote
remote: Handle fetching negative refspecs
2 parents 536f868 + 099e62c commit ccc2028

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

include/git2/refspec.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
7878
*/
7979
GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);
8080

81+
/**
82+
* Check if a refspec's source descriptor matches a negative reference
83+
*
84+
* @param refspec the refspec
85+
* @param refname the name of the reference to check
86+
* @return 1 if the refspec matches, 0 otherwise
87+
*/
88+
GIT_EXTERN(int) git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname);
89+
8190
/**
8291
* Check if a refspec's source descriptor matches a reference
8392
*

src/libgit2/refspec.c

Lines changed: 28 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)
@@ -225,6 +236,14 @@ int git_refspec_force(const git_refspec *refspec)
225236
return refspec->force;
226237
}
227238

239+
int git_refspec_src_matches_negative(const git_refspec *refspec, const char *refname)
240+
{
241+
if (refspec == NULL || refspec->src == NULL || !git_refspec_is_negative(refspec))
242+
return false;
243+
244+
return (wildmatch(refspec->src + 1, refname, 0) == 0);
245+
}
246+
228247
int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
229248
{
230249
if (refspec == NULL || refspec->src == NULL)
@@ -340,6 +359,14 @@ int git_refspec_is_wildcard(const git_refspec *spec)
340359
return (spec->src[strlen(spec->src) - 1] == '*');
341360
}
342361

362+
int git_refspec_is_negative(const git_refspec *spec)
363+
{
364+
GIT_ASSERT_ARG(spec);
365+
GIT_ASSERT_ARG(spec->src);
366+
367+
return (spec->src[0] == '^' && spec->dst == NULL);
368+
}
369+
343370
git_direction git_refspec_direction(const git_refspec *spec)
344371
{
345372
GIT_ASSERT_ARG(spec);

src/libgit2/refspec.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ int git_refspec__serialize(git_str *out, const git_refspec *refspec);
4545
*/
4646
int git_refspec_is_wildcard(const git_refspec *spec);
4747

48+
/**
49+
* Determines if a refspec is a negative refspec.
50+
*
51+
* @param spec the refspec
52+
* @return 1 if the refspec is a negative, 0 otherwise
53+
*/
54+
int git_refspec_is_negative(const git_refspec *spec);
55+
4856
/**
4957
* DWIM `spec` with `refs` existing on the remote, append the dwim'ed
5058
* result in `out`.

src/libgit2/remote.c

Lines changed: 7 additions & 3 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

2637-
if (git_refspec_src_matches(spec, refname))
2638-
return spec;
2638+
if (git_refspec_src_matches_negative(spec, refname))
2639+
return NULL;
2640+
2641+
if (git_refspec_src_matches(spec, refname) && match == NULL)
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_REFSPEC_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: 61 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,61 @@ 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< 10000 /span>(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 = { &refspec_strs, 1 };
209+
210+
git_remote *remote;
211+
212+
cl_git_pass(git_remote_create_anonymous(&remote, repo2,
213+
git_repository_path(repo1)));
214+
cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message"));
215+
216+
git_remote_free(remote);
217+
}
218+
}
219+
220+
void test_remote_fetch__skip_negative_refspec_match(void) {
221+
git_oid commit1id;
222+
git_reference *ref1;
223+
git_reference *ref2;
224+
225+
do_fetch_repo_with_ref_matching_negative_refspec(&commit1id);
226+
227+
/* assert that the reference in exists in repo1 but not in repo2 */
228+
cl_git_pass(git_reference_lookup(&ref1, repo1, REPO1_UNDERSCORE_REFNAME));
229+
cl_assert_equal_b(git_reference_lookup(&ref2, repo2, REPO2_UNDERSCORE_REFNAME), GIT_ENOTFOUND);
230+
231+
git_reference_free(ref1);
232+
git_reference_free(ref2);
233+
}

0 commit comments

Comments
 (0)
0