mirror of
https://github.com/git/git.git
synced 2024-11-18 17:23:49 +01:00
Merge branch 'nd/worktree-various-heads'
The experimental "multiple worktree" feature gains more safety to forbid operations on a branch that is checked out or being actively worked on elsewhere, by noticing that e.g. it is being rebased. * nd/worktree-various-heads: branch: do not rename a branch under bisect or rebase worktree.c: check whether branch is bisected in another worktree wt-status.c: split bisect detection out of wt_status_get_state() worktree.c: check whether branch is rebased in another worktree worktree.c: avoid referencing to worktrees[i] multiple times wt-status.c: make wt_status_check_rebase() work on any worktree wt-status.c: split rebase detection out of wt_status_get_state() path.c: refactor and add worktree_git_path() worktree.c: mark current worktree worktree.c: make find_shared_symref() return struct worktree * worktree.c: store "id" instead of "git_dir" path.c: add git_common_path() and strbuf_git_common_path() dir.c: rename str(n)cmp_icase to fspath(n)cmp
This commit is contained in:
commit
352d72a30e
18
branch.c
18
branch.c
@ -334,15 +334,16 @@ void remove_branch_state(void)
|
||||
unlink(git_path_squash_msg());
|
||||
}
|
||||
|
||||
void die_if_checked_out(const char *branch)
|
||||
void die_if_checked_out(const char *branch, int ignore_current_worktree)
|
||||
{
|
||||
char *existing;
|
||||
const struct worktree *wt;
|
||||
|
||||
existing = find_shared_symref("HEAD", branch);
|
||||
if (existing) {
|
||||
skip_prefix(branch, "refs/heads/", &branch);
|
||||
die(_("'%s' is already checked out at '%s'"), branch, existing);
|
||||
}
|
||||
wt = find_shared_symref("HEAD", branch);
|
||||
if (!wt || (ignore_current_worktree && wt->is_current))
|
||||
return;
|
||||
skip_prefix(branch, "refs/heads/", &branch);
|
||||
die(_("'%s' is already checked out at '%s'"),
|
||||
branch, wt->path);
|
||||
}
|
||||
|
||||
int replace_each_worktree_head_symref(const char *oldref, const char *newref)
|
||||
@ -357,7 +358,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)
|
||||
if (strcmp(oldref, worktrees[i]->head_ref))
|
||||
continue;
|
||||
|
||||
if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {
|
||||
if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),
|
||||
newref)) {
|
||||
ret = -1;
|
||||
error(_("HEAD of working tree %s is not updated"),
|
||||
worktrees[i]->path);
|
||||
|
2
branch.h
2
branch.h
@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
|
||||
* worktree and die (with a message describing its checkout location) if
|
||||
* it is.
|
||||
*/
|
||||
extern void die_if_checked_out(const char *branch);
|
||||
extern void die_if_checked_out(const char *branch, int ignore_current_worktree);
|
||||
|
||||
/*
|
||||
* Update all per-worktree HEADs pointing at the old ref to point the new ref.
|
||||
|
@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
|
||||
name = mkpathdup(fmt, bname.buf);
|
||||
|
||||
if (kinds == FILTER_REFS_BRANCHES) {
|
||||
char *worktree = find_shared_symref("HEAD", name);
|
||||
if (worktree) {
|
||||
const struct worktree *wt =
|
||||
find_shared_symref("HEAD", name);
|
||||
if (wt) {
|
||||
error(_("Cannot delete branch '%s' "
|
||||
"checked out at '%s'"),
|
||||
bname.buf, worktree);
|
||||
free(worktree);
|
||||
bname.buf, wt->path);
|
||||
ret = 1;
|
||||
continue;
|
||||
}
|
||||
@ -526,6 +526,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
|
||||
ref_array_clear(&array);
|
||||
}
|
||||
|
||||
static void reject_rebase_or_bisect_branch(const char *target)
|
||||
{
|
||||
struct worktree **worktrees = get_worktrees();
|
||||
int i;
|
||||
|
||||
for (i = 0; worktrees[i]; i++) {
|
||||
struct worktree *wt = worktrees[i];
|
||||
|
||||
if (!wt->is_detached)
|
||||
continue;
|
||||
|
||||
if (is_worktree_being_rebased(wt, target))
|
||||
die(_("Branch %s is being rebased at %s"),
|
||||
target, wt->path);
|
||||
|
||||
if (is_worktree_being_bisected(wt, target))
|
||||
die(_("Branch %s is being bisected at %s"),
|
||||
target, wt->path);
|
||||
}
|
||||
|
||||
free_worktrees(worktrees);
|
||||
}
|
||||
|
||||
static void rename_branch(const char *oldname, const char *newname, int force)
|
||||
{
|
||||
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
|
||||
@ -555,6 +578,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)
|
||||
|
||||
validate_new_branchname(newname, &newref, force, clobber_head_ok);
|
||||
|
||||
reject_rebase_or_bisect_branch(oldref.buf);
|
||||
|
||||
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
|
||||
oldref.buf, newref.buf);
|
||||
|
||||
|
@ -1110,7 +1110,7 @@ static int checkout_branch(struct checkout_opts *opts,
|
||||
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
|
||||
if (head_ref &&
|
||||
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
|
||||
die_if_checked_out(new->path);
|
||||
die_if_checked_out(new->path, 1);
|
||||
free(head_ref);
|
||||
}
|
||||
|
||||
|
@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)
|
||||
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
|
||||
0, UPDATE_REFS_DIE_ON_ERR);
|
||||
else { /* Merge has unresolved conflicts */
|
||||
char *existing;
|
||||
const struct worktree *wt;
|
||||
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
|
||||
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
|
||||
0, UPDATE_REFS_DIE_ON_ERR);
|
||||
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
|
||||
existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
|
||||
if (existing)
|
||||
wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
|
||||
if (wt)
|
||||
die(_("A notes merge into %s is already in-progress at %s"),
|
||||
default_notes_ref(), existing);
|
||||
default_notes_ref(), wt->path);
|
||||
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
|
||||
die("Failed to store link to current notes ref (%s)",
|
||||
default_notes_ref());
|
||||
|
@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
|
||||
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
|
||||
ref_exists(symref.buf)) { /* it's a branch */
|
||||
if (!opts->force)
|
||||
die_if_checked_out(symref.buf);
|
||||
die_if_checked_out(symref.buf, 0);
|
||||
} else { /* must be a commit */
|
||||
commit = lookup_commit_reference_by_name(refname);
|
||||
if (!commit)
|
||||
@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
|
||||
if (!opts.force &&
|
||||
!strbuf_check_branch_ref(&symref, opts.new_branch) &&
|
||||
ref_exists(symref.buf))
|
||||
die_if_checked_out(symref.buf);
|
||||
die_if_checked_out(symref.buf, 0);
|
||||
strbuf_release(&symref);
|
||||
}
|
||||
|
||||
|
3
cache.h
3
cache.h
@ -808,11 +808,14 @@ extern void check_repository_format(void);
|
||||
*/
|
||||
extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
|
||||
extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
|
||||
__attribute__((format (printf, 3, 4)));
|
||||
extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
|
||||
|
13
dir.c
13
dir.c
@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
int check_only, const struct path_simplify *simplify);
|
||||
static int get_dtype(struct dirent *de, const char *path, int len);
|
||||
|
||||
/* helper string functions with support for the ignore_case flag */
|
||||
int strcmp_icase(const char *a, const char *b)
|
||||
int fspathcmp(const char *a, const char *b)
|
||||
{
|
||||
return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
|
||||
}
|
||||
|
||||
int strncmp_icase(const char *a, const char *b, size_t count)
|
||||
int fspathncmp(const char *a, const char *b, size_t count)
|
||||
{
|
||||
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
|
||||
}
|
||||
@ -795,12 +794,12 @@ int match_basename(const char *basename, int basenamelen,
|
||||
{
|
||||
if (prefix == patternlen) {
|
||||
if (patternlen == basenamelen &&
|
||||
!strncmp_icase(pattern, basename, basenamelen))
|
||||
!fspathncmp(pattern, basename, basenamelen))
|
||||
return 1;
|
||||
} else if (flags & EXC_FLAG_ENDSWITH) {
|
||||
/* "*literal" matching against "fooliteral" */
|
||||
if (patternlen - 1 <= basenamelen &&
|
||||
!strncmp_icase(pattern + 1,
|
||||
!fspathncmp(pattern + 1,
|
||||
basename + basenamelen - (patternlen - 1),
|
||||
patternlen - 1))
|
||||
return 1;
|
||||
@ -837,7 +836,7 @@ int match_pathname(const char *pathname, int pathlen,
|
||||
*/
|
||||
if (pathlen < baselen + 1 ||
|
||||
(baselen && pathname[baselen] != '/') ||
|
||||
strncmp_icase(pathname, base, baselen))
|
||||
fspathncmp(pathname, base, baselen))
|
||||
return 0;
|
||||
|
||||
namelen = baselen ? pathlen - baselen - 1 : pathlen;
|
||||
@ -851,7 +850,7 @@ int match_pathname(const char *pathname, int pathlen,
|
||||
if (prefix > namelen)
|
||||
return 0;
|
||||
|
||||
if (strncmp_icase(pattern, name, prefix))
|
||||
if (fspathncmp(pattern, name, prefix))
|
||||
return 0;
|
||||
pattern += prefix;
|
||||
patternlen -= prefix;
|
||||
|
4
dir.h
4
dir.h
@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
|
||||
/* tries to remove the path with empty directories along it, ignores ENOENT */
|
||||
extern int remove_path(const char *path);
|
||||
|
||||
extern int strcmp_icase(const char *a, const char *b);
|
||||
extern int strncmp_icase(const char *a, const char *b, size_t count);
|
||||
extern int fspathcmp(const char *a, const char *b);
|
||||
extern int fspathncmp(const char *a, const char *b, size_t count);
|
||||
|
||||
/*
|
||||
* The prefix part of pattern must not contains wildcards.
|
||||
|
@ -1512,7 +1512,7 @@ static int tree_content_set(
|
||||
t = root->tree;
|
||||
for (i = 0; i < t->entry_count; i++) {
|
||||
e = t->entries[i];
|
||||
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
|
||||
if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
|
||||
if (!*slash1) {
|
||||
if (!S_ISDIR(mode)
|
||||
&& e->versions[1].mode == mode
|
||||
@ -1602,7 +1602,7 @@ static int tree_content_remove(
|
||||
t = root->tree;
|
||||
for (i = 0; i < t->entry_count; i++) {
|
||||
e = t->entries[i];
|
||||
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
|
||||
if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
|
||||
if (*slash1 && !S_ISDIR(e->versions[1].mode))
|
||||
/*
|
||||
* If p names a file in some subdirectory, and a
|
||||
@ -1669,7 +1669,7 @@ static int tree_content_get(
|
||||
t = root->tree;
|
||||
for (i = 0; i < t->entry_count; i++) {
|
||||
e = t->entries[i];
|
||||
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
|
||||
if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
|
||||
if (!*slash1)
|
||||
goto found_entry;
|
||||
if (!S_ISDIR(e->versions[1].mode))
|
||||
|
53
path.c
53
path.c
@ -5,6 +5,7 @@
|
||||
#include "strbuf.h"
|
||||
#include "string-list.h"
|
||||
#include "dir.h"
|
||||
#include "worktree.h"
|
||||
|
||||
static int get_st_mode_bits(const char *path, int *mode)
|
||||
{
|
||||
@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
|
||||
update_common_dir(buf, git_dir_len, NULL);
|
||||
}
|
||||
|
||||
static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
|
||||
static void do_git_path(const struct worktree *wt, struct strbuf *buf,
|
||||
const char *fmt, va_list args)
|
||||
{
|
||||
int gitdir_len;
|
||||
strbuf_addstr(buf, get_git_dir());
|
||||
strbuf_addstr(buf, get_worktree_git_dir(wt));
|
||||
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
|
||||
strbuf_addch(buf, '/');
|
||||
gitdir_len = buf->len;
|
||||
@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
|
||||
va_list args;
|
||||
strbuf_reset(buf);
|
||||
va_start(args, fmt);
|
||||
do_git_path(buf, fmt, args);
|
||||
do_git_path(NULL, buf, fmt, args);
|
||||
va_end(args);
|
||||
return buf->buf;
|
||||
}
|
||||
@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(sb, fmt, args);
|
||||
do_git_path(NULL, sb, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(pathname, fmt, args);
|
||||
do_git_path(NULL, pathname, fmt, args);
|
||||
va_end(args);
|
||||
return pathname->buf;
|
||||
}
|
||||
@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(&path, fmt, args);
|
||||
do_git_path(NULL, &path, fmt, args);
|
||||
va_end(args);
|
||||
return strbuf_detach(&path, NULL);
|
||||
}
|
||||
@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...)
|
||||
return cleanup_path(pathname->buf);
|
||||
}
|
||||
|
||||
const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
|
||||
{
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_path(wt, pathname, fmt, args);
|
||||
va_end(args);
|
||||
return pathname->buf;
|
||||
}
|
||||
|
||||
static void do_submodule_path(struct strbuf *buf, const char *path,
|
||||
const char *fmt, va_list args)
|
||||
{
|
||||
@ -503,6 +515,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void do_git_common_path(struct strbuf *buf,
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
strbuf_addstr(buf, get_git_common_dir());
|
||||
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
|
||||
strbuf_addch(buf, '/');
|
||||
strbuf_vaddf(buf, fmt, args);
|
||||
strbuf_cleanup_path(buf);
|
||||
}
|
||||
|
||||
const char *git_common_path(const char *fmt, ...)
|
||||
{
|
||||
struct strbuf *pathname = get_pathname();
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_common_path(pathname, fmt, args);
|
||||
va_end(args);
|
||||
return pathname->buf;
|
||||
}
|
||||
|
||||
void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
do_git_common_path(sb, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
int validate_headref(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
|
@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (!strcmp_icase(ent->base, normalized_objdir)) {
|
||||
if (!fspathcmp(ent->base, normalized_objdir)) {
|
||||
free(ent);
|
||||
return -1;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ test_description='test git worktree add'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-rebase.sh
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_commit init
|
||||
'
|
||||
@ -225,4 +227,61 @@ test_expect_success '"add" worktree with --checkout' '
|
||||
test_cmp init.t swamp2/init.t
|
||||
'
|
||||
|
||||
test_expect_success 'put a worktree under rebase' '
|
||||
git worktree add under-rebase &&
|
||||
(
|
||||
cd under-rebase &&
|
||||
set_fake_editor &&
|
||||
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
|
||||
git worktree list | grep "under-rebase.*detached HEAD"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'add a worktree, checking out a rebased branch' '
|
||||
test_must_fail git worktree add new-rebase under-rebase &&
|
||||
! test -d new-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'checking out a rebased branch from another worktree' '
|
||||
git worktree add new-place &&
|
||||
test_must_fail git -C new-place checkout under-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'not allow to delete a branch under rebase' '
|
||||
(
|
||||
cd under-rebase &&
|
||||
test_must_fail git branch -D under-rebase
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'rename a branch under rebase not allowed' '
|
||||
test_must_fail git branch -M under-rebase rebase-with-new-name
|
||||
'
|
||||
|
||||
test_expect_success 'check out from current worktree branch ok' '
|
||||
(
|
||||
cd under-rebase &&
|
||||
git checkout under-rebase &&
|
||||
git checkout - &&
|
||||
git rebase --abort
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'checkout a branch under bisect' '
|
||||
git worktree add under-bisect &&
|
||||
(
|
||||
cd under-bisect &&
|
||||
git bisect start &&
|
||||
git bisect bad &&
|
||||
git bisect good HEAD~2 &&
|
||||
git worktree list | grep "under-bisect.*detached HEAD" &&
|
||||
test_must_fail git worktree add new-bisect under-bisect &&
|
||||
! test -d new-bisect
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'rename a branch under bisect not allowed' '
|
||||
test_must_fail git branch -M under-bisect bisect-with-new-name
|
||||
'
|
||||
|
||||
test_done
|
||||
|
122
worktree.c
122
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]);
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
char *existing = NULL;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
28
worktree.h
28
worktree.h
@ -3,11 +3,12 @@
|
||||
|
||||
struct worktree {
|
||||
char *path;
|
||||
char *git_dir;
|
||||
char *id;
|
||||
char *head_ref;
|
||||
unsigned char head_sha1[20];
|
||||
int is_detached;
|
||||
int is_bare;
|
||||
int is_current;
|
||||
};
|
||||
|
||||
/* Functions for acting on the information about worktrees. */
|
||||
@ -22,6 +23,12 @@ struct worktree {
|
||||
*/
|
||||
extern struct worktree **get_worktrees(void);
|
||||
|
||||
/*
|
||||
* Return git dir of the worktree. Note that the path may be relative.
|
||||
* If wt is NULL, git dir of current worktree is returned.
|
||||
*/
|
||||
extern const char *get_worktree_git_dir(const struct worktree *wt);
|
||||
|
||||
/*
|
||||
* Free up the memory for worktree(s)
|
||||
*/
|
||||
@ -29,10 +36,21 @@ extern void free_worktrees(struct worktree **);
|
||||
|
||||
/*
|
||||
* Check if a per-worktree symref points to a ref in the main worktree
|
||||
* or any linked worktree, and return the path to the exising worktree
|
||||
* if it is. Returns NULL if there is no existing ref. The caller is
|
||||
* responsible for freeing the returned path.
|
||||
* or any linked worktree, and return the worktree that holds the ref,
|
||||
* or NULL otherwise. The result may be destroyed by the next call.
|
||||
*/
|
||||
extern char *find_shared_symref(const char *symref, const char *target);
|
||||
extern const struct worktree *find_shared_symref(const char *symref,
|
||||
const char *target);
|
||||
|
||||
int is_worktree_being_rebased(const struct worktree *wt, const char *target);
|
||||
int is_worktree_being_bisected(const struct worktree *wt, const char *target);
|
||||
|
||||
/*
|
||||
* Similar to git_path() but can produce paths for a specified
|
||||
* worktree instead of current one
|
||||
*/
|
||||
extern const char *worktree_git_path(const struct worktree *wt,
|
||||
const char *fmt, ...)
|
||||
__attribute__((format (printf, 2, 3)));
|
||||
|
||||
#endif
|
||||
|
69
wt-status.c
69
wt-status.c
@ -15,6 +15,7 @@
|
||||
#include "column.h"
|
||||
#include "strbuf.h"
|
||||
#include "utf8.h"
|
||||
#include "worktree.h"
|
||||
|
||||
static const char cut_line[] =
|
||||
"------------------------ >8 ------------------------\n";
|
||||
@ -1263,13 +1264,13 @@ static void show_bisect_in_progress(struct wt_status *s,
|
||||
/*
|
||||
* Extract branch information from rebase/bisect
|
||||
*/
|
||||
static char *read_and_strip_branch(const char *path)
|
||||
static char *get_branch(const struct worktree *wt, const char *path)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
unsigned char sha1[20];
|
||||
const char *branch_name;
|
||||
|
||||
if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
|
||||
if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
|
||||
goto got_nothing;
|
||||
|
||||
while (sb.len && sb.buf[sb.len - 1] == '\n')
|
||||
@ -1361,6 +1362,46 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
|
||||
strbuf_release(&cb.buf);
|
||||
}
|
||||
|
||||
int wt_status_check_rebase(const struct worktree *wt,
|
||||
struct wt_status_state *state)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
|
||||
if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
|
||||
state->am_in_progress = 1;
|
||||
if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
|
||||
state->am_empty_patch = 1;
|
||||
} else {
|
||||
state->rebase_in_progress = 1;
|
||||
state->branch = get_branch(wt, "rebase-apply/head-name");
|
||||
state->onto = get_branch(wt, "rebase-apply/onto");
|
||||
}
|
||||
} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
|
||||
if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
|
||||
state->rebase_interactive_in_progress = 1;
|
||||
else
|
||||
state->rebase_in_progress = 1;
|
||||
state->branch = get_branch(wt, "rebase-merge/head-name");
|
||||
state->onto = get_branch(wt, "rebase-merge/onto");
|
||||
} else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wt_status_check_bisect(const struct worktree *wt,
|
||||
struct wt_status_state *state)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
|
||||
state->bisect_in_progress = 1;
|
||||
state->branch = get_branch(wt, "BISECT_START");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wt_status_get_state(struct wt_status_state *state,
|
||||
int get_detached_from)
|
||||
{
|
||||
@ -1369,32 +1410,14 @@ void wt_status_get_state(struct wt_status_state *state,
|
||||
|
||||
if (!stat(git_path_merge_head(), &st)) {
|
||||
state->merge_in_progress = 1;
|
||||
} else if (!stat(git_path("rebase-apply"), &st)) {
|
||||
if (!stat(git_path("rebase-apply/applying"), &st)) {
|
||||
state->am_in_progress = 1;
|
||||
if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
|
||||
state->am_empty_patch = 1;
|
||||
} else {
|
||||
state->rebase_in_progress = 1;
|
||||
state->branch = read_and_strip_branch("rebase-apply/head-name");
|
||||
state->onto = read_and_strip_branch("rebase-apply/onto");
|
||||
}
|
||||
} else if (!stat(git_path("rebase-merge"), &st)) {
|
||||
if (!stat(git_path("rebase-merge/interactive"), &st))
|
||||
state->rebase_interactive_in_progress = 1;
|
||||
else
|
||||
state->rebase_in_progress = 1;
|
||||
state->branch = read_and_strip_branch("rebase-merge/head-name");
|
||||
state->onto = read_and_strip_branch("rebase-merge/onto");
|
||||
} else if (wt_status_check_rebase(NULL, state)) {
|
||||
; /* all set */
|
||||
} else if (!stat(git_path_cherry_pick_head(), &st) &&
|
||||
!get_sha1("CHERRY_PICK_HEAD", sha1)) {
|
||||
state->cherry_pick_in_progress = 1;
|
||||
hashcpy(state->cherry_pick_head_sha1, sha1);
|
||||
}
|
||||
if (!stat(git_path("BISECT_LOG"), &st)) {
|
||||
state->bisect_in_progress = 1;
|
||||
state->branch = read_and_strip_branch("BISECT_START");
|
||||
}
|
||||
wt_status_check_bisect(NULL, state);
|
||||
if (!stat(git_path_revert_head(), &st) &&
|
||||
!get_sha1("REVERT_HEAD", sha1)) {
|
||||
state->revert_in_progress = 1;
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "color.h"
|
||||
#include "pathspec.h"
|
||||
|
||||
struct worktree;
|
||||
|
||||
enum color_wt_status {
|
||||
WT_STATUS_HEADER = 0,
|
||||
WT_STATUS_UPDATED,
|
||||
@ -100,6 +102,10 @@ void wt_status_prepare(struct wt_status *s);
|
||||
void wt_status_print(struct wt_status *s);
|
||||
void wt_status_collect(struct wt_status *s);
|
||||
void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
|
||||
int wt_status_check_rebase(const struct worktree *wt,
|
||||
struct wt_status_state *state);
|
||||
int wt_status_check_bisect(const struct worktree *wt,
|
||||
struct wt_status_state *state);
|
||||
|
||||
void wt_shortstatus_print(struct wt_status *s);
|
||||
void wt_porcelain_print(struct wt_status *s);
|
||||
|
Loading…
Reference in New Issue
Block a user