git merge — 枝分かれした歴史をつなぐ

2-3 では、Windows のノート PC と Mac で別々にコミットすると、push も git pull --ff-only も止まるのを見ました。枝分かれした2本の歴史を1本につなぐ方法の1つが git merge です。merge は「別のブランチの付箋が指すコミットを、今のブランチに取り込む」コマンドで、取り込み方は2通りあります。付箋を前へずらすだけの fast-forward(早送り)と、親が2つのマージコミットを作るやり方です。この回では、どちらになるかの決まりと --ff-only・--no-ff を見てから、同じ行を両方が変えたときのコンフリクトの直し方とやめ方を確かめます。最後に、2-3 で止まった枝分かれを merge で解きます。

画面の見方
  • 上のターミナルは、実際に打ったときと同じ出力です(Git は英語で答えます)
  • 下の中で起きていることは、コマンドの結果、Git の中の何が変わったかを図にしたものです。丸がコミット、黒い札がブランチ、ピンクの札が HEAD です。マージコミットには親が2つあるので、2本の線がその丸で合流します
  • コンフリクトで止まっている間は、ターミナルのプロンプトに |MERGING(マージの途中)が付きます
  • 6 の区間だけはリモートがあり、グラフが2つ並びます。上が GitHub 側、下が手元です。Mac での操作はターミナルには何も出ず、上の図だけが進みます
  • 右の説明をスクロールして読み進めると、左のターミナルでコマンドが1つずつ打たれます。→ キーでも1つずつ進みます(← で1つ戻ります。スマホでは「次へ」ボタン)。最後まで進んでから → を押すと次の区間へ移ります。黄色く光っているのが、そのコマンドで変わった場所です

1. merge が取り込むもの — fast-forward

git merge memo は、付箋 memo が指すコミットと、そこから親を辿れるコミットを、今のブランチに取り込むコマンドです。「今のブランチ」は HEAD が乗っているブランチで、動くのはその付箋だけです。相手の memo は動きません。向きを取り違えやすいので、main に memo を取り込みたいなら、先に main に switch してから git merge memo を打つ、と覚えてください。

いちばん簡単なのは、今のブランチが相手の祖先(相手から親を辿っていくと行き着くコミット)になっている場面です。作業用のブランチ memo でコミットしてから main に戻り、memo を取り込みます。

付箋をずらすだけのマージ — fast-forward

  1. 作業用のブランチ memo を作って乗り換えます。いまは main と memo の付箋が、同じ 卵を追加 に貼ってあります。

    $ git switch -c memo
    Switched to a new branch 'memo'
  2. memo の上で、新しいファイル notes.txt を書きます。

    $ echo "牛乳は低脂肪" > notes.txt
  3. notes.txt をステージに載せます。

    $ git add notes.txt
  4. memo だけが メモを追加 に進みました。main は 卵を追加 に残っています。

    $ git commit -m "メモを追加"
    [memo b78e6bd] メモを追加
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  5. main に戻ります。作業ツリーから notes.txt が消えたのは、main のコミットにまだ入っていないからです。

    $ git switch main
    Switched to branch 'main'
  6. main の 卵を追加 は、memo の メモを追加 の親、つまり 祖先 です。だから Git は新しいコミットを作らず、付箋 main を メモを追加 へずらすだけで済ませます(Fast-forward)。--ff-only は「早送りで済むときだけ進める」指定で、ここでは済むので通ります。

    $ git merge --ff-only memo
    Updating 8b41d07..b78e6bd
    Fast-forward
     notes.txt | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  7. もう一度 merge すると、Already up to date.(もう取り込み済み)と答えて何もしません。memo のコミットは、すべて main から辿れるからです。

    $ git merge memo
    Already up to date.
  8. 取り込み済みなので、付箋 memo は -d で剥がせます。コミット メモを追加 は main から辿れるので消えません。

    $ git branch -d memo
    Deleted branch memo (was b78e6bd).
  9. 歴史は1本のまっすぐな列のままです。fast-forward では、「ブランチで作業した」という跡はグラフに残りません。

    $ git log --oneline
    b78e6bd (HEAD -> main) メモを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

