summaryrefslogtreecommitdiffstats
path: root/pkg/gui
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/gui')
-rw-r--r--pkg/gui/context/menu_context.go1
-rw-r--r--pkg/gui/context/sub_commits_context.go2
-rw-r--r--pkg/gui/context/suggestions_context.go11
-rw-r--r--pkg/gui/controllers/branches_controller.go67
-rw-r--r--pkg/gui/controllers/confirmation_controller.go10
-rw-r--r--pkg/gui/controllers/custom_command_action.go27
-rw-r--r--pkg/gui/controllers/custom_patch_options_menu_action.go5
-rw-r--r--pkg/gui/controllers/diffing_menu_action.go1
-rw-r--r--pkg/gui/controllers/helpers/branches_helper.go6
-rw-r--r--pkg/gui/controllers/helpers/confirmation_helper.go17
-rw-r--r--pkg/gui/controllers/helpers/fixup_helper.go192
-rw-r--r--pkg/gui/controllers/helpers/fixup_helper_test.go137
-rw-r--r--pkg/gui/controllers/helpers/merge_and_rebase_helper.go61
-rw-r--r--pkg/gui/controllers/helpers/refresh_helper.go37
-rw-r--r--pkg/gui/controllers/helpers/sub_commits_helper.go1
-rw-r--r--pkg/gui/controllers/helpers/window_arrangement_helper.go13
-rw-r--r--pkg/gui/controllers/helpers/window_arrangement_helper_test.go81
-rw-r--r--pkg/gui/controllers/helpers/working_tree_helper.go6
-rw-r--r--pkg/gui/controllers/local_commits_controller.go52
-rw-r--r--pkg/gui/controllers/status_controller.go21
-rw-r--r--pkg/gui/controllers/suggestions_controller.go28
-rw-r--r--pkg/gui/controllers/sync_controller.go38
-rw-r--r--pkg/gui/controllers/undo_controller.go2
-rw-r--r--pkg/gui/filetree/build_tree_test.go6
-rw-r--r--pkg/gui/filetree/file_node_test.go2
-rw-r--r--pkg/gui/filetree/file_tree_test.go1
-rw-r--r--pkg/gui/gui.go14
-rw-r--r--pkg/gui/gui_driver.go5
-rw-r--r--pkg/gui/mergeconflicts/find_conflicts.go2
-rw-r--r--pkg/gui/mergeconflicts/state_test.go1
-rw-r--r--pkg/gui/patch_exploring/focus_test.go1
-rw-r--r--pkg/gui/popup/popup_handler.go16
-rw-r--r--pkg/gui/presentation/branches.go79
-rw-r--r--pkg/gui/presentation/branches_test.go101
-rw-r--r--pkg/gui/presentation/commits.go134
-rw-r--r--pkg/gui/presentation/commits_test.go182
-rw-r--r--pkg/gui/presentation/files_test.go2
-rw-r--r--pkg/gui/presentation/graph/graph.go1
-rw-r--r--pkg/gui/presentation/graph/graph_test.go2
-rw-r--r--pkg/gui/presentation/status.go8
-rw-r--r--pkg/gui/services/custom_commands/handler_creator.go10
-rw-r--r--pkg/gui/services/custom_commands/menu_generator_test.go1
-rw-r--r--pkg/gui/services/custom_commands/models.go100
-rw-r--r--pkg/gui/services/custom_commands/resolver.go1
-rw-r--r--pkg/gui/services/custom_commands/session_state_loader.go182
-rw-r--r--pkg/gui/style/style_test.go2
-rw-r--r--pkg/gui/test_mode.go2
-rw-r--r--pkg/gui/types/common.go24
48 files changed, 1380 insertions, 315 deletions
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index e4c3d7280..a6b0e77cb 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -105,7 +105,6 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
menuItems := self.FilteredListViewModel.GetItems()
var prevSection *types.MenuSection = nil
for i, menuItem := range menuItems {
- menuItem := menuItem
if menuItem.Section != nil && menuItem.Section != prevSection {
if prevSection != nil {
result = append(result, &NonModelItem{
diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go
index b37be8667..f540dba87 100644
--- a/pkg/gui/context/sub_commits_context.go
+++ b/pkg/gui/context/sub_commits_context.go
@@ -75,7 +75,7 @@ func NewSubCommitsContext(
endIdx,
// Don't show the graph in the left/right view; we'd like to, but
// it's too complicated:
- shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
+ shouldShowGraph(c),
git_commands.NewNullBisectInfo(),
false,
)
diff --git a/pkg/gui/context/suggestions_context.go b/pkg/gui/context/suggestions_context.go
index 59908fe5e..c741cc769 100644
--- a/pkg/gui/context/suggestions_context.go
+++ b/pkg/gui/context/suggestions_context.go
@@ -14,10 +14,13 @@ type SuggestionsContext struct {
}
type SuggestionsContextState struct {
- Suggestions []*types.Suggestion
- OnConfirm func() error
- OnClose func() error
- AsyncHandler *tasks.AsyncHandler
+ Suggestions []*types.Suggestion
+ OnConfirm func() error
+ OnClose func() error
+ OnDeleteSuggestion func() error
+ AsyncHandler *tasks.AsyncHandler
+
+ AllowEditSuggestion bool
// FindSuggestions will take a string that the user has typed into a prompt
// and return a slice of suggestions which match that string.
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index b08ddd0cd..62eda703e 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -100,14 +100,13 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
DisplayOnScreen: true,
},
{
- Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
- Handler: opts.Guards.OutsideFilterMode(self.rebase),
- GetDisabledReason: self.require(
- self.singleItemSelected(self.notRebasingOntoSelf),
- ),
- Description: self.c.Tr.RebaseBranch,
- Tooltip: self.c.Tr.RebaseBranchTooltip,
- DisplayOnScreen: true,
+ Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
+ Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)),
+ GetDisabledReason: self.require(self.singleItemSelected()),
+ Description: self.c.Tr.RebaseBranch,
+ Tooltip: self.c.Tr.RebaseBranchTooltip,
+ OpensMenu: true,
+ DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
@@ -205,6 +204,40 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
},
}
+ var disabledReason *types.DisabledReason
+ baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(selectedBranch, self.c.Model().MainBranches)
+ if err != nil {
+ return err
+ }
+ if baseBranch == "" {
+ baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
+ disabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
+ }
+ shortBaseBranchName := helpers.ShortBranchName(baseBranch)
+ label := utils.ResolvePlaceholderString(
+ self.c.Tr.ViewDivergenceFromBaseBranch,
+ map[string]string{"baseBranch": shortBaseBranchName},
+ )
+ viewDivergenceFromBaseBranchItem := &types.MenuItem{
+ LabelColumns: []string{label},
+ Key: 'b',
+ OnPress: func() error {
+ branch := self.context().GetSelected()
+ if branch == nil {
+ return nil
+ }
+
+ return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
+ Ref: branch,
+ TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), shortBaseBranchName),
+ RefToShowDivergenceFrom: baseBranch,
+ Context: self.context(),
+ ShowBranchHeads: false,
+ })
+ },
+ DisabledReason: disabledReason,
+ }
+
unsetUpstreamItem := &types.MenuItem{
LabelColumns: []string{self.c.Tr.UnsetUpstream},
OnPress: func() error {
@@ -312,6 +345,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
options := []*types.MenuItem{
viewDivergenceItem,
+ viewDivergenceFromBaseBranchItem,
unsetUpstreamItem,
setUpstreamItem,
upstreamResetItem,
@@ -598,19 +632,8 @@ func (self *BranchesController) merge() error {
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
}
-func (self *BranchesController) rebase() error {
- selectedBranchName := self.context().GetSelected().Name
- return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
-}
-
-func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *types.DisabledReason {
- selectedBranchName := branch.Name
- checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
- if selectedBranchName == checkedOutBranch {
- return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
- }
-
- return nil
+func (self *BranchesController) rebase(branch *models.Branch) error {
+ return self.c.Helpers().MergeAndRebase.RebaseOntoRef(branch.Name)
}
func (self *BranchesController) fastForward(branch *models.Branch) error {
@@ -620,7 +643,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
if !branch.RemoteBranchStoredLocally() {
return errors.New(self.c.Tr.FwdNoLocalUpstream)
}
- if branch.HasCommitsToPush() {
+ if branch.IsAheadForPull() {
return errors.New(self.c.Tr.FwdCommitsToPush)
}
diff --git a/pkg/gui/controllers/confirmation_controller.go b/pkg/gui/controllers/confirmation_controller.go
index aa5617fa8..45bd16a45 100644
--- a/pkg/gui/controllers/confirmation_controller.go
+++ b/pkg/gui/controllers/confirmation_controller.go
@@ -1,6 +1,8 @@
package controllers
import (
+ "fmt"
+
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -39,6 +41,14 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error {
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
+ subtitle := ""
+ if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
+ // We assume that whenever things are deletable, they
+ // are also editable, so we show both keybindings
+ subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
+ self.c.UserConfig.Keybinding.Universal.Remove, self.c.UserConfig.Keybinding.Universal.Edit)
+ }
+ self.c.Views().Suggestions.Subtitle = subtitle
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
}
return nil
diff --git a/pkg/gui/controllers/custom_command_action.go b/pkg/gui/controllers/custom_command_action.go
index f4de3218e..39777e70a 100644
--- a/pkg/gui/controllers/custom_command_action.go
+++ b/pkg/gui/controllers/custom_command_action.go
@@ -1,6 +1,7 @@
package controllers
import (
+ "slices"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
@@ -17,6 +18,7 @@ func (self *CustomCommandAction) Call() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.CustomCommand,
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
+ AllowEditSuggestion: true,
HandleConfirm: func(command string) error {
if self.shouldSaveCommand(command) {
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
@@ -32,13 +34,34 @@ func (self *CustomCommandAction) Call() error {
self.c.OS().Cmd.NewShell(command),
)
},
+ HandleDeleteSuggestion: func(index int) error {
+ // index is the index in the _filtered_ list of suggestions, so we
+ // need to map it back to the full list. There's no really good way
+ // to do this, but fortunately we keep the items in the
+ // CustomCommandsHistory unique, which allows us to simply search
+ // for it by string.
+ item := self.c.Contexts().Suggestions.GetItems()[index].Value
+ fullIndex := lo.IndexOf(self.c.GetAppState().CustomCommandsHistory, item)
+ if fullIndex == -1 {
+ // Should never happen, but better be safe
+ return nil
+ }
+
+ self.c.GetAppState().CustomCommandsHistory = slices.Delete(
+ self.c.GetAppState().CustomCommandsHistory, fullIndex, fullIndex+1)
+ self.c.SaveAppStateAndLogError()
+ self.c.Contexts().Suggestions.RefreshSuggestions()
+ return nil
+ },
})
}
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
- history := self.c.GetAppState().CustomCommandsHistory
+ return func(input string) []*types.Suggestion {
+ history := self.c.GetAppState().CustomCommandsHistory
- return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())
+ return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())(input)
+ }
}
// this mimics the shell functionality `ignorespace`
diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go
index 2d57f0ac0..f5099ae2e 100644
--- a/pkg/gui/controllers/custom_patch_options_menu_action.go
+++ b/pkg/gui/controllers/custom_patch_options_menu_action.go
@@ -217,7 +217,10 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
_ = self.c.Helpers().Commits.PopCommitMessageContexts()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex, summary, description)
- return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
+ if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
+ return err
+ }
+ return self.c.PushContext(self.c.Contexts().LocalCommits)
})
},
},
diff --git a/pkg/gui/controllers/diffing_menu_action.go b/pkg/gui/controllers/diffing_menu_action.go
index ec4006b1a..be44e471b 100644
--- a/pkg/gui/controllers/diffing_menu_action.go
+++ b/pkg/gui/controllers/diffing_menu_action.go
@@ -17,7 +17,6 @@ func (self *DiffingMenuAction) Call() error {
menuItems := []*types.MenuItem{}
for _, name := range names {
- name := name
menuItems = append(menuItems, []*types.MenuItem{
{
Label: fmt.Sprintf("%s %s", self.c.Tr.Diff, name),
diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go
index d9d6dbd9a..c07d1d72b 100644
--- a/pkg/gui/controllers/helpers/branches_helper.go
+++ b/pkg/gui/controllers/helpers/branches_helper.go
@@ -1,6 +1,8 @@
package helpers
import (
+ "strings"
+
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -44,3 +46,7 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
},
})
}
+
+func ShortBranchName(fullBranchName string) string {
+ return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
+}
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go
index 8c1265c15..1e60b5f08 100644
--- a/pkg/gui/controllers/helpers/confirmation_helper.go
+++ b/pkg/gui/controllers/helpers/confirmation_helper.go
@@ -159,6 +159,7 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
+ suggestionsView.Subtitle = ""
}
self.ResizeConfirmationPanel()
@@ -223,6 +224,8 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
return err
}
+ self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion
+
self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
return self.c.PushContext(self.c.Contexts().Confirmation)
@@ -270,10 +273,20 @@ func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts
onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)
+ onDeleteSuggestion := func() error {
+ if opts.HandleDeleteSuggestion == nil {
+ return nil
+ }
+
+ idx := self.c.Contexts().Suggestions.GetSelectedLineIdx()
+ return opts.HandleDeleteSuggestion(idx)
+ }
+
self.c.Contexts().Confirmation.State.OnConfirm = onConfirm
self.c.Contexts().Confirmation.State.OnClose = onClose
self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm
self.c.Contexts().Suggestions.State.OnClose = onClose
+ self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion
return nil
}
@@ -284,6 +297,7 @@ func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() {
self.c.Contexts().Confirmation.State.OnClose = noop
self.c.Contexts().Suggestions.State.OnConfirm = noop
self.c.Contexts().Suggestions.State.OnClose = noop
+ self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop
}
func (self *ConfirmationHelper) getSelectedSuggestionValue() string {
@@ -354,7 +368,8 @@ func (self *ConfirmationHelper) resizeMenu() {
if selectedItem != nil {
tooltip = self.TooltipForMenuItem(selectedItem)
}
- tooltipHeight := getMessageHeight(true, tooltip, panelWidth) + 2 // plus 2 for the frame
+ contentWidth := panelWidth - 2 // minus 2 for the frame
+ tooltipHeight := getMessageHeight(true, tooltip, contentWidth) + 2 // plus 2 for the frame
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
}
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
index b60d48f4f..7177398bb 100644
--- a/pkg/gui/controllers/helpers/fixup_helper.go
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -5,13 +5,13 @@ import (
"fmt"
"regexp"
"strings"
- "sync"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
+ "golang.org/x/sync/errgroup"
)
type FixupHelper struct {
@@ -26,7 +26,17 @@ func NewFixupHelper(
}
}
-type deletedLineInfo struct {
+// hunk describes the lines in a diff hunk. Used for two distinct cases:
+//
+// - when the hunk contains some deleted lines. Because we're diffing with a
+// context of 0, all deleted lines always come first, and then the added lines
+// (if any). In this case, numLines is only the number of deleted lines, we
+// ignore whether there are also some added lines in the hunk, as this is not
+// relevant for our algorithm.
+//
+// - when the hunk contains only added lines, in which case (obviously) numLines
+// is the number of added lines.
+type hunk struct {
filename string
startLineIdx int
numLines int
@@ -37,17 +47,25 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
if err != nil {
return err
}
- if diff == "" {
+
+ deletedLineHunks, addedLineHunks := parseDiff(diff)
+
+ 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)
}
- deletedLineInfos, hasHunksWithOnlyAddedLines := self.parseDiff(diff)
- if len(deletedLineInfos) == 0 {
- return errors.New(self.c.Tr.NoDeletedLinesInDiff)
+ if err != nil {
+ return err
}
- hashes := self.blameDeletedLines(deletedLineInfos)
-
if len(hashes) == 0 {
// This should never happen
return errors.New(self.c.Tr.NoBaseCommitsFound)
@@ -63,9 +81,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return fmt.Errorf("%s\n\n%s", message, subjects)
}
- commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
- return commit.Hash == hashes[0]
- })
+ commit, index, ok := self.findCommit(hashes[0])
if !ok {
commits := self.c.Model().Commits
if commits[len(commits)-1].Status == models.StatusMerged {
@@ -93,7 +109,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return self.c.PushContext(self.c.Contexts().LocalCommits)
}
- if hasHunksWithOnlyAddedLines {
+ if warnAboutAddedLines {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.FindBaseCommitForFixup,
Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning,
@@ -122,29 +138,38 @@ func (self *FixupHelper) getDiff() (string, bool, error) {
return diff, hasStagedChanges, err
}
-func (self *FixupHelper) parseDiff(diff string) ([]*deletedLineInfo, bool) {
+// Parse the diff output into hunks, and return two lists of hunks: the first
+// are ones that contain deleted lines, the second are ones that contain only
+// added lines.
+func parseDiff(diff string) ([]*hunk, []*hunk) {
lines := strings.Split(strings.TrimSuffix(diff, "\n"), "\n")
- deletedLineInfos := []*deletedLineInfo{}
- hasHunksWithOnlyAddedLines := false
+ deletedLineHunks := []*hunk{}
+ addedLineHunks := []*hunk{}
hunkHeaderRegexp := regexp.MustCompile(`@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@`)
var filename string
- var currentLineInfo *deletedLineInfo
+ var currentHunk *hunk
+ numDeletedLines := 0
+ numAddedLines := 0
finishHunk := func() {
- if currentLineInfo != nil {
- if currentLineInfo.numLines > 0 {
- deletedLineInfos = append(deletedLineInfos, currentLineInfo)
- } else {
- hasHunksWithOnlyAddedLines = true
+ if currentHunk != nil {
+ if numDeletedLines > 0 {
+ currentHunk.numLines = numDeletedLines
+ deletedLineHunks = append(deletedLineHunks, currentHunk)
+ } else if numAddedLines > 0 {
+ currentHunk.numLines = numAddedLines
+ addedLineHunks = append(addedLineHunks, currentHunk)
}
}
+ numDeletedLines = 0
+ numAddedLines = 0
}
for _, line := range lines {
if strings.HasPrefix(line, "diff --git") {
finishHunk()
- currentLineInfo = nil
+ currentHunk = nil
} else if strings.HasPrefix(line, "--- ") {
// For some reason, the line ends with a tab character if the file
// name contains spaces
@@ -153,40 +178,42 @@ func (self *FixupHelper) parseDiff(diff string) ([]*deletedLineInfo, bool) {
finishHunk()
match := hunkHeaderRegexp.FindStringSubmatch(line)
startIdx := utils.MustConvertToInt(match[1])
- currentLineInfo = &deletedLineInfo{filename, startIdx, 0}
- } else if currentLineInfo != nil && line[0] == '-' {
- currentLineInfo.numLines++
+ currentHunk = &hunk{filename, startIdx, 0}
+ } else if currentHunk != nil && line[0] == '-' {
+ numDeletedLines++
+ } else if currentHunk != nil && line[0] == '+' {
+ numAddedLines++
}
}
finishHunk()
- return deletedLineInfos, hasHunksWithOnlyAddedLines
+ return deletedLineHunks, addedLineHunks
}
// returns the list of commit hashes that introduced the lines which have now been deleted
-func (self *FixupHelper) blameDeletedLines(deletedLineInfos []*deletedLineInfo) []string {
- var wg sync.WaitGroup
+func (self *FixupHelper) blameDeletedLines(deletedLineHunks []*hunk) ([]string, error) {
+ errg := errgroup.Group{}
hashChan := make(chan string)
- for _, info := range deletedLineInfos {
- wg.Add(1)
- go func(info *deletedLineInfo) {
- defer wg.Done()
-
- blameOutput, err := self.c.Git().Blame.BlameLineRange(info.filename, "HEAD", info.startLineIdx, info.numLines)
+ for _, h := range deletedLineHunks {
+ errg.Go(func() error {
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(h.filename, "HEAD", h.startLineIdx, h.numLines)
if err != nil {
- self.c.Log.Errorf("Error blaming file '%s': %v", info.filename, err)
- return
+ return err
}
blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n")
for _, line := range blameLines {
hashChan <- strings.Split(line, " ")[0]
}
- }(info)
+ return nil
+ })
}
go func() {
- wg.Wait()
+ // 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(hashChan)
}()
@@ -195,5 +222,92 @@ func (self *FixupHelper) blameDeletedLines(deletedLineInfos []*deletedLineInfo)
result.Add(hash)
}
- return result.ToSlice()
+ 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.T