共有フォルダで始めるGit

tl;dr
--------

- 未検証ネタです。機能しました報告が欲しいです。機能しなかったら消しますのでコメントお願いします。
- 共有フォルダ（NFS/CIFS等）にリモートリポジトリを立てて開発はできる（はず）
  - 直接編集する人がいても耐えれる（テキストファイルに限る）
- ポイントは `git config receive.denyCurrentBranch updateInstead`

未検証箇所は、実際にNFSやCIFS等にある共有フォルダを扱った場合の確認です。


背景
---------

最近「共有フォルダ上でソースコードを管理している」という環境があることを聞きました。そこで、共有フォルダ上をリモートリポジトリにしてもGitは使えるんじゃないか、と思い書きました。

開発でコードのバージョン管理するにあたって、どこにGitリポジトリ建てればー、サーバ確保しないとーとか、インターネットは危険だーとか、ライセンス買わないとー、とかでなかなか重い腰を上げてくれない現場があると思います。でも共有フォルダはある。

Gitは~~そんな悲惨な~~環境でも機能します。


前提
-----------

- **Git 2.3以上**
    - Git for Windows を想定
- Git for Windowsを入れてよいPC

以降の手順はGit for windowsに付属のGit Bashでやっています。

例なので手順はローカルでやっていますが、NFSやCIFSならネットワークドライブとしてドライブレタ―を付けてマウントするなり、OneDriveなら特定のPathに同期ツールで同期させたりするイメージです。なので、フォルダのパスは適宜置き換えてください。


作業
-----------

まず、次の感じの共有フォルダ`share`があるとします。

``` bash
mkdir -p share/src
echo 'print "hello world";' > share/src/hello.pl
```

今まではこの`share`フォルダをみんなで仲良く読み書きしていました。
しかし、Aさんが書いたコードをBさんが上書きしたり、誰かが変なファイルをおいたままにしたり、ファイルをうっかり消したり大惨事になったりしていました。

### 共有フォルダの作業


ここからGitを始めるとしたら、まず共有フォルダをGitリポジトリ化します。（共有フォルダとして使えているなら、Git Bashで操作もできるはずです・・・未確認ですが・・・）

``` bash
cd share
git init
git config receive.denyCurrentBranch updateInstead
git add .
git commit -m "initial commit"
```

これだけです。`receive.denyCurrentBranch updateInstead`が見慣れませんが、今回のケースでは重要なのでわすれないでください。詳しくは最後に説明します。

後は他の利用者に**「.gitを消さないで！」**と警告しておきます。**「`_.gitは消さないで！.txt`」**というファイルを置いておいても良いかもしれませんね。

### ローカルフォルダの作業

次にローカルに共有フォルダのGitリポジトリをクローンします。cloneはgitリポジトリが対象であれば良いのでローカルでも動作します。

``` bash
cd -
git clone share hogeproject

cd hogeproject
perl src/hello.pl
```


次に修正作業をします。実際はIDE等を使ってください。

``` bash
sed -i 's/hello world/Hello World/' src/hello.pl
```

修正作業が終わったらcommitです。

``` bash
git add src/hello.pl
git commit -m "大文字を使うようにした"
```

修正作業を共有フォルダのGitリポジトリにも反映しましょう。

``` bash
git push
```

これで共有フォルダのファイルも修正できました。

- バージョン管理できているので、ある日の状態に戻せる
- 共有フォルダにもデータがあるので、作業者のマシンが死んでも大丈夫
- 共有フォルダが消失しても、作業者のマシンにリポジトリのコピーがあるのである程度は復元できる

開発するだけなら充分ではないでしょうか。

本当に充分？
--------------------

共有フォルダの性質上、以下もちょっと考えてみます。


### 作業者が複数いたら？

大丈夫です。よくあるGitを使った開発フローと変わりません。

### Gitを理解してない人が直接共有フォルダの内容を変更したら？

これは残念ながらちょっと手間がかかります。

幸い、直接変更された場合でも、Gitは変更状態として検知できます。
この状態でgit pushで更新しようとしても、コンフリクト扱いとなります。安全ですね。