main の先頭は memo の祖先なので、付箋 main を memo の位置まで前へずらせば、memo のコミットはすべて main から辿れるようになります。これが fast-forward(早送り)で、新しいコミットは1つもできません。出力の読み方はこうです。

出力の行意味
Updating 8b41d07..b78e6bd今のブランチの付箋を 8b41d07 から b78e6bd へ動かす
Fast-forward早送りで済ませた(新しいコミットは作っていない)
notes.txt | 1 + など動かす前と後で、作業ツリーのファイルがどう変わったか
Already up to date.相手のコミットは、もう全部今のブランチから辿れる。何もしなかった

2-3 の git pull --ff-only や、2-4 でプルリクエストのマージのあとに手元の main を追いつかせたときも、中でやっていたのはこの早送りです。相手が memo ではなく origin/main だった、というだけの違いです。

2. 両方が進んでいたら — マージコミット

今のブランチと相手が、同じ地点から別々に進んでいることもあります。2-3 で見た枝分かれ(diverged)です。このときは付箋をずらすだけでは済みません。main を memo の位置へずらすと、main にしかないコミットが辿れなくなるからです。

そこで Git は、両方の先頭を親に持つ新しいコミットを1つ作り、今のブランチの付箋をそこへ進めます。これがマージコミットです。中身は、分かれた地点・今のブランチの先頭・相手の先頭の3つを比べて決めます。分かれた地点から片方だけが変えたところは、その変更を採ります。両方の変更が1つのコミットに集まり、どちらのコミットも消えません。

早送りになるか、マージコミットになるかは、2本の位置の関係だけで決まります。

今のブランチと相手の関係git merge 相手 の結果出力
相手が今のブランチの祖先(取り込み済み)何もしないAlready up to date.
今のブランチが相手の祖先fast-forward(付箋をずらすだけ)Updating a..b / Fast-forward
どちらも祖先ではない(枝分かれ)マージコミットを作るMerge made by the 'ort' strategy.

次のシーンでは、main が todo.txt の1行目を、memo が notes.txt を足してから同じ todo.txt の4行目を変えています。

両方が進んでいたら — マージコミット

  1. 今度は main と memo が、買い物リストを作る から別々に進んでいます。main は1行目を 牛乳を2本買う に、memo は notes.txt を足してから4行目を バターを2箱買う に変えました。

    $ git log --oneline --all
    7c3e915 (HEAD -> main) 牛乳を2本に
    d29f4b7 (memo) バターを2箱に
    a61c0e8 メモを追加
    3f2a9c1 買い物リストを作る
  2. main の先頭は memo の祖先ではないので、付箋をずらすだけでは追いつけません。--ff-only は Not possible to fast-forward, aborting.(早送りできないので中止します)と止まり、何も変えません。

    $ git merge --ff-only memo
    hint: Diverging branches can't be fast-forwarded, you need to either:
    hint:
    hint: 	git merge --no-ff
    hint:
    hint: or:
    hint:
    hint: 	git rebase
    hint:
    hint: Disable this message with "git config set advice.diverging false"
    fatal: Not possible to fast-forward, aborting.
  3. オプションを付けない merge は、親が2つの マージコミット を作ります。同じ todo.txt でも、変えた行が離れていれば Auto-merging todo.txt(自動で混ぜた)で済みます。

    $ git merge memo
    Auto-merging todo.txt
    Merge made by the 'ort' strategy.
     notes.txt | 1 +
     todo.txt  | 2 +-
     2 files changed, 2 insertions(+), 1 deletion(-)
     create mode 100644 notes.txt
  4. 1行目は main の変更、4行目は memo の変更で、両方が入っています。

    $ cat todo.txt
    牛乳を2本買う
    卵を買う
    パンを買う
    バターを2箱買う
  5. main から辿れるコミットに、memo の2つが加わりました。git log はマージコミットの親を両方辿るので、2本の枝のコミットが1列に並びます。

    $ git log --oneline
    942c065 (HEAD -> main) Merge branch 'memo'
    7c3e915 牛乳を2本に
    d29f4b7 (memo) バターを2箱に
    a61c0e8 メモを追加
    3f2a9c1 買い物リストを作る
  6. 取り込んだあとに同じ merge を打っても、Already up to date. で何も起きません。

    $ git merge memo
    Already up to date.
  7. memo のコミットはすべて main から辿れるので、-d で付箋を剥がせます。

    $ git branch -d memo
    Deleted branch memo (was d29f4b7).

