Skip to content

Commit d12ceeb

Browse files
authored
Add Co-Author support to new commits (#3097)
- Adds Co-Author support to commit menu (`<C-o>` by default) - `e` Opens up the commit message in your editor - `c` Lets you add a co author to your commit - Cleans up and amend commit attribute menu related code
2 parents 272e419 + 1ec8736 commit d12ceeb

File tree

16 files changed

+278
-37
lines changed

16 files changed

+278
-37
lines changed

docs/Config.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ keybinding:
254254
moveDownCommit: '<c-j>' # move commit down one
255255
moveUpCommit: '<c-k>' # move commit up one
256256
amendToCommit: 'A'
257+
amendAttributeMenu: 'a'
257258
pickCommit: 'p' # pick commit (when mid-rebase)
258259
revertCommit: 't'
259260
cherryPickCopy: 'C'
@@ -276,6 +277,12 @@ keybinding:
276277
init: 'i'
277278
update: 'u'
278279
bulkMenu: 'b'
280+
commitMessage:
281+
commitMenu: '<c-o>'
282+
amendAttribute:
283+
addCoAuthor: 'c'
284+
resetAuthor: 'a'
285+
setAuthor: 'A'
279286
```
280287
281288
## Platform Defaults

pkg/commands/git_commands/commit.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ func (self *CommitCommands) SetAuthor(value string) error {
3939
}
4040

4141
// Add a commit's coauthor using Github/Gitlab Co-authored-by metadata. Value is expected to be of the form 'Name <Email>'
42-
func (self *CommitCommands) AddCoAuthor(sha string, value string) error {
42+
func (self *CommitCommands) AddCoAuthor(sha string, author string) error {
4343
message, err := self.GetCommitMessage(sha)
4444
if err != nil {
4545
return err
4646
}
4747

48-
message = message + fmt.Sprintf("\nCo-authored-by: %s", value)
48+
message = AddCoAuthorToMessage(message, author)
4949

5050
cmdArgs := NewGitCmd("commit").
5151
Arg("--allow-empty", "--amend", "--only", "-m", message).
@@ -54,6 +54,25 @@ func (self *CommitCommands) AddCoAuthor(sha string, value string) error {
5454
return self.cmd.New(cmdArgs).Run()
5555
}
5656

57+
func AddCoAuthorToMessage(message string, author string) string {
58+
subject, body, _ := strings.Cut(message, "\n")
59+
60+
return strings.TrimSpace(subject) + "\n\n" + AddCoAuthorToDescription(strings.TrimSpace(body), author)
61+
}
62+
63+
func AddCoAuthorToDescription(description string, author string) string {
64+
if description != "" {
65+
lines := strings.Split(description, "\n")
66+
if strings.HasPrefix(lines[len(lines)-1], "Co-authored-by:") {
67+
description += "\n"
68+
} else {
69+
description += "\n\n"
70+
}
71+
}
72+
73+
return description + fmt.Sprintf("Co-authored-by: %s", author)
74+
}
75+
5776
// ResetToCommit reset to commit
5877
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
5978
cmdArgs := NewGitCmd("reset").Arg("--"+strength, sha).ToArgv()

pkg/commands/git_commands/commit_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,70 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
333333
})
334334
}
335335
}
336+
337+
func TestAddCoAuthorToMessage(t *testing.T) {
338+
scenarios := []struct {
339+
name string
340+
message string
341+
expectedResult string
342+
}{
343+
{
344+
// This never happens, I think it isn't possible to create a commit
345+
// with an empty message. Just including it for completeness.
346+
name: "Empty message",
347+
message: "",
348+
expectedResult: "\n\nCo-authored-by: John Doe <[email protected]>",
349+
},
350+
{
351+
name: "Just a subject, no body",
352+
message: "Subject",
353+
expectedResult: "Subject\n\nCo-authored-by: John Doe <[email protected]>",
354+
},
355+
{
356+
name: "Subject and body",
357+
message: "Subject\n\nBody",
358+
expectedResult: "Subject\n\nBody\n\nCo-authored-by: John Doe <[email protected]>",
359+
},
360+
{
361+
name: "Body already ending with a Co-authored-by line",
362+
message: "Subject\n\nBody\n\nCo-authored-by: Jane Smith <[email protected]>",
363+
expectedResult: "Subject\n\nBody\n\nCo-authored-by: Jane Smith <[email protected]>\nCo-authored-by: John Doe <[email protected]>",
364+
},
365+
}
366+
for _, s := range scenarios {
367+
t.Run(s.name, func(t *testing.T) {
368+
result := AddCoAuthorToMessage(s.message, "John Doe <[email protected]>")
369+
assert.Equal(t, s.expectedResult, result)
370+
})
371+
}
372+
}
373+
374+
func TestAddCoAuthorToDescription(t *testing.T) {
375+
scenarios := []struct {
376+
name string
377+
description string
378+
expectedResult string
379+
}{
380+
{
381+
name: "Empty description",
382+
description: "",
383+
expectedResult: "Co-authored-by: John Doe <[email protected]>",
384+
},
385+
{
386+
name: "Non-empty description",
387+
description: "Body",
388+
expectedResult: "Body\n\nCo-authored-by: John Doe <[email protected]>",
389+
},
390+
{
391+
name: "Description already ending with a Co-authored-by line",
392+
description: "Body\n\nCo-authored-by: Jane Smith <[email protected]>",
393+
expectedResult: "Body\n\nCo-authored-by: Jane Smith <[email protected]>\nCo-authored-by: John Doe <[email protected]>",
394+
},
395+
}
396+
for _, s := range scenarios {
397+
t.Run(s.name, func(t *testing.T) {
398+
result := AddCoAuthorToDescription(s.description, "John Doe <[email protected]>")
399+
assert.Equal(t, s.expectedResult, result)
400+
})
401+
}
402+
}

pkg/config/user_config.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,17 +281,18 @@ type UpdateConfig struct {
281281
}
282282

283283
type KeybindingConfig struct {
284-
Universal KeybindingUniversalConfig `yaml:"universal"`
285-
Status KeybindingStatusConfig `yaml:"status"`
286-
Files KeybindingFilesConfig `yaml:"files"`
287-
Branches KeybindingBranchesConfig `yaml:"branches"`
288-
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
289-
Commits KeybindingCommitsConfig `yaml:"commits"`
290-
Stash KeybindingStashConfig `yaml:"stash"`
291-
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
292-
Main KeybindingMainConfig `yaml:"main"`
293-
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
294-
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
284+
Universal KeybindingUniversalConfig `yaml:"universal"`
285+
Status KeybindingStatusConfig `yaml:"status"`
286+
Files KeybindingFilesConfig `yaml:"files"`
287+
Branches KeybindingBranchesConfig `yaml:"branches"`
288+
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
289+
Commits KeybindingCommitsConfig `yaml:"commits"`
290+
AmendAttribute KeybindingAmendAttributeConfig `yaml:"amendAttribute"`
291+
Stash KeybindingStashConfig `yaml:"stash"`
292+
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
293+
Main KeybindingMainConfig `yaml:"main"`
294+
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
295+
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
295296
}
296297

297298
// damn looks like we have some inconsistencies here with -alt and -alt1
@@ -440,6 +441,12 @@ type KeybindingCommitsConfig struct {
440441
StartInteractiveRebase string `yaml:"startInteractiveRebase"`
441442
}
442443

444+
type KeybindingAmendAttributeConfig struct {
445+
ResetAuthor string `yaml:"resetAuthor"`
446+
SetAuthor string `yaml:"setAuthor"`
447+
AddCoAuthor string `yaml:"addCoAuthor"`
448+
}
449+
443450
type KeybindingStashConfig struct {
444451
PopStash string `yaml:"popStash"`
445452
RenameStash string `yaml:"renameStash"`
@@ -462,7 +469,7 @@ type KeybindingSubmodulesConfig struct {
462469
}
463470

464471
type KeybindingCommitMessageConfig struct {
465-
SwitchToEditor string `yaml:"switchToEditor"`
472+
CommitMenu string `yaml:"commitMenu"`
466473
}
467474

468475
// OSConfig contains config on the level of the os
@@ -836,6 +843,11 @@ func GetDefaultConfig() *UserConfig {
836843
ViewBisectOptions: "b",
837844
StartInteractiveRebase: "i",
838845
},
846+
AmendAttribute: KeybindingAmendAttributeConfig{
847+
ResetAuthor: "a",
848+
SetAuthor: "A",
849+
AddCoAuthor: "c",
850+
},
839851
Stash: KeybindingStashConfig{
840852
PopStash: "g",
841853
RenameStash: "r",
@@ -854,7 +866,7 @@ func GetDefaultConfig() *UserConfig {
854866
BulkMenu: "b",
855867
},
856868
CommitMessage: KeybindingCommitMessageConfig{
857-
SwitchToEditor: "<c-o>",
869+
CommitMenu: "<c-o>",
858870
},
859871
},
860872
OS: OSConfig{},

pkg/gui/context/commit_message_context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ func (self *CommitMessageContext) SetPanelState(
115115
subtitleTemplate := lo.Ternary(onSwitchToEditor != nil, self.c.Tr.CommitDescriptionSubTitle, self.c.Tr.CommitDescriptionSubTitleNoSwitch)
116116
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(subtitleTemplate,
117117
map[string]string{
118-
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
119-
"switchToEditorKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.SwitchToEditor),
118+
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
119+
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.CommitMenu),
120120
})
121121
}
122122

pkg/gui/controllers/commit_description_controller.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
3636
Handler: self.confirm,
3737
},
3838
{
39-
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
40-
Handler: self.switchToEditor,
39+
Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu),
40+
Handler: self.openCommitMenu,
4141
},
4242
}
4343

@@ -64,6 +64,7 @@ func (self *CommitDescriptionController) confirm() error {
6464
return self.c.Helpers().Commits.HandleCommitConfirm()
6565
}
6666

67-
func (self *CommitDescriptionController) switchToEditor() error {
68-
return self.c.Helpers().Commits.SwitchToEditor()
67+
func (self *CommitDescriptionController) openCommitMenu() error {
68+
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
69+
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
6970
}

pkg/gui/controllers/commit_message_controller.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
4848
Handler: self.switchToCommitDescription,
4949
},
5050
{
51-
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
52-
Handler: self.switchToEditor,
51+
Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu),
52+
Handler: self.openCommitMenu,
5353
},
5454
}
5555

@@ -89,10 +89,6 @@ func (self *CommitMessageController) switchToCommitDescription() error {
8989
return nil
9090
}
9191

92-
func (self *CommitMessageController) switchToEditor() error {
93-
return self.c.Helpers().Commits.SwitchToEditor()
94-
}
95-
9692
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
9793
currentIndex := self.context().GetSelectedIndex()
9894
newIndex := currentIndex + value
@@ -134,3 +130,8 @@ func (self *CommitMessageController) confirm() error {
134130
func (self *CommitMessageController) close() error {
135131
return self.c.Helpers().Commits.CloseCommitMessagePanel()
136132
}
133+
134+
func (self *CommitMessageController) openCommitMenu() error {
135+
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
136+
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
137+
}

pkg/gui/controllers/helpers/commits_helper.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/jesseduffield/gocui"
9+
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
910
"github.com/jesseduffield/lazygit/pkg/gui/types"
1011
"github.com/samber/lo"
1112
)
@@ -215,3 +216,39 @@ func (self *CommitsHelper) commitMessageContexts() []types.Context {
215216
self.c.Contexts().CommitMessage,
216217
}
217218
}
219+
220+
func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.Suggestion) error {
221+
menuItems := []*types.MenuItem{
222+
{
223+
Label: self.c.Tr.OpenInEditor,
224+
OnPress: func() error {
225+
return self.SwitchToEditor()
226+
},
227+
Key: 'e',
228+
},
229+
{
230+
Label: self.c.Tr.AddCoAuthor,
231+
OnPress: func() error {
232+
return self.addCoAuthor(suggestionFunc)
233+
},
234+
Key: 'c',
235+
},
236+
}
237+
return self.c.Menu(types.CreateMenuOptions{
238+
Title: self.c.Tr.CommitMenuTitle,
239+
Items: menuItems,
240+
})
241+
}
242+
243+
func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Suggestion) error {
244+
return self.c.Prompt(types.PromptOpts{
245+
Title: self.c.Tr.AddCoAuthorPromptTitle,
246+
FindSuggestionsFunc: suggestionFunc,
247+
HandleConfirm: func(value string) error {
248+
commitDescription := self.getCommitDescription()
249+
commitDescription = git_commands.AddCoAuthorToDescription(commitDescription, value)
250+
self.setCommitDescription(commitDescription)
251+
return nil
252+
},
253+
})
254+
}

pkg/gui/controllers/local_commits_controller.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,25 +673,26 @@ func (self *LocalCommitsController) canAmend(commit *models.Commit) *types.Disab
673673
}
674674

675675
func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error {
676+
opts := self.c.KeybindingsOpts()
676677
return self.c.Menu(types.CreateMenuOptions{
677678
Title: "Amend commit attribute",
678679
Items: []*types.MenuItem{
679680
{
680681
Label: self.c.Tr.ResetAuthor,
681682
OnPress: self.resetAuthor,
682-
Key: 'a',
683-
Tooltip: "Reset the commit's author to the currently configured user. This will also renew the author timestamp",
683+
Key: opts.GetKey(opts.Config.AmendAttribute.ResetAuthor),
684+
Tooltip: self.c.Tr.ResetAuthorTooltip,
684685
},
685686
{
686687
Label: self.c.Tr.SetAuthor,
687688
OnPress: self.setAuthor,
688-
Key: 'A',
689-
Tooltip: "Set the author based on a prompt",
689+
Key: opts.GetKey(opts.Config.AmendAttribute.SetAuthor),
690+
Tooltip: self.c.Tr.SetAuthorTooltip,
690691
},
691692
{
692693
Label: self.c.Tr.AddCoAuthor,
693694
OnPress: self.addCoAuthor,
694-
Key: 'c',
695+
Key: opts.GetKey(opts.Config.AmendAttribute.AddCoAuthor),
695696
Tooltip: self.c.Tr.AddCoAuthorTooltip,
696697
},
697698
},

pkg/i18n/english.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ type TranslationSet struct {
147147
AmendCommitTooltip string
148148
Amend string
149149
ResetAuthor string
150+
ResetAuthorTooltip string
150151
SetAuthor string
152+
SetAuthorTooltip string
151153
AddCoAuthor string
152154
AmendCommitAttribute string
153155
AmendCommitAttributeTooltip string
@@ -270,6 +272,7 @@ type TranslationSet struct {
270272
SearchTitle string
271273
TagsTitle string
272274
MenuTitle string
275+
CommitMenuTitle string
273276
RemotesTitle string
274277
RemoteBranchesTitle string
275278
PatchBuildingTitle string
@@ -1093,7 +1096,9 @@ func EnglishTranslationSet() TranslationSet {
10931096
AmendCommitTooltip: "Amend commit with staged changes. If the selected commit is the HEAD commit, this will perform `git commit --amend`. Otherwise the commit will be amended via a rebase.",
10941097
Amend: "Amend",
10951098
ResetAuthor: "Reset author",
1099+
ResetAuthorTooltip: "Reset the commit's author to the currently configured user. This will also renew the author timestamp",
10961100
SetAuthor: "Set author",
1101+
SetAuthorTooltip: "Set the author based on a prompt",
10971102
AddCoAuthor: "Add co-author",
10981103
AmendCommitAttribute: "Amend commit attribute",
10991104
AmendCommitAttributeTooltip: "Set/Reset commit author or set co-author.",
@@ -1209,12 +1214,13 @@ func EnglishTranslationSet() TranslationSet {
12091214
RebaseOptionsTitle: "Rebase options",
12101215
CommitSummaryTitle: "Commit summary",
12111216
CommitDescriptionTitle: "Commit description",
1212-
CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus, {{.switchToEditorKeyBinding}} to switch to editor",
1217+
CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus, {{.commitMenuKeybinding}} to open menu",
12131218
CommitDescriptionSubTitleNoSwitch: "Press {{.togglePanelKeyBinding}} to toggle focus",
12141219
LocalBranchesTitle: "Local branches",
12151220
SearchTitle: "Search",
12161221
TagsTitle: "Tags",
12171222
MenuTitle: "Menu",
1223+
CommitMenuTitle: "Commit Menu",
12181224
RemotesTitle: "Remotes",
12191225
RemoteBranchesTitle: "Remote branches",
12201226
PatchBuildingTitle: "Main panel (patch building)",

0 commit comments

Comments
 (0)