git config --add receive.denyCurrentBranch ignoreはどう危険なのか
git config --add receive.denyCurrentBranch ignoreをやるとどう危険なのか。一言で言うと「ある人が行った実装を、別の人が無意識に削除してコミットする」という事態を引き起こす。これが危険じゃなくて何なんだ。
まずローカルで実験用のリポジトリを作ってみよう。fという名前のリポジトリを作って、READMEをおく。今は中身は空っぽだ。
$ git init f Initialized empty Git repository in /Users/nishio/tmp//f/.git/ $ cd f f$ touch README f$ git add README f$ git commit -m "initial" [master (root-commit) ce6d7d5] initial 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 README
次はそのリポジトリをgって名前でcloneしてみる。
f$ cd .. $ git clone f g Cloning into g... done.
できた。次はgのワークツリーでREADMEを編集してfにpushしてみよう。READMEにhogeって書き足してコミットし、pushする。push時にエラーが出る。
$ cd g g$ cat >> README hoge g$ git add README g$ git commit -m "modify on g" [master 93295fc] modify on g 1 files changed, 1 insertions(+), 0 deletions(-) g$ git push Counting objects: 5, done. Writing objects: 100% (3/3), 256 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. remote: error: refusing to update checked out branch: refs/heads/master remote: error: By default, updating the current branch in a non-bare repository remote: error: is denied, because it will make the index and work tree inconsistent remote: error: with what you pushed, and will require 'git reset --hard' to match remote: error: the work tree to HEAD. remote: error: remote: error: You can set 'receive.denyCurrentBranch' configuration variable to remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into remote: error: its current branch; however, this is not recommended unless you remote: error: arranged to update its work tree to match what you pushed in some remote: error: other way. remote: error: remote: error: To squelch this message and still keep the default behaviour, set remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'. To /Users/nishio/tmp/gittest/f ! [remote rejected] master -> master (branch is currently checked out) error: failed to push some refs to '/Users/nishio/tmp/gittest/f'
push時に「bareでない(ワークツリーを持っている)リポジトリの、今チェックアウトされているブランチをpushすることはできない。なぜならインデックスとワークツリーが不整合になるからだ」と言われてエラーになっている。
こういう状況になった時に「ちょっと危険だけど git config --add receive.denyCurrentBranch ignore すればいいよ」なんてことを説明しているサイトがあるらしい。じゃあそれをやったらどんなことが起こるのか経験してみよう。リポジトリfでgit config --add receive.denyCurrentBranch ignoreする。
g$ cd ../f f$ git config --add receive.denyCurrentBranch ignore
リポジトリgに戻ってpushすると、今度はすんなり成功する。
f$ cd ../g g$ git push Counting objects: 5, done. Writing objects: 100% (3/3), 256 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /Users/nishio/tmp/gittest/f ce6d7d5..93295fc master -> master
この状態、fさんにとっては「知らない間にgさんがmasterブランチを書き換えている」って状態になっている。ログを見ると確かにgさんが書き換えたって情報は出ている。でもワークツリーのREADMEはgさんが書き換える前の「空っぽ」のままだ。これにfugaと「追記」してコミットしてみよう。
g$ cd ../f f$ git log --oneline 93295fc modify on g ce6d7d5 initial f$ cat >> README fuga f$ git add README f$ git commit -m "modify on f" [master 14a82ca] modify on f 1 files changed, 1 insertions(+), 1 deletions(-)
目ざとい人は気づいたかもしれない。「1 deletions(-)」と表示されている。
では今のコミットで何が行われたか確認してみよう。
f$ git show commit 14a82ca87ba5dd536492b9758582179104de09bd Author: NISHIO Hirokazu <nishio.hirokazu@gmail.com> Date: Mon Apr 16 12:35:11 2012 +0900 modify on f diff --git a/README b/README index 2262de0..9128c3e 100644 --- a/README +++ b/README @@ -1 +1 @@ -hoge +fuga
fさんのコミットは「gさんの行った修正を削除して自分の修正を追加」になっている。多分しばらくたってから
f「おい、g、この前頼んだ修正はまだか」
g「え、だいぶ前にコミットしたぞ」
f「直ってねーよ!ほら!」
g「そんなはずはない…なんだこれは、お前が俺の修正を削除したんじゃないか」
f「えっ、そんなはずは…ほんとだ…いや、消したつもりはないんだが」
g「勝手に消えたのか?git怖い」
などという不毛な会話がかわされることになる。ほんとうに怖いのは「ちょっと危険だけど」とか言いながらバッドノウハウを撒き散らすサイトの存在だ。
じゃあどうする?
一番素直なのは「githubなりbitbucketなり自分で作るなりしてbareなブランチを用意する」なんだけども、あえて今回のような「bareでないリポジトリからcloneして、しかも二人ともmasterで作業している」というひどい状態からどうするかを考えてみる。
まずgではまた何か変更を行なったとする。piyoって書き足している。
g$ cat >> README piyo g$ git add README g$ git commit Waiting for Emacs... [master 29cfa4a] modify on g (piyo) 1 files changed, 1 insertions(+), 0 deletions(-)
本当を言えば「変更の前にブランチを作ってその中で作業する」が望ましいんだが、いまからでも遅くないので「piyoを追加する」という意味のブランチを作る。
g$ git checkout -b add_feature_piyo Switched to a new branch 'add_feature_piyo'
そして、そのブランチだけをorigin(clone元、つまりこの場合はf)にpushする。
g$ git push origin add_feature_piyo Counting objects: 9, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (9/9), 678 bytes, done. Total 9 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. To /Users/nishio/tmp/gittest/f * [new branch] add_feature_piyo -> add_feature_piyo
そうすると、fの側ではgの変更が新しいブランチとして現れるので、fの作業が一段落した、fの都合の良いタイミングでそこからmergeする。
g$ cd ../f f$ git branch add_feature_piyo * master f$ git merge add_feature_piyo Auto-merging README CONFLICT (content): Merge conflict in README Automatic merge failed; fix conflicts and then commit the result. f$ cat README <<<<<<< HEAD fuga ======= hoge piyo >>>>>>> add_feature_piyo
さっきうっかり「gの変更を削除してコミット」しちゃったせいでコンフリクトが起きているね。READMEの中身はこんな風になっているので、今したいことは両方の変更を採用することだからそうなるように編集しよう。
f$ emacsclient README # 編集している Waiting for Emacs... f$ cat README fuga hoge piyo
あとは普通にこのファイルをaddしてcommitするだけ。ちなみにcommit -vをすると、今から何をコミットしようとしているかのdiffなどが見れるからより安心。今回のケースでgit commit -vすると、下のようにhogeとpiyoを追加した旨が表示される。
Merge branch 'add_feature_piyo' Conflicts: README # # It looks like you may be committing a merge. # If this is not correct, please remove the file # .git/MERGE_HEAD # and try again. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # # modified: README # diff --git a/README b/README index 9128c3e..05a9867 100644 --- a/README +++ b/README @@ -1 +1,3 @@ fuga +hoge +piyo