module apply import src.snapshot fn entry(path string, hash string) snapshot.FileEntry { return snapshot.FileEntry{ path: path hash: hash } } fn hashes(files []snapshot.FileEntry) map[string]string { mut out := map[string]string{} for file in files { out[file.path] = file.hash } return out } fn test_clean_apply_takes_the_new_content() { current := [entry('a.txt', 'aaa'), entry('b.txt', 'bbb')] base := [entry('a.txt', 'aaa'), entry('b.txt', 'bbb')] target := [entry('a.txt', 'AAA'), entry('b.txt', 'bbb')] plan := three_way(current, base, target, [], []) assert !conflicts(plan) assert plan.applied == ['a.txt'] assert hashes(plan.files) == { 'a.txt': 'AAA' 'b.txt': 'bbb' } } fn test_already_applied_is_left_alone() { current := [entry('a.txt', 'AAA')] base := [entry('a.txt', 'aaa')] target := [entry('a.txt', 'AAA')] plan := three_way(current, base, target, [], []) assert !conflicts(plan) assert plan.applied.len == 0 assert hashes(plan.files) == { 'a.txt': 'AAA' } } fn test_a_file_the_user_has_since_edited_is_a_hard_conflict() { current := [entry('a.txt', 'mine')] base := [entry('a.txt', 'aaa')] target := [entry('a.txt', 'theirs')] plan := three_way(current, base, target, [], []) assert conflicts(plan) assert plan.conflict == 'a.txt' } fn test_additions_removals_and_untouched_paths() { current := [entry('kept.txt', 'keep'), entry('gone.txt', 'bye')] base := [entry('gone.txt', 'bye')] target := [entry('added.txt', 'new')] plan := three_way(current, base, target, [], []) assert !conflicts(plan) assert plan.applied == ['added.txt', 'gone.txt'] assert hashes(plan.files) == { 'added.txt': 'new' 'kept.txt': 'keep' } } fn test_filters_limit_the_change_to_paths_or_folders() { current := [entry('src/a.txt', 'aaa'), entry('docs/b.txt', 'bbb')] base := [entry('src/a.txt', 'aaa'), entry('docs/b.txt', 'bbb')] target := [entry('src/a.txt', 'AAA'), entry('docs/b.txt', 'BBB')] plan := three_way(current, base, target, ['src'], []) assert plan.applied == ['src/a.txt'] assert hashes(plan.files) == { 'src/a.txt': 'AAA' 'docs/b.txt': 'bbb' } // A folder filter also covers everything beneath it, with or without the // trailing slash. plan2 := three_way(current, base, target, ['docs/'], []) assert plan2.applied == ['docs/b.txt'] } fn test_reverse_is_the_same_engine_with_the_sides_swapped() { // pluck --reverse passes base=target and target=base; the engine does not // need to know which way round it is. current := [entry('a.txt', 'AAA')] original := [entry('a.txt', 'aaa')] changed := [entry('a.txt', 'AAA')] forward := three_way(current, original, changed, [], []) assert forward.applied.len == 0 back := three_way(current, changed, original, [], []) assert !conflicts(back) assert hashes(back.files) == { 'a.txt': 'aaa' } } fn test_executable_bit_counts_as_a_change() { current := [snapshot.FileEntry{ path: 'run.sh' hash: 'same' }] base := [snapshot.FileEntry{ path: 'run.sh' hash: 'same' }] target := [ snapshot.FileEntry{ path: 'run.sh' hash: 'same' executable: true }, ] plan := three_way(current, base, target, [], []) assert !conflicts(plan) assert plan.applied == ['run.sh'] assert plan.files[0].executable }