この記事は、なんか作ろうの会 Advent Calendar 2024 の3日目の記事として書かれました。
結論
Git管理しているリポジトリで、リポジトリのルートの絶対パスを環境に依らず取得するには、以下のコマンドが便利です。
git rev-parse --show-toplevel
はじめに
Git管理しているリポジトリに何らかのスクリプトを配置したとき、リポジトリのルートの絶対パスが欲しくなることがよくありませんか? 私はよくあります。
たとえば、スクリプト内で別のディレクトリやファイルを処理するとき、リポジトリのルートの絶対パスがあればそこを起点にリポジトリ内のパスを容易に構築できて可読性もよいです。
- スクリプトを実行するディレクトリによらない
- スクリプトが存在するディレクトリによらない
このような条件を満たすパスの取得方法が望まれます。
ケーススタディ
前提
以下のようなディレクトリ構造があります。
$ pwd
/tmp/20241203-advent
$ tree
.
├── deep
│ └── path
│ └── script.sh
├── input
│ └── sample.txt
└── output
└── result.txt
5 directories, 3 files
このとき、deep/path/script.sh を使って input/sample.txt の内容を output/result.txt に追記することを考えます。
また、script.sh は後々べつのディレクトリに動かす可能性があります。
案1. 相対パスを使う
#!/bin/bash
cat ../../input/sample.txt >> ../../output/result.txt
上記のコマンドは、deep/path にcdしていて、 ./script.sh などと実行されることが前提です。
スクリプトの実行位置によってエラーになるので、あまり良い手とは言えないでしょう。
案2. dirname や realpath を使う
#!/bin/bash
cat "$(dirname $(dirname $(dirname $0)))/input/sample.txt" >> "$(dirname $(dirname $(dirname $0)))/output/result.txt"
Gitリポジトリのルートまで dirname でさかのぼる案です。どのディレクトリにいる状態でスクリプトを実行するか、には結果が左右されなくなりましたが、今度はスクリプトが存在する位置によってエラーになる可能性が出てきてしまいます。
たとえば deep/path/script.sh を deep/path/hoge/script.sh に移動したら、このスクリプトは成立しなくなります。
案3. git rev-parse --show-toplevel を使う
#!/bin/bash
cat "$(git rev-parse --show-toplevel)/input/sample.txt" >> "$(git rev-parse --show-toplevel)/output/result.txt"
満を持して、gitコマンドを使うパターンです。このパターンであれば、リポジトリのルートであればどこで実行しても結果が変わりません。$(git rev-parse --show-toplevel) がリポジトリのルートの絶対パスで展開されます。スクリプトをリポジトリ内の別のディレクトリに移動しても、返されるパスは変わらないので保守性が高いです。
また、gitのtoplevelを表示しようとしている、というのはコマンドについての知識が無くても何となく雰囲気から読み取ることができ、可読性も高いです。
制約として実行位置および実行されるスクリプトがともにリポジトリのルート以下の階層である、という前提は置かれます。しかし、むしろgitリポジトリ外で実行したりgitリポジトリ外のディレクトリにアクセスしたりする方が特殊かと思われますので、問題になることは少ないでしょう。
おわりに
Gitリポジトリのルートの絶対パスは意外と欲しくなることが多いのではないかと個人的には思っています。少なくとも自分は表題のコマンドをそらんじられるようになるくらいには欲しいと思う機会がありました。
ちょっとしたテクニック的なことではありますが、どなたかのお役に立てばとても嬉しく思います。