Skip to content

Commit 1713ab4

Browse files
committed
Respect the force flag on refspecs in git_remote_fetch
1 parent 1ee3c37 commit 1713ab4

File tree

3 files changed

+185
-1
lines changed

3 files changed

+185
-1
lines changed

docs/coding-style.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ tags:
172172
*
173173
* @param s String to froznicate
174174
* @return A newly allocated string or `NULL` in case an error occurred.
175-
* /
175+
*/
176176
char *froznicate(const char *s);
177177
```
178178

src/remote.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,9 @@ static int update_tips_for_spec(
14601460
if (error < 0 && error != GIT_ENOTFOUND)
14611461
goto on_error;
14621462

1463+
if (!error && !spec->force && !git_graph_descendant_of(remote->repo, &head->oid, &old))
1464+
continue;
1465+
14631466
if (error == GIT_ENOTFOUND) {
14641467
memset(&old, 0, GIT_OID_RAWSZ);
14651468

tests/remote/fetch.c

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#include "../clar_libgit2.h"
2+
3+
#include "remote.h"
4+
#include "repository.h"
5+
6+
static git_repository *repo1;
7+
static git_repository *repo2;
8+
9+
static const char *REPO1_REFNAME = "refs/heads/main";
10+
static const char *REPO2_REFNAME = "refs/remotes/repo1/main";
11+
static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main";
12+
static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main";
13+
14+
char* strip_trailing_slash(char *path) {
15+
char *result = NULL;
16+
if (path[strlen(path) - 1] == '/') {
17+
result = (char *) malloc(strlen(path) - 1);
18+
memcpy(result, path, strlen(path) - 1);
19+
} else {
20+
result = (char *) malloc(strlen(path));
21+
strncpy(result, path, strlen(path));
22+
}
23+
return result;
24+
}
25+
26+
27+
void test_remote_fetch__initialize(void) {
28+
git_config *c;
29+
git_buf repo1_path = GIT_BUF_INIT;
30+
git_buf repo2_path = GIT_BUF_INIT;
31+
const char *sandbox = clar_sandbox_path();
32+
33+
cl_git_pass(git_buf_join(&repo1_path, '/', sandbox, "fetchtest_repo1"));
34+
cl_git_pass(git_repository_init(&repo1, repo1_path.ptr, true));
35+
36+
cl_git_pass(git_buf_join(&repo2_path, '/', sandbox, "fetchtest_repo2"));
37+
cl_git_pass(git_repository_init(&repo2, repo2_path.ptr, true));
38+
39+
cl_git_pass(git_repository_config(&c, repo1));
40+
cl_git_pass(git_config_set_string(c, "user.email", "some@email"));
41+
cl_git_pass(git_config_set_string(c, "user.name", "some@name"));
42+
git_config_free(c);
43+
git_buf_dispose(&repo1_path);
44+
git_buf_dispose(&repo2_path);
45+
}
46+
47+
void test_remote_fetch__cleanup(void) {
48+
char *repo1_path = strip_trailing_slash(repo1->gitdir);
49+
char *repo2_path = strip_trailing_slash(repo2->gitdir);
50+
51+
git_repository_free(repo1);
52+
git_repository_free(repo2);
53+
54+
cl_git_pass(git_futils_rmdir_r(repo1_path, NULL, GIT_RMDIR_REMOVE_FILES));
55+
free(repo1_path);
56+
57+
cl_git_pass(git_futils_rmdir_r(repo2_path, NULL, GIT_RMDIR_REMOVE_FILES));
58+
free(repo2_path);
59+
}
60+
61+
62+
/**
63+
* This checks that the '+' flag on fetchspecs is respected. We create a
64+
* repository that has a reference to two commits, one a child of the other.
65+
* We fetch this repository into a second repository. Then we reset the
66+
* reference in the first repository and run the fetch again. If the '+' flag
67+
* is used then the reference in the second repository will change, but if it
68+
* is not then it should stay the same.
69+
*
70+
* @param commit1id A pointer to an OID which will be populated with the first
71+
* commit.
72+
* @param commit2id A pointer to an OID which will be populated with the second
73+
* commit, which is a descendant of the first.
74+
* @param force Whether to use a spec with '+' prefixed to force the refs
75+
* to update
76+
*/
77+
void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id,
78+
bool force) {
79+
char *refspec_strs = {
80+
force ? FORCE_FETCHSPEC : NON_FORCE_FETCHSPEC,
81+
};
82+
git_strarray refspecs = {
83+
.count = 1,
84+
.strings = &refspec_strs,
85+
};
86+
87+
// create two commits in repo 1 and a reference to them
88+
{
89+
git_oid empty_tree_id;
90+
git_tree *empty_tree;
91+
git_signature *sig;
92+
git_treebuilder *tb;
93+
cl_git_pass(git_treebuilder_new(&tb, repo1, NULL));
94+
cl_git_pass(git_treebuilder_write(&empty_tree_id, tb));
95+
cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id));
96+
cl_git_pass(git_signature_default(&sig, repo1));
97+
cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig,
98+
sig, NULL, "one", empty_tree, 0, NULL));
99+
cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig,
100+
sig, NULL, "two", empty_tree, 1, commit1id));
101+
102+
git_tree_free(empty_tree);
103+
git_signature_free(sig);
104+
git_treebuilder_free(tb);
105+
}
106+
107+
// fetch the reference via the remote
108+
{
109+
git_remote *remote;
110+
111+
cl_git_pass(git_remote_create_anonymous(&remote, repo2,
112+
git_repository_path(repo1)));
113+
cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message"));
114+
115+
git_remote_free(remote);
116+
}
117+
118+
// assert that repo2 references the second commit
119+
{
120+
const git_oid *target;
121+
git_reference *ref;
122+
cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME));
123+
target = git_reference_target(ref);
124+
cl_assert_equal_b(git_oid_cmp(target, commit2id), 0);
125+
git_reference_free(ref);
126+
}
127+
128+
// set the reference in repo1 to point to the older commit
129+
{
130+
git_reference *ref;
131+
git_reference *ref2;
132+
cl_git_pass(git_reference_lookup(&ref, repo1, REPO1_REFNAME));
133+
cl_git_pass(git_reference_set_target(&ref2, ref, commit1id,
134+
"rollback"));
135+
git_reference_free(ref);
136+
git_reference_free(ref2);
137+
}
138+
139+
// fetch the reference again
140+
{
141+
git_remote *remote;
142+
143+
cl_git_pass(git_remote_create_anonymous(&remote, repo2,
144+
git_repository_path(repo1)));
145+
cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message"));
146+
147+
git_remote_free(remote);
148+
}
149+
}
150+
151+
void test_remote_fetch__dont_update_refs_if_not_descendant_and_not_force(void) {
152+
const git_oid *target;
153+
git_oid commit1id;
154+
git_oid commit2id;
155+
git_reference *ref;
156+
157+
do_time_travelling_fetch(&commit1id, &commit2id, false);
158+
159+
// assert that the reference in repo2 has not changed
160+
cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME));
161+
target = git_reference_target(ref);
162+
cl_assert_equal_b(git_oid_cmp(target, &commit2id), 0);
163+
164+
git_reference_free(ref);
165+
}
166+
167+
void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) {
168+
const git_oid *target;
169+
git_oid commit1id;
170+
git_oid commit2id;
171+
git_reference *ref;
172+
173+
do_time_travelling_fetch(&commit1id, &commit2id, true);
174+
175+
// assert that the reference in repo2 has changed
176+
cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME));
177+
target = git_reference_target(ref);
178+
cl_assert_equal_b(git_oid_cmp(target, &commit1id), 0);
179+
180+
git_reference_free(ref);
181+
}

0 commit comments

Comments
 (0)