mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
discard reads Model().Commits and the selected commit index from its WithWaitingStatus worker; read them in HandleConfirm instead. toggleForPatch reads the commit-files ref name from the worker, and its startPatchBuilder call reads the context's canRebase and diff range from there too. Capture the ref name and run startPatchBuilder in HandleConfirm before dispatching; PatchBuilder.Start only assigns fields, so moving it off the worker changes no timing. discard still collapses the range selection from the worker; that write is a separate concern, left for a follow-up.
660 lines
21 KiB
Go
660 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
|
|
}
|
|
|
|
if self.context().RangeSelectEnabled() {
|
|
self.context().GetList().CancelRangeSelect()
|
|
}
|
|
|
|
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
|
|
}
|