ort は、Git がいま標準で使っているマージの方式の名前です。出力に出るだけで、気にしなくてかまいません。どこまで自動で混ざるかは、変えた場所で決まります。

両方が変えた場所merge の動き
別々のファイル自動で混ざる
同じファイルの、離れた行自動で混ざる(Auto-merging todo.txt)
同じ行、または隣り合う行コンフリクトで止まる(4 で扱う)

気をつけたいのは、「自動で混ざった」は、行の位置がぶつからなかったという意味でしかないことです。Git は中身の意味を見ていません。たとえばプログラムで、片方が関数の名前を変え、もう片方が古い名前でその関数を呼ぶ行を足すと、行はぶつからないので静かに混ざり、できあがったコードは動きません。マージのあとは、テストを走らせるなどして中身を確かめる習慣が要ります。

もう1つ、人がターミナルで git merge を打ってマージコミットができるときは、メッセージを確かめるエディタが開くことがあります。何も変えずに保存して閉じれば、Merge branch 'memo' のまま確定します。Claude Code のようにプログラムから打つとエディタは開かず、そのまま確定します。

3. --ff-only と --no-ff — 合わせ方を指定する

オプションを付けない git merge は、早送りで済めば早送り、済まなければマージコミット、と Git に任せます。合わせ方を自分で決めたいときに付けるのが、次の2つです。

書き方早送りできるとき枝分かれしているとき
git merge 相手fast-forwardマージコミットを作る
git merge --ff-only 相手fast-forward何も変えずに止まる
git merge --no-ff 相手マージコミットを作るマージコミットを作る

--ff-only は、2-3 の git pull --ff-only と同じ約束です。歴史の形を変えずに済むときだけ進め、済まないときは止まって判断を返すので、1 と 2 のシーンで見たとおり、通っても止まっても何も壊しません。止まったら、その先をどうつなぐかを人が決めます。

--no-ff(no fast-forward)は逆に、早送りできる場面でもわざわざマージコミットを作ります。2-4 で、GitHub の Merge pull request は「GitHub 側の main が進んでいなくても、いつもマージコミットを作る」と書きました。手元で同じ形を作るのが --no-ff です。

早送りできてもマージコミットを作る — --no-ff

  1. 1 の merge の前と同じ形です。memo が main より1つ先にあり、ふつうに merge すれば早送りになります。

    $ git log --oneline --all
    5d7e2b4 (memo) メモを追加
    8b41d07 (HEAD -> main) 卵を追加
    3f2a9c1 牛乳を追加
  2. --no-ff を付けると、早送りできる場面でも マージコミット を作ります。Merge made by the 'ort' strategy.(ort という方式でマージした)は、マージコミットを作ったときの出力です。グラフでは 卵を追加 からの線と メモを追加 からの線が、新しいコミットで合流します。

    $ git merge --no-ff memo
    Merge made by the 'ort' strategy.
     notes.txt | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  3. メッセージには Merge branch 'memo' が自動で付きます。マージコミットの親は2つで、1つ目が 卵を追加(それまでの main)、2つ目が メモを追加 です。

    $ git log --oneline
    361488b (HEAD -> main) Merge branch 'memo'
    5d7e2b4 (memo) メモを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加
  4. 付箋 memo を剥がしても、メモを追加 はマージコミットの2つ目の親として main から辿れます。

    $ git branch -d memo
    Deleted branch memo (was 5d7e2b4).
  5. memo の札は消えましたが、「ここで memo を取り込んだ」というマージコミットが歴史に残りました。GitHub の Merge pull request がいつも作るのが、この形です。

    $ git log --oneline --all
    361488b (HEAD -> main) Merge branch 'memo'
    5d7e2b4 メモを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

1 の fast-forward と見比べると違いがはっきりします。fast-forward のあとは、メモを追加 がもともと memo で作られたことは歴史から読み取れません。--no-ff なら、マージコミットの2つ目の親を辿れば「ここからここまでが memo の作業だった」と分かります。作業のまとまりを歴史に残したいチームが --no-ff を選び、まっすぐな歴史を好むなら早送り、という使い分けです。1人で2台を使うだけなら、どちらを選んでも困ることはありません。

