Skip to content

Commit 8d9fdd7

Browse files
pcloudsgitster
authored andcommitted
worktree.c: check whether branch is rebased in another worktree
This function find_shared_symref() is used in a couple places: 1) in builtin/branch.c: it's used to detect if a branch is checked out elsewhere and refuse to delete the branch. 2) in builtin/notes.c: it's used to detect if a note is being merged in another worktree 3) in branch.c, the function die_if_checked_out() is actually used by "git checkout" and "git worktree add" to see if a branch is already checked out elsewhere and refuse the operation. In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached mode, find_shared_symref() fails to detect it and declares "no branch is checked out here", which is not really what we want. This patch tightens the test. If the given symref is "HEAD", we try to detect if rebase is ongoing. If so return the branch being rebased. This makes checkout and branch delete operations safer because you can't checkout a branch being rebased in another place, or delete it. Special case for checkout. If the current branch is being rebased, git-rebase.sh may use "git checkout" to abort and return back to the original branch. The updated test in find_shared_symref() will prevent that and "git rebase --abort" will fail as a result. find_shared_symref() and die_if_checked_out() have to learn a new option ignore_current_worktree to loosen the test a bit. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c871714 commit 8d9fdd7

File tree

6 files changed

+76
-6
lines changed

6 files changed

+76
-6
lines changed

branch.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,12 @@ void remove_branch_state(void)
334334
unlink(git_path_squash_msg());
335335
}
336336

337-
void die_if_checked_out(const char *branch)
337+
void die_if_checked_out(const char *branch, int ignore_current_worktree)
338338
{
339339
const struct worktree *wt;
340340

341341
wt = find_shared_symref("HEAD", branch);
342-
if (!wt)
342+
if (!wt || (ignore_current_worktree && wt->is_current))
343343
return;
344344
skip_prefix(branch, "refs/heads/", &branch);
345345
die(_("'%s' is already checked out at '%s'"),

branch.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
5858
* worktree and die (with a message describing its checkout location) if
5959
* it is.
6060
*/
61-
extern void die_if_checked_out(const char *branch);
61+
extern void die_if_checked_out(const char *branch, int ignore_current_worktree);
6262

6363
/*
6464
* Update all per-worktree HEADs pointing at the old ref to point the new ref.

builtin/checkout.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,
11111111
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
11121112
if (head_ref &&
11131113
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
1114-
die_if_checked_out(new->path);
1114+
die_if_checked_out(new->path, 1);
11151115
free(head_ref);
11161116
}
11171117

builtin/worktree.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
205205
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
206206
ref_exists(symref.buf)) { /* it's a branch */
207207
if (!opts->force)
208-
die_if_checked_out(symref.buf);
208+
die_if_checked_out(symref.buf, 0);
209209
} else { /* must be a commit */
210210
commit = lookup_commit_reference_by_name(refname);
211211
if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
349349
if (!opts.force &&
350350
!strbuf_check_branch_ref(&symref, opts.new_branch) &&
351351
ref_exists(symref.buf))
352-
die_if_checked_out(symref.buf);
352+
die_if_checked_out(symref.buf, 0);
353353
strbuf_release(&symref);
354354
}
355355

t/t2025-worktree-add.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ test_description='test git worktree add'
44

55
. ./test-lib.sh
66

7+
. "$TEST_DIRECTORY"/lib-rebase.sh
8+
79
test_expect_success 'setup' '
810
test_commit init
911
'
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' '
225227
test_cmp init.t swamp2/init.t
226228
'
227229

230+
test_expect_success 'put a worktree under rebase' '
231+
git worktree add under-rebase &&
232+
(
233+
cd under-rebase &&
234+
set_fake_editor &&
235+
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
236+
git worktree list | grep "under-rebase.*detached HEAD"
237+
)
238+
'
239+
240+
test_expect_success 'add a worktree, checking out a rebased branch' '
241+
test_must_fail git worktree add new-rebase under-rebase &&
242+
! test -d new-rebase
243+
'
244+
245+
test_expect_success 'checking out a rebased branch from another worktree' '
246+
git worktree add new-place &&
247+
test_must_fail git -C new-place checkout under-rebase
248+
'
249+
250+
test_expect_success 'not allow to delete a branch under rebase' '
251+
(
252+
cd under-rebase &&
253+
test_must_fail git branch -D under-rebase
254+
)
255+
'
256+
257+
test_expect_success 'check out from current worktree branch ok' '
258+
(
259+
cd under-rebase &&
260+
git checkout under-rebase &&
261+
git checkout - &&
262+
git rebase --abort
263+
)
264+
'
265+
228266
test_done

worktree.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "strbuf.h"
44
#include "worktree.h"
55
#include "dir.h"
6+
#include "wt-status.h"
67

78
void free_worktrees(struct worktree **worktrees)
89
{
@@ -215,6 +216,30 @@ const char *get_worktree_git_dir(const struct worktree *wt)
215216
return git_common_path("worktrees/%s", wt->id);
216217
}
217218

219+
static int is_worktree_being_rebased(const struct worktree *wt,
220+
const char *target)
221+
{
222+
struct wt_status_state state;
223+
int found_rebase;
224+
225+
memset(&state, 0, sizeof(state));
226+
found_rebase = wt_status_check_rebase(wt, &state) &&
227+
((state.rebase_in_progress ||
228+
state.rebase_interactive_in_progress) &&
229+
state.branch &&
230+
starts_with(target, "refs/heads/") &&
231+
!strcmp(state.branch, target + strlen("refs/heads/")));
232+
free(state.branch);
233+
free(state.onto);
234+
return found_rebase;
235+
}
236+
237+
/*
238+
* note: this function should be able to detect shared symref even if
239+
* HEAD is temporarily detached (e.g. in the middle of rebase or
240+
* bisect). New commands that do similar things should update this
241+
* function as well.
242+
*/
218243
const struct worktree *find_shared_symref(const char *symref,
219244
const char *target)
220245
{
@@ -231,6 +256,13 @@ const struct worktree *find_shared_symref(const char *symref,
231256
for (i = 0; worktrees[i]; i++) {
232257
struct worktree *wt = worktrees[i];
233258

259+
if (wt->is_detached && !strcmp(symref, "HEAD")) {
260+
if (is_worktree_being_rebased(wt, target)) {
261+
existing = wt;
262+
break;
263+
}
264+
}
265+
234266
strbuf_reset(&path);
235267
strbuf_reset(&sb);
236268
strbuf_addf(&path, "%s/%s",

0 commit comments

Comments
 (0)