1
0
Fork 0
mirror of synced 2024-12-22 06:31:07 -05:00

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 #490).
* Use git ls-files to filter out which tracked files to consider for alt
  processing. Should speed up auto-alt (#505).
* Use nocasematch when comparing distro and distro_family. Fixed #455.
This commit is contained in:
Erik Flodin 2024-11-28 23:28:32 +01:00
parent b164d03594
commit b2b0b143d6
No known key found for this signature in database
GPG key ID: 420A7C865EE3F85F
9 changed files with 259 additions and 296 deletions

View file

@ -170,6 +170,21 @@ def test_alt_templates(runner, paths, kind, label):
assert str(paths.work.join(source_file)) in created 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"##template,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.usefixtures("ds1_copy")
@pytest.mark.parametrize("autoalt", [None, "true", "false"]) @pytest.mark.parametrize("autoalt", [None, "true", "false"])
def test_auto_alt(runner, yadm_cmd, paths, autoalt): 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")) run = runner(yadm_cmd("alt"))
assert run.success assert run.success
assert run.err == "" 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.read() == expected_content
assert alt_path.islink() == expect_link assert alt_path.islink() == expect_link

View file

@ -19,6 +19,7 @@ REPORT_RESULTS = """
echo "SCORES:${alt_scores[@]}" echo "SCORES:${alt_scores[@]}"
echo "TARGETS:${alt_targets[@]}" echo "TARGETS:${alt_targets[@]}"
echo "SOURCES:${alt_sources[@]}" 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 "SCORES:\n" in run.out
assert "TARGETS:\n" in run.out assert "TARGETS:\n" in run.out
assert "SOURCES:\n" in run.out assert "SOURCES:\n" in run.out
assert "TEMPLATE_CMDS:\n" in run.out
def test_new_scores(runner, yadm): def test_new_scores(runner, yadm):
@ -46,9 +48,9 @@ def test_new_scores(runner, yadm):
script = f""" script = f"""
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
{INIT_VARS} {INIT_VARS}
record_score "1" "tgt_one" "src_one" record_score "1" "tgt_one" "src_one" ""
record_score "2" "tgt_two" "src_two" record_score "2" "tgt_two" "src_two" ""
record_score "4" "tgt_three" "src_three" record_score "4" "tgt_three" "src_three" ""
{REPORT_RESULTS} {REPORT_RESULTS}
""" """
run = runner(command=["bash"], inp=script) 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 "SCORES:1 2 4\n" in run.out
assert "TARGETS:tgt_one tgt_two tgt_three\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 "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"]) @pytest.mark.parametrize("difference", ["lower", "equal", "higher"])
@ -81,7 +84,8 @@ def test_existing_scores(runner, yadm, difference):
alt_scores=(2) alt_scores=(2)
alt_targets=("testtgt") alt_targets=("testtgt")
alt_sources=("existing_src") alt_sources=("existing_src")
record_score "{score}" "testtgt" "new_src" alt_template_cmds=("")
record_score "{score}" "testtgt" "new_src" ""
{REPORT_RESULTS} {REPORT_RESULTS}
""" """
run = runner(command=["bash"], inp=script) 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 f"SCORES:{expected_score}\n" in run.out
assert "TARGETS:testtgt\n" in run.out assert "TARGETS:testtgt\n" in run.out
assert f"SOURCES:{expected_src}\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): def test_existing_template(runner, yadm):
@ -101,9 +106,9 @@ def test_existing_template(runner, yadm):
{INIT_VARS} {INIT_VARS}
alt_scores=(1) alt_scores=(1)
alt_targets=("testtgt") alt_targets=("testtgt")
alt_sources=() alt_sources=("src")
alt_template_cmds=("existing_template") alt_template_cmds=("existing_template")
record_score "2" "testtgt" "new_src" record_score "2" "testtgt" "new_src" ""
{REPORT_RESULTS} {REPORT_RESULTS}
""" """
run = runner(command=["bash"], inp=script) run = runner(command=["bash"], inp=script)
@ -112,7 +117,8 @@ def test_existing_template(runner, yadm):
assert "SIZE:1\n" in run.out assert "SIZE:1\n" in run.out
assert "SCORES:1\n" in run.out assert "SCORES:1\n" in run.out
assert "TARGETS:testtgt\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): def test_config_first(runner, yadm):
@ -123,20 +129,61 @@ def test_config_first(runner, yadm):
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
{INIT_VARS} {INIT_VARS}
YADM_CONFIG={config} YADM_CONFIG={config}
record_score "1" "tgt_before" "src_before" record_score "1" "tgt_before" "src_before" ""
record_template "tgt_tmp" "cmd_tmp" "src_tmp" record_score "1" "tgt_tmp" "src_tmp" "cmd_tmp"
record_score "2" "{config}" "src_config" record_score "2" "{config}" "src_config" ""
record_score "3" "tgt_after" "src_after" 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} {REPORT_RESULTS}
echo "CMD_VALUE:${{alt_template_cmds[@]}}"
echo "CMD_INDEX:${{!alt_template_cmds[@]}}"
""" """
run = runner(command=["bash"], inp=script) run = runner(command=["bash"], inp=script)
assert run.success assert run.success
assert run.err == "" assert run.err == ""
assert "SIZE:3\n" in run.out assert "SIZE:3\n" in run.out
assert "SCORES:2 1 3\n" in run.out assert "SCORES:0 0 0\n" in run.out
assert f"TARGETS:{config} tgt_before tgt_tmp tgt_after\n" in run.out assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out
assert "SOURCES:src_config src_before src_tmp src_after\n" in run.out assert "SOURCES:src_one src_two src_three\n" in run.out
assert "CMD_VALUE:cmd_tmp\n" in run.out assert "TEMPLATE_CMDS:cmd_one cmd_two cmd_three\n" in run.out
assert "CMD_INDEX:2\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""" script = f"""
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
possible_alts=({link}) possible_alt_targets=({link})
alt_linked=({alt_linked}) alt_linked=({alt_linked})
function rm() {{ echo rm "$@"; }} function rm() {{ echo rm "$@"; }}
remove_stale_links remove_stale_links

View file

@ -89,7 +89,7 @@ def calculate_score(filename):
else: else:
score = 0 score = 0
break break
elif label in TEMPLATE_LABELS: elif label not in TEMPLATE_LABELS:
score = 0 score = 0
break break
return score return score
@ -190,7 +190,7 @@ def test_score_values(runner, yadm, default, arch, system, distro, cla, host, us
expected = "" expected = ""
for filename, score in filenames.items(): for filename, score in filenames.items():
script += f""" script += f"""
score_file "{filename}" score_file "{filename}" "dest"
echo "{filename}" echo "{filename}"
echo "$score" echo "$score"
""" """
@ -255,7 +255,7 @@ def test_score_values_templates(runner, yadm):
expected = "" expected = ""
for filename, score in filenames.items(): for filename, score in filenames.items():
script += f""" script += f"""
score_file "{filename}" score_file "{filename}" "dest"
echo "{filename}" echo "{filename}"
echo "$score" echo "$score"
""" """
@ -279,7 +279,7 @@ def test_template_recording(runner, yadm, cmd_generated):
script = f""" script = f"""
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
function record_template() {{ echo "template recorded"; }} function record_score() {{ [ -n "$4" ] && echo "template recorded"; }}
{mock} {mock}
score_file "testfile##template.kind" score_file "testfile##template.kind"
""" """
@ -289,15 +289,15 @@ def test_template_recording(runner, yadm, cmd_generated):
assert run.out.rstrip() == expected assert run.out.rstrip() == expected
def test_underscores_in_distro_and_family(runner, yadm): def test_underscores_and_upper_case_in_distro_and_family(runner, yadm):
"""Test replacing spaces in distro / distro_family with underscores""" """Test replacing spaces with underscores and lowering case in distro / distro_family"""
local_distro = "test distro" local_distro = "test distro"
local_distro_family = "test family" local_distro_family = "test family"
filenames = { filenames = {
"filename##distro.test distro": 1004, "filename##distro.Test Distro": 1004,
"filename##distro.test-distro": 0, "filename##distro.test-distro": 0,
"filename##distro.test_distro": 1004, "filename##distro.test_distro": 1004,
"filename##distro_family.test family": 1008, "filename##distro_family.test FAMILY": 1008,
"filename##distro_family.test-family": 0, "filename##distro_family.test-family": 0,
"filename##distro_family.test_family": 1008, "filename##distro_family.test_family": 1008,
} }

View file

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

375
yadm
View file

@ -166,188 +166,139 @@ function main() {
# ****** Alternate Processing ****** # ****** Alternate Processing ******
function score_file() { function score_file() {
src="$1" local source="$1"
tgt="${src%%##*}" local target="$2"
conditions="${src#*##}" local conditions="${source#*##}"
if [ "${tgt#"$YADM_ALT/"}" != "${tgt}" ]; then
tgt="${YADM_BASE}/${tgt#"$YADM_ALT/"}"
fi
score=0 score=0
local template_cmd=""
IFS=',' read -ra fields <<< "$conditions" IFS=',' read -ra fields <<< "$conditions"
for field in "${fields[@]}"; do for field in "${fields[@]}"; do
label=${field%%.*} local label=${field%%.*}
value=${field#*.} local value=${field#*.}
[ "$field" = "$label" ] && value="" # when .value is omitted [ "$field" = "$label" ] && value="" # when .value is omitted
# extension isn't a condition and doesn't affect the score
if [[ "$label" =~ ^(e|extension)$ ]]; then local -i delta=-1
continue case "$label" in
fi default)
score=$((score + 1000)) delta=0
# default condition ;;
if [[ "$label" =~ ^(default)$ ]]; then a|arch)
score=$((score + 0)) [ "$value" = "$local_arch" ] && delta=1
# variable conditions ;;
elif [[ "$label" =~ ^(a|arch)$ ]]; then o|os)
if [ "$value" = "$local_arch" ]; then [ "$value" = "$local_system" ] && delta=2
score=$((score + 1)) ;;
else d|distro)
score=0 shopt -s nocasematch
return [[ "${value// /_}" = "${local_distro// /_}" ]] && delta=4
fi shopt -u nocasematch
elif [[ "$label" =~ ^(o|os)$ ]]; then ;;
if [ "$value" = "$local_system" ]; then f|distro_family)
score=$((score + 2)) shopt -s nocasematch
else [[ "${value// /_}" = "${local_distro_family// /_}" ]] && delta=8
score=0 shopt -u nocasematch
return ;;
fi c|class)
elif [[ "$label" =~ ^(d|distro)$ ]]; then in_list "$value" "${local_classes[@]}" && delta=16
if [ "${value/\ /_}" = "${local_distro/\ /_}" ]; then ;;
score=$((score + 4)) h|hostname)
else [ "$value" = "$local_host" ] && delta=32
score=0 ;;
return u|user)
fi [ "$value" = "$local_user" ] && delta=64
elif [[ "$label" =~ ^(f|distro_family)$ ]]; then ;;
if [ "${value/\ /_}" = "${local_distro_family/\ /_}" ]; then e|extension)
score=$((score + 8)) # extension isn't a condition and doesn't affect the score
else continue
score=0 ;;
return t|template|yadm)
fi if [ -d "$source" ]; then
elif [[ "$label" =~ ^(c|class)$ ]]; then INVALID_ALT+=("$source")
if in_list "$value" "${local_classes[@]}"; then else
score=$((score + 16)) template_cmd=$(choose_template_cmd "$value")
else if [ -n "$template_cmd" ]; then
score=0 delta=0
return else
fi debug "No supported template processor for template $source"
elif [[ "$label" =~ ^(h|hostname)$ ]]; then [ -n "$loud" ] && echo "No supported template processor for template $source"
if [ "$value" = "$local_host" ]; then fi
score=$((score + 32)) fi
else ;;
score=0 *)
return INVALID_ALT+=("$source")
fi ;;
elif [[ "$label" =~ ^(u|user)$ ]]; then esac
if [ "$value" = "$local_user" ]; then
score=$((score + 64)) if (( delta < 0 )); then
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
score=0 score=0
return return
fi fi
score=$(( score + 1000 + delta ))
done done
record_score "$score" "$tgt" "$src" record_score "$score" "$target" "$source" "$template_cmd"
} }
function record_score() { function record_score() {
score="$1" local score="$1"
tgt="$2" local target="$2"
src="$3" local source="$3"
local template_cmd="$4"
# record nothing if the score is zero # 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 # search for the index of this target, to see if we already are tracking it
index=-1 local -i index=$((${#alt_targets[@]} - 1))
for search_index in "${!alt_targets[@]}"; do for (( ; index >= 0; --index )); do
if [ "${alt_targets[$search_index]}" = "$tgt" ]; then if [ "${alt_targets[$index]}" = "$target" ]; then
index="$search_index" break
break
fi fi
done 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 # $YADM_CONFIG must be processed first, in case other templates lookup yadm configurations
if [ "$tgt" = "$YADM_CONFIG" ]; then if [ "$target" = "$YADM_CONFIG" ]; then
alt_targets=("$tgt" "${alt_targets[@]}") alt_targets=("$target" "${alt_targets[@]}")
alt_sources=("$src" "${alt_sources[@]}")
alt_scores=(0 "${alt_scores[@]}") alt_sources=("$source" "${alt_sources[@]}")
index=0 alt_scores=("$score" "${alt_scores[@]}")
# increase the index of any existing alt_template_cmds alt_template_cmds=("$template_cmd" "${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
else else
alt_targets+=("$tgt") alt_targets+=("$target")
# 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
# record nothing if a template command is registered for this file alt_sources+=("$source")
[ "${alt_template_cmds[$index]+isset}" ] && return alt_scores+=("$score")
alt_template_cmds+=("$template_cmd")
# 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
fi fi
done return
# 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
fi fi
# record the template command, last one wins if [[ -n "${alt_template_cmds[$index]}" ]]; then
alt_template_cmds[index]="$cmd" if [[ -z "$template_cmd" || "$score" -lt "${alt_scores[$index]}" ]]; then
alt_sources[index]="$src" # 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() { function choose_template_cmd() {
kind="$1" local kind="$1"
if [ "$kind" = "default" ] || [ "$kind" = "" ] && awk_available; then if [ "$kind" = "default" ] || [ "$kind" = "" ]; then
echo "template_default" awk_available && echo "template_default"
elif [ "$kind" = "esh" ] && esh_available; then elif [ "$kind" = "esh" ]; then
echo "template_esh" esh_available && echo "template_esh"
elif [ "$kind" = "j2cli" ] || [ "$kind" = "j2" ] && j2cli_available; then elif [ "$kind" = "j2cli" ] || [ "$kind" = "j2" ] && j2cli_available; then
echo "template_j2cli" echo "template_j2cli"
elif [ "$kind" = "envtpl" ] || [ "$kind" = "j2" ] && envtpl_available; then elif [ "$kind" = "envtpl" ] || [ "$kind" = "j2" ] && envtpl_available; then
@ -488,7 +439,7 @@ EOF
-v distro="$local_distro" \ -v distro="$local_distro" \
-v distro_family="$local_distro_family" \ -v distro_family="$local_distro_family" \
-v source="$input" \ -v source="$input" \
-v source_dir="$(dirname "$input")" \ -v source_dir="$(builtin_dirname "$input")" \
"$awk_pgm" \ "$awk_pgm" \
"$input" "${local_classes[@]}" > "$temp_file" || rm -f "$temp_file" "$input" "${local_classes[@]}" > "$temp_file" || rm -f "$temp_file"
@ -599,29 +550,45 @@ function alt() {
# determine all tracked files # determine all tracked files
local tracked_files=() local tracked_files=()
local IFS=$'\n' 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") tracked_files+=("$tracked_file")
done done
# generate data for removing stale links local alt_targets=()
local possible_alts=() local alt_sources=()
local IFS=$'\n' local alt_scores=()
for possible_alt in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do local alt_template_cmds=()
if [[ $possible_alt =~ .\#\#. ]]; then
base_alt="${possible_alt%%##*}" # For removing stale links
yadm_alt="${YADM_BASE}/${base_alt}" local possible_alt_targets=()
if [ "${yadm_alt#"$YADM_ALT/"}" != "${yadm_alt}" ]; then
base_alt="${yadm_alt#"$YADM_ALT/"}" local alt_source
fi for alt_source in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do
possible_alts+=("$YADM_BASE/${base_alt}") local conditions="${alt_source#*##}"
if [ "$alt_source" = "$conditions" ]; then
continue
fi 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 done
local alt_linked=() local alt_linked=()
alt_linking alt_linking
remove_stale_links remove_stale_links
report_invalid_alts report_invalid_alts
} }
function report_invalid_alts() { function report_invalid_alts() {
@ -662,7 +629,7 @@ function remove_stale_links() {
# if a possible alt IS linked, but it's source is not part of alt_linked, # if a possible alt IS linked, but it's source is not part of alt_linked,
# remove it. # remove it.
if readlink_available; then if readlink_available; then
for stale_candidate in "${possible_alts[@]}"; do for stale_candidate in "${possible_alt_targets[@]}"; do
if [ -L "$stale_candidate" ]; then if [ -L "$stale_candidate" ]; then
src=$(readlink "$stale_candidate" 2>/dev/null) src=$(readlink "$stale_candidate" 2>/dev/null)
if [ -n "$src" ]; then if [ -n "$src" ]; then
@ -681,8 +648,8 @@ function set_local_alt_values() {
local -a all_classes local -a all_classes
all_classes=$(config --get-all local.class) all_classes=$(config --get-all local.class)
while IFS='' read -r class; do while IFS='' read -r class; do
local_classes+=("$class") local_classes+=("$class")
local_class="$class" local_class="$class"
done <<< "$all_classes" done <<< "$all_classes"
local_arch="$(config local.arch)" local_arch="$(config local.arch)"
@ -712,50 +679,38 @@ function set_local_alt_values() {
} }
function alt_linking() { 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=() if [[ -L "$target" ]]; then
local alt_targets=() rm -f "$target"
local alt_sources=() elif [[ -d "$target" ]]; then
local alt_template_cmds=() echo "Skipping alt $source as $target is a directory"
continue
else
assert_parent "$target"
fi
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 if [[ -n "$template_cmd" ]]; then
alt_path="$YADM_BASE/$alt_path" debug "Creating $target from template $source"
if [[ "$alt_path" =~ .\#\#. ]]; then [[ -n "$loud" ]] && echo "Creating $target from template $source"
if [ -e "$alt_path" ] ; then
score_file "$alt_path" "$template_cmd" "$source" "$target"
fi elif [[ "$do_copy" -eq 1 ]]; then
debug "Copying $source to $target"
[[ -n "$loud" ]] && echo "Copying $source to $target"
cp -f "$source" "$target"
else
debug "Linking $source to $target"
[[ -n "$loud" ]] && echo "Linking $source to $target"
ln_relative "$source" "$target"
fi fi
done 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() { function ln_relative() {
@ -765,7 +720,7 @@ function ln_relative() {
local rel_source local rel_source
rel_source=$(relative_path "$(builtin_dirname "$target")" "$source") rel_source=$(relative_path "$(builtin_dirname "$target")" "$source")
ln -nfs "$rel_source" "$target" ln -fs "$rel_source" "$target"
alt_linked+=("$rel_source") alt_linked+=("$rel_source")
} }

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. link will be created.
Links are also created for directories named this way, as long as they have at 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 yadm will automatically create these links by default. This can be disabled
using the using the