mirror of
https://github.com/tj/git-extras.git
synced 2026-09-10 15:36:21 -04:00
* feat(scp): add -v/--verbose and -i/--interactive flags git scp/git rscp printed a diff via plain `git diff`/`git diff --stat` with no `--no-pager` and no TTY check, blocking on the user's pager. Add -v/--verbose to print the diff via --no-pager before syncing, and -i/--interactive to also prompt for confirmation before the real push/copy. Both apply to git scp and git rscp, and must precede <remote>. Updates man/git-scp.md and Commands.md. * feat(scp): add -n/--dry-run flag Add -n/--dry-run to preview a sync without changing anything: rsync runs with -n, and git add --force / ssh ... rm are skipped outright. Implies --verbose, since confirming or previewing with nothing shown is useless. Also restructures scp_and_stage's push/delete blocks from `&&`-chains into if/then, needed to branch on dry-run, which incidentally fixes a latent bug where a non-TTY COLOR_RESET could silently skip the real rsync/git add/ssh rm calls. Updates man/git-scp.md and Commands.md. * test(scp): add coverage for -n/--dry-run and -i/--interactive abort git-scp had no test coverage at all. Cover the two paths that need no real remote destination: dry-run must preview without touching the index, working tree, or remote path, and declining the interactive confirmation must abort before anything syncs. * feat(scp): Code review * feat(scp): Code review
268 lines
5.8 KiB
Bash
Executable file
268 lines
5.8 KiB
Bash
Executable file
#!/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] <remote> [ref|file..] # scp and stage your files to specified remote
|
|
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
|
git rscp [options] <remote> [<file|directory>] # copy <remote> 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
|