diff options
author | Alex Good <alex@memoryandthought.me> | 2021-05-02 13:41:59 +0100 |
---|---|---|
committer | Alex Good <alex@memoryandthought.me> | 2021-06-11 17:58:36 +0100 |
commit | 1713ab45510ceeae06d8d3f75e6e4b71a7630658 (patch) | |
tree | a1dd18f5bf315ef28b9f1553d4d3c075080fd71b | |
parent | 1ee3c37f48479e92f57c1a5da8c8393f4a745d13 (diff) | |
download | libgit2-1713ab45510ceeae06d8d3f75e6e4b71a7630658.tar.gz |
Respect the force flag on refspecs in git_remote_fetch
-rw-r--r-- | docs/coding-style.md | 2 | ||||
-rw-r--r-- | src/remote.c | 3 | ||||
-rw-r--r-- | tests/remote/fetch.c | 181 |
3 files changed, 185 insertions, 1 deletions
diff --git a/docs/coding-style.md b/docs/coding-style.md index d5188f0bc..b8b94d69c 100644 --- a/docs/coding-style.md +++ b/docs/coding-style.md @@ -172,7 +172,7 @@ tags: * * @param s String to froznicate * @return A newly allocated string or `NULL` in case an error occurred. - * / + */ char *froznicate(const char *s); ``` diff --git a/src/remote.c b/src/remote.c index 5f6ba5bf7..d17c38951 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1460,6 +1460,9 @@ static int update_tips_for_spec( if (error < 0 && error != GIT_ENOTFOUND) goto on_error; + if (!error && !spec->force && !git_graph_descendant_of(remote->repo, &head->oid, &old)) + continue; + if (error == GIT_ENOTFOUND) { memset(&old, 0, GIT_OID_RAWSZ); diff --git a/tests/remote/fetch.c b/tests/remote/fetch.c new file mode 100644 index 000000000..ac37fc2d3 --- /dev/null +++ b/tests/remote/fetch.c @@ -0,0 +1,181 @@ +#include "../clar_libgit2.h" + +#include "remote.h" +#include "repository.h" + +static git_repository *repo1; +static git_repository *repo2; + +static const char *REPO1_REFNAME = "refs/heads/main"; +static const char *REPO2_REFNAME = "refs/remotes/repo1/main"; +static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main"; +static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main"; + +char* strip_trailing_slash(char *path) { + char *result = NULL; + if (path[strlen(path) - 1] == '/') { + result = (char *) malloc(strlen(path) - 1); + memcpy(result, path, strlen(path) - 1); + } else { + result = (char *) malloc(strlen(path)); + strncpy(result, path, strlen(path)); + } + return result; +} + + +void test_remote_fetch__initialize(void) { + git_config *c; + git_buf repo1_path = GIT_BUF_INIT; + git_buf repo2_path = GIT_BUF_INIT; + const char *sandbox = clar_sandbox_path(); + + cl_git_pass(git_buf_join(&repo1_path, '/', sandbox, "fetchtest_repo1")); + cl_git_pass(git_repository_init(&repo1, repo1_path.ptr, true)); + + cl_git_pass(git_buf_join(&repo2_path, '/', sandbox, "fetchtest_repo2")); + cl_git_pass(git_repository_init(&repo2, repo2_path.ptr, true)); + + cl_git_pass(git_repository_config(&c, repo1)); + cl_git_pass(git_config_set_string(c, "user.email", "some@email")); + cl_git_pass(git_config_set_string(c, "user.name", "some@name")); + git_config_free(c); + git_buf_dispose(&repo1_path); + git_buf_dispose(&repo2_path); +} + +void test_remote_fetch__cleanup(void) { + char *repo1_path = strip_trailing_slash(repo1->gitdir); + char *repo2_path = strip_trailing_slash(repo2->gitdir); + + git_repository_free(repo1); + git_repository_free(repo2); + + cl_git_pass(git_futils_rmdir_r(repo1_path, NULL, GIT_RMDIR_REMOVE_FILES)); + free(repo1_path); + + cl_git_pass(git_futils_rmdir_r(repo2_path, NULL, GIT_RMDIR_REMOVE_FILES)); + free(repo2_path); +} + + +/** + * This checks that the '+' flag on fetchspecs is respected. We create a + * repository that has a reference to two commits, one a child of the other. + * We fetch this repository into a second repository. Then we reset the + * reference in the first repository and run the fetch again. If the '+' flag + * is used then the reference in the second repository will change, but if it + * is not then it should stay the same. + * + * @param commit1id A pointer to an OID which will be populated with the first + * commit. + * @param commit2id A pointer to an OID which will be populated with the second + * commit, which is a descendant of the first. + * @param force Whether to use a spec with '+' prefixed to force the refs + * to update + */ +void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, + bool force) { + char *refspec_strs = { + force ? FORCE_FETCHSPEC : NON_FORCE_FETCHSPEC, + }; + git_strarray refspecs = { + .count = 1, + .strings = &refspec_strs, + }; + + // create two commits in repo 1 and a reference to them + { + 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_REFNAME, sig, + sig, NULL, "one", empty_tree, 0, NULL)); + cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, + sig, NULL, "two", empty_tree, 1, commit1id)); + + git_tree_free(empty_tree); + git_signature_free(sig); + git_treebuilder_free(tb); + } + + // fetch the reference via the remote + { + 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); + } + + // assert that repo2 references the second commit + { + const git_oid *target; + git_reference *ref; + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, commit2id), 0); + git_reference_free(ref); + } + + // set the reference in repo1 to point to the older commit + { + git_reference *ref; + git_reference *ref2; + cl_git_pass(git_reference_lookup(&ref, repo1, REPO1_REFNAME)); + cl_git_pass(git_reference_set_target(&ref2, ref, commit1id, + "rollback")); + git_reference_free(ref); + git_reference_free(ref2); + } + + // fetch the reference again + { + 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__dont_update_refs_if_not_descendant_and_not_force(void) { + const git_oid *target; + git_oid commit1id; + git_oid commit2id; + git_reference *ref; + + do_time_travelling_fetch(&commit1id, &commit2id, false); + + // assert that the reference in repo2 has not changed + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, &commit2id), 0); + + git_reference_free(ref); +} + +void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) { + const git_oid *target; + git_oid commit1id; + git_oid commit2id; + git_reference *ref; + + do_time_travelling_fetch(&commit1id, &commit2id, true); + + // assert that the reference in repo2 has changed + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, &commit1id), 0); + + git_reference_free(ref); +} |