mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-04-23 12:18:51 +02:00
Before we changed the commitPrefix config to a list in #4261, we had this bug where the defaults section in Config.md would erroneously list the default for commitPrefix as git: commitPrefix: pattern: "" replace: "" This was not correct, commitPrefix was a pointer, and the default for that was nil, which is not the same. Now, some people copied and pasted the entire defaults section into their config files, setting the commitPrefix to an empty pattern (which is not the same as not setting it at all). And this caused the branch name to be filled in to the subject field for every commit; see for example https://github.com/jesseduffield/lazygit/discussions/3632. New users copying the defaults section into their config file in the current version no longer have this problem because now that commitPrefix is a list, it is no longer included in the defaults section. However, the migration that we added in #4261 would happily carry over those empty strings into a list entry, so people migrating from an older version still have the broken config in their config files. To work around the issue, ignore commit prefix settings whose pattern is an empty string. I can't imagine a valid reason why people would actually want to set the pattern to an empty string, so I assume this only comes from the broken defaults problem described above.
243 lines
6.7 KiB
Go
243 lines
6.7 KiB
Go
package helpers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
type IWorkingTreeHelper interface {
|
|
AnyStagedFiles() bool
|
|
AnyTrackedFiles() bool
|
|
IsWorkingTreeDirty() bool
|
|
FileForSubmodule(submodule *models.SubmoduleConfig) *models.File
|
|
}
|
|
|
|
type WorkingTreeHelper struct {
|
|
c *HelperCommon
|
|
refHelper *RefsHelper
|
|
commitsHelper *CommitsHelper
|
|
gpgHelper *GpgHelper
|
|
}
|
|
|
|
func NewWorkingTreeHelper(
|
|
c *HelperCommon,
|
|
refHelper *RefsHelper,
|
|
commitsHelper *CommitsHelper,
|
|
gpgHelper *GpgHelper,
|
|
) *WorkingTreeHelper {
|
|
return &WorkingTreeHelper{
|
|
c: c,
|
|
refHelper: refHelper,
|
|
commitsHelper: commitsHelper,
|
|
gpgHelper: gpgHelper,
|
|
}
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) AnyStagedFiles() bool {
|
|
for _, file := range self.c.Model().Files {
|
|
if file.HasStagedChanges {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
|
|
for _, file := range self.c.Model().Files {
|
|
if file.Tracked {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) IsWorkingTreeDirty() bool {
|
|
return self.AnyStagedFiles() || self.AnyTrackedFiles()
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
|
|
for _, file := range self.c.Model().Files {
|
|
if file.IsSubmodule([]*models.SubmoduleConfig{submodule}) {
|
|
return file
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) OpenMergeTool() error {
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.MergeToolTitle,
|
|
Prompt: self.c.Tr.MergeToolPrompt,
|
|
HandleConfirm: func() error {
|
|
self.c.LogAction(self.c.Tr.Actions.OpenMergeTool)
|
|
return self.c.RunSubprocessAndRefresh(
|
|
self.c.Git().WorkingTree.OpenMergeToolCmdObj(),
|
|
)
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage string) error {
|
|
return self.WithEnsureCommittableFiles(func() error {
|
|
self.commitsHelper.OpenCommitMessagePanel(
|
|
&OpenCommitMessagePanelOpts{
|
|
CommitIndex: context.NoCommitIndex,
|
|
InitialMessage: initialMessage,
|
|
SummaryTitle: self.c.Tr.CommitSummaryTitle,
|
|
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
|
PreserveMessage: true,
|
|
OnConfirm: self.handleCommit,
|
|
OnSwitchToEditor: self.switchFromCommitMessagePanelToEditor,
|
|
},
|
|
)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) handleCommit(summary string, description string) error {
|
|
cmdObj := self.c.Git().Commit.CommitCmdObj(summary, description)
|
|
self.c.LogAction(self.c.Tr.Actions.Commit)
|
|
return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
|
|
self.commitsHelper.OnCommitSuccess()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath string) error {
|
|
// We won't be able to tell whether the commit was successful, because
|
|
// RunSubprocessAndRefresh doesn't return the error (it opens an error alert
|
|
// itself and returns nil on error). But even if we could, we wouldn't have
|
|
// access to the last message that the user typed, and it might be very
|
|
// different from what was last in the commit panel. So the best we can do
|
|
// here is to always clear the remembered commit message.
|
|
self.commitsHelper.OnCommitSuccess()
|
|
|
|
self.c.LogAction(self.c.Tr.Actions.Commit)
|
|
return self.c.RunSubprocessAndRefresh(
|
|
self.c.Git().Commit.CommitInEditorWithMessageFileCmdObj(filepath),
|
|
)
|
|
}
|
|
|
|
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
|
// their editor rather than via the popup panel
|
|
func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
|
return self.WithEnsureCommittableFiles(func() error {
|
|
self.c.LogAction(self.c.Tr.Actions.Commit)
|
|
return self.c.RunSubprocessAndRefresh(
|
|
self.c.Git().Commit.CommitEditorCmdObj(),
|
|
)
|
|
})
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) HandleWIPCommitPress() error {
|
|
skipHookPrefix := self.c.UserConfig().Git.SkipHookPrefix
|
|
if skipHookPrefix == "" {
|
|
return errors.New(self.c.Tr.SkipHookPrefixNotConfigured)
|
|
}
|
|
|
|
return self.HandleCommitPressWithMessage(skipHookPrefix)
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) HandleCommitPress() error {
|
|
message := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError()
|
|
|
|
if message == "" {
|
|
commitPrefixConfigs := self.commitPrefixConfigsForRepo()
|
|
for _, commitPrefixConfig := range commitPrefixConfigs {
|
|
prefixPattern := commitPrefixConfig.Pattern
|
|
if prefixPattern == "" {
|
|
continue
|
|
}
|
|
prefixReplace := commitPrefixConfig.Replace
|
|
branchName := self.refHelper.GetCheckedOutRef().Name
|
|
rgx, err := regexp.Compile(prefixPattern)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %s", self.c.Tr.CommitPrefixPatternError, err.Error())
|
|
}
|
|
|
|
if rgx.MatchString(branchName) {
|
|
prefix := rgx.ReplaceAllString(branchName, prefixReplace)
|
|
message = prefix
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return self.HandleCommitPressWithMessage(message)
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) WithEnsureCommittableFiles(handler func() error) error {
|
|
if err := self.prepareFilesForCommit(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(self.c.Model().Files) == 0 {
|
|
return errors.New(self.c.Tr.NoFilesStagedTitle)
|
|
}
|
|
|
|
if !self.AnyStagedFiles() {
|
|
return self.promptToStageAllAndRetry(handler)
|
|
}
|
|
|
|
return handler()
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) error {
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.NoFilesStagedTitle,
|
|
Prompt: self.c.Tr.NoFilesStagedPrompt,
|
|
HandleConfirm: func() error {
|
|
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
|
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
|
|
return err
|
|
}
|
|
if err := self.syncRefresh(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return retry()
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// for when you need to refetch files before continuing an action. Runs synchronously.
|
|
func (self *WorkingTreeHelper) syncRefresh() error {
|
|
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) prepareFilesForCommit() error {
|
|
noStagedFiles := !self.AnyStagedFiles()
|
|
if noStagedFiles && self.c.UserConfig().Gui.SkipNoStagedFilesWarning {
|
|
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
|
err := self.c.Git().WorkingTree.StageAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return self.syncRefresh()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefixConfig {
|
|
cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
|
|
if ok {
|
|
return append(cfg, self.c.UserConfig().Git.CommitPrefix...)
|
|
} else {
|
|
return self.c.UserConfig().Git.CommitPrefix
|
|
}
|
|
}
|