Skip to content

Commit 76b40ae

Browse files
committed
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 1e6d020 commit 76b40ae

File tree

6 files changed

+138
-6
lines changed

6 files changed

+138
-6
lines changed

Diff for: docs/Config.md

+9
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:

Diff for: docs/Custom_Command_Keybindings.md

+18
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)

Diff for: pkg/gui/controllers/helpers/refs_helper.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package helpers
22

33
import (
44
"fmt"
5-
"strings"
6-
75
"github.com/jesseduffield/gocui"
86
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
97
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -12,6 +10,8 @@ import (
1210
"github.com/jesseduffield/lazygit/pkg/gui/types"
1311
"github.com/jesseduffield/lazygit/pkg/utils"
1412
"github.com/samber/lo"
13+
"strings"
14+
"text/template"
1515
)
1616

1717
type IRefsHelper interface {
@@ -329,7 +329,27 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
329329
)
330330

331331
if suggestedBranchName == "" {
332-
suggestedBranchName = self.c.UserConfig().Git.BranchPrefix
332+
var err error
333+
334+
suggestedBranchName, err = utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{
335+
"runCommand": func(command string) (string, error) {
336+
output, err := self.c.Git().Custom.RunWithOutput(command)
337+
if err != nil {
338+
return "", err
339+
}
340+
341+
output = strings.TrimRight(output, "\r\n")
342+
343+
if strings.ContainsAny(output, "\r\n\t ") {
344+
return "", fmt.Errorf("command output contains whitespace characters: %s", output)
345+
}
346+
347+
return output, nil
348+
},
349+
})
350+
if err != nil {
351+
return err
352+
}
333353
}
334354

335355
refresh := func() error {

Diff for: pkg/gui/services/custom_commands/handler_creator.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ package custom_commands
33
import (
44
"errors"
55
"fmt"
6-
"strings"
7-
"text/template"
8-
96
"github.com/jesseduffield/gocui"
107
"github.com/jesseduffield/lazygit/pkg/config"
118
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
129
"github.com/jesseduffield/lazygit/pkg/gui/style"
1310
"github.com/jesseduffield/lazygit/pkg/gui/types"
1411
"github.com/jesseduffield/lazygit/pkg/utils"
1512
"github.com/samber/lo"
13+
"strings"
14+
"text/template"
1615
)
1716

1817
// takes a custom command and returns a function that will be called when the corresponding user-defined keybinding is pressed
@@ -247,6 +246,19 @@ func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptR
247246

248247
funcs := template.FuncMap{
249248
"quote": self.c.OS().Quote,
249+
"runCommand": func(command string) (string, error) {
250+
output, err := self.c.Git().Custom.RunWithOutput(command)
251+
if err != nil {
252+
return "", err
253+
}
254+
output = strings.TrimRight(output, "\r\n")
255+
256+
if strings.Contains(output, "\r\n") {
257+
return "", fmt.Errorf("command output contains newlines: %s", output)
258+
}
259+
260+
return output, nil
261+
},
250262
}
251263

252264
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 from a commit with a default name",
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+
branchName := "my-branch-name"
29+
t.ExpectPopup().Prompt().Title(Contains("New branch name")).Type(branchName).Confirm()
30+
t.Git().CurrentBranchName("myprefix/dynamic/" + branchName)
31+
})
32+
},
33+
})

Diff for: pkg/integration/tests/custom_commands/run_command.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
SetupConfig: func(cfg *config.AppConfig) {
13+
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
14+
{
15+
Key: "a",
16+
Context: "localBranches",
17+
Command: `git checkout {{.Form.Branch}}`,
18+
Prompts: []config.CustomCommandPrompt{
19+
{
20+
Key: "Branch",
21+
Type: "input",
22+
Title: "Enter a branch name",
23+
InitialValue: "myprefix/{{ runCommand \"echo dynamic\" }}/",
24+
},
25+
},
26+
},
27+
}
28+
},
29+
Run: func(t *TestDriver, keys config.KeybindingConfig) {
30+
t.Views().Branches().
31+
Focus().
32+
Press("a")
33+
34+
t.ExpectPopup().Prompt().
35+
Title(Equals("Enter a branch name")).
36+
Type("three").
37+
InitialText(Contains("myprefix/dynamic/")).
38+
Confirm()
39+
},
40+
})

0 commit comments

Comments
 (0)