4. 同じ行を両方が変えたとき — コンフリクト

同じ行を両方が別々に変えていると、Git にはどちらを採ればよいか決められません。この状態をコンフリクト(衝突)と呼びます。Git は自動で混ぜられるところまで済ませてから、ぶつかった箇所のファイルに印を書き込み、マージの途中で止まります。1-7 stash で「pop がぶつかるとき」として触れたのも、このコンフリクトです。

題材は、main で「卵を2つ買う」、feature で「卵を6つ買う」と、同じ2行目を別々に変えた場面です。

同じ行を両方が変えたとき — コンフリクト

  1. main は2行目を 卵を2つ買う に、feature は同じ2行目を 卵を6つ買う に変えています。どちらを採るか Git には決められないので、CONFLICT (content): Merge conflict in todo.txt と言って途中で止まります。ここから先のプロンプトには、(main|MERGING)(マージの途中)と付きます。

    $ git merge feature
    Auto-merging todo.txt
    CONFLICT (content): Merge conflict in todo.txt
    Automatic merge failed; fix conflicts and then commit the result.
  2. ぶつかった行に、Git が 印 を書き込みました。<<<<<<< HEAD から ======= までが今のブランチ(main)の側、======= から >>>>>>> feature までが取り込もうとした相手の側です。ぶつからなかった1行目と3行目は、そのまま残っています。

    $ cat todo.txt
    牛乳を買う
    <<<<<<< HEAD
    卵を2つ買う
    =======
    卵を6つ買う
    >>>>>>> feature
    パンを買う
  3. You have unmerged paths.(まだ解決していないファイルがある)と、both modified:(両方が変えた)が出ます。直して commit するか、git merge --abort でやめるか、の案内も出ています。

    $ git status
    On branch main
    You have unmerged paths.
      (fix conflicts and run "git commit")
      (use "git merge --abort" to abort the merge)
    
    Unmerged paths:
      (use "git add <file>..." to mark resolution)
    	both modified:   todo.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
  4. 印を消して、残したい中身にファイルを書き直します。ここでは 卵を6つ買う を採りました。両方を残しても、別の文に書き換えてもかまいません。

    $ printf '牛乳を買う\n卵を6つ買う\nパンを買う\n' > todo.txt
  5. マージの途中の git add は、「このファイルは 解決した」という印を付ける意味になります。

    $ git add todo.txt
  6. All conflicts fixed but you are still merging.(コンフリクトは全部直ったが、まだマージの途中)に変わりました。あとは commit でマージを締めくくります。

    $ git status
    On branch main
    All conflicts fixed but you are still merging.
      (use "git commit" to conclude merge)
    
    Changes to be committed:
    	modified:   todo.txt
    
  7. --no-edit は、Git が用意したメッセージ Merge branch 'feature' をそのまま使う指定です。親が2つのマージコミットができてマージの途中が終わり、プロンプトから MERGING が外れます。

    $ git commit --no-edit
    [main dc55e06] Merge branch 'feature'
  8. 卵を2つ と 卵を6つ の両方が、main の歴史に入りました。いまのファイルの中身は、手で直した 卵を6つ買う です。

    $ git log --oneline
    dc55e06 (HEAD -> main) Merge branch 'feature'
    c3c3c3c (feature) 卵を6つ
    b4e81a2 卵を2つ
    3f2a9c1 買い物リストを作る

印は3つで1組です。上から順に読みます。

印の行意味
<<<<<<< HEADここから下が、今のブランチ(HEAD。このシーンでは main)の側
=======区切り。ここから下が、取り込もうとした相手の側
>>>>>>> feature相手の側の終わり。feature は相手のブランチの名前

直し方は3手です。

  1. ファイルを書き直して、印を消す。どちらかの側を採る、両方を残す、新しい文に書き換える、のどれでもかまいません。シーンでは printf でファイルを丸ごと書き直しましたが、エディタで印の行を消して直しても同じです
  2. git add ファイル で「解決した」と印を付ける。マージの途中の git add は、ステージに載せると同時に、both modified の一覧から外す意味を持ちます
  3. git commit --no-edit でマージコミットを作る。--no-edit は、Git が用意したメッセージ(Merge branch 'feature')をそのまま使う指定です。自分で書くなら git commit -m "…"

