Skip to content

Instantly share code, notes, and snippets.

@ianmariano
Last active June 26, 2024 17:53
Show Gist options
  • Save ianmariano/5987871 to your computer and use it in GitHub Desktop.
Save ianmariano/5987871 to your computer and use it in GitHub Desktop.
Syncs your repo fork with the upstream master and then pushes the synced master to origin. Presumes you have an 'upstream' remote which is from whence your fork was created. Put on your path and chmod a+x it then do: git fork-sync. Use -h for usage help.
#!/usr/bin/env bash
set -Eeuo pipefail
VERSION="20200421.1"
_usage() {
cat << __EOF
$0 usage:
$0 [options]
Where [options] include:
-h|--help Show this help
-v|--version Show the version and exit
-r|--remote REMOTE_NAME Fetch only this remote
-m|--merge Merge instead of rebase. Interaction
required. (Only for -t or --topic)
-X|--merge-strategy Merge strategy used for rebasing or
merging (only for -t or --topic)
Defaults to "ours" for rebasing,
"theirs" for merges
-t|--topic TOPIC_BRANCH And rebase MAIN_BRANCH onto this
topic branch after syncing unless
-m or --merge is specified
-g|--origin-reset Reset topic branches specified using
-t or --topic to the origin version when
rebasing or merging
-u|--upstream REMOTE_NAME And push synced MAIN_BRANCH here too
-c|--checkout TOPIC_BRANCH When done, checkout this branch
-s|--mine-only Only sync with your origin
-a|--fetch-all With -s, also fetch all remotes
-n|--no-deploy-branches Do not sync up deployment branches if found.
If qa|stage|staging|prod|production are found
they will be reset to their upstream versions
and pushed to your origin (if applicable)
-b|--main-branch BRANCH Use BRANCH instead of master for the repos
MAIN_BRANCH
MAIN_BRANCH is defaulted to master if -b or --main-branch is not specified.
If remote is not specified, all remotes are fetched
and your local MAIN_BRANCH is rebased with upstream/MAIN_BRANCH
then pushed to your origin.
If remote is specified, only that remote is fetched
and your local MAIN_BRANCH is rebased to remote/MAIN_BRANCH
then pushed to your origin.
-t|--topic may be used multiple times
-u|--upstream may be used multiple times
-s|--mine-only will ignore other options except -a
If you only have an origin, git fork-sync will only sync with
your origin as if you specified -s or --mine-only.
If topics are specified (-t or --topic) and you specify -m or --merge, a
non-fast-forward merge will be performed which will require interaction.
In any case, this will attempt to reset the local topic branch to the
version from origin unless -g or --no-origin-reset is specified.
git fork-sync will fail if you do not have at least origin defined
as a remote.
__EOF
exit 1
}
_die() {
echo "$*" >&2
exit 1
}
bold=$(tput bold)
normal=$(tput sgr0)
GIT_VER=`git --version | cut -f 3 -d ' '`
REBASE_ARG="-p"
PULL_REBASE_ARG="--rebase=preserve"
REMOTE=""
MINE_ONLY=""
FETCH_ALL=""
NO_REBASE=""
MERGE_STRATEGY=""
TOPICS=()
DO_ORIGIN_RESET=""
UPSTREAMS=()
CHECKOUT=""
NO_DEPLOY_BRANCHES=""
MAIN_BRANCH="master"
while [ $# -ge 1 ]
do
key="$1"
shift
case $key in
-h|--help)
_usage
exit 1
;;
-v|--version)
echo "git-fork-sync version $VERSION"
echo""
exit 0
;;
-r|--remote)
REMOTE="$1"
shift
;;
-m|--merge)
NO_REBASE="yes"
;;
-X|--merge-strategy)
MERGE_STRATEGY="$1"
shift
;;
-t|--topic)
TOPICS+=("$1")
shift
;;
-g|--origin-reset)
DO_ORIGIN_RESET="yes"
;;
-u|--upstream)
UPSTREAMS+=("$1")
shift
;;
-c|--checkout)
CHECKOUT="$1"
shift
;;
-s|--mine-only)
MINE_ONLY="yes"
;;
-a|--fetch-all)
FETCH_ALL="yes"
;;
-n|--no-deploy-branches)
NO_DEPLOY_BRANCHES="yes"
;;
-b|--main-branch)
MAIN_BRANCH="$1"
shift
;;
*)
_usage
exit 1
;;
esac
done
if [ -z "$MERGE_STRATEGY" ]
then
if [ -z "$NO_REBASE" ]
then
MERGE_STRATEGY="ours"
else
MERGE_STRATEGY="theirs"
fi
fi
if [ "$GIT_VER" \> "2.26.0" ]
then
REBASE_ARG="-r"
PULL_REBASE_ARG="--rebase=merges"
else
echo "You are using an older version of git, will use the older preserve"
echo "merges flag."
echo ""
fi
if output=$(git status --untracked-files=no --porcelain) && [ -z "$output" ]
then
echo "${bold}Working directory is clean.${normal}"
else
_die "You have uncommitted changes."
fi
echo "${bold}Checking connectivity:${normal}"
all_remotes=$(git remote)
cr=($all_remotes)
have_origin=""
have_upstream=""
for r in $all_remotes
do
echo -n " $r... "
if [ "origin" == "$r" ]
then
have_origin="yes"
elif [ "upstream" == "$r" ]
then
have_upstream="yes"
fi
if git ls-remote --exit-code $r &>/dev/null
then
echo "ok"
else
_die "FAILED!"
fi
done
if [ -z "$have_origin" ]
then
_die "You do not have an origin defined."
fi
if [ 1 == ${#cr[@]} ]
then
echo "${bold}You only have an origin, falling back to single mode.${normal}"
MINE_ONLY="yes"
fi
if [ -z "$have_upstream" ]
then
echo "${bold}You do not have an upstream, falling back to single mode and fetching all.${normal}"
MINE_ONLY="yes"
FETCH_ALL="yes"
fi
echo "${bold}Using $MAIN_BRANCH as the main branch.${normal}"
git checkout $MAIN_BRANCH
if ! [ -z "$MINE_ONLY" ]
then
if [ -z "$FETCH_ALL" ]
then
echo "${bold}Only syncing with your origin.${normal}"
git pull $PULL_REBASE_ARG --tags
echo -n "Now pruning remote origin... "
git remote prune origin && echo "done."
else
echo "${bold}Getting all remote history and syncing only with your origin.${normal}"
git pull $PULL_REBASE_ARG --all --tags
echo "${bold}Pruning all remotes:${normal}"
for r in $all_remotes
do
echo -n " $r... "
git remote prune $r && echo "done"
done
fi
exit 0
elif [ -z "$REMOTE" ]
then
echo "${bold}Getting all remote history.${normal}"
git pull $PULL_REBASE_ARG --all --tags
echo "${bold}Syncing with upstream.${normal}"
git rebase $REBASE_ARG upstream/$MAIN_BRANCH
git push origin $MAIN_BRANCH
echo "${bold}Pruning all remotes:${normal}"
for r in $all_remotes
do
echo -n " $r... "
git remote prune $r && echo "done"
done
else
echo "${bold}Only syncing $REMOTE with $MAIN_BRANCH.${normal}"
git pull $PULL_REBASE_ARG --tags
git fetch $REMOTE
git rebase $REMOTE/$MAIN_BRANCH
git push origin $MAIN_BRANCH
git remote prune $REMOTE
fi
if [ ${#TOPICS[@]} -gt 0 ]
then
if ! [ -z "$NO_REBASE" ]
then
echo "${bold}Merging $MAIN_BRANCH with topic branches:${normal}"
else
echo "${bold}Rebasing topic branches with history from $MAIN_BRANCH:${normal}"
fi
for t in ${TOPICS[@]}
do
if ! [ -z "$NO_REBASE" ]
then
echo " ${bold}with $t${normal}"
git checkout $t
if ! [ -z "$DO_ORIGIN_RESET" ]
then
git reset --hard origin/$t
fi
git merge --no-ff -X$MERGE_STRATEGY $MAIN_BRANCH
else
echo " ${bold}onto $t${normal}"
git checkout $t
if ! [ -z "$DO_ORIGIN_RESET" ]
then
git reset --hard origin/$t
fi
git rebase $REBASE_ARG -X$MERGE_STRATEGY $MAIN_BRANCH
fi
done
git checkout $MAIN_BRANCH
fi
if [ ${#UPSTREAMS[@]} -gt 0 ]
then
echo "${bold}Pushing synced $MAIN_BRANCH:${normal}"
for u in ${UPSTREAMS[@]}
do
echo " ${bold}to $u${normal}"
git push $u $MAIN_BRANCH
done
fi
if [ -z "$NO_DEPLOY_BRANCHES" ]
then
echo "${bold}Checking for deployment branches to sync.${normal}"
all_branches=`git branch | sed -E "s/\*? +//g"`
for b in $all_branches
do
if [ "qa" == "$b" ] || [ "stage" == "$b" ] || [ "staging" == "$b" ] || [ "prod" == "$b" ] || [ "production" == "$b" ]
then
if ! [ -z "$MINE_ONLY" ]
then
echo "${bold}Syncing $b with your origin.${normal}"
git checkout $b
git reset --hard origin/$b
elif [ -z "$REMOTE" ]
then
echo "${bold}Syncing $b with your upstream.${normal}"
git checkout $b
git reset --hard upstream/$b
git push origin $b
else
echo "${bold}Only syncing $b with $REMOTE.${normal}"
fi
fi
done
git checkout $MAIN_BRANCH
fi
if ! [ -z "$CHECKOUT" ]
then
git checkout $CHECKOUT
fi
echo "${bold}Done.${normal}"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment