ブランチ作成
コミットを1つにまとめる方法
作業ブランチでコミットを1つにまとめる方法
トラブルシューティング
【事象】
VSCodeで3つをコミットした。
ステータスバーのGit Graphのボタンをクリックして、Git Graphの画面を開く。
まとめたいコミットの一つ前のコミット(今回だとinit)を右クリックして、「Rebase current branch on this Commit...」を選択する。
「Launch Interactive Rebase in new Terminal」にチェックを入れて「Yes, rebase」をクリックする。
vimで「i」を入力してまとめたいコミットを上から順にpickから「f」に変更する。
最後の一つはpickのままにした。
その後にESCキー押下して:wqを入力しました。
VSCodeのターミナルに下記が表示されました。
git rebase --interactive xxxxxx
error: cannot 'fixup' without a previous commit
error: cannot 'fixup' without a previous commit
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
Or you can abort the rebase with 'git rebase --abort'.
(回答)
原因は、Interactive Rebaseで fixup(f) に変更したコミットの直前に、pick のコミットが存在しないためです。
今回の操作では、コミットをまとめる順番とpick / fの指定が原因になっている可能性が高いです。
1. なぜエラーになるのか
GitのInteractive Rebaseでは、次のルールがあります。
pick:そのコミットを採用する
fixup(f):直前のコミットに変更内容をまとめる
つまり、fixup は単独では使えません。必ず直前に pick などの取り込み対象コミットが必要です。
例えば、次のようになっているとエラーになります。
f commit A
f commit B
pick commit C
正しい指定方法
3つのコミットを1つにまとめたい場合、例えば以下のようにします。
pick commit A
f commit B
f commit C
この場合、commit Aを残して、commit Bとcommit Cの変更をcommit Aにまとめます。
ただし、コミットの順番はGit Graphに表示される履歴とInteractive Rebaseの編集画面で確認してください。
- 今回のエラーを解消する方法
エラーメッセージにあるとおり、次のコマンドを実行します。
git rebase --edit-todo
コミット名を変更する
コミット名を変更するコマンドは下記です。
git commit --amend -m "新しいコミットメッセージ"
チェリーピック
Cherry-pickとは?
Cherry-pickは「違うブランチにあるコミットの変更を、現在のブランチに適用する」機能
取り込む順番は、必ず古いコミット番号から取り込むこと。
今回は、異なるブランチにある同じファイルをcherry-pickコマンドを使ってコミットを更新する方法を紹介します。
まずは、新規ブランチを作成します。
git switch -c feature/child/<新規ブランチ名>
1. 現在の状態を確認
まず以下を実行してください。
git status
Cherry-pick中ではないことを確認します。
※Cherry-pick中の場合は、このコマンド「git cherry-pick --skip」でCherry-pickを停止します。
- 1つ目をCherry-pick
git cherry-pick <コミット番号>
成功すれば、1つ目のコミットが作成されます。
- 2つ目をCherry-pick
git cherry-pick <コミット番号>
4. 結果を確認
git log --graph --oneline --decorate -10
期待するイメージは以下です。
* xxxx (HEAD -> feature/child/pjnum2) 2番目のコミット
* yyyy 1番目のコミット
* a5fba3a aaa
※ yyyy と xxxx はCherry-pickによって新しく作られるコミットIDです。
トラブルシューティング
作業ブランチにてブランチが意図しないところにつながってしまった

(原因)
状況としては、childブランチの履歴がリベースを繰り返した結果、意図しない3方向に分岐してしまった。
作業ブランチだけというのは好条件です。リモートへの影響を心配せずに整理できます。
ただし、今はまだresetやrebaseをしないでください。
最初にGitのコミット履歴を、ブランチの分岐や統合の様子も含めて、見やすく表示するコマンドを実行します。
git log --graph --oneline --decorate --all -30
結論から言うと、childブランチでリベースした履歴と、リモート側に残っていた古い履歴が、複数回 merge されてしまっています。
ただし、良いニュースがあります。
■main とparentは コミットIDa5fba3a で一致しています。
■child側の本来のコミット3つ(最初・2番目・3番目)は連続して残っています。
■リモートにはまだ push していないとのことなので、ローカルで整理できます。
ローカルchildを整理する
現在のログでは、すでにHEADが3750853なので、次の操作で目的の位置に戻せます。
なので、下記のコマンドを実行します。
git switch feature/child/pjnum1
git reset --hard 3750853
ただし、reset --hard は未コミットの変更を消すため、事前に git status がcleanであることを確認してください。
これで、作業ブランチがぐちゃぐちゃな状態から抜け出すことができました。
ただし、VSCodeの「Sync Changes」ボタンは押さないこと。
今回の状況では、押すと意図しない変更がGitHub側に反映されたり、リモートの変更を取り込んだりする可能性があります。
現在の状態
いまは次のようになっています。
・ローカルchild:きれいな3コミットの履歴
・リモートchild:古いmerge履歴(a0b9a87 など)
・main / parent:変更なし
つまり、ローカルとリモートで履歴が異なっています。
現在のGitの状態を、短くコンパクトに表示するコマンドを入力します。
git status -sb
続いて、
git fetch origin
これはリモートの情報を取得するだけで、GitHub側のブランチを書き換える操作ではありません。
最後に、
git log --graph --oneline --decorate --all -15
git status -sbを実行すると下記になった。
## feature/child/pjnum1...origin/feature/child/pjnum1 [behind 6]
これは、ローカルのchildがリモートのchildより6コミット分遅れているという意味です。
今回のケースでは、リモート側に不要なmerge履歴が残っているため、単純に6コミット分Pullすればよいわけではありません。
次に確認したいこと
以下のコマンドを実行してください。
git log --oneline --graph --left-right HEAD...origin/feature/child/pjnum1
このコマンドで、ローカルとリモートの差分を確認できます。
■ <:ローカルにしかないコミット
■ >:リモートにしかないコミット
さらに、次のコマンドも実行してください。
git merge-base HEAD origin/feature/child/pjnum1
これで両者の共通祖先が分かります。
次は、リモートの作業ブランチとローカルの作業ブランチの履歴を合わせるようにします。
今の状態で不用意にPullやSync Changesをすると、リモートに残っている古い履歴を再び取り込んでしまい、同じようにブランチが複雑になる可能性があります。
ただし、今回おすすめしたいのは、リモートのブランチを削除することではなく、ローカルのchildを正しい状態に保ち、必要ならリモートを後から整理することです
- リモートの6コミットはどうする?
現在の状況はこうです。
- リモートを更新する
git push --force-with-lease origin feature/child/pjnum1
3. 今後のリベース運用
今後、childでコミットをまとめる場合は次の流れがおすすめです。
■git rebase -i でコミットをまとめる。
■ローカルで履歴を確認する。
■git push --force-with-lease でリモートを更新する。
■VSCodeの通常のPullやSync Changesで古い履歴を取り込まない。
引用


