diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go index 8f3b4920d..a01ed201c 100644 --- a/pkg/commands/git_commands/status.go +++ b/pkg/commands/git_commands/status.go @@ -73,6 +73,16 @@ func (self *StatusCommands) IsInSquashMerge() (bool, error) { return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "SQUASH_MSG")) } +// SquashMergeMessage returns the message in SQUASH_MSG (see IsInSquashMerge), or +// an empty string if there is no squash merge in progress +func (self *StatusCommands) SquashMergeMessage() string { + content, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "SQUASH_MSG")) + if err != nil { + return "" + } + return strings.TrimSpace(string(content)) +} + func (self *StatusCommands) IsInCherryPick() (bool, error) { hasCherryPickHead, err := self.hasCherryPickHead() if err != nil || hasCherryPickHead { diff --git a/pkg/gui/context/commit_message_context.go b/pkg/gui/context/commit_message_context.go index 7db3a085b..f6db124fe 100644 --- a/pkg/gui/context/commit_message_context.go +++ b/pkg/gui/context/commit_message_context.go @@ -131,6 +131,10 @@ func (self *CommitMessageContext) GetInitialMessage() string { return self.viewModel.initialMessage } +func (self *CommitMessageContext) SetInitialMessage(message string) { + self.viewModel.initialMessage = message +} + func (self *CommitMessageContext) GetHistoryMessage() string { return self.viewModel.historyMessage } @@ -148,7 +152,6 @@ func (self *CommitMessageContext) SetPanelState( summaryTitle string, descriptionTitle string, preserveMessage bool, - initialMessage string, onConfirm func(string, string) error, onSwitchToEditor func(string) error, forceSkipHooks bool, @@ -156,7 +159,6 @@ func (self *CommitMessageContext) SetPanelState( ) { self.viewModel.selectedindex = index self.viewModel.preserveMessage = preserveMessage - self.viewModel.initialMessage = initialMessage self.viewModel.onConfirm = onConfirm self.viewModel.onSwitchToEditor = onSwitchToEditor self.viewModel.forceSkipHooks = forceSkipHooks diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 5e50ba7b2..448c451d3 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -143,10 +143,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp } // When there's no explicit initial message but we're in a preservation - // context, fall back to any previously preserved message. This is stored as - // the "initial" value so the unchanged-message check on close still works - // correctly (in particular, clearing the panel then escaping will notice - // the difference and delete the preserved file). + // context, fall back to any previously preserved message. initialMessage := opts.InitialMessage initialMessageIsPreserved := false if opts.PreserveMessage && initialMessage == "" { @@ -159,7 +156,6 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp opts.SummaryTitle, opts.DescriptionTitle, opts.PreserveMessage, - initialMessage, onConfirm, opts.OnSwitchToEditor, opts.ForceSkipHooks, @@ -172,6 +168,14 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp self.SetMessageAndDescriptionInView(initialMessage) } + // PreserveCommitMessage compares the message in the panel with this when the + // panel closes, so that it preserves the message only if the user changed it + // (in particular, clearing the panel then escaping will notice the + // difference and delete the preserved file). Take it from the panel, because + // a message with a blank line after the summary comes out of the panel + // without it. + self.c.Contexts().CommitMessage.SetInitialMessage(self.JoinCommitMessageAndUnwrappedDescription()) + self.c.Context().Push(self.c.Contexts().CommitMessage, types.OnFocusOpts{}) } diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go index a11830649..b7ce2f431 100644 --- a/pkg/gui/controllers/helpers/working_tree_helper.go +++ b/pkg/gui/controllers/helpers/working_tree_helper.go @@ -237,6 +237,9 @@ func (self *WorkingTreeHelper) HandleCommitPress() error { var initialMessage string preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError() if preservedMessage == "" { + initialMessage = self.c.Git().Status.SquashMergeMessage() + } + if preservedMessage == "" && initialMessage == "" { commitPrefixConfigs := self.commitPrefixConfigsForRepo() for _, commitPrefixConfig := range commitPrefixConfigs { prefixPattern := commitPrefixConfig.Pattern diff --git a/pkg/integration/tests/commit/cancel_commit_during_squash_merge.go b/pkg/integration/tests/commit/cancel_commit_during_squash_merge.go new file mode 100644 index 000000000..a6867acff --- /dev/null +++ b/pkg/integration/tests/commit/cancel_commit_during_squash_merge.go @@ -0,0 +1,35 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CancelCommitDuringSquashMerge = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Open the commit panel during a squash merge started outside lazygit, and close it without changing git's message", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("one"). + NewBranch("feature-branch"). + CreateFileAndAdd("feature-file", "content"). + Commit("feature"). + Checkout("master"). + RunCommand([]string{"git", "merge", "--squash", "feature-branch"}) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + Focus(). + Lines( + Equals("A feature-file"), + ). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + InitialText(Equals("Squashed commit of the following:")). + Cancel() + + t.FileSystem().PathNotPresent(".git/LAZYGIT_PENDING_COMMIT") + }, +}) diff --git a/pkg/integration/tests/commit/commit_during_squash_merge.go b/pkg/integration/tests/commit/commit_during_squash_merge.go new file mode 100644 index 000000000..9edf8d4e7 --- /dev/null +++ b/pkg/integration/tests/commit/commit_during_squash_merge.go @@ -0,0 +1,58 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var CommitDuringSquashMerge = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Commit the changes of a squash merge that was left uncommitted, using the squash merge message", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Git.CommitPrefix = []config.CommitPrefixConfig{{Pattern: "^(\\w+)-branch$", Replace: "[$1] "}} + }, + SetupRepo: func(shell *Shell) { + shell.NewBranch("original-branch"). + EmptyCommit("one"). + NewBranch("feature-branch"). + CreateFileAndAdd("feature-file", "content"). + Commit("feature"). + Checkout("original-branch") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Focus(). + Lines( + Contains("original-branch").IsSelected(), + Contains("feature-branch"), + ). + SelectNextItem(). + Press(keys.Branches.MergeIntoCurrentBranch) + + t.ExpectPopup().Menu(). + Title(Equals("Merge")). + Select(Contains("Squash merge and leave uncommitted")). + Confirm() + + t.Views().Files(). + Focus(). + Lines( + Equals("A feature-file"), + ). + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + Title(Equals("Commit summary")). + InitialText(Equals("Squash merge feature-branch into original-branch")). + Confirm() + + t.Views().Status().Content(Equals("repo → original-branch")) + + t.Views().Commits(). + Lines( + Contains("Squash merge feature-branch into original-branch"), + Contains("one"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 14f97af07..ffde2e1e4 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -137,12 +137,14 @@ var tests = []*components.IntegrationTest{ commit.AmendWhenThereAreConflictsAndCancel, commit.AmendWhenThereAreConflictsAndContinue, commit.AutoWrapMessage, + commit.CancelCommitDuringSquashMerge, commit.Checkout, commit.CheckoutFileFromCommit, commit.CheckoutFileFromRangeSelectionOfCommits, commit.CheckoutFileWithLocalModifications, commit.Commit, commit.CommitAllWithNestedRepository, + commit.CommitDuringSquashMerge, commit.CommitMultiline, commit.CommitSkipHooks, commit.CommitSwitchToEditor,