From 74a24005146d0f908b2660db5f7e198adad4ab64 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 21 Sep 2012 10:28:20 +0200 Subject: [PATCH 01/13] refs: use constants for well-known names --- src/clone.c | 8 ++++---- src/refs.c | 2 +- src/repository.c | 8 ++++---- src/revparse.c | 12 ++++++------ src/submodule.c | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/clone.c b/src/clone.c index 80a13d0f2..9dfc22c59 100644 --- a/src/clone.c +++ b/src/clone.c @@ -50,7 +50,7 @@ static int create_tracking_branch(git_repository *repo, const git_oid *target, c git_buf merge_target = GIT_BUF_INIT; if (!git_buf_printf(&remote, "branch.%s.remote", name) && !git_buf_printf(&merge, "branch.%s.merge", name) && - !git_buf_printf(&merge_target, "refs/heads/%s", name) && + !git_buf_printf(&merge_target, GIT_REFS_HEADS_DIR "%s", name) && !git_config_set_string(cfg, git_buf_cstr(&remote), "origin") && !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { retcode = 0; @@ -77,7 +77,7 @@ static int reference_matches_remote_head(const char *head_name, void *payload) if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) && !git_oid_cmp(&head_info->remote_head_oid, &oid)) { git_buf_puts(&head_info->branchname, - head_name+strlen("refs/remotes/origin/")); + head_name+strlen(GIT_REFS_REMOTES_DIR "origin/")); } return 0; } @@ -90,7 +90,7 @@ static int update_head_to_new_branch(git_repository *repo, const git_oid *target git_reference *head; if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) { git_buf targetbuf = GIT_BUF_INIT; - if (!git_buf_printf(&targetbuf, "refs/heads/%s", name)) { + if (!git_buf_printf(&targetbuf, GIT_REFS_HEADS_DIR "%s", name)) { retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf)); } git_buf_free(&targetbuf); @@ -115,7 +115,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) head_info.repo = repo; /* Check to see if "master" matches the remote head */ - if (!git_reference_name_to_oid(&oid, repo, "refs/remotes/origin/master") && + if (!git_reference_name_to_oid(&oid, repo, GIT_REFS_REMOTES_DIR "origin/master") && !git_oid_cmp(&remote_head->oid, &oid)) { retcode = update_head_to_new_branch(repo, &oid, "master"); } diff --git a/src/refs.c b/src/refs.c index bd6fbee0e..2a3b8dde3 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1404,7 +1404,7 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) git_reference_free(head); head = NULL; - if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { + if (git_reference_create_symbolic(&head, ref->owner, GIT_HEAD_FILE, new_name, 1) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); goto cleanup; diff --git a/src/repository.c b/src/repository.c index 1a46db0a5..1641129d3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -654,10 +654,10 @@ static int repo_init_create_head(const char *git_dir, const char *ref_name) if (!ref_name) ref_name = GIT_BRANCH_MASTER; - if (git__prefixcmp(ref_name, "refs/") == 0) + if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0) fmt = "ref: %s\n"; else - fmt = "ref: refs/heads/%s\n"; + fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; if (git_filebuf_printf(&ref, fmt, ref_name) < 0 || git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) @@ -1219,7 +1219,7 @@ int git_repository_is_empty(git_repository *repo) git_reference *head = NULL, *branch = NULL; int error; - if (git_reference_lookup(&head, repo, "HEAD") < 0) + if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; if (git_reference_type(head) != GIT_REF_SYMBOLIC) { @@ -1227,7 +1227,7 @@ int git_repository_is_empty(git_repository *repo) return 0; } - if (strcmp(git_reference_target(head), "refs/heads/master") != 0) { + if (strcmp(git_reference_target(head), GIT_REFS_HEADS_DIR "master") != 0) { git_reference_free(head); return 0; } diff --git a/src/revparse.c b/src/revparse.c index 5e2db99cd..191f6374c 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -28,11 +28,11 @@ static int disambiguate_refname(git_reference **out, git_repository *repo, const static const char* formatters[] = { "%s", - "refs/%s", - "refs/tags/%s", - "refs/heads/%s", - "refs/remotes/%s", - "refs/remotes/%s/HEAD", + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + GIT_REFS_REMOTES_DIR "%s", + GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE, NULL }; @@ -520,7 +520,7 @@ static int handle_grep_syntax(git_object **out, git_repository *repo, const git_ if (spec_oid == NULL) { // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails - if (git_revwalk_push_glob(walk, "refs/heads/*") < 0) + if (git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*") < 0) goto cleanup; } else if (git_revwalk_push(walk, spec_oid) < 0) goto cleanup; diff --git a/src/submodule.c b/src/submodule.c index a2162496a..180528641 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1315,7 +1315,7 @@ static int lookup_head_remote(git_buf *url, git_repository *repo) /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */ if (git_reference_type(remote) != GIT_REF_SYMBOLIC || - git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0) + git__prefixcmp(git_reference_target(remote), GIT_REFS_REMOTES_DIR) != 0) { giterr_set(GITERR_SUBMODULE, "Cannot resolve relative URL when HEAD is not symbolic"); @@ -1323,7 +1323,7 @@ static int lookup_head_remote(git_buf *url, git_repository *repo) goto cleanup; } - scan = tgt = git_reference_target(remote) + strlen("refs/remotes/"); + scan = tgt = git_reference_target(remote) + strlen(GIT_REFS_REMOTES_DIR); while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\'))) scan++; /* find non-escaped slash to end ORIGIN name */ From 096d9e94aa9e19669e778c8644ccfa7f41f98eaf Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 7 Oct 2012 21:00:46 +0200 Subject: [PATCH 02/13] remote: use constants for well-known names --- src/clone.c | 4 ++-- src/remote.h | 2 ++ src/repository.c | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/clone.c b/src/clone.c index 9dfc22c59..211288c2c 100644 --- a/src/clone.c +++ b/src/clone.c @@ -51,7 +51,7 @@ static int create_tracking_branch(git_repository *repo, const git_oid *target, c if (!git_buf_printf(&remote, "branch.%s.remote", name) && !git_buf_printf(&merge, "branch.%s.merge", name) && !git_buf_printf(&merge_target, GIT_REFS_HEADS_DIR "%s", name) && - !git_config_set_string(cfg, git_buf_cstr(&remote), "origin") && + !git_config_set_string(cfg, git_buf_cstr(&remote), GIT_REMOTE_ORIGIN) && !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { retcode = 0; } @@ -150,7 +150,7 @@ static int setup_remotes_and_fetch(git_repository *repo, if (!fetch_stats) fetch_stats = &dummy_stats; /* Create the "origin" remote */ - if (!git_remote_add(&origin, repo, "origin", origin_url)) { + if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) { /* Connect and download everything */ if (!git_remote_connect(origin, GIT_DIR_FETCH)) { if (!git_remote_download(origin, &bytes, fetch_stats)) { diff --git a/src/remote.h b/src/remote.h index b8bb2c55d..05073db8c 100644 --- a/src/remote.h +++ b/src/remote.h @@ -13,6 +13,8 @@ #include "transport.h" #include "repository.h" +#define GIT_REMOTE_ORIGIN "origin" + struct git_remote { char *name; char *url; diff --git a/src/repository.c b/src/repository.c index 1641129d3..1b49228fb 100644 --- a/src/repository.c +++ b/src/repository.c @@ -19,6 +19,7 @@ #include "refs.h" #include "filter.h" #include "odb.h" +#include "remote.h" #define GIT_FILE_CONTENT_PREFIX "gitdir:" @@ -1095,7 +1096,7 @@ static int repo_init_create_origin(git_repository *repo, const char *url) int error; git_remote *remote; - if (!(error = git_remote_add(&remote, repo, "origin", url))) { + if (!(error = git_remote_add(&remote, repo, GIT_REMOTE_ORIGIN, url))) { error = git_remote_save(remote); git_remote_free(remote); } From b52b6571afc96006de69aac77c8a9f97b3ebb9d3 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 22 Sep 2012 12:42:16 +0200 Subject: [PATCH 03/13] branch: enhance branch moving test coverage --- tests-clar/refs/branches/move.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 6750473e1..9ab7da4e1 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -62,3 +62,16 @@ void test_refs_branches_move__can_force_move_over_an_existing_branch(void) { cl_git_pass(git_branch_move(ref, "master", 1)); } + +void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) +{ + git_reference *branch; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + cl_git_pass(git_branch_move(branch, "master2", 0)); + git_reference_free(branch); + + cl_git_pass(git_repository_head(&branch, repo)); + cl_assert_equal_s("refs/heads/master2", git_reference_name(branch)); + git_reference_free(branch); +} From a147408f944abb67e1a4a50071c9bb0a20c57e98 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 22 Sep 2012 12:47:17 +0200 Subject: [PATCH 04/13] reset: make reset rely on git_repository_head() --- src/reset.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/reset.c b/src/reset.c index 4ce21e2cf..c536e75b8 100644 --- a/src/reset.c +++ b/src/reset.c @@ -29,6 +29,7 @@ int git_reset( git_tree *tree = NULL; int error = -1; git_checkout_opts opts; + git_reference *head = NULL; assert(repo && target); assert(reset_type == GIT_RESET_SOFT @@ -49,7 +50,10 @@ int git_reset( //TODO: Check for unmerged entries - if (git_reference__update(repo, git_object_id(commit), GIT_HEAD_FILE) < 0) + if (git_repository_head(&head, repo) < 0) + goto cleanup; + + if (git_reference_set_oid(head, git_object_id(commit)) < 0) goto cleanup; if (reset_type == GIT_RESET_SOFT) { @@ -96,6 +100,7 @@ int git_reset( error = 0; cleanup: + git_reference_free(head); git_object_free(commit); git_index_free(index); git_tree_free(tree); From 7eca3c561d489dc3f3d4ee802ea4fd358214f632 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 22 Sep 2012 12:50:18 +0200 Subject: [PATCH 05/13] clone: deploy git_repository_set_head() usage --- src/clone.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/clone.c b/src/clone.c index 211288c2c..c233977b7 100644 --- a/src/clone.c +++ b/src/clone.c @@ -28,7 +28,11 @@ struct HeadInfo { git_buf branchname; }; -static int create_tracking_branch(git_repository *repo, const git_oid *target, const char *name) +static int create_tracking_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *name) { git_object *head_obj = NULL; git_reference *branch_ref; @@ -42,7 +46,6 @@ static int create_tracking_branch(git_repository *repo, const git_oid *target, c if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) { git_config *cfg; - git_reference_free(branch_ref); /* Set up tracking */ if (!git_repository_config(&cfg, repo)) { git_buf remote = GIT_BUF_INIT; @@ -63,6 +66,12 @@ static int create_tracking_branch(git_repository *repo, const git_oid *target, c } git_object_free(head_obj); + + if (!retcode) + *branch = branch_ref; + else + git_reference_free(branch_ref); + return retcode; } @@ -84,21 +93,17 @@ static int reference_matches_remote_head(const char *head_name, void *payload) static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name) { - int retcode = GIT_ERROR; + git_reference *tracking_branch; + int error; - if (!create_tracking_branch(repo, target, name)) { - git_reference *head; - if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) { - git_buf targetbuf = GIT_BUF_INIT; - if (!git_buf_printf(&targetbuf, GIT_REFS_HEADS_DIR "%s", name)) { - retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf)); - } - git_buf_free(&targetbuf); - git_reference_free(head); - } - } + if (create_tracking_branch(&tracking_branch, repo, target, name) < 0) + return -1; - return retcode; + error = git_repository_set_head(repo, git_reference_name(tracking_branch)); + + git_reference_free(tracking_branch); + + return error; } static int update_head_to_remote(git_repository *repo, git_remote *remote) From f3cc78340a7a0d2dab8a38c48fc80c6c36419450 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 22 Sep 2012 12:51:34 +0200 Subject: [PATCH 06/13] refs: deploy git_repository_set_head() usage --- src/refs.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/refs.c b/src/refs.c index 2a3b8dde3..3b33a3201 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1343,6 +1343,7 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) unsigned int normalization_flags; git_buf aux_path = GIT_BUF_INIT; char normalized[GIT_REFNAME_MAX]; + bool should_head_be_updated = false; const char *head_target = NULL; git_reference *head = NULL; @@ -1366,6 +1367,15 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) return -1; + /* + * Check if we have to update HEAD. + */ + if (git_repository_head(&head, ref->owner) < 0) + goto cleanup; + + should_head_be_updated = !strcmp(git_reference_name(head), ref->name); + git_reference_free(head); + /* * Now delete the old ref and remove an possibly existing directory * named `new_name`. Note that using the internal `reference_delete` @@ -1390,25 +1400,13 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) goto rollback; /* - * Check if we have to update HEAD. + * Update HEAD it was poiting to the reference being renamed. */ - if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { - giterr_set(GITERR_REFERENCE, - "Failed to update HEAD after renaming reference"); - goto cleanup; - } - - head_target = git_reference_target(head); - - if (head_target && !strcmp(head_target, ref->name)) { - git_reference_free(head); - head = NULL; - - if (git_reference_create_symbolic(&head, ref->owner, GIT_HEAD_FILE, new_name, 1) < 0) { + if (should_head_be_updated && + git_repository_set_head(ref->owner, new_name) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); goto cleanup; - } } /* @@ -1426,12 +1424,10 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) /* The reference is no longer packed */ ref->flags &= ~GIT_REF_PACKED; - git_reference_free(head); git_buf_free(&aux_path); return 0; cleanup: - git_reference_free(head); git_buf_free(&aux_path); return -1; From 0c78f685ebeed293c666815b1668b8209f4ff258 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 6 Oct 2012 10:41:53 +0200 Subject: [PATCH 07/13] branch: introduce git_branch_is_head() --- include/git2/branch.h | 11 +++++ src/branch.c | 23 +++++++++ tests-clar/refs/branches/ishead.c | 80 +++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 tests-clar/refs/branches/ishead.c diff --git a/include/git2/branch.h b/include/git2/branch.h index f072799c5..f06609a51 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -148,6 +148,17 @@ GIT_EXTERN(int) git_branch_tracking( git_reference **tracking_out, git_reference *branch); +/** + * Determine if the current local branch is pointed at by HEAD. + * + * @param branch Current underlying reference of the branch. + * + * @return 1 if HEAD points at the branch, 0 if it isn't, + * error code otherwise. + */ +GIT_EXTERN(int) git_branch_is_head( + git_reference *branch); + /** @} */ GIT_END_DECL #endif diff --git a/src/branch.c b/src/branch.c index 103dfe621..3cee956f0 100644 --- a/src/branch.c +++ b/src/branch.c @@ -271,3 +271,26 @@ cleanup: git_buf_free(&buf); return error; } + +int git_branch_is_head( + git_reference *branch) +{ + git_reference *head; + bool is_same = false; + + assert(branch); + + if (!git_reference_is_branch(branch)) + return false; + + if (git_repository_head(&head, git_reference_owner(branch)) < 0) + return -1; + + is_same = strcmp( + git_reference_name(branch), + git_reference_name(head)) == 0; + + git_reference_free(head); + + return is_same; +} diff --git a/tests-clar/refs/branches/ishead.c b/tests-clar/refs/branches/ishead.c new file mode 100644 index 000000000..c40a43189 --- /dev/null +++ b/tests-clar/refs/branches/ishead.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_ishead__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); +} + +void test_refs_branches_ishead__cleanup(void) +{ + git_reference_free(branch); + git_repository_free(repo); +} + +void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_assert_equal_i(true, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__can_tell_if_a_branch_is_not_pointed_at_by_HEAD(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__wont_be_fooled_by_a_non_branch(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +/* + * $ git init . + * Initialized empty Git repository in d:/temp/tempee/.git/ + * + * $ touch a && git add a + * $ git commit -m" boom" + * [master (root-commit) b47b758] boom + * 0 files changed + * create mode 100644 a + * + * $ echo "ref: refs/heads/master" > .git/refs/heads/linked + * $ echo "ref: refs/heads/linked" > .git/refs/heads/super + * $ echo "ref: refs/heads/super" > .git/HEAD + * + * $ git branch + * linked -> master + * * master + * super -> master + */ +void test_refs_branches_ishead__only_direct_references_are_considered(void) +{ + git_reference *linked, *super, *head; + + git_repository_free(repo); + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_reference_create_symbolic(&linked, repo, "refs/heads/linked", "refs/heads/master", 0)); + cl_git_pass(git_reference_create_symbolic(&super, repo, "refs/heads/super", "refs/heads/linked", 0)); + cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, "refs/heads/super", 1)); + + cl_assert_equal_i(false, git_branch_is_head(linked)); + cl_assert_equal_i(false, git_branch_is_head(super)); + + cl_git_pass(git_repository_head(&branch, repo)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); + + git_reference_free(linked); + git_reference_free(super); + git_reference_free(head); + cl_git_sandbox_cleanup(); + repo = NULL; +} From 4ba23be1e5bd480d9f6bb3eb212d5a3409fa88bd Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 6 Oct 2012 12:20:13 +0200 Subject: [PATCH 08/13] branch: deploy git_branch_is_head() --- src/branch.c | 29 +++++++++-------------------- src/refs.c | 9 ++------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/branch.c b/src/branch.c index 3cee956f0..6d497b055 100644 --- a/src/branch.c +++ b/src/branch.c @@ -92,7 +92,7 @@ cleanup: int git_branch_delete(git_reference *branch) { - git_reference *head = NULL; + int is_head; assert(branch); @@ -102,27 +102,16 @@ int git_branch_delete(git_reference *branch) return -1; } - if (git_reference_lookup(&head, git_reference_owner(branch), GIT_HEAD_FILE) < 0) { - giterr_set(GITERR_REFERENCE, "Cannot locate HEAD."); - goto on_error; + if ((is_head = git_branch_is_head(branch)) < 0) + return is_head; + + if (is_head) { + giterr_set(GITERR_REFERENCE, + "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch)); + return -1; } - if ((git_reference_type(head) == GIT_REF_SYMBOLIC) - && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) { - giterr_set(GITERR_REFERENCE, - "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch)); - goto on_error; - } - - if (git_reference_delete(branch) < 0) - goto on_error; - - git_reference_free(head); - return 0; - -on_error: - git_reference_free(head); - return -1; + return git_reference_delete(branch); } typedef struct { diff --git a/src/refs.c b/src/refs.c index 3b33a3201..9dc422e1b 100644 --- a/src/refs.c +++ b/src/refs.c @@ -15,6 +15,7 @@ #include #include #include +#include GIT__USE_STRMAP; @@ -1345,9 +1346,6 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) char normalized[GIT_REFNAME_MAX]; bool should_head_be_updated = false; - const char *head_target = NULL; - git_reference *head = NULL; - normalization_flags = ref->flags & GIT_REF_SYMBOLIC ? GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; @@ -1370,12 +1368,9 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) /* * Check if we have to update HEAD. */ - if (git_repository_head(&head, ref->owner) < 0) + if ((should_head_be_updated = git_branch_is_head(ref)) < 0) goto cleanup; - should_head_be_updated = !strcmp(git_reference_name(head), ref->name); - git_reference_free(head); - /* * Now delete the old ref and remove an possibly existing directory * named `new_name`. Note that using the internal `reference_delete` From 70edc1b0fc98c22e6c0f73c7292cb858e444e5c2 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 26 Sep 2012 11:05:12 +0200 Subject: [PATCH 09/13] clone: align type casing with convention --- src/clone.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/clone.c b/src/clone.c index c233977b7..dac741f33 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,12 +22,6 @@ #include "refs.h" #include "path.h" -struct HeadInfo { - git_repository *repo; - git_oid remote_head_oid; - git_buf branchname; -}; - static int create_tracking_branch( git_reference **branch, git_repository *repo, @@ -75,9 +69,15 @@ static int create_tracking_branch( return retcode; } +struct head_info { + git_repository *repo; + git_oid remote_head_oid; + git_buf branchname; +}; + static int reference_matches_remote_head(const char *head_name, void *payload) { - struct HeadInfo *head_info = (struct HeadInfo *)payload; + struct head_info *head_info = (struct head_info *)payload; git_oid oid; /* Stop looking if we've already found a match */ @@ -111,7 +111,7 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) int retcode = GIT_ERROR; git_remote_head *remote_head; git_oid oid; - struct HeadInfo head_info; + struct head_info head_info; /* Get the remote's HEAD. This is always the first ref in remote->refs. */ remote_head = remote->refs.contents[0]; From 3e012fca770944211bd2b32632f64ae07c42e25d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 26 Sep 2012 19:15:11 +0200 Subject: [PATCH 10/13] refspec: introduce git_refspec_transform_l() --- src/refspec.c | 18 ++++++++++++++---- src/refspec.h | 11 +++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/refspec.c b/src/refspec.c index cd3a528bd..b1790b32c 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -194,20 +194,20 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con return 0; } -int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) +static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name) { - if (git_buf_sets(out, spec->dst) < 0) + if (git_buf_sets(out, to) < 0) return -1; /* - * No '*' at the end means that it's mapped to one specific local + * No '*' at the end means that it's mapped to one specific * branch, so no actual transformation is needed. */ if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') return 0; git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ - git_buf_puts(out, name + strlen(spec->src) - 1); + git_buf_puts(out, name + strlen(from) - 1); if (git_buf_oom(out)) return -1; @@ -215,3 +215,13 @@ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *n return 0; } +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) +{ + return refspec_transform(out, spec->src, spec->dst, name); +} + +int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name) +{ + return refspec_transform(out, spec->dst, spec->src, name); +} + diff --git a/src/refspec.h b/src/refspec.h index a5df458c6..6e0596a55 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -40,4 +40,15 @@ void git_refspec__free(git_refspec *refspec); */ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name); +/** + * Transform a reference from its target following the refspec's rules, + * and writes the results into a git_buf. + * + * @param out where to store the source name + * @param spec the refspec + * @param name the name of the reference to transform + * @return 0 or error if buffer allocation fails + */ +int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name); + #endif From d280c71b8e6b4df3d8d134b14740e1ec7d885565 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 26 Sep 2012 19:22:21 +0200 Subject: [PATCH 11/13] clone: leverage refspec transform --- src/clone.c | 93 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/src/clone.c b/src/clone.c index dac741f33..312a38e1f 100644 --- a/src/clone.c +++ b/src/clone.c @@ -73,21 +73,47 @@ struct head_info { git_repository *repo; git_oid remote_head_oid; git_buf branchname; + const git_refspec *refspec; }; -static int reference_matches_remote_head(const char *head_name, void *payload) +static int reference_matches_remote_head( + const char *reference_name, + void *payload) { struct head_info *head_info = (struct head_info *)payload; git_oid oid; - /* Stop looking if we've already found a match */ - if (git_buf_len(&head_info->branchname) > 0) return 0; + /* TODO: Should we guard against references + * which name doesn't start with refs/heads/ ? + */ - if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) && - !git_oid_cmp(&head_info->remote_head_oid, &oid)) { - git_buf_puts(&head_info->branchname, - head_name+strlen(GIT_REFS_REMOTES_DIR "origin/")); + /* Stop looking if we've already found a match */ + if (git_buf_len(&head_info->branchname) > 0) + return 0; + + if (git_reference_name_to_oid( + &oid, + head_info->repo, + reference_name) < 0) { + /* TODO: How to handle not found references? + */ + return -1; } + + if (git_oid_cmp(&head_info->remote_head_oid, &oid) == 0) { + /* Determine the local reference name from the remote tracking one */ + if (git_refspec_transform_l( + &head_info->branchname, + head_info->refspec, + reference_name) < 0) + return -1; + + if (git_buf_sets( + &head_info->branchname, + git_buf_cstr(&head_info->branchname) + strlen(GIT_REFS_HEADS_DIR)) < 0) + return -1; + } + return 0; } @@ -108,31 +134,60 @@ static int update_head_to_new_branch(git_repository *repo, const git_oid *target static int update_head_to_remote(git_repository *repo, git_remote *remote) { - int retcode = GIT_ERROR; + int retcode = -1; git_remote_head *remote_head; - git_oid oid; struct head_info head_info; + git_buf remote_master_name = GIT_BUF_INIT; /* Get the remote's HEAD. This is always the first ref in remote->refs. */ remote_head = remote->refs.contents[0]; git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); git_buf_init(&head_info.branchname, 16); head_info.repo = repo; + head_info.refspec = git_remote_fetchspec(remote); + + /* Determine the remote tracking reference name from the local master */ + if (git_refspec_transform_r( + &remote_master_name, + head_info.refspec, + GIT_REFS_HEADS_MASTER_FILE) < 0) + return -1; - /* Check to see if "master" matches the remote head */ - if (!git_reference_name_to_oid(&oid, repo, GIT_REFS_REMOTES_DIR "origin/master") && - !git_oid_cmp(&remote_head->oid, &oid)) { - retcode = update_head_to_new_branch(repo, &oid, "master"); + /* Check to see if the remote HEAD points to the remote master */ + if (reference_matches_remote_head(git_buf_cstr(&remote_master_name), &head_info) < 0) + goto cleanup; + + if (git_buf_len(&head_info.branchname) > 0) { + retcode = update_head_to_new_branch( + repo, + &head_info.remote_head_oid, + git_buf_cstr(&head_info.branchname)); + + goto cleanup; } + /* Not master. Check all the other refs. */ - else if (!git_reference_foreach(repo, GIT_REF_LISTALL, - reference_matches_remote_head, - &head_info) && - git_buf_len(&head_info.branchname) > 0) { - retcode = update_head_to_new_branch(repo, &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname)); + if (git_reference_foreach( + repo, + GIT_REF_LISTALL, + reference_matches_remote_head, + &head_info) < 0) + goto cleanup; + + if (git_buf_len(&head_info.branchname) > 0) { + retcode = update_head_to_new_branch( + repo, + &head_info.remote_head_oid, + git_buf_cstr(&head_info.branchname)); + + goto cleanup; + } else { + /* TODO: What should we do if nothing has been found? + */ } +cleanup: + git_buf_free(&remote_master_name); git_buf_free(&head_info.branchname); return retcode; } From ebecf1e7d8e1b2f6c73c7b1de0a6371e65f27285 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 7 Oct 2012 12:04:07 +0200 Subject: [PATCH 12/13] clone: reorganize tests --- tests-clar/clone/clone.c | 57 +++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c index 237e607dd..661094310 100644 --- a/tests-clar/clone/clone.c +++ b/tests-clar/clone/clone.c @@ -15,12 +15,11 @@ void test_clone_clone__initialize(void) g_repo = NULL; } -void test_clone_clone__cleanup(void) +static void cleanup_repository(void *path) { - if (g_repo) { + if (g_repo) git_repository_free(g_repo); - g_repo = NULL; - } + cl_fixture_cleanup((const char *)path); } // TODO: This is copy/pasted from network/remotelocal.c. @@ -63,7 +62,6 @@ static void build_local_file_url(git_buf *out, const char *fixture) git_buf_free(&path_buf); } - void test_clone_clone__bad_url(void) { /* Clone should clean up the mess if the URL isn't a git repository */ @@ -73,33 +71,44 @@ void test_clone_clone__bad_url(void) cl_assert(!git_path_exists("./foo.git")); } - void test_clone_clone__local(void) { git_buf src = GIT_BUF_INIT; build_local_file_url(&src, cl_fixture("testrepo.git")); #if DO_LOCAL_TEST + cl_set_cleanup(&cleanup_repository, "./local"); + cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL)); - git_repository_free(g_repo); - git_futils_rmdir_r("./local", GIT_DIRREMOVAL_FILES_AND_DIRS); - cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); - git_futils_rmdir_r("./local.git", GIT_DIRREMOVAL_FILES_AND_DIRS); #endif git_buf_free(&src); } +void test_clone_clone__local_bare(void) +{ + git_buf src = GIT_BUF_INIT; + build_local_file_url(&src, cl_fixture("testrepo.git")); + +#if DO_LOCAL_TEST + cl_set_cleanup(&cleanup_repository, "./local.git"); + + cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); +#endif + + git_buf_free(&src); +} void test_clone_clone__network_full(void) { #if DO_LIVE_NETWORK_TESTS git_remote *origin; + cl_set_cleanup(&cleanup_repository, "./test2"); + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test2", NULL, NULL, NULL)); cl_assert(!git_repository_is_bare(g_repo)); cl_git_pass(git_remote_load(&origin, g_repo, "origin")); - git_futils_rmdir_r("./test2", GIT_DIRREMOVAL_FILES_AND_DIRS); #endif } @@ -108,32 +117,38 @@ void test_clone_clone__network_bare(void) #if DO_LIVE_NETWORK_TESTS git_remote *origin; - cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "test", NULL)); + cl_set_cleanup(&cleanup_repository, "./test"); + + cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL)); cl_assert(git_repository_is_bare(g_repo)); cl_git_pass(git_remote_load(&origin, g_repo, "origin")); - git_futils_rmdir_r("./test", GIT_DIRREMOVAL_FILES_AND_DIRS); #endif } - -void test_clone_clone__already_exists(void) +void test_clone_clone__cope_with_already_existing_directory(void) { #if DO_LIVE_NETWORK_TESTS - /* Should pass with existing-but-empty dir */ + cl_set_cleanup(&cleanup_repository, "./foo"); + p_mkdir("./foo", GIT_DIR_MODE); cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); git_repository_free(g_repo); g_repo = NULL; - git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); #endif +} + +void test_clone_clone__fail_when_the_target_is_a_file(void) +{ + cl_set_cleanup(&cleanup_repository, "./foo"); - /* Should fail with a file */ cl_git_mkfile("./foo", "Bar!"); cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); - git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +} + +void test_clone_clone__fail_with_already_existing_but_non_empty_directory(void) +{ + cl_set_cleanup(&cleanup_repository, "./foo"); - /* Should fail with existing-and-nonempty dir */ p_mkdir("./foo", GIT_DIR_MODE); cl_git_mkfile("./foo/bar", "Baz!"); cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); - git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); } From bf0e62a2b8b6116ae61dae37f95a3eb840246ec6 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 7 Oct 2012 12:50:18 +0200 Subject: [PATCH 13/13] clone: fix cloning of empty repository --- src/clone.c | 147 ++++++++++++++++++++++++++++----------- tests-clar/clone/clone.c | 21 ++++++ 2 files changed, 126 insertions(+), 42 deletions(-) diff --git a/src/clone.c b/src/clone.c index 312a38e1f..a5f4867db 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,7 +22,7 @@ #include "refs.h" #include "path.h" -static int create_tracking_branch( +static int create_branch( git_reference **branch, git_repository *repo, const git_oid *target, @@ -30,43 +30,74 @@ static int create_tracking_branch( { git_object *head_obj = NULL; git_reference *branch_ref; - int retcode = GIT_ERROR; + int error; /* Find the target commit */ - if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0) - return GIT_ERROR; + if ((error = git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY)) < 0) + return error; /* Create the new branch */ - if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) { - git_config *cfg; - - /* Set up tracking */ - if (!git_repository_config(&cfg, repo)) { - git_buf remote = GIT_BUF_INIT; - git_buf merge = GIT_BUF_INIT; - git_buf merge_target = GIT_BUF_INIT; - if (!git_buf_printf(&remote, "branch.%s.remote", name) && - !git_buf_printf(&merge, "branch.%s.merge", name) && - !git_buf_printf(&merge_target, GIT_REFS_HEADS_DIR "%s", name) && - !git_config_set_string(cfg, git_buf_cstr(&remote), GIT_REMOTE_ORIGIN) && - !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { - retcode = 0; - } - git_buf_free(&remote); - git_buf_free(&merge); - git_buf_free(&merge_target); - git_config_free(cfg); - } - } + error = git_branch_create(&branch_ref, repo, name, head_obj, 0); git_object_free(head_obj); - if (!retcode) + if (!error) *branch = branch_ref; else git_reference_free(branch_ref); - return retcode; + return error; +} + +static int setup_tracking_config( + git_repository *repo, + const char *branch_name, + const char *remote_name, + const char *merge_target) +{ + git_config *cfg; + git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT; + int error = -1; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0) + goto cleanup; + + if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_buf_free(&remote_key); + git_buf_free(&merge_key); + return error; +} + +static int create_tracking_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *branch_name) +{ + int error; + + if ((error = create_branch(branch, repo, target, branch_name)) < 0) + return error; + + return setup_tracking_config( + repo, + branch_name, + GIT_REMOTE_ORIGIN, + git_reference_name(*branch)); } struct head_info { @@ -117,13 +148,20 @@ static int reference_matches_remote_head( return 0; } -static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name) +static int update_head_to_new_branch( + git_repository *repo, + const git_oid *target, + const char *name) { git_reference *tracking_branch; int error; - if (create_tracking_branch(&tracking_branch, repo, target, name) < 0) - return -1; + if ((error = create_tracking_branch( + &tracking_branch, + repo, + target, + name)) < 0) + return error; error = git_repository_set_head(repo, git_reference_name(tracking_branch)); @@ -139,6 +177,15 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; + /* Did we just clone an empty repository? */ + if (remote->refs.length == 0) { + return setup_tracking_config( + repo, + "master", + GIT_REMOTE_ORIGIN, + GIT_REFS_HEADS_MASTER_FILE); + } + /* Get the remote's HEAD. This is always the first ref in remote->refs. */ remote_head = remote->refs.contents[0]; git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); @@ -244,11 +291,14 @@ static bool path_is_okay(const char *path) } -static int clone_internal(git_repository **out, - const char *origin_url, - const char *path, - git_indexer_stats *fetch_stats, - int is_bare) +static int clone_internal( + git_repository **out, + const char *origin_url, + const char *path, + git_indexer_stats *fetch_stats, + git_indexer_stats *checkout_stats, + git_checkout_opts *checkout_opts, + int is_bare) { int retcode = GIT_ERROR; git_repository *repo = NULL; @@ -271,6 +321,9 @@ static int clone_internal(git_repository **out, } } + if (!retcode && !is_bare && !git_repository_head_orphan(repo)) + retcode = git_checkout_head(*out, checkout_opts, checkout_stats); + return retcode; } @@ -280,7 +333,15 @@ int git_clone_bare(git_repository **out, git_indexer_stats *fetch_stats) { assert(out && origin_url && dest_path); - return clone_internal(out, origin_url, dest_path, fetch_stats, 1); + + return clone_internal( + out, + origin_url, + dest_path, + fetch_stats, + NULL, + NULL, + 1); } @@ -291,12 +352,14 @@ int git_clone(git_repository **out, git_indexer_stats *checkout_stats, git_checkout_opts *checkout_opts) { - int retcode = GIT_ERROR; - assert(out && origin_url && workdir_path); - if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) - retcode = git_checkout_head(*out, checkout_opts, checkout_stats); - - return retcode; + return clone_internal( + out, + origin_url, + workdir_path, + fetch_stats, + checkout_stats, + checkout_opts, + 0); } diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c index 661094310..42ddb8ae6 100644 --- a/tests-clar/clone/clone.c +++ b/tests-clar/clone/clone.c @@ -6,6 +6,7 @@ #define DO_LOCAL_TEST 0 #define DO_LIVE_NETWORK_TESTS 0 #define LIVE_REPO_URL "git://github.com/nulltoken/TestGitRepository" +#define LIVE_EMPTYREPO_URL "git://github.com/nulltoken/TestEmptyRepository" static git_repository *g_repo; @@ -152,3 +153,23 @@ void test_clone_clone__fail_with_already_existing_but_non_empty_directory(void) cl_git_mkfile("./foo/bar", "Baz!"); cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); } + +void test_clone_clone__empty_repository(void) +{ +#if DO_LIVE_NETWORK_TESTS + git_reference *head; + + cl_set_cleanup(&cleanup_repository, "./empty"); + + cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./empty", NULL, NULL, NULL)); + + cl_assert_equal_i(true, git_repository_is_empty(g_repo)); + cl_assert_equal_i(true, git_repository_head_orphan(g_repo)); + + cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_target(head)); + + git_reference_free(head); +#endif +}