From 4ea3ed9e2af11421d8fc029a8438ce74c1bbdd1c Mon Sep 17 00:00:00 2001 From: Tim Byrne Date: Thu, 10 Oct 2019 08:23:36 -0500 Subject: [PATCH] Allow storing alternates elsewhere (#90) This change allows alternates to be stored in "$YADM_DIR/alt". The correct path within the work tree will be symlinked. Storing alternates within the work tree is still allowed. Both locations will be considered when choosing an appropriate alternate file. --- test/test_alt.py | 54 ++++++++++++++++++++++++++++++++---------------- test/utils.py | 35 ++++++++++++++++++------------- yadm | 7 +++++++ yadm.1 | 13 +++++++++++- 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/test/test_alt.py b/test/test_alt.py index def2037..cbac403 100644 --- a/test/test_alt.py +++ b/test/test_alt.py @@ -9,6 +9,7 @@ TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR] @pytest.mark.usefixtures('ds1_copy') +@pytest.mark.parametrize('yadm_alt', [True, False], ids=['alt', 'worktree']) @pytest.mark.parametrize( 'tracked,encrypt,exclude', [ (False, False, False), @@ -17,32 +18,39 @@ TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR] (False, True, True), ], ids=['untracked', 'tracked', 'encrypted', 'excluded']) def test_alt_source( - runner, yadm_y, paths, - tracked, encrypt, exclude): + runner, paths, + tracked, encrypt, exclude, + yadm_alt): """Test yadm alt operates on all expected sources of alternates""" + yadm_dir = setup_standard_yadm_dir(paths) utils.create_alt_files( - paths, '##default', tracked=tracked, encrypt=encrypt, exclude=exclude) - run = runner(yadm_y('alt')) + paths, '##default', tracked=tracked, encrypt=encrypt, exclude=exclude, + yadm_alt=yadm_alt, yadm_dir=yadm_dir) + run = runner([paths.pgm, '-Y', yadm_dir, 'alt']) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) + basepath = yadm_dir.join('alt') if yadm_alt else paths.work + for link_path in TEST_PATHS: - source_file = link_path + '##default' + source_file_content = link_path + '##default' + source_file = basepath.join(source_file_content) + link_file = paths.work.join(link_path) if tracked or (encrypt and not exclude): - assert paths.work.join(link_path).islink() - target = py.path.local(paths.work.join(link_path).readlink()) + assert link_file.islink() + target = py.path.local(link_file.readlink()) if target.isfile(): - assert paths.work.join(link_path).read() == source_file - assert str(paths.work.join(source_file)) in linked + assert link_file.read() == source_file_content + assert str(source_file) in linked else: - assert paths.work.join(link_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked + assert link_file.join( + utils.CONTAINED).read() == source_file_content + assert str(source_file) in linked else: - assert not paths.work.join(link_path).exists() - assert str(paths.work.join(source_file)) not in linked + assert not link_file.exists() + assert str(source_file) not in linked @pytest.mark.usefixtures('ds1_copy') @@ -55,9 +63,10 @@ def test_alt_source( '##u.$tst_user', '##user.$tst_user', ]) def test_alt_conditions( - runner, yadm_y, paths, + runner, paths, tst_sys, tst_distro, tst_host, tst_user, suffix): """Test conditions supported by yadm alt""" + yadm_dir = setup_standard_yadm_dir(paths) # set the class tst_class = 'testclass' @@ -72,7 +81,7 @@ def test_alt_conditions( ) utils.create_alt_files(paths, suffix) - run = runner(yadm_y('alt')) + run = runner([paths.pgm, '-Y', yadm_dir, 'alt']) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -94,12 +103,13 @@ def test_alt_conditions( @pytest.mark.parametrize('kind', ['builtin', '', 'envtpl', 'j2cli', 'j2']) @pytest.mark.parametrize('label', ['t', 'template', 'yadm', ]) def test_alt_templates( - runner, yadm_y, paths, kind, label): + runner, paths, kind, label): """Test templates supported by yadm alt""" + yadm_dir = setup_standard_yadm_dir(paths) suffix = f'##{label}.{kind}' utils.create_alt_files(paths, suffix) - run = runner(yadm_y('alt')) + run = runner([paths.pgm, '-Y', yadm_dir, 'alt']) assert run.success assert run.err == '' created = utils.parse_alt_output(run.out, linked=False) @@ -220,3 +230,11 @@ def test_template_overwrite_symlink(runner, yadm_y, paths, tst_sys): assert not link.islink() assert target.read().strip() == 'target' assert link.read().strip() == 'test-data' + + +def setup_standard_yadm_dir(paths): + """Configure a yadm home within the work tree""" + std_yadm_dir = paths.work.mkdir('.config').mkdir('yadm') + std_yadm_dir.join('repo.git').mksymlinkto(paths.repo, absolute=1) + std_yadm_dir.join('encrypt').mksymlinkto(paths.encrypt, absolute=1) + return std_yadm_dir diff --git a/test/utils.py b/test/utils.py index 00a0fdd..32db94a 100644 --- a/test/utils.py +++ b/test/utils.py @@ -32,7 +32,8 @@ def set_local(paths, variable, value): def create_alt_files(paths, suffix, preserve=False, tracked=True, encrypt=False, exclude=False, - content=None, includefile=False): + content=None, includefile=False, + yadm_alt=False, yadm_dir=None): """Create new files, and add to the repo This is used for testing alternate files. In each case, a suffix is @@ -40,17 +41,19 @@ def create_alt_files(paths, suffix, repo handling are dependent upon the function arguments. """ + basepath = yadm_dir.join('alt') if yadm_alt else paths.work + if not preserve: for remove_path in (ALT_FILE1, ALT_FILE2, ALT_DIR): - if paths.work.join(remove_path).exists(): - paths.work.join(remove_path).remove(rec=1, ignore_errors=True) - assert not paths.work.join(remove_path).exists() + if basepath.join(remove_path).exists(): + basepath.join(remove_path).remove(rec=1, ignore_errors=True) + assert not basepath.join(remove_path).exists() - new_file1 = paths.work.join(ALT_FILE1 + suffix) + new_file1 = basepath.join(ALT_FILE1 + suffix) new_file1.write(ALT_FILE1 + suffix, ensure=True) - new_file2 = paths.work.join(ALT_FILE2 + suffix) + new_file2 = basepath.join(ALT_FILE2 + suffix) new_file2.write(ALT_FILE2 + suffix, ensure=True) - new_dir = paths.work.join(ALT_DIR + suffix).join(CONTAINED) + new_dir = basepath.join(ALT_DIR + suffix).join(CONTAINED) new_dir.write(ALT_DIR + suffix, ensure=True) # Do not test directory support for jinja alternates @@ -65,9 +68,11 @@ def create_alt_files(paths, suffix, test_path.write('\n' + content, mode='a', ensure=True) assert test_path.exists() - _create_includefiles(includefile, paths, test_paths) + _create_includefiles(includefile, test_paths, basepath) _create_tracked(tracked, test_paths, paths) - _create_encrypt(encrypt, test_names, suffix, paths, exclude) + + prefix = '.config/yadm/alt/' if yadm_alt else '' + _create_encrypt(encrypt, test_names, suffix, paths, exclude, prefix) def parse_alt_output(output, linked=True): @@ -86,10 +91,10 @@ def parse_alt_output(output, linked=True): return parsed_list.values() -def _create_includefiles(includefile, paths, test_paths): +def _create_includefiles(includefile, test_paths, basepath): if includefile: for dpath in INCLUDE_DIRS: - incfile = paths.work.join(dpath + '/' + INCLUDE_FILE) + incfile = basepath.join(dpath + '/' + INCLUDE_FILE) incfile.write(INCLUDE_CONTENT, ensure=True) test_paths += [incfile] @@ -101,9 +106,11 @@ def _create_tracked(tracked, test_paths, paths): os.system(f'GIT_DIR={str(paths.repo)} git commit -m "Add test files"') -def _create_encrypt(encrypt, test_names, suffix, paths, exclude): +def _create_encrypt(encrypt, test_names, suffix, paths, exclude, prefix): if encrypt: for encrypt_name in test_names: - paths.encrypt.write(f'{encrypt_name + suffix}\n', mode='a') + paths.encrypt.write( + f'{prefix + encrypt_name + suffix}\n', mode='a') if exclude: - paths.encrypt.write(f'!{encrypt_name + suffix}\n', mode='a') + paths.encrypt.write( + f'!{prefix + encrypt_name + suffix}\n', mode='a') diff --git a/yadm b/yadm index 1529785..687264a 100755 --- a/yadm +++ b/yadm @@ -33,6 +33,7 @@ YADM_ENCRYPT="encrypt" YADM_ARCHIVE="files.gpg" YADM_BOOTSTRAP="bootstrap" YADM_HOOKS="hooks" +YADM_ALT="alt" HOOK_COMMAND="" FULL_COMMAND="" @@ -137,6 +138,11 @@ function score_file() { target="$1" filename="${target%%##*}" conditions="${target#*##}" + + if [ "${filename#$YADM_ALT/}" != "${filename}" ]; then + filename="${YADM_WORK}/${filename#$YADM_ALT/}" + fi + score=0 IFS=',' read -ra fields <<< "$conditions" for field in "${fields[@]}"; do @@ -1223,6 +1229,7 @@ function configure_paths() { YADM_ARCHIVE="$YADM_DIR/$YADM_ARCHIVE" YADM_BOOTSTRAP="$YADM_DIR/$YADM_BOOTSTRAP" YADM_HOOKS="$YADM_DIR/$YADM_HOOKS" + YADM_ALT="$YADM_DIR/$YADM_ALT" # independent overrides for paths if [ -n "$YADM_OVERRIDE_REPO" ]; then diff --git a/yadm.1 b/yadm.1 index 925650f..aff25f3 100644 --- a/yadm.1 +++ b/yadm.1 @@ -478,6 +478,12 @@ condition. The number of conditions is the next largest factor in scoring. Files with more conditions will always be favored. Any invalid condition will disqualify that file completely. +If you don't care to have all versions of alternates stored in the same +directory as the generated symlink, you can place them in the +.I $HOME/.config/yadm/alt +directory. The generated symlink or processed template will be created using +same relative path. + Alternate linking may best be demonstrated by example. Assume the following files are managed by yadm's repository: @@ -771,7 +777,7 @@ Otherwise it will be .IR "$HOME/.config/yadm" . The following are the default paths yadm uses for its own data. -These paths can be altered using universal options. +Most of these paths can be altered using universal options. See the OPTIONS section for details. .TP .I $HOME/.config/yadm @@ -781,6 +787,11 @@ directory. .I $YADM_DIR/config Configuration file for yadm. .TP +.I $YADM_DIR/alt +This is a directory to keep "alternate files" without having them side-by-side +with the resulting symlink or processed template. Alternate files placed in +this directory will be created relative to $HOME instead. +.TP .I $YADM_DIR/repo.git Git repository used by yadm. .TP