From c7feba9bb1e629a06bc8d1cf6502833976ad3f3d Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 4 Apr 2025 08:12:20 +0200 Subject: [PATCH 1/2] Move test from commit to branch --- .../tests/{commit => branch}/new_branch_with_prefix.go | 2 +- pkg/integration/tests/test_list.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename pkg/integration/tests/{commit => branch}/new_branch_with_prefix.go (98%) diff --git a/pkg/integration/tests/commit/new_branch_with_prefix.go b/pkg/integration/tests/branch/new_branch_with_prefix.go similarity index 98% rename from pkg/integration/tests/commit/new_branch_with_prefix.go rename to pkg/integration/tests/branch/new_branch_with_prefix.go index 2b489648f..61da06f94 100644 --- a/pkg/integration/tests/commit/new_branch_with_prefix.go +++ b/pkg/integration/tests/branch/new_branch_with_prefix.go @@ -1,4 +1,4 @@ -package commit +package branch import ( "github.com/jesseduffield/lazygit/pkg/config" diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 2590e03ca..0cd16fa2b 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -50,6 +50,7 @@ var tests = []*components.IntegrationTest{ branch.NewBranchAutostash, branch.NewBranchFromRemoteTrackingDifferentName, branch.NewBranchFromRemoteTrackingSameName, + branch.NewBranchWithPrefix, branch.OpenPullRequestInvalidTargetRemoteName, branch.OpenPullRequestNoUpstream, branch.OpenPullRequestSelectRemoteAndTargetBranch, @@ -118,7 +119,6 @@ var tests = []*components.IntegrationTest{ commit.History, commit.HistoryComplex, commit.NewBranch, - commit.NewBranchWithPrefix, commit.PasteCommitMessage, commit.PasteCommitMessageOverExisting, commit.PreserveCommitMessage, From 12820481e6100a21548fcf3f036d8a965c3eb104 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 31 Mar 2025 15:21:44 +0200 Subject: [PATCH 2/2] 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\"" }}/' ``` --- docs/Config.md | 9 ++++ docs/Custom_Command_Keybindings.md | 18 ++++++++ pkg/commands/git_commands/custom.go | 22 +++++++++- pkg/gui/controllers/helpers/refs_helper.go | 11 ++++- .../custom_commands/handler_creator.go | 3 +- ...ew_branch_with_prefix_using_run_command.go | 36 ++++++++++++++++ .../tests/custom_commands/run_command.go | 42 +++++++++++++++++++ pkg/integration/tests/test_list.go | 2 + 8 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go create mode 100644 pkg/integration/tests/custom_commands/run_command.go diff --git a/docs/Config.md b/docs/Config.md index b7fcadc7a..e9a8dab2a 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -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: diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md index 265e86eb9..62c84d28e 100644 --- a/docs/Custom_Command_Keybindings.md +++ b/docs/Custom_Command_Keybindings.md @@ -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) diff --git a/pkg/commands/git_commands/custom.go b/pkg/commands/git_commands/custom.go index ac8b0b1c9..c58a2baa7 100644 --- a/pkg/commands/git_commands/custom.go +++ b/pkg/commands/git_commands/custom.go @@ -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 +} diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go index c78b42b30..296d66aca 100644 --- a/pkg/gui/controllers/helpers/refs_helper.go +++ b/pkg/gui/controllers/helpers/refs_helper.go @@ -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 { diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 5f647606b..58e4d0aff 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -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) } diff --git a/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go b/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go new file mode 100644 index 000000000..1d41eda63 --- /dev/null +++ b/pkg/integration/tests/branch/new_branch_with_prefix_using_run_command.go @@ -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") + }) + }, +}) diff --git a/pkg/integration/tests/custom_commands/run_command.go b/pkg/integration/tests/custom_commands/run_command.go new file mode 100644 index 000000000..107b9e285 --- /dev/null +++ b/pkg/integration/tests/custom_commands/run_command.go @@ -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() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 0cd16fa2b..d76292526 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -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,