Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/commands/git_commands/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ func (self *WorktreeCommands) Delete(worktreePath string, force bool) error {
return self.cmd.New(cmdArgs).Run()
}

func (self *WorktreeCommands) IsWorktreeClean(worktreePath string) bool {
cmdArgs := NewGitCmd("status").Arg("--porcelain").Dir(worktreePath).ToArgv()

output, err := self.cmd.New(cmdArgs).RunWithOutput()
return err == nil && output == ""
}

func (self *WorktreeCommands) Detach(worktreePath string) error {
cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv()

Expand Down
39 changes: 31 additions & 8 deletions pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {

worktreeForRef, ok := self.worktreeForBranch(selectedBranch)
if ok && !worktreeForRef.IsCurrent {
return self.promptToCheckoutWorktree(worktreeForRef)
return self.promptToCheckoutWorktree(worktreeForRef, selectedBranch.Name)
}

self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
Expand All @@ -498,16 +498,39 @@ func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*model
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
}

func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktree) error {
prompt := utils.ResolvePlaceholderString(self.c.Tr.AlreadyCheckedOutByWorktree, map[string]string{
func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktree, branchName string) error {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty long function; would be good to move it to BranchesHelper.

title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of putting this text in the title. Branch names can be very long, so this might get truncated. I see that we are doing the same thing already when deleting branches, but it's not good there, either. :)

I'd suggest to use a shorter title and put the longer text in a menu prompt (see CreateMenuOptions.Prompt), something like

Branch 'xyz' is checked out by worktree 'abc'. How would you like to proceed?

Switch to worktree
Detach worktree and checkout branch here
Cancel

"worktreeName": worktree.Name,
"branchName": branchName,
})

return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipSwitchWorktreeOnCheckoutWarning, types.ConfirmOpts{
Title: self.c.Tr.SwitchToWorktree,
Prompt: prompt,
HandleConfirm: func() error {
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
var detachDisabledReason *types.DisabledReason
if !worktree.IsPathMissing && !self.c.Git().Worktree.IsWorktreeClean(worktree.Path) {
detachDisabledReason = &types.DisabledReason{Text: self.c.Tr.DetachWorktreeHasUncommittedChanges}
}
Comment on lines +508 to +510
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this. Detaching a worktree when there are modified files doesn't lose data, the modifications will stay the same. And we don't guard against this when detaching a worktree in response to deleting a branch, either.


return self.c.Menu(types.CreateMenuOptions{
Title: title,
Items: []*types.MenuItem{
{
Label: self.c.Tr.SwitchToWorktree,
OnPress: func() error {
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
},
},
{
Label: self.c.Tr.DetachWorktree,
Tooltip: self.c.Tr.DetachWorktreeTooltip,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tooltip sounds as if detaching the worktree is the only thing that will happen. It should also mention that the branch is going to be checked out in the current worktree afterwards.

DisabledReason: detachDisabledReason,
OnPress: func() error {
return self.c.Helpers().Refs.CheckoutRef(branchName, types.CheckoutRefOptions{
PreCheckoutCommand: func() error {
self.c.LogAction(self.c.Tr.DetachingWorktree)
return self.c.Git().Worktree.Detach(worktree.Path)
},
})
},
},
},
})
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/gui/controllers/helpers/refs_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})

return withCheckoutStatus(func(gocui.Task) error {
if options.PreCheckoutCommand != nil {
if err := options.PreCheckoutCommand(); err != nil {
return err
}
}
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option

Expand Down
4 changes: 4 additions & 0 deletions pkg/gui/types/common_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ type CheckoutRefOptions struct {
// (e.g. checking out a remote branch), but it not needed when checking out an existing local
// branch or a detached head (e.g. a tag).
RefreshPullRequests bool

// If set, this function is called right before the checkout command.
// Used e.g. to detach a worktree before checking out the branch.
PreCheckoutCommand func() error
}
2 changes: 2 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ type TranslationSet struct {
BranchCheckedOutByWorktree string
SomeBranchesCheckedOutByWorktreeError string
DetachWorktreeTooltip string
DetachWorktreeHasUncommittedChanges string
Switching string
RemoveWorktree string
RemoveWorktreeTitle string
Expand Down Expand Up @@ -1985,6 +1986,7 @@ func EnglishTranslationSet() *TranslationSet {
BranchCheckedOutByWorktree: "Branch {{.branchName}} is checked out by worktree {{.worktreeName}}",
SomeBranchesCheckedOutByWorktreeError: "Some of the selected branches are checked out by other worktrees. Select them one by one to delete them.",
DetachWorktreeTooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone.",
DetachWorktreeHasUncommittedChanges: "Worktree has uncommitted changes that would be lost",
Switching: "Switching",
RemoveWorktree: "Remove worktree",
RemoveWorktreeTitle: "Remove worktree",
Expand Down
6 changes: 3 additions & 3 deletions pkg/integration/tests/worktree/add_from_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ var AddFromBranch = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("mybranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")).
t.ExpectPopup().Menu().
Title(Equals("Branch mybranch is checked out by worktree repo")).
Select(Equals("Switch to worktree")).
Confirm()
}).
Lines(
Expand Down
12 changes: 6 additions & 6 deletions pkg/integration/tests/worktree/associate_branch_bisect.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ var AssociateBranchBisect = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("newbranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree linked-worktree. Do you want to switch to that worktree?")).
t.ExpectPopup().Menu().
Title(Equals("Branch newbranch is checked out by worktree linked-worktree")).
Select(Equals("Switch to worktree")).
Confirm()

t.Views().Information().Content(DoesNotContain("Bisecting"))
Expand All @@ -79,9 +79,9 @@ var AssociateBranchBisect = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("mybranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")).
t.ExpectPopup().Menu().
Title(Equals("Branch mybranch is checked out by worktree repo")).
Select(Equals("Switch to worktree")).
Confirm()
})
},
Expand Down
12 changes: 6 additions & 6 deletions pkg/integration/tests/worktree/associate_branch_rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("newbranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree linked-worktree. Do you want to switch to that worktree?")).
t.ExpectPopup().Menu().
Title(Equals("Branch newbranch is checked out by worktree linked-worktree")).
Select(Equals("Switch to worktree")).
Confirm()

t.Views().Information().Content(DoesNotContain("Rebasing"))
Expand All @@ -74,9 +74,9 @@ var AssociateBranchRebase = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("mybranch")).
Press(keys.Universal.Select).
Tap(func() {
t.ExpectPopup().Confirmation().
Title(Equals("Switch to worktree")).
Content(Equals("This branch is checked out by worktree repo. Do you want to switch to that worktree?")).
t.ExpectPopup().Menu().
Title(Equals("Branch mybranch is checked out by worktree repo")).
Select(Equals("Switch to worktree")).
Confirm()
}).
Lines(
Expand Down
Loading