注意したいのは、Git は印が残っているかどうかを確かめずに add も commit も通すことです。印を消し忘れたまま add すると、<<<<<<< HEAD の行ごとコミットされてしまいます。add の前に、ファイルに <<<<<<< や ======= が残っていないかを見てください。

Claude Code にコンフリクトを任せると、ファイルを編集して印を消し、git add と git commit で締めくくる流れになります。許可画面で見るべきなのはコマンドではなく、どちらの側を採ったかです。「卵を2つ」と「卵を6つ」のように、どちらも正しそうに見える変更ほど、採った側が意図どおりかを差分で確かめてから許可します。

5. merge --abort — やめて元に戻す

コンフリクトで止まったマージは、解決して commit するか、やめるかしないと終わりません。その間は commit もブランチの乗り換えも止められます。「今はこのマージを片づけたくない」というときにやめるのが git merge --abort です。

やめて元に戻す — merge --abort

  1. 4 と同じ場面です。merge がコンフリクトで止まりました。

    $ git merge feature
    Auto-merging todo.txt
    CONFLICT (content): Merge conflict in todo.txt
    Automatic merge failed; fix conflicts and then commit the result.
  2. 解決していないファイルがあるうちは、commit できません(Committing is not possible because you have unmerged files.)。最後の行の U は、Unmerged(未解決)の印です。

    $ git commit -m "とりあえず保存"
    error: Committing is not possible because you have unmerged files.
    hint: Fix them up in the work tree, and then use 'git add/rm <file>'
    hint: as appropriate to mark resolution and make a commit.
    fatal: Exiting because of an unresolved conflict.
    U	todo.txt
  3. ブランチの乗り換えも、you need to resolve your current index first(先に解決して)と止められます。止まった merge は、片づけるまで付いて回ります。

    $ git switch feature
    error: you need to resolve your current index first
    todo.txt: needs merge
  4. 直しかけて、卵を4つ買う と書いてみました。でも、このマージは今はやめることにします。

    $ printf '牛乳を買う\n卵を4つ買う\nパンを買う\n' > todo.txt
  5. git merge --abort は、merge を打つ前の状態に戻します。付箋もコミットも動かず、作業ツリーとステージが 卵を2つ のコミットの中身に戻ります。直しかけた 卵を4つ買う も消えます。

    $ git merge --abort
  6. マージの途中ではなくなり、作業ツリーもきれいです。プロンプトからも MERGING が消えました。

    $ git status
    On branch main
    nothing to commit, working tree clean
  7. 卵を2つ買う に戻っています。feature はそのまま残っているので、あとでいつでも merge し直せます。

    $ cat todo.txt
    牛乳を買う
    卵を2つ買う
    パンを買う

--abort が戻すのは、merge を打つ直前の状態です。コンフリクトで止まったマージは、まだ付箋もコミットも動かしていないので、そこは何も変わりません。変わるのは作業ツリーとステージで、印入りのファイルも、直しかけた中身も、HEAD のコミットの中身に戻ります。直しかけた作業はコミットしていないので、どこにも残りません。長いコンフリクトを半分まで直してから --abort すると、その半分は最初からやり直しです。

もう1つ、git merge は書きかけのあるファイルを上書きしそうなときは、始める前に Your local changes to the following files would be overwritten by merge(書きかけが merge で上書きされる)と止まります。checkout や pull と同じ守りです。merge は、作業ツリーをきれいにしてから打つのが基本で、書きかけがあるなら先にコミットするか、1-7 の stash で退避します。

6. 2台で枝分かれしたとき — fetch・merge・push

ここまでの道具で、2-3 で止まった場面を解けます。Windows のノート PC と Mac で別々にコミットして、手元の main と GitHub の main が枝分かれした場面です。手順は git fetch で GitHub の様子を取り込み、git merge origin/main で2本をつなぎ、git push で送る、の3手です。

