diff --git a/pkg/commands/git_commands/bisect.go b/pkg/commands/git_commands/bisect.go index 300613e1673..8932b6ad7c0 100644 --- a/pkg/commands/git_commands/bisect.go +++ b/pkg/commands/git_commands/bisect.go @@ -39,6 +39,12 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo { return info } + // Get actual HEAD hash to detect when user manually checked out a different commit + headHash, err := self.cmd.New(NewGitCmd("rev-parse").Arg("HEAD").ToArgv()).DontLog().RunWithOutput() + if err == nil { + info.head = strings.TrimSpace(headHash) + } + startContent, err := os.ReadFile(bisectStartPath) if err != nil { self.Log.Infof("error getting git bisect info: %s", err.Error()) diff --git a/pkg/commands/git_commands/bisect_info.go b/pkg/commands/git_commands/bisect_info.go index c49d6866f70..2a41d289e7e 100644 --- a/pkg/commands/git_commands/bisect_info.go +++ b/pkg/commands/git_commands/bisect_info.go @@ -34,6 +34,9 @@ type BisectInfo struct { // the hash of the commit that's under test current string + + // the actual HEAD hash (may differ from current if user manually checked out a different commit) + head string } type BisectStatus int @@ -63,6 +66,10 @@ func (self *BisectInfo) GetCurrentHash() string { return self.current } +func (self *BisectInfo) GetHeadHash() string { + return self.head +} + func (self *BisectInfo) GetStartHash() string { return self.start } diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index a5799bcb333..fc013dc5c51 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -280,6 +280,8 @@ const ( BisectStatusCandidate // also adding this BisectStatusCurrent + // BisectStatusHead is shown when user manually checks out a different commit during bisect + BisectStatusHead ) func getBisectStatus(index int, commitHash string, bisectInfo *git_commands.BisectInfo, bisectBounds *bisectBounds) BisectStatus { @@ -291,6 +293,13 @@ func getBisectStatus(index int, commitHash string, bisectInfo *git_commands.Bise return BisectStatusCurrent } + // Check if this is the HEAD commit when it differs from the bisect expected commit + // This helps users see where they are when they manually checkout during bisect + headHash := bisectInfo.GetHeadHash() + if headHash != "" && headHash == commitHash && headHash != bisectInfo.GetCurrentHash() { + return BisectStatusHead + } + status, ok := bisectInfo.Status(commitHash) if ok { switch status { @@ -325,8 +334,9 @@ func getBisectStatusText(bisectStatus BisectStatus, bisectInfo *git_commands.Bis case BisectStatusOld: return style.Sprintf("<-- " + bisectInfo.OldTerm()) case BisectStatusCurrent: - // TODO: i18n return style.Sprintf("<-- current") + case BisectStatusHead: + return style.Sprintf("<-- YOU ARE HERE") case BisectStatusSkipped: return style.Sprintf("<-- skipped") case BisectStatusCandidate: @@ -461,6 +471,8 @@ func getBisectStatusColor(status BisectStatus) style.TextStyle { return style.FgYellow case BisectStatusCurrent: return style.FgMagenta + case BisectStatusHead: + return style.FgCyan case BisectStatusCandidate: return style.FgBlue } diff --git a/pkg/integration/tests/bisect/checkout.go b/pkg/integration/tests/bisect/checkout.go new file mode 100644 index 00000000000..29a7351f97c --- /dev/null +++ b/pkg/integration/tests/bisect/checkout.go @@ -0,0 +1,74 @@ +package bisect + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var Checkout = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Test checkout during bisect shows HEAD position correctly", + 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) { + // Start bisect by marking commit 09 as bad and commit 02 as good + t.Views().Commits(). + Focus(). + SelectedLine(Contains("CI commit 10")). + NavigateToLine(Contains("CI commit 09")). + Press(keys.Commits.ViewBisectOptions) + + t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as bad`)).Confirm() + + t.Views().Information().Content(Contains("Bisecting")) + + t.Views().Commits(). + IsFocused(). + NavigateToLine(Contains("CI commit 02")). + Press(keys.Commits.ViewBisectOptions) + + t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`Mark .* as good`)).Confirm() + + // Now we're bisecting, we should be at commit 05 (the midpoint) + t.Views().Commits(). + IsFocused(). + SelectedLine(Contains("CI commit 05").Contains("<-- current")) + + // Try to checkout a different commit (commit 03) + t.Views().Commits(). + NavigateToLine(Contains("CI commit 03")). + PressPrimaryAction() + + // The checkout menu should appear + t.ExpectPopup().Menu(). + Title(Contains("Checkout branch or commit")). + Select(MatchesRegexp("Checkout commit .* as detached head")). + Confirm() + + // After checkout, focus moves to branches panel + t.Views().Branches(). + IsFocused() + + // Now go back to commits panel and verify HEAD position is shown + t.Views().Commits(). + Focus(). + // Commit 05 should still show "<-- current" (bisect expected) + // Commit 03 should show "<-- YOU ARE HERE" (actual HEAD position) + Content( + Contains("<-- bad"). + Contains("<-- current"). + Contains("<-- YOU ARE HERE"). + Contains("<-- good"), + ) + + // Bisect should still be active + t.Views().Information().Content(Contains("Bisecting")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index f19d5aef333..6107deac454 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -36,6 +36,7 @@ import ( var tests = []*components.IntegrationTest{ bisect.Basic, + bisect.Checkout, bisect.ChooseTerms, bisect.FromOtherBranch, bisect.Skip,