mirror of
https://github.com/tj/git-extras.git
synced 2026-09-10 07:26:17 -04:00
feat(scp): add verbose, interactive, and dry-run flags (#1261)
* 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
This commit is contained in:
parent
b450b36036
commit
5a14757944
|
|
@ -1203,6 +1203,14 @@ Internally this script uses `rsync` and not `scp` as the name suggests.
|
|||
|
||||
`git-rscp` - The reverse of `git-scp`. Copies specific files from the working directory of a remote repository to the current working directory.
|
||||
|
||||
The following options are available (must precede `<remote>`):
|
||||
|
||||
```bash
|
||||
-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)
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Copy unstaged files to remote. Useful when you want to make quick test without making any commits
|
||||
|
|
|
|||
122
bin/git-scp
122
bin/git-scp
|
|
@ -77,6 +77,17 @@ function _sanitize()
|
|||
|
||||
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
|
||||
|
||||
|
|
@ -94,46 +105,116 @@ function scp_and_stage
|
|||
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
|
||||
elif [ -n "$refhead" ]
|
||||
then
|
||||
git diff --stat "$refhead"
|
||||
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
|
||||
list=$(git diff "$refhead" --name-only)
|
||||
else
|
||||
git diff
|
||||
[ "$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" &&
|
||||
_sanitize $list &&
|
||||
_info "Pushing to $remote ($(git config "remote.$remote.url"))" &&
|
||||
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
|
||||
git add --force $list &&
|
||||
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)
|
||||
|
||||
[ -n "$deleted" ] &&
|
||||
COLOR_RED &&
|
||||
echo Deleted remote files &&
|
||||
ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted" &&
|
||||
echo "$deleted"
|
||||
COLOR_RESET
|
||||
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" &&
|
||||
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
|
||||
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"
|
||||
}
|
||||
|
||||
|
|
@ -148,9 +229,14 @@ function _usage()
|
|||
{
|
||||
echo "Usage:
|
||||
git scp -h|help|?
|
||||
git scp <remote> [ref|file..] # scp and stage your files to specified remote
|
||||
git scp <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
||||
git rscp <remote> [<file|directory>] # copy <remote> files to current working directory
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ git-scp(1) -- Copy files to SSH compatible `git-remote`
|
|||
## SYNOPSIS
|
||||
|
||||
`git scp` -h|help|?
|
||||
`git scp` <remote> [<commits>...|<path>...]
|
||||
`git rscp` <remote> <path>
|
||||
`git scp` [options] <remote> [<commits>...|<path>...]
|
||||
`git rscp` [options] <remote> <path>
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
|
|
@ -29,6 +29,18 @@ Internally this script uses `rsync` and not `scp` as the name suggests.
|
|||
|
||||
The <paths> parameters, when given, are used to limit the diff to the named paths (you can give directory names and get diff for all files under them).
|
||||
|
||||
-v, --verbose
|
||||
|
||||
Print what will be synced before syncing: the diff for `git scp`, or a preview (rsync dry-run) for `git rscp`.
|
||||
|
||||
-i, --interactive
|
||||
|
||||
Same as `--verbose`, but also prompt for confirmation before syncing. Implies `--verbose`
|
||||
|
||||
-n, --dry-run
|
||||
|
||||
Show what would be synced, change nothing. Implies `--verbose`, and no staging or remote writes/deletes happen.
|
||||
|
||||
## GIT CONFIGS
|
||||
|
||||
To sanitize files using `dos2unix` before copying files
|
||||
|
|
|
|||
46
tests/git-scp.bats
Normal file
46
tests/git-scp.bats
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# shellcheck shell=bash
|
||||
|
||||
source "$BATS_TEST_DIRNAME/test_util.sh"
|
||||
|
||||
setup_file() {
|
||||
test_util.setup_file
|
||||
}
|
||||
|
||||
setup() {
|
||||
test_util.cd_test
|
||||
|
||||
test_util.git_init
|
||||
printf '%s\n' 'hello' > tracked.txt
|
||||
git add tracked.txt
|
||||
git commit -m 'Initial commit'
|
||||
|
||||
# never created: neither test below should ever touch it
|
||||
git remote add fake "$BATS_TEST_TMPDIR/never-created"
|
||||
|
||||
printf '%s\n' 'world' >> tracked.txt
|
||||
}
|
||||
|
||||
@test "dry-run previews the sync without changing anything" {
|
||||
run git scp -n fake
|
||||
assert_success
|
||||
assert_line -p 'tracked.txt'
|
||||
assert_line -p 'DRY RUN'
|
||||
|
||||
run git status --short
|
||||
assert_line -p 'M tracked.txt'
|
||||
|
||||
run test -e "$BATS_TEST_TMPDIR/never-created"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "interactive mode aborts without syncing when declined" {
|
||||
run bash -c 'echo n | git scp -i fake'
|
||||
assert_success
|
||||
assert_line -p 'Aborted, nothing synced.'
|
||||
|
||||
run git status --short
|
||||
assert_line -p 'M tracked.txt'
|
||||
|
||||
run test -e "$BATS_TEST_TMPDIR/never-created"
|
||||
assert_failure
|
||||
}
|
||||
Loading…
Reference in a new issue