Skip to content

Instantly share code, notes, and snippets.

@tkatochin
Last active November 29, 2018 09:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tkatochin/1754a062003b43951664 to your computer and use it in GitHub Desktop.
Save tkatochin/1754a062003b43951664 to your computer and use it in GitHub Desktop.
master、ブランチを一括pull
#!/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
@tkatochin
Copy link
Author

・追跡ブランチが紐ついていないものは、何かのタイミングでリモートと切れたと見なして、同名ブランチがoriginに存在するかチェックし、あればoriginの同名ブランチに紐つけるようにした。

・追跡ブランチが異なる名前のものでも同じ名前の場合と同じようにpullできるようにした。

・危うい --force パラメータを無くした。

・divergedの場合は、is_lastcommit_in_other_branch.sh コマンド(私のgistにある別のコマンド)を使って originの追跡ブランチがローカルブランチの最後のコミットを取り込み済みであればリモートに合わせるようにした。なので--force不要を実現できた。

@tkatochin
Copy link
Author

developやmasterがoriginと紐ついていなかった場合に紐つけるという対応したというのに、それが同じ位置だった場合に、「対応方法が未定義の状態です。」と表示されてしまう不具合を修正。

@tkatochin
Copy link
Author

originの追跡ブランチと一致していない、ローカルのみのブランチを削除する際には、マージされた別のブランチがチェックアウトされていなければ削除に失敗するため、master、developの順にチェックアウトしてマージ済みかを判断した上で実行するようにした。

@tkatochin
Copy link
Author

リモートの追跡ブランチ名が異なるブランチ名だが既にどこかにいってしまっている場合は、同名ブランチ名を探して追跡させるようにした。リモート同名ブランチが無い場合は、masterかdevelopにそのローカルブランチがマージ済みであれば削除するようにした。

@tkatochin
Copy link
Author

gitのバージョンによってgit statusの出力メッセージが異なるので両方対応できるようにした。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment