mirror of
https://github.com/git-for-windows/git.git
synced 2025-12-15 12:58:18 -06:00
Similar to the rebase case, we want to detect if "HEAD" in some worktree is being bisected because 1) we do not want to checkout this branch in another worktree, after bisect is done it will want to go back to this branch 2) we do not want to delete the branch is either or git bisect will fail to return to the (long gone) branch Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
306 lines
7.4 KiB
C
306 lines
7.4 KiB
C
#include "cache.h"
|
|
#include "refs.h"
|
|
#include "strbuf.h"
|
|
#include "worktree.h"
|
|
#include "dir.h"
|
|
#include "wt-status.h"
|
|
|
|
void free_worktrees(struct worktree **worktrees)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; worktrees[i]; i++) {
|
|
free(worktrees[i]->path);
|
|
free(worktrees[i]->id);
|
|
free(worktrees[i]->head_ref);
|
|
free(worktrees[i]);
|
|
}
|
|
free (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).
|
|
*
|
|
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
|
|
* for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
|
|
* git_path). Parse the ref ourselves.
|
|
*
|
|
* return -1 if the ref is not a proper ref, 0 otherwise (success)
|
|
*/
|
|
static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
|
|
{
|
|
if (is_detached)
|
|
*is_detached = 0;
|
|
if (!strbuf_readlink(ref, path_to_ref, 0)) {
|
|
/* HEAD is symbolic link */
|
|
if (!starts_with(ref->buf, "refs/") ||
|
|
check_refname_format(ref->buf, 0))
|
|
return -1;
|
|
} else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
|
|
/* textual symref or detached */
|
|
if (!starts_with(ref->buf, "ref:")) {
|
|
if (is_detached)
|
|
*is_detached = 1;
|
|
} else {
|
|
strbuf_remove(ref, 0, strlen("ref:"));
|
|
strbuf_trim(ref);
|
|
if (check_refname_format(ref->buf, 0))
|
|
return -1;
|
|
}
|
|
} else
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Add the head_sha1 and head_ref (if not detached) to the given worktree
|
|
*/
|
|
static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
|
|
{
|
|
if (head_ref->len) {
|
|
if (worktree->is_detached) {
|
|
get_sha1_hex(head_ref->buf, worktree->head_sha1);
|
|
} else {
|
|
resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
|
|
worktree->head_ref = strbuf_detach(head_ref, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the main worktree
|
|
*/
|
|
static struct worktree *get_main_worktree(void)
|
|
{
|
|
struct worktree *worktree = NULL;
|
|
struct strbuf path = STRBUF_INIT;
|
|
struct strbuf worktree_path = STRBUF_INIT;
|
|
struct strbuf head_ref = STRBUF_INIT;
|
|
int is_bare = 0;
|
|
int is_detached = 0;
|
|
|
|
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, "/.");
|
|
|
|
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
|
|
|
|
if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
|
|
goto done;
|
|
|
|
worktree = xmalloc(sizeof(struct worktree));
|
|
worktree->path = strbuf_detach(&worktree_path, 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(&worktree_path);
|
|
strbuf_release(&head_ref);
|
|
return worktree;
|
|
}
|
|
|
|
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 head_ref = STRBUF_INIT;
|
|
int is_detached = 0;
|
|
|
|
if (!id)
|
|
die("Missing linked worktree name");
|
|
|
|
strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
|
|
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
|
|
/* invalid gitdir file */
|
|
goto done;
|
|
|
|
strbuf_rtrim(&worktree_path);
|
|
if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
|
|
strbuf_reset(&worktree_path);
|
|
strbuf_addstr(&worktree_path, absolute_path("."));
|
|
strbuf_strip_suffix(&worktree_path, "/.");
|
|
}
|
|
|
|
strbuf_reset(&path);
|
|
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
|
|
|
|
if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
|
|
goto done;
|
|
|
|
worktree = xmalloc(sizeof(struct worktree));
|
|
worktree->path = strbuf_detach(&worktree_path, 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(&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;
|
|
struct strbuf path = STRBUF_INIT;
|
|
DIR *dir;
|
|
struct dirent *d;
|
|
int counter = 0, alloc = 2;
|
|
|
|
list = xmalloc(alloc * sizeof(struct worktree *));
|
|
|
|
if ((list[counter] = get_main_worktree()))
|
|
counter++;
|
|
|
|
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
|
dir = opendir(path.buf);
|
|
strbuf_release(&path);
|
|
if (dir) {
|
|
while ((d = readdir(dir)) != NULL) {
|
|
struct worktree *linked = NULL;
|
|
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
|
continue;
|
|
|
|
if ((linked = get_linked_worktree(d->d_name))) {
|
|
ALLOC_GROW(list, counter + 1, alloc);
|
|
list[counter++] = linked;
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
ALLOC_GROW(list, counter + 1, alloc);
|
|
list[counter] = NULL;
|
|
|
|
mark_current_worktree(list);
|
|
return list;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int is_worktree_being_bisected(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_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;
|
|
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",
|
|
get_worktree_git_dir(wt),
|
|
symref);
|
|
|
|
if (parse_ref(path.buf, &sb, NULL)) {
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(sb.buf, target)) {
|
|
existing = wt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
strbuf_release(&path);
|
|
strbuf_release(&sb);
|
|
|
|
return existing;
|
|
}
|