Gitのブランチ操作に関して練習したり、その方法を記事に纏めたりしたい。そのためには多少複雑なコミット履歴が必要になるのだが、どう用意しようか困っていた。
- 手動で作る
- 10コミット作るのも大変
- ランダム性が低く、あまり複雑な形にならない
- 実際のOSSのリポジトリを使う
- 手頃なものを見つける必要がある
- 間違えても本家にpushしてはいけない
- 練習とはいえ、意味の無いコミットをしづらい
- 実業務のリポジトリを使う
- 論外 最悪クビになる
なので、シェルスクリプトで自動的に作ることにした。自動化するためには普段使わないようなgitコマンドやオプションなどが必要だが、一度書いてしまえば済むので案外問題にならなかった。
実行例
後に示す create_git_sample.sh
で、20コミットのリポジトリを作ってみる。
$mkdir-p ~/projects/git-test &&cd ~/projects/git-test
$bash ~/create_git_sample.sh 20
#1: grow branch_1
#2: grow branch_2
#3: grow branch_3
#4: grow branch_1
#5: grow branch_5
#6: merge branch_5
#7: grow branch_2
#8: grow branch_8
#9: grow branch_3
#10: grow branch_10
#11: merge branch_1
#12: grow branch_3
#13: grow branch_2
#14: merge branch_3
#15: grow branch_10
#16: grow branch_16
#17: merge branch_16
#18: grow branch_10
#19: grow branch_8
#20: grow branch_20
$git branch
branch_10
branch_2
* branch_20
branch_8
master
$git log --all--graph--pretty='format:%h %s%d'# 結果は以下
commit-tree
* 31d637a commit #20 (HEAD -> branch_20)
* f2aa4aa commit #17 (master)
|\
| * a657015 commit #16
|/
* aba2761 commit #14
|\
| * 2ee59c2 commit #12
| * 41636ee commit #9
| * 4331a92 commit #3
| | * 067ef23 commit #18 (branch_10)
| | * 6183b74 commit #15
| | * 7623eea commit #10
| |/
|/|
* | c571fd8 commit #6
|\ \
| |/
|/|
| * 410deba commit #5
| * 85f8ad8 commit #4
| * aed22a4 commit #1
|/
| * f0b9948 commit #19 (branch_8)
| * ab171f9 commit #8
| | * 8eb7aa6 commit #13 (branch_2)
| |/
| * e4fdf2d commit #7
| * 86f5b66 commit #2
|/
* e06942b commit #0
rebaseなどの練習ができそうな、いい感じに複雑な履歴になっている。
リポジトリを消すときは次のコマンドを使う。(他のリポジトリのディレクトリで実行しないこと)
rm-rf* .gitignore .git/
スクリプト
動作は以下の通り。
- リポジトリを新規作成する
- 指定回数だけ以下のいずれかを実行する
- ランダムに選んだブランチを伸ばす
- masterや現在と同じブランチを選んだときは、ブランチを新規作成して伸ばす
- ファイルに書き込むデータは何でもいいので、とりあえず
git log --oneline
の結果としている
- ランダムに選んだブランチをmasterにマージして削除する
- master自身を選んでしまったときは、上記のブランチを伸ばす操作に切り替える
- ランダムに選んだブランチを伸ばす
Gitのバージョンは 2.7.4
。別のバージョンではオプションが使えなかったり、もっと良いオプションが使える可能性がある。
create_git_sample.sh
#!/bin/bashset-eufunction setup_git_repository(){
git init
git config --local user.email "xxx@example.com"
git config --local user.name "xxx"cat<<EOF> .gitignore
!*.txt
!.gitignore
EOF
git add .
git commit -m"commit #0"}function show_branch_name(){
git symbolic-ref --short HEAD
}function pick_branch_name(){
git for-each-ref--format='%(refname:short)''refs/heads/*' | shuf-n1}function grow_branch(){current_branch="$(show_branch_name)"target_branch="$(pick_branch_name)"if[-z"${target_branch}"\-o"${target_branch}"="master"\-o"${target_branch}"="${current_branch}"];then
target_branch="branch_${i}"
git branch "${target_branch}""$(shuf-e-n1--'HEAD''master')"fi
git checkout "${target_branch}" 2> /dev/null
git log --oneline>"${target_branch}.txt"
git add .
git commit -m"commit #${1}"echo"#${1}: grow ${target_branch}" 1>&2
}function merge_branch(){target_branch="$(pick_branch_name)"if[-z"${target_branch}"];then return 1;fi
if["${target_branch}"="master"];then return 2;fi
git checkout master 2> /dev/null
git merge --no-ff-X"theirs"-m"commit #${1}"--"${target_branch}"
git branch -d"${target_branch}"echo"#${1}: merge ${target_branch}" 1>&2
}#--- main ---#if[-d".git"];then
echo"repository already exists." 2> /dev/null
exit 1
fi
setup_git_repository > /dev/null
for i in$(seq"${1}");do
case"$(shuf-e-n1--'grow''grow''merge')"in"grow") grow_branch "${i}";;"merge") merge_branch "${i}"|| grow_branch "${i}";;esacdone> /dev/null
コマンド等のメモ
- シェルコマンド
set
: シェルの動作を設定・確認する-e
: コマンドがエラーを返したら即座に終了する(if文などエラーを利用するものは除く)-u
: 未設定の変数を使ったらエラーとする
shuf
: 入力の並びをランダムにする-n
: 出力個数を制限する → 1個にすると、ランダムに1個を選択してくれる-e
: コマンドライン引数を入力列とする
- gitコマンド
symbolic-ref
: 今回は現在のブランチ名を取得するのに使っているfor-each-ref
: 今回はブランチ一覧を取得するのに使っている ※git branch
では出力を改めて整形する必要があるmerge
: ブランチをマージする--no-ff
: fast-forwardを使わない → 「マージした」という履歴を必ず残せる-X
: マージ戦略を指定する → 今回はconflictで手動対処が必要になるのを防ぐために使っているtheirs
: conflict時は相手の版を採用する
課題
- ランダムなので、同じ形のリポジトリを再現できない
- 生成手順を保存して流し込めればいい?(ただしコミットのハッシュ値は変わる)
- いい具合に複雑な履歴とならないことがそこそこある
shuf -e -n1 -- 'grow' 'grow' 'merge'
と同じ引数を入れて、確率を調整してみている- 新しいブランチを作る時も、
shuf -e -n1 -- 'HEAD' 'master'
としてmasterから分岐する確率を上げている