From d849400f63ff007c6fa86722f4bf36ceb3cce9d3 Mon Sep 17 00:00:00 2001 From: "Oliver Surke (Gitea)" Date: Tue, 12 May 2026 11:49:04 +0200 Subject: [PATCH] fix: skip dot-paths already declared in Mackup built-in app definitions Previously ssr-custom cfg generation only checked whether the path was already in the cloud backup directory. It did not consult Mackup's ~400 built-in app definitions, causing duplicates like ssr-claude.cfg when Mackup already has a 'claude-code' entry covering .claude. Changes: - _ssr_mackup_declared_paths(): parses all built-in *.cfg files from Mackup's Python package apps/ dir + hand-written ~/.mackup/*.cfg (excluding our own ssr-*.cfg) into a sorted path set - generate_custom_cfg now skips any path already in that set - Stale ssr-*.cfg files that now duplicate a built-in are removed automatically on the next sync run Co-authored-by: Cursor --- lib/mackup.sh | 76 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/lib/mackup.sh b/lib/mackup.sh index 9d04e8a..5a28ce9 100644 --- a/lib/mackup.sh +++ b/lib/mackup.sh @@ -71,11 +71,44 @@ _ssr_mackup_write_cfg() { printf '%s\n' "$desired" > "$cfg_file" } +# Build a newline-separated list of all paths already declared in Mackup's +# built-in app definitions AND user-defined ~/.mackup/*.cfg files. +# Our own generated ssr-*.cfg files are excluded to avoid circular checks. +_ssr_mackup_declared_paths() { + local apps_dir="" + + # Locate Mackup's built-in apps/ directory via Python. + if command -v python3 >/dev/null 2>&1; then + apps_dir="$(python3 -c \ + 'import os, mackup; print(os.path.join(os.path.dirname(mackup.__file__), "apps"))' \ + 2>/dev/null || true)" + fi + + # Also include hand-written user definitions (not our generated ssr-*.cfg). + local user_cfg_dir="$HOME/.mackup" + + { + [[ -d "$apps_dir" ]] \ + && grep -rh '' "$apps_dir"/*.cfg 2>/dev/null || true + [[ -d "$user_cfg_dir" ]] \ + && grep -rhl '' "$user_cfg_dir"/*.cfg 2>/dev/null \ + | grep -v '/ssr-' \ + | xargs grep -h '' 2>/dev/null || true + } | awk ' + /^\[configuration_files\]/ { in_section=1; next } + /^\[/ { in_section=0 } + in_section && /^[^#[:space:]]/ { print $1 } + ' | sort -u +} + # Generate one ~/.mackup/ssr-.cfg per uncovered dot-dir/file. -# 1. Curated entries that exist in $HOME and are not yet in the Mackup backup. -# 2. Auto-discovered dot-dirs in $HOME not in the skip list and not in backup. +# 1. Curated entries that exist in $HOME and are not yet covered. +# 2. Auto-discovered dot-dirs in $HOME not in the skip list and not covered. # -# Removes the old combined ssr-custom.cfg if found (migration from old format). +# "Covered" means: already declared in a built-in or hand-written Mackup app +# definition OR already materialised in the Mackup backup directory. +# +# Removes the old combined ssr-custom.cfg if found (migration). # Each file is picked up automatically by `mackup backup/restore`. ssr::mackup::generate_custom_cfg() { local mackup_dir="$1" @@ -85,8 +118,34 @@ ssr::mackup::generate_custom_cfg() { # Remove legacy combined file if present. [[ -f "$cfg_dir/ssr-custom.cfg" ]] && rm -f "$cfg_dir/ssr-custom.cfg" + # Build declared-paths lookup early so we can also purge stale ssr-*.cfg + # files that now duplicate a built-in definition (e.g. ssr-claude.cfg after + # Mackup added a built-in "claude-code" app). + local declared + declared="$(_ssr_mackup_declared_paths)" + + local stale_cfg stale_path + for stale_cfg in "$cfg_dir"/ssr-*.cfg; do + [[ -f "$stale_cfg" ]] || continue + # Extract the single path from [configuration_files]. + stale_path="$(awk '/^\[configuration_files\]/{f=1;next}/^\[/{f=0}f&&/^[^#[:space:]]/{print $1;exit}' "$stale_cfg")" + if printf '%s\n' "$declared" | grep -qxF "$stale_path"; then + ssr::log "Removing stale duplicate: ${stale_cfg##*/} ($stale_path now covered by built-in)" + rm -f "$stale_cfg" + fi + done + local skip_set=" ${_SSR_MACKUP_SKIP[*]} " + # Build declared-paths lookup (newline-separated, sorted). + local declared + declared="$(_ssr_mackup_declared_paths)" + + # Return true when is already declared in any existing app definition. + _is_declared() { + printf '%s\n' "$declared" | grep -qxF "$1" + } + # Return true when is already present in the Mackup cloud backup. _in_backup() { [[ -e "$mackup_dir/$1" ]]; } local -a items=() @@ -95,6 +154,7 @@ ssr::mackup::generate_custom_cfg() { local item for item in "${_SSR_MACKUP_CURATED[@]}" ${SSR_MACKUP_EXTRA_PATHS:-}; do [[ -e "$HOME/$item" ]] || continue + _is_declared "$item" && continue _in_backup "$item" && continue items+=("$item") done @@ -104,7 +164,8 @@ ssr::mackup::generate_custom_cfg() { while IFS= read -r dotdir; do name="${dotdir##*/}" [[ "$skip_set" == *" $name "* ]] && continue - _in_backup "$name" && continue + _is_declared "$name" && continue + _in_backup "$name" && continue local dup=false for item in "${items[@]:-}"; do [[ "$item" == "$name" ]] && dup=true && break; done $dup && continue @@ -118,12 +179,11 @@ ssr::mackup::generate_custom_cfg() { local written=0 it for it in "${items[@]}"; do - if _ssr_mackup_write_cfg "$cfg_dir" "$it"; then - written=$((written + 1)) - fi + _ssr_mackup_write_cfg "$cfg_dir" "$it" + written=$((written + 1)) done - ssr::ok "~/.mackup/ssr-*.cfg: ${#items[@]} app definitions written (${written} new/updated)" + ssr::ok "~/.mackup/ssr-*.cfg: ${written} definition(s) written" ssr::log " → ${items[*]}" }