あれ?戻らない…とりあえずブランチを変えていたが。
ファイルを編集して「やっぱり前の状態が良かったな」と思って、 いつもの感覚で 「戻る」を押してみたけど……戻らない。
「あれ?おかしいな?」
そう思って、とりあえず今の状態を壊したくなかったので、 別のブランチを切って切り替えるという方法でとりあえず戻った。
git switch -c feature/B # 編集途中を別ブランチに分ける
git switch feature/A # ひとつ前のコミットに戻る
これで目的の状態には戻れたんですが、 ふと思いました。
「このやり方って、正攻法なの?」
個人開発だからかGitの使い方が適当すぎて、いざ困ったときにあたふたするのは前回でも言ったとおり。そんな疑問が出てきたので、またChatGPTに聞いてみた。
ChatGPTの回答:–softで戻せ。
- あいうえお → コミット(A)
- かきくけこ → コミット(B)
- さしすせそ → 編集中(未コミット)
このとき、「さしすせそ」は間違っていたから、 **一つ前の『かきくけこ』まで戻したい!**という状況を想定する。
方法1:いったんコミットして --soft で戻す(安全)
git commit -m "さしすせそ"
git reset --soft HEAD~1
HEAD~1は「1つ前のコミット」って意味。
この操作をすると、次のような状態になります:
※以下はChatGPTのコメントそのまま載せてます(「かきくけこ」を残せって言っているのに、「あいうえお」を残してます)
さしすせそをコミットしていない状態で実行すると、かきくけこ ではなく あいうえお まで戻るので、おそらくそういう想定だろう。
コミット履歴:
A: あいうえお
(B: かきくけこ → 履歴からは消えるが、内容はステージ状態で残る)
ツリー構造で表すと、こうなります:
[あいうえお] ← HEAD(現在)
↑
B(かきくけこ):ステージ状態で残る
そのまま修正して再度コミットすればOKです。 履歴を汚さず、安全に作業を続けられる方法です。
方法2:--hard で強制的に戻す(危険)
git reset --hard HEAD~1
この操作は、直前のコミット(かきくけこ)だけでなく、 作業中の内容(さしすせそ)もすべて消えてしまうという点で注意が必要です。
結果:
コミット履歴:
- A: あいうえお
ツリー構造はこう:
[あいうえお] ← HEAD(現在)
ファイルの中身も履歴も完全に巻き戻されます。
⚠️
--hardは本当に「全部なかったことにしたい」ときだけ使うべきです。
試してみたけど –softで戻らない。
「かきくけこ」を残せって言っているのに、「あいうえお」を残したのはまあ・・・それは目をつぶって、
とりあえずわかったとChatGPTの情報を鵜呑みにして、だいたいはそこで終わるんだけど、
一応本当かどうか実証しておこうかと思って試してみた。
aaaaa と書いて「コミット:あいうえお」
bbbbb と追記して「コミット:かきくけこ」
ccccc と追記して「コミット:さしすせそ」
入力した結果、こうなっている。
左:Git Graph(VSCode拡張)で現在のコミット状態を確認
右:Cursorエディタで開いたテキストファイルの中身

git reset –soft HEAD~1を実行。

たしかにコミットは戻っているけど、文章が戻ってない。(cccccが残っている)
git reset –hard HEAD~1だと確かに戻った。

コミットも文章も戻っているようだ(cccccが消えている)
もう一度ChatGPTに聞いてみる。
- git reset –soft HEAD~1で戻らないんだけど。
-
はい、それ、実は「正常な挙動」です。
✅--softは「履歴だけを戻す」git reset --soft HEAD~1は、最後のコミットをなかったことにするだけで、
ファイルの中身(=ワークツリー)やステージ状態はそのまま残ります。
この辺がよくいう「プロンプト」の難しいところ。
「かきくけこ」に戻れるんだよね?と試す前に念は押したんだけど、
「はい、そのとおりです。このケースでは、git reset –soft HEAD~1 を使えば「かきくけこ」の状態に戻れます!」
と自信満々で回答しただけに、なかなか意図が伝わずに難しいところ。
それか、単純によく言われる「ChatGPTは回答がコロコロ変わる」という所なのか。
よく個人開発は「ChatGPT」よりも「Claude」のほうが名前はよく出てくるが、変な情もわいてきて「ChatGPT」+「Cursor(コードエディタ)」を現在は採用している。
結局、ブランチ変えたほうが楽かな?
その後も、「教えてほしいことはこういうことだった」とか、「他の方法とかないの?」とか小一時間くらい話したけど、結局内容など何も入ってこない。
色々ブランチ変えるのはいいのか悪いのか結局結論が出なかったけど、結局そのほうが失敗が少ないかと。
個人開発なのでまあ自分がわかればいいかと、思っている。
(追記)mergeで衝突したらどうする?
今回の経緯(なぜ衝突が起きたのか)
今回、自分がやってしまったのはこういう流れでした。
- A→B→Cまでコミットを重ねた。
- でも「この方向は違う」と判断して、Aに戻って修正した
- その修正を積み重ねてDブランチを作った。
- 「よし、これをmainに入れよう」と思ってmainにmergeしたら衝突した
つまり、状況としてはこう。
main側(C側)でも編集していて、D側でも同じファイルを編集していた。だからGitが「どっちの内容にする?」と止まった。
実際に衝突していたのは、たとえばこんなファイルでした。
src/middleware.tssrc/app/(auth)/layout.tsx
そもそも「衝突」って何?
衝突(conflict)はエラーではなく、Gitの確認待ちです。
Gitはmergeのとき、だいたいは自動で合体できます。でも、
同じファイルの同じ行付近を、両方のブランチが書き換えていた
このときだけ、Gitは自動で決められません。
だから「あなたが決めてください」となります。これが衝突。
衝突したかどうかの確認方法
mergeして衝突すると、だいたいこんな表示が出ます。
CONFLICT (content): Merge conflict in ...Automatic merge failed; fix conflicts and then commit the result.
まずは状態確認。
git status
ここで

