From 710fb7181ed9db865fbf4f27b05031e542522d91 Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Sun, 18 Dec 2011 00:22:01 +0800 Subject: [PATCH] Implemented a robust git_index_update_all_branches function, to automatically perform a safe fast-forward of all local branches, if their remtoes and merge refs are configured. Can be run from a cron script to keep all repos up to date --- lib/git/repo_index.sh | 85 ++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/lib/git/repo_index.sh b/lib/git/repo_index.sh index 7f663ad..7ffe126 100644 --- a/lib/git/repo_index.sh +++ b/lib/git/repo_index.sh @@ -106,7 +106,7 @@ function git_index() { fi cd "$base_path" # Run git callback (either update or show changes), if we are in the root directory - if [ -z "${sub_path%/}" ]; then _git_index_update_or_status; fi + if [ -z "${sub_path%/}" ]; then _git_index_status_if_dirty; fi else echo -e "$_wrn_col'$1' did not match any git repos in $GIT_REPO_DIR$_txt_col" fi @@ -171,7 +171,7 @@ parse_git_branch() { } # If the working directory is clean, update the git repository. Otherwise, show changes. -function _git_index_update_or_status() { +function _git_index_status_if_dirty() { if ! [ `git status --porcelain | wc -l` -eq 0 ]; then # Fall back to 'git status' if git status alias isn't configured if type $git_status_command 2>&1 | grep -qv "not found"; then @@ -179,36 +179,77 @@ function _git_index_update_or_status() { else git status fi - else - # Check that a local 'origin' remote exists. - if (git remote -v | grep -q origin); then - # Only update the git repo if it hasn't been touched for at least 6 hours. - if $(find ".git" -maxdepth 0 -type d -mmin +360 | grep -q "\.git"); then - _git_index_update_branch_or_master - fi - fi fi } -_git_index_update_branch_or_master() { - branch=$(parse_git_branch) - # If we aren't on any branch, checkout master. - if [ "$branch" = "(no branch)" ]; then - echo -e "=== Checking out$_git_col master$_txt_col branch." - git checkout master - branch="master" + +_git_index_update_all_branches() { + echo -e "\n## $base_path\n" + + # Save current branch or HEAD revision + local orig_branch=$(parse_git_branch) + if [[ "$orig_branch" = "(no branch)" ]]; then + orig_branch=$(git rev-parse HEAD) fi - echo -e "=== Updating '$branch' branch in $_bld_col$base_path$_txt_col from$_git_col origin$_txt_col... (Press Ctrl+C to cancel)" - # Pull the latest code from the server - git pull origin $branch + + # If working directory is dirty, abort + if [[ -n "$(git status --short 2> /dev/null)" ]]; then + echo "=== Working directory dirty, nothing to do." + return + fi + + local remotes merges branches + # Get branch configuration from .git/config + IFS=$'\n' + for branch in $(git branch 2> /dev/null | sed -e 's/.\{2\}\(.*\)/\1/'); do + # Skip '(no branch)' + if [[ "$branch" = "(no branch)" ]]; then continue; fi + + local remote=$(git config --get branch.$branch.remote) + local merge=$(git config --get branch.$branch.merge) + + # Ignore branch if remote and merge is not configured + if [[ -n "$remote" ]] && [[ -n "$merge" ]]; then + branches=("${branches[@]}" "$branch") + remotes=("${remotes[@]}" "$remote") + # Get branch from merge ref (refs/heads/master => master) + merges=("${merges[@]}" "$(basename $merge)") + else + echo "=== Skipping $branch: remote and merge refs are not configured." + fi + done + unset IFS + + # Update all remotes if there are any branches to update + if [ -n "${branches[*]}" ]; then git fetch --all 2> /dev/null; fi + + local index=0 + # Iterate over branches, and update those that can be fast-forwarded + for branch in $branches; do + branch_rev="$(git rev-parse $branch)" + # Local branch can be fast-forwarded if revision is ancestor of remote revision, and not the same. + # (see http://stackoverflow.com/a/2934062/304706) + if [[ "$branch_rev" != "$(git rev-parse ${remotes[$index]}/${merges[$index]})" ]] && \ + [[ "$(git merge-base $branch_rev ${remotes[$index]}/${merges[$index]})" = "$branch_rev" ]]; then + echo "=== Updating '$branch' branch in $base_path from ${remotes[$index]}/${merges[$index]}..." + # Checkout branch if we aren't already on it. + if [[ "$branch" != "$(parse_git_branch)" ]]; then git checkout $branch; fi + git merge "${remotes[$index]}/${merges[$index]}" + fi + let index++ + done + + # Checkout original branch/revision if we aren't already on it. + if [[ "$orig_branch" != "$(parse_git_branch)" ]]; then git checkout "$orig_branch"; fi } # Updates all git repositories with clean working directories. function _git_index_update_all() { - echo -e "== Updating code in $_bld_col$(_git_index_count)$_txt_col repos...\n" - _git_index_batch_cmd _git_index_update_branch_or_master + echo -e "== Safely updating all local branches in $_bld_col$(_git_index_count)$_txt_col repos...\n" + _git_index_batch_cmd _git_index_update_all_branches } + # Runs a command for all git repos function _git_index_batch_cmd() { cwd="$PWD"