分裂する2つのディレクトリ。一度はハマる...git管理におけるmacの濁音問題


要は...「濁音問題」をgit管理の観点から深掘りしてみました。

濁音ディレクトリを含むプロジェクトをgitで管理するケースにおいて、stagingする(git addコマンドを打ち込む)ディレクトリ次第では特定ディレクトリが分裂することを、gitの挙動とあわせて細かめに説明している記事になります。

結論はこちらから

始めに...git管理下で日本語で構成されたディレクトリ管理をすることがありますよね...?我々、日本人は日本語という言葉を使って日々のコミュニケーションを取る。そんなことは英語を主として物事を進める人には知らんこっちゃあない。

通常、programを管理するディレクトリ名などに日本語名を採用することは少ない。
一方で設計書などには採用することは多いとまではいかないにせよ、存在する。

設計書に関しては、機械が読み込んで処理する必要はない。どちらかといえば人間が開発を進める上で必要となる。そのため、人間(ここでは日本人)が用いる設計書のディレクトリ名やファイル名、詳細設計は、日本語で書かれることがある(というか多い)。設計書を作成するくらいのものであれば、他の人との共有(共同管理)が前提になるため、gitを使って管理することがある。
git管理下で日本語で構成されたディレクトリ管理をすることがある...これ関連で今回はこれでハマった。
[補足]開発環境はmacOS Sierra, git version 2.17.0

事件発生...生き別れ(分裂)した日本語ディレクトリを見つける...

こうなっていた。なんじゃあ、こりゃあああああ。
該当リポジトリ... dakuon-nfc-nfd-test

開発環境のgit配下で管理していると以下のように見えている...のに

 dakuon-nfc-nfd-test (master %=)$ tree -N
.
├── 01_テスト
│   └── 01_ダダダ
│       └── 01_ディル.text
└── README.md

2 directories, 2 files

心の声
なんで開発環境では1つなのにgithub2つになってるんじゃ。リロード、リロード、リロード。。。あれ、見間違いでもない。
ほっぺをつねってみる、、、リロード。現実ですか。

[補足]
ちなみにgitのdefaultだと日本語は文字化けしている場合は、以下のコマンドで文字化けを制御。git config --local core.quotepath false 
ちなみに、ちなみにtreeコマンドのdefaultでも日本語文字化けしている場合は、以下のコマンドで文字化け制御tree -N

事件経緯を追ってみる...ということで Lets 再現

まず、以下のディレクトリ構成を作る。


dakuon-nfc-nfd-test (master %=)$ tree -N
.
├── 01_テスト
│   └── 01_ダダダ
│       └── 01_ディル.text
└── README.md

2 directories, 2 files

ルートディレクトリで状況を確認→うん、ステージングされていないのは想定どおり。


dakuon-nfc-nfd-test (master %=)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    01_テスト/

問題(濁音)ディレクトリ直下で状況確認→うん、ステージングされていないのは想定どおり。

 dakuon-nfc-nfd-test (master %=)$ cd 01_テスト/01_ダダダ/
 01_ダダダ (master %=)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    ../

問題(濁音)ディレクトリ直下でstaging+commit

 01_ダダダ (master +%=)$ git add .
 01_ダダダ (master +%=)$ git commit -m "ダダダをcommit"
[master 391a491] ダダダをcommit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 01_テスト/01_ダダダ/01_ディル.text

問題(濁音)ディレクトリ直下で状況を確認→あれ、まだ残っている...ちゃんとcommitまでできてなかった...??

  01_ダダダ (master %>)$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    ../01_ダダダ/

ログを見ている→できている

 01_ダダダ (master %>)$ git log
commit 391a4914639f9098258d55e0627332a02315bdbb (HEAD -> master)
Date:   Sun Mar 10 09:47:08 2019 +0900

    ダダダをcommit

commit 6b595b5394c596451815ec3c9557c5e24f591747 (origin/master, origin/HEAD)
Date:   Sun Mar 10 09:11:18 2019 +0900

    Initial commit

ブロジェクトのルートディレクトリに戻って、ステータスチェックしてみる→あれ、やっぱり残っている??

 dakuon-nfc-nfd-test (master %>)$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    01_テスト/01_ダダダ/

ルートディレクトリでstagingしてstatus

 dakuon-nfc-nfd-test (master %>)$ git add .
 dakuon-nfc-nfd-test (master +>)$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   01_テスト/01_ダダダ/01_ディル.text

そしてcommit

dakuon-nfc-nfd-test (master +>)$ git commit -m "2回目のダダダをcommit"

※ちなみに、treeでみるディレクトリ構造は変わらず

さて、満を持して、開発環境からremote repositoriesへpush→ブラウザgithubへ→
「ダダダ」が2個になっている。
とりあえず再現は完了

githubの「ダダダ」ディレクトリのタイトルをコピって、microsoftのwordに貼り付けてみる→なんか違う。「濁点」とペアとなる文字の組み合わせ方が違う。

俗にいう「濁音問題」が事件の根本原因!!
参考までに...Mac OS X の NFD 問題での対策諸々

起きるケースの整理
staging場所... ルートディレクトリ直下 濁音を含まないディレクトリ直下 濁音を含むディレクトリ直下
問題が... 起きない 起きない 起きる
  • ルートディレクトリ以外の濁音ディレクトリ直下でgit操作(staging)してもgit管理下では、stagingしたはずが、そう認識されていない
  • ルートディレクトリでstagingすると濁音ディレクトリ直下でstagingした本来同じはずのファイルが全く別のものとして管理される」
起きる問題...濁音を含む日本語ディレクトリの生き別れ(分裂)が発生する。つまるところ、不整合が発生しえる状況になる。1つが2つになるので、管理ができなくなるし、別々のものを更新しえるため。

