summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2024-05-19 20:11:50 +0200
committerStefan Haller <stefan@haller-berlin.de>2024-06-01 08:31:18 +0200
commitdbdabb34f34458a55c1be9d88b0350dcf36f10db (patch)
treebbe92f1e0bc8143a51928b517dce48600472c837
parentc1a65546ad7c881e8e34aabafe11161569be3dba (diff)
Make "Find base commit for fixup" work with hunks with only added lines
To understand what this does and why, read the design document that I'm about to add in the next commit.
-rw-r--r--pkg/gui/controllers/helpers/fixup_helper.go100
-rw-r--r--pkg/i18n/english.go2
-rw-r--r--pkg/i18n/polish.go1
-rw-r--r--pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go84
-rw-r--r--pkg/integration/tests/test_list.go1
5 files changed, 178 insertions, 10 deletions
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
index e816f425d..7177398bb 100644
--- a/pkg/gui/controllers/helpers/fixup_helper.go
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -47,16 +47,21 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
if err != nil {
return err
}
- if diff == "" {
- return errors.New(self.c.Tr.NoChangedFiles)
- }
deletedLineHunks, addedLineHunks := parseDiff(diff)
- if len(deletedLineHunks) == 0 {
- return errors.New(self.c.Tr.NoDeletedLinesInDiff)
+
+ var hashes []string
+ warnAboutAddedLines := false
+
+ if len(deletedLineHunks) > 0 {
+ hashes, err = self.blameDeletedLines(deletedLineHunks)
+ warnAboutAddedLines = len(addedLineHunks) > 0
+ } else if len(addedLineHunks) > 0 {
+ hashes, err = self.blameAddedLines(addedLineHunks)
+ } else {
+ return errors.New(self.c.Tr.NoChangedFiles)
}
- hashes, err := self.blameDeletedLines(deletedLineHunks)
if err != nil {
return err
}
@@ -104,7 +109,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return self.c.PushContext(self.c.Contexts().LocalCommits)
}
- if len(addedLineHunks) > 0 {
+ if warnAboutAddedLines {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.FindBaseCommitForFixup,
Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning,
@@ -220,6 +225,87 @@ func (self *FixupHelper) blameDeletedLines(deletedLineHunks []*hunk) ([]string,
return result.ToSlice(), errg.Wait()
}
+func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, error) {
+ errg := errgroup.Group{}
+ hashesChan := make(chan []string)
+
+ for _, h := range addedLineHunks {
+ errg.Go(func() error {
+ result := make([]string, 0, 2)
+
+ appendBlamedLine := func(blameOutput string) {
+ blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n")
+ if len(blameLines) == 1 {
+ result = append(result, strings.Split(blameLines[0], " ")[0])
+ }
+ }
+
+ // Blame the line before this hunk, if there is one
+ if h.startLineIdx > 0 {
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, 1)
+ if err != nil {
+ return err
+ }
+ appendBlamedLine(blameOutput)
+ }
+
+ // Blame the line after this hunk. We don't know how many lines the
+ // file has, so we can't check if there is a line after the hunk;
+ // let the error tell us.
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx+1, 1)
+ if err != nil {
+ // If this fails, we're probably at the end of the file (we
+ // could have checked this beforehand, but it's expensive). If
+ // there was a line before this hunk, this is fine, we'll just
+ // return that one; if not, the hunk encompasses the entire
+ // file, and we can't blame the lines before and after the hunk.
+ // This is an error.
+ if h.startLineIdx == 0 {
+ return errors.New("Entire file") // TODO i18n
+ }
+ } else {
+ appendBlamedLine(blameOutput)
+ }
+
+ hashesChan <- result
+ return nil
+ })
+ }
+
+ go func() {
+ // We don't care about the error here, we'll check it later (in the
+ // return statement below). Here we only wait for all the goroutines to
+ // finish so that we can close the channel.
+ _ = errg.Wait()
+ close(hashesChan)
+ }()
+
+ result := set.New[string]()
+ for hashes := range hashesChan {
+ if len(hashes) == 1 {
+ result.Add(hashes[0])
+ } else if len(hashes) > 1 {
+ if hashes[0] == hashes[1] {
+ result.Add(hashes[0])
+ } else {
+ _, index1, ok1 := self.findCommit(hashes[0])
+ _, index2, ok2 := self.findCommit(hashes[1])
+ if ok1 && ok2 {
+ result.Add(lo.Ternary(index1 < index2, hashes[0], hashes[1]))
+ } else if ok1 {
+ result.Add(hashes[0])
+ } else if ok2 {
+ result.Add(hashes[1])
+ } else {
+ return nil, errors.New(self.c.Tr.NoBaseCommitsFound)
+ }
+ }
+ }
+ }
+
+ return result.ToSlice(), errg.Wait()
+}
+
func (self *FixupHelper) findCommit(hash string) (*models.Commit, int, bool) {
return lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
return commit.Hash == hash
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index fe088f8e0..a9c8fcec5 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -42,7 +42,6 @@ type TranslationSet struct {
CommitChangesWithEditor string
FindBaseCommitForFixup string
FindBaseCommitForFixupTooltip string
- NoDeletedLinesInDiff string
NoBaseCommitsFound string
MultipleBaseCommitsFoundStaged string
MultipleBaseCommitsFoundUnstaged string
@@ -1005,7 +1004,6 @@ func EnglishTranslationSet() TranslationSet {
CommitChangesWithEditor: "Commit changes using git editor",
FindBaseCommitForFixup: "Find base commit for fixup",
FindBaseCommitForFixupTooltip: "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: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md>",
- NoDeletedLinesInDiff: "No deleted lines in diff",
NoBaseCommitsFound: "No base commits found",
MultipleBaseCommitsFoundStaged: "Multiple base commits found. (Try staging fewer changes at once)",
MultipleBaseCommitsFoundUnstaged: "Multiple base commits found. (Try staging some of the changes)",
diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go
index 97a68a326..ae99829aa 100644
--- a/pkg/i18n/polish.go
+++ b/pkg/i18n/polish.go
@@ -33,7 +33,6 @@ func polishTranslationSet() TranslationSet {
CommitChangesWithEditor: "Zatwierdź zmiany używając edytora git",
FindBaseCommitForFixup: "Znajdź bazowy commit do poprawki",
FindBaseCommitForFixupTooltip: "Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md>",
- NoDeletedLinesInDiff: "Brak usuniętych linii w różnicach",
NoBaseCommitsFound: "Nie znaleziono bazowych commitów",
MultipleBaseCommitsFoundStaged: "Znaleziono wiele bazowych commitów. (Spróbuj zatwierdzić mniej zmian naraz)",
MultipleBaseCommitsFoundUnstaged: "Znaleziono wiele bazowych commitów. (Spróbuj zatwierdzić część zmian)",
diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go b/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go
new file mode 100644
index 000000000..281fe72f9
--- /dev/null
+++ b/pkg/integration/tests/commit/find_base_commit_for_fixup_only_added_lines.go
@@ -0,0 +1,84 @@
+package commit
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var FindBaseCommitForFixupOnlyAddedLines = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Finds the base commit to create a fixup for, when all staged hunks have only added lines",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.NewBranch("mybranch").
+ EmptyCommit("1st commit").
+ CreateFileAndAdd("file1", "line A\nline B\nline C\n").
+ Commit("2nd commit").
+ UpdateFileAndAdd("file1", "line A\nline B changed\nline C\n").
+ Commit("3rd commit").
+ CreateFileAndAdd("file2", "line X\nline Y\nline Z\n").
+ Commit("4th commit").
+ UpdateFile("file1", "line A\nline B changed\nline B'\nline C\n").
+ UpdateFile("file2", "line W\nline X\nline Y\nline Z\n")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Commits().
+ Lines(
+ Contains("4th commit"),
+ Contains("3rd commit"),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ )
+
+ // Two changes from different commits: this fails
+ t.Views().Files().
+ Focus().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.ExpectPopup().Alert().
+ Title(Equals("Error")).
+ Content(
+ Contains("Multiple base commits found").
+ Contains("3rd commit").
+ Contains("4th commit"),
+ ).
+ Confirm()
+
+ // Stage only one of the files: this succeeds
+ t.Views().Files().
+ IsFocused().
+ NavigateToLine(Contains("file1")).
+ PressPrimaryAction().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.Views().Commits().
+ IsFocused().
+ Lines(
+ Contains("4th commit"),
+ Contains("3rd commit").IsSelected(),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ ).
+ Press(keys.Commits.AmendToCommit)
+
+ t.ExpectPopup().Confirmation().
+ Title(Equals("Amend commit")).
+ Content(Contains("Are you sure you want to amend this commit with your staged files?")).
+ Confirm()
+
+ // Now only the other file is modified (and unstaged); this works now
+ t.Views().Files().
+ Focus().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.Views().Commits().
+ IsFocused().
+ Lines(
+ Contains("4th commit").IsSelected(),
+ Contains("3rd commit"),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index e1ded611b..d4a093de8 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -79,6 +79,7 @@ var tests = []*components.IntegrationTest{
commit.CreateTag,
commit.DiscardOldFileChanges,
commit.FindBaseCommitForFixup,
+ commit.FindBaseCommitForFixupOnlyAddedLines,
commit.FindBaseCommitForFixupWarningForAddedLines,
commit.Highlight,
commit.History,