Git管理下に置くが変更は無視する、はできない

# 概要
設定ファイルに含まれるパスワードなどの機密情報を伏せて Git の管理下に置き、各自がローカルで書き入れた部分の変更を無視させたいことがあるが、**Git にはそのための機能はない**。

# どうすべきか

例えば以下のような方法で代替する。

機密情報を含むファイルを `config.ini` とすると、

1. `.gitignore` に `config.ini` を追加する
- 機密情報を伏せて `config.ini.tmpl` のような別名で保存する
- `config.ini` がすでにコミットされている場合、`git rm --cached config.ini` で削除する
- 変更をコミットする

README などに、リポジトリをクローンしたら `config.ini.tmpl` を `config.ini` にコピーしたうえで機密情報を書き入れるよう指示しておくと良い。

# うまくいかない方法

検索すると以下の方法がよく挙げられているが、いずれも**有効なのはローカルにおいてのみ**である。

## .gitignore に追加して git rm --cached する

ファイルが Git 管理下にある限り、変更は追跡される。
Git による管理から外すが、ローカルにあるファイルは削除したくない場合には有効。

<details><summary>検証</summary><div>
`config.ini` をコミットしたうえで、`.gitignore` に追加してみる。

```shell_session
$ echo PUT_SECRET_HERE > config.ini
$ git add . && git commit -m "機密情報を含む設定ファイル"
[master (root-commit) 02a5408] 機密情報を含む設定ファイル
 1 file changed, 1 insertion(+)
 create mode 100644 config.ini
$ echo config.ini > .gitignore
$ git add . && git commit -m "gitinogreに追加"
[master f30c427] gitinogreに追加
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore
$ echo my_secret > config.ini
$ git diff
diff --git a/config.ini b/config.ini
index 68c539e..0506d9e 100644
--- a/config.ini
+++ b/config.ini
@@ -1 +1 @@
-PUT_SECRET_HERE
+my_secret
```
変更が追跡されていることが分かる

この変更を元に戻したうえで、`git rm --cached` してみる。

```shell_session
$ git checkout config.ini
Updated 1 path from the index
$ git rm --cached config.ini
rm 'config.ini'
$ git commit -a -m "いったん追跡から外す" 
[master f09e4fc] いったん追跡から外す
 1 file changed, 1 deletion(-)
 delete mode 100644 config.ini
```

すると `config.ini` は Git の管理化から外れた。

さて、`.gitignore` に追加した後であれば、変更は無視されるだろうか？
`config.ini` を再度 Git 管理下に入れてみよう。`config.ini` は `.gitignore` により無視されており`git add` ではステージングできないため、 `-f` オプションをつける必要がある。

```shell_session
$ git add -f config.ini
$ git commit -m "再度追跡させる"
[master 2fc25de] 再度追跡させる
 1 file changed, 1 insertion(+)
 create mode 100644 config.ini
$ echo new_secret > config.ini        
$ git diff
diff --git a/config.ini b/config.ini
index 68c539e..ff0ae85 100644
--- a/config.ini
+++ b/config.ini
@@ -1 +1 @@
-PUT_SECRET_HERE
+new_secret
```

変更の追跡が再開していることが分かる。
</div></details>

## git update-index --skip-worktree する

ローカルでは追跡されなくなるが、 upstream はこの設定を保持しない。
つまり、共有リポジトリでこれを運用するなら、**すべてのコミッターがこの設定を行う必要がある**。
いずれ何も知らない新人によって機密情報は公開されてしまうだろう。

<details><summary>検証</summary><div>
`git update-index --skip-worktree` により、ローカルでは変更が追跡されなくなることを確認する。

```shell_session
$ cat config.ini
PUT_SECRET_HERE
$ git update-index --skip-worktree config.ini
$ echo my_secret > config.ini
$ git diff
$ cat config.ini
my_secret
```

`config.ini` の変更が追跡されていないことが分かる。

この状態では変更がなくコミットできないので、適当なファイルを追加してプッシュしてみよう。

```shell_session
$ touch something
$ git add . && git commit -m "skip-worktreeを実行"  
[master 377ba4a] skip-worktreeを実行
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 something
$ git push 
```

これを別のディレクトリにクローンして、`config.ini` を変更してみる。

```shell_session
$ git clone https://example.com/repo.git new_repo
$ Cloning into 'new_repo'...
$ cd new_repo 
$ echo new_secret > config.ini
$ git diff
diff --git a/config.ini b/config.ini
index 68c539e..ff0ae85 100644
--- a/config.ini
+++ b/config.ini
@@ -1 +1 @@
-PUT_SECRET_HERE
+new_secret
```

やはり `config.ini` の変更は追跡されていることが分かる。
</div></details>

公式ドキュメントでも、特定ファイルの変更を無視する目的に `update-index --skip-worktree` ないし `--assume-unchanged` を使用できないことが注記されている。

> Users often try to use the assume-unchanged and skip-worktree bits to tell Git to ignore changes to files that are tracked. This does not work as expected, since Git may still check working tree files against the index when performing certain operations.

> [Git - git-update-index Documentation](https://git-scm.com/docs/git-update-index#_notes)

# 参考
[How to stop tracking and ignore changes to a file in Git? - Stack Overflow](https://stackoverflow.com/a/40272289)