Skip to content

Commit 5fd4423

Browse files
authored
Merge pull request libgit2#5854 from alexjg/respect-force-flag-in-remote-fetch
Respect the force flag on refspecs in git_remote_fetch
2 parents 9c64acd + a569670 commit 5fd4423

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

src/remote.c

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

1463+
if (!(error || error == GIT_ENOTFOUND)
1464+
&& !spec->force
1465+
&& !git_graph_descendant_of(remote->repo, &head->oid, &old))
1466+
continue;
1467+
14631468
if (error == GIT_ENOTFOUND) {
14641469
memset(&old, 0, GIT_OID_RAWSZ);
14651470

tests/remote/fetch.c

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

0 commit comments

Comments
 (0)