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\"" }}/'
```
This commit is contained in:
Ruud Kamphuis 2025-03-31 15:21:44 +02:00 committed by Stefan Haller
parent c7feba9bb1
commit 12820481e6
8 changed files with 140 additions and 3 deletions

View file

@ -1027,6 +1027,15 @@ git:
branchPrefix: "firstlast/"
```
It's possible to use a dynamic prefix by using the `runCommand` function:
```yaml
git:
branchPrefix: "firstlast/{{ runCommand "date +\"%Y/%-m\"" }}/"
```
This would produce something like: `firstlast/2025/4/`
## Custom git log command
You can override the `git log` command that's used to render the log of the selected branch like so:

View file

@ -320,6 +320,24 @@ We don't support accessing all elements of a range selection yet. We might add t
command: "git format-patch {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}}"
```
We support the following functions:
### Quoting
Quote wraps a string in quotes with necessary escaping for the current platform.
```
git {{.SelectedFile.Name | quote}}
```
### Running a command
Runs a command and returns the output. If the command outputs more than a single line, it will produce an error.
```
initialValue: "username/{{ runCommand "date +\"%Y/%-m\"" }}/"
```
## Keybinding collisions
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)

View file

@ -1,6 +1,11 @@
package git_commands
import "github.com/mgutz/str"
import (
"fmt"
"strings"
"github.com/mgutz/str"
)
type CustomCommands struct {
*GitCommon
@ -18,3 +23,18 @@ func NewCustomCommands(gitCommon *GitCommon) *CustomCommands {
func (self *CustomCommands) RunWithOutput(cmdStr string) (string, error) {
return self.cmd.New(str.ToArgv(cmdStr)).RunWithOutput()
}
// A function that can be used as a "runCommand" entry in the template.FuncMap of templates.
func (self *CustomCommands) TemplateFunctionRunCommand(cmdStr string) (string, error) {
output, err := self.RunWithOutput(cmdStr)
if err != nil {
return "", err
}
output = strings.TrimRight(output, "\r\n")
if strings.Contains(output, "\r\n") {
return "", fmt.Errorf("command output contains newlines: %s", output)
}
return output, nil
}

View file

@ -3,6 +3,7 @@ package helpers
import (
"fmt"
"strings"
"text/template"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@ -318,7 +319,15 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
)
if suggestedBranchName == "" {
suggestedBranchName = self.c.UserConfig().Git.BranchPrefix
var err error
suggestedBranchName, err = utils.ResolveTemplate(self.c.UserConfig().Git.BranchPrefix, nil, template.FuncMap{
"runCommand": self.c.Git().Custom.TemplateFunctionRunCommand,
})
if err != nil {
return err
}
suggestedBranchName = strings.ReplaceAll(suggestedBranchName, "\t", " ")
}
refresh := func() error {

View file

@ -246,7 +246,8 @@ func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptR
}
funcs := template.FuncMap{
"quote": self.c.OS().Quote,
"quote": self.c.OS().Quote,
"runCommand": self.c.Git().Custom.TemplateFunctionRunCommand,
}
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }

View file

@ -0,0 +1,36 @@
package branch
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var NewBranchWithPrefixUsingRunCommand = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Creating a new branch with a branch prefix using a runCommand",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.BranchPrefix = "myprefix/{{ runCommand \"echo dynamic\" }}/"
},
SetupRepo: func(shell *Shell) {
shell.
EmptyCommit("commit 1")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("commit 1").IsSelected(),
).
SelectNextItem().
Press(keys.Universal.New).
Tap(func() {
t.ExpectPopup().Prompt().
Title(Contains("New branch name")).
InitialText(Equals("myprefix/dynamic/")).
Type("my-branch").
Confirm()
t.Git().CurrentBranchName("myprefix/dynamic/my-branch")
})
},
})

View file

@ -0,0 +1,42 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var RunCommand = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using a custom command that uses runCommand template function in a prompt step",
ExtraCmdArgs: []string{},
Skip: false,
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("blah")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "a",
Context: "localBranches",
Command: `git checkout {{.Form.Branch}}`,
Prompts: []config.CustomCommandPrompt{
{
Key: "Branch",
Type: "input",
Title: "Enter a branch name",
InitialValue: "myprefix/{{ runCommand \"echo dynamic\" }}/",
},
},
},
}
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Press("a")
t.ExpectPopup().Prompt().
Title(Equals("Enter a branch name")).
InitialText(Contains("myprefix/dynamic/")).
Confirm()
},
})

View file

@ -51,6 +51,7 @@ var tests = []*components.IntegrationTest{
branch.NewBranchFromRemoteTrackingDifferentName,
branch.NewBranchFromRemoteTrackingSameName,
branch.NewBranchWithPrefix,
branch.NewBranchWithPrefixUsingRunCommand,
branch.OpenPullRequestInvalidTargetRemoteName,
branch.OpenPullRequestNoUpstream,
branch.OpenPullRequestSelectRemoteAndTargetBranch,
@ -154,6 +155,7 @@ var tests = []*components.IntegrationTest{
custom_commands.MenuFromCommandsOutput,
custom_commands.MultipleContexts,
custom_commands.MultiplePrompts,
custom_commands.RunCommand,
custom_commands.SelectedCommit,
custom_commands.SelectedCommitRange,
custom_commands.SelectedPath,