diff options
| author | Edward Thomson <ethomson@edwardthomson.com> | 2021-11-16 23:29:22 -0500 |
|---|---|---|
| committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-02-22 22:07:45 -0500 |
| commit | 3344fddc97bbdea9c1b6ebb6f7fb6dbd70b41dfb (patch) | |
| tree | fd6368a72944571c51627b40c592e7d58e0036e1 /tests/libgit2/refs/reflog | |
| parent | 91ba089663f5efc3bd4ba14a5099372cf5ce57a6 (diff) | |
| download | libgit2-3344fddc97bbdea9c1b6ebb6f7fb6dbd70b41dfb.tar.gz | |
refactor: `tests` is now `tests/libgit2`
Like we want to separate libgit2 and utility source code, we want to
separate libgit2 and utility tests. Start by moving all the tests into
libgit2.
Diffstat (limited to 'tests/libgit2/refs/reflog')
| -rw-r--r-- | tests/libgit2/refs/reflog/drop.c | 115 | ||||
| -rw-r--r-- | tests/libgit2/refs/reflog/messages.c | 421 | ||||
| -rw-r--r-- | tests/libgit2/refs/reflog/reflog.c | 469 | ||||
| -rw-r--r-- | tests/libgit2/refs/reflog/reflog_helpers.c | 120 | ||||
| -rw-r--r-- | tests/libgit2/refs/reflog/reflog_helpers.h | 12 |
5 files changed, 1137 insertions, 0 deletions
diff --git a/tests/libgit2/refs/reflog/drop.c b/tests/libgit2/refs/reflog/drop.c new file mode 100644 index 000000000..916bd9933 --- /dev/null +++ b/tests/libgit2/refs/reflog/drop.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" + +#include "reflog.h" + +static git_repository *g_repo; +static git_reflog *g_reflog; +static size_t entrycount; + +void test_refs_reflog_drop__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + + git_reflog_read(&g_reflog, g_repo, "HEAD"); + entrycount = git_reflog_entrycount(g_reflog); +} + +void test_refs_reflog_drop__cleanup(void) +{ + git_reflog_free(g_reflog); + g_reflog = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0)); + + cl_assert_equal_sz(entrycount, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry(void) +{ + cl_assert(entrycount > 4); + + cl_git_pass(git_reflog_drop(g_reflog, 2, 0)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *before_current; + const git_reflog_entry *after_current; + git_oid before_current_old_oid, before_current_cur_oid; + + cl_assert(entrycount > 4); + + before_current = git_reflog_entry_byindex(g_reflog, 1); + + git_oid_cpy(&before_current_old_oid, &before_current->oid_old); + git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur); + + cl_git_pass(git_reflog_drop(g_reflog, 1, 1)); + + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + after_current = git_reflog_entry_byindex(g_reflog, 0); + + cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old)); + cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur)); +} + +void test_refs_reflog_drop__can_drop_the_oldest_entry(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); +} + +void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); +} + +void test_refs_reflog_drop__can_drop_all_the_entries(void) +{ + cl_assert(--entrycount > 0); + + do { + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + } while (--entrycount > 0); + + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + + cl_assert_equal_i(0, (int)git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_persist_deletion_on_disk(void) +{ + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + cl_git_pass(git_reflog_write(g_reflog)); + + git_reflog_free(g_reflog); + + git_reflog_read(&g_reflog, g_repo, "HEAD"); + + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); +} diff --git a/tests/libgit2/refs/reflog/messages.c b/tests/libgit2/refs/reflog/messages.c new file mode 100644 index 000000000..ed183d2f2 --- /dev/null +++ b/tests/libgit2/refs/reflog/messages.c @@ -0,0 +1,421 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "refs.h" +#include "reflog_helpers.h" + +static const char *g_email = "foo@example.com"; +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_refs_reflog_messages__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_set_ident(g_repo, "Foo Bar", g_email)); +} + +void test_refs_reflog_messages__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_messages__setting_head_updates_reflog(void) +{ + git_object *tag; + git_annotated_commit *annotated; + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 4 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/unborn")); + cl_git_pass(git_revparse_single(&tag, g_repo, "tags/test")); + cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(tag))); /* 3 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 2 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/tags/test")); /* 1 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/remotes/test/master")); /* 0 */ + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 4, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from master to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 3, + NULL, "tags/test^{commit}", + "foo@example.com", + "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 2, + "tags/test^{commit}", "refs/heads/haacked", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 1, + "refs/heads/haacked", "tags/test^{commit}", + "foo@example.com", + "checkout: moving from haacked to test"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "tags/test^{commit}", "refs/remotes/test/master", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master"); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "haacked~0")); + cl_git_pass(git_repository_set_head_detached_from_annotated(g_repo, annotated)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0"); + + git_annotated_commit_free(annotated); + git_object_free(tag); +} + +void test_refs_reflog_messages__setting_head_to_same_target_ignores_reflog(void) +{ + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); +} + +void test_refs_reflog_messages__detaching_writes_reflog(void) +{ + git_oid id; + const char *msg; + + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + msg = "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "e90810b8df3e80c413d903f631643c716887138d", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); +} + +void test_refs_reflog_messages__orphan_branch_does_not_count(void) +{ + git_oid id; + const char *msg; + + /* Have something known */ + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* Switching to an orphan branch does not write to the reflog */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* And coming back, we set the source to zero */ + msg = "checkout: moving from orphan to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "0000000000000000000000000000000000000000", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); +} + +void test_refs_reflog_messages__branch_birth(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref; + const char *msg; + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJECT_TREE)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries, nentries_after); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); + + git_signature_free(sig); + git_tree_free(tree); + git_reference_free(ref); +} + +void test_refs_reflog_messages__commit_on_symbolic_ref_updates_head_reflog(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref1, *ref2; + const char *msg; + size_t nentries_head, nentries_master; + + nentries_head = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref1, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref1, GIT_OBJECT_TREE)); + + nentries_master = reflog_entrycount(g_repo, "refs/heads/master"); + + msg = "message 1"; + cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, "refs/heads/master", "refs/heads/foo", 1, msg)); + + cl_assert_equal_i(0, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head + 1, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + git_signature_free(sig); + git_reference_free(ref1); + git_reference_free(ref2); + git_tree_free(tree); +} + +void test_refs_reflog_messages__show_merge_for_merge_commits(void) +{ + git_oid b1_oid; + git_oid b2_oid; + git_oid merge_commit_oid; + git_commit *b1_commit; + git_commit *b2_commit; + git_signature *s; + git_commit *parent_commits[2]; + git_tree *tree; + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_pass(git_reference_name_to_id(&b1_oid, g_repo, "HEAD")); + cl_git_pass(git_reference_name_to_id(&b2_oid, g_repo, "refs/heads/test")); + + cl_git_pass(git_commit_lookup(&b1_commit, g_repo, &b1_oid)); + cl_git_pass(git_commit_lookup(&b2_commit, g_repo, &b2_oid)); + + parent_commits[0] = b1_commit; + parent_commits[1] = b2_commit; + + cl_git_pass(git_commit_tree(&tree, b1_commit)); + + cl_git_pass(git_commit_create(&merge_commit_oid, + g_repo, "HEAD", s, s, NULL, + "Merge commit", tree, + 2, (const struct git_commit **) parent_commits)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, + git_oid_tostr_s(&merge_commit_oid), + NULL, "commit (merge): Merge commit"); + + git_tree_free(tree); + git_commit_free(b1_commit); + git_commit_free(b2_commit); + git_signature_free(s); +} + +void test_refs_reflog_messages__creating_a_direct_reference(void) +{ + git_reference *reference; + git_oid id; + git_reflog *reflog; + const git_reflog_entry *entry; + + const char *name = "refs/heads/new-head"; + const char *message = "You've been logged, mate!"; + + cl_git_pass(git_reference_name_to_id(&id, g_repo, "HEAD")); + + cl_git_pass(git_reference_create(&reference, g_repo, name, &id, 0, message)); + + cl_git_pass(git_reflog_read(&reflog, g_repo, name)); + cl_assert_equal_sz(1, git_reflog_entrycount(reflog)); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + cl_assert_equal_oid(&id, &entry->oid_cur); + cl_assert_equal_s(message, entry->msg); + + git_reflog_free(reflog); + git_reference_free(reference); +} + +void test_refs_reflog_messages__newline_gets_replaced(void) +{ + const git_reflog_entry *entry; + git_signature *signature; + git_reflog *reflog; + git_oid oid; + + cl_git_pass(git_signature_now(&signature, "me", "foo@example.com")); + cl_git_pass(git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, "HEAD")); + cl_assert_equal_sz(7, git_reflog_entrycount(reflog)); + cl_git_pass(git_reflog_append(reflog, &oid, signature, "inner\nnewline")); + cl_assert_equal_sz(8, git_reflog_entrycount(reflog)); + + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "inner newline"); + + git_signature_free(signature); + git_reflog_free(reflog); +} + +void test_refs_reflog_messages__renaming_ref(void) +{ + git_reference *ref, *new_ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_ref, ref, "refs/heads/renamed", false, + "message")); + + cl_reflog_check_entry(g_repo, git_reference_name(new_ref), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "foo@example.com", "message"); + + git_reference_free(ref); + git_reference_free(new_ref); +} + +void test_refs_reflog_messages__updating_a_direct_reference(void) +{ + git_reference *ref, *ref_out, *target_ref; + git_oid target_id; + const char *message = "You've been logged, mate!"; + + git_reference_name_to_id(&target_id, g_repo, "refs/heads/haacked"); + cl_git_pass(git_reference_lookup(&target_ref, g_repo, "refs/heads/haacked")); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + + cl_git_pass(git_reference_set_target(&ref_out, ref, &target_id, message)); + + cl_reflog_check_entry(g_repo, "refs/heads/master", 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, message); + + git_reference_free(target_ref); + git_reference_free(ref); + git_reference_free(ref_out); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_reflog_messages__creating_branches_default_messages(void) +{ + git_str buf = GIT_STR_INIT; + git_annotated_commit *annotated; + git_object *obj; + git_commit *target; + git_reference *branch1, *branch2; + + cl_git_pass(git_revparse_single(&obj, g_repo, "e90810b8df3")); + cl_git_pass(git_commit_lookup(&target, g_repo, git_object_id(obj))); + git_object_free(obj); + + cl_git_pass(git_branch_create(&branch1, g_repo, NEW_BRANCH_NAME, target, false)); + + cl_git_pass(git_str_printf(&buf, "branch: Created from %s", git_oid_tostr_s(git_commit_id(target)))); + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, git_str_cstr(&buf)); + + cl_git_pass(git_reference_remove(g_repo, "refs/heads/" NEW_BRANCH_NAME)); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "e90810b8df3")); + cl_git_pass(git_branch_create_from_annotated(&branch2, g_repo, NEW_BRANCH_NAME, annotated, true)); + + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, "branch: Created from e90810b8df3"); + + git_annotated_commit_free(annotated); + git_str_dispose(&buf); + git_commit_free(target); + git_reference_free(branch1); + git_reference_free(branch2); +} + +void test_refs_reflog_messages__moving_branch_default_message(void) +{ + git_reference *branch; + git_reference *new_branch; + git_oid id; + + cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master")); + git_oid_cpy(&id, git_reference_target(branch)); + cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); + + cl_reflog_check_entry(g_repo, git_reference_name(new_branch), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + g_email, + "branch: renamed refs/heads/master to refs/heads/master2"); + + git_reference_free(branch); + git_reference_free(new_branch); +} + +void test_refs_reflog_messages__detaching_head_default_message(void) +{ + git_reference *ref; + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + cl_git_pass(git_repository_detach_head(g_repo)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "checkout: moving from master to a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_assert_equal_i(true, git_repository_head_detached(g_repo)); + + /* take the repo back to its original state */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", "refs/heads/master", + true, "REATTACH")); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "REATTACH"); + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/reflog/reflog.c b/tests/libgit2/refs/reflog/reflog.c new file mode 100644 index 000000000..32ce7ffb7 --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog.c @@ -0,0 +1,469 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *new_ref = "refs/heads/test-reflog"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +#define commit_msg "commit: bla bla" + +static git_repository *g_repo; + + +/* helpers */ +static void assert_signature(const git_signature *expected, const git_signature *actual) +{ + cl_assert(actual); + cl_assert_equal_s(expected->name, actual->name); + cl_assert_equal_s(expected->email, actual->email); + cl_assert(expected->when.offset == actual->when.offset); + cl_assert(expected->when.time == actual->when.time); +} + + +/* Fixture setup and teardown */ +void test_refs_reflog_reflog__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_reflog_reflog__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_appends(const git_signature *committer, const git_oid *oid) +{ + git_repository *repo2; + git_reference *lookedup_ref; + git_reflog *reflog; + const git_reflog_entry *entry; + + /* Reopen a new instance of the repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo.git")); + + /* Lookup the previously created branch */ + cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); + + /* Read and parse the reflog for this branch */ + cl_git_pass(git_reflog_read(&reflog, repo2, new_ref)); + cl_assert_equal_i(3, (int)git_reflog_entrycount(reflog)); + + /* The first one was the creation of the branch */ + entry = git_reflog_entry_byindex(reflog, 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + + entry = git_reflog_entry_byindex(reflog, 1); + assert_signature(committer, entry->committer); + cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + cl_assert(entry->msg == NULL); + + entry = git_reflog_entry_byindex(reflog, 0); + assert_signature(committer, entry->committer); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + cl_assert_equal_s(commit_msg, entry->msg); + + git_reflog_free(reflog); + git_repository_free(repo2); + + git_reference_free(lookedup_ref); +} + +void test_refs_reflog_reflog__append_then_read(void) +{ + /* write a reflog for a given reference and ensure it can be read back */ + git_reference *ref; + git_oid oid; + git_signature *committer; + git_reflog *reflog; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0, NULL)); + git_reference_free(ref); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_write(reflog)); + + assert_appends(committer, &oid); + + git_reflog_free(reflog); + git_signature_free(committer); +} + +void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) +{ + git_reference *master, *new_master; + git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; + + git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_puts(&moved_log_path, git_str_cstr(&master_log_path)); + git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); + git_str_joinpath(&moved_log_path, git_str_cstr(&moved_log_path), "refs/moved"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&moved_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); + git_reference_free(master); + + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&moved_log_path))); + + git_reference_free(new_master); + git_str_dispose(&moved_log_path); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__deleting_the_reference_deletes_the_reflog(void) +{ + git_reference *master; + git_str master_log_path = GIT_STR_INIT; + + git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_delete(master)); + git_reference_free(master); + + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__removes_empty_reflog_dir(void) +{ + git_reference *ref; + git_str log_path = GIT_STR_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + + git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); + + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + /* new ref creation should succeed since new-dir is empty */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_str_dispose(&log_path); +} + +void test_refs_reflog_reflog__fails_gracefully_on_nonempty_reflog_dir(void) +{ + git_reference *ref; + git_str log_path = GIT_STR_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + git_reference_free(ref); + + git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); + + /* delete the ref manually, leave the reflog */ + cl_must_pass(p_unlink("testrepo.git/refs/heads/new-dir/new-head")); + + /* new ref creation should fail since new-dir contains reflogs still */ + git_oid_fromstr(&id, current_master_tip); + cl_git_fail_with(GIT_EDIRECTORY, git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_str_dispose(&log_path); +} + +static void assert_has_reflog(bool expected_result, const char *name) +{ + cl_assert_equal_i(expected_result, git_reference_has_log(g_repo, name)); +} + +void test_refs_reflog_reflog__reference_has_reflog(void) +{ + assert_has_reflog(true, "HEAD"); + assert_has_reflog(true, "refs/heads/master"); + assert_has_reflog(false, "refs/heads/subtrees"); +} + +void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) +{ + git_reflog *reflog; + const char *refname = "refs/heads/subtrees"; + git_str subtrees_log_path = GIT_STR_INIT; + + git_str_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&subtrees_log_path))); + + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); + + cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + git_str_dispose(&subtrees_log_path); +} + +void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_succeeds(void) +{ + git_reflog *reflog; + const char *refname = "refs/heads/newline"; + const char *refmessage = + "Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse."; + const git_reflog_entry *entry; + git_reference *ref; + git_oid id; + git_str logpath = GIT_STR_INIT, logcontents = GIT_STR_INIT; + char *star; + + /* Create a new branch. */ + cl_git_pass(git_oid_fromstr(&id, current_master_tip)); + cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage)); + + /* + * Corrupt the branch reflog by introducing a newline inside the reflog message. + * We do this by replacing '*' with '\n' + */ + cl_git_pass(git_str_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname)); + cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); + cl_assert((star = strchr(git_str_cstr(&logcontents), '*')) != NULL); + *star = '\n'; + cl_git_rewritefile(git_str_cstr(&logpath), git_str_cstr(&logcontents)); + + /* + * Confirm that the file was rewritten successfully + * and now contains a '\n' in the expected location + */ + cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); + cl_assert(strstr(git_str_cstr(&logcontents), "Reflog\nmessage") != NULL); + + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "Reflog"); + + git_reference_free(ref); + git_reflog_free(reflog); + git_str_dispose(&logpath); + git_str_dispose(&logcontents); +} + +void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) +{ + git_reference *master, *new_master; + git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; + git_reflog *reflog; + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master")); + + cl_git_pass(git_reflog_write(reflog)); + + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); + git_reference_free(master); + + cl_git_fail(git_reflog_write(reflog)); + + git_reflog_free(reflog); + git_reference_free(new_master); + git_str_dispose(&moved_log_path); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id")); +} + +void test_refs_reflog_reflog__write_only_std_locations(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, current_master_tip); + + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/foo", &id, 1, NULL)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/notes/foo", &id, 1, NULL)); + git_reference_free(ref); + + assert_has_reflog(true, "refs/heads/foo"); + assert_has_reflog(false, "refs/tags/foo"); + assert_has_reflog(true, "refs/notes/foo"); + +} + +void test_refs_reflog_reflog__write_when_explicitly_active(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, current_master_tip); + git_reference_ensure_log(g_repo, "refs/tags/foo"); + + cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); + git_reference_free(ref); + assert_has_reflog(true, "refs/tags/foo"); +} + +void test_refs_reflog_reflog__append_to_HEAD_when_changing_current_branch(void) +{ + size_t nlogs, nlogs_after; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + /* Move it back */ + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs + 1); +} + +void test_refs_reflog_reflog__do_not_append_when_no_update(void) +{ + size_t nlogs, nlogs_after; + git_reference *ref, *ref2; + git_reflog *log; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/heads/master", + git_reference_target(ref), 1, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); +} + +static void assert_no_reflog_update(void) +{ + size_t nlogs, nlogs_after; + size_t nlogs_master, nlogs_master_after; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); + nlogs_master = git_reflog_entrycount(log); + git_reflog_free(log); + + /* Move it back */ + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); + nlogs_master_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); + cl_assert_equal_i(nlogs_master_after, nlogs_master); + +} + +void test_refs_reflog_reflog__logallrefupdates_bare_set_false(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); + git_config_free(config); + + assert_no_reflog_update(); +} + +void test_refs_reflog_reflog__logallrefupdates_bare_set_always(void) +{ + git_config *config; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_string(config, "core.logallrefupdates", "always")); + git_config_free(config); + + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/bork", &id, 1, "message")); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/bork")); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + cl_assert_equal_s("message", git_reflog_entry_byindex(log, 0)->msg); + + git_reflog_free(log); + git_reference_free(ref); +} + +void test_refs_reflog_reflog__logallrefupdates_bare_unset(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_delete_entry(config, "core.logallrefupdates")); + git_config_free(config); + + assert_no_reflog_update(); +} + +void test_refs_reflog_reflog__logallrefupdates_nonbare_set_false(void) +{ + git_config *config; + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("testrepo"); + + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); + git_config_free(config); + + assert_no_reflog_update(); +} diff --git a/tests/libgit2/refs/reflog/reflog_helpers.c b/tests/libgit2/refs/reflog/reflog_helpers.c new file mode 100644 index 000000000..2ea41ee06 --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog_helpers.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "reflog.h" +#include "reflog_helpers.h" + +int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry) +{ + char old_oid[GIT_OID_HEXSZ], new_oid[GIT_OID_HEXSZ]; + + assert(out && entry); + + git_oid_tostr((char *)&old_oid, GIT_OID_HEXSZ, git_reflog_entry_id_old(entry)); + git_oid_tostr((char *)&new_oid, GIT_OID_HEXSZ, git_reflog_entry_id_new(entry)); + + return git_str_printf(out, "%s %s %s %s", old_oid, new_oid, "somesig", git_reflog_entry_message(entry)); +} + +size_t reflog_entrycount(git_repository *repo, const char *name) +{ + git_reflog *log; + size_t ret; + + cl_git_pass(git_reflog_read(&log, repo, name)); + ret = git_reflog_entrycount(log); + git_reflog_free(log); + + return ret; +} + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, + const char *file, const char *func, int line) +{ + git_reflog *log; + const git_reflog_entry *entry; + git_str result = GIT_STR_INIT; + + cl_git_pass(git_reflog_read(&log, repo, reflog)); + entry = git_reflog_entry_byindex(log, idx); + if (entry == NULL) + clar__fail(file, func, line, "Reflog has no such entry", NULL, 1); + + if (old_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, old_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_str_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, old_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_str_puts(&result, "\"\n"); + } + git__free(oid); + } + } + if (new_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, new_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_str_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, new_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_str_puts(&result, "\"\n"); + } + git__free(oid); + } + } + + if (email && strcmp(email, git_reflog_entry_committer(entry)->email) != 0) + git_str_printf(&result, "\tEmail: \"%s\" != \"%s\"\n", email, git_reflog_entry_committer(entry)->email); + + if (message) { + const char *entry_msg = git_reflog_entry_message(entry); + if (entry_msg == NULL) entry_msg = ""; + + if (entry_msg && strcmp(message, entry_msg) != 0) + git_str_printf(&result, "\tMessage: \"%s\" != \"%s\"\n", message, entry_msg); + } + if (git_str_len(&result) != 0) + clar__fail(file, func, line, "Reflog entry mismatch", git_str_cstr(&result), 1); + + git_str_dispose(&result); + git_reflog_free(log); +} + +void reflog_print(git_repository *repo, const char *reflog_name) +{ + git_reflog *reflog; + size_t idx; + git_str out = GIT_STR_INIT; + + git_reflog_read(&reflog, repo, reflog_name); + + for (idx = 0; idx < git_reflog_entrycount(reflog); idx++) { + const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, idx); + reflog_entry_tostr(&out, entry); + git_str_putc(&out, '\n'); + } + + fprintf(stderr, "%s", git_str_cstr(&out)); + git_str_dispose(&out); + git_reflog_free(reflog); +} diff --git a/tests/libgit2/refs/reflog/reflog_helpers.h b/tests/libgit2/refs/reflog/reflog_helpers.h new file mode 100644 index 000000000..4cd92cadc --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog_helpers.h @@ -0,0 +1,12 @@ +size_t reflog_entrycount(git_repository *repo, const char *name); + +#define cl_reflog_check_entry(repo, reflog, idx, old_spec, new_spec, email, message) \ + cl_reflog_check_entry_(repo, reflog, idx, old_spec, new_spec, email, message, __FILE__, __FUNCTION__, __LINE__) + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, + const char *file, const char *func, int line); + +void reflog_print(git_repository *repo, const char *reflog_name); +int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry); |
