MarkdownとBullet Journal

いわゆるプログラマーのつぶやき

【Git】複数PCを用いたresetやamend利用法

commit改変時の複数PCのリポジトリの合わせ方

個人開発では会社用と自宅用にそれぞれPCを置いて同じリポジトリを用いながら編集を継続する運用方法がある(私もそう)。その場合、どちらかのPCでリモートからpull&更新&commit&pushを行い、もう1台のPCを使う際には再びリモートからpull&更新&commit&pushを行う形で、違うPCでもよどみなく編集を継続出来る。

但しcommitを整理する目的でgit rebase,git resetgit commit --amendなどを使用するとcommit IDが変わるため、リモートリポジトリやもう1台でのローカルリポジトリgit pullを行う際にconflictする。この記事は個人で複数台のPC使用時にreset使用等でconflictした場合のcommit履歴の整理方法を記載する。

f:id:ProgrammingForEver:20210922205020p:plain

git reset使用例

個人開発で複数台のPCを利用するケースで、git resetなどcommit IDの改変が行われる作業を行うと、複数人での開発同様に各ローカルリポジトリやリモートリポジトリとのconflictが生じる。

以下の例では、ある1台のPCで一気に最初のcommitまでresetする極端なgit resetを行ったと仮定する。その段階での作業ディレクトリにはa.sh, b.sh, c.sh, d.shの4fileだけがあると仮定し、reset直後にstaging&commitを行うとする(2回目のcommitになる)。

$ git reset HEAD~10
Unstaged changes after reset:
M       a.sh
M       b.sh
M       c.sh
M       d.sh

$ git log
commit f0f8ac18d38bf64ff3cab023fb7330280fdadcf4 (HEAD -> main)
Author: ************
Date:   Mon Sep 20 15:39:36 2021 +0900

    最初のcommit

$ git add .
$ git commit -m "2回目のcommit"
[main 736ed4a] 2回目のcommit
 2 files changed, 6 insertions(+)

$ git log --oneline
736ed4a (HEAD -> main, origin/main) 2回目のcommit   # - 1台目PCのcommit履歴
f0f8ac1 最初のcommit

リモートリポジトリへ強制push

次にresetした後のcommit(新2回目)をGitHubなどのリモートリポジトリにpushする。但し、reset前の2回目のcommitからIDが変化しているので普通にpushするとマッチしないためエラーで停止する。そこでpushコマンドに強制オプションの-fを付けて実行することで、resetを行ったローカルリポジトリの内容をリモートリポジトリに強制的に更新する。

この記事では自分のPC間のリポジトリ共有を対象としているので納得した上で行えるが、複数人で開発している場合は事前許諾を得てから行わなければならない。

$ git push -f
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 788 bytes | 788.00 KiB/s, done.
Total 5 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.

conflict発生

さてリモートリポジトリにreset後のcommitが無事に登録出来たので、もう1台のPCでこのリモートリポジトリからgit pullすると、ここでもconflictが発生して本当なら2つだけのcommitのはずが4つcommitが見えており、マージ作業が求められる。これはもう1台の2番目のcommit(reset前のcommit:旧IDとする)とreset&push&pullした新しいcommitのID(新ID)が一致しないためで、このconflictも当然の結果だ。

$ git pull
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 5 (delta 4), reused 5 (delta 4), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://github.com/*********
 + e0a9a2d...0fccb57 main       -> origin/main  (forced update)
Merge made by the 'recursive' strategy.

$ git log --oneline
9739d8f (HEAD -> main) Merge branch 'main' of https://github.com/********** into main
0fccb57 (origin/main) 2回目のcommit   # - git pull後のcommit(新ID)
e0a9a2d 2回目のcommit   # - git pull前のcommit(旧ID)
f0f8ac1 最初のcommit

しかし本来commitの整理のためにgit resetgit commit --amendを使ったのに、その都度余分なマージcommitが生成される様では本末転倒である。ここでやりたいのはgit push -fの様に強制的なgit pullなのでその方法を以下説明する。

git-pullの強制conflictの解決方法

作業対象をmainブランチとする。なお下記作業を行うとstaging、作業ディレクトリにある変更が全て消失する点に注意。

$ git checkout main  # - mainブランチにcheckoutする
$ git fetch origin main   # -  リモートの最新状態を取り込む
$ git reset --hard origin/main   # - hardモードでリセット

$ git log --oneline
736ed4a (HEAD -> main, origin/main) 2回目のcommit   # - 1台目PCと同じcommit履歴になる
f0f8ac1 最初のcommit

既にgit pullでconflictしてしまった場合も以下で対応出来る。

$ git merge --abort     # - mergeを取り消す
$ git fetch origin main   # - リモートの最新状態を取り込む
$ git reset --hard origin/main   # - hardモードでリセット

$ git log --oneline
736ed4a (HEAD -> main, origin/main) 2回目のcommit   # - 1台目PCと同じcommit履歴になる
f0f8ac1 最初のcommit

上記の様に2台目のPCも1台目のPCと同じく整理されたcommit状態になる