1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-28 16:02:01 +03:00

Associate branches with worktrees even when mid-rebase

This commit is contained in:
Jesse Duffield
2023-07-17 13:40:26 +10:00
parent 6f2f9f6677
commit ec839e9e96
8 changed files with 104 additions and 37 deletions

View File

@ -2,7 +2,6 @@ package git_commands
import ( import (
"fmt" "fmt"
"os"
"regexp" "regexp"
"strings" "strings"
@ -118,11 +117,6 @@ outer:
} }
func (self *BranchLoader) obtainBranches() []*models.Branch { func (self *BranchLoader) obtainBranches() []*models.Branch {
currentDir, err := os.Getwd()
if err != nil {
panic(err)
}
output, err := self.getRawBranches() output, err := self.getRawBranches()
if err != nil { if err != nil {
panic(err) panic(err)
@ -144,7 +138,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
return nil, false return nil, false
} }
return obtainBranch(split, currentDir), true return obtainBranch(split), true
}) })
} }
@ -172,32 +166,28 @@ var branchFields = []string{
"upstream:track", "upstream:track",
"subject", "subject",
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE), fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
"worktreepath",
} }
// Obtain branch information from parsed line output of getRawBranches() // Obtain branch information from parsed line output of getRawBranches()
func obtainBranch(split []string, currentDir string) *models.Branch { func obtainBranch(split []string) *models.Branch {
headMarker := split[0] headMarker := split[0]
fullName := split[1] fullName := split[1]
upstreamName := split[2] upstreamName := split[2]
track := split[3] track := split[3]
subject := split[4] subject := split[4]
commitHash := split[5] commitHash := split[5]
branchDir := split[6]
checkedOutByOtherWorktree := len(branchDir) > 0 && branchDir != currentDir
name := strings.TrimPrefix(fullName, "heads/") name := strings.TrimPrefix(fullName, "heads/")
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track) pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
return &models.Branch{ return &models.Branch{
Name: name, Name: name,
Pushables: pushables, Pushables: pushables,
Pullables: pullables, Pullables: pullables,
UpstreamGone: gone, UpstreamGone: gone,
Head: headMarker == "*", Head: headMarker == "*",
Subject: subject, Subject: subject,
CommitHash: commitHash, CommitHash: commitHash,
CheckedOutByOtherWorktree: checkedOutByOtherWorktree,
} }
} }

View File

@ -81,7 +81,7 @@ func TestObtainBranch(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
branch := obtainBranch(s.input, "current-dir") branch := obtainBranch(s.input)
assert.EqualValues(t, s.expectedBranch, branch) assert.EqualValues(t, s.expectedBranch, branch)
}) })
} }

View File

@ -6,6 +6,8 @@ import (
"io/fs" "io/fs"
"log" "log"
"os" "os"
"github.com/jesseduffield/lazygit/pkg/commands/models"
) )
type WorktreeCommands struct { type WorktreeCommands struct {
@ -84,3 +86,22 @@ func (self *WorktreeCommands) IsWorktreePathMissing(path string) bool {
func EqualPath(a string, b string) bool { func EqualPath(a string, b string) bool {
return a == b return a == b
} }
func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) {
for _, worktree := range worktrees {
if worktree.Branch == branch.Name {
return worktree, true
}
}
return nil, false
}
func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktree) bool {
worktree, ok := WorktreeForBranch(branch, worktrees)
if !ok {
return false
}
return !IsCurrentWorktree(worktree.Path)
}

View File

@ -2,6 +2,7 @@ package git_commands
import ( import (
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
@ -76,9 +77,66 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
} }
} }
// Some worktrees are on a branch but are mid-rebase, and in those cases,
// `git worktree list` will not show the branch name. We can get the branch
// name from the `rebase-merge/head-name` file (if it exists) in the folder
// for the worktree in the parent repo's .git/worktrees folder.
for _, worktree := range worktrees {
// No point checking if we already have a branch name
if worktree.Branch != "" {
continue
}
rebaseBranch, ok := rebaseBranch(worktree.Path)
if ok {
worktree.Branch = rebaseBranch
}
}
return worktrees, nil return worktrees, nil
} }
func rebaseBranch(worktreePath string) (string, bool) {
// need to find the actual path of the worktree in the .git dir
gitPath, ok := worktreeGitPath(worktreePath)
if !ok {
return "", false
}
// now we look inside that git path for a file `rebase-merge/head-name`
// if it exists, we update the worktree to say that it has that for a head
headNameContents, err := os.ReadFile(filepath.Join(gitPath, "rebase-merge", "head-name"))
if err != nil {
return "", false
}
headName := strings.TrimSpace(string(headNameContents))
shortHeadName := strings.TrimPrefix(headName, "refs/heads/")
return shortHeadName, true
}
func worktreeGitPath(worktreePath string) (string, bool) {
// first we get the path of the worktree, then we look at the contents of the `.git` file in that path
// then we look for the line that says `gitdir: /path/to/.git/worktrees/<worktree-name>`
// then we return that path
gitFileContents, err := os.ReadFile(filepath.Join(worktreePath, ".git"))
if err != nil {
return "", false
}
gitDirLine := lo.Filter(strings.Split(string(gitFileContents), "\n"), func(line string, _ int) bool {
return strings.HasPrefix(line, "gitdir: ")
})
if len(gitDirLine) == 0 {
return "", false
}
gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ")
return gitDir, true
}
type pathWithIndexT struct { type pathWithIndexT struct {
path string path string
index int index int

View File

@ -26,8 +26,6 @@ type Branch struct {
Subject string Subject string
// commit hash // commit hash
CommitHash string CommitHash string
CheckedOutByOtherWorktree bool
} }
func (b *Branch) FullRefName() string { func (b *Branch) FullRefName() string {

View File

@ -31,6 +31,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
c.Modes().Diffing.Ref, c.Modes().Diffing.Ref,
c.Tr, c.Tr,
c.UserConfig, c.UserConfig,
c.Model().Worktrees,
) )
} }

