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
10 changes: 10 additions & 0 deletions pkg/commands/git_commands/bisect.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
currentHash := strings.TrimSpace(string(currentContent))
info.current = currentHash

// Read actual HEAD to detect manual checkouts during bisect.
// During bisect, HEAD is always a detached hash (not a symbolic ref).
headContent, err := os.ReadFile(filepath.Join(gitDir, "HEAD"))
if err == nil {
headStr := strings.TrimSpace(string(headContent))
if !strings.HasPrefix(headStr, "ref: ") {
info.headHash = headStr
}
}

return info
}

Expand Down
13 changes: 12 additions & 1 deletion pkg/commands/git_commands/bisect_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ type BisectInfo struct {
// map of commit hashes to their status
statusMap map[string]BisectStatus

// the hash of the commit that's under test
// the hash of the commit that git bisect expects to be under test
// (from BISECT_EXPECTED_REV)
current string

// the actual HEAD hash, which may differ from current if the user
// manually checked out a different commit during bisect
headHash string
}

type BisectStatus int
Expand Down Expand Up @@ -63,6 +68,12 @@ func (self *BisectInfo) GetCurrentHash() string {
return self.current
}

// GetHeadHash returns the actual HEAD commit hash. During bisect this may
// differ from GetCurrentHash() if the user manually checked out a commit.
func (self *BisectInfo) GetHeadHash() string {
return self.headHash
}

func (self *BisectInfo) GetStartHash() string {
return self.start
}
Expand Down
12 changes: 9 additions & 3 deletions pkg/gui/controllers/bisect_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
// Originally we were allowing the user to, from the bisect menu, select whether
// they were talking about the selected commit or the current bisect commit,
// and that was a bit confusing (and required extra keypresses).
selectCurrentAfter := info.GetCurrentHash() == "" || info.GetCurrentHash() == commit.Hash()
// Use actual HEAD to determine the current commit, since the user may have
// manually checked out a different commit during bisect.
headHash := info.GetHeadHash()
if headHash == "" {
headHash = info.GetCurrentHash()
}
selectCurrentAfter := info.GetCurrentHash() == "" || headHash == commit.Hash()
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
// ref, because we'll be reloading our commits in that case.
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
Expand All @@ -78,7 +84,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
// use the selected commit in that case.

bisecting := info.GetCurrentHash() != ""
hashToMark := lo.Ternary(bisecting, info.GetCurrentHash(), commit.Hash())
hashToMark := lo.Ternary(bisecting, headHash, commit.Hash())
shortHashToMark := utils.ShortHash(hashToMark)

// For marking a commit as bad, when we're not already bisecting, we require
Expand Down Expand Up @@ -130,7 +136,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Key: 's',
},
}
if info.GetCurrentHash() != "" && info.GetCurrentHash() != commit.Hash() {
if info.GetCurrentHash() != "" && headHash != commit.Hash() {
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortHash()),
OnPress: func() error {
Expand Down
9 changes: 8 additions & 1 deletion pkg/gui/presentation/commits.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,14 @@ func getBisectStatus(index int, commitHash string, bisectInfo *git_commands.Bise
return BisectStatusNone
}

if bisectInfo.GetCurrentHash() == commitHash {
// Use actual HEAD hash to determine the current position, since the user
// may have manually checked out a different commit during bisect.
// Fall back to BISECT_EXPECTED_REV if HEAD hash is not available.
currentHash := bisectInfo.GetHeadHash()
if currentHash == "" {
currentHash = bisectInfo.GetCurrentHash()
}
if currentHash == commitHash {
return BisectStatusCurrent
}

Expand Down
83 changes: 83 additions & 0 deletions pkg/integration/tests/bisect/checkout_during_bisect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package bisect

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var CheckoutDuringBisect = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Checkout a different commit during a bisect and verify the current marker follows HEAD",
ExtraCmdArgs: []string{},
Skip: false,
SetupRepo: func(shell *Shell) {
shell.
NewBranch("mybranch").
CreateNCommits(10)
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.Log.ShowGraph = "never"
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
SelectedLine(Contains("CI commit 10")).
Press(keys.Commits.ViewBisectOptions).
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as bad`)).Confirm()
}).
NavigateToLine(Contains("CI commit 01")).
Press(keys.Commits.ViewBisectOptions).
Tap(func() {
t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm()
}).
// bisect has auto-selected commit 05 as current
Lines(
Contains("CI commit 10").Contains("<-- bad"),
Contains("CI commit 09").DoesNotContain("<--"),
Contains("CI commit 08").DoesNotContain("<--"),
Contains("CI commit 07").DoesNotContain("<--"),
Contains("CI commit 06").DoesNotContain("<--"),
Contains("CI commit 05").Contains("<-- current").IsSelected(),
Contains("CI commit 04").DoesNotContain("<--"),
Contains("CI commit 03").DoesNotContain("<--"),
Contains("CI commit 02").DoesNotContain("<--"),
Contains("CI commit 01").Contains("<-- good"),
).
// now checkout commit 08 manually
NavigateToLine(Contains("CI commit 08")).
Press(keys.Commits.CheckoutCommit).
Tap(func() {
t.ExpectPopup().Menu().
Title(Contains("Checkout branch or commit")).
Select(MatchesRegexp("Checkout commit .* as detached head")).
Confirm()
})

// after checkout, go back to commits panel and verify the current
// marker moved to commit 08
t.Views().Commits().
Focus().
Lines(
Contains("CI commit 10").Contains("<-- bad"),
Contains("CI commit 09").DoesNotContain("<--"),
Contains("CI commit 08").Contains("<-- current"),
Contains("CI commit 07").DoesNotContain("<--"),
Contains("CI commit 06").DoesNotContain("<--"),
Contains("CI commit 05").DoesNotContain("<--"),
Contains("CI commit 04").DoesNotContain("<--"),
Contains("CI commit 03").DoesNotContain("<--"),
Contains("CI commit 02").DoesNotContain("<--"),
Contains("CI commit 01").Contains("<-- good"),
).
// mark the manually checked-out commit as good via bisect menu
NavigateToLine(Contains("CI commit 08")).
Press(keys.Commits.ViewBisectOptions).
Tap(func() {
// the bisect menu should show the HEAD commit (08) as current
t.ExpectPopup().Menu().Title(Equals("Bisect")).
Select(MatchesRegexp(`Mark .* as good`)).Confirm()
}).
// after marking 08 as good, bisect narrows the range
SelectedLine(Contains("<-- current"))
},
})
1 change: 1 addition & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

var tests = []*components.IntegrationTest{
bisect.Basic,
bisect.CheckoutDuringBisect,
bisect.ChooseTerms,
bisect.FromOtherBranch,
bisect.Skip,
Expand Down