Skip to content

Commit 9d976bc

Browse files
committed
Add confirmation dialogs for destructive workspace reset actions
The workspace reset menu had several destructive actions that executed immediately without confirmation, risking accidental data loss. Add confirmation prompts before: discard unstaged changes (git checkout -- .), discard untracked files (git clean -fd), and discard staged changes (stash and drop). Update the existing integration test to expect the new confirmation dialog. Closes #874
1 parent 4c2c0ce commit 9d976bc

File tree

3 files changed

+56
-24
lines changed

3 files changed

+56
-24
lines changed

pkg/gui/controllers/workspace_reset_controller.go

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,21 @@ func (self *FilesController) createResetMenu() error {
6262
red.Sprint("git checkout -- ."),
6363
},
6464
OnPress: func() error {
65-
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedFileChanges)
66-
if err := self.c.Git().WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
67-
return err
68-
}
65+
self.c.Confirm(types.ConfirmOpts{
66+
Title: self.c.Tr.DiscardAnyUnstagedChanges,
67+
Prompt: self.c.Tr.DiscardAnyUnstagedChangesConfirmation,
68+
HandleConfirm: func() error {
69+
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedFileChanges)
70+
if err := self.c.Git().WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
71+
return err
72+
}
6973

70-
self.c.Refresh(
71-
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
72-
)
74+
self.c.Refresh(
75+
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
76+
)
77+
return nil
78+
},
79+
})
7380
return nil
7481
},
7582
Key: 'u',
@@ -80,14 +87,21 @@ func (self *FilesController) createResetMenu() error {
8087
red.Sprint("git clean -fd"),
8188
},
8289
OnPress: func() error {
83-
self.c.LogAction(self.c.Tr.Actions.RemoveUntrackedFiles)
84-
if err := self.c.Git().WorkingTree.RemoveUntrackedFiles(); err != nil {
85-
return err
86-
}
90+
self.c.Confirm(types.ConfirmOpts{
91+
Title: self.c.Tr.DiscardUntrackedFiles,
92+
Prompt: self.c.Tr.DiscardUntrackedFilesConfirmation,
93+
HandleConfirm: func() error {
94+
self.c.LogAction(self.c.Tr.Actions.RemoveUntrackedFiles)
95+
if err := self.c.Git().WorkingTree.RemoveUntrackedFiles(); err != nil {
96+
return err
97+
}
8798

88-
self.c.Refresh(
89-
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
90-
)
99+
self.c.Refresh(
100+
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
101+
)
102+
return nil
103+
},
104+
})
91105
return nil
92106
},
93107
Key: 'c',
@@ -99,20 +113,27 @@ func (self *FilesController) createResetMenu() error {
99113
},
100114
Tooltip: self.c.Tr.DiscardStagedChangesDescription,
101115
OnPress: func() error {
102-
self.c.LogAction(self.c.Tr.Actions.RemoveStagedFiles)
103116
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() {
104117
return errors.New(self.c.Tr.NoTrackedStagedFilesStash)
105118
}
106-
if err := self.c.Git().Stash.SaveStagedChanges("[lazygit] tmp stash"); err != nil {
107-
return err
108-
}
109-
if err := self.c.Git().Stash.DropNewest(); err != nil {
110-
return err
111-
}
119+
self.c.Confirm(types.ConfirmOpts{
120+
Title: self.c.Tr.DiscardStagedChanges,
121+
Prompt: self.c.Tr.DiscardStagedChangesConfirmation,
122+
HandleConfirm: func() error {
123+
self.c.LogAction(self.c.Tr.Actions.RemoveStagedFiles)
124+
if err := self.c.Git().Stash.SaveStagedChanges("[lazygit] tmp stash"); err != nil {
125+
return err
126+
}
127+
if err := self.c.Git().Stash.DropNewest(); err != nil {
128+
return err
129+
}
112130

113-
self.c.Refresh(
114-
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
115-
)
131+
self.c.Refresh(
132+
types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}},
133+
)
134+
return nil
135+
},
136+
})
116137
return nil
117138
},
118139
Key: 'S',

pkg/i18n/english.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,9 @@ type TranslationSet struct {
825825
NukeDescription string
826826
NukeTreeConfirmation string
827827
DiscardStagedChangesDescription string
828+
DiscardAnyUnstagedChangesConfirmation string
829+
DiscardUntrackedFilesConfirmation string
830+
DiscardStagedChangesConfirmation string
828831
EmptyOutput string
829832
Patch string
830833
CustomPatch string
@@ -1944,6 +1947,9 @@ func EnglishTranslationSet() *TranslationSet {
19441947
NukeDescription: "If you want to make all the changes in the worktree go away, this is the way to do it. If there are dirty submodule changes this will stash those changes in the submodule(s).",
19451948
NukeTreeConfirmation: "Are you sure you want to nuke the working tree? This will discard all changes in the worktree (staged, unstaged and untracked), which is not undoable.",
19461949
DiscardStagedChangesDescription: "This will create a new stash entry containing only staged files and then drop it, so that the working tree is left with only unstaged changes",
1950+
DiscardAnyUnstagedChangesConfirmation: "Are you sure you want to discard all unstaged changes? This is not undoable.",
1951+
DiscardUntrackedFilesConfirmation: "Are you sure you want to discard all untracked files? This is not undoable.",
1952+
DiscardStagedChangesConfirmation: "Are you sure you want to discard all staged changes? This is not undoable.",
19471953
EmptyOutput: "<Empty output>",
19481954
Patch: "Patch",
19491955
CustomPatch: "Custom patch",

pkg/integration/tests/file/discard_staged_changes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ var DiscardStagedChanges = NewIntegrationTest(NewIntegrationTestArgs{
4141

4242
t.ExpectPopup().Menu().Title(Equals("")).Select(Contains("Discard staged changes")).Confirm()
4343

44+
t.ExpectPopup().Confirmation().
45+
Title(Equals("Discard staged changes")).
46+
Content(Equals("Are you sure you want to discard all staged changes? This is not undoable.")).
47+
Confirm()
48+
4449
// staged file has been removed
4550
t.Views().Files().
4651
Lines(

0 commit comments

Comments
 (0)