Configure a Git Shell Prompt Under Nix

Michael Lynch

在 Nix 下配置 Git Shell 提示符

原文由 Michael Lynch 发布,订阅该博客

最近我读了 Julia Evans 的关于 git 的最新一期 zine,其中有一个小技巧是将终端 shell 提示符配置为显示 git 状态

Julia 的终端提示符是这样的:

~/work/homepage (main) $

main 是 Julia 当前所在的 git 分支。当她正在执行 bisect 或 merge 这类 git 操作时,终端会变成这样:

~/work/homepage (main|MERGING) $

我以前从没想过要自定义 shell 提示符,但立刻就意识到了它的价值。

我经常要运行 git status 来确认自己在哪个分支上,而且也经常忘记自己正处于 git 合并的中途。

在终端中设置 git 提示符

Julia 的 zine 说服了我把终端 shell 自定义为显示 git 状态,但具体怎么做却不太清楚。看起来,我需要一个叫 git-prompt.sh 的文件,但在我安装了 git 2.30.2 的 Debian 系统上却找不到它:

$ find / -name 'git-prompt.sh' -type f -print
# No results

另一个办法是从 GitHub 下载一份官方的 git-prompt.sh

# Download git-prompt.sh
curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh > ~/.git-prompt.sh

# Add it to my .bashrc
echo '. ~/.git-prompt.sh' >> ~/.bashrc

# Reload .bashrc
. ~/.bashrc

为了测试它是否生效,我创建了一个临时的 git 仓库:

mkdir examplerepo && \
  cd examplerepo && \
  git init

如果 git-prompt.sh 正常工作,__git_ps1 命令应该会显示当前的 git 分支:

$ __git_ps1
(master)

太好了,生效了。

但我不想每次都手动调用 __git_ps1——我想像 Julia Evans 演示的那样,让分支自动出现在终端提示符里。

在终端 shell 中显示 git 状态最简单的方法,就是在 bash 会话中重写 PS1 变量:

$ export PS1='$(__git_ps1 "(%s)") \$ '
(master) $

现在,当我执行 git 命令时,终端会随之变化以反映当前状态:

(master) $ echo 'hi' > hello.txt && git add -A && git commit -m "Test commit"
[master (root-commit) 7b14d2a] Test commit
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt
(master) $ git checkout -b branchA && echo 'hey' > hello.txt && git add -A && git commit -m "test A"
Switched to a new branch 'branchA'
[branchA 65b3d63] test A
 1 file changed, 1 insertion(+), 1 deletion(-)
(branchA) $ git checkout master
Switched to branch 'master'
(master) $ git checkout -b branchB && echo 'hello' > hello.txt && git add -A && git commit -m "test B"
Switched to a new branch 'branchB'
[branchB 198c875] test B
 1 file changed, 1 insertion(+), 1 deletion(-)
(branchB) $ git merge branchA
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.
(branchB|MERGING) $

不错,看起来是生效了。

但这样一来,shell 提示符里就看不到当前工作目录了,于是我把 PS1 改成了这样:

$ export PS1='\w $(__git_ps1 "(%s)") \$ '
~/examplerepo (branchB|MERGING) $

我还想让提示符带上好看的颜色,于是也加上了:

export PS1='\[\033[01;34m\]\w\[\033[00m\]\[\033[01;32m\]$(__git_ps1 " (%s)")\[\033[00m\]\$ '

到了这一步,编辑 PS1 已经变得相当复杂了,我建议使用Bash Prompt Generator这类免费工具来帮你生成命令并自动加上颜色。

这是我的最终终端提示符效果:

~/examplerepo (branchB|MERGING)$

为了让这个改动永久生效,我把那行 export PS1 加到了 ~/.bashrc 文件中,这样每次启动新的 bash shell 时都会使用我自定义的终端提示符。

在 Nix 上把 git 集成到 bash shell 提示符中

上面这些步骤的问题在于,我使用的是NixHome Manager,它们虽然很好用,却让 bash 配置的方方面面都变得更复杂了。我不能简单地往 .bashrc 里加一行,因为下次运行 Home Manager 时,它会把我的改动覆盖掉。

幸运的是,Jeff Kreeftmeijer 的这篇文章帮我搞清楚了如何把 git-prompt.sh 的方案适配到 Nix 上。

我在 Home Manager 的 home.nix 文件中做了如下修改:

  programs.bash = {
    enable = true;
    enableCompletion = true;
    initExtra = ''
      # Load __git_ps1 bash command.
      . ~/.nix-profile/share/git/contrib/completion/git-prompt.sh
      # Also load git command completions for bash.
      . ~/.nix-profile/share/git/contrib/completion/git-completion.bash

      # Show git branch status in terminal shell.
      export PS1='\[\033[01;34m\]\w\[\033[00m\]\[\033[01;32m\]$(__git_ps1 " (%s)")\[\033[00m\]\$ '
    '';
  };

更新完文件后,我运行了 home-manager switch,它更新了我的 .bashrc,一切都按预期工作了。

清理开发环境中 Nix 终端的多余信息

不过,我自定义的 bash 终端提示符还有最后一个小问题。

我所有的开发工作都是在按项目隔离的 Nix shell中进行的。当我运行 nix develop 来加载开发环境时,发现 Nix 在我的终端前额外加了一段前缀:

~/examplerepo (branchB|MERGING) $ nix develop
...
(nix:nix-shell-env) ~/examplerepo (branchB|MERGING) $
^^^^^^^^^^^^^^^^^^^ why?

这个前缀占了很大一块地方。现在的终端提示符几乎有一半只是在告诉我正处于 Nix 环境中。

在某些项目中,Nix 的 shell 提示符前缀甚至更长。这是我在PicoShare中看到的情况:

~/examplerepo (branchB|MERGING) $ nix develop
...
(nix:nix-shell-x86_64-unknown-linux-musl-env) ~/picoshare (master)$
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ too much!

这个 shell 前缀占用了大量屏幕空间。怎么才能去掉它呢?

原来 nix develop 有一个 --bash-prompt-prefix 参数,可以让我像这样自定义终端 shell 前缀:

$ nix develop --bash-prompt-prefix 'nix:'
nix:~/picoshare (master)$

如果完全不想要前缀,也可以把它设为空字符串:

$ nix develop --bash-prompt-prefix ''
~/picoshare (master)$

但显然,我不想每次运行 nix develop 时都额外加上这个命令行参数。

我发现这些多余信息其实来自我的 nix.conf 文件,在我的 Debian 系统上它位于 /etc/nix/nix.conf。我猜这个前缀是Determinate Systems Nix installer帮我设置的:

$ grep 'bash-prompt-prefix' /etc/nix/nix.conf
bash-prompt-prefix = (nix:$name)

我把那一行改成了更简洁的 nix: 前缀:

bash-prompt-prefix = nix:

然后,我重启了 Nix 以使改动生效:

sudo systemctl restart nix-daemon

现在,当我处于 Nix 开发环境中时,会看到这样一个简短而有用的前缀:

nix:~/picoshare (sqlite-performance)$

总结

在进行软件开发时,自定义终端 shell、让每个 shell 提示符都能显示所在目录的 git 状态,会很有帮助。

希望这篇文章能帮助那些想在普通 bash 系统,以及由 Nix 和 Home Manager 管理 bash 配置的系统上实现类似配置的人。

本文章由 muse-spark-1.2-contributor 进行翻译

评论