Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to find base commit for creating a fixup #3105

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ keybinding:
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
amendLastCommit: 'A'
commitChangesWithEditor: 'C'
findBaseCommitForFixup: '<c-f>'
confirmDiscard: 'x'
ignoreFile: 'i'
refreshFiles: 'r'
Expand Down
64 changes: 64 additions & 0 deletions docs/Fixup_Commits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Fixup Commits
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great writeup


## Background

There's this common scenario that you have a PR in review, the reviewer is
requesting some changes, and you make those changes and would normally simply
squash them into the original commit that they came from. If you do that,
however, there's no way for the reviewer to see what you changed. You could just
make a separate commit with those changes at the end of the branch, but this is
not ideal because it results in a git history that is not very clean.

To help with this, git has a concept of fixup commits: you do make a separate
commit, but the subject of this commit is the string "fixup! " followed by the
original commit subject. This both tells the reviewer what's going on (you are
making a change that you later will squash into the designated commit), and it
provides an easy way to actually perform this squash operation when you are
ready to do that (before merging).

## Creating fixup commits

You could of course create fixup commits manually by typing in the commit
message with the prefix yourself. But lazygit has an easier way to do that:
in the Commits view, select the commit that you want to create a fixup for, and
press shift-F (for "Create fixup commit for this commit"). This automatically
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.

## Squashing fixup commits

When you're ready to merge the branch and want to squash all these fixup commits
that you created, that's very easy to do: select the first commit of your branch
and hit shift-S (for "Squash all 'fixup!' commits above selected commit
(autosquash)"). Boom, done.

## Finding the commit to create a fixup for

When you are making changes to code that you changed earlier in a long branch,
it can be tedious to find the commit to squash it into. Lazygit has a command to
help you with this, too: in the Files view, press ctrl-f to select the right
base commit in the Commits view automatically. From there, you can either press
shift-F to create a fixup commit for it, or shift-A to amend your changes into
the commit if you haven't published your branch yet.

This command works in many cases, and when it does it almost feels like magic,
but it's important to understand its limitations because it doesn't always work.
The way it works is that it looks at the deleted lines of your current
modifications, blames them to find out which commit those lines come from, and
if they all come from the same commit, it selects it. So here are cases where it
doesn't work:

- Your current diff has only added lines, but no deleted lines. In this case
there's no way for lazygit to know which commit you want to add them to.
- The deleted lines belong to multiple different commits. In this case you can
help lazygit by staging a set of files or hunks that all belong to the same
commit; if some changes are staged, the ctrl-f command works only on those.
- The found commit is already on master; in this case, lazygit refuses to select
it, because it doesn't make sense to create fixups for it, let alone amend to
it.

To sum it up: the command works great if you are changing code again that you
changed or added earlier in the same branch. This is a common enough case to
make the command useful.
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: Commit changes without pre-commit hook
<kbd>A</kbd>: Amend last commit
<kbd>C</kbd>: Commit changes using git editor
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: Edit file
<kbd>o</kbd>: Open file
<kbd>i</kbd>: Ignore or exclude file
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
<kbd>A</kbd>: 最新のコミットにamend
<kbd>C</kbd>: gitエディタを使用して変更をコミット
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: ファイルを編集
<kbd>o</kbd>: ファイルを開く
<kbd>i</kbd>: ファイルをignore
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: Commit changes without pre-commit hook
<kbd>A</kbd>: 마지맛 커밋 수정
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: 파일 편집
<kbd>o</kbd>: 파일 닫기
<kbd>i</kbd>: Ignore file
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
<kbd>A</kbd>: Wijzig laatste commit
<kbd>C</kbd>: Commit veranderingen met de git editor
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: Verander bestand
<kbd>o</kbd>: Open bestand
<kbd>i</kbd>: Ignore or exclude file
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
<kbd>A</kbd>: Zmień ostatni commit
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: Edytuj plik
<kbd>o</kbd>: Otwórz plik
<kbd>i</kbd>: Ignore or exclude file
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ _Связки клавиш_
<kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
<kbd>A</kbd>: Правка последнего коммита
<kbd>C</kbd>: Сохранить изменения с помощью редактора git
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: Редактировать файл
<kbd>o</kbd>: Открыть файл
<kbd>i</kbd>: Игнорировать или исключить файл
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>w</kbd>: 提交更改而无需预先提交钩子
<kbd>A</kbd>: 修补最后一次提交
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: 编辑文件
<kbd>o</kbd>: 打开文件
<kbd>i</kbd>: 忽略文件
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<kbd>w</kbd>: 沒有預提交 hook 就提交更改
<kbd>A</kbd>: 修正上次提交
<kbd>C</kbd>: 使用 git 編輯器提交變更
<kbd>&lt;c-f&gt;</kbd>: Find base commit for fixup
<kbd>e</kbd>: 編輯檔案
<kbd>o</kbd>: 開啟檔案
<kbd>i</kbd>: 忽略或排除檔案
Expand Down
3 changes: 3 additions & 0 deletions pkg/commands/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

// GitCommand is our main git interface
type GitCommand struct {
Blame *git_commands.BlameCommands
Branch *git_commands.BranchCommands
Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands
Expand Down Expand Up @@ -160,6 +161,7 @@ func NewGitCommandAux(
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon)
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
blameCommands := git_commands.NewBlameCommands(gitCommon)

branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
Expand All @@ -171,6 +173,7 @@ func NewGitCommandAux(
tagLoader := git_commands.NewTagLoader(cmn, cmd)

return &GitCommand{
Blame: blameCommands,
Branch: branchCommands,
Commit: commitCommands,
Config: configCommands,
Expand Down
33 changes: 33 additions & 0 deletions pkg/commands/git_commands/blame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package git_commands

import (
"fmt"
)

type BlameCommands struct {
*GitCommon
}

func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
return &BlameCommands{
GitCommon: gitCommon,
}
}

// Blame a range of lines. For each line, output the hash of the commit where
// the line last changed, then a space, then a description of the commit (author
// and date), another space, and then the line. For example:
//
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 11) func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 12) return &BlameCommands{
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 13) GitCommon: gitCommon,
func (self *BlameCommands) BlameLineRange(filename string, commit string, firstLine int, numLines int) (string, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment giving an example output from the function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 17616fb

cmdArgs := NewGitCmd("blame").
Arg("-l").
Arg(fmt.Sprintf("-L%d,+%d", firstLine, numLines)).
Arg(commit).
Arg("--").
Arg(filename)

return self.cmd.New(cmdArgs.ToArgv()).RunWithOutput()
}
14 changes: 14 additions & 0 deletions pkg/commands/git_commands/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, e
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}

