mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-29 18:24:21 -05:00
The discard handler cancelled the commit-files range selection from its WithWaitingStatus worker. Bounce it through OnUIThread, keeping it after the successful CheckMergeOrRebase as before.
663 lines
21 KiB
Go
663 lines
21 KiB
Go
package controllers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
|
"github.com/jesseduffield/lazygit/pkg/constants"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type CommitFilesController struct {
|
|
baseController
|
|
*ListControllerTrait[*filetree.CommitFileNode]
|
|
c *ControllerCommon
|
|
}
|
|
|
|
var _ types.IController = &CommitFilesController{}
|
|
|
|
func NewCommitFilesController(
|
|
c *ControllerCommon,
|
|
) *CommitFilesController {
|
|
return &CommitFilesController{
|
|
baseController: baseController{},
|
|
c: c,
|
|
ListControllerTrait: NewListControllerTrait(
|
|
c,
|
|
c.Contexts().CommitFiles,
|
|
c.Contexts().CommitFiles.GetSelected,
|
|
c.Contexts().CommitFiles.GetSelectedItems,
|
|
),
|
|
}
|
|
}
|
|
|
|
func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
|
bindings := []*types.Binding{
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Files.CopyFileInfoToClipboard),
|
|
Handler: self.openCopyMenu,
|
|
Description: self.c.Tr.CopyToClipboardMenu,
|
|
OpensMenu: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.CommitFiles.CheckoutCommitFile),
|
|
Handler: self.withItem(self.checkout),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.Checkout,
|
|
Tooltip: self.c.Tr.CheckoutCommitFileTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Remove),
|
|
Handler: self.withItems(self.discard),
|
|
GetDisabledReason: self.require(self.itemsSelected(self.canDiscardFileChanges)),
|
|
Description: self.c.Tr.Discard,
|
|
Tooltip: self.c.Tr.DiscardOldFileChangeTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.OpenFile),
|
|
Handler: self.withItem(self.open),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.OpenFile,
|
|
Tooltip: self.c.Tr.OpenFileTooltip,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Edit),
|
|
Handler: self.withItems(self.edit),
|
|
GetDisabledReason: self.require(self.itemsSelected(self.canEditFiles)),
|
|
Description: self.c.Tr.Edit,
|
|
Tooltip: self.c.Tr.EditFileTooltip,
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.OpenDiffTool),
|
|
Handler: self.withItem(self.openDiffTool),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.OpenDiffTool,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
|
Handler: self.withItems(self.toggleForPatch),
|
|
GetDisabledReason: self.require(self.itemsSelected()),
|
|
Description: self.c.Tr.ToggleAddToPatch,
|
|
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.ToggleAddToPatchTooltip,
|
|
map[string]string{"doc": constants.Links.Docs.CustomPatchDemo},
|
|
),
|
|
DisplayOnScreen: true,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Files.ToggleStagedAll),
|
|
Handler: self.withItem(self.toggleAllForPatch),
|
|
Description: self.c.Tr.ToggleAllInPatch,
|
|
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.ToggleAllInPatchTooltip,
|
|
map[string]string{"doc": constants.Links.Docs.CustomPatchDemo},
|
|
),
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Universal.GoInto),
|
|
Handler: self.withItem(self.enter),
|
|
GetDisabledReason: self.require(self.singleItemSelected()),
|
|
Description: self.c.Tr.EnterCommitFile,
|
|
Tooltip: self.c.Tr.EnterCommitFileTooltip,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Files.ToggleTreeView),
|
|
Handler: self.toggleTreeView,
|
|
Description: self.c.Tr.ToggleTreeView,
|
|
Tooltip: self.c.Tr.ToggleTreeViewTooltip,
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Files.CollapseAll),
|
|
Handler: self.collapseAll,
|
|
Description: self.c.Tr.CollapseAll,
|
|
Tooltip: self.c.Tr.CollapseAllTooltip,
|
|
GetDisabledReason: self.require(self.isInTreeMode),
|
|
},
|
|
{
|
|
Keys: opts.GetKeys(opts.Config.Files.ExpandAll),
|
|
Handler: self.expandAll,
|
|
Description: self.c.Tr.ExpandAll,
|
|
Tooltip: self.c.Tr.ExpandAllTooltip,
|
|
GetDisabledReason: self.require(self.isInTreeMode),
|
|
},
|
|
}
|
|
|
|
return bindings
|
|
}
|
|
|
|
func (self *CommitFilesController) context() *context.CommitFilesContext {
|
|
return self.c.Contexts().CommitFiles
|
|
}
|
|
|
|
func (self *CommitFilesController) GetOnClick() func(opts gocui.ViewMouseBindingOpts) error {
|
|
return func(opts gocui.ViewMouseBindingOpts) error {
|
|
clickedIdx := self.context().GetSelectedLineIdx()
|
|
node := self.context().CommitFileTreeViewModel.Get(clickedIdx)
|
|
if node == nil || node.File != nil {
|
|
return nil
|
|
}
|
|
|
|
// The arrow is at column visualDepth*2 (after indentation of 2 spaces per level).
|
|
// Only treat clicks on the arrow and the trailing space as arrow clicks.
|
|
visualDepth := self.context().CommitFileTreeViewModel.GetVisualDepth(clickedIdx)
|
|
arrowStartCol := visualDepth * 2
|
|
arrowEndCol := arrowStartCol + 1
|
|
if opts.X < arrowStartCol || opts.X > arrowEndCol {
|
|
return nil
|
|
}
|
|
|
|
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetInternalPath())
|
|
self.c.PostRefreshUpdate(self.context())
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (self *CommitFilesController) GetOnRenderToMain() func() {
|
|
return func() {
|
|
node := self.context().GetSelected()
|
|
if node == nil {
|
|
return
|
|
}
|
|
|
|
from, to := self.context().GetFromAndToForDiff()
|
|
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
|
|
|
paths := self.pathsForDiff(node)
|
|
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, paths, false)
|
|
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
|
|
|
self.c.RenderToMainViews(types.RefreshMainOpts{
|
|
Pair: self.c.MainViewPairs().Normal,
|
|
Main: &types.ViewUpdateOpts{
|
|
Title: self.c.Tr.Patch,
|
|
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
|
|
Task: task,
|
|
},
|
|
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
|
})
|
|
}
|
|
}
|
|
|
|
func (self *CommitFilesController) copyDiffToClipboard(paths []string, toastMessage string) error {
|
|
from, to := self.context().GetFromAndToForDiff()
|
|
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
|
|
|
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, paths, true)
|
|
diff, err := cmdObj.RunWithOutput()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := self.c.OS().CopyToClipboard(diff); err != nil {
|
|
return err
|
|
}
|
|
self.c.Toast(toastMessage)
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) copyFileContentToClipboard(path string) error {
|
|
_, to := self.context().GetFromAndToForDiff()
|
|
cmdObj := self.c.Git().Commit.ShowFileContentCmdObj(to, path)
|
|
diff, err := cmdObj.RunWithOutput()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return self.c.OS().CopyToClipboard(diff)
|
|
}
|
|
|
|
func (self *CommitFilesController) openCopyMenu() error {
|
|
node := self.context().GetSelected()
|
|
|
|
copyNameItem := &types.MenuItem{
|
|
Label: self.c.Tr.CopyFileName,
|
|
OnPress: func() error {
|
|
if err := self.c.OS().CopyToClipboard(node.Name()); err != nil {
|
|
return err
|
|
}
|
|
self.c.Toast(self.c.Tr.FileNameCopiedToast)
|
|
return nil
|
|
},
|
|
DisabledReason: self.require(self.singleItemSelected())(),
|
|
Keys: menuKey('n'),
|
|
}
|
|
copyRelativePathItem := &types.MenuItem{
|
|
Label: self.c.Tr.CopyRelativeFilePath,
|
|
OnPress: func() error {
|
|
if err := self.c.OS().CopyToClipboard(node.GetPath()); err != nil {
|
|
return err
|
|
}
|
|
self.c.Toast(self.c.Tr.FilePathCopiedToast)
|
|
return nil
|
|
},
|
|
DisabledReason: self.require(self.singleItemSelected())(),
|
|
Keys: menuKey('p'),
|
|
}
|
|
copyAbsolutePathItem := &types.MenuItem{
|
|
Label: self.c.Tr.CopyAbsoluteFilePath,
|
|
OnPress: func() error {
|
|
absPath, err := filepath.Abs(node.GetPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := self.c.OS().CopyToClipboard(absPath); err != nil {
|
|
return err
|
|
}
|
|
self.c.Toast(self.c.Tr.FilePathCopiedToast)
|
|
return nil
|
|
},
|
|
DisabledReason: self.require(self.singleItemSelected())(),
|
|
Keys: menuKey('P'),
|
|
}
|
|
copyFileDiffItem := &types.MenuItem{
|
|
Label: self.c.Tr.CopySelectedDiff,
|
|
OnPress: func() error {
|
|
return self.copyDiffToClipboard(self.pathsForDiff(node), self.c.Tr.FileDiffCopiedToast)
|
|
},
|
|
DisabledReason: self.require(self.singleItemSelected())(),
|
|
Keys: menuKey('s'),
|
|
}
|
|
copyAllDiff := &types.MenuItem{
|
|
Label: self.c.Tr.CopyAllFilesDiff,
|
|
OnPress: func() error {
|
|
return self.copyDiffToClipboard([]string{"."}, self.c.Tr.AllFilesDiffCopiedToast)
|
|
},
|
|
DisabledReason: self.require(self.itemsSelected())(),
|
|
Keys: menuKey('a'),
|
|
}
|
|
copyFileContentItem := &types.MenuItem{
|
|
Label: self.c.Tr.CopyFileContent,
|
|
OnPress: func() error {
|
|
if err := self.copyFileContentToClipboard(node.GetPath()); err != nil {
|
|
return err
|
|
}
|
|
self.c.Toast(self.c.Tr.FileContentCopiedToast)
|
|
return nil
|
|
},
|
|
DisabledReason: self.require(self.singleItemSelected(
|
|
func(node *filetree.CommitFileNode) *types.DisabledReason {
|
|
if !node.IsFile() {
|
|
return &types.DisabledReason{
|
|
Text: self.c.Tr.ErrCannotCopyContentOfDirectory,
|
|
ShowErrorInPanel: true,
|
|
}
|
|
}
|
|
return nil
|
|
}))(),
|
|
Keys: menuKey('c'),
|
|
}
|
|
|
|
return self.c.Menu(types.CreateMenuOptions{
|
|
Title: self.c.Tr.CopyToClipboardMenu,
|
|
Items: []*types.MenuItem{
|
|
copyNameItem,
|
|
copyRelativePathItem,
|
|
copyAbsolutePathItem,
|
|
copyFileDiffItem,
|
|
copyAllDiff,
|
|
copyFileContentItem,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
|
|
hasModifiedFiles := helpers.AnyTrackedFilesInPathExceptSubmodules(node.GetPath(),
|
|
self.c.Model().Files, self.c.Model().Submodules)
|
|
if hasModifiedFiles {
|
|
return errors.New(self.c.Tr.CannotCheckoutWithModifiedFilesErr)
|
|
}
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
|
|
_, to := self.context().GetFromAndToForDiff()
|
|
if err := self.c.Git().WorkingTree.CheckoutFile(to, node.GetPath()); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error {
|
|
prompt := lo.Ternary(self.c.Git().Patch.PatchBuilder.Active(),
|
|
self.c.Tr.DiscardFileChangesPromptResetPatch,
|
|
self.c.Tr.DiscardFileChangesPrompt)
|
|
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.DiscardFileChangesTitle,
|
|
Prompt: prompt,
|
|
HandleConfirm: func() error {
|
|
commits := self.c.Model().Commits
|
|
selectedLineIdx := self.c.Contexts().LocalCommits.GetSelectedLineIdx()
|
|
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
|
var filePaths []string
|
|
selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes)
|
|
|
|
// Reset the current patch if there is one.
|
|
if self.c.Git().Patch.PatchBuilder.Active() {
|
|
self.c.Git().Patch.PatchBuilder.Reset()
|
|
}
|
|
|
|
for _, node := range selectedNodes {
|
|
_ = node.ForEachFile(func(file *models.CommitFile) error {
|
|
// For a rename we discard both the new and the old path,
|
|
// so that the new file is removed and the old one is
|
|
// restored.
|
|
filePaths = append(filePaths, file.Names()...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
err := self.c.Git().Rebase.DiscardOldFileChanges(commits, selectedLineIdx, filePaths)
|
|
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.c.OnUIThread(func() error {
|
|
if self.context().RangeSelectEnabled() {
|
|
self.context().GetList().CancelRangeSelect()
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return nil
|
|
})
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) canDiscardFileChanges(nodes []*filetree.CommitFileNode) *types.DisabledReason {
|
|
parentContext := self.c.Context().Current().GetParentContext()
|
|
if parentContext == nil || parentContext.GetKey() != context.LOCAL_COMMITS_CONTEXT_KEY {
|
|
return &types.DisabledReason{
|
|
Text: self.c.Tr.CanOnlyDiscardFromLocalCommits,
|
|
ShowErrorInPanel: true,
|
|
}
|
|
}
|
|
|
|
if self.c.Contexts().LocalCommits.AreMultipleItemsSelected() {
|
|
return &types.DisabledReason{
|
|
Text: self.c.Tr.CannotDiscardFromMultipleCommits,
|
|
ShowErrorInPanel: true,
|
|
}
|
|
}
|
|
|
|
if self.c.Git().Status.WorkingTreeState().Any() {
|
|
return &types.DisabledReason{
|
|
Text: self.c.Tr.CantPatchWhileRebasingError,
|
|
ShowErrorInPanel: true,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) open(node *filetree.CommitFileNode) error {
|
|
return self.c.Helpers().Files.OpenFile(node.GetPath())
|
|
}
|
|
|
|
func (self *CommitFilesController) edit(nodes []*filetree.CommitFileNode) error {
|
|
return self.c.Helpers().Files.EditFiles(lo.FilterMap(nodes,
|
|
func(node *filetree.CommitFileNode, _ int) (string, bool) {
|
|
return node.GetPath(), node.IsFile()
|
|
}))
|
|
}
|
|
|
|
func (self *CommitFilesController) canEditFiles(nodes []*filetree.CommitFileNode) *types.DisabledReason {
|
|
if lo.NoneBy(nodes, func(node *filetree.CommitFileNode) bool { return node.IsFile() }) {
|
|
return &types.DisabledReason{
|
|
Text: self.c.Tr.ErrCannotEditDirectory,
|
|
ShowErrorInPanel: true,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) openDiffTool(node *filetree.CommitFileNode) error {
|
|
from, to := self.context().GetFromAndToForDiff()
|
|
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
|
_, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj(
|
|
git_commands.DiffToolCmdOptions{
|
|
Filepath: node.GetPath(),
|
|
FromCommit: from,
|
|
ToCommit: to,
|
|
Reverse: reverse,
|
|
IsDirectory: !node.IsFile(),
|
|
Staged: false,
|
|
}))
|
|
return err
|
|
}
|
|
|
|
func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.CommitFileNode) error {
|
|
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
|
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch,
|
|
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
|
}
|
|
|
|
refName := self.context().GetRef().RefName()
|
|
|
|
toggle := func() error {
|
|
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error {
|
|
selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes)
|
|
|
|
// Find if any file in the selection is unselected or partially added
|
|
adding := lo.SomeBy(selectedNodes, func(node *filetree.CommitFileNode) bool {
|
|
return node.SomeFile(func(file *models.CommitFile) bool {
|
|
fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, refName)
|
|
return fileStatus == patch.PART || fileStatus == patch.UNSELECTED
|
|
})
|
|
})
|
|
|
|
patchOperationFunction := self.c.Git().Patch.PatchBuilder.RemoveFile
|
|
|
|
if adding {
|
|
patchOperationFunction = self.c.Git().Patch.PatchBuilder.AddFileWhole
|
|
}
|
|
|
|
for _, node := range selectedNodes {
|
|
err := node.ForEachFile(func(file *models.CommitFile) error {
|
|
return patchOperationFunction(file.Path, file.PreviousPath)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
|
self.c.Git().Patch.PatchBuilder.Reset()
|
|
}
|
|
|
|
self.c.OnUIThread(func() error {
|
|
self.c.PostRefreshUpdate(self.context())
|
|
return nil
|
|
})
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
from, to, reverse := self.currentFromToReverseForPatchBuilding()
|
|
mustDiscardPatch := self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.NewPatchRequired(from, to, reverse)
|
|
return self.c.ConfirmIf(mustDiscardPatch, types.ConfirmOpts{
|
|
Title: self.c.Tr.DiscardPatch,
|
|
Prompt: self.c.Tr.DiscardPatchConfirm,
|
|
HandleConfirm: func() error {
|
|
if mustDiscardPatch {
|
|
self.c.Git().Patch.PatchBuilder.Reset()
|
|
}
|
|
|
|
if !self.c.Git().Patch.PatchBuilder.Active() {
|
|
if err := self.startPatchBuilder(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return toggle()
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *CommitFilesController) toggleAllForPatch(_ *filetree.CommitFileNode) error {
|
|
root := self.context().CommitFileTreeViewModel.GetRoot()
|
|
return self.toggleForPatch([]*filetree.CommitFileNode{root})
|
|
}
|
|
|
|
func (self *CommitFilesController) startPatchBuilder() error {
|
|
commitFilesContext := self.context()
|
|
|
|
canRebase := commitFilesContext.GetCanRebase()
|
|
from, to, reverse := self.currentFromToReverseForPatchBuilding()
|
|
|
|
self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase)
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) currentFromToReverseForPatchBuilding() (string, string, bool) {
|
|
commitFilesContext := self.context()
|
|
|
|
from, to := commitFilesContext.GetFromAndToForDiff()
|
|
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
|
return from, to, reverse
|
|
}
|
|
|
|
func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error {
|
|
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
|
|
}
|
|
|
|
func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error {
|
|
if node.File == nil {
|
|
return self.handleToggleCommitFileDirCollapsed(node)
|
|
}
|
|
|
|
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
|
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch,
|
|
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
|
}
|
|
|
|
from, to, reverse := self.currentFromToReverseForPatchBuilding()
|
|
mustDiscardPatch := self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.NewPatchRequired(from, to, reverse)
|
|
return self.c.ConfirmIf(mustDiscardPatch, types.ConfirmOpts{
|
|
Title: self.c.Tr.DiscardPatch,
|
|
Prompt: self.c.Tr.DiscardPatchConfirm,
|
|
HandleConfirm: func() error {
|
|
if mustDiscardPatch {
|
|
self.c.Git().Patch.PatchBuilder.Reset()
|
|
}
|
|
|
|
if !self.c.Git().Patch.PatchBuilder.Active() {
|
|
if err := self.startPatchBuilder(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
self.c.Context().Push(self.c.Contexts().CustomPatchBuilder, opts)
|
|
self.c.Helpers().PatchBuilding.ShowHunkStagingHint()
|
|
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error {
|
|
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetInternalPath())
|
|
|
|
self.c.PostRefreshUpdate(self.context())
|
|
|
|
return nil
|
|
}
|
|
|
|
// NOTE: this is very similar to handleToggleFileTreeView, could be DRY'd with generics
|
|
func (self *CommitFilesController) toggleTreeView() error {
|
|
self.context().CommitFileTreeViewModel.ToggleShowTree()
|
|
|
|
self.c.PostRefreshUpdate(self.context())
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) collapseAll() error {
|
|
self.context().CommitFileTreeViewModel.CollapseAll()
|
|
|
|
self.c.PostRefreshUpdate(self.context())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) expandAll() error {
|
|
self.context().CommitFileTreeViewModel.ExpandAll()
|
|
|
|
self.c.PostRefreshUpdate(self.context())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error {
|
|
return func(mainViewName string, clickedLineIdx int) error {
|
|
node := self.getSelectedItem()
|
|
if node != nil && node.File != nil {
|
|
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: clickedLineIdx})
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// pathsForDiff returns the file paths to use for a diff command. When a text
|
|
// filter is active and the node is a directory, only the visible (filtered)
|
|
// file paths are returned so the diff reflects what the user sees.
|
|
func (self *CommitFilesController) pathsForDiff(node *filetree.CommitFileNode) []string {
|
|
if !node.IsFile() && self.context().IsFiltering() {
|
|
var paths []string
|
|
_ = node.ForEachFile(func(file *models.CommitFile) error {
|
|
// For a rename we need to pass both paths so that git detects it as
|
|
// a rename rather than an unrelated delete and add.
|
|
paths = append(paths, file.Names()...)
|
|
return nil
|
|
})
|
|
return paths
|
|
}
|
|
if file := node.GetFile(); file != nil {
|
|
return file.Names()
|
|
}
|
|
return []string{node.GetPath()}
|
|
}
|
|
|
|
// NOTE: these functions are identical to those in files_controller.go (except for types) and
|
|
// could also be cleaned up with some generics
|
|
func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode {
|
|
return lo.Filter(selectedNodes, func(node *filetree.CommitFileNode, _ int) bool {
|
|
return !isDescendentOfSelectedCommitFileNodes(node, selectedNodes)
|
|
})
|
|
}
|
|
|
|
func isDescendentOfSelectedCommitFileNodes(node *filetree.CommitFileNode, selectedNodes []*filetree.CommitFileNode) bool {
|
|
for _, selectedNode := range selectedNodes {
|
|
selectedNodePath := selectedNode.GetPath()
|
|
nodePath := node.GetPath()
|
|
|
|
if strings.HasPrefix(nodePath, selectedNodePath) && nodePath != selectedNodePath {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (self *CommitFilesController) isInTreeMode() *types.DisabledReason {
|
|
if !self.context().CommitFileTreeViewModel.InTreeMode() {
|
|
return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView}
|
|
}
|
|
|
|
return nil
|
|
}
|