Last active
June 26, 2024 17:53
-
-
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.
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 -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