feat: add option to save generated patch to file

Add a new option to the custom patch options menu that allows
saving the generated patch directly to a file.

The new option:
- Prompts the user for a file path
- Supports both absolute and relative paths (relative paths are
  resolved from the repository root)
- Writes the aggregated patch content to disk
- Shows a confirmation toast after the file is saved

This complements the existing clipboard option and provides a
more convenient workflow for users who want to review, share,
or manually apply patches.

Closes #5117
This commit is contained in:
climatex 2025-12-14 23:22:08 +08:00
parent 8651c46e14
commit 3c17993c79
2 changed files with 43 additions and 0 deletions

View file

@ -3,6 +3,8 @@ package controllers
import (
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
@ -104,6 +106,12 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
}
menuItems = append(menuItems, []*types.MenuItem{
{
Label: self.c.Tr.SavePatch,
Tooltip: self.c.Tr.SavePatchTooltip,
OnPress: self.handleSavePatchToFile,
Key: 's',
},
{
Label: self.c.Tr.CopyPatchToClipboard,
OnPress: func() error { return self.copyPatchToClipboard() },
@ -290,6 +298,31 @@ func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
})
}
func (self *CustomPatchOptionsMenuAction) handleSavePatchToFile() error {
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.SavePatchTitle,
HandleConfirm: func(path string) error {
path = strings.TrimSpace(path)
if !filepath.IsAbs(path) {
path = filepath.Join(self.c.Git().RepoPaths.WorktreePath(), path)
}
path = filepath.Clean(path)
self.c.LogAction(self.c.Tr.Actions.SavePatchToFile)
if err := self.c.OS().CreateFileWithContent(path, patch); err != nil {
return err
}
self.c.Toast(fmt.Sprintf(self.c.Tr.PatchSavedToFile, path))
return nil
},
})
return nil
}
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)

View file

@ -826,6 +826,10 @@ type TranslationSet struct {
MovePatchIntoNewCommitBeforeTooltip string
MovePatchToSelectedCommit string
MovePatchToSelectedCommitTooltip string
SavePatch string
SavePatchTooltip string
SavePatchTitle string
PatchSavedToFile string
CopyPatchToClipboard string
MustStageFilesAffectedByPatchTitle string
MustStageFilesAffectedByPatchWarning string
@ -987,6 +991,7 @@ type Actions struct {
CopyCommitAttributeToClipboard string
CopyCommitTagsToClipboard string
CopyPatchToClipboard string
SavePatchToFile string
CustomCommand string
DiscardAllChangesInFile string
DiscardAllUnstagedChangesInFile string
@ -1923,6 +1928,10 @@ func EnglishTranslationSet() *TranslationSet {
MovePatchIntoNewCommitBeforeTooltip: "Move the patch out of its commit and into a new commit before the original commit. This works best when the custom patch contains only entire hunks or even entire files; if it contains partial hunks, you are likely to get conflicts.",
MovePatchToSelectedCommit: "Move patch to selected commit (%s)",
MovePatchToSelectedCommitTooltip: "Move the patch out of its original commit and into the selected commit. This is achieved by starting an interactive rebase at the original commit, applying the patch in reverse, then continuing the rebase up to the selected commit, before applying the patch forward and amending the selected commit. The rebase is then continued to completion. If commits between the source and destination commit depend on the patch, you may need to resolve conflicts.",
SavePatch: "Save patch to file",
SavePatchTooltip: "Write the current patch to a file. Relative paths are resolved from the repository root.",
SavePatchTitle: "Save patch to file",
PatchSavedToFile: "Patch saved to %s",
CopyPatchToClipboard: "Copy patch to clipboard",
MustStageFilesAffectedByPatchTitle: "Must stage files",
MustStageFilesAffectedByPatchWarning: "Applying a patch to the index requires staging the unstaged files that are affected by the patch. Note that you might get conflicts when applying the patch. Continue?",
@ -2045,6 +2054,7 @@ func EnglishTranslationSet() *TranslationSet {
CopyCommitAuthorToClipboard: "Copy commit author to clipboard",
CopyCommitAttributeToClipboard: "Copy to clipboard",
CopyPatchToClipboard: "Copy patch to clipboard",
SavePatchToFile: "Save patch to file",
MoveCommitUp: "Move commit up",
MoveCommitDown: "Move commit down",
CustomCommand: "Custom command",