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%n' # 結果は以下
rebaseなどの練習ができそうな、いい感じに複雑な履歴になっている。
リポジトリを消すときは次のコマンドを使う。(他のリポジトリのディレクトリで実行しないこと)
rm -rf * .gitignore .git/
スクリプト
動作は以下の通り。
- リポジトリを新規作成する
- 指定回数だけ以下のいずれかを実行する
- ランダムに選んだブランチを伸ばす
- masterや現在と同じブランチを選んだときは、ブランチを新規作成して伸ばす
- ファイルに書き込むデータは何でもいいので、とりあえず
git log --oneline
の結果としている
- ランダムに選んだブランチをmasterにマージして削除する
- master自身を選んでしまったときは、上記のブランチを伸ばす操作に切り替える
- ランダムに選んだブランチを伸ばす
Gitのバージョンは 2.7.4
。別のバージョンではオプションが使えなかったり、もっと良いオプションが使える可能性がある。
create_git_sample.sh
#!/bin/bash
set -eu
function 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}" ;;
esac
done > /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から分岐する確率を上げている
-