mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-27 12:32:37 +02:00
Make it easy to create "amend!" commits
To support this, we turn the confirmation prompt of the "Create fixup commit" command into a menu; creating a fixup commit is the first entry, so that "shift-F, enter" behaves the same as before. But there are additional entries for creating "amend!" commits, either with or without file changes. These make it easy to reword commit messages of existing commits.
This commit is contained in:
parent
c92e9d9bdc
commit
150cc70698
@ -27,6 +27,19 @@ creates a commit with the appropriate subject line.
|
||||
Don't confuse this with the lowercase "f" command ("Fixup commit"); that one
|
||||
squashes the selected commit into its parent, this is not what we want here.
|
||||
|
||||
## Creating amend commits
|
||||
|
||||
There's a special type of fixup commit that uses "amend!" instead of "fixup!" in
|
||||
the commit message subject; in addition to fixing up the original commit with
|
||||
changes it allows you to also (or only) change the commit message of the
|
||||
original commit. The menu that appears when pressing shift-F has options for
|
||||
both of these; they bring up a commit message panel similar to when you reword a
|
||||
commit, but then create the "amend!" commit containing the new message. Note
|
||||
that in that panel you only type the new message as you want it to be
|
||||
eventually; lazygit then takes care of formatting the "amend!" commit
|
||||
appropriately for you (with the subject of your new message moving into the body
|
||||
of the "amend!" commit).
|
||||
|
||||
## Squashing fixup commits
|
||||
|
||||
When you're ready to merge the branch and want to squash all these fixup commits
|
||||
|
@ -297,6 +297,21 @@ func (self *CommitCommands) CreateFixupCommit(sha string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// CreateAmendCommit creates a commit that changes the commit message of a previous commit
|
||||
func (self *CommitCommands) CreateAmendCommit(originalSubject, newSubject, newDescription string, includeFileChanges bool) error {
|
||||
description := newSubject
|
||||
if newDescription != "" {
|
||||
description += "\n\n" + newDescription
|
||||
}
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
Arg("-m", "amend! "+originalSubject).
|
||||
Arg("-m", description).
|
||||
ArgIf(!includeFileChanges, "--only", "--allow-empty").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// a value of 0 means the head commit, 1 is the parent commit, etc
|
||||
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
|
||||
cmdArgs := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H").
|
||||
|
@ -180,6 +180,57 @@ func TestCommitCreateFixupCommit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitCreateAmendCommit(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
originalSubject string
|
||||
newSubject string
|
||||
newDescription string
|
||||
includeFileChanges bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "subject only",
|
||||
originalSubject: "original subject",
|
||||
newSubject: "new subject",
|
||||
newDescription: "",
|
||||
includeFileChanges: true,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject"}, "", nil),
|
||||
},
|
||||
{
|
||||
testName: "subject and description",
|
||||
originalSubject: "original subject",
|
||||
newSubject: "new subject",
|
||||
newDescription: "new description",
|
||||
includeFileChanges: true,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject\n\nnew description"}, "", nil),
|
||||
},
|
||||
{
|
||||
testName: "without file changes",
|
||||
originalSubject: "original subject",
|
||||
newSubject: "new subject",
|
||||
newDescription: "",
|
||||
includeFileChanges: false,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject", "--only", "--allow-empty"}, "", nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
err := instance.CreateAmendCommit(s.originalSubject, s.newSubject, s.newDescription, s.includeFileChanges)
|
||||
assert.NoError(t, err)
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitShowCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
|
@ -832,17 +832,21 @@ func (self *LocalCommitsController) afterRevertCommit() error {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) error {
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.SureCreateFixupCommit,
|
||||
map[string]string{
|
||||
"commit": commit.Sha,
|
||||
},
|
||||
)
|
||||
var disabledReasonWhenFilesAreNeeded *types.DisabledReason
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
disabledReasonWhenFilesAreNeeded = &types.DisabledReason{
|
||||
Text: self.c.Tr.NoFilesStagedTitle,
|
||||
ShowErrorInPanel: true,
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.CreateFixupCommit,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.FixupMenu_Fixup,
|
||||
Key: 'f',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
|
||||
@ -855,7 +859,60 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
})
|
||||
})
|
||||
},
|
||||
DisabledReason: disabledReasonWhenFilesAreNeeded,
|
||||
Tooltip: self.c.Tr.FixupMenu_FixupTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FixupMenu_AmendWithChanges,
|
||||
Key: 'a',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.createAmendCommit(commit, true)
|
||||
})
|
||||
},
|
||||
DisabledReason: disabledReasonWhenFilesAreNeeded,
|
||||
Tooltip: self.c.Tr.FixupMenu_AmendWithChangesTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FixupMenu_AmendWithoutChanges,
|
||||
Key: 'r',
|
||||
OnPress: func() error { return self.createAmendCommit(commit, false) },
|
||||
Tooltip: self.c.Tr.FixupMenu_AmendWithoutChangesTooltip,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
|
||||
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
|
||||
}
|
||||
originalSubject, _, _ := strings.Cut(commitMessage, "\n")
|
||||
return self.c.Helpers().Commits.OpenCommitMessagePanel(
|
||||
&helpers.OpenCommitMessagePanelOpts{
|
||||
CommitIndex: self.context().GetSelectedLineIdx(),
|
||||
InitialMessage: commitMessage,
|
||||
SummaryTitle: self.c.Tr.CreateAmendCommit,
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: false,
|
||||
OnConfirm: func(summary string, description string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
|
||||
if err := self.c.Git().Commit.CreateAmendCommit(originalSubject, summary, description, includeFileChanges); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.context().MoveSelectedLine(1)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
},
|
||||
OnSwitchToEditor: nil,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashFixupCommits() error {
|
||||
|
@ -254,7 +254,6 @@ func chineseTranslationSet() TranslationSet {
|
||||
CreateFixupCommit: `为此提交创建修正`,
|
||||
SquashAboveCommitsTooltip: `压缩在所选提交之上的所有“fixup!”提交(自动压缩)`,
|
||||
CreateFixupCommitTooltip: `创建修正提交`,
|
||||
SureCreateFixupCommit: `您确定要对 {{.commit}} 创建修正提交吗?`,
|
||||
ExecuteCustomCommand: "执行自定义命令",
|
||||
CustomCommand: "自定义命令:",
|
||||
CommitChangesWithoutHook: "提交更改而无需预先提交钩子",
|
||||
|
@ -217,7 +217,6 @@ func dutchTranslationSet() TranslationSet {
|
||||
CreateFixupCommit: `Creëer fixup commit`,
|
||||
SquashAboveCommitsTooltip: `Squash bovenstaande commits`,
|
||||
CreateFixupCommitTooltip: `Creëer fixup commit`,
|
||||
SureCreateFixupCommit: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`,
|
||||
ExecuteCustomCommand: "Voer aangepaste commando uit",
|
||||
CustomCommand: "Aangepaste commando:",
|
||||
CommitChangesWithoutHook: "Commit veranderingen zonder pre-commit hook",
|
||||
|
@ -392,6 +392,13 @@ type TranslationSet struct {
|
||||
FileResetOptionsTooltip string
|
||||
CreateFixupCommit string
|
||||
CreateFixupCommitTooltip string
|
||||
CreateAmendCommit string
|
||||
FixupMenu_Fixup string
|
||||
FixupMenu_FixupTooltip string
|
||||
FixupMenu_AmendWithChanges string
|
||||
FixupMenu_AmendWithChangesTooltip string
|
||||
FixupMenu_AmendWithoutChanges string
|
||||
FixupMenu_AmendWithoutChangesTooltip string
|
||||
SquashAboveCommitsTooltip string
|
||||
SquashCommitsAboveSelectedTooltip string
|
||||
SquashCommitsInCurrentBranchTooltip string
|
||||
@ -399,7 +406,6 @@ type TranslationSet struct {
|
||||
SquashCommitsInCurrentBranch string
|
||||
SquashCommitsAboveSelectedCommit string
|
||||
CannotSquashCommitsInCurrentBranch string
|
||||
SureCreateFixupCommit string
|
||||
ExecuteCustomCommand string
|
||||
ExecuteCustomCommandTooltip string
|
||||
CustomCommand string
|
||||
@ -1356,6 +1362,13 @@ func EnglishTranslationSet() TranslationSet {
|
||||
FixupTooltip: "Meld the selected commit into the commit below it. Similar to fixup, but the selected commit's message will be discarded.",
|
||||
CreateFixupCommit: "Create fixup commit",
|
||||
CreateFixupCommitTooltip: "Create 'fixup!' commit for the selected commit. Later on, you can press `{{.squashAbove}}` on this same commit to apply all above fixup commits.",
|
||||
CreateAmendCommit: `Create "amend!" commit`,
|
||||
FixupMenu_Fixup: "fixup! commit",
|
||||
FixupMenu_FixupTooltip: "Lets you fixup another commit and keep the original commit's message.",
|
||||
FixupMenu_AmendWithChanges: "amend! commit with changes",
|
||||
FixupMenu_AmendWithChangesTooltip: "Lets you fixup another commit and also change its commit message.",
|
||||
FixupMenu_AmendWithoutChanges: "amend! commit without changes (pure reword)",
|
||||
FixupMenu_AmendWithoutChangesTooltip: "Lets you change the commit message of another commit without changing its content.",
|
||||
SquashAboveCommits: "Apply fixup commits",
|
||||
SquashAboveCommitsTooltip: `Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash).`,
|
||||
SquashCommitsAboveSelectedTooltip: `Squash all 'fixup!' commits above the selected commit (autosquash).`,
|
||||
@ -1363,7 +1376,6 @@ func EnglishTranslationSet() TranslationSet {
|
||||
SquashCommitsInCurrentBranch: "In current branch",
|
||||
SquashCommitsAboveSelectedCommit: "Above the selected commit",
|
||||
CannotSquashCommitsInCurrentBranch: "Cannot squash commits in current branch: the HEAD commit is a merge commit or is present on the main branch.",
|
||||
SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||
ExecuteCustomCommand: "Execute custom command",
|
||||
ExecuteCustomCommandTooltip: "Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands.",
|
||||
CustomCommand: "Custom command:",
|
||||
|
@ -264,7 +264,6 @@ func japaneseTranslationSet() TranslationSet {
|
||||
// LcSquashAboveCommits: `squash all 'fixup!' commits above selected commit (autosquash)`,
|
||||
// SquashAboveCommits: `Squash all 'fixup!' commits above selected commit (autosquash)`,
|
||||
CreateFixupCommit: `Fixupコミットを作成`,
|
||||
SureCreateFixupCommit: `{{.commit}} に対する fixup! コミットを作成します。よろしいですか?`,
|
||||
ExecuteCustomCommand: "カスタムコマンドを実行",
|
||||
CustomCommand: "カスタムコマンド:",
|
||||
CommitChangesWithoutHook: "pre-commitフックを実行せずに変更をコミット",
|
||||
|
@ -258,7 +258,6 @@ func koreanTranslationSet() TranslationSet {
|
||||
CreateFixupCommitTooltip: `Create fixup commit for this commit`,
|
||||
SquashAboveCommitsTooltip: `Squash all 'fixup!' commits above selected commit (autosquash)`,
|
||||
CreateFixupCommit: `Create fixup commit`,
|
||||
SureCreateFixupCommit: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||
ExecuteCustomCommand: "Execute custom command",
|
||||
CustomCommand: "Custom command:",
|
||||
CommitChangesWithoutHook: "Commit changes without pre-commit hook",
|
||||
|
@ -390,7 +390,6 @@ func polishTranslationSet() TranslationSet {
|
||||
SquashCommitsAboveSelectedCommit: "Powyżej wybranego commita",
|
||||
CannotSquashCommitsInCurrentBranch: "Nie można scalić commitów w bieżącej gałęzi: commit HEAD jest commit merge lub jest obecny na głównej gałęzi.",
|
||||
CreateFixupCommit: `Utwórz commit fixup`,
|
||||
SureCreateFixupCommit: `Czy na pewno chcesz utworzyć commit fixup! dla commita {{.commit}}?`,
|
||||
ExecuteCustomCommand: "Wykonaj polecenie niestandardowe",
|
||||
ExecuteCustomCommandTooltip: "Wyświetl monit, w którym możesz wprowadzić polecenie powłoki do wykonania. Nie należy mylić z wcześniej skonfigurowanymi poleceniami niestandardowymi.",
|
||||
CustomCommand: "Polecenie niestandardowe:",
|
||||
|
@ -311,7 +311,6 @@ func RussianTranslationSet() TranslationSet {
|
||||
CreateFixupCommitTooltip: `Создать fixup коммит для этого коммита`,
|
||||
SquashAboveCommitsTooltip: `Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)`,
|
||||
CreateFixupCommit: `Создать fixup коммит`,
|
||||
SureCreateFixupCommit: `Вы уверены, что хотите создать fixup! коммит для коммита {{.commit}}?`,
|
||||
ExecuteCustomCommand: "Выполнить пользовательскую команду",
|
||||
CustomCommand: "Пользовательская Команда:",
|
||||
CommitChangesWithoutHook: "Закоммитить изменения без предварительного хука коммита",
|
||||
|
@ -340,7 +340,6 @@ func traditionalChineseTranslationSet() TranslationSet {
|
||||
SquashAboveCommits: "壓縮上方所有「fixup」提交(自動壓縮)",
|
||||
SquashAboveCommitsTooltip: "是否壓縮上方 {{.commit}} 所有「fixup」提交?",
|
||||
CreateFixupCommit: "建立修復提交",
|
||||
SureCreateFixupCommit: "你確定要為提交{{.commit}}建立fixup!提交?",
|
||||
ExecuteCustomCommand: "執行自訂命令",
|
||||
CustomCommand: "自訂命令:",
|
||||
CommitChangesWithoutHook: "沒有預提交 hook 就提交更改",
|
||||
|
60
pkg/integration/tests/commit/create_amend_commit.go
Normal file
60
pkg/integration/tests/commit/create_amend_commit.go
Normal file
@ -0,0 +1,60 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var CreateAmendCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Create an amend commit for an existing commit",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(3).
|
||||
CreateFileAndAdd("fixup-file", "fixup content")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
NavigateToLine(Contains("commit 02")).
|
||||
Press(keys.Commits.CreateFixupCommit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Create fixup commit")).
|
||||
Select(Contains("amend! commit with changes")).
|
||||
Confirm()
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Content(Equals("commit 02")).
|
||||
Type(" amended").Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("amend! commit 02"),
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
if t.Git().Version().IsAtLeast(2, 32, 0) { // Support for auto-squashing "amend!" commits was added in git 2.32.0
|
||||
t.Views().Commits().
|
||||
Press(keys.Commits.SquashAboveCommits).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Apply fixup commits")).
|
||||
Select(Contains("Above the selected commit")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02 amended").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
@ -26,9 +26,9 @@ var SquashFixupsAbove = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NavigateToLine(Contains("commit 02")).
|
||||
Press(keys.Commits.CreateFixupCommit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Create fixup commit")).
|
||||
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
||||
Select(Contains("fixup! commit")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
|
@ -25,9 +25,9 @@ var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NavigateToLine(Contains("commit 01")).
|
||||
Press(keys.Commits.CreateFixupCommit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Create fixup commit")).
|
||||
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
||||
Select(Contains("fixup! commit")).
|
||||
Confirm()
|
||||
}).
|
||||
NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")).
|
||||
|
@ -72,6 +72,7 @@ var tests = []*components.IntegrationTest{
|
||||
commit.CommitSwitchToEditor,
|
||||
commit.CommitWipWithPrefix,
|
||||
commit.CommitWithPrefix,
|
||||
commit.CreateAmendCommit,
|
||||
commit.CreateTag,
|
||||
commit.DiscardOldFileChanges,
|
||||
commit.FindBaseCommitForFixup,
|
||||
|
Loading…
x
Reference in New Issue
Block a user