1
0
Fork 0
mirror of synced 2024-12-04 14:45:36 -05:00

Compare commits

...

1 commit

Author SHA1 Message Date
Erik Flodin
3abfd103cf
Refactor alt handling
* Simplify score_file() by using case in instead of nested ifs with regexps.
* Merge record_score() and record_template().
* Alt condition processing no longer stops when a template condition is seen
  but continues processing to verify that all conditions are valid (as the
  documentation says it should). Fixes #478.
* Support alt dirs with deeply nested tracked files (fixes #495).
* Use git ls-files to filter out which tracked files to consider for alt
  processing. Should speed up auto-alt (#505).
2024-11-29 00:13:43 +01:00
9 changed files with 244 additions and 290 deletions

View file

@ -170,6 +170,21 @@ def test_alt_templates(runner, paths, kind, label):
assert str(paths.work.join(source_file)) in created
@pytest.mark.usefixtures("ds1_copy")
def test_alt_template_with_condition(runner, paths, tst_arch):
"""Test template with extra condition"""
yadm_dir, yadm_data = setup_standard_yadm_dir(paths)
suffix = f"##default,arch.not{tst_arch}"
utils.create_alt_files(paths, suffix)
run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"])
assert run.success
assert run.err == ""
created = utils.parse_alt_output(run.out, linked=False)
assert len(created) == 0
@pytest.mark.usefixtures("ds1_copy")
@pytest.mark.parametrize("autoalt", [None, "true", "false"])
def test_auto_alt(runner, yadm_cmd, paths, autoalt):

View file

@ -40,7 +40,8 @@ def test_alt_copy(runner, yadm_cmd, paths, tst_sys, setting, expect_link, pre_ex
run = runner(yadm_cmd("alt"))
assert run.success
assert run.err == ""
assert "Linking" in run.out
action = "Copying" if setting is True else "Linking"
assert action in run.out
assert alt_path.read() == expected_content
assert alt_path.islink() == expect_link

View file

@ -19,6 +19,7 @@ REPORT_RESULTS = """
echo "SCORES:${alt_scores[@]}"
echo "TARGETS:${alt_targets[@]}"
echo "SOURCES:${alt_sources[@]}"
echo "TEMPLATE_CMDS:${alt_template_cmds[@]}"
"""
@ -38,6 +39,7 @@ def test_dont_record_zeros(runner, yadm):
assert "SCORES:\n" in run.out
assert "TARGETS:\n" in run.out
assert "SOURCES:\n" in run.out
assert "TEMPLATE_CMDS:\n" in run.out
def test_new_scores(runner, yadm):
@ -46,9 +48,9 @@ def test_new_scores(runner, yadm):
script = f"""
YADM_TEST=1 source {yadm}
{INIT_VARS}
record_score "1" "tgt_one" "src_one"
record_score "2" "tgt_two" "src_two"
record_score "4" "tgt_three" "src_three"
record_score "1" "tgt_one" "src_one" ""
record_score "2" "tgt_two" "src_two" ""
record_score "4" "tgt_three" "src_three" ""
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
@ -58,6 +60,7 @@ def test_new_scores(runner, yadm):
assert "SCORES:1 2 4\n" in run.out
assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out
assert "SOURCES:src_one src_two src_three\n" in run.out
assert "TEMPLATE_CMDS: \n" in run.out
@pytest.mark.parametrize("difference", ["lower", "equal", "higher"])
@ -81,7 +84,8 @@ def test_existing_scores(runner, yadm, difference):
alt_scores=(2)
alt_targets=("testtgt")
alt_sources=("existing_src")
record_score "{score}" "testtgt" "new_src"
alt_template_cmds=("")
record_score "{score}" "testtgt" "new_src" ""
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
@ -91,6 +95,7 @@ def test_existing_scores(runner, yadm, difference):
assert f"SCORES:{expected_score}\n" in run.out
assert "TARGETS:testtgt\n" in run.out
assert f"SOURCES:{expected_src}\n" in run.out
assert "TEMPLATE_CMDS:\n" in run.out
def test_existing_template(runner, yadm):
@ -101,9 +106,9 @@ def test_existing_template(runner, yadm):
{INIT_VARS}
alt_scores=(1)
alt_targets=("testtgt")
alt_sources=()
alt_sources=("src")
alt_template_cmds=("existing_template")
record_score "2" "testtgt" "new_src"
record_score "2" "testtgt" "new_src" ""
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
@ -112,7 +117,8 @@ def test_existing_template(runner, yadm):
assert "SIZE:1\n" in run.out
assert "SCORES:1\n" in run.out
assert "TARGETS:testtgt\n" in run.out
assert "SOURCES:\n" in run.out
assert "SOURCES:src\n" in run.out
assert "TEMPLATE_CMDS:existing_template\n" in run.out
def test_config_first(runner, yadm):
@ -123,20 +129,61 @@ def test_config_first(runner, yadm):
YADM_TEST=1 source {yadm}
{INIT_VARS}
YADM_CONFIG={config}
record_score "1" "tgt_before" "src_before"
record_template "tgt_tmp" "cmd_tmp" "src_tmp"
record_score "2" "{config}" "src_config"
record_score "3" "tgt_after" "src_after"
record_score "1" "tgt_before" "src_before" ""
record_score "1" "tgt_tmp" "src_tmp" "cmd_tmp"
record_score "2" "{config}" "src_config" ""
record_score "3" "tgt_after" "src_after" ""
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
assert run.success
assert run.err == ""
assert "SIZE:4\n" in run.out
assert "SCORES:2 1 1 3\n" in run.out
assert f"TARGETS:{config} tgt_before tgt_tmp tgt_after\n" in run.out
assert "SOURCES:src_config src_before src_tmp src_after\n" in run.out
assert "TEMPLATE_CMDS: cmd_tmp \n" in run.out
def test_new_template(runner, yadm):
"""Test new template"""
script = f"""
YADM_TEST=1 source {yadm}
{INIT_VARS}
record_score 0 "tgt_one" "src_one" "cmd_one"
record_score 0 "tgt_two" "src_two" "cmd_two"
record_score 0 "tgt_three" "src_three" "cmd_three"
{REPORT_RESULTS}
echo "CMD_VALUE:${{alt_template_cmds[@]}}"
echo "CMD_INDEX:${{!alt_template_cmds[@]}}"
"""
run = runner(command=["bash"], inp=script)
assert run.success
assert run.err == ""
assert "SIZE:3\n" in run.out
assert "SCORES:2 1 3\n" in run.out
assert f"TARGETS:{config} tgt_before tgt_tmp tgt_after\n" in run.out
assert "SOURCES:src_config src_before src_tmp src_after\n" in run.out
assert "CMD_VALUE:cmd_tmp\n" in run.out
assert "CMD_INDEX:2\n" in run.out
assert "SCORES:0 0 0\n" in run.out
assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out
assert "SOURCES:src_one src_two src_three\n" in run.out
assert "TEMPLATE_CMDS:cmd_one cmd_two cmd_three\n" in run.out
def test_overwrite_existing_template(runner, yadm):
"""Overwrite existing templates"""
script = f"""
YADM_TEST=1 source {yadm}
{INIT_VARS}
alt_scores=(0)
alt_targets=("testtgt")
alt_template_cmds=("existing_cmd")
alt_sources=("existing_src")
record_score 0 "testtgt" "new_src" "new_cmd"
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
assert run.success
assert run.err == ""
assert "SIZE:1\n" in run.out
assert "SCORES:0\n" in run.out
assert "TARGETS:testtgt\n" in run.out
assert "SOURCES:new_src\n" in run.out
assert "TEMPLATE_CMDS:new_cmd\n" in run.out

View file

@ -1,55 +0,0 @@
"""Unit tests: record_template"""
INIT_VARS = """
alt_targets=()
alt_template_cmds=()
alt_sources=()
"""
REPORT_RESULTS = """
echo "SIZE:${#alt_targets[@]}"
echo "TARGETS:${alt_targets[@]}"
echo "CMDS:${alt_template_cmds[@]}"
echo "SOURCES:${alt_sources[@]}"
"""
def test_new_template(runner, yadm):
"""Test new template"""
script = f"""
YADM_TEST=1 source {yadm}
{INIT_VARS}
record_template "tgt_one" "cmd_one" "src_one"
record_template "tgt_two" "cmd_two" "src_two"
record_template "tgt_three" "cmd_three" "src_three"
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
assert run.success
assert run.err == ""
assert "SIZE:3\n" in run.out
assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out
assert "CMDS:cmd_one cmd_two cmd_three\n" in run.out
assert "SOURCES:src_one src_two src_three\n" in run.out
def test_existing_template(runner, yadm):
"""Overwrite existing templates"""
script = f"""
YADM_TEST=1 source {yadm}
{INIT_VARS}
alt_targets=("testtgt")
alt_template_cmds=("existing_cmd")
alt_sources=("existing_src")
record_template "testtgt" "new_cmd" "new_src"
{REPORT_RESULTS}
"""
run = runner(command=["bash"], inp=script)
assert run.success
assert run.err == ""
assert "SIZE:1\n" in run.out
assert "TARGETS:testtgt\n" in run.out
assert "CMDS:new_cmd\n" in run.out
assert "SOURCES:new_src\n" in run.out

View file

@ -25,7 +25,7 @@ def test_remove_stale_links(runner, yadm, tmpdir, kind, linked):
script = f"""
YADM_TEST=1 source {yadm}
possible_alts=({link})
possible_alt_targets=({link})
alt_linked=({alt_linked})
function rm() {{ echo rm "$@"; }}
remove_stale_links

View file

@ -89,7 +89,7 @@ def calculate_score(filename):
else:
score = 0
break
elif label in TEMPLATE_LABELS:
elif label not in TEMPLATE_LABELS:
score = 0
break
return score
@ -190,7 +190,7 @@ def test_score_values(runner, yadm, default, arch, system, distro, cla, host, us
expected = ""
for filename, score in filenames.items():
script += f"""
score_file "{filename}"
score_file "{filename}" "dest"
echo "{filename}"
echo "$score"
"""
@ -255,7 +255,7 @@ def test_score_values_templates(runner, yadm):
expected = ""
for filename, score in filenames.items():
script += f"""
score_file "{filename}"
score_file "{filename}" "dest"
echo "{filename}"
echo "$score"
"""
@ -279,7 +279,7 @@ def test_template_recording(runner, yadm, cmd_generated):
script = f"""
YADM_TEST=1 source {yadm}
function record_template() {{ echo "template recorded"; }}
function record_score() {{ [ -n "$4" ] && echo "template recorded"; }}
{mock}
score_file "testfile##template.kind"
"""

View file

@ -12,7 +12,7 @@ ALT_DIR = "test alt/test alt dir"
# Directory based alternates must have a tracked contained file.
# This will be the test contained file name
CONTAINED = "contained_file"
CONTAINED = "contained_dir/contained_file"
# These variables are used for making include files which will be processed
# within jinja templates
@ -84,7 +84,7 @@ def parse_alt_output(output, linked=True):
"""Parse output of 'alt', and return list of linked files"""
regex = r"Creating (.+) from template (.+)$"
if linked:
regex = r"Linking (.+) to (.+)$"
regex = r"(?:Copy|Link)ing (.+) to (.+)$"
parsed_list = {}
for line in output.splitlines():
match = re.match(regex, line)

362
yadm
View file

@ -166,188 +166,135 @@ function main() {
# ****** Alternate Processing ******
function score_file() {
src="$1"
tgt="${src%%##*}"
conditions="${src#*##}"
if [ "${tgt#"$YADM_ALT/"}" != "${tgt}" ]; then
tgt="${YADM_BASE}/${tgt#"$YADM_ALT/"}"
fi
local source="$1"
local target="$2"
local conditions="${source#*##}"
score=0
local template_cmd=""
IFS=',' read -ra fields <<< "$conditions"
for field in "${fields[@]}"; do
label=${field%%.*}
value=${field#*.}
local label=${field%%.*}
local value=${field#*.}
[ "$field" = "$label" ] && value="" # when .value is omitted
# extension isn't a condition and doesn't affect the score
if [[ "$label" =~ ^(e|extension)$ ]]; then
continue
fi
score=$((score + 1000))
# default condition
if [[ "$label" =~ ^(default)$ ]]; then
score=$((score + 0))
# variable conditions
elif [[ "$label" =~ ^(a|arch)$ ]]; then
if [ "$value" = "$local_arch" ]; then
score=$((score + 1))
else
score=0
return
fi
elif [[ "$label" =~ ^(o|os)$ ]]; then
if [ "$value" = "$local_system" ]; then
score=$((score + 2))
else
score=0
return
fi
elif [[ "$label" =~ ^(d|distro)$ ]]; then
if [ "${value/\ /_}" = "${local_distro/\ /_}" ]; then
score=$((score + 4))
else
score=0
return
fi
elif [[ "$label" =~ ^(f|distro_family)$ ]]; then
if [ "${value/\ /_}" = "${local_distro_family/\ /_}" ]; then
score=$((score + 8))
else
score=0
return
fi
elif [[ "$label" =~ ^(c|class)$ ]]; then
if in_list "$value" "${local_classes[@]}"; then
score=$((score + 16))
else
score=0
return
fi
elif [[ "$label" =~ ^(h|hostname)$ ]]; then
if [ "$value" = "$local_host" ]; then
score=$((score + 32))
else
score=0
return
fi
elif [[ "$label" =~ ^(u|user)$ ]]; then
if [ "$value" = "$local_user" ]; then
score=$((score + 64))
else
score=0
return
fi
# templates
elif [[ "$label" =~ ^(t|template|yadm)$ ]]; then
score=0
cmd=$(choose_template_cmd "$value")
if [ -n "$cmd" ]; then
record_template "$tgt" "$cmd" "$src"
else
debug "No supported template processor for template $src"
[ -n "$loud" ] && echo "No supported template processor for template $src"
fi
return 0
# unsupported values
else
if [[ "${src##*/}" =~ .\#\#. ]]; then
INVALID_ALT+=("$src")
fi
local -i delta=-1
case "$label" in
default)
delta=0
;;
a|arch)
[ "$value" = "$local_arch" ] && delta=1
;;
o|os)
[ "$value" = "$local_system" ] && delta=2
;;
d|distro)
[ "${value// /_}" = "${local_distro// /_}" ] && delta=4
;;
f|distro_family)
[ "${value// /_}" = "${local_distro_family// /_}" ] && delta=8
;;
c|class)
in_list "$value" "${local_classes[@]}" && delta=16
;;
h|hostname)
[ "$value" = "$local_host" ] && delta=32
;;
u|user)
[ "$value" = "$local_user" ] && delta=64
;;
e|extension)
# extension isn't a condition and doesn't affect the score
continue
;;
t|template|yadm)
if [ -d "$source" ]; then
INVALID_ALT+=("$source")
else
template_cmd=$(choose_template_cmd "$value")
if [ -n "$template_cmd" ]; then
delta=0
else
debug "No supported template processor for template $source"
[ -n "$loud" ] && echo "No supported template processor for template $source"
fi
fi
;;
*)
INVALID_ALT+=("$source")
;;
esac
if (( delta < 0 )); then
score=0
return
fi
score=$(( score + 1000 + delta ))
done
record_score "$score" "$tgt" "$src"
record_score "$score" "$target" "$source" "$template_cmd"
}
function record_score() {
score="$1"
tgt="$2"
src="$3"
local score="$1"
local target="$2"
local source="$3"
local template_cmd="$4"
# record nothing if the score is zero
[ "$score" -eq 0 ] && return
[ "$score" -eq 0 ] && [ -z "$template_cmd" ] && return
# search for the index of this target, to see if we already are tracking it
index=-1
for search_index in "${!alt_targets[@]}"; do
if [ "${alt_targets[$search_index]}" = "$tgt" ]; then
index="$search_index"
break
local -i index=$((${#alt_targets[@]} - 1))
for (( ; index >= 0; --index )); do
if [ "${alt_targets[$index]}" = "$target" ]; then
break
fi
done
# if we don't find an existing index, create one by appending to the array
if [ "$index" -eq -1 ]; then
if [ $index -lt 0 ]; then
# $YADM_CONFIG must be processed first, in case other templates lookup yadm configurations
if [ "$tgt" = "$YADM_CONFIG" ]; then
alt_targets=("$tgt" "${alt_targets[@]}")
alt_sources=("$src" "${alt_sources[@]}")
alt_scores=(0 "${alt_scores[@]}")
index=0
# increase the index of any existing alt_template_cmds
new_cmds=()
for cmd_index in "${!alt_template_cmds[@]}"; do
new_cmds[cmd_index+1]="${alt_template_cmds[$cmd_index]}"
done
alt_template_cmds=()
for cmd_index in "${!new_cmds[@]}"; do
alt_template_cmds[cmd_index]="${new_cmds[$cmd_index]}"
done
if [ "$target" = "$YADM_CONFIG" ]; then
alt_targets=("$target" "${alt_targets[@]}")
alt_sources=("$source" "${alt_sources[@]}")
alt_scores=("$score" "${alt_scores[@]}")
alt_template_cmds=("$template_cmd" "${alt_template_cmds[@]}")
else
alt_targets+=("$tgt")
# set index to the last index (newly created one)
for index in "${!alt_targets[@]}"; do :; done
# and set its initial score to zero
alt_scores[index]=0
fi
fi
alt_targets+=("$target")
# record nothing if a template command is registered for this file
[ "${alt_template_cmds[$index]+isset}" ] && return
# record higher scoring sources
if [ "$score" -gt "${alt_scores[$index]}" ]; then
alt_scores[index]="$score"
alt_sources[index]="$src"
fi
}
function record_template() {
tgt="$1"
cmd="$2"
src="$3"
# search for the index of this target, to see if we already are tracking it
index=-1
for search_index in "${!alt_targets[@]}"; do
if [ "${alt_targets[$search_index]}" = "$tgt" ]; then
index="$search_index"
break
alt_sources+=("$source")
alt_scores+=("$score")
alt_template_cmds+=("$template_cmd")
fi
done
# if we don't find an existing index, create one by appending to the array
if [ "$index" -eq -1 ]; then
alt_targets+=("$tgt")
# set index to the last index (newly created one)
for index in "${!alt_targets[@]}"; do :; done
return
fi
# record the template command, last one wins
alt_template_cmds[index]="$cmd"
alt_sources[index]="$src"
if [[ -n "${alt_template_cmds[$index]}" ]]; then
if [[ -z "$template_cmd" || "$score" -lt "${alt_scores[$index]}" ]]; then
# No template command, or template command but lower score
return
fi
elif [[ -z "$template_cmd" && "$score" -le "${alt_scores[$index]}" ]]; then
# No template command and too low score
return
fi
# Record new alt
alt_sources[index]="$source"
alt_scores[index]="$score"
alt_template_cmds[index]="$template_cmd"
}
function choose_template_cmd() {
kind="$1"
local kind="$1"
if [ "$kind" = "default" ] || [ "$kind" = "" ] && awk_available; then
echo "template_default"
elif [ "$kind" = "esh" ] && esh_available; then
echo "template_esh"
if [ "$kind" = "default" ] || [ "$kind" = "" ]; then
awk_available && echo "template_default"
elif [ "$kind" = "esh" ]; then
esh_available && echo "template_esh"
elif [ "$kind" = "j2cli" ] || [ "$kind" = "j2" ] && j2cli_available; then
echo "template_j2cli"
elif [ "$kind" = "envtpl" ] || [ "$kind" = "j2" ] && envtpl_available; then
@ -599,29 +546,45 @@ function alt() {
# determine all tracked files
local tracked_files=()
local IFS=$'\n'
for tracked_file in $("$GIT_PROGRAM" ls-files | LC_ALL=C sort); do
for tracked_file in $("$GIT_PROGRAM" ls-files -- '*##*'); do
tracked_files+=("$tracked_file")
done
# generate data for removing stale links
local possible_alts=()
local IFS=$'\n'
for possible_alt in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do
if [[ $possible_alt =~ .\#\#. ]]; then
base_alt="${possible_alt%%##*}"
yadm_alt="${YADM_BASE}/${base_alt}"
if [ "${yadm_alt#"$YADM_ALT/"}" != "${yadm_alt}" ]; then
base_alt="${yadm_alt#"$YADM_ALT/"}"
fi
possible_alts+=("$YADM_BASE/${base_alt}")
local alt_targets=()
local alt_sources=()
local alt_scores=()
local alt_template_cmds=()
# For removing stale links
local possible_alt_targets=()
local alt_source
for alt_source in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do
local conditions="${alt_source#*##}"
if [ "$alt_source" = "$conditions" ]; then
continue
fi
local target_base="${alt_source%%##*}"
alt_source="${YADM_BASE}/${target_base}##${conditions%%/*}"
local alt_target="${YADM_BASE}/${target_base}"
if [ "${alt_target#"$YADM_ALT/"}" != "$alt_target" ]; then
target_base="${alt_target#"$YADM_ALT/"}"
fi
alt_target="${YADM_BASE}/${target_base}"
if ! in_list "$alt_target" "${possible_alt_targets[@]}"; then
possible_alt_targets+=("$alt_target")
fi
score_file "$alt_source" "$alt_target"
done
local alt_linked=()
alt_linking
remove_stale_links
report_invalid_alts
}
function report_invalid_alts() {
@ -662,7 +625,7 @@ function remove_stale_links() {
# if a possible alt IS linked, but it's source is not part of alt_linked,
# remove it.
if readlink_available; then
for stale_candidate in "${possible_alts[@]}"; do
for stale_candidate in "${possible_alt_targets[@]}"; do
if [ -L "$stale_candidate" ]; then
src=$(readlink "$stale_candidate" 2>/dev/null)
if [ -n "$src" ]; then
@ -681,8 +644,8 @@ function set_local_alt_values() {
local -a all_classes
all_classes=$(config --get-all local.class)
while IFS='' read -r class; do
local_classes+=("$class")
local_class="$class"
local_classes+=("$class")
local_class="$class"
done <<< "$all_classes"
local_arch="$(config local.arch)"
@ -712,50 +675,33 @@ function set_local_alt_values() {
}
function alt_linking() {
local -i index
for (( index = 0; index < ${#alt_targets[@]}; ++index )); do
local target="${alt_targets[$index]}"
local source="${alt_sources[$index]}"
local template_cmd="${alt_template_cmds[$index]}"
local alt_scores=()
local alt_targets=()
local alt_sources=()
local alt_template_cmds=()
assert_parent "$target"
for alt_path in $(for tracked in "${tracked_files[@]}"; do printf "%s\n" "$tracked" "${tracked%/*}"; done | LC_ALL=C sort -u) "${ENCRYPT_INCLUDE_FILES[@]}"; do
alt_path="$YADM_BASE/$alt_path"
if [[ "$alt_path" =~ .\#\#. ]]; then
if [ -e "$alt_path" ] ; then
score_file "$alt_path"
fi
if [[ -n "$template_cmd" ]]; then
debug "Creating $target from template $source"
[[ -n "$loud" ]] && echo "Creating $target from template $source"
[[ -L "$target" ]] && rm -f "$target"
"$template_cmd" "$source" "$target"
elif [[ "$do_copy" -eq 1 ]]; then
debug "Copying $source to $target"
[[ -n "$loud" ]] && echo "Copying $source to $target"
[[ -L "$target" ]] && rm -f "$target"
cp -f "$source" "$target"
else
debug "Linking $source to $target"
[[ -n "$loud" ]] && echo "Linking $source to $target"
ln_relative "$source" "$target"
fi
done
for index in "${!alt_targets[@]}"; do
tgt="${alt_targets[$index]}"
src="${alt_sources[$index]}"
template_cmd="${alt_template_cmds[$index]}"
if [ -n "$template_cmd" ]; then
# a template is defined, process the template
debug "Creating $tgt from template $src"
[ -n "$loud" ] && echo "Creating $tgt from template $src"
# ensure the destination path exists
assert_parent "$tgt"
# remove any existing symlink before processing template
[ -L "$tgt" ] && rm -f "$tgt"
"$template_cmd" "$src" "$tgt"
elif [ -n "$src" ]; then
# a link source is defined, create symlink
debug "Linking $src to $tgt"
[ -n "$loud" ] && echo "Linking $src to $tgt"
# ensure the destination path exists
assert_parent "$tgt"
if [ "$do_copy" -eq 1 ]; then
# remove any existing symlink before copying
[ -L "$tgt" ] && rm -f "$tgt"
cp -f "$src" "$tgt"
else
ln_relative "$src" "$tgt"
fi
fi
done
}
function ln_relative() {

2
yadm.1
View file

@ -595,7 +595,7 @@ If no "##default" version exists and no files have valid conditions, then no
link will be created.
Links are also created for directories named this way, as long as they have at
least one yadm managed file within them (at the top level).
least one yadm managed file within them.
yadm will automatically create these links by default. This can be disabled
using the