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 merge 的過程中。

在終端機中設定 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 進行翻譯

留言