2台で枝分かれしたら — fetch して merge して push

  1. 2-3 の続きです。Windows のノート PC で、GitHub の様子を確かめずに todo.txt に1行書き足します。

    $ echo "バターを買う" >> todo.txt
  2. 手元の main が バターを追加 に進みました。

    $ git commit -am "バターを追加"
    [main 15e1410] バターを追加
     1 file changed, 1 insertion(+)
  3. 同じころ、Mac でも メモを追加 を作って push しました。GitHub 側の main は、手元とは別の方向へ進みます。

  4. push は (fetch first) で拒否されます。GitHub 側の メモを追加 を消さないために、Git が止めています。

    $ git push
    To https://github.com/you/memo-app.git
     ! [rejected]        main -> main (fetch first)
    error: failed to push some refs to 'https://github.com/you/memo-app.git'
    hint: Updates were rejected because the remote contains work that you do not
    hint: have locally. This is usually caused by another repository pushing to
    hint: the same ref. If you want to integrate the remote changes, use
    hint: 'git pull' before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  5. fetch すると メモを追加 が手元に届き、origin/main がそこへ進みます。手元のグラフでも、卵を追加 から2本に分かれたのが見えるようになりました。

    $ git fetch
    From https://github.com/you/memo-app
       c43e6a8..9ae3d51  main       -> origin/main
  6. have diverged(枝分かれした)と出ます。2-3 では、ここで git pull --ff-only が止まりました。今度は2本をつなぎます。

    $ git status
    On branch main
    Your branch and 'origin/main' have diverged,
    and have 1 and 1 different commits each, respectively.
      (use "git pull" if you want to integrate the remote branch with yours)
    
    nothing to commit, working tree clean
  7. 取り込む相手は、追跡ブランチ origin/main でもかまいません。バターを追加 と メモを追加 を親に持つマージコミットができ、メッセージは Merge remote-tracking branch 'origin/main' になります。

    $ git merge origin/main
    Merge made by the 'ort' strategy.
     notes.txt | 1 +
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  8. ahead of 'origin/main' by 2 commits に変わりました。バターを追加 とマージコミットの2つが、まだ GitHub に無いコミットです。もう枝分かれではありません。

    $ git status
    On branch main
    Your branch is ahead of 'origin/main' by 2 commits.
      (use "git push" to publish your local commits)
    
    nothing to commit, working tree clean
  9. 今度は push が通ります。手元の main は GitHub 側の メモを追加 を含んでいるので、GitHub の付箋は前に進むだけで、何も消えないからです。

    $ git push
    To https://github.com/you/memo-app.git
       9ae3d51..530423b  main -> main
  10. Windows の バターを追加 も Mac の メモを追加 も、GitHub の main に残りました。git fetch → git merge origin/main → git push が、枝分かれを解く手順の1つです。

    $ git log --oneline
    530423b (HEAD -> main, origin/main, origin/HEAD) Merge remote-tracking branch 'origin/main'
    9ae3d51 メモを追加
    15e1410 バターを追加
    c43e6a8 卵を追加
    3f2a9c1 牛乳を追加

push が通ったのは、マージコミットが GitHub 側の メモを追加 を親として含んでいるからです。GitHub の main から見ると、手元の main は「自分の先にまっすぐ続いている」ので、付箋を前へ進めるだけで済みます。merge で2本をつなぐと、どちらのコミットも消さずに push できる形になるわけです。このあと Mac の側では、git pull --ff-only で早送りするだけで追いつけます。Mac の main(メモを追加)は、新しいマージコミットの祖先だからです。

2-3 で残しておいた、オプション無しの git pull の話もここでつながります。git pull は fetch と取り込みを続けて行うコマンドで、pull.rebase false と設定してあれば、取り込みは git merge origin/main です。つまりこのシーンの fetch と merge を、1回で打つのと同じです。設定が無いまま枝分かれした状態で git pull を打つと、You have divergent branches and need to specify how to reconcile them.(枝分かれしているので、どうつなぐか決めて)と止まります。

fetch と merge を分けて打つ利点は、間で git status や git log --oneline --all を打って、何が届いたかを確かめてから取り込めることです。コンフリクトしそうなら、そこで止まって考えられます。

