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:
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user