Skip to content

Instantly share code, notes, and snippets.

@SnuktheGreat
Created April 26, 2017 08:45
Show Gist options
  • Save SnuktheGreat/ac84ff612d75117fc834b04aceb05da9 to your computer and use it in GitHub Desktop.
Save SnuktheGreat/ac84ff612d75117fc834b04aceb05da9 to your computer and use it in GitHub Desktop.
This very specific bash script is used to amend commits in a git tree and then rebase all affected branches and move all affected tags to the new commit. Crazy and dangerous, but better than doing it manually all the time.
#!/usr/bin/env bash
set -e
OLD_BASE=$1 # v1.0.0-step-1
ADDITION=$2
N=$'\n'
ME="$( basename ${BASH_SOURCE[0]} )"
DRY_RUN=false
VERBOSE=true
function echoAndEval() {
if [ "${VERBOSE}" = true ]; then
echo "$@"
fi
if [ "${DRY_RUN}" = false ]; then
eval "$@"
fi
}
function moveChildren() {
for CHILD in "${!WITH_PARENT[@]}"; do
if [ "${WITH_PARENT[$CHILD]}" = "$1" ]; then
OLD_BRANCH=$(echo ${CHILD}| cut -d'/' -f 2)
NEW_BRANCH=${OLD_BRANCH}_on_$2
echo "$N[$ME] Creating ${NEW_BRANCH} from ${OLD_BRANCH} on $3."
echoAndEval git checkout -b ${NEW_BRANCH} ${CHILD}
NEW_BRANCHES[${#NEW_BRANCHES[*]}]=${NEW_BRANCH}
set +e
echoAndEval git rebase --committer-date-is-author-date --preserve-merges --onto $3 $(git_parent_commit ${CHILD})
set -e
if [ "${DRY_RUN}" = false ]; then
while [ "$(git status | grep 'interactive rebase in progress' | wc -l)" -gt "0" ]; do
echoAndEval git rebase --skip
done
fi
DESTRUCTIVE+=("git push --force origin ${NEW_BRANCH}:${OLD_BRANCH}")
CLEANUP+=("git branch -d ${NEW_BRANCH}")
moveChildren ${CHILD} ${NEW_BASE} ${NEW_BRANCH}
fi
done
}
BRANCHES=$(git branch -r --contains ${OLD_BASE})
TAGS=$(git tag --contains ${OLD_BASE})
ROOTS=()
declare -A WITH_PARENT=()
while read -r CURRENT; do
PARENT="$CURRENT"
DIST="1000000"
while read -r CANDIDATE; do
CHILD_DISTANCE="$(git rev-list --count $(git merge-base ${CURRENT} ${CANDIDATE})..${CURRENT})"
PARENT_DISTANCE="$(git rev-list --count $(git merge-base ${CURRENT} ${CANDIDATE})..${CANDIDATE})"
if [ "${CHILD_DISTANCE}" -gt "0" ] && [ "${PARENT_DISTANCE}" -eq "0" ] && [ "${CHILD_DISTANCE}" -lt "${DIST}" ]; then
PARENT=${CANDIDATE}
DIST=${CHILD_DISTANCE}
fi
done <<< "$BRANCHES"
if [ "${PARENT}" = "${CURRENT}" ]; then
ROOTS[${#ROOTS[*]}]=${CURRENT}
else
WITH_PARENT[${CURRENT}]=${PARENT}
fi
done <<< "$BRANCHES"
echo "[$ME] You are about to add ${ADDITION} onto ${OLD_BASE}. This will affect:"
echo "[$ME] Roots: ${ROOTS[*]}"
echo "[$ME] Children: ${!WITH_PARENT[@]}"
echo "[$ME] Tags:"
echo "${TAGS}"
read -p "[$ME] Continue? [Y/n] " -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]; then
DESTRUCTIVE=()
CLEANUP=()
echoAndEval git fetch
echoAndEval git checkout ${OLD_BASE}
echoAndEval git cherry-pick -n ${ADDITION}
echoAndEval git commit --amend -C HEAD
if [ "${DRY_RUN}" = true ]; then
NEW_BASE="ccccccc"
else
NEW_BASE=$(git rev-parse --short HEAD) # 288c354
fi
echo "$N[$ME] Replacing ${OLD_BASE} with ${NEW_BASE}."
NEW_BRANCHES=()
for ROOT in "${ROOTS[*]}"; do
OLD_BRANCH=$(echo ${ROOT}| cut -d'/' -f 2)
NEW_BRANCH=${OLD_BRANCH}_on_${NEW_BASE}
echo "$N[$ME] Creating ${NEW_BRANCH} from ${OLD_BRANCH} on ${NEW_BASE}."
echoAndEval git checkout -b ${NEW_BRANCH} ${ROOT}
NEW_BRANCHES[${#NEW_BRANCHES[*]}]=${NEW_BRANCH}
echoAndEval git rebase --committer-date-is-author-date --preserve-merges -X ours --onto ${NEW_BASE} ${OLD_BASE}
DESTRUCTIVE+=("git push --force origin ${NEW_BRANCH}:${OLD_BRANCH}")
CLEANUP+=("git branch -d ${NEW_BRANCH}")
moveChildren ${ROOT} ${NEW_BASE} ${NEW_BRANCH}
done
while read -r TAG; do
MESSAGE="$(git --no-pager log -1 ${TAG} --pretty=%B)"
NEW_COMMIT="$(eval git --no-pager log --grep=\'${MESSAGE}\' --pretty=%h ${NEW_BRANCHES})"
echoAndEval git tag -d "${TAG}"
echoAndEval git tag "${TAG}" "${NEW_COMMIT}"
DESTRUCTIVE+=("git push origin :refs/tags/${TAG}")
done <<< "$TAGS"
DESTRUCTIVE+=("git push --tags")
echo "${N}WARNING: Changes are done locally. Execute the following to update origin."
for CMD in "${DESTRUCTIVE[@]}"; do
echo "${CMD}"
done
echo "${N}Remove local copy branches:"
for CMD in "${CLEANUP[@]}"; do
echo "${CMD}"
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment