Files
lazygit/pkg/gui/context/sub_commits_context.go
T
Stefan HallerandClaude Opus 5.5 51e91fbb85 Open files of a branch's diff from the worktree that has it checked out
If you enter a branch in the branches panel and press `e` on a line of
a commit's diff, or on a file in the commit files panel entered from
there, lazygit opens the file in the current worktree. If another
worktree has the branch checked out, this is the wrong copy of the
file. The current worktree may have it in a different state, or not
have it at all. Clicking a lazygit-edit:// hyperlink of a diff renderer
in such a diff has the same problem.

Open the file from the worktree that has the branch checked out
instead, and carry the line number forward to the file as that worktree
has it.

If no other worktree has the branch checked out, the file is still
opened from the current worktree.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-10 13:58:04 +02:00

276 lines
7.9 KiB
Go

package context
import (
"time"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type SubCommitsContext struct {
c *ContextCommon
*SubCommitsViewModel
*ListContextTrait
*DynamicTitleBuilder
*SearchTrait
}
var (
_ types.IListContext = (*SubCommitsContext)(nil)
_ types.DiffableContext = (*SubCommitsContext)(nil)
_ types.ISearchableContext = (*SubCommitsContext)(nil)
_ types.DiffMainViewContext = (*SubCommitsContext)(nil)
_ types.PullRequestDiffContext = (*SubCommitsContext)(nil)
_ types.BranchDiffContext = (*SubCommitsContext)(nil)
)
func (self *SubCommitsContext) GetDiffMainViewType() types.DiffMainViewType {
return types.DiffMainViewTypePatchBuilding
}
// This panel shows the commits of the branch it was entered from, and of the branches
// below it in a stack. PullRequestDiff looks for their pull request among those
// branches. The panel is also entered from a tag, a remote branch and the reflog, none of
// which a pull request is made from.
func (self *SubCommitsContext) PullRequestDiff() types.PullRequestDiff {
branch, ok := self.GetRef().(*models.Branch)
if !ok {
return types.PullRequestDiff{}
}
_, selectionStart, selectionEnd := self.GetSelectedItems()
startIdx, endIdx := commitRangeShownInDiff(
selectionStart, selectionEnd, self.GetSelectedLineIdx(), self.GetSelectedRefRangeForDiffFiles())
return pullRequestDiff(
self.GetCommits(), startIdx, endIdx, branch.Name, self.c.Model().Branches, self.c.Model().PullRequestsMap)
}
// BranchOfDiff is nil if the panel was entered from a tag, a remote branch or the
// reflog instead of a local branch.
func (self *SubCommitsContext) BranchOfDiff() *models.Branch {
branch, _ := self.GetRef().(*models.Branch)
return branch
}
func NewSubCommitsContext(
c *ContextCommon,
) *SubCommitsContext {
viewModel := &SubCommitsViewModel{
ListViewModel: NewListViewModel(
func() []*models.Commit { return c.Model().SubCommits },
),
ref: nil,
limitCommits: true,
}
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
// This can happen if a sub-commits view is asked to be rerendered while
// it is invisible; for example when switching screen modes, which
// rerenders all views.
if viewModel.GetRef() == nil {
return [][]string{}
}
var selectedCommitHashPtr *string
if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitHashPtr = selectedCommit.HashPtr()
}
}
branches := []*models.Branch{}
if viewModel.GetShowBranchHeads() {
branches = c.Model().Branches
}
hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs()
return presentation.GetCommitListDisplayStrings(
c.Common,
c.Model().SubCommits,
branches,
viewModel.GetRef().RefName(),
hasRebaseUpdateRefsConfig,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
"",
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
time.Now(),
c.UserConfig().Git.ParseEmoji,
selectedCommitHashPtr,
startIdx,
endIdx,
shouldShowGraph(c),
commitGraphSymbolSet(c),
git_commands.NewNullBisectInfo(),
)
}
getNonModelItems := func() []*NonModelItem {
result := []*NonModelItem{}
if viewModel.GetRefToShowDivergenceFrom() != "" {
_, upstreamIdx, found := lo.FindIndexOf(
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight })
if !found {
upstreamIdx = 0
}
result = append(result, &NonModelItem{
Index: upstreamIdx,
Content: formatListSectionHeader(c.Tr.DivergenceSectionHeaderRemote),
})
_, localIdx, found := lo.FindIndexOf(
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
if !found {
localIdx = len(c.Model().SubCommits)
}
result = append(result, &NonModelItem{
Index: localIdx,
Content: formatListSectionHeader(c.Tr.DivergenceSectionHeaderLocal),
})
}
return result
}
ctx := &SubCommitsContext{
c: c,
SubCommitsViewModel: viewModel,
SearchTrait: NewSearchTrait(c),
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().SubCommits,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
NeedsRerenderOnHeightChange: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
getDisplayStrings: getDisplayStrings,
getNonModelItems: getNonModelItems,
},
c: c,
refreshViewportOnChange: true,
renderOnlyVisibleLines: true,
},
}
return ctx
}
type SubCommitsViewModel struct {
// name of the ref that the sub-commits are shown for
ref models.Ref
refToShowDivergenceFrom string
*ListViewModel[*models.Commit]
limitCommits bool
showBranchHeads bool
}
func (self *SubCommitsViewModel) SetRef(ref models.Ref) {
self.ref = ref
}
func (self *SubCommitsViewModel) GetRef() models.Ref {
return self.ref
}
func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) {
self.refToShowDivergenceFrom = ref
}
func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string {
return self.refToShowDivergenceFrom
}
func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
self.showBranchHeads = value
}
func (self *SubCommitsViewModel) GetShowBranchHeads() bool {
return self.showBranchHeads
}
func (self *SubCommitsContext) CanRebase() bool {
return false
}
func (self *SubCommitsContext) GetSelectedRef() models.Ref {
commit := self.GetSelected()
if commit == nil {
return nil
}
return commit
}
func (self *SubCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
commits, startIdx, endIdx := self.GetSelectedItems()
if commits == nil || startIdx == endIdx {
return nil
}
from := commits[len(commits)-1]
to := commits[0]
if from.Divergence != to.Divergence {
return nil
}
return &types.RefRange{From: from, To: to}
}
func (self *SubCommitsContext) GetCommits() []*models.Commit {
return self.getModel()
}
func (self *SubCommitsContext) SetLimitCommits(value bool) {
self.limitCommits = value
}
func (self *SubCommitsContext) GetLimitCommits() bool {
return self.limitCommits
}
func (self *SubCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
commits, _, _ := self.GetSelectedItems()
if commits == nil {
return ""
}
return commits[0].Hash()
}
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.modelToViewIndexConverter(), searchStr)
}
func (self *SubCommitsContext) IndexForGotoBottom() int {
commits := self.GetCommits()
selectedIdx := self.GetSelectedLineIdx()
if selectedIdx >= 0 && selectedIdx < len(commits)-1 {
if commits[selectedIdx+1].Status != models.StatusMerged {
_, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool {
return c.Status == models.StatusMerged
})
if found {
return idx - 1
}
}
}
return self.list.Len() - 1
}