Table of Contents
Describe the Error
ローカル側でgit add
& git commit
を実行した後, リモートへpushを試みた際に以下のようなエラーメッセージに直面.
1
2
3
4
5
6
7
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/RyoNakagami/github_sandbox.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Remote Repository側の変更をローカルに反映していないことに起因するエラーなので, git pull
を実行すれば良いとのことなので,
一旦 git pull
を実行. しかし, ローカルでcommit
してしまっていたため次のようなエラーに直面.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% git pull origin main
...
...
From https://github.com/RyoNakagami/github_sandbox
* branch main -> FETCH_HEAD
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
▶ どういうとき発生しやすいのか?
- Remote Repository側でPull requestをmergeした場合
- Issue TemplateをRemote側で設定してcommitした場合など
How to Solve the Problem
方針
- 方針1: ローカル側のcommitを取り消し,
pull
&commit
&push
- 方針2:
git rebase
を使って, 最新のアップストリームブランチを参照先に変更する
方針 1.1: git reset
を使って, Commitを取り消す
▶ Commands
1
2
3
% git reset --soft <commit-ID>
% git reset --mixed <commit-ID> # Default, ステージングエリアも取り消し(= git addの取り消し)
% git reset --soft HEAD^ # 直前に戻る場合
Optionを問わず共通しているのは, Commitを取り消し, commit-ID
で指定された特定の時点まで戻ること(=HEADの位置が修正される)です. 誤ったコミット自体を削除出来るので修正後のコミットログが見やすくなるというメリットがあります. なお, HEAD^
は, HEADリビジョンの1つ前までという意味なので, 直前に戻りたい場合はcommit-ID
をわざわざ指定するのではなくHEAD^
の方が簡単です.
Optionによる挙動の違いは以下:
Option | 挙動 |
---|---|
--hard |
コミット, インデックス, ファイルの変更をすべて削除 |
--mixed |
コミット, インデックスを削除. ファイルの変更だけは残す |
--soft |
コミットだけを削除する. インデックス, ファイルの変更は残す |
git reset --hard
を指定する場合は「変更自体を手元(ワーキングツリー)に残す必要すらないほど」という例外的な状況と認識しています. 昔の環境での挙動を確認したい場合など完全に昔の環境に戻したい場合はあるかもですが, そのような場合はgit checkout
を利用することが推奨されます.
▶ 運用における注意点
- コミットそのものを削除してしまうので, 他の開発者が依拠している親コミットを消してしまうリスクがある
- ローカルな変更を取り消して元に戻したいときに限って使用
方針 1.2: git revert
を使って, Commitを戻す
▶ Commands
1
2
% git revert <commit-ID>
% git revert HEAD^ #直前に戻る場合
git reset
と異なり, git revert
で指定したコミット時点の状態まで作業ツリーを戻す = 逆向きのコミット」の履歴が残るという特徴があります. コミット自体を削除するわけではないので, 安全にコミットを元に戻すことができるというメリットがあります.
▶ 運用における注意点
- conflictを引き起こすCommitが1つだけならば問題ないが, 複数の場合は変更履歴の整理が大変になるので非推奨
方針2: git pull --rebase
を使って, 最新のアップストリームブランチを参照先に変更する
方針
リモート側の変更内容を diff-1
, ローカル側の変更内容群を diff-2
としたとき,
ローカル側にて以下の手順でコンフリクトを解消していきます:
git pull --rebase
でremoteとlocalのconflict箇所を確認する(このときbranch nameは<local branch>|rebase
となっている)diff-1
とdiff-2
の採択の取捨選択をし,git add
git rebase --continue
でrebaseを実行(このときbranch nameは<local branch>
に戻る)- remoteへpush = remote repositoryにdiff-2をリモートに反映させる
logをきれいにしたままconflictを解消することが出来ます.
▶ Commands
1
2
3
4
5
6
% git pull origin <branch name> --rebase
From https://github.com/RyoNakagami/github_sandbox
* branch <branch name> -> FETCH_HEAD
Successfully rebased and updated refs/heads/main.
% git add <conflict-resolved files>
% git rebase --continue
References
- stackoverflow > git pull –rebase - how to proceed after conflict resolution
- YoheiM.NET > [git] 変更を取り消す
- ラクスエンジニアブログ > 【Git入門】git commitを取り消したい、元に戻す方法まとめ
(注意:GitHub Accountが必要となります)