From b2b0b143d685559a73bd19565a66c2c672d28841 Mon Sep 17 00:00:00 2001 From: Erik Flodin Date: Thu, 28 Nov 2024 23:28:32 +0100 Subject: [PATCH] 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. --- test/test_alt.py | 15 ++ test/test_alt_copy.py | 3 +- test/test_unit_record_score.py | 83 ++++-- test/test_unit_record_template.py | 55 ---- test/test_unit_remove_stale_links.py | 2 +- test/test_unit_score_file.py | 16 +- test/utils.py | 4 +- yadm | 375 ++++++++++++--------------- yadm.1 | 2 +- 9 files changed, 259 insertions(+), 296 deletions(-) delete mode 100644 test/test_unit_record_template.py diff --git a/test/test_alt.py b/test/test_alt.py index 5d6706e..ef421f7 100644 --- a/test/test_alt.py +++ b/test/test_alt.py @@ -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"##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.parametrize("autoalt", [None, "true", "false"]) def test_auto_alt(runner, yadm_cmd, paths, autoalt): diff --git a/test/test_alt_copy.py b/test/test_alt_copy.py index e1beece..de08b8b 100644 --- a/test/test_alt_copy.py +++ b/test/test_alt_copy.py @@ -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 diff --git a/test/test_unit_record_score.py b/test/test_unit_record_score.py index 3d67b0f..9049b1b 100644 --- a/test/test_unit_record_score.py +++ b/test/test_unit_record_score.py @@ -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 diff --git a/test/test_unit_record_template.py b/test/test_unit_record_template.py deleted file mode 100644 index 4f3c3e8..0000000 --- a/test/test_unit_record_template.py +++ /dev/null @@ -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 diff --git a/test/test_unit_remove_stale_links.py b/test/test_unit_remove_stale_links.py index d92c96b..275832d 100644 --- a/test/test_unit_remove_stale_links.py +++ b/test/test_unit_remove_stale_links.py @@ -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 diff --git a/test/test_unit_score_file.py b/test/test_unit_score_file.py index 0d3833e..be4631f 100644 --- a/test/test_unit_score_file.py +++ b/test/test_unit_score_file.py @@ -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" """ @@ -289,15 +289,15 @@ def test_template_recording(runner, yadm, cmd_generated): assert run.out.rstrip() == expected -def test_underscores_in_distro_and_family(runner, yadm): - """Test replacing spaces in distro / distro_family with underscores""" +def test_underscores_and_upper_case_in_distro_and_family(runner, yadm): + """Test replacing spaces with underscores and lowering case in distro / distro_family""" local_distro = "test distro" local_distro_family = "test family" filenames = { - "filename##distro.test distro": 1004, + "filename##distro.Test Distro": 1004, "filename##distro.test-distro": 0, "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": 1008, } diff --git a/test/utils.py b/test/utils.py index c36ecac..7e6a36d 100644 --- a/test/utils.py +++ b/test/utils.py @@ -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) diff --git a/yadm b/yadm index 82be8b8..51ed02e 100755 --- a/yadm +++ b/yadm @@ -166,188 +166,139 @@ 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) + shopt -s nocasematch + [[ "${value// /_}" = "${local_distro// /_}" ]] && delta=4 + shopt -u nocasematch + ;; + f|distro_family) + shopt -s nocasematch + [[ "${value// /_}" = "${local_distro_family// /_}" ]] && delta=8 + shopt -u nocasematch + ;; + 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 @@ -488,7 +439,7 @@ EOF -v distro="$local_distro" \ -v distro_family="$local_distro_family" \ -v source="$input" \ - -v source_dir="$(dirname "$input")" \ + -v source_dir="$(builtin_dirname "$input")" \ "$awk_pgm" \ "$input" "${local_classes[@]}" > "$temp_file" || rm -f "$temp_file" @@ -599,29 +550,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 +629,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 +648,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 +679,38 @@ 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=() + if [[ -L "$target" ]]; then + rm -f "$target" + elif [[ -d "$target" ]]; then + 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 - 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" + + "$template_cmd" "$source" "$target" + 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 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() { @@ -765,7 +720,7 @@ function ln_relative() { local rel_source rel_source=$(relative_path "$(builtin_dirname "$target")" "$source") - ln -nfs "$rel_source" "$target" + ln -fs "$rel_source" "$target" alt_linked+=("$rel_source") } diff --git a/yadm.1 b/yadm.1 index 5dd08fd..b430c4f 100644 --- a/yadm.1 +++ b/yadm.1 @@ -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