Skip to content

Instantly share code, notes, and snippets.

@dltj
Created August 30, 2026 18:18
Show Gist options
  • Select an option

  • Save dltj/f53399de3e441f76e6d737a32c491485 to your computer and use it in GitHub Desktop.

Select an option

Save dltj/f53399de3e441f76e6d737a32c491485 to your computer and use it in GitHub Desktop.
VuFind 3-Way Merge
#!/usr/bin/env -S uv run --script
# Note: This script requires 'uv' (https://docs.astral.sh/uv/).
# Install: curl -LsSf https://astral.sh/uv/install.sh | sh (or `brew install uv`)
# Execution: ./script.py OR uv run script.py
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "click",
# ]
# ///
import os
import shutil
import subprocess
from pathlib import Path
from typing import Iterator
import click
DIFF_EXECUTABLE = os.getenv("DIFF_EXECUTABLE") or shutil.which("kdiff3") or "kdiff3"
def iter_files(directory: Path) -> Iterator[Path]:
"""Recursively yields file paths from a directory."""
return (path for path in directory.rglob("*") if path.is_file())
@click.command()
@click.argument("site_dir", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path))
@click.argument("base_dir", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path))
@click.argument("target_dir", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path))
def threeway_merge(site_dir: Path, base_dir: Path, target_dir: Path) -> None:
"""Perform a three-way merge of a site config against changed VuFind versions."""
for site_file_path in iter_files(site_dir):
relative_path = site_file_path.relative_to(site_dir)
base_file_path = base_dir / relative_path
target_file_path = target_dir / relative_path
print(f"{site_file_path=} {base_file_path=} {target_file_path=}")
try:
result = subprocess.run(
[
DIFF_EXECUTABLE,
base_file_path,
site_file_path,
target_file_path,
"-o",
site_file_path,
],
check=True, # Raise CalledProcessError if the command exits with a non-zero status
text=True, # Capture stdout and stderr as strings
capture_output=True, # Capture stdout and stderr
)
print("Output:", result.stdout)
print("Error:", result.stderr)
except subprocess.CalledProcessError as e:
print(f"Command '{' '.join(map(str, e.cmd))}' exited with code {e.returncode}.")
print(f"Error output: {e.stderr}")
if __name__ == "__main__":
threeway_merge()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment