From 122d6e5f0d458a6b732df3844056cfd4c4966db3 Mon Sep 17 00:00:00 2001 From: Chris McDonnell Date: Sun, 18 May 2025 23:32:18 -0400 Subject: [PATCH 1/4] Add FullRefName to all reset menus --- pkg/gui/controllers/basic_commits_controller.go | 2 +- pkg/gui/controllers/branches_controller.go | 5 +++-- pkg/gui/controllers/files_controller.go | 2 +- pkg/gui/controllers/helpers/refs_helper.go | 6 +++--- pkg/gui/controllers/remote_branches_controller.go | 2 +- pkg/gui/controllers/tags_controller.go | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go index 97a1ffeac..b3a5bf38c 100644 --- a/pkg/gui/controllers/basic_commits_controller.go +++ b/pkg/gui/controllers/basic_commits_controller.go @@ -361,7 +361,7 @@ func (self *BasicCommitsController) newBranch(commit *models.Commit) error { } func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error { - return self.c.Helpers().Refs.CreateGitResetMenu(commit.Hash()) + return self.c.Helpers().Refs.CreateGitResetMenu(commit.Hash(), commit.Hash()) } func (self *BasicCommitsController) checkout(commit *models.Commit) error { diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index b3ce28ab8..4b5318dcb 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -326,7 +326,8 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc LabelColumns: []string{upstreamResetOptions}, OpensMenu: true, OnPress: func() error { - err := self.c.Helpers().Refs.CreateGitResetMenu(upstream) + // We only can invoke this when the remote branch is stored locally, so using the selectedBranch here is fine. + err := self.c.Helpers().Refs.CreateGitResetMenu(upstream, selectedBranch.FullUpstreamRefName()) if err != nil { return err } @@ -686,7 +687,7 @@ func (self *BranchesController) createSortMenu() error { } func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error { - return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name) + return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name, selectedBranch.FullRefName()) } func (self *BranchesController) rename(branch *models.Branch) error { diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 6df28a523..211232b13 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -1144,7 +1144,7 @@ func (self *FilesController) stash() error { } func (self *FilesController) createResetToUpstreamMenu() error { - return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}") + return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}", "@{upstream}") } func (self *FilesController) handleToggleDirCollapsed() error { diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go index b69a68c79..8936a1d61 100644 --- a/pkg/gui/controllers/helpers/refs_helper.go +++ b/pkg/gui/controllers/helpers/refs_helper.go @@ -231,7 +231,7 @@ func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, onSelecte }) } -func (self *RefsHelper) CreateGitResetMenu(ref string) error { +func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error { type strengthWithKey struct { strength string label string @@ -249,7 +249,7 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error { return &types.MenuItem{ LabelColumns: []string{ row.label, - style.FgRed.Sprintf("reset --%s %s", row.strength, ref), + style.FgRed.Sprintf("reset --%s %s", row.strength, name), }, OnPress: func() error { self.c.LogAction("Reset") @@ -261,7 +261,7 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error { }) return self.c.Menu(types.CreateMenuOptions{ - Title: fmt.Sprintf("%s %s", self.c.Tr.ResetTo, ref), + Title: fmt.Sprintf("%s %s", self.c.Tr.ResetTo, name), Items: menuItems, }) } diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go index 39e09a12a..1d59b5547 100644 --- a/pkg/gui/controllers/remote_branches_controller.go +++ b/pkg/gui/controllers/remote_branches_controller.go @@ -158,7 +158,7 @@ func (self *RemoteBranchesController) createSortMenu() error { } func (self *RemoteBranchesController) createResetMenu(selectedBranch *models.RemoteBranch) error { - return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.FullName()) + return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.FullName(), selectedBranch.FullRefName()) } func (self *RemoteBranchesController) setAsUpstream(selectedBranch *models.RemoteBranch) error { diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go index 664a0f279..5ce674d91 100644 --- a/pkg/gui/controllers/tags_controller.go +++ b/pkg/gui/controllers/tags_controller.go @@ -302,7 +302,7 @@ func (self *TagsController) push(tag *models.Tag) error { } func (self *TagsController) createResetMenu(tag *models.Tag) error { - return self.c.Helpers().Refs.CreateGitResetMenu(tag.Name) + return self.c.Helpers().Refs.CreateGitResetMenu(tag.Name, tag.FullRefName()) } func (self *TagsController) create() error { From 737a99b1c8cfc01df9d2ab0cdf3956f4ea2787ce Mon Sep 17 00:00:00 2001 From: Chris McDonnell Date: Sat, 24 May 2025 21:00:31 -0400 Subject: [PATCH 2/4] Add integration tests showing resetting to duplicate named tags and branches --- .../branch/reset_to_duplicate_named_tag.go | 52 +++++++++++++++++++ .../tag/reset_to_duplicate_named_branch.go | 48 +++++++++++++++++ pkg/integration/tests/test_list.go | 2 + 3 files changed, 102 insertions(+) create mode 100644 pkg/integration/tests/branch/reset_to_duplicate_named_tag.go create mode 100644 pkg/integration/tests/tag/reset_to_duplicate_named_branch.go diff --git a/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go b/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go new file mode 100644 index 000000000..d940d9fe1 --- /dev/null +++ b/pkg/integration/tests/branch/reset_to_duplicate_named_tag.go @@ -0,0 +1,52 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ResetToDuplicateNamedTag = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Hard reset to a branch when a tag shares the same name", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.NewBranch("current-branch") + + shell.EmptyCommit("other-branch-tag commit") + shell.CreateLightweightTag("other-branch", "HEAD") + + shell.EmptyCommit("other-branch commit") + shell.NewBranch("other-branch") + + shell.Checkout("current-branch") + shell.EmptyCommit("current-branch commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().Lines( + Contains("current-branch commit"), + Contains("other-branch commit"), + Contains("other-branch-tag commit"), + ) + + t.Views().Branches(). + Focus(). + Lines( + Contains("current-branch").IsSelected(), + Contains("other-branch"), + ). + SelectNextItem(). + Press(keys.Commits.ViewResetOptions) + + t.ExpectPopup().Menu(). + Title(Contains("Reset to other-branch")). + Select(Contains("Hard reset")). + Confirm() + + t.Views().Commits(). + Lines( + Contains("other-branch commit"), + Contains("other-branch-tag commit"), + ) + }, +}) diff --git a/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go b/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go new file mode 100644 index 000000000..096bcff04 --- /dev/null +++ b/pkg/integration/tests/tag/reset_to_duplicate_named_branch.go @@ -0,0 +1,48 @@ +package tag + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ResetToDuplicateNamedBranch = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Hard reset to a tag when a branch shares the same name", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.NewBranch("current-branch") + + shell.EmptyCommit("other-branch-tag commit") + shell.CreateLightweightTag("other-branch", "HEAD") + + shell.EmptyCommit("other-branch commit") + shell.NewBranch("other-branch") + + shell.Checkout("current-branch") + shell.EmptyCommit("current-branch commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().Lines( + Contains("current-branch commit"), + Contains("other-branch commit"), + Contains("other-branch-tag commit"), + ) + + t.Views().Tags(). + Focus(). + Lines( + Contains("other-branch").IsSelected(), + ). + Press(keys.Commits.ViewResetOptions) + + t.ExpectPopup().Menu(). + Title(Contains("Reset to other-branch")). + Select(Contains("Hard reset")). + Confirm() + + t.Views().Commits().Lines( + Contains("other-branch-tag commit"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 9470858a1..e46ff0ca3 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -71,6 +71,7 @@ var tests = []*components.IntegrationTest{ branch.RebaseToUpstream, branch.Rename, branch.Reset, + branch.ResetToDuplicateNamedTag, branch.ResetToUpstream, branch.SelectCommitsOfCurrentBranch, branch.SetUpstream, @@ -406,6 +407,7 @@ var tests = []*components.IntegrationTest{ tag.ForceTagAnnotated, tag.ForceTagLightweight, tag.Reset, + tag.ResetToDuplicateNamedBranch, ui.Accordion, ui.DisableSwitchTabWithPanelJumpKeys, ui.EmptyMenu, From fa238809ae9c4cc8344f9cb4d7b9a1a4e5a3d509 Mon Sep 17 00:00:00 2001 From: Chris McDonnell Date: Tue, 27 May 2025 23:00:34 -0400 Subject: [PATCH 3/4] Use full refname instead of short to prevent disambiguation with tag In the unlikely scenario that you have a remote branch on `origin` called `foo`, and a local tag called `origin/foo`, git changes the behavior of the previous command such that it produces ``` $ git for-each-ref --sort=refname --format=%(refname:short) refs/remotes origin/branch1 remotes/origin/foo ``` with `remotes/` prepended. Presumably this is to disambiguate it from the local tag `origin/foo`. Unfortunately, this breaks the existing behavior of this function, so the remote branch is never shown. By changing the command, we now get ``` $ git for-each-ref --sort=refname --format=%(refname) refs/remotes refs/remotes/origin/branch1 refs/remotes/origin/foo ``` This allows easy parsing based on the `/`, and none of the code outside this function has to change. ---- We previously were not showing remote HEADs for modern git versions based on how they were formatted from "%(refname:short)". We have decided that this is a feature, not a bug, so we are building that into the code here. --- pkg/commands/git_commands/remote_loader.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/commands/git_commands/remote_loader.go b/pkg/commands/git_commands/remote_loader.go index dcf62de97..1b2e33682 100644 --- a/pkg/commands/git_commands/remote_loader.go +++ b/pkg/commands/git_commands/remote_loader.go @@ -96,19 +96,23 @@ func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models. cmdArgs := NewGitCmd("for-each-ref"). Arg(fmt.Sprintf("--sort=%s", sortOrder)). - Arg("--format=%(refname:short)"). + Arg("--format=%(refname)"). Arg("refs/remotes"). ToArgv() err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) { line = strings.TrimSpace(line) - split := strings.SplitN(line, "/", 2) - if len(split) != 2 { + split := strings.SplitN(line, "/", 4) + if len(split) != 4 { + return false, nil + } + remoteName := split[2] + name := split[3] + + if name == "HEAD" { return false, nil } - remoteName := split[0] - name := split[1] _, ok := remoteBranchesByRemoteName[remoteName] if !ok { From 706891e92bf54f659952f60b905bd5b3ac45d9e9 Mon Sep 17 00:00:00 2001 From: Chris McDonnell Date: Tue, 27 May 2025 22:47:04 -0400 Subject: [PATCH 4/4] Add integration test for resetting to upstream branch with duplicate name --- .../reset_to_duplicate_named_upstream.go | 57 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 58 insertions(+) create mode 100644 pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go diff --git a/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go b/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go new file mode 100644 index 000000000..a375e7e75 --- /dev/null +++ b/pkg/integration/tests/branch/reset_to_duplicate_named_upstream.go @@ -0,0 +1,57 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ResetToDuplicateNamedUpstream = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Hard reset the current branch to an upstream branch when there is a competing tag name", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CloneIntoRemote("origin"). + NewBranch("foo"). + EmptyCommit("commit 1"). + PushBranchAndSetUpstream("origin", "foo"). + EmptyCommit("commit 2"). + CreateLightweightTag("origin/foo", "HEAD") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().Lines( + Contains("commit 2"), + Contains("commit 1"), + ) + t.Views().Tags().Focus().Lines(Contains("origin/foo")) + + t.Views().Remotes().Focus(). + Lines(Contains("origin")). + PressEnter() + t.Views().RemoteBranches().IsFocused(). + Lines(Contains("foo")). + Press(keys.Commits.ViewResetOptions) + t.ExpectPopup().Menu(). + Title(Contains("Reset to origin/foo")). + Select(Contains("Hard reset")). + Confirm() + + t.Views().Commits().Lines( + Contains("commit 1"), + ) + + t.Views().Tags().Focus(). + Lines(Contains("origin/foo")). + Press(keys.Commits.ViewResetOptions) + t.ExpectPopup().Menu(). + Title(Contains("Reset to origin/foo")). + Select(Contains("Hard reset")). + Confirm() + + t.Views().Commits().Lines( + Contains("commit 2"), + Contains("commit 1"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index e46ff0ca3..8ef18fe47 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -72,6 +72,7 @@ var tests = []*components.IntegrationTest{ branch.Rename, branch.Reset, branch.ResetToDuplicateNamedTag, + branch.ResetToDuplicateNamedUpstream, branch.ResetToUpstream, branch.SelectCommitsOfCurrentBranch, branch.SetUpstream,