ただ、2台で作業するたびに merge でつなぐと、そのたびにマージコミットが1つ増え、グラフは小さな輪がいくつも連なった形になっていきます。中身には問題がありませんが、あとで歴史を読むときに「Merge remote-tracking branch 'origin/main'」の行がたくさん挟まります。これを避けて、手元のコミットを origin/main の先へ並べ直すのが、次の回の rebase です。

7. Claude Code が打つ merge

Claude Code が打った Git のコマンド延べ約14,000回のうち、git merge は55回で、そのうち25回が git merge --ff-only でした。git pull も51回のうち24回が --ff-only です。半分近くが「早送りで済むときだけ」の指定になっているのは、済まない場面では止まって、つなぎ方の判断を人に返せるからです。

ですから、--ff-only が止まったあとに Claude Code が --ff-only を外した git merge を打とうとしたら、それは「マージコミットを作る」という判断です。中身が消えることはありませんが、歴史の形は変わります。rebase で並べ直すほうがよいか、そもそもつなぐ前に確かめることがないかを、許可の前に一度考えてみてください。

8. Claude Code の許可画面で見かけたら

この回で見たコマンドを、「何が消えうるか」で並べるとこうなります。

コマンド何が起きるか消えうるもの
git merge 相手(早送りになるとき)今のブランチの付箋を前へずらす。新しいコミットは作らないなし(作業ツリーのファイルが新しい中身になる。書きかけが上書きされるなら止まる)
git merge 相手(枝分かれしているとき)親が2つのマージコミットを足して、今のブランチを進める。ぶつかればコンフリクトで止まるなし(歴史にマージコミットが残る)
git merge --ff-only 相手早送りで済むときだけ進める。済まないときは何も変えずに止まるなし
git merge --no-ff 相手早送りできるときでも、マージコミットを作るなし
git add / git commit --no-edit(コンフリクトの途中)解決した印を付ける / マージコミットを作ってマージを終えるなし(印が残っていても、そのままコミットされる)
git merge --abortmerge を打つ前の状態に戻す。付箋とコミットは動かない注意 コンフリクトを直しかけた作業
git pull(オプション無し)fetch して、設定によってマージコミットを作る・rebase する・止まる注意 歴史の形が変わる(設定を確かめる)

見分け方は、merge そのものは何も消さないということです。付箋を前へずらすか、コミットを1つ足すだけで、どちらのブランチのコミットも辿れるまま残ります。気をつけるのは、直しかけを捨てる --abort と、設定しだいで動きの変わる git pull の2つです。作ってしまったマージコミットを push の前に取り消したいときは、1-5 reset で付箋を1つ前に戻せます(--hard は書きかけも消すので、作業ツリーがきれいなときだけにします)。

まとめ

  • git merge 相手 は、相手の付箋が指すコミットを今のブランチに取り込む。動くのは今のブランチの付箋だけ
  • 今のブランチが相手の祖先なら fast-forward(付箋をずらすだけで、新しいコミットはできない)。枝分かれしていれば、親が2つのマージコミットを作る。取り込み済みなら Already up to date.
  • --ff-only は早送りで済むときだけ進めて、済まなければ止まる。--no-ff は早送りできてもマージコミットを作る(GitHub の Merge pull request と同じ形)
  • 別々のファイル・離れた行の変更は自動で混ざる。同じ行を両方が変えるとコンフリクトになり、<<<<<<< HEAD(今の側)/=======/>>>>>>> 相手(相手の側)の印が入る
  • 直し方は、ファイルを書き直して印を消す → git add → git commit --no-edit。やめるなら git merge --abort(直しかけは消える)。止まっている間は commit も switch もできない
  • 2台で枝分かれしたら、git fetch → git merge origin/main → git push でつなげる

merge は、枝分かれした2本をそのままの形で残して、マージコミットで結ぶ方法でした。次の回では、もう1つの方法 git rebase を見ます。手元のコミットを相手の先頭の上に作り直して、歴史を1本にまっすぐ並べるやり方です。作り直したコミットは中身が同じでも ID が変わります。Claude Code がよく打つ git rebase --autostash と git rebase --continue が何をしているかも、そこで1コマンドずつ確かめます。