Support -b <branch>
when cloning (#133)
This commit is contained in:
parent
6a3199ceea
commit
f3bde37f78
5 changed files with 134 additions and 17 deletions
|
@ -55,7 +55,7 @@ if declare -F _git > /dev/null; then
|
||||||
|
|
||||||
case "$antepenultimate" in
|
case "$antepenultimate" in
|
||||||
clone)
|
clone)
|
||||||
COMPREPLY=( $(compgen -W "-f -w --bootstrap --no-bootstrap" -- "$current") )
|
COMPREPLY=( $(compgen -W "-f -w -b --bootstrap --no-bootstrap" -- "$current") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
@ -251,6 +251,51 @@ def test_clone_perms(
|
||||||
f'.{private_type} has not been secured by auto.perms')
|
f'.{private_type} has not been secured by auto.perms')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('remote')
|
||||||
|
@pytest.mark.parametrize('branch', ['master', 'valid', 'invalid'])
|
||||||
|
def test_alternate_branch(runner, paths, yadm_y, repo_config, branch):
|
||||||
|
"""Test cloning a branch other than master"""
|
||||||
|
|
||||||
|
# add a "valid" branch to the remote
|
||||||
|
os.system(f'GIT_DIR="{paths.remote}" git checkout -b valid')
|
||||||
|
os.system(
|
||||||
|
f'GIT_DIR="{paths.remote}" git commit '
|
||||||
|
f'--allow-empty -m "This branch is valid"')
|
||||||
|
|
||||||
|
# clear out the work path
|
||||||
|
paths.work.remove()
|
||||||
|
paths.work.mkdir()
|
||||||
|
|
||||||
|
remote_url = f'file://{paths.remote}'
|
||||||
|
|
||||||
|
# run the clone command
|
||||||
|
args = ['clone', '-w', paths.work]
|
||||||
|
if branch != 'master':
|
||||||
|
args += ['-b', branch]
|
||||||
|
args += [remote_url]
|
||||||
|
run = runner(command=yadm_y(*args))
|
||||||
|
|
||||||
|
if branch == 'invalid':
|
||||||
|
assert run.failure
|
||||||
|
assert 'ERROR: Clone failed' in run.out
|
||||||
|
assert f"'origin/{branch}' does not exist in {remote_url}" in run.out
|
||||||
|
else:
|
||||||
|
assert successful_clone(run, paths, repo_config)
|
||||||
|
|
||||||
|
# confirm correct Git origin
|
||||||
|
run = runner(
|
||||||
|
command=('git', 'remote', '-v', 'show'),
|
||||||
|
env={'GIT_DIR': paths.repo})
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert f'origin\t{remote_url}' in run.out
|
||||||
|
run = runner(command=yadm_y('show'))
|
||||||
|
if branch == 'valid':
|
||||||
|
assert 'This branch is valid' in run.out
|
||||||
|
else:
|
||||||
|
assert 'Initial commit' in run.out
|
||||||
|
|
||||||
|
|
||||||
def successful_clone(run, paths, repo_config, expected_code=0):
|
def successful_clone(run, paths, repo_config, expected_code=0):
|
||||||
"""Assert clone is successful"""
|
"""Assert clone is successful"""
|
||||||
assert run.code == expected_code
|
assert run.code == expected_code
|
||||||
|
|
40
test/test_unit_is_valid_branch_name.py
Normal file
40
test/test_unit_is_valid_branch_name.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"""Unit tests: is_valid_branch_name"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Git branches do not allow:
|
||||||
|
# * path component that begins with "."
|
||||||
|
# * double dot
|
||||||
|
# * "~", "^", ":", "\", space
|
||||||
|
# * end with a "/"
|
||||||
|
# * end with ".lock"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'branch, expected', [
|
||||||
|
('master', 'valid'),
|
||||||
|
('path/branch', 'valid'),
|
||||||
|
('path/.branch', 'invalid'),
|
||||||
|
('path..branch', 'invalid'),
|
||||||
|
('path~branch', 'invalid'),
|
||||||
|
('path^branch', 'invalid'),
|
||||||
|
('path:branch', 'invalid'),
|
||||||
|
('path\\branch', 'invalid'),
|
||||||
|
('path branch', 'invalid'),
|
||||||
|
('path/branch/', 'invalid'),
|
||||||
|
('branch.lock', 'invalid'),
|
||||||
|
])
|
||||||
|
def test_is_valid_branch_name(runner, yadm, branch, expected):
|
||||||
|
"""Test function is_valid_branch_name()"""
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
YADM_TEST=1 source {yadm}
|
||||||
|
if is_valid_branch_name "{branch}"; then
|
||||||
|
echo valid
|
||||||
|
else
|
||||||
|
echo invalid
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
run = runner(command=['bash'], inp=script)
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ''
|
||||||
|
assert run.out.strip() == expected
|
54
yadm
54
yadm
|
@ -610,11 +610,20 @@ function clean() {
|
||||||
function clone() {
|
function clone() {
|
||||||
|
|
||||||
DO_BOOTSTRAP=1
|
DO_BOOTSTRAP=1
|
||||||
|
local branch
|
||||||
|
branch="master"
|
||||||
|
|
||||||
clone_args=()
|
clone_args=()
|
||||||
while [[ $# -gt 0 ]] ; do
|
while [[ $# -gt 0 ]] ; do
|
||||||
key="$1"
|
key="$1"
|
||||||
case $key in
|
case $key in
|
||||||
|
-b)
|
||||||
|
if ! is_valid_branch_name "$2"; then
|
||||||
|
error_out "You must provide a branch name when using '-b'"
|
||||||
|
fi
|
||||||
|
branch="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--bootstrap) # force bootstrap, without prompt
|
--bootstrap) # force bootstrap, without prompt
|
||||||
DO_BOOTSTRAP=2
|
DO_BOOTSTRAP=2
|
||||||
;;
|
;;
|
||||||
|
@ -634,12 +643,12 @@ function clone() {
|
||||||
local empty=
|
local empty=
|
||||||
init $empty
|
init $empty
|
||||||
|
|
||||||
# add the specified remote, and configure the repo to track origin/master
|
# add the specified remote, and configure the repo to track origin/$branch
|
||||||
debug "Adding remote to new repo"
|
debug "Adding remote to new repo"
|
||||||
"$GIT_PROGRAM" remote add origin "${clone_args[@]}"
|
"$GIT_PROGRAM" remote add origin "${clone_args[@]}"
|
||||||
debug "Configuring new repo to track origin/master"
|
debug "Configuring new repo to track origin/${branch}"
|
||||||
"$GIT_PROGRAM" config branch.master.remote origin
|
"$GIT_PROGRAM" config "branch.${branch}.remote" origin
|
||||||
"$GIT_PROGRAM" config branch.master.merge refs/heads/master
|
"$GIT_PROGRAM" config "branch.${branch}.merge" "refs/heads/${branch}"
|
||||||
|
|
||||||
# fetch / merge (and possibly fallback to reset)
|
# fetch / merge (and possibly fallback to reset)
|
||||||
debug "Doing an initial fetch of the origin"
|
debug "Doing an initial fetch of the origin"
|
||||||
|
@ -648,30 +657,36 @@ function clone() {
|
||||||
rm -rf "$YADM_REPO"
|
rm -rf "$YADM_REPO"
|
||||||
error_out "Unable to fetch origin ${clone_args[0]}"
|
error_out "Unable to fetch origin ${clone_args[0]}"
|
||||||
}
|
}
|
||||||
|
debug "Verifying '${branch}' is a valid branch to merge"
|
||||||
|
[ -f "${YADM_REPO}/refs/remotes/origin/${branch}" ] || {
|
||||||
|
debug "Removing repo after failed clone"
|
||||||
|
rm -rf "$YADM_REPO"
|
||||||
|
error_out "Clone failed, 'origin/${branch}' does not exist in ${clone_args[0]}"
|
||||||
|
}
|
||||||
debug "Determining if repo tracks private directories"
|
debug "Determining if repo tracks private directories"
|
||||||
for private_dir in .ssh/ .gnupg/; do
|
for private_dir in .ssh/ .gnupg/; do
|
||||||
found_log=$("$GIT_PROGRAM" log -n 1 origin/master -- "$private_dir" 2>/dev/null)
|
found_log=$("$GIT_PROGRAM" log -n 1 "origin/${branch}" -- "$private_dir" 2>/dev/null)
|
||||||
if [ -n "$found_log" ]; then
|
if [ -n "$found_log" ]; then
|
||||||
debug "Private directory $private_dir is tracked by repo"
|
debug "Private directory $private_dir is tracked by repo"
|
||||||
assert_private_dirs "$private_dir"
|
assert_private_dirs "$private_dir"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
[ -n "$DEBUG" ] && display_private_perms "pre-merge"
|
[ -n "$DEBUG" ] && display_private_perms "pre-merge"
|
||||||
debug "Doing an initial merge of origin/master"
|
debug "Doing an initial merge of origin/${branch}"
|
||||||
"$GIT_PROGRAM" merge origin/master || {
|
"$GIT_PROGRAM" merge "origin/${branch}" || {
|
||||||
debug "Merge failed, doing a reset and stashing conflicts."
|
debug "Merge failed, doing a reset and stashing conflicts."
|
||||||
"$GIT_PROGRAM" reset origin/master
|
"$GIT_PROGRAM" reset "origin/${branch}"
|
||||||
if cd "$YADM_WORK"; then # necessary because of a bug in Git
|
if cd "$YADM_WORK"; then # necessary because of a bug in Git
|
||||||
"$GIT_PROGRAM" -c user.name='yadm clone' -c user.email='yadm' stash save Conflicts preserved from yadm clone command 2>&1
|
"$GIT_PROGRAM" -c user.name='yadm clone' -c user.email='yadm' stash save Conflicts preserved from yadm clone command 2>&1
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
**NOTE**
|
**NOTE**
|
||||||
Merging origin/master failed.
|
Merging origin/${branch} failed.
|
||||||
|
|
||||||
As a result, yadm did 'reset origin/master', and then
|
As a result, yadm did 'reset origin/${branch}', and then
|
||||||
stashed the conflicting data.
|
stashed the conflicting data.
|
||||||
|
|
||||||
This likely happened because you had files in \$HOME
|
This likely happened because you had files in \$HOME
|
||||||
which conflicted with files tracked by origin/master.
|
which conflicted with files tracked by origin/${branch}.
|
||||||
|
|
||||||
You can review the stashed conflicts with the
|
You can review the stashed conflicts with the
|
||||||
command 'yadm stash show -p' from within your
|
command 'yadm stash show -p' from within your
|
||||||
|
@ -685,15 +700,15 @@ EOF
|
||||||
DO_BOOTSTRAP=0
|
DO_BOOTSTRAP=0
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
**NOTE**
|
**NOTE**
|
||||||
Merging origin/master failed.
|
Merging origin/${branch} failed.
|
||||||
yadm did 'reset origin/master' instead.
|
yadm did 'reset origin/${branch}' instead.
|
||||||
|
|
||||||
yadm did not stash these conflicts beacuse it was unable
|
yadm did not stash these conflicts beacuse it was unable
|
||||||
to change to the $YADM_WORK directory.
|
to change to the $YADM_WORK directory.
|
||||||
|
|
||||||
Please review and resolve any differences appropriately
|
Please review and resolve any differences appropriately
|
||||||
If you know what you're doing, and want to overwrite the
|
If you know what you're doing, and want to overwrite the
|
||||||
tracked files, consider 'yadm reset --hard origin/master'
|
tracked files, consider 'yadm reset --hard origin/${branch}'
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -1036,6 +1051,17 @@ function version() {
|
||||||
|
|
||||||
# ****** Utility Functions ******
|
# ****** Utility Functions ******
|
||||||
|
|
||||||
|
function is_valid_branch_name() {
|
||||||
|
# Git branches do not allow:
|
||||||
|
# * path component that begins with "."
|
||||||
|
# * double dot
|
||||||
|
# * "~", "^", ":", "\", space
|
||||||
|
# * end with a "/"
|
||||||
|
# * end with ".lock"
|
||||||
|
[[ "$1" =~ (\/\.|\.\.|[~^:\\ ]|\/$|\.lock$) ]] && return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
function query_distro() {
|
function query_distro() {
|
||||||
distro=""
|
distro=""
|
||||||
if command -v "$LSB_RELEASE_PROGRAM" >/dev/null 2>&1; then
|
if command -v "$LSB_RELEASE_PROGRAM" >/dev/null 2>&1; then
|
||||||
|
|
10
yadm.1
10
yadm.1
|
@ -19,13 +19,15 @@ yadm \- Yet Another Dotfiles Manager
|
||||||
init
|
init
|
||||||
.RB [ -f ]
|
.RB [ -f ]
|
||||||
.RB [ -w
|
.RB [ -w
|
||||||
.IR directory ]
|
.IR dir ]
|
||||||
|
|
||||||
.B yadm
|
.B yadm
|
||||||
.RI clone " url
|
.RI clone " url
|
||||||
.RB [ -f ]
|
.RB [ -f ]
|
||||||
.RB [ -w
|
.RB [ -w
|
||||||
.IR directory ]
|
.IR dir ]
|
||||||
|
.RB [ -b
|
||||||
|
.IR branch ]
|
||||||
.RB [ --bootstrap ]
|
.RB [ --bootstrap ]
|
||||||
.RB [ --no-bootstrap ]
|
.RB [ --no-bootstrap ]
|
||||||
|
|
||||||
|
@ -146,6 +148,10 @@ but this can be overridden with the
|
||||||
.BR -w " option.
|
.BR -w " option.
|
||||||
yadm can be forced to overwrite an existing repository by providing the
|
yadm can be forced to overwrite an existing repository by providing the
|
||||||
.BR -f " option.
|
.BR -f " option.
|
||||||
|
If you want to use a branch other than
|
||||||
|
.IR origin/master ,
|
||||||
|
you can specify it using the
|
||||||
|
.BR -b " option.
|
||||||
By default yadm will ask the user if the bootstrap program should be run (if it
|
By default yadm will ask the user if the bootstrap program should be run (if it
|
||||||
exists). The options
|
exists). The options
|
||||||
.BR --bootstrap " or " --no-bootstrap
|
.BR --bootstrap " or " --no-bootstrap
|
||||||
|
|
Loading…
Reference in a new issue