実際にコンフリクトの動作を確認してみましょう。
まず、共有フォルダ上で直接変更した操作をエミュレートしてみます。

``` bash
# 共有フォルダ作業
cd -
cd share

# 1行追加する変更を加えた
echo 'print "hoge";' >> src/hello.pl
```

この状態でローカル作業を行いましょう。

``` bash
# ローカル作業
cd -
cd hogeproject

sed -i 's/World/World !!/' src/hello.pl

git add src/hello.pl
git commit -m "update"
```

これで共有フォルダにはコミットされていない変更がある状態、ローカルはコミットしているが共有フォルダの変更が取り込まれていない状態になります。

この状態でPushしてもちゃんとコンフリクトで失敗します。試してください。

``` bash
git push
```

この後、どうすれば良いかというと、以下の作業をとります。

- 共有フォルダ上にコミットされていない変更があるのでまずそれをコミット
- ローカルでpull, コンフリクト解消してマージしてから再度push

この方針は、直接変更した場合はその変更は無条件で受け入れるようにし、Git利用者のマージを頑張るという形です。現実解じゃあないでしょうか。

まず共有フォルダの作業をします。

``` bash
# 共有フォルダ作業
cd -
cd share
git add .
git commit -m "anonymous update"
```

共有フォルダの作業が終わったら、ローカルでマージ作業を行います。

``` bash
# ローカルの作業
cd -
cd hogeproject
git pull

# コンフリクトを解消する
vi src/hello.pl 

# # 以下の形になるように修正
# print "Hello World !!";
# print "hoge"

# 修正したらaddで取り込みマージ再開
git add src/hello.pl
git merge --continue

# 共有フォルダへpush
git push
```

正直、Git利用者の負荷が高まるだけで開発としては微妙なんじゃ・・・という感じですが仕方がないです。

しかし、共有フォルダ運用と比べて以下のメリットが得られます。

- 変更が失われることはない（コンフリクトで気づける）
- 差分もGitの機能で確認できる

やっぱりGit使ったほうが良いじゃないか！

### エコシステムが使えないのでは？（JenkinsとかRedmineとか）

エコシステムが使える環境なのに共有フォルダとか冗談でしょう？


### 直接変更を加えるひとが最新のファイルを使っているとは限らないのでは？

それは開発体制の問題なので、声掛けして同期してもらうなりしてください。


### 共有フォルダ上の.gitをゴミと勘違いして削除する人がいる場合は？

それは作業者の問題なので、~~辞めて~~止めてもらえるように説得してください。


まとめ
--------------

微妙だった。良い会社にいきましょう。



おまけ
--------------

### `git config receive.denyCurrentBranch updateInstead`について


`receive.denyCurrentBranch` オプションは悪名高いオプションで、このオプションで調べれば危険性が説明された記事がたくさん出ます。（誤用している例もあります）
要は、pushされたときにどう振舞うか、というオプションです。
cloneはgitリポジトリを構築するので、当然リポジトリが出来ます。gitリポジトリなので、push等の操作もできるわけです。
デフォルトは`refuse`でpushを受け付けない形になっています。作業中に他人が自分の作業リポジトリに対していきなりpushしてくるなんていうことは普通ないですから気にすることも無いと思います。

今回使っている`updateInstead`という値は[2.3から追加されたオプション値](https://github.blog/2015-02-06-git-2-3-has-been-released/#push-to-deploy)で、pushしたときにワーキングツリーに反映する感じになります。ワーキングツリーに変更がある状態であればちゃんとコンフリクトします。

上記の参考先の通り、デプロイ先をGitリポジトリとして管理しておき、コンテンツを反映させたいときの手段に使えます。リモート上からpullして反映する方法がありましたが、このオプションを使うことでpushで反映する事が出来るようになります。

今回はこれを共有フォルダ上のGitリポジトリに対して使ったというわけです。ちなみに、このオプションがrefuseの場合はPushができなくなり、ignore,warnの場合は直接編集した人の変更が失われる可能性がある動きになります。