Gitの基本的運用を模索する

# はじめに

個人で細々と Visual Studio Code のExtensionを作って公開しています。
テストデータや画像ファイルなど master ブランチには含めたくないものをどうやって管理するか悩んでいました。
merge ドライバーを使って特定のファイルを除外する方法は、設定が悪いのかもしれませんが機能しませんでした。
(衝突が起きないために merge ドライバーが呼ばれないのだと思いますが)
そこでそれ以外の方法を模索してみました。

# 環境

Windows 10 Pro 64bit
git version 2.27.0.windows.1

# 要件

管理しなければいけないデータは、以下のようなものです。

- npm(Atom)で管理できるパッケージ
  - メインコンテンツ
- 自作の共通ライブラリ
  - 別のパッケージと共通で使う自作ライブラリ(npm モジュール)
- パッケージのためのバイナリデータ
  - jpg などの画像データ
- テストのためのデータおよびテストコード
  - テストフレームワークのコード
  - テストデータ
  - 開発中のアイデア等を書き込んでおくメモなど

このうち、テストのためのデータや画像データは、リポジトリで管理しますが公開するパッケージには含めないことにします。
バイナリデータは、常に最新の物のみ管理することにします。
履歴は要りません。
テストコードも履歴を管理します。

必要なファイルの概要は、以下のようになります。

```text
/package
  main.js
  readme.md
  ...

/library
  main.js
  ...

/image
  image.jpg
  ...

/test
  /testCode
    main.test.js
    ...
  /testData
    test1.txt
    test2.txt
    ...

todo.txt
```

# リポジトリの構成

要件を満たすためのリポジトリ構成は、以下のようにしました。
なお、個人で開発していますので衝突が起こることは考えていません。
また、最悪リモートリポジトリを作り直すことも出来るものとしています。

- 公開用のリモートリポジトリ  
  - リリースのための master ブランチ
  - 開発のための develop ブランチ
  - バイナリデータのための binary(image)ブランチ
  - テストのための test ブランチ
- 作業用のローカルリポジトリ  
  - master ブランチ
  - 開発のための develop ブランチ
  - バイナリデータのための binary(image)ブランチ
  - テストのための test ブランチ

# 運用方針

- リモートリポジトリは、GitHub もしくは GitBucket を使う。

単純ですが、準備も必要なく無料で使えるからです。

- 複数のパッケージで使う自作ライブラリは、それ単体で npm パッケージとして公開する。

サブモジュールで実現しようとしましたが、Atom のパッケージマネージャがバージョンなどをうまく解決できない等の不都合がありました。
余計なことをしないで既存の便利な仕組みを使うことにします。

- バイナリデータは独立(orphan)ブランチで管理する。

リリースするデータに含めずに Git で管理するためです。
また、バックアップの意味でリモートリポジトリに上げておきます。

- README に必要な画像データに関して、GitHub の Issue を使ったテクニックは使わない。

GitHub がサポートしているか不透明だからです。

- 場合によっては、バイナリデータは GoogleDrive などのクラウドストレージを使う。

容量などの問題で必要に応じて選択します。
もっとも、ダウンロードの仕組みの煩雑化や、一元管理できなくなるなど避けたいところです。

- バイナリデータの管理に、Git LFS を使わない。

不特定多数の人にダウンロードされる都合上、ダウンロード制限がある GitHub では使えません。
また、容量制限に関してもバイナリファイルを更新するごとに容量を圧迫します。
いちいち容量を減らすためにリポジトリから古いデータを削除するぐらいならば、LFS を使わなくても運用で何とかなります。
もともと、バイナリデータは、最新のものしか管理するつもりがないので古いデータを削除してから新しいファイルをプッシュすることにします。
これは、面倒なので改善したい点です。

- 改行コードは全て `LF` にする
  Windows で開発を行っていますが `CRLF` によるメリットは何もないので `LF` に統一します。

- コミットは機能単位で細かく分ける。もしくは、リリース毎に 1 つ程度にまとめてしまう。

- merge ドライバーによる除外は、使用しない

./.git/config に以下のように追記し、

```text:./.git/config
[merge "ours"]
  driver = true
```

.gitattributes に

```text:.gitattributes
.gitignore merge=ours
```

とする方法は、残念ながらうまく行きせんでした。
衝突が起きた時は、うまく行ったりもするのですが、基本的に自分一人で作っているので衝突が起きずにうまく行かないことの方が多いです。
そのためこの方法は諦めました。

