#!/usr/bin/env bash COLOR_RED() { test -t 1 && echo -n "$(tput setaf 1)"; } COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; } COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; } COLOR_BLUE() { test -t 1 && echo -n "$(tput setaf 4)"; } COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; } function _test_git_scp() { command -v rsync > /dev/null || _error requires rsync command -v git > /dev/null || _error requires git command -v ssh > /dev/null || _error requires ssh command -v php > /dev/null || _info optional php command -v dos2unix > /dev/null || _info optional dos2unix } function set_remote() { remote=$1 if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ] then COLOR_RED echo "Remote $remote does not exist in your git config" COLOR_RESET exit 1 fi } # Check file for PHP syntax errors # takes a list of filenames function php_lint() { local error_count=() for i do # check if file exists # check if file ends with ".php" test ! -f "$i" && continue case "$i" in *\.php|*\.phtml) if ! php -l "$i" > /dev/null; then error_count[${#error_count[@]}]="$i" fi ;; esac done # syntax check fails, force exit test ${#error_count[@]} -gt 0 && COLOR_RED && echo "Error: ${#error_count[@]} PHP syntax error found" && echo "${error_count[@]}" | tr " " '\n' && COLOR_RESET && exit 255 return 0 } function _dos2unix() { command -v dos2unix > /dev/null && dos2unix "$@" return 0 } function _sanitize() { git config --get-all extras.scp.sanitize | while read -r i do case $i in php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix esac done return $? } function scp_and_stage { local verbose=0 interactive=0 dry_run=0 while : do case "$1" in -v|--verbose) verbose=1; shift;; -i|--interactive) verbose=1; interactive=1; shift;; -n|--dry-run) verbose=1; dry_run=1; shift;; *) break;; esac done set_remote "$1" shift local refhead refhead="$(git rev-parse --quiet --verify "$1")" if [ -n "$refhead" ] then shift [ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] && _error "refhead provided is not part of current branch" fi if [ $# -ge 1 ] then list=$(git ls-files "$@")" "$(git ls-files -o "$@") elif [ -n "$refhead" ] then [ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead" list=$(git diff "$refhead" --name-only) else [ "$verbose" -eq 1 ] && git --no-pager diff list=$(git diff --name-only) fi deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done) list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done) if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; } then local reply read -r -p "Proceed with sync to $remote? [y/N] " reply case "$reply" in y|Y|yes|YES) ;; *) _info "Aborted, nothing synced."; exit 0;; esac fi local status=0 if [ -n "$list" ] then local _TMP=${0///} # shellcheck disable=SC2086 echo "$list" > "$_TMP" if [ "$dry_run" -eq 1 ] || _sanitize $list then _info "Pushing to $remote ($(git config "remote.$remote.url"))" if [ "$dry_run" -eq 1 ] then rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" else rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" && git add --force $list fi status=$? else status=1 fi rm "$_TMP" fi deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done) if [ -n "$deleted" ] then COLOR_RED echo Deleted remote files if [ "$dry_run" -eq 1 ] then echo "$deleted" else if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted" then echo "$deleted" else status=1 fi fi COLOR_RESET fi return "$status" } function reverse_scp() { local verbose=0 interactive=0 dry_run=0 while : do case "$1" in -v|--verbose) verbose=1; shift;; -i|--interactive) verbose=1; interactive=1; shift;; -n|--dry-run) verbose=1; dry_run=1; shift;; *) break;; esac done set_remote "$1" shift local _TMP=${0///} echo "$@" > "$_TMP" local status=0 if [ "$verbose" -eq 1 ] then rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ status=$? fi if [ "$dry_run" -eq 1 ] then rm "$_TMP" return "$status" fi if [ "$interactive" -eq 1 ] then local reply read -r -p "Proceed with copy from $remote? [y/N] " reply case "$reply" in y|Y|yes|YES) ;; *) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;; esac fi rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ && rm "$_TMP" } function _info() { COLOR_YELLOW test $# -gt 0 && echo "$@" COLOR_RESET } function _usage() { echo "Usage: git scp -h|help|? git scp [options] [ref|file..] # scp and stage your files to specified remote git scp [options] [] # show diff relative to and upload unstaged files to git rscp [options] [] # copy files to current working directory OPTIONS: -v, --verbose print what will be synced before syncing -i, --interactive prompt for confirmation before syncing (implies --verbose) -n, --dry-run show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes) " case $1 in -v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;; esac exit } function _error() { [ $# -eq 0 ] && _usage && exit 0 echo echo "ERROR: $*" echo exit 1 } ### OPTIONS ### case $(basename "$0") in git-scp) case $1 in ''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";; *) scp_and_stage "$@";; esac ;; git-rscp) reverse_scp "$@";; esac