git

リモートリポジトリの更新後にローカルリポジトリを編集 & commitした場合のエラー対策

git trouble-shooting 1/N

公開日: 2020-12-30
更新日: 2024-07-11

  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としたとき,

ローカル側にて以下の手順でコンフリクトを解消していきます:

  1. git pull --rebaseでremoteとlocalのconflict箇所を確認する(このときbranch nameは<local branch>|rebaseとなっている)
  2. diff-1diff-2の採択の取捨選択をし, git add
  3. git rebase --continueでrebaseを実行(このときbranch nameは<local branch>に戻る)
  4. 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



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)