|
|
@@ -1,78 +1,223 @@
|
|
|
#!/bin/bash
|
|
|
|
|
|
+# Dotfiles sync script
|
|
|
+set -euo pipefail
|
|
|
+
|
|
|
+# Configuration
|
|
|
checkout_dir=$HOME/.dofiles
|
|
|
script_dir=$checkout_dir/dotfiles
|
|
|
dots_dir=$checkout_dir/dotfiles/dots
|
|
|
repo="https://git.capella.pro/capella/dotfiles.git"
|
|
|
+ProgName=$(basename "$0")
|
|
|
+
|
|
|
+# Colors for output
|
|
|
+RED='\033[0;31m'
|
|
|
+GREEN='\033[0;32m'
|
|
|
+YELLOW='\033[1;33m'
|
|
|
+NC='\033[0m' # No Color
|
|
|
|
|
|
-set -ex
|
|
|
-trap popd EXIT
|
|
|
+# Logging functions
|
|
|
+log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
|
+log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
|
|
|
+log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
|
|
|
|
-mkdir -pv $checkout_dir
|
|
|
-pushd $checkout_dir
|
|
|
+# Ensure we cleanup on exit
|
|
|
+trap 'cd "$HOME"' EXIT
|
|
|
+
|
|
|
+# Create checkout directory if needed
|
|
|
+mkdir -p "$checkout_dir"
|
|
|
|
|
|
|
|
|
sub_help(){
|
|
|
- echo "Usage: $ProgName <subcommand> [options]\n"
|
|
|
- echo "Subcommands:"
|
|
|
- echo " sync <dot_file_to_add> add a dotfyle to sync"
|
|
|
- echo ""
|
|
|
- echo "For help with each subcommand run:"
|
|
|
- echo "$ProgName <subcommand> -h|--help"
|
|
|
- echo ""
|
|
|
+ cat << EOF
|
|
|
+Usage: $ProgName <subcommand> [options]
|
|
|
+
|
|
|
+Subcommands:
|
|
|
+ sync [dot_file_to_add] Sync dotfiles (optionally add a new dotfile to track)
|
|
|
+ list List currently synced dotfiles
|
|
|
+ status Show git status of dotfiles repo
|
|
|
+
|
|
|
+For help with each subcommand run:
|
|
|
+ $ProgName <subcommand> -h|--help
|
|
|
+
|
|
|
+EOF
|
|
|
}
|
|
|
|
|
|
sub_sync(){
|
|
|
- if [[ ! -d $checkout_dir/dotfiles/.git ]]; then
|
|
|
- git clone $repo
|
|
|
- pushd dotfiles
|
|
|
+ # Clone repo if it doesn't exist
|
|
|
+ if [[ ! -d "$checkout_dir/dotfiles/.git" ]]; then
|
|
|
+ log_info "Cloning dotfiles repository..."
|
|
|
+ cd "$checkout_dir"
|
|
|
+ if ! git clone "$repo"; then
|
|
|
+ log_error "Failed to clone repository"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ cd "$script_dir"
|
|
|
git submodule init
|
|
|
- popd
|
|
|
+ git submodule update
|
|
|
fi
|
|
|
|
|
|
- cd $script_dir
|
|
|
- git pull origin master
|
|
|
- git submodule update
|
|
|
+ # Navigate to script directory
|
|
|
+ cd "$script_dir"
|
|
|
+
|
|
|
+ # Get current branch
|
|
|
+ current_branch=$(git branch --show-current)
|
|
|
+ log_info "Pulling latest changes from $current_branch..."
|
|
|
+ git pull origin "$current_branch" || log_warn "Git pull failed, continuing anyway"
|
|
|
+ git submodule update --init --recursive
|
|
|
+
|
|
|
+ # Add new dotfile if provided
|
|
|
+ if [ -n "${1:-}" ]; then
|
|
|
+ local file_to_add="$1"
|
|
|
+
|
|
|
+ # Validate that file exists
|
|
|
+ if [[ ! -e "$HOME/$file_to_add" ]]; then
|
|
|
+ log_error "File $HOME/$file_to_add does not exist"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
|
|
|
- if [ ! -z "$1" ]; then
|
|
|
- real_path=$(realpath $HOME/$1)
|
|
|
- to_copy=${real_path#*$HOME}
|
|
|
- cp -R $real_path $dots_dir/$to_copy
|
|
|
- rm -rf $real_path
|
|
|
- echo "${to_copy/\//}" >> $script_dir/to_sync
|
|
|
+ log_info "Adding new dotfile: $file_to_add"
|
|
|
+
|
|
|
+ real_path=$(realpath "$HOME/$file_to_add")
|
|
|
+ to_copy=${real_path#"$HOME/"}
|
|
|
+
|
|
|
+ # Create parent directory structure in dots_dir
|
|
|
+ mkdir -p "$(dirname "$dots_dir/$to_copy")"
|
|
|
+
|
|
|
+ # Copy to dots directory
|
|
|
+ if cp -R "$real_path" "$dots_dir/$to_copy"; then
|
|
|
+ log_info "Copied $file_to_add to $dots_dir/$to_copy"
|
|
|
+ else
|
|
|
+ log_error "Failed to copy $file_to_add"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Add to to_sync file if not already there
|
|
|
+ if ! grep -qxF "$to_copy" "$script_dir/to_sync" 2>/dev/null; then
|
|
|
+ echo "$to_copy" >> "$script_dir/to_sync"
|
|
|
+ log_info "Added $to_copy to sync list"
|
|
|
+ else
|
|
|
+ log_info "$to_copy already in sync list"
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Remove original and create symlink
|
|
|
+ rm -rf "$real_path"
|
|
|
+ ln -s "$dots_dir/$to_copy" "$real_path"
|
|
|
+ log_info "Created symlink: $real_path -> $dots_dir/$to_copy"
|
|
|
fi
|
|
|
|
|
|
- while read p; do
|
|
|
+ # Add tracked files to git
|
|
|
+ log_info "Staging changes..."
|
|
|
+ while IFS= read -r p; do
|
|
|
+ [[ -z "$p" ]] && continue # Skip empty lines
|
|
|
file="$dots_dir/$p"
|
|
|
- git add $file
|
|
|
- done < $script_dir/to_sync
|
|
|
+ if [[ -e "$file" ]]; then
|
|
|
+ git add "$file"
|
|
|
+ else
|
|
|
+ log_warn "Tracked file $file does not exist"
|
|
|
+ fi
|
|
|
+ done < "$script_dir/to_sync"
|
|
|
+
|
|
|
git add start to_sync
|
|
|
|
|
|
- if git commit -am "$(date)"; then
|
|
|
- git remote add origin $repo || true
|
|
|
- git push -u origin master
|
|
|
+ # Commit and push if there are changes
|
|
|
+ if git diff --cached --quiet; then
|
|
|
+ log_info "No changes to commit"
|
|
|
+ else
|
|
|
+ log_info "Committing changes..."
|
|
|
+ if git commit -m "Sync: $(date '+%Y-%m-%d %H:%M:%S')"; then
|
|
|
+ log_info "Pushing to remote..."
|
|
|
+ git remote add origin "$repo" 2>/dev/null || true
|
|
|
+ if git push -u origin "$current_branch"; then
|
|
|
+ log_info "Successfully pushed changes"
|
|
|
+ else
|
|
|
+ log_error "Failed to push changes"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ log_error "Failed to commit changes"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
fi
|
|
|
|
|
|
- while read p; do
|
|
|
+ # Create symlinks for all tracked dotfiles
|
|
|
+ log_info "Creating symlinks..."
|
|
|
+ while IFS= read -r p; do
|
|
|
+ [[ -z "$p" ]] && continue # Skip empty lines
|
|
|
destination="$HOME/$p"
|
|
|
- rm -rf $destination
|
|
|
- ln -s "$dots_dir/$p" $destination
|
|
|
- done < $script_dir/to_sync
|
|
|
+ source="$dots_dir/$p"
|
|
|
+
|
|
|
+ if [[ ! -e "$source" ]]; then
|
|
|
+ log_warn "Source file $source does not exist, skipping"
|
|
|
+ continue
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Check if destination is already a correct symlink
|
|
|
+ if [[ -L "$destination" ]] && [[ "$(readlink "$destination")" == "$source" ]]; then
|
|
|
+ continue # Already correctly linked
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Remove destination if it exists
|
|
|
+ if [[ -e "$destination" ]] || [[ -L "$destination" ]]; then
|
|
|
+ log_warn "Removing existing $destination"
|
|
|
+ rm -rf "$destination"
|
|
|
+ fi
|
|
|
+
|
|
|
+ # Create parent directory if needed
|
|
|
+ mkdir -p "$(dirname "$destination")"
|
|
|
+
|
|
|
+ # Create symlink
|
|
|
+ if ln -s "$source" "$destination"; then
|
|
|
+ log_info "Linked: $destination -> $source"
|
|
|
+ else
|
|
|
+ log_error "Failed to create symlink for $p"
|
|
|
+ fi
|
|
|
+ done < "$script_dir/to_sync"
|
|
|
+
|
|
|
+ log_info "Sync complete!"
|
|
|
+}
|
|
|
+
|
|
|
+sub_list(){
|
|
|
+ if [[ ! -f "$script_dir/to_sync" ]]; then
|
|
|
+ log_error "No to_sync file found"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ echo "Currently synced dotfiles:"
|
|
|
+ while IFS= read -r p; do
|
|
|
+ [[ -z "$p" ]] && continue
|
|
|
+ if [[ -L "$HOME/$p" ]]; then
|
|
|
+ echo " ✓ $p"
|
|
|
+ else
|
|
|
+ echo " ✗ $p (not linked)"
|
|
|
+ fi
|
|
|
+ done < "$script_dir/to_sync"
|
|
|
+}
|
|
|
+
|
|
|
+sub_status(){
|
|
|
+ if [[ ! -d "$script_dir/.git" ]]; then
|
|
|
+ log_error "Dotfiles repository not found"
|
|
|
+ exit 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ cd "$script_dir"
|
|
|
+ log_info "Git status:"
|
|
|
+ git status
|
|
|
}
|
|
|
|
|
|
|
|
|
-subcommand=$1
|
|
|
+subcommand=${1:-}
|
|
|
case $subcommand in
|
|
|
"" | "-h" | "--help")
|
|
|
sub_help
|
|
|
;;
|
|
|
- *)
|
|
|
+ sync|list|status)
|
|
|
shift
|
|
|
- sub_${subcommand} $@
|
|
|
- if [ $? = 127 ]; then
|
|
|
- echo "Error: '$subcommand' is not a known subcommand." >&2
|
|
|
- echo " Run '$ProgName --help' for a list of known subcommands." >&2
|
|
|
- exit 1
|
|
|
- fi
|
|
|
+ "sub_${subcommand}" "$@"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ log_error "'$subcommand' is not a known subcommand."
|
|
|
+ echo " Run '$ProgName --help' for a list of known subcommands." >&2
|
|
|
+ exit 1
|
|
|
;;
|
|
|
esac
|