Skip to content

Commit 34c9166

Browse files
ruudkstefanhaller
authored andcommitted
Add runCommand function to Go template syntax
This makes it possible to use date and time in initial values like this: ```yaml initialValue: 'ruudk/{{ runCommand "date +\"%Y/%-m\"" }}/' ``` I want to use this to configure my BranchPrefix like this: ```yaml git: branchPrefix: 'ruudk/{{ runCommand "date +\"%Y/%-m\"" }}/' ```
1 parent a611336 commit 34c9166

File tree

8 files changed

+140
-3
lines changed

8 files changed

+140
-3
lines changed

docs/Config.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,15 @@ git:
10081008
branchPrefix: "firstlast/"
10091009
```
10101010

1011+
It's possible to use a dynamic prefix by using the `runCommand` function:
1012+
1013+
```yaml
1014+
git:
1015+
branchPrefix: "firstlast/{{ runCommand "date +\"%Y/%-m\"" }}/"
1016+
```
1017+
1018+
This would produce something like: `firstlast/2025/4/`
1019+
10111020
## Custom git log command
10121021

10131022
You can override the `git log` command that's used to render the log of the selected branch like so:

docs/Custom_Command_Keybindings.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,24 @@ We don't support accessing all elements of a range selection yet. We might add t
320320
command: "git format-patch {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}}"
321321
```
322322

323+
We support the following functions:
324+
325+
### Quoting
326+
327+
Quote wraps a string in quotes with necessary escaping for the current platform.
328+
329+
```
330+
git {{.SelectedFile.Name | quote}}
331+
```
332+
333+
### Running a command
334+
335+
Runs a command and returns the output. If the command outputs more than a single line, it will produce an error.
336+
337+
```
338+
initialValue: "username/{{ runCommand "date +\"%Y/%-m\"" }}/"
339+
```
340+
323341
## Keybinding collisions
324342
325343
If your custom keybinding collides with an inbuilt keybinding that is defined for the same context, only the custom keybinding will be executed. This also applies to the global context. However, one caveat is that if you have a custom keybinding defined on the global context for some key, and there is an in-built keybinding defined for the same key and for a specific context (say the 'files' context), then the in-built keybinding will take precedence. See how to change in-built keybindings [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#keybindings)

pkg/commands/git_commands/custom.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package git_commands
22

3-
import "github.com/mgutz/str"
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/mgutz/str"
8+
)
49

