Skip to content

Commit 7d26a75

Browse files
authored
Enhance support for GPG signed tags (#4394)
- **PR Description** When using gpg signing on tags via the git config `tag.gpgSign=true`, it is not possible to do a lightweight tag. You must do an annotated tag (Which is allowed to have an empty description). This PR augments the existing `WithGpgHandling` helper to allow it to specifically target commit signing and tag signing. It then uses that handler, and creates an annotated tag, when the git config demands tag GPG signing. By default it will launch the tag signing in a new subprocess. This does require that users click an extra `<enter>` after creating a signed tag, but this is currently the behavior for signed commits, so I don't feel too bad about that. If they want, they can use the LazyGit configuration option of `git.overrideGpg: true` to promise to LazyGit that they do not need a GPG sub-process (because they reliably already have gpg-agent or similar running in the background). This again matches the current behavior for GPG signed commits. This has no integration test because we don't have the machinery set up in place to set up a GPG key inside of our integration test framework. Fixes #2955
2 parents 67b0db0 + c765da1 commit 7d26a75

File tree

11 files changed

+69
-46
lines changed

11 files changed

+69
-46
lines changed

pkg/commands/git_commands/config.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,31 @@ func (self *ConfigCommands) GetPager(width int) string {
5757
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
5858
}
5959

60-
// UsingGpg tells us whether the user has gpg enabled so that we can know
61-
// whether we need to run a subprocess to allow them to enter their password
62-
func (self *ConfigCommands) UsingGpg() bool {
60+
type GpgConfigKey string
61+
62+
const (
63+
CommitGpgSign GpgConfigKey = "commit.gpgSign"
64+
TagGpgSign GpgConfigKey = "tag.gpgSign"
65+
)
66+
67+
// NeedsGpgSubprocess tells us whether the user has gpg enabled for the specified action type
68+
// and needs a subprocess because they have a process where they manually
69+
// enter their password every time a GPG action is taken
70+
func (self *ConfigCommands) NeedsGpgSubprocess(key GpgConfigKey) bool {
6371
overrideGpg := self.UserConfig().Git.OverrideGpg
6472
if overrideGpg {
6573
return false
6674
}
6775

68-
return self.gitConfig.GetBool("commit.gpgsign")
76+
return self.gitConfig.GetBool(string(key))
77+
}
78+
79+
func (self *ConfigCommands) NeedsGpgSubprocessForCommit() bool {
80+
return self.NeedsGpgSubprocess(CommitGpgSign)
81+
}
82+
83+
func (self *ConfigCommands) GetGpgTagSign() bool {
84+
return self.gitConfig.GetBool(string(TagGpgSign))
6985
}
7086

7187
func (self *ConfigCommands) GetCoreEditor() string {

pkg/commands/git_commands/patch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
150150
// we can make this GPG thing possible it just means we need to do this in two parts:
151151
// one where we handle the possibility of a credential request, and the other
152152
// where we continue the rebase
153-
if self.config.UsingGpg() {
153+
if self.config.NeedsGpgSubprocessForCommit() {
154154
return errors.New(self.Tr.DisabledForGPG)
155155
}
156156

pkg/commands/git_commands/rebase.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func NewRebaseCommands(
3535
}
3636

3737
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
38-
if self.config.UsingGpg() {
38+
if self.config.NeedsGpgSubprocessForCommit() {
3939
return errors.New(self.Tr.DisabledForGPG)
4040
}
4141

@@ -413,7 +413,7 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
413413
// we can make this GPG thing possible it just means we need to do this in two parts:
414414
// one where we handle the possibility of a credential request, and the other
415415
// where we continue the rebase
416-
if self.config.UsingGpg() {
416+
if self.config.NeedsGpgSubprocessForCommit() {
417417
return errors.New(self.Tr.DisabledForGPG)
418418
}
419419

pkg/commands/git_commands/rebase_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
128128
},
129129
{
130130
testName: "returns error when using gpg",
131-
gitConfigMockResponses: map[string]string{"commit.gpgsign": "true"},
131+
gitConfigMockResponses: map[string]string{"commit.gpgSign": "true"},
132132
commits: []*models.Commit{{Name: "commit", Hash: "123456"}},
133133
commitIndex: 0,
134134
fileName: []string{"test999.txt"},

pkg/commands/git_commands/tag.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package git_commands
22

3-
import "github.com/jesseduffield/gocui"
3+
import (
4+
"github.com/jesseduffield/gocui"
5+
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
6+
)
47

58
type TagCommands struct {
69
*GitCommon
@@ -12,24 +15,24 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
1215
}
1316
}
1417

15-
func (self *TagCommands) CreateLightweight(tagName string, ref string, force bool) error {
18+
func (self *TagCommands) CreateLightweightObj(tagName string, ref string, force bool) oscommands.ICmdObj {
1619
cmdArgs := NewGitCmd("tag").
1720
ArgIf(force, "--force").
1821
Arg("--", tagName).
1922
ArgIf(len(ref) > 0, ref).
2023
ToArgv()
2124

22-
return self.cmd.New(cmdArgs).Run()
25+
return self.cmd.New(cmdArgs)
2326
}
2427

25-
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) error {
28+
func (self *TagCommands) CreateAnnotatedObj(tagName, ref, msg string, force bool) oscommands.ICmdObj {
2629
cmdArgs := NewGitCmd("tag").Arg(tagName).
2730
ArgIf(force, "--force").
2831
ArgIf(len(ref) > 0, ref).
2932
Arg("-m", msg).
3033
ToArgv()
3134

32-
return self.cmd.New(cmdArgs).Run()
35+
return self.cmd.New(cmdArgs)
3336
}
3437

3538
func (self *TagCommands) HasTag(tagName string) bool {

pkg/gui/controllers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (gui *Gui) resetHelpersAndControllers() {
106106
Suggestions: suggestionsHelper,
107107
Files: helpers.NewFilesHelper(helperCommon),
108108
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
109-
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
109+
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper, gpgHelper),
110110
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
111111
GPG: helpers.NewGpgHelper(helperCommon),
112112
MergeAndRebase: rebaseHelper,

pkg/gui/controllers/helpers/amend_helper.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package helpers
22

3+
import "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
4+
35
type AmendHelper struct {
46
c *HelperCommon
57
gpg *GpgHelper
@@ -18,5 +20,5 @@ func NewAmendHelper(
1820
func (self *AmendHelper) AmendHead() error {
1921
cmdObj := self.c.Git().Commit.AmendHeadCmdObj()
2022
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
21-
return self.gpg.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
23+
return self.gpg.WithGpgHandling(cmdObj, git_commands.CommitGpgSign, self.c.Tr.AmendingStatus, nil, nil)
2224
}

pkg/gui/controllers/helpers/gpg_helper.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/jesseduffield/gocui"
7+
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
78
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
89
"github.com/jesseduffield/lazygit/pkg/gui/types"
910
)
@@ -22,29 +23,29 @@ func NewGpgHelper(c *HelperCommon) *GpgHelper {
2223
// WithWaitingStatus we get stuck there and can't return to lazygit. We could
2324
// fix this bug, or just stop running subprocesses from within there, given that
2425
// we don't need to see a loading status if we're in a subprocess.
25-
func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
26-
useSubprocess := self.c.Git().Config.UsingGpg()
26+
func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, configKey git_commands.GpgConfigKey, waitingStatus string, onSuccess func() error, refreshScope []types.RefreshableView) error {
27+
useSubprocess := self.c.Git().Config.NeedsGpgSubprocess(configKey)
2728
if useSubprocess {
2829
success, err := self.c.RunSubprocess(cmdObj)
2930
if success && onSuccess != nil {
3031
if err := onSuccess(); err != nil {
3132
return err
3233
}
3334
}
34-
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
35+
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope}); err != nil {
3536
return err
3637
}
3738

3839
return err
3940
} else {
40-
return self.runAndStream(cmdObj, waitingStatus, onSuccess)
41+
return self.runAndStream(cmdObj, waitingStatus, onSuccess, refreshScope)
4142
}
4243
}
4344

44-
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
45+
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error, refreshScope []types.RefreshableView) error {
4546
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
4647
if err := cmdObj.StreamOutput().Run(); err != nil {
47-
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
48+
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
4849
return fmt.Errorf(
4950
self.c.Tr.GitCommandFailed, self.c.UserConfig().Keybinding.Universal.ExtrasMenu,
5051
)
@@ -56,6 +57,6 @@ func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus str
5657
}
5758
}
5859

59-
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
60+
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
6061
})
6162
}

pkg/gui/controllers/helpers/tags_helper.go

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package helpers
22

33
import (
4-
"github.com/jesseduffield/gocui"
4+
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
5+
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
56
"github.com/jesseduffield/lazygit/pkg/gui/context"
67
"github.com/jesseduffield/lazygit/pkg/gui/types"
78
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -10,36 +11,32 @@ import (
1011
type TagsHelper struct {
1112
c *HelperCommon
1213
commitsHelper *CommitsHelper
14+
gpg *GpgHelper
1315
}
1416

15-
func NewTagsHelper(c *HelperCommon, commitsHelper *CommitsHelper) *TagsHelper {
17+
func NewTagsHelper(c *HelperCommon, commitsHelper *CommitsHelper, gpg *GpgHelper) *TagsHelper {
1618
return &TagsHelper{
1719
c: c,
1820
commitsHelper: commitsHelper,
21+
gpg: gpg,
1922
}
2023
}
2124

2225
func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
2326
doCreateTag := func(tagName string, description string, force bool) error {
24-
return self.c.WithWaitingStatus(self.c.Tr.CreatingTag, func(gocui.Task) error {
25-
if description != "" {
26-
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
27-
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, description, force); err != nil {
28-
return err
29-
}
30-
} else {
31-
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
32-
if err := self.c.Git().Tag.CreateLightweight(tagName, ref, force); err != nil {
33-
return err
34-
}
35-
}
27+
var command oscommands.ICmdObj
28+
if description != "" || self.c.Git().Config.GetGpgTagSign() {
29+
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
30+
command = self.c.Git().Tag.CreateAnnotatedObj(tagName, ref, description, force)
31+
} else {
32+
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
33+
command = self.c.Git().Tag.CreateLightweightObj(tagName, ref, force)
34+
}
3635

36+
return self.gpg.WithGpgHandling(command, git_commands.TagGpgSign, self.c.Tr.CreatingTag, func() error {
3737
self.commitsHelper.OnCommitSuccess()
38-
39-
return self.c.Refresh(types.RefreshOptions{
40-
Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS},
41-
})
42-
})
38+
return nil
39+
}, []types.RefreshableView{types.COMMITS, types.TAGS})
4340
}
4441

4542
onConfirm := func(tagName string, description string) error {

pkg/gui/controllers/helpers/working_tree_helper.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"regexp"
77

8+
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
89
"github.com/jesseduffield/lazygit/pkg/commands/models"
910
"github.com/jesseduffield/lazygit/pkg/config"
1011
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -111,10 +112,11 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
111112
func (self *WorkingTreeHelper) handleCommit(summary string, description string, forceSkipHooks bool) error {
112113
cmdObj := self.c.Git().Commit.CommitCmdObj(summary, description, forceSkipHooks)
113114
self.c.LogAction(self.c.Tr.Actions.Commit)
114-
return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
115-
self.commitsHelper.OnCommitSuccess()
116-
return nil
117-
})
115+
return self.gpgHelper.WithGpgHandling(cmdObj, git_commands.CommitGpgSign, self.c.Tr.CommittingStatus,
116+
func() error {
117+
self.commitsHelper.OnCommitSuccess()
118+
return nil
119+
}, nil)
118120
}
119121

120122
func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath string, forceSkipHooks bool) error {

0 commit comments

Comments
 (0)