parent
67718b243a
commit
fe6a8d4eb5
@ -0,0 +1,384 @@ |
||||
#!/usr/bin/env bash |
||||
# |
||||
# git-sync |
||||
# |
||||
# synchronize tracking repositories |
||||
# |
||||
# 2012-20 by Simon Thum and contributors |
||||
# Licensed as: CC0 |
||||
# |
||||
# This script intends to sync via git near-automatically |
||||
# in "tracking" repositories where a nice history is not |
||||
# crucial, but having one at all is. |
||||
# |
||||
# Unlike the myriad of scripts to do just that already available, |
||||
# it follows the KISS principle: It is small, requires nothing but |
||||
# git and bash, but does not even try to shield you from git. |
||||
# |
||||
# Mode sync (default) |
||||
# |
||||
# Sync will likely get from you from a dull normal git repo with trivial |
||||
# changes to an updated dull normal git repo equal to origin. No more, |
||||
# no less. The intent is to do everything that's needed to sync |
||||
# automatically, and resort to manual intervention as soon |
||||
# as something non-trivial occurs. It is designed to be safe |
||||
# in that it will likely refuse to do anything not known to |
||||
# be safe. |
||||
# |
||||
# Mode check |
||||
# |
||||
# Check only performs the basic checks to make sure the repository |
||||
# is in an orderly state to continue syncing, i.e. committing |
||||
# changes, pull etc. without losing any data. When check returns |
||||
# 0, sync can start immediately. This does not, however, indicate |
||||
# that syncing is at all likely to succeed. |
||||
|
||||
# command used to auto-commit file modifications |
||||
DEFAULT_AUTOCOMMIT_CMD="git add -u ; git commit -m \"%message\";" |
||||
|
||||
# command used to auto-commit all changes |
||||
ALL_AUTOCOMMIT_CMD="git add -A ; git commit -m \"%message\";" |
||||
|
||||
# default commit message substituted into autocommit commands |
||||
DEFAULT_AUTOCOMMIT_MSG="changes from $(uname -n) on $(date)" |
||||
|
||||
|
||||
# AUTOCOMMIT_CMD="echo \"Please commit or stash pending changes\"; exit 1;" |
||||
# TODO mode for stash push & pop |
||||
|
||||
print_usage() { |
||||
cat << EOF |
||||
usage: $0 [-h] [-n] [-s] [MODE] |
||||
|
||||
Synchronize the current branch to a remote backup |
||||
MODE may be either "sync" (the default) or "check", to verify that the branch is ready to sync |
||||
|
||||
OPTIONS: |
||||
-h Show this message |
||||
-n Commit new files even if branch.\$branch_name.syncNewFiles isn't set |
||||
-s Sync the branch even if branch.\$branch_name.sync isn't set |
||||
EOF |
||||
} |
||||
sync_new_files_anyway="false" |
||||
sync_anyway="false" |
||||
|
||||
while getopts "hns" opt ; do |
||||
case $opt in |
||||
h ) |
||||
print_usage |
||||
exit 0 |
||||
;; |
||||
n ) |
||||
sync_new_files_anyway="true" |
||||
;; |
||||
s ) |
||||
sync_anyway="true" |
||||
;; |
||||
esac |
||||
done |
||||
shift $((OPTIND-1)) |
||||
|
||||
# |
||||
# utility functions, some adapted from git bash completion |
||||
# |
||||
|
||||
__log_msg() |
||||
{ |
||||
echo git-sync: $1 |
||||
} |
||||
|
||||
# echo the git dir |
||||
__gitdir() |
||||
{ |
||||
if [ "true" = "$(git rev-parse --is-inside-work-tree "$PWD" | head -1)" ]; then |
||||
git rev-parse --git-dir "$PWD" 2>/dev/null |
||||
fi |
||||
} |
||||
|
||||
# echos repo state |
||||
git_repo_state () |
||||
{ |
||||
local g="$(__gitdir)" |
||||
if [ -n "$g" ]; then |
||||
if [ -f "$g/rebase-merge/interactive" ]; then |
||||
echo "REBASE-i" |
||||
elif [ -d "$g/rebase-merge" ]; then |
||||
echo "REBASE-m" |
||||
else |
||||
if [ -d "$g/rebase-apply" ]; then |
||||
echo "AM/REBASE" |
||||
elif [ -f "$g/MERGE_HEAD" ]; then |
||||
echo "MERGING" |
||||
elif [ -f "$g/CHERRY_PICK_HEAD" ]; then |
||||
echo "CHERRY-PICKING" |
||||
elif [ -f "$g/BISECT_LOG" ]; then |
||||
echo "BISECTING" |
||||
fi |
||||
fi |
||||
if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then |
||||
if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then |
||||
echo "|BARE" |
||||
else |
||||
echo "|GIT_DIR" |
||||
fi |
||||
elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then |
||||
git diff --no-ext-diff --quiet --exit-code || echo "|DIRTY" |
||||
# if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then |
||||
# git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" |
||||
# fi |
||||
# |
||||
# if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then |
||||
# if [ -n "$(git ls-files --others --exclude-standard)" ]; then |
||||
# u="%" |
||||
# fi |
||||
# fi |
||||
# |
||||
# if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then |
||||
# __git_ps1_show_upstream |
||||
# fi |
||||
fi |
||||
else |
||||
echo "NOGIT" |
||||
fi |
||||
} |
||||
|
||||
# check if we only have untouched, modified or (if configured) new files |
||||
check_initial_file_state() |
||||
{ |
||||
local syncNew="$(git config --get --bool branch.$branch_name.syncNewFiles)" |
||||
if [[ "true" == "$syncNew" || "true" == "$sync_new_files_anyway" ]]; then |
||||
# allow for new files |
||||
if [ ! -z "$(git status --porcelain | grep -E '^[^ \?][^M\?] *')" ]; then |
||||
echo "NonNewOrModified" |
||||
fi |
||||
else |
||||
# also bail on new files |
||||
if [ ! -z "$(git status --porcelain | grep -E '^[^ ][^M] *')" ]; then |
||||
echo "NotOnlyModified" |
||||
fi |
||||
fi |
||||
} |
||||
|
||||
# look for local changes |
||||
# used to decide if autocommit should be invoked |
||||
local_changes() |
||||
{ |
||||
if [ ! -z "$(git status --porcelain | grep -E '^(\?\?|[MARC] |[ MARC][MD])*')" ]; then |
||||
echo "LocalChanges" |
||||
fi |
||||
} |
||||
|
||||
# determine sync state of repository, i.e. how the remote relates to our HEAD |
||||
sync_state() |
||||
{ |
||||
local count="$(git rev-list --count --left-right $remote_name/$branch_name...HEAD)" |
||||
|
||||
case "$count" in |
||||
"") # no upstream |
||||
echo "noUpstream" |
||||
false |
||||
;; |
||||
"0 0") |
||||
echo "equal" |
||||
true |
||||
;; |
||||
"0 "*) |
||||
echo "ahead" |
||||
true |
||||
;; |
||||
*" 0") |
||||
echo "behind" |
||||
true |
||||
;; |
||||
*) |
||||
echo "diverged" |
||||
true |
||||
;; |
||||
esac |
||||
} |
||||
|
||||
# exit, issue warning if not in sync |
||||
exit_assuming_sync() { |
||||
if [ "equal" == "$(sync_state)" ] ; then |
||||
__log_msg "In sync, all fine." |
||||
exit 0; |
||||
else |
||||
__log_msg "Synchronization FAILED! You should definitely check your repository carefully!" |
||||
__log_msg "(Possibly a transient network problem? Please try again in that case.)" |
||||
exit 3 |
||||
fi |
||||
} |
||||
|
||||
# |
||||
# Here git-sync actually starts |
||||
# |
||||
|
||||
# first some sanity checks |
||||
rstate="$(git_repo_state)" |
||||
if [[ -z "$rstate" || "|DIRTY" = "$rstate" ]]; then |
||||
__log_msg "Preparing. Repo in $(__gitdir)" |
||||
elif [[ "NOGIT" = "$rstate" ]] ; then |
||||
__log_msg "No git repository detected. Exiting." |
||||
exit 128 # matches git's error code |
||||
else |
||||
__log_msg "Git repo state considered unsafe for sync: $(git_repo_state)" |
||||
exit 2 |
||||
fi |
||||
|
||||
# determine the current branch (thanks to stackoverflow) |
||||
branch_name=$(git symbolic-ref -q HEAD) |
||||
branch_name=${branch_name##refs/heads/} |
||||
|
||||
if [ -z "$branch_name" ] ; then |
||||
__log_msg "Syncing is only possible on a branch." |
||||
git status |
||||
exit 2 |
||||
fi |
||||
|
||||
# while at it, determine the remote to operate on |
||||
remote_name=$(git config --get branch.$branch_name.pushRemote) |
||||
if [ -z "$remote_name" ] ; then |
||||
remote_name=$(git config --get remote.pushDefault) |
||||
fi |
||||
if [ -z "$remote_name" ] ; then |
||||
remote_name=$(git config --get branch.$branch_name.remote) |
||||
fi |
||||
|
||||
if [ -z "$remote_name" ] ; then |
||||
__log_msg "the current branch does not have a configured remote." |
||||
echo |
||||
__log_msg "Please use" |
||||
echo |
||||
__log_msg " git branch --set-upstream-to=[remote_name]/$branch_name" |
||||
echo |
||||
__log_msg "replacing [remote_name] with the name of your remote, i.e. - origin" |
||||
__log_msg "to set the remote tracking branch for git-sync to work" |
||||
exit 2 |
||||
fi |
||||
|
||||
# check if current branch is configured for sync |
||||
if [[ "true" != "$(git config --get --bool branch.$branch_name.sync)" && "true" != "$sync_anyway" ]] ; then |
||||
echo |
||||
__log_msg "Please use" |
||||
echo |
||||
__log_msg " git config --bool branch.$branch_name.sync true" |
||||
echo |
||||
__log_msg "to enlist branch $branch_name for synchronization." |
||||
__log_msg "Branch $branch_name has to have a same-named remote branch" |
||||
__log_msg "for git-sync to work." |
||||
echo |
||||
__log_msg "(If you don't know what this means, you should change that" |
||||
__log_msg "before relying on this script. You have been warned.)" |
||||
echo |
||||
exit 1 |
||||
fi |
||||
|
||||
# determine mode |
||||
if [[ -z "$1" || "$1" == "sync" ]]; then |
||||
mode="sync" |
||||
elif [[ "check" == "$1" ]]; then |
||||
mode="check" |
||||
else |
||||
__log_msg "Mode $1 not recognized" |
||||
exit 100 |
||||
fi |
||||
|
||||
__log_msg "Mode $mode" |
||||
|
||||
__log_msg "Using $remote_name/$branch_name" |
||||
|
||||
# check for intentionally unhandled file states |
||||
if [ ! -z "$(check_initial_file_state)" ] ; then |
||||
__log_msg "There are changed files you should probably handle manually." |
||||
git status |
||||
exit 1 |
||||
fi |
||||
|
||||
# if in check mode, this is all we need to know |
||||
if [ $mode == "check" ] ; then |
||||
__log_msg "check OK; sync may start." |
||||
exit 0 |
||||
fi |
||||
|
||||
# check if we have to commit local changes, if yes, do so |
||||
if [ ! -z "$(local_changes)" ]; then |
||||
autocommit_cmd="" |
||||
config_autocommit_cmd="$(git config --get branch.$branch_name.autocommitscript)" |
||||
|
||||
# discern the three ways to auto-commit |
||||
if [ ! -z "$config_autocommit_cmd" ]; then |
||||
autocommit_cmd="$config_autocommit_cmd" |
||||
elif [[ "true" == "$(git config --get --bool branch.$branch_name.syncNewFiles)" || "true" == "$sync_new_files_anyway" ]]; then |
||||
autocommit_cmd=${ALL_AUTOCOMMIT_CMD} |
||||
else |
||||
autocommit_cmd=${DEFAULT_AUTOCOMMIT_CMD} |
||||
fi |
||||
|
||||
commit_msg="$(git config --get branch.$branch_name.syncCommitMsg)" |
||||
if [ "" == "$commit_msg" ]; then |
||||
commit_msg=${DEFAULT_AUTOCOMMIT_MSG} |
||||
fi |
||||
autocommit_cmd=$(echo "$autocommit_cmd" | sed "s/%message/$commit_msg/") |
||||
|
||||
__log_msg "Committing local changes using ${autocommit_cmd}" |
||||
eval $autocommit_cmd |
||||
|
||||
# after autocommit, we should be clean |
||||
rstate="$(git_repo_state)" |
||||
if [[ ! -z "$rstate" ]]; then |
||||
__log_msg "Auto-commit left uncommitted changes. Please add or remove them as desired and retry." |
||||
exit 1 |
||||
fi |
||||
fi |
||||
|
||||
# fetch remote to get to the current sync state |
||||
# TODO make fetching/pushing optional |
||||
__log_msg "Fetching from $remote_name/$branch_name" |
||||
git fetch $remote_name $branch_name |
||||
if [ $? != 0 ] ; then |
||||
__log_msg "git fetch $remote_name returned non-zero. Likely a network problem; exiting." |
||||
exit 3 |
||||
fi |
||||
|
||||
case "$(sync_state)" in |
||||
"noUpstream") |
||||
__log_msg "Strange state, you're on your own. Good luck." |
||||
exit 2 |
||||
;; |
||||
"equal") |
||||
exit_assuming_sync |
||||
;; |
||||
"ahead") |
||||
__log_msg "Pushing changes..." |
||||
git push $remote_name $branch_name:$branch_name |
||||
if [ $? == 0 ]; then |
||||
exit_assuming_sync |
||||
else |
||||
__log_msg "git push returned non-zero. Likely a connection failure." |
||||
exit 3 |
||||
fi |
||||
;; |
||||
"behind") |
||||
__log_msg "We are behind, fast-forwarding..." |
||||
git merge --ff --ff-only $remote_name/$branch_name |
||||
if [ $? == 0 ]; then |
||||
exit_assuming_sync |
||||
else |
||||
__log_msg "git merge --ff --ff-only returned non-zero ($?). Exiting." |
||||
exit 2 |
||||
fi |
||||
;; |
||||
"diverged") |
||||
__log_msg "We have diverged. Trying to rebase..." |
||||
git rebase $remote_name/$branch_name |
||||
if [[ $? == 0 && -z "$(git_repo_state)" && "ahead" == "$(sync_state)" ]] ; then |
||||
__log_msg "Rebasing went fine, pushing..." |
||||
git push $remote_name $branch_name:$branch_name |
||||
exit_assuming_sync |
||||
else |
||||
__log_msg "Rebasing failed, likely there are conflicting changes. Resolve them and finish the rebase before repeating git-sync." |
||||
exit 1 |
||||
fi |
||||
# TODO: save master, if rebasing fails, make a branch of old master |
||||
;; |
||||
esac |
@ -0,0 +1,12 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -eu |
||||
|
||||
file_to_host=$1 |
||||
file_name=$(basename "$file_to_host") |
||||
scp "$file_to_host" "serveemcgee.local:/www/$file_name" |
||||
|
||||
hosted_url="https://chez.kindrobot.ca/$file_name" |
||||
|
||||
echo "$hosted_url" | xsel -b |
||||
echo $hosted_url |
Binary file not shown.
Loading…
Reference in new issue