510
type CustomCommands struct {
611
*GitCommon
@@ -18,3 +23,18 @@ func NewCustomCommands(gitCommon *GitCommon) *CustomCommands {
1823
func (self *CustomCommands) RunWithOutput(cmdStr string) (string, error) {
1924
return self.cmd.New(str.ToArgv(cmdStr)).RunWithOutput()
2025
}
26+
27+
// A function that can be used as a "runCommand" entry in the template.FuncMap of templates.
28+
func (self *CustomCommands) TemplateFunctionRunCommand(cmdStr string) (string, error) {
29+
output, err := self.RunWithOutput(cmdStr)
30+
if err != nil {
31+
return "", err
32+
}
33+
output = strings.TrimRight(output, "\r\n")
34+
35+
if strings.Contains(output, "\r\n") {
36+
return "", fmt.Errorf("command output contains newlines: %s", output)
37+
}
38+
39+
return output, nil
40+
}

pkg/gui/controllers/helpers/refs_helper.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package helpers
33
import (
44
"fmt"
55
"strings"
6+
"text/template"
67

78
"github.com/jesseduffield/gocui"
89
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -329,7 +330,15 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
329330
)
330331

331332
if suggestedBranchName == "" {
332-
suggestedBranchName = self.c.UserConfig().Git.BranchPrefix
333+
var err error
334+
335+
suggestedBranchName, err = utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{
336+
"runCommand": self.c.Git().Custom.TemplateFunctionRunCommand,
337+
})
338+
if err != nil {
339+
return err
340+
}
341+
suggestedBranchName = strings.ReplaceAll(suggestedBranchName, "\t", " ")
333342
}
334343

335344
refresh := func() error {

pkg/gui/services/custom_commands/handler_creator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptR
246246
}
247247

248248
funcs := template.FuncMap{
249-
"quote": self.c.OS().Quote,
249+
"quote": self.c.OS().Quote,
250+
"runCommand": self.c.Git().Custom.TemplateFunctionRunCommand,
250251
}
251252

252253
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package branch
2+
3+
import (
4+
"github.com/jesseduffield/lazygit/pkg/config"
5+
. "github.com/jesseduffield/lazygit/pkg/integration/components"
6+
)
7+
8+
var NewBranchWithPrefixUsingRunCommand = NewIntegrationTest(NewIntegrationTestArgs{
9+
Description: "Creating a new branch with a branch prefix using a runCommand",
10+
ExtraCmdArgs: []string{},
11+
Skip: false,
12+
SetupConfig: func(cfg *config.AppConfig) {
13+
cfg.GetUserConfig().Git.BranchPrefix = "myprefix/{{ runCommand \"echo dynamic\" }}/"
14+
},
15+
SetupRepo: func(shell *Shell) {
16+
shell.
17+
EmptyCommit("commit 1")
18+
},
19+
Run: func(t *TestDriver, keys config.KeybindingConfig) {
20+
t.Views().Commits().
21+
Focus().
22+
Lines(
23+
Contains("commit 1").IsSelected(),
24+
).
25+
SelectNextItem().
26+
Press(keys.Universal.New).
27+
Tap(func() {
28+
t.ExpectPopup().Prompt().
29+
Title(Contains("New branch name")).
30+
InitialText(Equals("myprefix/dynamic/")).
31+
Type("my-branch").
32+
Confirm()
33+
t.Git().CurrentBranchName("myprefix/dynamic/my-branch")
34+
})
35+
},
36+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package custom_commands
2+
3+
import (
4+
"github.com/jesseduffield/lazygit/pkg/config"
5+
. "github.com/jesseduffield/lazygit/pkg/integration/components"
6+
)
7+
8+
var RunCommand = NewIntegrationTest(NewIntegrationTestArgs{
9+
Description: "Using a custom command that uses runCommand template function in a prompt step",
10+
ExtraCmdArgs: []string{},
11+
Skip: false,
12+
SetupRepo: func(shell *Shell) {
13+
shell.EmptyCommit("blah")
14+
},
15+
SetupConfig: func(cfg *config.AppConfig) {
16+
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
17+
{
18+
Key: "a",
19+
Context: "localBranches",
20+
Command: `git checkout {{.Form.Branch}}`,
21+
Prompts: []config.CustomCommandPrompt{
22+
{
23+
Key: "Branch",
24+
Type: "input",
25+
Title: "Enter a branch name",
26+
InitialValue: "myprefix/{{ runCommand \"echo dynamic\" }}/",
27+
},
28+
},
29+
},
30+
}
31+
},
32+
Run: func(t *TestDriver, keys config.KeybindingConfig) {
33+
t.Views().Branches().
34+
Focus().
35+
Press("a")
36+
37+
t.ExpectPopup().Prompt().
38+
Title(Equals("Enter a branch name")).
39+
InitialText(Contains("myprefix/dynamic/")).
40+
Confirm()
41+
},
42+
})

pkg/integration/tests/test_list.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ var tests = []*components.IntegrationTest{
5151
branch.NewBranchFromRemoteTrackingDifferentName,
5252
branch.NewBranchFromRemoteTrackingSameName,
5353
branch.NewBranchWithPrefix,
54+
branch.NewBranchWithPrefixUsingRunCommand,
5455
branch.OpenPullRequestInvalidTargetRemoteName,
5556
branch.OpenPullRequestNoUpstream,
5657
branch.OpenPullRequestSelectRemoteAndTargetBranch,
@@ -153,6 +154,7 @@ var tests = []*components.IntegrationTest{
153154
custom_commands.MenuFromCommandsOutput,
154155
custom_commands.MultipleContexts,
155156
custom_commands.MultiplePrompts,
157+
custom_commands.RunCommand,
156158
custom_commands.SelectedCommit,
157159
custom_commands.SelectedCommitRange,
158160
custom_commands.SelectedPath,

0 commit comments

Comments
 (0)