Gitベアリポジトリで作る 一風変わったdotfiles管理術

[atlassian-tutorial]:https://www.atlassian.com/git/tutorials/dotfiles
[hacker-news]:https://news.ycombinator.com/item?id=11070797

これは Atlassian が公開している Git チュートリアル記事 [The best way to store your dotfiles: A bare Git repository][atlassian-tutorial] を参考にして、筆者がドットファイルを Git で管理するまでに行った手順を解説した記事です。基となった [Hacker New の投稿][hacker-news] は 2016 年ですが、このテクニックについて解説している日本語記事があまり見つからなかったため、書いてみました。

筆者が実際に公開しているリポジトリはこちらにあります
→ https://github.com/kosh04/dotfiles

## 用語のおさらい

* __ドットファイル__: UNIX 系 OS における各種プログラムの設定ファイルのこと。
  習慣としてファイル名がドット (".") で始まるものは「隠しファイル」とみなされ、通常の一覧表示では見えないようになっている。昔ながらのドットファイルは一般的なユーザが扱う必要のないファイルとしてホームディレクトリ直下に配置されることが多い。
* __dotfiles__: それぞれの開発者が運用＆公開しているドットファイルリポジトリの通称
  → 参考リンク：[dotfiles.github.io](https://dotfiles.github.io/)
* __ベアリポジトリ__: 作業ディレクトリ (worktree) を持たない、更新情報を格納しているディレクトリが剥き出し (bare) な状態のリポジトリ。

## 特徴

1. ベアリポジトリ＋ git コマンドラッパー
2. ホームディレクトリ直下のドットファイルを管理できる (symlink 不要)
3. 他ソフトのファイル管理に干渉しない／されない

この記事におけるテクニックの肝は以下のエイリアス定義にあります

```sh
alias dotfiles='git --git-dir=$HOME/.dotfiles.git --work-tree=$HOME'
```

軽く説明すると `dotfiles` は Git リポジトリ (`--git-dir=$HOME/.dotfiles.git`) と作業ディレクトリ (`--work-tree=$HOME`) を明示して git コマンドを呼び出すだけの単純なラッパーコマンドです。元記事ではエイリアス `config` や `~/.myconf`, `~/.cfg` などの名前を利用しているようです。
その辺はお好みでどうぞ。

個人的には「ドットファイルを管理するコマンド」ということで `dotfiles` + `~/.dotfiles.git` という名前で統一感を出すのがわかりやすいかなと思いました。

ドットファイルを共有するノウハウを書いた記事は他にも多々ありますが、このベアリポジトリを使ったテクニックでは、特徴その２が示すように __「ホームディレクトリのファイル構成」と「Git リポジトリのコンテンツ構成」が一対一にマッピングされる__ のが大きな特徴です。例えば `~/.config/git/config` は Git リポジトリ内でもそのまま `.config/git/config` を指します。そのため dotfiles のセットアップ作業でよく見かける「シンボリックリンクを貼る工程」が不要となります。シンプルさはとても大事。

やろうと思えばホームディレクトリを直接 git の管理配下にする運用も可能です（実際そういった解説記事もあります）。一見シンプルそうですが、その場合ソースコード管理に対応したエディタや Git クライアントなどのソフトウェアでは `~/.git` ディレクトリが影響を及ぼす可能性があります。
その点 `dotfiles` コマンドは他ソフトのファイル管理に干渉することなく閉じた環境でのリポジトリ操作を提供します。コマンド自体は単なる git のラッパーなので、セットアップ例を見れば分かるように git とほとんど同じ感覚で利用できるのもポイントです。

もちろん欠点もあります。

Github Desktop をはじめとする __各種 Git クライアントは素の状態ではベアリポジトリに対するソースコード管理に対応していません__（要出典; 筆者が無知なだけの可能性もあります）。
必然的にリポジトリ操作の殆どを CUI で行うことが前提となるため、普段からターミナル作業に慣れている人向けのテクニックといえるでしょう。

## セットアップ

ようやく本題。

まずはホームディレクトリ以下に空のベアリポジトリを立ち上げます。
あらかじめ github などで新規の空リポジトリを作成しておいて、それをベアリポジトリとしてクローンすると、リモートリポジトリの設定が省けて少しだけ簡単になります。

ちなみにベアリポジトリのディレクトリ名は末尾に `.git` を付与するのが慣例らしいです。参考リンク: [Pro Git ja](https://git-scm.com/book/ja/v1/Git-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E7%94%A8%E3%81%AE-Git-%E3%81%AE%E5%8F%96%E5%BE%97)

```sh
$ git init --bare ~/.dotfiles.git
あるいは
$ git clone --bare <repository_url> ~/.dotfiles.git
```

次にリポジトリ操作用コマンド `dotfiles` の定義といくつかの設定を行います。
キー `status.showUntrackedFiles` にはステータス表示時に未追跡のファイル一覧を隠す
フラグを設定します。これは `git status --untracked-files=no` と同じ効果が得られます。

```sh
$ alias dotfiles='git --git-dir=$HOME/.dotfiles.git --work-tree=$HOME'
$ alias dotfiles >> ~/.bashrc
$ dotfiles config status.showuntrackedfiles no
```

実際のコンテンツをリポジトリから `$HOME` にチェックアウトします（別マシンにてリポジトリの作成とプッシュが済んでいる場合）

```sh
$ dotfiles checkout
```

この時チェックアウトによって元から存在するファイルが上書きされる可能性がある場合は、問題のあるファイル一覧を出力してエラー終了するかもしれません。

```
error: The following untracked working tree files would be overwritten by checkout:
	.bashrc
	.config/git/config
Please move or remove them before you can switch branches.
Aborting
```

その場合はそれらのファイルを退避させた後、改めてチェックアウトします。以下のコマンド例ではバックアップ用のディレクトリを掘って、アーカイブ作成コマンド (`tar`) を利用したファイルのバックアップを行っています。

```sh
$ mkdir -p ${BACKUP_DIR=~/.dotfiles.bak}
$ dotfiles checkout 2>&1 |\
  grep $'^\t'            |\
  cut -c 2-              |\
  tar cf - -T -          |\
  tar xvf - -C "${BACKUP_DIR}"
$ dotfiles checkout --force
```

これでセットアップは完了しました。あとは `dotfiles` コマンドを利用してドットファイルのバージョン管理を行うだけです。ファイルの更新が済んだら適当なリモートリポジトリ宛にプッシュします。

```sh
$ dotfiles status
$ dotfiles add .vimrc
$ dotfiles commit -m "Add vimrc"
$ dotfiles add .bashrc
$ dotfiles commit -m "Add bashrc"
$ dotfiles remote add origin <repository_url> (新規に git init でリポジトリを作成した場合のみ)
$ dotfiles push origin master
```

## Git クライアントを利用したい

Git クライアントは便利ですが、先述したとおりベアリポジトリをそのまま扱うことはできません。

これには制限付きですが解決策があります。 `dotfiles` コマンド経由で外部コマンドを呼び出すことで、肝である Git リポジトリとワークツリーの情報を引き継がせた状態でプログラムを起動させることができます。内部的には環境変数 `GIT_DIR`, `GIT_WORK_TREE` が子プロセスに継承されるようです。

例えば [Tig](https://jonas.github.io/tig/) (ターミナルで動作する Git クライアント) を起動するエイリアスを定義すると次のように実行できます。

```sh
$ dotfiles config alias.tig '!tig'
$ dotfiles tig status
```

とはいえ、その他の Git クライアントでも期待通りに動作するかは保証できません :p