From 61d5b1646d518f1ae41d63706fb057cd890b93c5 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 25 Aug 2025 19:05:18 +0200 Subject: [PATCH 1/4] Cleanup: bring stash loader test up to date It didn't actually test how it parses the unix time stamps, and the test only succeeded because the code is lenient against it missing. --- .../git_commands/stash_loader_test.go | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/commands/git_commands/stash_loader_test.go b/pkg/commands/git_commands/stash_loader_test.go index 99e6a57d256..3b0bf3eea11 100644 --- a/pkg/commands/git_commands/stash_loader_test.go +++ b/pkg/commands/git_commands/stash_loader_test.go @@ -1,7 +1,9 @@ package git_commands import ( + "fmt" "testing" + "time" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" @@ -17,6 +19,9 @@ func TestGetStashEntries(t *testing.T) { expectedStashEntries []*models.StashEntry } + hoursAgo := time.Now().Unix() - 3*3600 - 1800 + daysAgo := time.Now().Unix() - 3*3600*24 - 3600*12 + scenarios := []scenario{ { "No stash entries found", @@ -30,17 +35,20 @@ func TestGetStashEntries(t *testing.T) { "", oscommands.NewFakeRunner(t). ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, - "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00", - nil, - ), + fmt.Sprintf("%d|WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00%d|WIP on master: bb86a3f update github template\x00", + hoursAgo, + daysAgo, + ), nil), []*models.StashEntry{ { - Index: 0, - Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", + Index: 0, + Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", + Recency: "3h", }, { - Index: 1, - Name: "WIP on master: bb86a3f update github template", + Index: 1, + Name: "WIP on master: bb86a3f update github template", + Recency: "3d", }, }, }, From ee81a8e3c23ab7e7573d30f94b49a02235c4aa9b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 25 Aug 2025 13:49:34 +0200 Subject: [PATCH 2/4] Add hash field to models.StashEntry --- pkg/commands/git_commands/stash_loader.go | 10 ++++++++-- pkg/commands/git_commands/stash_loader_test.go | 8 +++++--- pkg/commands/models/stash_entry.go | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/commands/git_commands/stash_loader.go b/pkg/commands/git_commands/stash_loader.go index 67c3fb3170f..a1e16ae4725 100644 --- a/pkg/commands/git_commands/stash_loader.go +++ b/pkg/commands/git_commands/stash_loader.go @@ -32,7 +32,7 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry return self.getUnfilteredStashEntries() } - cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%gd:%ct|%gs").ToArgv() + cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%gd:%H|%ct|%gs").ToArgv() rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() if err != nil { return self.getUnfilteredStashEntries() @@ -66,7 +66,7 @@ outer: } func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { - cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%ct|%gs").ToArgv() + cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%H|%ct|%gs").ToArgv() rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput() return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry { @@ -80,6 +80,12 @@ func stashEntryFromLine(line string, index int) *models.StashEntry { Index: index, } + hash, line, ok := strings.Cut(line, "|") + if !ok { + return model + } + model.Hash = hash + tstr, msg, ok := strings.Cut(line, "|") if !ok { return model diff --git a/pkg/commands/git_commands/stash_loader_test.go b/pkg/commands/git_commands/stash_loader_test.go index 3b0bf3eea11..7ed000589d4 100644 --- a/pkg/commands/git_commands/stash_loader_test.go +++ b/pkg/commands/git_commands/stash_loader_test.go @@ -27,15 +27,15 @@ func TestGetStashEntries(t *testing.T) { "No stash entries found", "", oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, "", nil), + ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"}, "", nil), []*models.StashEntry{}, }, { "Several stash entries found", "", oscommands.NewFakeRunner(t). - ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, - fmt.Sprintf("%d|WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00%d|WIP on master: bb86a3f update github template\x00", + ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"}, + fmt.Sprintf("fa1afe1|%d|WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00deadbeef|%d|WIP on master: bb86a3f update github template\x00", hoursAgo, daysAgo, ), nil), @@ -44,11 +44,13 @@ func TestGetStashEntries(t *testing.T) { Index: 0, Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", Recency: "3h", + Hash: "fa1afe1", }, { Index: 1, Name: "WIP on master: bb86a3f update github template", Recency: "3d", + Hash: "deadbeef", }, }, }, diff --git a/pkg/commands/models/stash_entry.go b/pkg/commands/models/stash_entry.go index 92cb06a0759..caf20b1d6c8 100644 --- a/pkg/commands/models/stash_entry.go +++ b/pkg/commands/models/stash_entry.go @@ -7,6 +7,7 @@ type StashEntry struct { Index int Recency string Name string + Hash string } func (s *StashEntry) FullRefName() string { From 5cb80b8635556011ad62df586e1757f0cd84e675 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 25 Aug 2025 19:12:38 +0200 Subject: [PATCH 3/4] Fix command log for stash commands With the exception of Rename Stash, they would all just log "Stash". --- pkg/gui/controllers/stash_controller.go | 6 +++--- pkg/i18n/english.go | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go index 6af07438623..40d309e0b47 100644 --- a/pkg/gui/controllers/stash_controller.go +++ b/pkg/gui/controllers/stash_controller.go @@ -112,7 +112,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err Title: self.c.Tr.StashApply, Prompt: self.c.Tr.SureApplyStashEntry, HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Stash) + self.c.LogAction(self.c.Tr.Actions.ApplyStash) err := self.c.Git().Stash.Apply(stashEntry.Index) self.postStashRefresh() if err != nil { @@ -128,7 +128,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error { pop := func() error { - self.c.LogAction(self.c.Tr.Actions.Stash) + self.c.LogAction(self.c.Tr.Actions.PopStash) err := self.c.Git().Stash.Pop(stashEntry.Index) self.postStashRefresh() if err != nil { @@ -160,7 +160,7 @@ func (self *StashController) handleStashDrop(stashEntries []*models.StashEntry) Title: self.c.Tr.StashDrop, Prompt: self.c.Tr.SureDropStashEntry, HandleConfirm: func() error { - self.c.LogAction(self.c.Tr.Actions.Stash) + self.c.LogAction(self.c.Tr.Actions.DropStash) for i := len(stashEntries) - 1; i >= 0; i-- { err := self.c.Git().Stash.Drop(stashEntries[i].Index) self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 3f57e6ff79f..ea9f805a277 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1011,6 +1011,9 @@ type Actions struct { UpdateRemote string ApplyPatch string Stash string + PopStash string + ApplyStash string + DropStash string RenameStash string RemoveSubmodule string ResetSubmodule string @@ -2050,6 +2053,9 @@ func EnglishTranslationSet() *TranslationSet { UpdateRemote: "Update remote", ApplyPatch: "Apply patch", Stash: "Stash", + PopStash: "Pop stash", + ApplyStash: "Apply stash", + DropStash: "Drop stash", RenameStash: "Rename stash", RemoveSubmodule: "Remove submodule", ResetSubmodule: "Reset submodule", From 438f5c0eda4ac75743bf275b0a9f040e7434ac41 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 25 Aug 2025 14:48:40 +0200 Subject: [PATCH 4/4] Log hashes when dropping/popping stashes If you dropped/popped a stash accidentally, the logged hash can help recover it more easily. --- pkg/gui/controllers/stash_controller.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go index 40d309e0b47..889bc087293 100644 --- a/pkg/gui/controllers/stash_controller.go +++ b/pkg/gui/controllers/stash_controller.go @@ -129,6 +129,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error { pop := func() error { self.c.LogAction(self.c.Tr.Actions.PopStash) + self.c.LogCommand("Popping stash "+stashEntry.Hash, false) err := self.c.Git().Stash.Pop(stashEntry.Index) self.postStashRefresh() if err != nil { @@ -162,6 +163,7 @@ func (self *StashController) handleStashDrop(stashEntries []*models.StashEntry) HandleConfirm: func() error { self.c.LogAction(self.c.Tr.Actions.DropStash) for i := len(stashEntries) - 1; i >= 0; i-- { + self.c.LogCommand("Dropping stash "+stashEntries[i].Hash, false) err := self.c.Git().Stash.Drop(stashEntries[i].Index) self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}}) if err != nil {