mirror of
https://github.com/TheLocehiliosan/yadm
synced 2025-06-07 01:53:59 +00:00
Change handling of dirs with alt conditions
Instead of creating symlinks pointing at the directory, create individual symlinks for each file within the dir alternate (fixes #490). Also rework how stale symlinks are removed. Now a (stale) symlink will only be removed if it's pointing at a file that's an altnerate file (fixes #236).
This commit is contained in:
parent
7f76a455bb
commit
4214de8d91
150
test/test_alt.py
150
test/test_alt.py
@ -40,15 +40,15 @@ def test_alt_source(runner, paths, tracked, encrypt, exclude, yadm_alt):
|
|||||||
source_file_content = link_path + "##default"
|
source_file_content = link_path + "##default"
|
||||||
source_file = basepath.join(source_file_content)
|
source_file = basepath.join(source_file_content)
|
||||||
link_file = paths.work.join(link_path)
|
link_file = paths.work.join(link_path)
|
||||||
|
if link_path == utils.ALT_DIR:
|
||||||
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
if tracked or (encrypt and not exclude):
|
if tracked or (encrypt and not exclude):
|
||||||
assert link_file.islink()
|
assert link_file.islink()
|
||||||
target = py.path.local(os.path.realpath(link_file))
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
if target.isfile():
|
assert target.isfile()
|
||||||
assert link_file.read() == source_file_content
|
assert link_file.read() == source_file_content
|
||||||
assert str(source_file) in linked
|
assert str(source_file) in linked
|
||||||
else:
|
|
||||||
assert link_file.join(utils.CONTAINED).read() == source_file_content
|
|
||||||
assert str(source_file) in linked
|
|
||||||
else:
|
else:
|
||||||
assert not link_file.exists()
|
assert not link_file.exists()
|
||||||
assert str(source_file) not in linked
|
assert str(source_file) not in linked
|
||||||
@ -73,6 +73,9 @@ def test_relative_link(runner, paths, yadm_alt):
|
|||||||
source_file_content = link_path + "##default"
|
source_file_content = link_path + "##default"
|
||||||
source_file = basepath.join(source_file_content)
|
source_file = basepath.join(source_file_content)
|
||||||
link_file = paths.work.join(link_path)
|
link_file = paths.work.join(link_path)
|
||||||
|
if link_path == utils.ALT_DIR:
|
||||||
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
link = link_file.readlink()
|
link = link_file.readlink()
|
||||||
relpath = os.path.relpath(source_file, start=os.path.dirname(link_file))
|
relpath = os.path.relpath(source_file, start=os.path.dirname(link_file))
|
||||||
assert link == relpath
|
assert link == relpath
|
||||||
@ -128,15 +131,17 @@ def test_alt_conditions(runner, paths, tst_arch, tst_sys, tst_distro, tst_distro
|
|||||||
linked = utils.parse_alt_output(run.out)
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
for link_path in TEST_PATHS:
|
for link_path in TEST_PATHS:
|
||||||
source_file = link_path + suffix
|
source_file_content = link_path + suffix
|
||||||
assert paths.work.join(link_path).islink()
|
source_file = paths.work.join(source_file_content)
|
||||||
target = py.path.local(os.path.realpath(paths.work.join(link_path)))
|
link_file = paths.work.join(link_path)
|
||||||
if target.isfile():
|
if link_path == utils.ALT_DIR:
|
||||||
assert paths.work.join(link_path).read() == source_file
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
assert str(paths.work.join(source_file)) in linked
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
else:
|
assert link_file.islink()
|
||||||
assert paths.work.join(link_path).join(utils.CONTAINED).read() == source_file
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
assert str(paths.work.join(source_file)) in linked
|
assert target.isfile()
|
||||||
|
assert link_file.read() == source_file_content
|
||||||
|
assert str(source_file) in linked
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ds1_copy")
|
@pytest.mark.usefixtures("ds1_copy")
|
||||||
@ -156,6 +161,7 @@ def test_alt_templates(runner, paths, kind, label):
|
|||||||
suffix = f"##{label}.{kind}"
|
suffix = f"##{label}.{kind}"
|
||||||
if kind is None:
|
if kind is None:
|
||||||
suffix = f"##{label}"
|
suffix = f"##{label}"
|
||||||
|
|
||||||
utils.create_alt_files(paths, suffix)
|
utils.create_alt_files(paths, suffix)
|
||||||
run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"])
|
run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"])
|
||||||
assert run.success
|
assert run.success
|
||||||
@ -163,11 +169,15 @@ def test_alt_templates(runner, paths, kind, label):
|
|||||||
created = utils.parse_alt_output(run.out, linked=False)
|
created = utils.parse_alt_output(run.out, linked=False)
|
||||||
|
|
||||||
for created_path in TEST_PATHS:
|
for created_path in TEST_PATHS:
|
||||||
if created_path != utils.ALT_DIR:
|
source_file_content = created_path + suffix
|
||||||
source_file = created_path + suffix
|
source_file = paths.work.join(source_file_content)
|
||||||
assert paths.work.join(created_path).isfile()
|
created_file = paths.work.join(created_path)
|
||||||
assert paths.work.join(created_path).read().strip() == source_file
|
if created_path == utils.ALT_DIR:
|
||||||
assert str(paths.work.join(source_file)) in created
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
created_file = created_file.join(utils.CONTAINED)
|
||||||
|
assert created_file.isfile()
|
||||||
|
assert created_file.read().strip() == source_file_content
|
||||||
|
assert str(source_file) in created
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ds1_copy")
|
@pytest.mark.usefixtures("ds1_copy")
|
||||||
@ -201,20 +211,22 @@ def test_auto_alt(runner, yadm_cmd, paths, autoalt):
|
|||||||
linked = utils.parse_alt_output(run.out)
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
for link_path in TEST_PATHS:
|
for link_path in TEST_PATHS:
|
||||||
source_file = link_path + "##default"
|
source_file_content = link_path + "##default"
|
||||||
|
source_file = paths.work.join(source_file_content)
|
||||||
|
link_file = paths.work.join(link_path)
|
||||||
|
if link_path == utils.ALT_DIR:
|
||||||
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
|
|
||||||
if autoalt == "false":
|
if autoalt == "false":
|
||||||
assert not paths.work.join(link_path).exists()
|
assert not link_file.exists()
|
||||||
else:
|
else:
|
||||||
assert paths.work.join(link_path).islink()
|
assert link_file.islink()
|
||||||
target = py.path.local(os.path.realpath(paths.work.join(link_path)))
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
if target.isfile():
|
assert target.isfile()
|
||||||
assert paths.work.join(link_path).read() == source_file
|
assert link_file.read() == source_file_content
|
||||||
# no linking output when run via auto-alt
|
# no linking output when run via auto-alt
|
||||||
assert str(paths.work.join(source_file)) not in linked
|
assert str(source_file) not in linked
|
||||||
else:
|
|
||||||
assert paths.work.join(link_path).join(utils.CONTAINED).read() == source_file
|
|
||||||
# no linking output when run via auto-alt
|
|
||||||
assert str(paths.work.join(source_file)) not in linked
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ds1_copy")
|
@pytest.mark.usefixtures("ds1_copy")
|
||||||
@ -236,6 +248,8 @@ def test_alt_exclude(runner, yadm_cmd, paths, autoexclude):
|
|||||||
status = run.out.split("\0")
|
status = run.out.split("\0")
|
||||||
|
|
||||||
for link_path in TEST_PATHS:
|
for link_path in TEST_PATHS:
|
||||||
|
if link_path == utils.ALT_DIR:
|
||||||
|
link_path = f"{link_path}/{utils.CONTAINED}"
|
||||||
flags = "??" if autoexclude == "false" else "!!"
|
flags = "??" if autoexclude == "false" else "!!"
|
||||||
assert f"{flags} {link_path}" in status
|
assert f"{flags} {link_path}" in status
|
||||||
|
|
||||||
@ -262,16 +276,18 @@ def test_stale_link_removal(runner, yadm_cmd, paths):
|
|||||||
linked = utils.parse_alt_output(run.out)
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
# assert the proper linking has occurred
|
# assert the proper linking has occurred
|
||||||
for stale_path in TEST_PATHS:
|
for link_path in TEST_PATHS:
|
||||||
source_file = stale_path + "##class." + tst_class
|
source_file_content = link_path + f"##class.{tst_class}"
|
||||||
assert paths.work.join(stale_path).islink()
|
source_file = paths.work.join(source_file_content)
|
||||||
target = py.path.local(os.path.realpath(paths.work.join(stale_path)))
|
link_file = paths.work.join(link_path)
|
||||||
if target.isfile():
|
if link_path == utils.ALT_DIR:
|
||||||
assert paths.work.join(stale_path).read() == source_file
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
assert str(paths.work.join(source_file)) in linked
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
else:
|
assert link_file.islink()
|
||||||
assert paths.work.join(stale_path).join(utils.CONTAINED).read() == source_file
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
assert str(paths.work.join(source_file)) in linked
|
assert target.isfile()
|
||||||
|
assert link_file.read() == source_file_content
|
||||||
|
assert str(source_file) in linked
|
||||||
|
|
||||||
# change the class so there are no valid alternates
|
# change the class so there are no valid alternates
|
||||||
utils.set_local(paths, "class", "changedclass")
|
utils.set_local(paths, "class", "changedclass")
|
||||||
@ -284,9 +300,53 @@ def test_stale_link_removal(runner, yadm_cmd, paths):
|
|||||||
|
|
||||||
# assert the linking is removed
|
# assert the linking is removed
|
||||||
for stale_path in TEST_PATHS:
|
for stale_path in TEST_PATHS:
|
||||||
source_file = stale_path + "##class." + tst_class
|
source_file_content = stale_path + f"##class.{tst_class}"
|
||||||
assert not paths.work.join(stale_path).exists()
|
source_file = paths.work.join(source_file_content)
|
||||||
assert str(paths.work.join(source_file)) not in linked
|
stale_file = paths.work.join(stale_path)
|
||||||
|
if stale_path == utils.ALT_DIR:
|
||||||
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
stale_file = stale_file.join(utils.CONTAINED)
|
||||||
|
assert not stale_file.exists()
|
||||||
|
assert str(source_file) not in linked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("ds1_copy")
|
||||||
|
def test_legacy_dir_link_removal(runner, yadm_cmd, paths):
|
||||||
|
"""Legacy link to alternative dir is removed
|
||||||
|
|
||||||
|
This test ensures that a legacy dir alternative (i.e. symlink to the dir
|
||||||
|
itself) is converted to indiividual links.
|
||||||
|
"""
|
||||||
|
|
||||||
|
utils.create_alt_files(paths, "##default")
|
||||||
|
|
||||||
|
# Create legacy link
|
||||||
|
link_dir = paths.work.join(utils.ALT_DIR)
|
||||||
|
link_dir.mksymlinkto(link_dir.basename + "##default")
|
||||||
|
assert link_dir.islink()
|
||||||
|
|
||||||
|
# run alt to trigger linking
|
||||||
|
run = runner(yadm_cmd("alt"))
|
||||||
|
assert run.success
|
||||||
|
assert run.err == ""
|
||||||
|
linked = utils.parse_alt_output(run.out)
|
||||||
|
|
||||||
|
# assert legacy link is removed
|
||||||
|
assert not link_dir.islink()
|
||||||
|
|
||||||
|
# assert the proper linking has occurred
|
||||||
|
for link_path in TEST_PATHS:
|
||||||
|
source_file_content = link_path + "##default"
|
||||||
|
source_file = paths.work.join(source_file_content)
|
||||||
|
link_file = paths.work.join(link_path)
|
||||||
|
if link_path == utils.ALT_DIR:
|
||||||
|
source_file = source_file.join(utils.CONTAINED)
|
||||||
|
link_file = link_file.join(utils.CONTAINED)
|
||||||
|
assert link_file.islink()
|
||||||
|
target = py.path.local(os.path.realpath(link_file))
|
||||||
|
assert target.isfile()
|
||||||
|
assert link_file.read() == source_file_content
|
||||||
|
assert str(source_file) in linked
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ds1_repo_copy")
|
@pytest.mark.usefixtures("ds1_repo_copy")
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
"""Unit tests: remove_stale_links"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("linked", [True, False])
|
|
||||||
@pytest.mark.parametrize("kind", ["file", "symlink"])
|
|
||||||
def test_remove_stale_links(runner, yadm, tmpdir, kind, linked):
|
|
||||||
"""Test remove_stale_links()"""
|
|
||||||
|
|
||||||
source_file = tmpdir.join("source_file")
|
|
||||||
source_file.write("source file", ensure=True)
|
|
||||||
link = tmpdir.join("link")
|
|
||||||
|
|
||||||
if kind == "file":
|
|
||||||
link.write("link file", ensure=True)
|
|
||||||
else:
|
|
||||||
os.system(f"ln -s {source_file} {link}")
|
|
||||||
|
|
||||||
alt_linked = ""
|
|
||||||
if linked:
|
|
||||||
alt_linked = source_file
|
|
||||||
|
|
||||||
script = f"""
|
|
||||||
YADM_TEST=1 source {yadm}
|
|
||||||
possible_alt_targets=({link})
|
|
||||||
alt_linked=({alt_linked})
|
|
||||||
function rm() {{ echo rm "$@"; }}
|
|
||||||
remove_stale_links
|
|
||||||
"""
|
|
||||||
|
|
||||||
run = runner(command=["bash"], inp=script)
|
|
||||||
assert run.err == ""
|
|
||||||
if kind == "symlink" and not linked:
|
|
||||||
assert f"rm -f {link}" in run.out
|
|
||||||
else:
|
|
||||||
assert run.out == ""
|
|
@ -39,13 +39,11 @@ CONDITION = {
|
|||||||
TEMPLATE_LABELS = ["t", "template", "yadm"]
|
TEMPLATE_LABELS = ["t", "template", "yadm"]
|
||||||
|
|
||||||
|
|
||||||
def calculate_score(filename):
|
def calculate_score(conditions):
|
||||||
"""Calculate the expected score"""
|
"""Calculate the expected score"""
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
score = 0
|
score = 0
|
||||||
|
|
||||||
_, conditions = filename.split("##", 1)
|
|
||||||
|
|
||||||
for condition in conditions.split(","):
|
for condition in conditions.split(","):
|
||||||
label = condition
|
label = condition
|
||||||
value = None
|
value = None
|
||||||
@ -111,70 +109,70 @@ def test_score_values(runner, yadm, default, arch, system, distro, cla, host, us
|
|||||||
local_distro = "testDISTro"
|
local_distro = "testDISTro"
|
||||||
local_host = "testHost"
|
local_host = "testHost"
|
||||||
local_user = "testUser"
|
local_user = "testUser"
|
||||||
filenames = {"filename##": 0}
|
conditions = {"": 0}
|
||||||
|
|
||||||
if default:
|
if default:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for label in CONDITION[default]["labels"]:
|
for label in CONDITION[default]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += label
|
newcond += label
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if arch:
|
if arch:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[arch]["labels"]:
|
for label in CONDITION[arch]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_arch if match else "badarch"])
|
newcond += ".".join([label, local_arch if match else "badarch"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if system:
|
if system:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[system]["labels"]:
|
for label in CONDITION[system]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_system if match else "badsys"])
|
newcond += ".".join([label, local_system if match else "badsys"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if distro:
|
if distro:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[distro]["labels"]:
|
for label in CONDITION[distro]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_distro if match else "baddistro"])
|
newcond += ".".join([label, local_distro if match else "baddistro"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if cla:
|
if cla:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[cla]["labels"]:
|
for label in CONDITION[cla]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_class if match else "badclass"])
|
newcond += ".".join([label, local_class if match else "badclass"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if host:
|
if host:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[host]["labels"]:
|
for label in CONDITION[host]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_host if match else "badhost"])
|
newcond += ".".join([label, local_host if match else "badhost"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
if user:
|
if user:
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for match in [True, False]:
|
for match in [True, False]:
|
||||||
for label in CONDITION[user]["labels"]:
|
for label in CONDITION[user]["labels"]:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, local_user if match else "baduser"])
|
newcond += ".".join([label, local_user if match else "baduser"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
|
|
||||||
script = f"""
|
script = f"""
|
||||||
YADM_TEST=1 source {yadm}
|
YADM_TEST=1 source {yadm}
|
||||||
@ -187,15 +185,15 @@ def test_score_values(runner, yadm, default, arch, system, distro, cla, host, us
|
|||||||
local_host={local_host}
|
local_host={local_host}
|
||||||
local_user={local_user}
|
local_user={local_user}
|
||||||
"""
|
"""
|
||||||
expected = ""
|
expected = []
|
||||||
for filename, score in filenames.items():
|
for condition, score in conditions.items():
|
||||||
script += f"""
|
script += f"""
|
||||||
score_file "{filename}" "dest"
|
score_file "source" "target" "{condition}"
|
||||||
echo "{filename}"
|
echo "{condition}=$score"
|
||||||
echo "$score"
|
|
||||||
"""
|
"""
|
||||||
expected += filename + "\n"
|
expected.append(f"{condition}={score}")
|
||||||
expected += str(score) + "\n"
|
expected.append("")
|
||||||
|
expected = "\n".join(expected)
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
assert run.success
|
assert run.success
|
||||||
assert run.err == ""
|
assert run.err == ""
|
||||||
@ -206,15 +204,15 @@ def test_score_values(runner, yadm, default, arch, system, distro, cla, host, us
|
|||||||
def test_extensions(runner, yadm, ext):
|
def test_extensions(runner, yadm, ext):
|
||||||
"""Verify extensions do not effect scores"""
|
"""Verify extensions do not effect scores"""
|
||||||
local_user = "testuser"
|
local_user = "testuser"
|
||||||
filename = f"filename##u.{local_user}"
|
condition = f"u.{local_user}"
|
||||||
if ext:
|
if ext:
|
||||||
filename += f",{ext}.xyz"
|
condition += f",{ext}.xyz"
|
||||||
expected = ""
|
expected = ""
|
||||||
script = f"""
|
script = f"""
|
||||||
YADM_TEST=1 source {yadm}
|
YADM_TEST=1 source {yadm}
|
||||||
score=0
|
score=0
|
||||||
local_user={local_user}
|
local_user={local_user}
|
||||||
score_file "{filename}"
|
score_file "source" "target" "{condition}"
|
||||||
echo "$score"
|
echo "$score"
|
||||||
"""
|
"""
|
||||||
expected = f'{1000 + CONDITION["user"]["modifier"]}\n'
|
expected = f'{1000 + CONDITION["user"]["modifier"]}\n'
|
||||||
@ -232,15 +230,15 @@ def test_score_values_templates(runner, yadm):
|
|||||||
local_distro = "testdistro"
|
local_distro = "testdistro"
|
||||||
local_host = "testhost"
|
local_host = "testhost"
|
||||||
local_user = "testuser"
|
local_user = "testuser"
|
||||||
filenames = {"filename##": 0}
|
conditions = {"": 0}
|
||||||
|
|
||||||
for filename in list(filenames):
|
for condition in list(conditions):
|
||||||
for label in TEMPLATE_LABELS:
|
for label in TEMPLATE_LABELS:
|
||||||
newfile = filename
|
newcond = condition
|
||||||
if not newfile.endswith("##"):
|
if newcond:
|
||||||
newfile += ","
|
newcond += ","
|
||||||
newfile += ".".join([label, "testtemplate"])
|
newcond += ".".join([label, "testtemplate"])
|
||||||
filenames[newfile] = calculate_score(newfile)
|
conditions[newcond] = calculate_score(newcond)
|
||||||
|
|
||||||
script = f"""
|
script = f"""
|
||||||
YADM_TEST=1 source {yadm}
|
YADM_TEST=1 source {yadm}
|
||||||
@ -252,15 +250,15 @@ def test_score_values_templates(runner, yadm):
|
|||||||
local_host={local_host}
|
local_host={local_host}
|
||||||
local_user={local_user}
|
local_user={local_user}
|
||||||
"""
|
"""
|
||||||
expected = ""
|
expected = []
|
||||||
for filename, score in filenames.items():
|
for condition, score in conditions.items():
|
||||||
script += f"""
|
script += f"""
|
||||||
score_file "{filename}" "dest"
|
score_file "source" "target" "{condition}"
|
||||||
echo "{filename}"
|
echo "{condition}=$score"
|
||||||
echo "$score"
|
|
||||||
"""
|
"""
|
||||||
expected += filename + "\n"
|
expected.append(f"{condition}={score}")
|
||||||
expected += str(score) + "\n"
|
expected.append("")
|
||||||
|
expected = "\n".join(expected)
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
assert run.success
|
assert run.success
|
||||||
assert run.err == ""
|
assert run.err == ""
|
||||||
@ -281,7 +279,7 @@ def test_template_recording(runner, yadm, processor_generated):
|
|||||||
YADM_TEST=1 source {yadm}
|
YADM_TEST=1 source {yadm}
|
||||||
function record_score() {{ [ -n "$4" ] && echo "template recorded"; }}
|
function record_score() {{ [ -n "$4" ] && echo "template recorded"; }}
|
||||||
{mock}
|
{mock}
|
||||||
score_file "testfile##template.kind"
|
score_file "source" "target" "template.kind"
|
||||||
"""
|
"""
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
assert run.success
|
assert run.success
|
||||||
@ -293,13 +291,13 @@ def test_underscores_and_upper_case_in_distro_and_family(runner, yadm):
|
|||||||
"""Test replacing spaces with underscores and lowering case in distro / distro_family"""
|
"""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 = {
|
conditions = {
|
||||||
"filename##distro.Test Distro": 1004,
|
"distro.Test Distro": 1004,
|
||||||
"filename##distro.test-distro": 0,
|
"distro.test-distro": 0,
|
||||||
"filename##distro.test_distro": 1004,
|
"distro.test_distro": 1004,
|
||||||
"filename##distro_family.test FAMILY": 1008,
|
"distro_family.test FAMILY": 1008,
|
||||||
"filename##distro_family.test-family": 0,
|
"distro_family.test-family": 0,
|
||||||
"filename##distro_family.test_family": 1008,
|
"distro_family.test_family": 1008,
|
||||||
}
|
}
|
||||||
|
|
||||||
script = f"""
|
script = f"""
|
||||||
@ -308,15 +306,15 @@ def test_underscores_and_upper_case_in_distro_and_family(runner, yadm):
|
|||||||
local_distro="{local_distro}"
|
local_distro="{local_distro}"
|
||||||
local_distro_family="{local_distro_family}"
|
local_distro_family="{local_distro_family}"
|
||||||
"""
|
"""
|
||||||
expected = ""
|
expected = []
|
||||||
for filename, score in filenames.items():
|
for condition, score in conditions.items():
|
||||||
script += f"""
|
script += f"""
|
||||||
score_file "{filename}"
|
score_file "source" "target" "{condition}"
|
||||||
echo "{filename}"
|
echo "{condition}=$score"
|
||||||
echo "$score"
|
|
||||||
"""
|
"""
|
||||||
expected += filename + "\n"
|
expected.append(f"{condition}={score}")
|
||||||
expected += str(score) + "\n"
|
expected.append("")
|
||||||
|
expected = "\n".join(expected)
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
assert run.success
|
assert run.success
|
||||||
assert run.err == ""
|
assert run.err == ""
|
||||||
@ -332,17 +330,17 @@ def test_negative_class_condition(runner, yadm):
|
|||||||
|
|
||||||
# 0
|
# 0
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##~class.testclass" "dest"
|
score_file "source" "target" "~class.testclass"
|
||||||
echo "score: $score"
|
echo "score: $score"
|
||||||
|
|
||||||
# 16
|
# 16
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##~class.badclass" "dest"
|
score_file "source" "target" "~class.badclass"
|
||||||
echo "score2: $score"
|
echo "score2: $score"
|
||||||
|
|
||||||
# 16
|
# 16
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##~c.badclass" "dest"
|
score_file "source" "target" "~c.badclass"
|
||||||
echo "score3: $score"
|
echo "score3: $score"
|
||||||
"""
|
"""
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
@ -363,27 +361,27 @@ def test_negative_combined_conditions(runner, yadm):
|
|||||||
|
|
||||||
# (0) + (0) = 0
|
# (0) + (0) = 0
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##~class.testclass,~distro.testdistro" "dest"
|
score_file "source" "target" "~class.testclass,~distro.testdistro"
|
||||||
echo "score: $score"
|
echo "score: $score"
|
||||||
|
|
||||||
# (1000 + 16) + (1000 + 4) = 2020
|
# (1000 + 16) + (1000 + 4) = 2020
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##class.testclass,distro.testdistro" "dest"
|
score_file "source" "target" "class.testclass,distro.testdistro"
|
||||||
echo "score2: $score"
|
echo "score2: $score"
|
||||||
|
|
||||||
# 0 (negated class condition)
|
# 0 (negated class condition)
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##~class.badclass,~distro.testdistro" "dest"
|
score_file "source" "target" "~class.badclass,~distro.testdistro"
|
||||||
echo "score3: $score"
|
echo "score3: $score"
|
||||||
|
|
||||||
# (1000 + 16) + (4) = 1020
|
# (1000 + 16) + (4) = 1020
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##class.testclass,~distro.baddistro" "dest"
|
score_file "source" "target" "class.testclass,~distro.baddistro"
|
||||||
echo "score4: $score"
|
echo "score4: $score"
|
||||||
|
|
||||||
# (1000 + 16) + (16) = 1032
|
# (1000 + 16) + (16) = 1032
|
||||||
score=0
|
score=0
|
||||||
score_file "filename##class.testclass,~class.badclass" "dest"
|
score_file "source" "target" "class.testclass,~class.badclass"
|
||||||
echo "score5: $score"
|
echo "score5: $score"
|
||||||
"""
|
"""
|
||||||
run = runner(command=["bash"], inp=script)
|
run = runner(command=["bash"], inp=script)
|
||||||
|
@ -61,12 +61,8 @@ def create_alt_files(
|
|||||||
new_dir = basepath.join(ALT_DIR + suffix).join(CONTAINED)
|
new_dir = basepath.join(ALT_DIR + suffix).join(CONTAINED)
|
||||||
new_dir.write(ALT_DIR + suffix, ensure=True)
|
new_dir.write(ALT_DIR + suffix, ensure=True)
|
||||||
|
|
||||||
# Do not test directory support for jinja alternates
|
test_paths = [new_file1, new_file2, new_dir]
|
||||||
test_paths = [new_file1, new_file2]
|
test_names = [ALT_FILE1, ALT_FILE2, ALT_DIR]
|
||||||
test_names = [ALT_FILE1, ALT_FILE2]
|
|
||||||
if not re.match(r"##(t$|t\.|template|yadm)", suffix):
|
|
||||||
test_paths += [new_dir]
|
|
||||||
test_names += [ALT_DIR]
|
|
||||||
|
|
||||||
for test_path in test_paths:
|
for test_path in test_paths:
|
||||||
if content:
|
if content:
|
||||||
|
84
yadm
84
yadm
@ -169,7 +169,7 @@ function main() {
|
|||||||
function score_file() {
|
function score_file() {
|
||||||
local source="$1"
|
local source="$1"
|
||||||
local target="$2"
|
local target="$2"
|
||||||
local conditions="${source#*##}"
|
local conditions="$3"
|
||||||
|
|
||||||
score=0
|
score=0
|
||||||
local template_processor=""
|
local template_processor=""
|
||||||
@ -223,7 +223,7 @@ function score_file() {
|
|||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
t | template | yadm)
|
t | template | yadm)
|
||||||
if [ -d "$source" ] || ((negate)); then
|
if ((negate)); then
|
||||||
INVALID_ALT+=("$source")
|
INVALID_ALT+=("$source")
|
||||||
else
|
else
|
||||||
template_processor=$(choose_template_processor "$value")
|
template_processor=$(choose_template_processor "$value")
|
||||||
@ -578,42 +578,50 @@ function alt() {
|
|||||||
local alt_scores=()
|
local alt_scores=()
|
||||||
local alt_template_processors=()
|
local alt_template_processors=()
|
||||||
|
|
||||||
# For removing stale links
|
local filename
|
||||||
local possible_alt_targets=()
|
for filename in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do
|
||||||
|
local suffix="${filename#*##}"
|
||||||
local alt_source
|
if [ "$filename" = "$suffix" ]; then
|
||||||
for alt_source in "${tracked_files[@]}" "${ENCRYPT_INCLUDE_FILES[@]}"; do
|
|
||||||
local conditions="${alt_source#*##}"
|
|
||||||
if [ "$alt_source" = "$conditions" ]; then
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
local conditions="${suffix%%/*}"
|
||||||
|
suffix="${suffix:${#conditions}}"
|
||||||
|
|
||||||
local target_base="${alt_source%%##*}"
|
local target="${YADM_BASE}/${filename%%##*}"
|
||||||
alt_source="${YADM_BASE}/${target_base}##${conditions%%/*}"
|
if [ "${target#"$YADM_ALT/"}" != "$target" ]; then
|
||||||
local alt_target="${YADM_BASE}/${target_base}"
|
target="${YADM_BASE}/${target#"$YADM_ALT/"}"
|
||||||
if [ "${alt_target#"$YADM_ALT/"}" != "$alt_target" ]; then
|
|
||||||
target_base="${alt_target#"$YADM_ALT/"}"
|
|
||||||
fi
|
fi
|
||||||
alt_target="${YADM_BASE}/${target_base}"
|
local source="${YADM_BASE}/${filename}"
|
||||||
|
|
||||||
if ! in_list "$alt_target" "${possible_alt_targets[@]}"; then
|
# If conditions are given on a directory we check if this alt, without the
|
||||||
possible_alt_targets+=("$alt_target")
|
# filename part, has a target that's a symlink pointing at this source
|
||||||
|
# (which was the legacy behavior for yadm) and if so remove this target.
|
||||||
|
if [ -n "$suffix" ]; then
|
||||||
|
if [ -L "$target" ] && [ "$target" -ef "${YADM_BASE}/${filename%"$suffix"}" ]; then
|
||||||
|
rm -f "$target"
|
||||||
|
fi
|
||||||
|
target="$target$suffix"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
score_file "$alt_source" "$alt_target"
|
# Remove target if it's a symlink pointing at source
|
||||||
|
if [ -L "$target" ] && [ "$target" -ef "$source" ]; then
|
||||||
|
rm -f "$target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
score_file "$source" "$target" "$conditions"
|
||||||
done
|
done
|
||||||
|
|
||||||
local alt_linked=()
|
local alt_linked=()
|
||||||
|
|
||||||
alt_linking
|
alt_linking
|
||||||
remove_stale_links
|
|
||||||
report_invalid_alts
|
report_invalid_alts
|
||||||
}
|
}
|
||||||
|
|
||||||
function report_invalid_alts() {
|
function report_invalid_alts() {
|
||||||
[ "$LEGACY_WARNING_ISSUED" = "1" ] && return
|
[ "$LEGACY_WARNING_ISSUED" = "1" ] && return
|
||||||
[ "${#INVALID_ALT[@]}" = "0" ] && return
|
[ "${#INVALID_ALT[@]}" = "0" ] && return
|
||||||
local path_list
|
local path_list=""
|
||||||
|
local invalid
|
||||||
for invalid in "${INVALID_ALT[@]}"; do
|
for invalid in "${INVALID_ALT[@]}"; do
|
||||||
path_list="$path_list * $invalid"$'\n'
|
path_list="$path_list * $invalid"$'\n'
|
||||||
done
|
done
|
||||||
@ -643,25 +651,6 @@ EOF
|
|||||||
printf '%s\n' "$msg" >&2
|
printf '%s\n' "$msg" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove_stale_links() {
|
|
||||||
# review alternate candidates for 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_alt_targets[@]}"; do
|
|
||||||
if [ -L "$stale_candidate" ]; then
|
|
||||||
src=$(readlink "$stale_candidate" 2>/dev/null)
|
|
||||||
if [ -n "$src" ]; then
|
|
||||||
for review_link in "${alt_linked[@]}"; do
|
|
||||||
[ "$src" = "$review_link" ] && continue 2
|
|
||||||
done
|
|
||||||
rm -f "$stale_candidate"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_local_alt_values() {
|
function set_local_alt_values() {
|
||||||
|
|
||||||
local -a all_classes
|
local -a all_classes
|
||||||
@ -716,20 +705,23 @@ function alt_linking() {
|
|||||||
local source="${alt_sources[$index]}"
|
local source="${alt_sources[$index]}"
|
||||||
local template_processor="${alt_template_processors[$index]}"
|
local template_processor="${alt_template_processors[$index]}"
|
||||||
|
|
||||||
if [[ -L "$target" ]]; then
|
if [ -L "$target" ]; then
|
||||||
rm -f "$target"
|
rm -f "$target"
|
||||||
elif [[ -d "$target" ]]; then
|
elif [ -d "$target" ]; then
|
||||||
echo "Skipping alt $source as $target is a directory"
|
echo "Skipping alt $source as $target is a directory"
|
||||||
continue
|
continue
|
||||||
else
|
else
|
||||||
assert_parent "$target"
|
assert_parent "$target"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$template_processor" ]]; then
|
if [ -n "$template_processor" ]; then
|
||||||
template "$template_processor" "$source" "$target"
|
template "$template_processor" "$source" "$target"
|
||||||
elif [[ "$do_copy" -eq 1 ]]; then
|
elif [ "$do_copy" -eq 1 ]; then
|
||||||
$log "Copying $source to $target"
|
$log "Copying $source to $target"
|
||||||
cp -f "$source" "$target"
|
cp -f "$source" "$target"
|
||||||
|
elif [ -e "$target" ]; then
|
||||||
|
echo "Skipping alt $source as $target exists"
|
||||||
|
continue
|
||||||
else
|
else
|
||||||
$log "Linking $source to $target"
|
$log "Linking $source to $target"
|
||||||
ln_relative "$source" "$target"
|
ln_relative "$source" "$target"
|
||||||
@ -748,7 +740,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 -fs "$rel_source" "$target"
|
ln -s "$rel_source" "$target"
|
||||||
alt_linked+=("$rel_source")
|
alt_linked+=("$rel_source")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2263,10 +2255,6 @@ function esh_available() {
|
|||||||
command -v "$ESH_PROGRAM" &>/dev/null && return
|
command -v "$ESH_PROGRAM" &>/dev/null && return
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
function readlink_available() {
|
|
||||||
command -v "readlink" &>/dev/null && return
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ****** Directory translations ******
|
# ****** Directory translations ******
|
||||||
|
|
||||||
|
12
yadm.1
12
yadm.1
@ -616,8 +616,16 @@ Subsystem for Linux, where the os is reported as WSL, the link will be:
|
|||||||
If no "##default" version exists and no files have valid conditions, then no
|
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
|
Conditions can also be used on directories and will then apply on all files
|
||||||
least one yadm managed file within them.
|
within the directory. The following files:
|
||||||
|
|
||||||
|
- $HOME/path/example.txt##os.Linux
|
||||||
|
- $HOME/path/subdir/other.txt##os.Linux
|
||||||
|
|
||||||
|
Would give the same result as:
|
||||||
|
|
||||||
|
- $HOME/path##os.Linux/example.txt
|
||||||
|
- $HOME/path##os.Linux/subdir/other.txt
|
||||||
|
|
||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user