diff options
Diffstat (limited to 'worktree.c')
-rw-r--r-- | worktree.c | 124 |
1 files changed, 105 insertions, 19 deletions
diff --git a/worktree.c b/worktree.c index 6181a66f1e..199b1ef94b 100644 --- a/worktree.c +++ b/worktree.c @@ -2,6 +2,8 @@ #include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "dir.h" +#include "wt-status.h" void free_worktrees(struct worktree **worktrees) { @@ -9,7 +11,7 @@ void free_worktrees(struct worktree **worktrees) for (i = 0; worktrees[i]; i++) { free(worktrees[i]->path); - free(worktrees[i]->git_dir); + free(worktrees[i]->id); free(worktrees[i]->head_ref); free(worktrees[i]); } @@ -18,7 +20,7 @@ void free_worktrees(struct worktree **worktrees) /* * read 'path_to_ref' into 'ref'. Also if is_detached is not NULL, - * set is_detached to 1 (0) if the ref is detatched (is not detached). + * set is_detached to 1 (0) if the ref is detached (is not detached). * * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses @@ -74,13 +76,11 @@ static struct worktree *get_main_worktree(void) struct worktree *worktree = NULL; struct strbuf path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT; int is_bare = 0; int is_detached = 0; - strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir())); - strbuf_addbuf(&worktree_path, &gitdir); + strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir())); is_bare = !strbuf_strip_suffix(&worktree_path, "/.git"); if (is_bare) strbuf_strip_suffix(&worktree_path, "/."); @@ -92,15 +92,15 @@ static struct worktree *get_main_worktree(void) worktree = xmalloc(sizeof(struct worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); - worktree->git_dir = strbuf_detach(&gitdir, NULL); + worktree->id = NULL; worktree->is_bare = is_bare; worktree->head_ref = NULL; worktree->is_detached = is_detached; + worktree->is_current = 0; add_head_info(&head_ref, worktree); done: strbuf_release(&path); - strbuf_release(&gitdir); strbuf_release(&worktree_path); strbuf_release(&head_ref); return worktree; @@ -111,16 +111,13 @@ static struct worktree *get_linked_worktree(const char *id) struct worktree *worktree = NULL; struct strbuf path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT; int is_detached = 0; if (!id) die("Missing linked worktree name"); - strbuf_addf(&gitdir, "%s/worktrees/%s", - absolute_path(get_git_common_dir()), id); - strbuf_addf(&path, "%s/gitdir", gitdir.buf); + strbuf_git_common_path(&path, "worktrees/%s/gitdir", id); if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0) /* invalid gitdir file */ goto done; @@ -140,20 +137,39 @@ static struct worktree *get_linked_worktree(const char *id) worktree = xmalloc(sizeof(struct worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); - worktree->git_dir = strbuf_detach(&gitdir, NULL); + worktree->id = xstrdup(id); worktree->is_bare = 0; worktree->head_ref = NULL; worktree->is_detached = is_detached; + worktree->is_current = 0; add_head_info(&head_ref, worktree); done: strbuf_release(&path); - strbuf_release(&gitdir); strbuf_release(&worktree_path); strbuf_release(&head_ref); return worktree; } +static void mark_current_worktree(struct worktree **worktrees) +{ + struct strbuf git_dir = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + int i; + + strbuf_addstr(&git_dir, absolute_path(get_git_dir())); + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; + strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt))); + wt->is_current = !fspathcmp(git_dir.buf, path.buf); + strbuf_reset(&path); + if (wt->is_current) + break; + } + strbuf_release(&git_dir); + strbuf_release(&path); +} + struct worktree **get_worktrees(void) { struct worktree **list = NULL; @@ -185,35 +201,105 @@ struct worktree **get_worktrees(void) } ALLOC_GROW(list, counter + 1, alloc); list[counter] = NULL; + + mark_current_worktree(list); return list; } -char *find_shared_symref(const char *symref, const char *target) +const char *get_worktree_git_dir(const struct worktree *wt) +{ + if (!wt) + return get_git_dir(); + else if (!wt->id) + return get_git_common_dir(); + else + return git_common_path("worktrees/%s", wt->id); +} + +int is_worktree_being_rebased(const struct worktree *wt, + const char *target) +{ + struct wt_status_state state; + int found_rebase; + + memset(&state, 0, sizeof(state)); + found_rebase = wt_status_check_rebase(wt, &state) && + ((state.rebase_in_progress || + state.rebase_interactive_in_progress) && + state.branch && + starts_with(target, "refs/heads/") && + !strcmp(state.branch, target + strlen("refs/heads/"))); + free(state.branch); + free(state.onto); + return found_rebase; +} + +int is_worktree_being_bisected(const struct worktree *wt, + const char *target) { - char *existing = NULL; + struct wt_status_state state; + int found_rebase; + + memset(&state, 0, sizeof(state)); + found_rebase = wt_status_check_bisect(wt, &state) && + state.branch && + starts_with(target, "refs/heads/") && + !strcmp(state.branch, target + strlen("refs/heads/")); + free(state.branch); + return found_rebase; +} + +/* + * note: this function should be able to detect shared symref even if + * HEAD is temporarily detached (e.g. in the middle of rebase or + * bisect). New commands that do similar things should update this + * function as well. + */ +const struct worktree *find_shared_symref(const char *symref, + const char *target) +{ + const struct worktree *existing = NULL; struct strbuf path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; - struct worktree **worktrees = get_worktrees(); + static struct worktree **worktrees; int i = 0; + if (worktrees) + free_worktrees(worktrees); + worktrees = get_worktrees(); + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; + + if (wt->is_detached && !strcmp(symref, "HEAD")) { + if (is_worktree_being_rebased(wt, target)) { + existing = wt; + break; + } + if (is_worktree_being_bisected(wt, target)) { + existing = wt; + break; + } + } + strbuf_reset(&path); strbuf_reset(&sb); - strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref); + strbuf_addf(&path, "%s/%s", + get_worktree_git_dir(wt), + symref); if (parse_ref(path.buf, &sb, NULL)) { continue; } if (!strcmp(sb.buf, target)) { - existing = xstrdup(worktrees[i]->path); + existing = wt; break; } } strbuf_release(&path); strbuf_release(&sb); - free_worktrees(worktrees); return existing; } |