Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 -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"
}

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