Configure a Git Shell Prompt Under Nix

Michael Lynch

在 Nix 下設定 Git Shell 提示字元

我最近讀了 Julia Evans(茱莉亞·伊凡斯)關於 Git 的最新小誌,其中一個技巧是將終端機的 shell 提示字元設定為顯示 Git 狀態

茱莉亞的終端機提示字元看起來像這樣:

~/work/homepage (main) $

main 是茱莉亞目前的 Git 分支。當她正處於 bisect 或 merge 等 Git 操作的過程中時,終端機會變成這樣:

~/work/homepage (main|MERGING) $

我從來沒想過要自訂自己的 shell 提示字元,但我立刻就明白了它的價值。

我經常執行 git status 來確認自己在哪個分支上,而且常常忘記自己正處於 Git 合併的過程中。

在我的終端機中設定 Git 提示字元

茱莉亞的小誌說服了我將終端機的 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——我想要像茱莉亞展示的那樣,讓分支自動顯示在終端機的提示字元中。

要在終端機的 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 自訂為在每次提示字元中都能看到目錄的 Git 狀態,是很有幫助的。

希望這篇文章能幫助其他想在一般 bash 系統上,以及在由 Nix 和 Home Manager 管理 bash 設定的系統上,做出類似設定的人。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