# 運用に即して使うコマンドあれこれ

## リポジトリの作成

master と develop ブランチの.gitignore には以下を追加しておく。

.gitignore

```text
todo.txt
test
image
```

image ブランチは orphan ブランチとして作成する。

```text:make.ps1
git init
echo "main" > main.js
echo "readme" > readme.md
git add main.js
git add readme.md
echo "todo.txt" > .gitignore
echo "image" >> .gitignore
echo "test" >> .gitignore
git add main.js
git add readme.js
git add .gitignore
git commit -m "frist commit"
git remote add origin <URL>
git push -u origin master

git switch -c develop
echo "script" > script.js
git add script.js
git commit -m "first develop"
git push -u origin develop

git switch -c test
git rm .gitignore
echo "image" >> .gitignore
git add .gitignore
mkdir test
mkdir test/testCode
echo "main.test" > test/testCode/main.test.js
echo "script.test" > test/testCode/script.test.js
mkdir test/testData
echo "01" > test/testData/01.txt
echo "02" > test/testData/02.txt
git add test
echo "todo" > todo.txt
git add todo.txt
git commit -m "first on test"
git push -u origin test

git switch -orphan image
git rm -rf .
echo "todo.txt" > .gitignore
echo "test" >> .gitignore
git add .gitignore
mkdir image
echo "image" > image/image.jpg
git add image
git commit -m "first on image"
git push -u origin image

git switch master
```

powershell で実行すると改行コードが `CRLF` になってしまいますが、実際には `LF` でのみ作成します。

## 作業ブランチ

開発作業をする場合は、常にdevelopブランチで行う。
ある程度まとまったらテストデータを取って来るか、testブランチに切り替えてdevelopブランチをマージする

```
git switch develop
... //作業
git push

git switch test
git merge develop
... //テスト作業
git push

git switch master
git merge develop
... //リリース
git push

git switch develop
git rebase(merge) master
git push
```

## 画像データのリンクについて

README などから画像データにアクセルする場合のリンクの指定方法は以下のようになります。

1. GitHub のリポジトリにあるファイルの場合、以下のようにすれば参照できる。
   `https://raw.githubusercontent.com/UserName/RepositoryName/BranchName/FileName`
2. GoogleDrive に保存した場合、以下のようにすれば参照できる。
   1. 共有リンクを取得する
      `https://drive.google.com/open?id=ID`
   2. そして以下の形式に変更して記述する
      `http://drive.google.com/uc?export=view&id=ID`
3. DropBox に保存した場合、以下のようにすれば参照できる。
   1. 共有リンクを取得する
      `https://www.dropbox.com/s/FileName?dl=0`
   2. そして以下の形式に変更して記述する
      `https://www.dropbox.com/s/FileName?raw=1`

## バイナリファイルを消すときに使うコマンド

履歴から`PATH-TO-DATA`で示されるファイルを全て削除する。
全てのブランチから削除したいならば最後に`--all`を付ける。
`git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch PATH-TO-DATA' --prune-empty --tag-name-filter cat`
フォルダの場合は、以下になる。
`git filter-branch --force --index-filter 'git rm -rf --cached --ignore-unmatch PATH-TO-DATA' --prune-empty --tag-name-filter cat`

削除結果をリモートに反映させる。
こちらも全てのブランチから削除したならば最後に`--all`を付ける。
`git push origin --force`
タグがある場合は、以下も行う。
`git push origin --force --tags`

refs/original フォルダに保存されているオブジェクトを削除する。
`git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin`

reflog を削除する。
`git reflog expire --expire=now --all`
ファイルを消した履歴とそれにアクセス出来そうな履歴だけを削除したいならば以下を使って個別に消す。
もっとも十分な知識がないと整合性が取れなくなる恐れがある。
`git reflog delete refs`

到達不能オブジェクトの削除と最適化
`git gc --prune=now`
もしくは、
`git gc --prune=now --agressive`

# test コードへのアクセス

ファイルを取ってくる場合は、以下のように行う。

```
git restore --source=test test
(git restore --staged test)
```

テストを更新したい場合は、test ブランチへ切り替えてから行う。

# 最後に

まだまだ決して使いやすいものになっていません。
色々と改善していきたいものです。
そもそも、orphan ブランチを別リポジトリにしてしまうのも 1 つの方法のようですが。

Git が油汚れでギットギットになってからでは遅いんです。(\`・ω・´)