Skip to content

Commit 0e32c5e

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 0560754 commit 0e32c5e

File tree

7 files changed

+142
-6
lines changed

7 files changed

+142
-6
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/gui/controllers/helpers/refs_helper.go

Lines changed: 23 additions & 3 deletions
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 {

pkg/gui/services/custom_commands/handler_creator.go

Lines changed: 15 additions & 3 deletions
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) }
Lines changed: 33 additions & 0 deletions
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+
})
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)