Unmerged pathsboth modified: ...
が出ていれば、まさに衝突中です。
衝突したらいったん中断してもいい
git merge –abort
これで、merge前の状態に戻ります。
- mainブランチはクリーンな状態に戻る
- 衝突は解消されていない(単に取り消しただけ)
- また `git merge` すれば同じ衝突が起きる
つまり、**「やり直しボタン」** です。
衝突の解決方法が決まったら、もう一度mergeすればいい。
---
「どっち側」が正しいかを決める(ours / theirs)
衝突でややこしいのは、“どっち側?” です。
ここは一行だけ覚えればOK。
いま自分がいるブランチが「ours」
mergeで取り込もうとしている相手が「theirs」
たとえば、こういう順番でやっているなら:
git switch main
git merge feature/D
この場合は
ours= main側theirs= feature/D側
になります。
完成ブランチ側(theirs)を採用する手順(丁寧版)
「今回の衝突は、A2側を採用したい」と決まっている場合は、次の流れで解決できます。
1) まずmainでmergeする
git switch main
git merge ファイル名(ここではfeature/D)
ここで衝突したら次へ。
2) 衝突中のファイルをまとめて”theirs(D側)”にする
git checkout --theirs ファイル名(ここではfeature/D)
これで「衝突しているファイル」を含め、作業ツリー側をA2側の内容に寄せられます。
3) 状態確認(衝突が解消されたか)
git status
Unmerged paths が消えていればOK。
念のため、衝突マーカーが残ってないかを確認するなら:
git grep -n "<<<<<<<"
何も出なければOK。
4) add → commit(これでmergeが完了)
git add .
git commit
このcommitは「衝突を解消してmergeを完成させた」記録になります。
5) 最後にpush
git push origin main
git merge -X theirs を使う方法
上の手順だと、mergeして衝突してから手動で解決しますが、最初から「衝突したらfeature側を優先したい」と決まっているなら、こう指定できます。
git switch main
git merge -X theirs ファイル名(ここではfeature/D)
-X theirs の意味
「衝突した箇所は、取り込む側(feature側)を優先して自動解決してね」という指定です。
重要なのはここ。
-X theirs は「衝突した部分だけ」に効く
- すべてのファイルを上書きするわけではない
- 衝突していない変更は通常通りマージされる
- 衝突した”行”だけ、feature側を採用する
つまり、かなり穏やかな自動解決です。
比較: 3つの方法の違い
ここが混乱ポイント。
① git merge -X theirs ファイル名(ここではfeature/D)
- merge時に衝突部分を自動でfeature優先
- 手動作業がほぼ不要
- 比較的安全
② git checkout --theirs
- すでに衝突している状態で使う
- 作業ツリーをfeature側に寄せる
- やや強め
③ git reset --hard ファイル名(ここではfeature/D)
- 完全にmainをfeature/A2と同じ状態にする
- mergeですらない
- 履歴の意味が変わる
注意点(これだけは知っておく)
「全部theirs」は強い
git checkout --theirs . や -X theirs は便利ですが、強いです。
- main側の変更も含めて、衝突ファイルは基本A2側に寄る
- 「ほんとはmain側の一部だけ残したかった」場合は、ファイル単位で調整が必要
逆に言うと、
“今回はDが正解”と決め切っている時だけ使う
が安全です。
まとめ: 衝突は失敗じゃない
衝突が起きたら、やることは1つ。
どっちを採用するか決めて、add→commitして終わらせる
衝突は「失敗」ではなく、Gitが「最終決定をあなたに委ねてくれている状態」です。
衝突で慌てるのは、
仕組みが分からないから怖い
だけ。
でも実際はこう。
- merge = 合体
- conflict = 人間に最終判断を求める
-X theirs= 衝突時の自動ルール指定
Gitは壊れていない。ただ確認しているだけ。
