Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
122 changes: 104 additions & 18 deletions bin/git-scp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")/"
Comment thread
anderewrey marked this conversation as resolved.
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 -rlDvni --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
Comment thread
anderewrey marked this conversation as resolved.
Outdated
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"
}

Expand All @@ -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
Expand Down
16 changes: 14 additions & 2 deletions man/git-scp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
46 changes: 46 additions & 0 deletions tests/git-scp.bats
Original file line number Diff line number Diff line change
@@ -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