Created
April 26, 2017 08:45
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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