-
-
Save tkatochin/1754a062003b43951664 to your computer and use it in GitHub Desktop.
#!/bin/sh | |
export current_branch=`git rev-parse --abbrev-ref HEAD` | |
if [ 0 == `git status | grep -E "nothing to commit, working (directory|tree) clean" | wc -l` ]; then | |
echo "管理外の未コミットのファイルがあるので中止します。" | |
exit 1; | |
fi | |
abs_dirname() { | |
local cwd="$(pwd)" | |
local path="$1" | |
while [ -n "$path" ]; do | |
cd "${path%/*}" | |
local name="${path##*/}" | |
path="$(readlink "$name" || true)" | |
done | |
pwd -P | |
cd "$cwd" | |
} | |
script_dir="$(abs_dirname "$0")" | |
deleteBranch () { | |
deleteTarget=$1 | |
deleteBranchParent=$2 | |
deletedMessage=$3 | |
# 作業中のブランチでもdevelopでもmasterでもなければローカルブランチを削除 | |
if [ $deleteTarget != $current_branch -a $deleteTarget != master -a $deleteTarget != develop ]; then | |
git checkout $deleteBranchParent > /dev/null 2>&1 | |
if [ $? = 0 ]; then | |
# 切り替えで他のブランチのファイルが未管理として現れる場合があるのでクリーン | |
git clean -d -f | |
git branch --delete $deleteTarget > /dev/null 2>&1 | |
if [ $? = 0 ]; then | |
echo "$deletedMessage" | |
else | |
echo "$deleteTarget のブランチの削除に失敗しました。" | |
fi | |
else | |
echo "$deleteTarget を削除しようと、マージ済みの親ブランチである $deleteBranchTarget に切り替えようとしましたが失敗しました。" | |
fi | |
fi | |
} | |
regexEscape () { | |
ruby_code="print Regexp.escape('$1')" | |
ruby -e "$ruby_code" | |
} | |
branchStatus () { | |
escaped_branch_name=`regexEscape $1` | |
git branch -vv | grep -E "\*?\s$escaped_branch_name\s" | |
} | |
#ブランチの-vvの情報からorigin/ のブランチ名を取り出す | |
pickupOriginBranch () { | |
source=`branchStatus $1` | |
source=${source#*[origin/} | |
source=${source%%]*} | |
source=${source%%: *} | |
echo $source | |
unset source | |
} | |
# 指定したローカルブランチとリモートブランチ名が一致している場合は1を返す | |
isMatchNameOrigin () { | |
#branchStatus $1 | grep -F "[origin/$1" > /dev/null | |
escaped_branch_name=`regexEscape $1` | |
branchStatus $1 | grep -E "\[origin\/$escaped_branch_name(\:|\]){1}" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isSynchronizedOrigin () { | |
branchStatus $1 | grep -F "[origin/$2]" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isLocalOnly () { | |
branchStatus $1 | grep -v -F "[origin/" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isExistsRemote () { | |
escaped_branch_name=`regexEscape $1` | |
git branch -r | grep -E "^\s*origin/$escaped_branch_name$" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isExistsLocal () { | |
escaped_branch_name=`regexEscape $1` | |
git branch | grep -E "^\*?\s*$escaped_branch_name$" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
#現在チェックアウト中のブランチに指定したブランチがマージ済みかどうか判定 | |
isMerged () { | |
escaped_branch_name=`regexEscape $1` | |
git branch --merged | grep -E "^\*?\s*$escaped_branch_name$" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isAhead () { | |
escaped_branch_name=`regexEscape $2` | |
branchStatus $1 | grep -E "\[origin\/$escaped_branch_name\: ahead [0-9]+\]" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isBehind () { | |
escaped_branch_name=`regexEscape $2` | |
branchStatus $1 | grep -E "\[origin\/$escaped_branch_name\: behind [0-9]+\]" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
isDiverged () { | |
escaped_branch_name=`regexEscape $2` | |
branchStatus $1 | grep -E "\[origin\/$escaped_branch_name\: ahead [0-9]+, behind [0-9]+\]" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
#追跡ブランチが消えてしまった。または一度削除して作り直されてどこかにいってしまった。 | |
isGone() { | |
branchStatus $1 | grep -E "\[origin\/.+ gone\]" > /dev/null | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
return 0 | |
} | |
checkout () { | |
git checkout $1 > /dev/null 2>&1 | |
if [ $? != 0 ]; then | |
echo "$1 のcheckoutができなかったため諦めてスキップします." | |
return 1 | |
fi | |
# 切り替えで他のブランチのファイルが未管理として現れる場合があるのでクリーン | |
git clean -d -f | |
if [ $? != 0 ]; then | |
echo "$1 のcleanができなかったため諦めてスキップします." | |
return 1 | |
fi | |
return 0 | |
} | |
deleteIfMerged() { | |
parent=$1 | |
branch=$2 | |
isExistsLocal $parent | |
if [ $? = 0 ]; then | |
return 1 | |
fi | |
checkout $parent | |
if [ $? != 0 ]; then | |
return 1 | |
fi | |
isMerged $branch | |
if [ $? = 1 ]; then | |
if [ "$3" = "" ]; then | |
deleteBranch $branch $parent "$branch は $parent にマージ済みのローカルブランチのため削除しました" | |
else | |
deleteBranch $branch $parent "$3" | |
fi | |
return 0 | |
fi | |
return 1 | |
} | |
git fetch --all > /dev/null 2>&1 | |
git fetch -p origin > /dev/null 2>&1 | |
export branches=`git branch | grep -v HEAD` | |
export branches=${branches//\*/} | |
for branch in $branches; do | |
echo "チェック中... $branch" | |
isMatchNameOrigin $branch | |
if [ $? = 0 ]; then | |
isLocalOnly $branch | |
if [ $? = 1 ]; then | |
#ローカルにしかないが追跡が切れているとみなす。リモートに同名ブランチがあれば紐つける | |
isExistsRemote $branch | |
if [ $? = 0 ]; then | |
#ただのローカルブランチは、masterかdevelopにマージ済みであれば削除する | |
deleteIfMerged master $branch | |
if [ $? != 0 ]; then | |
deleteIfMerged develop $branch | |
fi | |
continue | |
fi | |
echo "$branch は追跡できなくなっているため origin/$branch と紐つけます." | |
checkout $branch | |
if [ $? != 0 ]; then | |
continue | |
fi | |
git branch -u origin/$branch | |
if [ $? != 0 ]; then | |
# 失敗したのでスキップ | |
continue | |
fi | |
fi | |
fi | |
isGone $branch | |
if [ $? = 1 ]; then | |
#リモートに同名ブランチがあれば紐つける | |
isExistsRemote $branch | |
if [ $? = 0 ]; then | |
#紐付け先も不明になった | |
echo "$branch のリモートブランチ origin/$branch は存在しません。無効な追跡先を除きます。" | |
git branch --unset-upstream $branch | |
if [ $? = 0 ]; then | |
#ただのローカルブランチは、masterかdevelopにマージ済みであれば削除する | |
deleteIfMerged master $branch | |
if [ $? != 0 ]; then | |
deleteIfMerged develop $branch | |
fi | |
fi | |
continue | |
fi | |
echo "$branch は追跡できなくなっているため origin/$branch と紐つけます." | |
checkout $branch | |
if [ $? != 0 ]; then | |
continue | |
fi | |
git branch -u origin/$branch | |
if [ $? != 0 ]; then | |
# 失敗したのでスキップ | |
continue | |
fi | |
fi | |
remote_branch=`pickupOriginBranch $branch` | |
if [ "$remote_branch" = "" ]; then | |
echo "$branch は追跡ブランチがorginに見つからないためスキップします." | |
continue | |
fi | |
isSynchronizedOrigin $branch $remote_branch | |
if [ $? = 1 ]; then | |
# 一致しているのでなにもする必要がない | |
deleteIfMerged master $branch "$branch はorigin/$remote_branch と同じ位置です。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
if [ $? != 0 ]; then | |
deleteIfMerged develop $branch "$branch はorigin/$remote_branch と同じ位置です。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
fi | |
continue | |
fi | |
isBehind $branch $remote_branch | |
if [ $? = 1 ]; then | |
# pullできるので実行 | |
checkout $branch | |
if [ $? != 0 ]; then | |
continue | |
fi | |
# pull実行 | |
git pull --rebase origin $remote_branch | |
if [ $? = 0 ]; then | |
# 同じ位置になった。 | |
if [ `git show -s --format=%H` = `git show -s --format=%H origin/$remote_branch` ]; then | |
deleteIfMerged master $branch "$branch はorigin/$remote_branch と同じ位置になりました。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
if [ $? != 0 ]; then | |
deleteIfMerged develop $branch "$branch はorigin/$remote_branch と同じ位置になりました。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
fi | |
else | |
echo "$remote_branch のpullを行いましたが origin の最終コミットIDに合わせられませんでした。確認して対処してください." | |
fi | |
else | |
echo "$branch のpullが失敗しました。確認して対処してください." | |
exit 3 | |
fi | |
continue | |
fi | |
isAhead $branch $remote_branch | |
if [ $? = 1 ]; then | |
# ローカルが進んでいる.勝手にpushしない。 | |
echo "$branch はまだ origin/$remote_branch にマージしていない作業中の状態です。pullしません。" | |
continue | |
fi | |
isDiverged $branch $remote_branch | |
if [ $? = 1 ]; then | |
${script_dir}/is_last_commit_in_other_branch.sh $branch origin/$remote_branch | |
if [ $? = 1 ]; then | |
echo "origin/$remote_branch は $branch の最後のコミットを取り込み済みなので、origin/$remote_branch が同じか先に進んでいるとみなし、originに合わせます。" | |
checkout $branch | |
if [ $? != 0 ]; then | |
echo "$branch に切り替えられません!" | |
continue | |
fi | |
git reset --hard origin/$remote_branch | |
if [ $? = 0 ]; then | |
# 同じ位置になった。 | |
if [ `git show -s --format=%H` = `git show -s --format=%H origin/$remote_branch` ]; then | |
deleteIfMerged master $branch "$branch はorigin/$remote_branch と同じ位置になりました。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
if [ $? != 0 ]; then | |
deleteIfMerged develop $branch "$branch はorigin/$remote_branch と同じ位置になりました。作業中のブランチではないので削除しました。再び編集対象となった時にcheckoutしてください" | |
fi | |
fi | |
fi | |
else | |
# ローカルコミットが進んでいたり、枝が変わってしまっていた場合何もしない。 | |
echo "$branch はローカルとリモートで分岐している上にローカルのコミットが未だorigin/$remote_branch にマージしていないものもあるため、pullしません." | |
git status | |
fi | |
continue | |
fi | |
branchStatus $branch | |
echo "対応方法が未定義の状態です。" | |
done | |
git checkout $current_branch > /dev/null 2>&1 | |
unset current_branch | |
unset force_move_to_remote |
違う枝ではなく、リモートブランチから順送りで自分のコミットが進んでいるのであれば、--forceで変更が破棄されないようにした。
・リモートと同期がとれたローカルブランチのうち、注目してないブランチは削除するようにした。
(実行時のブランチとmasterと、あればdevelopは削除対象外)
・リモートが同じラインで進んでいたら reset --hardじゃなくpullするようにした。
・沢山のリポジトリを連続実行すると画面がウザウザなので出力省力化
メッセージ文字列内で$branchの後にスペースを置かなかったためおかしな変数名として化けちゃっていたのを修正。
・追跡ブランチが紐ついていないものは、何かのタイミングでリモートと切れたと見なして、同名ブランチがoriginに存在するかチェックし、あればoriginの同名ブランチに紐つけるようにした。
・追跡ブランチが異なる名前のものでも同じ名前の場合と同じようにpullできるようにした。
・危うい --force パラメータを無くした。
・divergedの場合は、is_lastcommit_in_other_branch.sh コマンド(私のgistにある別のコマンド)を使って originの追跡ブランチがローカルブランチの最後のコミットを取り込み済みであればリモートに合わせるようにした。なので--force不要を実現できた。
developやmasterがoriginと紐ついていなかった場合に紐つけるという対応したというのに、それが同じ位置だった場合に、「対応方法が未定義の状態です。」と表示されてしまう不具合を修正。
originの追跡ブランチと一致していない、ローカルのみのブランチを削除する際には、マージされた別のブランチがチェックアウトされていなければ削除に失敗するため、master、developの順にチェックアウトしてマージ済みかを判断した上で実行するようにした。
リモートの追跡ブランチ名が異なるブランチ名だが既にどこかにいってしまっている場合は、同名ブランチ名を探して追跡させるようにした。リモート同名ブランチが無い場合は、masterかdevelopにそのローカルブランチがマージ済みであれば削除するようにした。
gitのバージョンによってgit statusの出力メッセージが異なるので両方対応できるようにした。
リモートにないローカルブランチ削除ってやっぱり危険すぎるので、それはそれで別途やりゃあいいじゃんってことで除去。