View File

@ -202,11 +202,9 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch) return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
} }
if selectedBranch.CheckedOutByOtherWorktree { worktreeForRef, ok := self.worktreeForBranch(selectedBranch)
worktreeForRef, ok := self.worktreeForBranch(selectedBranch) if ok && !self.c.Git().Worktree.IsCurrentWorktree(worktreeForRef.Path) {
if ok && !self.c.Git().Worktree.IsCurrentWorktree(worktreeForRef.Path) { return self.promptToCheckoutWorktree(worktreeForRef)
return self.promptToCheckoutWorktree(worktreeForRef)
}
} }
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch) self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
@ -214,13 +212,7 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
} }
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) { func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
for _, worktree := range self.c.Model().Worktrees { return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
if worktree.Branch == branch.Name {
return worktree, true
}
}
return nil, false
} }
func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktree) error { func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktree) error {
@ -326,13 +318,17 @@ func (self *BranchesController) delete(branch *models.Branch) error {
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch) return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
} }
if branch.CheckedOutByOtherWorktree { if self.checkedOutByOtherWorktree(branch) {
return self.promptWorktreeBranchDelete(branch) return self.promptWorktreeBranchDelete(branch)
} }
return self.deleteWithForce(branch, false) return self.deleteWithForce(branch, false)
} }
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
}
func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error { func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
worktree, ok := self.worktreeForBranch(selectedBranch) worktree, ok := self.worktreeForBranch(selectedBranch)
if !ok { if !ok {

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
@ -23,10 +24,11 @@ func GetBranchListDisplayStrings(
diffName string, diffName string,
tr *i18n.TranslationSet, tr *i18n.TranslationSet,
userConfig *config.UserConfig, userConfig *config.UserConfig,
worktrees []*models.Worktree,
) [][]string { ) [][]string {
return slices.Map(branches, func(branch *models.Branch) []string { return slices.Map(branches, func(branch *models.Branch) []string {
diffed := branch.Name == diffName diffed := branch.Name == diffName
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig) return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
}) })
} }
@ -37,6 +39,7 @@ func getBranchDisplayStrings(
diffed bool, diffed bool,
tr *i18n.TranslationSet, tr *i18n.TranslationSet,
userConfig *config.UserConfig, userConfig *config.UserConfig,
worktrees []*models.Worktree,
) []string { ) []string {
displayName := b.Name displayName := b.Name
if b.DisplayName != "" { if b.DisplayName != "" {
@ -50,7 +53,7 @@ func getBranchDisplayStrings(
coloredName := nameTextStyle.Sprint(displayName) coloredName := nameTextStyle.Sprint(displayName)
branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft) branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft)
if b.CheckedOutByOtherWorktree { if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree)) worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon)) coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
} }