Skip to content

Commit f7787fc

Browse files
authored
Better support for shell aliases (#4385)
- **PR Description** In version 0.45.0 we started to use an interactive shell for running shell commands (see #4159). The idea was that this allows users to use their aliases and shell functions in lazygit without having to do any additional configuration. Unfortunately, this hasn't worked out well. For some users this resulted in lazygit hanging in the background upon trying to return from the shell command; we tried various fixes for this (see #4126, #4159, and #4350), but some users still have this problem (e.g. #4320). Also, starting an interactive shell can be a lot slower than starting a non-interactive one, depending on how much happens in the `.bashrc` or `.zshrc` file. For example, on my machine calling `zsh -ic true` takes 600ms, whereas `zsh -c true` takes less than 2ms. This is too high of a price to pay for using shell aliases, especially when _all_ users have to pay it, even those who don't care about using their aliases in lazygit. This PR reverts all commits related to interactive shells, and instead introduces a different approach: we let users specify a shell aliases file that will be sourced before running a command. The downside is that it doesn't work transparently out of the box, but requires configuration, and it may also require that users restructure their shell startup file(s) if they currently only have a single big one. The advantage is that only users who actually want to use aliases or functions are affected, and that we can now use this mechanism not only for shell commands, but also for custom commands and for calling the editor (some users have asked for this in the past).
2 parents 959f932 + d13306a commit f7787fc

File tree

13 files changed

+86
-83
lines changed

13 files changed

+86
-83
lines changed

docs/Config.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ os:
442442
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
443443
readFromClipboardCmd: ""
444444

445+
# A shell startup file containing shell aliases or shell functions. This will be sourced before running any shell commands, so that shell functions are available in the `:` command prompt or even in custom commands.
446+
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands
447+
shellFunctionsFile: ""
448+
445449
# If true, don't display introductory popups upon opening Lazygit.
446450
disableStartupPopups: false
447451

@@ -740,6 +744,21 @@ The `editInTerminal` option is used to decide whether lazygit needs to suspend i
740744

741745
Contributions of new editor presets are welcome; see the `getPreset` function in [`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
742746

747+
## Using aliases or functions in shell commands
748+
749+
Lazygit has a command prompt (`:`) for quickly executing shell commands without having to quit lazygit or switch to a different terminal. Most people find it convenient to have their usual shell aliases or shell functions available at this prompt. To achieve this, put your alias definitions in a separate shell startup file (which you source from your normal startup file, i.e. from `.bashrc` or `.zshrc`), and then tell lazygit about this file like so:
750+
751+
```yml
752+
os:
753+
shellFunctionsFile: ~/.my_aliases.sh
754+
```
755+
756+
For many people it might work well enough to use their entire shell config file (`~/.bashrc` or `~/.zshrc`) as the `shellFunctionsFile`, but these config files typically do a lot more than defining aliases (e.g. initialize the completion system, start an ssh-agent, etc.) and this may unnecessarily delay execution of shell commands.
757+
758+
When using zsh, aliases can't be used here, but functions can. It is easy to convert your existing aliases into functions, just change `alias l="ls -la"` to `l() ls -la`, for example. This way it will work as before both in the shell and in lazygit.
759+
760+
Note that the shell aliases file is not only used when executing shell commands, but also for [custom commands](Custom_Command_Keybindings.md), and when opening a file in the editor.
761+
743762
## Overriding default config file location
744763

745764
To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions.

pkg/commands/git_cmd_obj_builder.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,8 @@ func (self *gitCmdObjBuilder) New(args []string) oscommands.ICmdObj {
3434
return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar)
3535
}
3636

37-
func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
38-
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
39-
}
40-
41-
func (self *gitCmdObjBuilder) NewInteractiveShell(cmdStr string) oscommands.ICmdObj {
42-
return self.innerBuilder.NewInteractiveShell(cmdStr).AddEnvVars(defaultEnvVar)
37+
func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) oscommands.ICmdObj {
38+
return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar)
4339
}
4440

4541
func (self *gitCmdObjBuilder) Quote(str string) string {

pkg/commands/oscommands/cmd_obj_builder.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ type ICmdObjBuilder interface {
1313
// NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object.
1414
New(args []string) ICmdObj
1515
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
16-
NewShell(commandStr string) ICmdObj
17-
// Like NewShell, but uses the user's shell rather than "bash", and passes -i to it
18-
NewInteractiveShell(commandStr string) ICmdObj
16+
// shellFunctionsFile is an optional file path that will be sourced before executing the command. Callers should pass
17+
// the value of UserConfig.OS.ShellFunctionsFile.
18+
NewShell(commandStr string, shellFunctionsFile string) ICmdObj
1919
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
2020
Quote(str string) string
2121
}
@@ -44,20 +44,16 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
4444
}
4545
}
4646

47-
func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
47+
func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) ICmdObj {
48+
if len(shellFunctionsFile) > 0 {
49+
commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr)
50+
}
4851
quotedCommand := self.quotedCommandString(commandStr)
4952
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
5053

5154
return self.New(cmdArgs)
5255
}
5356

54-
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
55-
quotedCommand := self.quotedCommandString(commandStr + self.platform.InteractiveShellExit)
56-
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
57-
58-
return self.New(cmdArgs)
59-
}
60-
6157
func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
6258
// Windows does not seem to like quotes around the command
6359
if self.platform.OS == "windows" {

pkg/commands/oscommands/dummies.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,11 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
5151
}
5252

5353
var dummyPlatform = &Platform{
54-
OS: "darwin",
55-
Shell: "bash",
56-
InteractiveShell: "bash",
57-
ShellArg: "-c",
58-
InteractiveShellArg: "-i",
59-
InteractiveShellExit: "; exit $?",
60-
OpenCommand: "open {{filename}}",
61-
OpenLinkCommand: "open {{link}}",
54+
OS: "darwin",
55+
Shell: "bash",
56+
ShellArg: "-c",
57+
OpenCommand: "open {{filename}}",
58+
OpenLinkCommand: "open {{link}}",
6259
}
6360

6461
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {

pkg/commands/oscommands/os.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@ type OSCommand struct {
3535

3636
// Platform stores the os state
3737
type Platform struct {
38-
OS string
39-
Shell string
40-
InteractiveShell string
41-
ShellArg string
42-
InteractiveShellArg string
43-
InteractiveShellExit string
44-
OpenCommand string
45-
OpenLinkCommand string
38+
OS string
39+
Shell string
40+
ShellArg string
41+
PrefixForShellFunctionsFile string
42+
OpenCommand string
43+
OpenLinkCommand string
4644
}
4745

4846
// NewOSCommand os command runner
@@ -93,7 +91,7 @@ func (c *OSCommand) OpenFile(filename string) error {
9391
"filename": c.Quote(filename),
9492
}
9593
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
96-
return c.Cmd.NewShell(command).Run()
94+
return c.Cmd.NewShell(command, c.UserConfig().OS.ShellFunctionsFile).Run()
9795
}
9896

9997
func (c *OSCommand) OpenLink(link string) error {
@@ -110,7 +108,7 @@ func (c *OSCommand) OpenLink(link string) error {
110108
}
111109

112110
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
113-
return c.Cmd.NewShell(command).Run()
111+
return c.Cmd.NewShell(command, c.UserConfig().OS.ShellFunctionsFile).Run()
114112
}
115113

116114
// Quote wraps a message in platform-specific quotation marks
@@ -299,7 +297,7 @@ func (c *OSCommand) CopyToClipboard(str string) error {
299297
cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
300298
"text": c.Cmd.Quote(str),
301299
})
302-
return c.Cmd.NewShell(cmdStr).Run()
300+
return c.Cmd.NewShell(cmdStr, c.UserConfig().OS.ShellFunctionsFile).Run()
303301
}
304302

305303
return clipboard.WriteAll(str)
@@ -310,7 +308,7 @@ func (c *OSCommand) PasteFromClipboard() (string, error) {
310308
var err error
311309
if c.UserConfig().OS.CopyToClipboardCmd != "" {
312310
cmdStr := c.UserConfig().OS.ReadFromClipboardCmd
313-
s, err = c.Cmd.NewShell(cmdStr).RunWithOutput()
311+
s, err = c.Cmd.NewShell(cmdStr, c.UserConfig().OS.ShellFunctionsFile).RunWithOutput()
314312
} else {
315313
s, err = clipboard.ReadAll()
316314
}
@@ -360,5 +358,5 @@ func (c *OSCommand) UpdateWindowTitle() error {
360358
return getWdErr
361359
}
362360
argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit")
363-
return c.Cmd.NewShell(argString).Run()
361+
return c.Cmd.NewShell(argString, c.UserConfig().OS.ShellFunctionsFile).Run()
364362
}

pkg/commands/oscommands/os_default_platform.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,18 @@ import (
1212
func GetPlatform() *Platform {
1313
shell := getUserShell()
1414

15-
interactiveShell := shell
16-
interactiveShellArg := "-i"
17-
interactiveShellExit := "; exit $?"
18-
19-
if strings.HasSuffix(shell, "fish") {
20-
interactiveShellExit = "; exit $status"
21-
} else if !(strings.HasSuffix(shell, "bash") || strings.HasSuffix(shell, "zsh")) {
22-
interactiveShell = "bash"
23-
interactiveShellArg = ""
24-
interactiveShellExit = ""
15+
prefixForShellFunctionsFile := ""
16+
if strings.HasSuffix(shell, "bash") {
17+
prefixForShellFunctionsFile = "shopt -s expand_aliases\n"
2518
}
2619

2720
return &Platform{
28-
OS: runtime.GOOS,
29-
Shell: "bash",
30-
InteractiveShell: interactiveShell,
31-
ShellArg: "-c",
32-
InteractiveShellArg: interactiveShellArg,
33-
InteractiveShellExit: interactiveShellExit,
34-
OpenCommand: "open {{filename}}",
35-
OpenLinkCommand: "open {{link}}",
21+
OS: runtime.GOOS,
22+
Shell: shell,
23+
ShellArg: "-c",
24+
PrefixForShellFunctionsFile: prefixForShellFunctionsFile,
25+
OpenCommand: "open {{filename}}",
26+
OpenLinkCommand: "open {{link}}",
3627
}
3728
}
3829

pkg/commands/oscommands/os_windows.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ package oscommands
22

33
func GetPlatform() *Platform {
44
return &Platform{
5-
OS: "windows",
6-
Shell: "cmd",
7-
InteractiveShell: "cmd",
8-
ShellArg: "/c",
9-
InteractiveShellArg: "",
10-
InteractiveShellExit: "",
5+
OS: "windows",
6+
Shell: "cmd",
7+
ShellArg: "/c",
118
}
129
}

pkg/config/user_config.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,18 @@ type OSConfig struct {
581581
// Command for opening a link. Should contain "{{link}}".
582582
OpenLink string `yaml:"openLink,omitempty"`
583583

584+
// CopyToClipboardCmd is the command for copying to clipboard.
585+
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
586+
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
587+
588+
// ReadFromClipboardCmd is the command for reading the clipboard.
589+
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
590+
ReadFromClipboardCmd string `yaml:"readFromClipboardCmd,omitempty"`
591+
592+
// A shell startup file containing shell aliases or shell functions. This will be sourced before running any shell commands, so that shell functions are available in the `:` command prompt or even in custom commands.
593+
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands
594+
ShellFunctionsFile string `yaml:"shellFunctionsFile"`
595+
584596
// --------
585597

586598
// The following configs are all deprecated and kept for backward
@@ -603,14 +615,6 @@ type OSConfig struct {
603615
// OpenLinkCommand is the command for opening a link
604616
// Deprecated: use OpenLink instead.
605617
OpenLinkCommand string `yaml:"openLinkCommand,omitempty" jsonschema:"deprecated"`
606-
607-
// CopyToClipboardCmd is the command for copying to clipboard.
608-
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
609-
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
610-
611-
// ReadFromClipboardCmd is the command for reading the clipboard.
612-
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
613-
ReadFromClipboardCmd string `yaml:"readFromClipboardCmd,omitempty"`
614618
}
615619

616620
type CustomCommandAfterHook struct {

pkg/gui/controllers/helpers/files_helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ func (self *FilesHelper) OpenDirInEditor(path string) error {
7171
func (self *FilesHelper) callEditor(cmdStr string, suspend bool) error {
7272
if suspend {
7373
return self.c.RunSubprocessAndRefresh(
74-
self.c.OS().Cmd.NewShell(cmdStr),
74+
self.c.OS().Cmd.NewShell(cmdStr, self.c.UserConfig().OS.ShellFunctionsFile),
7575
)
7676
}
7777

78-
return self.c.OS().Cmd.NewShell(cmdStr).Run()
78+
return self.c.OS().Cmd.NewShell(cmdStr, self.c.UserConfig().OS.ShellFunctionsFile).Run()
7979
}
8080

8181
func (self *FilesHelper) OpenFile(filename string) error {

pkg/gui/controllers/shell_command_action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (self *ShellCommandAction) Call() error {
3131

3232
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
3333
return self.c.RunSubprocessAndRefresh(
34-
self.c.OS().Cmd.NewInteractiveShell(command),
34+
self.c.OS().Cmd.NewShell(command, self.c.UserConfig().OS.ShellFunctionsFile),
3535
)
3636
},
3737
HandleDeleteSuggestion: func(index int) error {

0 commit comments

Comments
 (0)