diff options
| author | Ben Straub <bs@github.com> | 2012-10-08 15:25:44 -0700 |
|---|---|---|
| committer | Ben Straub <bs@github.com> | 2012-10-08 15:25:44 -0700 |
| commit | 9adfa7d147b319593789cbaeea94a97141b1e4f8 (patch) | |
| tree | bd9a22164040adc03d389aa101e9d480cb2ce775 /src | |
| parent | 543864b677704845660085d2b2b2249cabb084a4 (diff) | |
| parent | bf0e62a2b8b6116ae61dae37f95a3eb840246ec6 (diff) | |
| download | libgit2-9adfa7d147b319593789cbaeea94a97141b1e4f8.tar.gz | |
Merge pull request #949 from nulltoken/topic/deploy_repository_set_head
Deploy git_repository_set_head()
Diffstat (limited to 'src')
| -rw-r--r-- | src/branch.c | 50 | ||||
| -rw-r--r-- | src/clone.c | 283 | ||||
| -rw-r--r-- | src/refs.c | 31 | ||||
| -rw-r--r-- | src/refspec.c | 18 | ||||
| -rw-r--r-- | src/refspec.h | 11 | ||||
| -rw-r--r-- | src/remote.h | 2 | ||||
| -rw-r--r-- | src/repository.c | 11 | ||||
| -rw-r--r-- | src/reset.c | 7 | ||||
| -rw-r--r-- | src/revparse.c | 12 | ||||
| -rw-r--r-- | src/submodule.c | 4 |
10 files changed, 292 insertions, 137 deletions
diff --git a/src/branch.c b/src/branch.c index 103dfe621..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 ((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 (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_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 { @@ -271,3 +260,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/src/clone.c b/src/clone.c index 80a13d0f2..a5f4867db 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,112 +22,219 @@ #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_repository *repo, const git_oid *target, const char *name) +static int create_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *name) { 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; + error = git_branch_create(&branch_ref, repo, name, head_obj, 0); + git_object_free(head_obj); + + if (!error) + *branch = branch_ref; + else git_reference_free(branch_ref); - /* 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, "refs/heads/%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; - } - git_buf_free(&remote); - git_buf_free(&merge); - git_buf_free(&merge_target); - git_config_free(cfg); - } - } - git_object_free(head_obj); - return retcode; + return error; } -static int reference_matches_remote_head(const char *head_name, void *payload) +static int setup_tracking_config( + git_repository *repo, + const char *branch_name, + const char *remote_name, + const char *merge_target) { - struct HeadInfo *head_info = (struct HeadInfo *)payload; + 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 { + git_repository *repo; + git_oid remote_head_oid; + git_buf branchname; + const git_refspec *refspec; +}; + +static int reference_matches_remote_head( + const char *reference_name, + void *payload) +{ + struct head_info *head_info = (struct head_info *)payload; git_oid oid; + /* TODO: Should we guard against references + * which name doesn't start with refs/heads/ ? + */ + /* Stop looking if we've already found a match */ - if (git_buf_len(&head_info->branchname) > 0) return 0; + 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_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/")); + 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; } -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) { - 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, "refs/heads/%s", name)) { - retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf)); - } - git_buf_free(&targetbuf); - git_reference_free(head); - } - } + if ((error = create_tracking_branch( + &tracking_branch, + repo, + target, + name)) < 0) + return error; - 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) { - int retcode = GIT_ERROR; + int retcode = -1; git_remote_head *remote_head; - git_oid oid; - struct HeadInfo head_info; + 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); git_buf_init(&head_info.branchname, 16); 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") && - !git_oid_cmp(&remote_head->oid, &oid)) { - retcode = update_head_to_new_branch(repo, &oid, "master"); + 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 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; } @@ -150,7 +257,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)) { @@ -184,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; @@ -211,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; } @@ -220,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); } @@ -231,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/src/refs.c b/src/refs.c index bd6fbee0e..9dc422e1b 100644 --- a/src/refs.c +++ b/src/refs.c @@ -15,6 +15,7 @@ #include <git2/tag.h> #include <git2/object.h> #include <git2/oid.h> +#include <git2/branch.h> GIT__USE_STRMAP; @@ -1343,9 +1344,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]; - - const char *head_target = NULL; - git_reference *head = NULL; + bool should_head_be_updated = false; normalization_flags = ref->flags & GIT_REF_SYMBOLIC ? GIT_REF_FORMAT_ALLOW_ONELEVEL @@ -1367,6 +1366,12 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) return -1; /* + * Check if we have to update HEAD. + */ + if ((should_head_be_updated = git_branch_is_head(ref)) < 0) + goto cleanup; + + /* * Now delete the old ref and remove an possibly existing directory * named `new_name`. Note that using the internal `reference_delete` * method deletes the ref from disk but doesn't free the pointer, so @@ -1390,25 +1395,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, "HEAD", 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 +1419,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; 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 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 1a46db0a5..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:" @@ -654,10 +655,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) @@ -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); } @@ -1219,7 +1220,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 +1228,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/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); 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 */ |