// Example output:
//
// cd50c79ae Preserve the commit message correctly even if the description has blank lines
// 3ebba5f32 Add test demonstrating a bug with preserving the commit message
// 9a423c388 Remove unused function
func (self *CommitCommands) GetShasAndCommitMessagesFirstLine(shas []string) (string, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 28373b0

cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%h %s").
Arg(shas...).
ToArgv()

return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}

func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--oneline").
Expand Down
8 changes: 8 additions & 0 deletions pkg/commands/git_commands/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,11 @@ func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands
Arg("--", opts.Filepath).
ToArgv())
}

func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) oscommands.ICmdObj {
return self.cmd.New(
NewGitCmd("diff-index").
Arg("--submodule", "--no-ext-diff", "--no-color", "--patch").
Arg(diffArgs...).ToArgv(),
)
}
2 changes: 2 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ type KeybindingFilesConfig struct {
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
AmendLastCommit string `yaml:"amendLastCommit"`
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
FindBaseCommitForFixup string `yaml:"findBaseCommitForFixup"`
ConfirmDiscard string `yaml:"confirmDiscard"`
IgnoreFile string `yaml:"ignoreFile"`
RefreshFiles string `yaml:"refreshFiles"`
Expand Down Expand Up @@ -762,6 +763,7 @@ func GetDefaultConfig() *UserConfig {
CommitChangesWithoutHook: "w",
AmendLastCommit: "A",
CommitChangesWithEditor: "C",
FindBaseCommitForFixup: "<c-f>",
IgnoreFile: "i",
RefreshFiles: "r",
StashAllChanges: "s",
Expand Down
1 change: 1 addition & 0 deletions pkg/gui/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func (gui *Gui) resetHelpersAndControllers() {
CherryPick: cherryPickHelper,
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
FixupHelper: helpers.NewFixupHelper(helperCommon),
Commits: commitsHelper,
Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper,
Expand Down
6 changes: 6 additions & 0 deletions pkg/gui/controllers/files_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
Description: self.c.Tr.CommitChangesWithEditor,
},
{
Key: opts.GetKey(opts.Config.Files.FindBaseCommitForFixup),
Handler: self.c.Helpers().FixupHelper.HandleFindBaseCommitForFixupPress,
Description: self.c.Tr.FindBaseCommitForFixup,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a tooltip: I can imagine seeing this description and not really knowing what it was talking about.

It could be something simple like 'Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <link to docs>'.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 330151c

Tooltip: self.c.Tr.FindBaseCommitForFixupTooltip,
},
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelectedFileNode(self.edit),
Expand Down
Loading
Loading