[補足] ちなみにmacでだと1つに見えるのは、「同じディレクトリ」と識別して優先的に片方を表示しているから。挙動的にはNFC(濁点がペアと統合されている方)を優先させて表示??

[本題] gitの中で、何が起きているのか??...を追ってみる

どうやらmacとwindow, linuxのunicode正規化の仕方が違うため、人間が「同じ」と認識している文字列でも、機械では「違う」と認識したのが原因だった。
  前提知識... Unicode正規化

  • ルートディレクトリでstagingした際にはNFC変換されるが、濁音ディレクトリ直下でstagingした際にはNFDのままindexに残っている、と思われる 検証を後述。
    • そのため濁音直下でstagingしたあとでもルートに戻るとstaging管理されていないfileがgitにて認識されていた、かと思われる 検証を後述。
  • 加えて、濁音ディレクトリをmacのファイルシステム上では同じものとして認識する(NFD, NFCを識別ないしは片方を優先させる)ためターミナル上ではあたかも1つしかないように見えるが、ブラウザではNFD, NFCを識別するため2つ表示することができた。

Lets 検証...ホントにそうなんでしょうか??

検証内容...各コミット時点での問題ディレクトリのパスを観察すればよい

[参照先] Gitのリポジトリの中身をなるべく正確に理解する
[引用]インデックスは、プロジェクトのある時点でのディレクトリツリー全体を表すデータをもつ。 具体的には、プロジェクトの各ファイルについて、対応するブロブへのポインタと、プロジェクトルートディレクトリからの相対パスが記録されている。 git ls-files --stageで.git/indexの内容を見れる。

gitのindexの中身を覗いてみる

100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   01_テスト/01_ダダダ/01_ディル.text
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   01_テスト/01_ダダダ/01_ディル.text

wordに貼り付けてみると...indexにて保存/管理するパス構成の濁音は別々として認識されていることがわかる

commitベースで何が起こったのかを覗いてみる。

観察対象となる2つのcommit↓

# こっちがルートディレクトリ直下でのstaging
commit 4d228ec1d9e16488e18556c15ac4e20af7b9f703
Date:   Sun Mar 10 09:51:09 2019 +0900

    2回目のダダダをcommit

# こっちが濁音ディレクトリ直下でのstaging
commit 391a4914639f9098258d55e0627332a02315bdbb
Date:   Sun Mar 10 09:47:08 2019 +0900

    ダダダをcommit

濁音ディレクトリ直下でのコミット/391a4914639f9098258d55e0627332a02315bdbbを覗いてみる

objects (GIT_DIR!)$ git cat-file -p 391a4914639f9098258d55e0627332a02315bdbb
tree f95cd9fbb46c9dc074dd3c53d75759f41d041c73
parent 6b595b5394c596451815ec3c9557c5e24f591747

ダダダをcommit
---
更に、treeを見てみると...
---
 objects (GIT_DIR!)$ git cat-file -p f95cd9fbb46c9dc074dd3c53d75759f41d041c73
040000 tree 5a6faeebdc7af940d335375ceeb3a34a74c0341b    01_テスト
100644 blob d6c4eb8703e3caef79b3efc4d2a8ada8ab81266a    README.md
---
01_テストのtreeを見てみる
---
objects (GIT_DIR!)$ git cat-file -p 5a6faeebdc7af940d335375ceeb3a34a74c0341b
040000 tree 6e7ff31ac54ab5e17b7388fce5a21938823215e2    01_ダダダ

ドキュメントにコピペしてみると、現時点(濁点ディレクト直下でのstaging直後)では、git上ではNFD(分解して文字列)でstagingされていることがわかった↓

ルートディレクトリ直下でのコミット/4d228ec1d9e16488e18556c15ac4e20af7b9f703を覗いてみる

objects (GIT_DIR!)$ git cat-file -p 4d228ec1d9e16488e18556c15ac4e20af7b9f703
tree 8788f8e16086819b67a0de126f5c8ab35572a265
parent 391a4914639f9098258d55e0627332a02315bdbb

2回目のダダダをcommit
---
更に、treeを見てみると...
---
 objects (GIT_DIR!)$ git cat-file -p 8788f8e16086819b67a0de126f5c8ab35572a265
040000 tree 00ca4aefa1277dabe7f6d54ee95b9a51553a28af    01_テスト
100644 blob d6c4eb8703e3caef79b3efc4d2a8ada8ab81266a    README.md
---
01_テストのtreeを見てみる
---
 objects (GIT_DIR!)$ git cat-file -p 00ca4aefa1277dabe7f6d54ee95b9a51553a28af
040000 tree 6e7ff31ac54ab5e17b7388fce5a21938823215e2    01_ダダダ
040000 tree 6e7ff31ac54ab5e17b7388fce5a21938823215e2    01_ダダダ
---
ここで新しいstagingがされていることがわかる
---

ドキュメントにコピペしてみると、ルートディレクト直下でのstaging直後では、git上ではNFC(分解しない/結合した文字列)でstagingされていることがわかった↓

やっぱり↓これっぽい 

  • ルートディレクトリでstagingした際にはNFC変換されるが、濁音ディレクトリ直下でstagingした際にはNFDのままindexに残っている。
    • そのため濁音直下でstagingしたあとでもルートに戻るとstaging管理されていないfileがgitにて認識されていた。

### 解決策
1. ディレクトリ名に濁音を使わない。というかディレクトリ名は全てalphabet化する→これが一番楽
2. ルートディレクトリ以外ではgit操作を禁止する→人がやるので無理
3. OSをアップデートする→影響範囲大きい
となりそう。

関連資料...thanks!!


お読みいただきありがとうございました。
もしご不明点、誤っている点があれば、コメントいただけると幸いです。