#!/usr/bin/env bash set -euo pipefail script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) src_dir="$script_dir/config" dest_dir="${HOME}/.config" dry_run=false target_path="" usage() { cat <<'EOF' Usage: sync-config.sh [--dry-run] [path] path can be: config (full config) config/tmux (specific folder) config/tmux/config (specific file) .config (full config) .config/tmux (specific folder) .config/tmux/config (specific file) tmux (folder under .config) tmux/config (file under .config) EOF } for arg in "$@"; do case "$arg" in --dry-run) dry_run=true ;; -h|--help) usage exit 0 ;; *) if [[ -z "$target_path" ]]; then target_path="$arg" else echo "Error: too many arguments." >&2 usage >&2 exit 1 fi ;; esac done hash_cmd="sha256sum" if ! command -v "$hash_cmd" >/dev/null 2>&1; then echo "Error: sha256sum not found. Install coreutils or adjust hash_cmd." >&2 exit 1 fi file_hash() { "$hash_cmd" "$1" | awk '{print $1}' } copy_file() { local src="$1" local dst="$2" if $dry_run; then echo "copy: $src -> $dst" else mkdir -p "$(dirname "$dst")" cp -a "$src" "$dst" echo "copied: $dst" fi } if [[ ! -d "$src_dir" ]]; then echo "Error: source config directory not found: $src_dir" >&2 exit 1 fi mkdir -p "$dest_dir" target_rel="$target_path" if [[ -n "$target_rel" ]]; then if [[ "$target_rel" == "config" || "$target_rel" == ".config" ]]; then target_rel="" elif [[ "$target_rel" == config/* ]]; then target_rel=${target_rel#config/} elif [[ "$target_rel" == .config/* ]]; then target_rel=${target_rel#.config/} fi target_rel=${target_rel#./} if [[ "$target_rel" == "." ]]; then target_rel="" fi if [[ "$target_rel" == /* ]]; then echo "Error: absolute paths are not supported: $target_path" >&2 exit 1 fi fi base_path="$src_dir" if [[ -n "$target_rel" ]]; then base_path="$src_dir/$target_rel" fi if [[ ! -e "$base_path" ]]; then echo "Error: path not found in source: $base_path" >&2 exit 1 fi sync_file() { local src_path="$1" local rel_path=${src_path#"$src_dir"/} local dst_path="$dest_dir/$rel_path" if [[ ! -f "$dst_path" ]]; then copy_file "$src_path" "$dst_path" return fi local src_hash local dst_hash src_hash=$(file_hash "$src_path") dst_hash=$(file_hash "$dst_path") if [[ "$src_hash" != "$dst_hash" ]]; then copy_file "$src_path" "$dst_path" fi } shopt -s globstar nullglob dotglob if [[ -f "$base_path" ]]; then sync_file "$base_path" elif [[ -d "$base_path" ]]; then for src_path in "$base_path"/**; do rel_path=${src_path#"$src_dir"/} if [[ "$rel_path" == ".git" || "$rel_path" == .git/* ]]; then continue fi dst_path="$dest_dir/$rel_path" if [[ -d "$src_path" ]]; then if ! $dry_run; then mkdir -p "$dst_path" else echo "mkdir: $dst_path" fi continue fi if [[ ! -f "$src_path" ]]; then continue fi sync_file "$src_path" done fi if $dry_run; then echo "Dry run complete. Re-run without --dry-run to apply changes." else echo "Sync complete. Updated files where content differed." fi