aboutsummaryrefslogtreecommitdiffstats
path: root/dot-backup.sh
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-16 18:39:40 +0200
committerDanilo M. <danix@danix.xyz>2026-06-16 18:39:40 +0200
commitb3568ae80439223b3b313bdc3bfad4edc5c7f939 (patch)
tree646932829da1897b1563cc31e7f65bb307eb2b86 /dot-backup.sh
parent7c99dbace31d9694e29bdf69f502f2add1968fda (diff)
downloaddots-backup-b3568ae80439223b3b313bdc3bfad4edc5c7f939.tar.gz
dots-backup-b3568ae80439223b3b313bdc3bfad4edc5c7f939.zip
feat: add do_suggest scan/filter/dedup function
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'dot-backup.sh')
-rwxr-xr-xdot-backup.sh84
1 files changed, 84 insertions, 0 deletions
diff --git a/dot-backup.sh b/dot-backup.sh
index 2ffdbcc..21388f0 100755
--- a/dot-backup.sh
+++ b/dot-backup.sh
@@ -226,6 +226,90 @@ do_rsync() {
fi
}
+# ── SUGGEST MODE ──────────────────────────────────────────────────────────────
+
+# True if candidate path $1 is already covered by any DOTFILES entry
+# (exact match, parent of, or child of an existing entry).
+_suggest_covered() {
+ local c="$1" e
+ for e in "${DOTFILES[@]}"; do
+ # normalize trailing slash
+ e="${e%/}"
+ [[ "$c" == "$e" ]] && return 0
+ [[ "$c" == "$e"/* ]] && return 0
+ [[ "$e" == "$c"/* ]] && return 0
+ done
+ return 1
+}
+
+# True if basename $1 is in SUGGEST_IGNORE
+_suggest_ignored() {
+ local b="$1" ig
+ for ig in "${SUGGEST_IGNORE[@]}"; do
+ [[ "$b" == "$ig" ]] && return 0
+ done
+ return 1
+}
+
+do_suggest() {
+ local epoch_file="${DEFAULT_OUTPUT_DIR}/lastupdate.epoch"
+ local last_epoch=0
+ local last_human="never"
+ if [[ -r "$epoch_file" ]]; then
+ local raw
+ raw="$(cat "$epoch_file")"
+ if [[ "$raw" =~ ^[0-9]+$ ]]; then
+ last_epoch="$raw"
+ last_human="$(date -d "@$last_epoch" 2>/dev/null || echo "$last_epoch")"
+ fi
+ fi
+
+ local -a candidates=()
+ local path emit base mtime
+
+ # Source 1: ~/.config/* (children of .config)
+ for path in "${HOME}/.config/"*; do
+ [[ -e "$path" ]] || continue
+ base="$(basename "$path")"
+ emit=".config/${base}"
+ _suggest_ignored "$base" && continue
+ _suggest_covered "$emit" && continue
+ mtime="$(stat -c %Y "$path" 2>/dev/null || echo 0)"
+ (( mtime > last_epoch )) && candidates+=("$emit")
+ done
+
+ # Source 2 + 3: ~/.* (hidden dirs and files at home top level)
+ for path in "${HOME}/".*; do
+ base="$(basename "$path")"
+ [[ "$base" == "." || "$base" == ".." ]] && continue
+ [[ -d "$path" || -f "$path" ]] || continue
+ emit="${base}"
+ _suggest_ignored "$base" && continue
+ _suggest_covered "$emit" && continue
+ mtime="$(stat -c %Y "$path" 2>/dev/null || echo 0)"
+ (( mtime > last_epoch )) && candidates+=("$emit")
+ done
+
+ if [[ ${#candidates[@]} -eq 0 ]]; then
+ echo -e "${GREEN}No new config dirs/files since last backup.${NC}"
+ return 0
+ fi
+
+ # sort alphabetically, stable output
+ local -a sorted=()
+ while IFS= read -r line; do sorted+=("$line"); done < <(printf '%s\n' "${candidates[@]}" | sort -u)
+
+ echo -e "${GREEN}New since last backup (${last_human}):${NC}"
+ for emit in "${sorted[@]}"; do
+ echo -e " ${BLUE}${emit}${NC}"
+ done
+ echo
+ echo "Paste into files.list:"
+ for emit in "${sorted[@]}"; do
+ echo "$emit"
+ done
+}
+
# ── RESTORE MODE ──────────────────────────────────────────────────────────────
do_restore() {