From 6afcc5bda8bbe9d9976fd50740564fe201c3ea41 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 17 May 2024 19:04:21 +0200 Subject: [PATCH] Create shims for all model classes in SessionStateLoader This guards against accidentally renaming a model field and thereby breaking user's custom commands. With this change we'll get a build failure when we do that. --- docs/Custom_Command_Keybindings.md | 2 +- pkg/gui/services/custom_commands/models.go | 96 ++++++++++ .../custom_commands/session_state_loader.go | 178 ++++++++++++++---- 3 files changed, 237 insertions(+), 39 deletions(-) create mode 100644 pkg/gui/services/custom_commands/models.go diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md index 8604a5247..426d6f8f6 100644 --- a/docs/Custom_Command_Keybindings.md +++ b/docs/Custom_Command_Keybindings.md @@ -305,7 +305,7 @@ SelectedWorktree CheckedOutBranch ``` -To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit Lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Hash}}` and `{{.SelectedLocalBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model. +To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file). ## Keybinding collisions diff --git a/pkg/gui/services/custom_commands/models.go b/pkg/gui/services/custom_commands/models.go new file mode 100644 index 000000000..a33a4daf8 --- /dev/null +++ b/pkg/gui/services/custom_commands/models.go @@ -0,0 +1,96 @@ +package custom_commands + +import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/stefanhaller/git-todo-parser/todo" +) + +// We create shims for all the model classes in order to get a more stable API +// for custom commands. At the moment these are almost identical to the model +// classes, but this allows us to add "private" fields to the model classes that +// we don't want to expose to custom commands, or rename a model field to a +// better name without breaking people's custom commands. In such a case we add +// the new, better name to the shim but keep the old one for backwards +// compatibility. We already did this for Commit.Sha, which was renamed to Hash. + +type Commit struct { + Hash string // deprecated: use Sha + Sha string + Name string + Status models.CommitStatus + Action todo.TodoCommand + Tags []string + ExtraInfo string + AuthorName string + AuthorEmail string + UnixTimestamp int64 + Divergence models.Divergence + Parents []string +} + +type File struct { + Name string + PreviousName string + HasStagedChanges bool + HasUnstagedChanges bool + Tracked bool + Added bool + Deleted bool + HasMergeConflicts bool + HasInlineMergeConflicts bool + DisplayString string + ShortStatus string + IsWorktree bool +} + +type Branch struct { + Name string + DisplayName string + Recency string + Pushables string + Pullables string + UpstreamGone bool + Head bool + DetachedHead bool + UpstreamRemote string + UpstreamBranch string + Subject string + CommitHash string +} + +type RemoteBranch struct { + Name string + RemoteName string +} + +type Remote struct { + Name string + Urls []string + Branches []*RemoteBranch +} + +type Tag struct { + Name string + Message string +} + +type StashEntry struct { + Index int + Recency string + Name string +} + +type CommitFile struct { + Name string + ChangeStatus string +} + +type Worktree struct { + IsMain bool + IsCurrent bool + Path string + IsPathMissing bool + GitDir string + Branch string + Name string +} diff --git a/pkg/gui/services/custom_commands/session_state_loader.go b/pkg/gui/services/custom_commands/session_state_loader.go index 6a3068df8..23369cb0b 100644 --- a/pkg/gui/services/custom_commands/session_state_loader.go +++ b/pkg/gui/services/custom_commands/session_state_loader.go @@ -3,7 +3,7 @@ package custom_commands import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" - "github.com/stefanhaller/git-todo-parser/todo" + "github.com/samber/lo" ) // loads the session state at the time that a custom command is invoked, for use @@ -20,22 +20,7 @@ func NewSessionStateLoader(c *helpers.HelperCommon, refsHelper *helpers.RefsHelp } } -type Commit struct { - Hash string - Sha string - Name string - Status models.CommitStatus - Action todo.TodoCommand - Tags []string - ExtraInfo string - AuthorName string - AuthorEmail string - UnixTimestamp int64 - Divergence models.Divergence - Parents []string -} - -func commitWrapperFromModelCommit(commit *models.Commit) *Commit { +func commitShimFromModelCommit(commit *models.Commit) *Commit { if commit == nil { return nil } @@ -56,39 +41,156 @@ func commitWrapperFromModelCommit(commit *models.Commit) *Commit { } } +func fileShimFromModelFile(file *models.File) *File { + if file == nil { + return nil + } + + return &File{ + Name: file.Name, + PreviousName: file.PreviousName, + HasStagedChanges: file.HasStagedChanges, + HasUnstagedChanges: file.HasUnstagedChanges, + Tracked: file.Tracked, + Added: file.Added, + Deleted: file.Deleted, + HasMergeConflicts: file.HasMergeConflicts, + HasInlineMergeConflicts: file.HasInlineMergeConflicts, + DisplayString: file.DisplayString, + ShortStatus: file.ShortStatus, + IsWorktree: file.IsWorktree, + } +} + +func branchShimFromModelBranch(branch *models.Branch) *Branch { + if branch == nil { + return nil + } + + return &Branch{ + Name: branch.Name, + DisplayName: branch.DisplayName, + Recency: branch.Recency, + Pushables: branch.Pushables, + Pullables: branch.Pullables, + UpstreamGone: branch.UpstreamGone, + Head: branch.Head, + DetachedHead: branch.DetachedHead, + UpstreamRemote: branch.UpstreamRemote, + UpstreamBranch: branch.UpstreamBranch, + Subject: branch.Subject, + CommitHash: branch.CommitHash, + } +} + +func remoteBranchShimFromModelRemoteBranch(remoteBranch *models.RemoteBranch) *RemoteBranch { + if remoteBranch == nil { + return nil + } + + return &RemoteBranch{ + Name: remoteBranch.Name, + RemoteName: remoteBranch.RemoteName, + } +} + +func remoteShimFromModelRemote(remote *models.Remote) *Remote { + if remote == nil { + return nil + } + + return &Remote{ + Name: remote.Name, + Urls: remote.Urls, + Branches: lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) *RemoteBranch { + return remoteBranchShimFromModelRemoteBranch(branch) + }), + } +} + +func tagShimFromModelRemote(tag *models.Tag) *Tag { + if tag == nil { + return nil + } + + return &Tag{ + Name: tag.Name, + Message: tag.Message, + } +} + +func stashEntryShimFromModelRemote(stashEntry *models.StashEntry) *StashEntry { + if stashEntry == nil { + return nil + } + + return &StashEntry{ + Index: stashEntry.Index, + Recency: stashEntry.Recency, + Name: stashEntry.Name, + } +} + +func commitFileShimFromModelRemote(commitFile *models.CommitFile) *CommitFile { + if commitFile == nil { + return nil + } + + return &CommitFile{ + Name: commitFile.Name, + ChangeStatus: commitFile.ChangeStatus, + } +} + +func worktreeShimFromModelRemote(worktree *models.Worktree) *Worktree { + if worktree == nil { + return nil + } + + return &Worktree{ + IsMain: worktree.IsMain, + IsCurrent: worktree.IsCurrent, + Path: worktree.Path, + IsPathMissing: worktree.IsPathMissing, + GitDir: worktree.GitDir, + Branch: worktree.Branch, + Name: worktree.Name, + } +} + // SessionState captures the current state of the application for use in custom commands type SessionState struct { SelectedLocalCommit *Commit SelectedReflogCommit *Commit SelectedSubCommit *Commit - SelectedFile *models.File + SelectedFile *File SelectedPath string - SelectedLocalBranch *models.Branch - SelectedRemoteBranch *models.RemoteBranch - SelectedRemote *models.Remote - SelectedTag *models.Tag - SelectedStashEntry *models.StashEntry - SelectedCommitFile *models.CommitFile + SelectedLocalBranch *Branch + SelectedRemoteBranch *RemoteBranch + SelectedRemote *Remote + SelectedTag *Tag + SelectedStashEntry *StashEntry + SelectedCommitFile *CommitFile SelectedCommitFilePath string - SelectedWorktree *models.Worktree - CheckedOutBranch *models.Branch + SelectedWorktree *Worktree + CheckedOutBranch *Branch } func (self *SessionStateLoader) call() *SessionState { return &SessionState{ - SelectedFile: self.c.Contexts().Files.GetSelectedFile(), + SelectedFile: fileShimFromModelFile(self.c.Contexts().Files.GetSelectedFile()), SelectedPath: self.c.Contexts().Files.GetSelectedPath(), - SelectedLocalCommit: commitWrapperFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()), - SelectedReflogCommit: commitWrapperFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()), - SelectedLocalBranch: self.c.Contexts().Branches.GetSelected(), - SelectedRemoteBranch: self.c.Contexts().RemoteBranches.GetSelected(), - SelectedRemote: self.c.Contexts().Remotes.GetSelected(), - SelectedTag: self.c.Contexts().Tags.GetSelected(), - SelectedStashEntry: self.c.Contexts().Stash.GetSelected(), - SelectedCommitFile: self.c.Contexts().CommitFiles.GetSelectedFile(), + SelectedLocalCommit: commitShimFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()), + SelectedReflogCommit: commitShimFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()), + SelectedLocalBranch: branchShimFromModelBranch(self.c.Contexts().Branches.GetSelected()), + SelectedRemoteBranch: remoteBranchShimFromModelRemoteBranch(self.c.Contexts().RemoteBranches.GetSelected()), + SelectedRemote: remoteShimFromModelRemote(self.c.Contexts().Remotes.GetSelected()), + SelectedTag: tagShimFromModelRemote(self.c.Contexts().Tags.GetSelected()), + SelectedStashEntry: stashEntryShimFromModelRemote(self.c.Contexts().Stash.GetSelected()), + SelectedCommitFile: commitFileShimFromModelRemote(self.c.Contexts().CommitFiles.GetSelectedFile()), SelectedCommitFilePath: self.c.Contexts().CommitFiles.GetSelectedPath(), - SelectedSubCommit: commitWrapperFromModelCommit(self.c.Contexts().SubCommits.GetSelected()), - SelectedWorktree: self.c.Contexts().Worktrees.GetSelected(), - CheckedOutBranch: self.refsHelper.GetCheckedOutRef(), + SelectedSubCommit: commitShimFromModelCommit(self.c.Contexts().SubCommits.GetSelected()), + SelectedWorktree: worktreeShimFromModelRemote(self.c.Contexts().Worktrees.GetSelected()), + CheckedOutBranch: branchShimFromModelBranch(self.refsHelper.GetCheckedOutRef()), } }