git で既存ツリーの途中にコミットを入れたい
◆ 1 ブランチなら単純にリベース
◆ 複数ブランチだとリベース先を考えながらリベースすればできる
◆ 一時ブランチに全部をマージして preserve-merges 設定して rebase すると構造が保持される
◆ リベース先にブランチを移動して 一時ブランチを消せば完成
◆ この処理も自動でするようにした
◆ 複数ブランチだとリベース先を考えながらリベースすればできる
◆ 一時ブランチに全部をマージして preserve-merges 設定して rebase すると構造が保持される
◆ リベース先にブランチを移動して 一時ブランチを消せば完成
◆ この処理も自動でするようにした
git のツリーですでにあるコミットとコミットの間に新しいコミットをはさみたいです
しかし はさみたい場所以降に多数のブランチがある場合は簡単には行きません
アルファベットがコミットで () の数字がブランチです
いろいろ試すために何度も作ったので自動で作れるスクリプト化したものもあります
このツリーの A と B の間にコミットを入れたいです
X を A から追加して
B 以降を X の後ろに持ってきたいです
ばらばらになります
B が全部にあります
うまくできました
しかし ツリーを見てどこにリベースすべきとか考えるのは大変ですし ブランチが多かったり 追加を何度もやることになると……
さすがにどうにかしたいです
リベースするときに 枝分かれしてマージされた情報も保持してリベースしてくれるものです
ただ これはマージされてるものをリベースしたときにしか意味がありません
なら 一時的にマージすればいいやと やってみました
うまくツリー構造が保持されています
あとはブランチをリベース先に移動させて一時的なマージブランチである 0 を消せば完了です
0 ブランチにマージするとき競合があっても どうせ消すものですから 無視してマージしてしまっても大丈夫です
shellscript 力がなくて複雑なことはできる気がしないので python ですることにしました
これまでのマージしてリベースする部分も含んでいます
プログラムから実行するとユーザとメールアドレスにいつものを使ってくれなかったので config で指定しています
移動させたい全部のブランチをマージしているので リベース後のマージブランチの各親コミットが新しいブランチにするコミットになります
現在のブランチのコミットとコミットメッセージを比較して同じものならそこに移動させます
同じコミットメッセージがあると判断できないのでブランチ移動はされません
1 ブランチなら
ブランチが一つなら はさみたいところの親コミットから分岐してはさみたいコミットを作って ブランチをそこへリベースすればいいです--A--B--C-->
⇩
Z
/
--A--B--C-->
⇩
Z--B--C-->
/
--A
しかし はさみたい場所以降に多数のブランチがある場合は簡単には行きません
ブランチが複数あるとき
こういう枝分かれしてるツリーがありますアルファベットがコミットで () の数字がブランチです
* H (4)
| * G (3)
|/
* F
| * E (2)
| | * D (1)
| |/
| * C
|/
* B
* A
いろいろ試すために何度も作ったので自動で作れるスクリプト化したものもあります
#!/bin/sh
function commit() {
touch $1
git add -A
git commit -m $1
}
git init
commit A
git branch 1
git checkout 1
commit B
git branch 3
commit C
git branch 2
commit D
git checkout 2
commit E
git checkout 3
commit F
git branch 4
commit G
git checkout 4
commit H
このツリーの A と B の間にコミットを入れたいです
X を A から追加して
* X (n)
| * H (4)
| | * G (3)
| |/
| * F
| | * E (2)
| | | * D (1)
| | |/
| | * C
| |/
| * B
|/
* A
B 以降を X の後ろに持ってきたいです
* H (4)
| * G (3)
|/
* F
| * E (2)
| | * D (1)
| |/
| * C
|/
* B
* X (n)
* A
単純リベース
単純に全ブランチを n (X のコミット) にリベースするとこうなります* H (4)
* F
* B
| * G (3)
| * F
| * B
|/
| * E (2)
| * C
| * B
|/
| * D (1)
| * C
| * B
|/
* X
* A
ばらばらになります
B が全部にあります
リベース先を調整すれば
リベース先を工夫すればgit rebase n 1
git rebase (新しいCのハッシュ) 2
git rebase (新しいBのハッシュ) 3
git rebase (新しいFのハッシュ) 4
* H (4)
| * G (3)
|/
* F
| * E (2)
| | * D (1)
| |/
| * C
|/
* B
* X (n)
* A
うまくできました
しかし ツリーを見てどこにリベースすべきとか考えるのは大変ですし ブランチが多かったり 追加を何度もやることになると……
さすがにどうにかしたいです
一時ブランチにマージしてリベース
考えているとそういえばリベースには preserve-merges という機能がありましたリベースするときに 枝分かれしてマージされた情報も保持してリベースしてくれるものです
ただ これはマージされてるものをリベースしたときにしか意味がありません
なら 一時的にマージすればいいやと やってみました
git checkout 1
git branch 0
git checkout 0
git merge 2 3 4
git rebase --preserve-merges n
*---. Merge branches '2', '3' and '4' into 0 (0)
|\ \ \
| | | * H
| | * | G
| | |/
| | * F
| * | E
* | | D
|/ /
* | C
|/
* B
* X (n)
| * H (4)
| | * G (3)
| |/
| * F
| | * E (2)
| | | * D (1)
| | |/
| | * C
| |/
| * B
|/
* A
うまくツリー構造が保持されています
あとはブランチをリベース先に移動させて一時的なマージブランチである 0 を消せば完了です
0 ブランチにマージするとき競合があっても どうせ消すものですから 無視してマージしてしまっても大丈夫です
* H (4)
| * G (3)
|/
* F
| * E (2)
| | * D (1)
| |/
| * C
|/
* B
* X (n)
* A
ブランチ移動なども自動でやりたい
ここまでできると ブランチを新しいリベース先に移動させて 0 ブランチの削除までやってしまいたいですshellscript 力がなくて複雑なことはできる気がしないので python ですることにしました
これまでのマージしてリベースする部分も含んでいます
プログラムから実行するとユーザとメールアドレスにいつものを使ってくれなかったので config で指定しています
import subprocess
branches = ["1", "2", "3", "4"]
rebaseto = "y"
tmpbranch = "0"
config = "-c user.name=dummy -c user.email=dummy@mail"
def runp(cmd):
return subprocess.run(cmd, check=True)
def run(cmd):
return subprocess.run(cmd, check=True, capture_output=True).stdout.decode("utf8")
runp(f"git checkout {branches[0]}")
runp(f"git branch {tmpbranch}")
runp(f"git checkout {tmpbranch}")
runp(f"git {config} merge {' '.join(branches)} --no-edit")
runp(f"git {config} rebase --preserve-merges {rebaseto}")
new_branches = run(f"git show --format=%P {tmpbranch}").split()
new_branches = [(b, run(f"git show {b} --format=%s -q").strip()) for b in new_branches]
old_branches = [(b, run(f"git show {b} --format=%s -q").strip()) for b in branches]
if len(old_branches) != len({b[1] for b in old_branches}):
print("同じコミットメッセージが存在するためブランチの移動は行なえません")
exit()
print("ob", old_branches)
print("nb", new_branches)
for ob in old_branches:
for nb in new_branches:
if ob[1] == nb[1]:
runp(f"git branch -f {ob[0]} {nb[0]}")
runp(f"git checkout {branches[0]}")
runp(f"git branch -D {tmpbranch}")
移動させたい全部のブランチをマージしているので リベース後のマージブランチの各親コミットが新しいブランチにするコミットになります
現在のブランチのコミットとコミットメッセージを比較して同じものならそこに移動させます
同じコミットメッセージがあると判断できないのでブランチ移動はされません