はじめに
Git Advent Calendar 2023 4日目の投稿です。よろしくおねがいします。
Gitではベアリポジトリと通信するために、いくつかのプロトコルをサポートしてます(ここ)。
が、この前、↓の状況になりました。
他社の環境(ここでは外とも言います)にベアリポジトリがあって自社(ここでは自分とも言います)からはリポジトリを操作、確認できない状況です。
この際に、他社のベアリポジトリで行われるコミットを追従して自社のリポジトリへ非リアルタイムで同期をとろうとしてgit format-patch
を使おうとしたので備忘録的にメモしておきます。
無理が分かって結局やめてます(できないことはなさそうです)。
やろうとしたこと概要
1.外のリポジトリをとりあえず再現する。
とりあえず全量リポジトリを送ってもらいます。この時点でダルい
2.外で作業が行われる。
外でリポジトリに対して作業がしこしこ行われる。
3.外でパッチの生成をしてもらう。
git format-patch
を使って指定された各コミットに対して1つずつパッチを生成する。
4.自分ちにパッチを取り込む。
git am
でパッチを取り込む。
実際にやってみる
※読むのが面倒で結論を知りたい方は問題点まで飛んじゃっても読めると思います。
とりあえず外にあるベアリポジトリとして、以下のリポジトリを用意しました。
これが自分らの環境からはgit clone
もgit pull
もできないリポジトリとします。
~/Desktop/git/remote (master)
$ git log --oneline
30c185d (HEAD -> master) third commit
46072e4 second commit
c44c928 first commit
1.に沿って、このリポジトリを物理コピーしてローカルに再現したとします。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline
30c185d (HEAD -> master) third commit
46072e4 second commit
c44c928 first commit
ここで2.として外のリポジトリで何かしら作業が行われたとします。c8e94c6
のコミットを増やしました。
~/Desktop/git/remote (master)
$ git log --oneline
c8e94c6 (HEAD -> master) fourth commit
30c185d third commit
46072e4 second commit
c44c928 first commit
3.として、外側のリポジトリでパッチを生成します。
パッチの生成はgit format-patch -n
です。
~/Desktop/git/remote (master)
$ git format-patch -1
0001-fourth-commit.patch
パッチファイルがリポジトリの直下にできました。
~/Desktop/git/remote (master)
$ ls
0001-fourth-commit.patch sample.txt
4.として、このパッチを自分のリポジトリにあてます。
とりあえず自分のリポジトリの直下にファイルを持ってきました。
~/Desktop/git/自分側の環境/remote (master)
$ ls
0001-fourth-commit.patch sample.txt
パッチをあてるにはgit am
です。
~/Desktop/git/自分側の環境/remote (master)
$ git am 0001-fourth-commit.patch
Applying: fourth commit
ログを見てみると自分のリポジトリが外のリポジトリと同期が取れたことが分かります。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline
5501a99 (HEAD -> master) fourth commit
30c185d third commit
46072e4 second commit
c44c928 first commit
複数コミットがあったらどうなるか
今、外側のリポジトリで2つコミットを追加しました(bd8b31a
とe993c7d
)。
~/Desktop/git/remote (master)
$ git log --oneline
e993c7d (HEAD -> master) 6 commit
bd8b31a 5 commit
c8e94c6 fourth commit
30c185d third commit
46072e4 second commit
c44c928 first commit
外側のリポジトリでパッチを生成します。
~/Desktop/git/remote (master)
$ git format-patch -2
0001-5-commit.patch
0002-6-commit.patch
これを自分のリポジトリに持ってきます。
~/Desktop/git/自分側の環境/remote (master)
$ ls
0001-5-commit.patch 0002-6-commit.patch sample.txt
もってきたパッチを自分のリポジトリに取り込むためにgit am
してみます。
~/Desktop/git/自分側の環境/remote (master)
$ git am *
Applying: 5 commit
Applying: 6 commit
Patch is empty.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
~/Desktop/git/自分側の環境/remote (master|AM 3/3)
$ git am --skip
git log
して再現できたかどうか確認してみます。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline
cf9face (HEAD -> master) 6 commit
d485670 5 commit
5501a99 fourth commit
30c185d third commit
46072e4 second commit
c44c928 first commit
コミットメッセージが同じなので無事再現できることを確認できました。
ブランチが1本の時はコミットが複数でも問題なく再現できそうです。
コミットツリーが複雑だとどうなるか
もう少しコミットツリーを複雑にしてみました。
~/Desktop/git/remote (master)
$ git log --oneline --graph --all
* 8c6827d (HEAD -> master) master3 commit
* 6a7990b Merge branch 'another'
|\
| * 7c84e0a (another) another2 commit
| * 56f65c3 another1 commit
* | 5f935ea master2 commit
* | 56dae81 master1 commit
|/
* e993c7d 6 commit
* bd8b31a 5 commit
* c8e94c6 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
今、自分のリポジトリではe993c7d
まで再現できている状態です。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline --graph --all
* cf9face (HEAD -> master) 6 commit
* d485670 5 commit
* 5501a99 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
とりあえずe993c7d
以降のパッチを生成してみます。
~/Desktop/git/remote (master)
$ git format-patch master~4
0001-another1-commit.patch
0002-another2-commit.patch
0003-master1-commit.patch
0004-master2-commit.patch
0005-master3-commit.patch
パッチを自分のリポジトリに持ってきました。
~/Desktop/git/自分側の環境/remote (master)
$ ls
0001-another1-commit.patch 0003-master1-commit.patch 0005-master3-commit.patch
0002-another2-commit.patch 0004-master2-commit.patch sample.txt
このパッチをよしなに適用してくれるのか、git am
で自分のリポジトリに適用してみます。
~/Desktop/git/自分側の環境/remote (master)
$ git am *
error: patch failed: sample.txt:1
error: sample.txt: patch does not apply
hint: Use 'git am --show-current-patch' to see the failed patch
Applying: another1 commit
Applying: another2 commit
Applying: master1 commit
Patch failed at 0003 master1 commit
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
エラーがおこりました。
↓ログを見てみると、どうやら自動的にanother
ブランチは作ってくれず、master
ブランチにanother
ブランチで行ったコミットを適用しているようです。これは外側のリポジトリの状況と違いますね。
~/Desktop/git/自分側の環境/remote (master|AM 3/6)
$ git log --oneline --graph --all
* 572a316 (HEAD -> master) another2 commit
* bc8c0e6 another1 commit
* cf9face 6 commit
* d485670 5 commit
* 5501a99 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
もう少し調査してみます。
いまmaster
ブランチにanother
ブランチのコミットを付け足してますね。
間違ってくっつけた最新コミットの↓572a316
のファイルの状況です。「hogeanother2」と書いてあります。
~/Desktop/git/自分側の環境/remote (master|AM 3/6)
$ cat sample.txt
hogeanother2
適用しようとしているパッチファイルの中身をのぞいてみます。
適用しようとしているパッチの情報は「.git/rebase-apply/patch」ファイルにあります。
~/Desktop/git/自分側の環境/remote (master|AM 3/6)
$ cat .git/rebase-apply/patch
---
sample.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sample.txt b/sample.txt
index 21f1c7e..862d3a0 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1 +1 @@
-hogehoge6
+hogemaster1
--
パッチで適用しようとしているのは「hogehoge6」と書いてあるファイルに対して「hogemaster1」へ修正する内容なので、エラーが起こっています。
パッチの生成元のブランチの特性を知ることなしにパッチを適用することは難し(不可能?)そうです。
なんか手間がかかりそうですが、なんとか適用したいです。
そもそも今の状況だと適用が失敗しているので、とりあえずもとに戻します。
~/Desktop/git/自分側の環境/remote (master|AM 3/6)
$ git am --abort
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline --graph
* cf9face (HEAD -> master) 6 commit
* d485670 5 commit
* 5501a99 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
再現したいコミットツリーはこうでした。
~/Desktop/git/remote (master)
$ git log --oneline --graph --all
* 8c6827d (HEAD -> master) master3 commit
* 6a7990b Merge branch 'another'
|\
| * 7c84e0a (another) another2 commit
| * 56f65c3 another1 commit
* | 5f935ea master2 commit
* | 56dae81 master1 commit
|/
* e993c7d 6 commit
...
それぞれのパッチの内容はこうです。
- 0001-another1-commit.patch:
e993c7d
と56f65c3
の差分 - 0002-another2-commit.patch:
56f65c3
と7c84e0a
の差分 - 0003-master1-commit.patch:
e993c7d
と56dae81
の差分 - 0004-master2-commit.patch:
56dae81
と5f935ea
の差分 - 0005-master3-commit.patch:
5f935ea
と8c6827d
の差分
※パッチにはマージコミットに関する情報がないですね
まずはマージ前のmaster
ブランチの内容を適用していきましょう。
~/Desktop/git/自分側の環境/remote (master)
$ git am 0003-master1-commit.patch
Applying: master1 commit
~/Desktop/git/自分側の環境/remote (master)
$ git am 0004-master2-commit.patch
Applying: master2 commit
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline
46259c7 (HEAD -> master) master2 commit
d560904 master1 commit
cf9face 6 commit
d485670 5 commit
5501a99 fourth commit
30c185d third commit
46072e4 second commit
c44c928 first commit
git format-patch
のパッチファイルではブランチを再現できないので、ブランチを作成します。
外のリポジトリではe993c7d
(自分のリポジトリだとcf9face
)からanother
ブランチが派生してるので、対応する自分のリポジトリのコミットからブランチを作ります。
~/Desktop/git/自分側の環境/remote (master)
$ git checkout -b another cf9face
Switched to a new branch 'another'
次に、自分側で作ったanother
ブランチに対して、外側のリポジトリのanother
ブランチのパッチを適用していきます。
~/Desktop/git/自分側の環境/remote (another)
$ git am 0001-another1-commit.patch
Applying: another1 commit
~/Desktop/git/自分側の環境/remote (another)
$ git am 0002-another2-commit.patch
Applying: another2 commit
ここまで再現できました。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline --graph --all
* 01c0879 (another) another2 commit
* 3a6d509 another1 commit
| * 46259c7 (HEAD -> master) master2 commit
| * d560904 master1 commit
|/
* cf9face 6 commit
* d485670 5 commit
* 5501a99 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
外のリポジトリではanother
とmaster
をマージしているので、自分の方でもマージしてあげます。
マージがどうなったのか確認するために、パッチファイルを確認します。
~/Desktop/git/自分側の環境/remote (master)
$ cat 0005-master3-commit.patch
From 8c6827dd19e21b57657676fe92cd1a64cd7d4c26 Mon Sep 17 00:00:00 2001
From: hogehoge <fugafuga>
Date: Tue, 14 Nov 2023 09:36:10 +0900
Subject: [PATCH 5/5] master3 commit
---
sample.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sample.txt b/sample.txt
index cdfa144..352dcc4 100644
--- a/sample.txt
+++ b/sample.txt
@@ -1 +1 @@
-hogemaster2
+hogemaster3
--
マージ後は「hogemaster2」になってますね。
手動でマージして外のリポジトリと同じように修正して競合を解消してあげます。
~/Desktop/git/自分側の環境/remote (master)
$ git merge another --no-ff
Auto-merging sample.txt
CONFLICT (content): Merge conflict in sample.txt
Automatic merge failed; fix conflicts and then commit the result.
~/Desktop/git/自分側の環境/remote (master|MERGING)
$ cat sample.txt
<<<<<<< HEAD
hogemaster2
=======
hogeanother2
>>>>>>> another
~/Desktop/git/自分側の環境/remote (master|MERGING)
$ echo "hogemaster2" > sample.txt
~/Desktop/git/自分側の環境/remote (master|MERGING)
$ git add sample.txt
~/Desktop/git/自分側の環境/remote (master|MERGING)
$ git commit
[master 629622a] Merge branch 'another'
最後にマージコミットの後のパッチをあててあげます。
~/Desktop/git/自分側の環境/remote (master)
$ git am 0005-master3-commit.patch
Applying: master3 commit
再現できました。
~/Desktop/git/自分側の環境/remote (master)
$ git log --oneline --graph --all
* 23148e2 (HEAD -> master) master3 commit
* 629622a Merge branch 'another'
|\
| * 01c0879 (another) another2 commit
| * 3a6d509 another1 commit
* | 46259c7 master2 commit
* | d560904 master1 commit
|/
* cf9face 6 commit
* d485670 5 commit
* 5501a99 fourth commit
* 30c185d third commit
* 46072e4 second commit
* c44c928 first commit
ブランチが複雑な場合のやり方をまとめると、
- とりあえずブランチ全部のログと自分のリポジトリにないパッチをもらう
- 自分のリポジトリに足りないブランチを手動で作成する
- パッチファイルの中身とログからどこのコミットに何のパッチをあてるか整理する
- 各ブランチにパッチをあてる
- 外のリポジトリでマージされてるときは手動でマージして競合があれば解決してあげる
正直やってられるか、って感じです。
一応問題点を整理
- ブランチ1本であれば問題なく再現できますが、ブランチが複数できた際にパッチではブランチを作成してくれないので自分で外側のリポジトリの状況を確認してブランチを作成したり競合解決するのがめちゃくちゃ手間です。
- あと、実際に使用する際は、外のリモートリポジトリ保持者に
git format-patch コミット範囲
でパッチを取得してもらって自分に送ってもらう必要があるのでそこもめんどくさいです。 - もはや
git format-patch
が今回の用途に合ってないことが分かった後に言うのもアレですが、1.の手順でリポジトリを再現する方法として物理コピーしてますが、プロジェクトが大きくなった後にやろうとすると、コピーもめんどくさいです。
上のような問題点があり結局git format-patch
を使いませんでした。
そもそもコマンドの目的が違うんでしょうね。勉強になりましたとさ。
- ドキュメント