mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 14:59:27 -05:00
Update the update script (#2220)
This commit is contained in:
@@ -4,3 +4,4 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
dots/.config/quickshell/ii/.qmlls.ini
|
||||
.update-lock
|
||||
|
||||
+15
-8
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
cd "$(dirname "$0")"
|
||||
export base="$(pwd)"
|
||||
# Use REPO_ROOT instead of base - when scripts are sourced they do not need export to inherit vars
|
||||
REPO_ROOT="$(pwd)"
|
||||
source ./sdata/lib/environment-variables.sh
|
||||
source ./sdata/lib/functions.sh
|
||||
source ./sdata/lib/package-installers.sh
|
||||
@@ -10,11 +11,17 @@ prevent_sudo_or_root
|
||||
set -e
|
||||
|
||||
#####################################################################################
|
||||
# For uninstall script
|
||||
if [[ "${EXPERIMENTAL_UNINSTALL_SCRIPT}" = true ]]; then
|
||||
source ./sdata/exp/uninstall.sh
|
||||
# For subcommands
|
||||
case ${SCRIPT_SUBCOMMAND} in
|
||||
exp-uninstall)
|
||||
source ./sdata/step/exp-uninstall.sh
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
exp-update)
|
||||
source ./sdata/step/exp-update.sh
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
#####################################################################################
|
||||
# 0. Before we start
|
||||
if [[ "${SKIP_ALLGREETING}" != true ]]; then
|
||||
@@ -33,9 +40,9 @@ fi
|
||||
#####################################################################################
|
||||
if [[ "${SKIP_ALLFILES}" != true ]]; then
|
||||
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
|
||||
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" != true ]]; then
|
||||
source ./sdata/step/3.install-files.sh
|
||||
else
|
||||
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
|
||||
source ./sdata/step/3.install-files.experimental.sh
|
||||
else
|
||||
source ./sdata/step/3.install-files.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,860 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# update.sh - Enhanced dotfiles update script
|
||||
#
|
||||
# Features:
|
||||
# - Pull latest commits from remote
|
||||
# - Rebuild packages if PKGBUILD files changed (user choice)
|
||||
# - Handle config file conflicts with user choices
|
||||
# - Respect .updateignore file for exclusions
|
||||
#
|
||||
set -uo pipefail
|
||||
|
||||
# === Configuration ===
|
||||
FORCE_CHECK=false
|
||||
CHECK_PACKAGES=false
|
||||
REPO_DIR="$(cd $(dirname $(dirname $(dirname $0))) &>/dev/null && pwd)"
|
||||
ARCH_PACKAGES_DIR="${REPO_DIR}/sdist/arch"
|
||||
UPDATE_IGNORE_FILE="${REPO_DIR}/.updateignore"
|
||||
HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore"
|
||||
|
||||
# Directories to monitor for changes
|
||||
MONITOR_DIRS=("dots/.config" "dots/.local/bin")
|
||||
|
||||
# === Color Codes ===
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# === Helper Functions ===
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||
}
|
||||
|
||||
log_header() {
|
||||
echo -e "\n${PURPLE}=== $1 ===${NC}"
|
||||
}
|
||||
|
||||
die() {
|
||||
log_error "$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to safely read input with terminal compatibility
|
||||
safe_read() {
|
||||
local prompt="$1"
|
||||
local varname="$2"
|
||||
local default="${3:-}"
|
||||
|
||||
# Simple approach: just use read with /dev/tty and handle errors
|
||||
local input_value=""
|
||||
|
||||
# Display prompt and read from terminal
|
||||
echo -n "$prompt"
|
||||
if read input_value </dev/tty 2>/dev/null || read input_value 2>/dev/null; then
|
||||
eval "$varname='$input_value'"
|
||||
return 0
|
||||
else
|
||||
# If read failed and we have a default, use it
|
||||
if [[ -n "$default" ]]; then
|
||||
echo
|
||||
log_warning "Using default: $default"
|
||||
eval "$varname='$default'"
|
||||
return 0
|
||||
else
|
||||
echo
|
||||
log_error "Failed to read input"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check if a file should be ignored
|
||||
should_ignore() {
|
||||
local file_path="$1"
|
||||
local relative_path="${file_path#$HOME/}"
|
||||
|
||||
# Also get path relative to repo for repo-level ignores
|
||||
local repo_relative=""
|
||||
if [[ "$file_path" == "$REPO_DIR"* ]]; then
|
||||
repo_relative="${file_path#$REPO_DIR/}"
|
||||
fi
|
||||
|
||||
# Check both repo and home ignore files
|
||||
for ignore_file in "$UPDATE_IGNORE_FILE" "$HOME_UPDATE_IGNORE_FILE"; do
|
||||
if [[ -f "$ignore_file" ]]; then
|
||||
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue
|
||||
# Remove leading/trailing whitespace
|
||||
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
[[ -z "$pattern" ]] && continue
|
||||
|
||||
# Handle different gitignore-style patterns
|
||||
local should_skip=false
|
||||
|
||||
# Exact match
|
||||
if [[ "$relative_path" == "$pattern" ]] || [[ "$repo_relative" == "$pattern" ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
|
||||
# Wildcard patterns (basic glob matching)
|
||||
if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
|
||||
# Directory patterns (ending with /)
|
||||
if [[ "$pattern" == */ ]]; then
|
||||
local dir_pattern="${pattern%/}"
|
||||
if [[ "$relative_path" == "$dir_pattern"/* ]] || [[ "$repo_relative" == "$dir_pattern"/* ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Patterns starting with / (from root)
|
||||
if [[ "$pattern" == /* ]]; then
|
||||
local root_pattern="${pattern#/}"
|
||||
if [[ "$relative_path" == "$root_pattern" ]] || [[ "$relative_path" == "$root_pattern"/* ]] ||
|
||||
[[ "$repo_relative" == "$root_pattern" ]] || [[ "$repo_relative" == "$root_pattern"/* ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Patterns with wildcards
|
||||
if [[ "$pattern" == *"*"* ]]; then
|
||||
if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
# Also check if any parent directory matches
|
||||
local temp_path="$relative_path"
|
||||
while [[ "$temp_path" == */* ]]; do
|
||||
temp_path="${temp_path%/*}"
|
||||
if [[ "$temp_path" == $pattern ]]; then
|
||||
should_skip=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Simple substring matching (for backward compatibility)
|
||||
if [[ ! "$should_skip" == true ]]; then
|
||||
if [[ "$file_path" == *"$pattern"* ]] || [[ "$relative_path" == *"$pattern"* ]]; then
|
||||
should_skip=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$should_skip" == true ]]; then
|
||||
return 0
|
||||
fi
|
||||
done <"$ignore_file"
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to show file diff with syntax highlighting if possible
|
||||
show_diff() {
|
||||
local file1="$1"
|
||||
local file2="$2"
|
||||
|
||||
echo -e "\n${CYAN}Showing differences:${NC}"
|
||||
echo -e "${CYAN}Old file: $file1${NC}"
|
||||
echo -e "${CYAN}New file: $file2${NC}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
if command -v diff &>/dev/null; then
|
||||
diff -u "$file1" "$file2" || true
|
||||
else
|
||||
echo "diff command not available"
|
||||
fi
|
||||
echo "----------------------------------------"
|
||||
}
|
||||
|
||||
# Function to handle file conflicts
|
||||
handle_file_conflict() {
|
||||
local repo_file="$1"
|
||||
local home_file="$2"
|
||||
local filename=$(basename "$home_file")
|
||||
local dirname=$(dirname "$home_file")
|
||||
|
||||
echo -e "\n${YELLOW}Conflict detected:${NC} $home_file"
|
||||
echo "Repository version differs from your local version."
|
||||
echo
|
||||
echo "Choose an action:"
|
||||
echo "1) Replace local file with repository version"
|
||||
echo "2) Keep local file unchanged"
|
||||
echo "3) Backup local file as ${filename}.old, use repository version"
|
||||
echo "4) Save repository version as ${filename}.new, keep local file"
|
||||
echo "5) Show diff and decide"
|
||||
echo "6) Skip this file"
|
||||
echo "7) Add to ignore and skip"
|
||||
echo
|
||||
|
||||
while true; do
|
||||
if ! safe_read "Enter your choice (1-7): " choice "6"; then
|
||||
echo
|
||||
log_warning "Failed to read input. Skipping file."
|
||||
return
|
||||
fi
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
cp -p "$repo_file" "$home_file"
|
||||
log_success "Replaced $home_file with repository version"
|
||||
break
|
||||
;;
|
||||
2)
|
||||
log_info "Keeping local version of $home_file"
|
||||
break
|
||||
;;
|
||||
3)
|
||||
mv "$home_file" "${dirname}/${filename}.old"
|
||||
cp -p "$repo_file" "$home_file"
|
||||
log_success "Backed up local file to ${filename}.old and updated with repository version"
|
||||
break
|
||||
;;
|
||||
4)
|
||||
cp -p "$repo_file" "${dirname}/${filename}.new"
|
||||
log_success "Saved repository version as ${filename}.new, kept local file"
|
||||
break
|
||||
;;
|
||||
5)
|
||||
show_diff "$home_file" "$repo_file"
|
||||
echo
|
||||
echo "After reviewing the diff, choose:"
|
||||
echo "r) Replace with repository version"
|
||||
echo "k) Keep local version"
|
||||
echo "b) Backup local and use repository version"
|
||||
echo "n) Save repository version as .new"
|
||||
echo "s) Skip this file"
|
||||
echo "i) Add to ignore and skip"
|
||||
|
||||
if ! safe_read "Enter your choice (r/k/b/n/s/i): " subchoice "s"; then
|
||||
echo
|
||||
log_warning "Failed to read input. Skipping file."
|
||||
return
|
||||
fi
|
||||
|
||||
case $subchoice in
|
||||
r)
|
||||
cp -p "$repo_file" "$home_file"
|
||||
log_success "Replaced $home_file with repository version"
|
||||
break
|
||||
;;
|
||||
k)
|
||||
log_info "Keeping local version of $home_file"
|
||||
break
|
||||
;;
|
||||
b)
|
||||
mv "$home_file" "${dirname}/${filename}.old"
|
||||
cp -p "$repo_file" "$home_file"
|
||||
log_success "Backed up local file to ${filename}.old and updated"
|
||||
break
|
||||
;;
|
||||
n)
|
||||
cp -p "$repo_file" "${dirname}/${filename}.new"
|
||||
log_success "Saved repository version as ${filename}.new"
|
||||
break
|
||||
;;
|
||||
s)
|
||||
log_info "Skipping $home_file"
|
||||
break
|
||||
;;
|
||||
i)
|
||||
local relative_path_to_home="${home_file#$HOME/}"
|
||||
echo "$relative_path_to_home" >>"$HOME_UPDATE_IGNORE_FILE"
|
||||
log_success "Added '$relative_path_to_home' to $HOME_UPDATE_IGNORE_FILE and skipped."
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Please try again."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
6)
|
||||
log_info "Skipping $home_file"
|
||||
break
|
||||
;;
|
||||
7)
|
||||
local relative_path_to_home="${home_file#$HOME/}"
|
||||
echo "$relative_path_to_home" >>"$HOME_UPDATE_IGNORE_FILE"
|
||||
log_success "Added '$relative_path_to_home' to $HOME_UPDATE_IGNORE_FILE and skipped."
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Please enter 1-7."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Function to check if PKGBUILD has changed
|
||||
check_pkgbuild_changed() {
|
||||
local pkg_dir="$1"
|
||||
local pkgbuild_path="${pkg_dir}/PKGBUILD"
|
||||
|
||||
[[ ! -f "$pkgbuild_path" ]] && return 1
|
||||
|
||||
# Get the path relative to repo
|
||||
local relative_path="${pkgbuild_path#$REPO_DIR/}"
|
||||
|
||||
# If force check is enabled, always return true
|
||||
if [[ "$FORCE_CHECK" == true ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if file changed in the last pull
|
||||
if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q "^${relative_path}$"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to list available packages
|
||||
list_packages() {
|
||||
local available_packages=()
|
||||
local changed_packages=()
|
||||
|
||||
if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then
|
||||
log_warning "No sdist/arch directory found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
local pkg_name=$(basename "$pkg_dir")
|
||||
available_packages+=("$pkg_name")
|
||||
|
||||
if check_pkgbuild_changed "$pkg_dir"; then
|
||||
changed_packages+=("$pkg_name")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#available_packages[@]} -eq 0 ]]; then
|
||||
log_info "No packages found in sdist/arch directory"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "\n${CYAN}Available packages:${NC}"
|
||||
for pkg in "${available_packages[@]}"; do
|
||||
if [[ " ${changed_packages[*]} " =~ " ${pkg} " ]]; then
|
||||
echo -e " ${GREEN}● ${pkg}${NC} (PKGBUILD changed)"
|
||||
else
|
||||
echo -e " ○ ${pkg}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#changed_packages[@]} -gt 0 ]]; then
|
||||
echo -e "\n${YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${NC}"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to build selected packages
|
||||
build_packages() {
|
||||
local build_mode="$1" # "changed", "all", or "select"
|
||||
local packages_to_build=()
|
||||
local rebuilt_packages=0
|
||||
|
||||
case "$build_mode" in
|
||||
"changed")
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
local pkg_name=$(basename "$pkg_dir")
|
||||
if check_pkgbuild_changed "$pkg_dir"; then
|
||||
packages_to_build+=("$pkg_name")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
;;
|
||||
"all")
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
local pkg_name=$(basename "$pkg_dir")
|
||||
packages_to_build+=("$pkg_name")
|
||||
fi
|
||||
done
|
||||
;;
|
||||
"select")
|
||||
echo -e "\nEnter package names separated by spaces (or 'all' for all packages):"
|
||||
if ! safe_read "Packages to build: " user_selection ""; then
|
||||
log_warning "Failed to read input. Skipping package builds."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$user_selection" == "all" ]]; then
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
local pkg_name=$(basename "$pkg_dir")
|
||||
packages_to_build+=("$pkg_name")
|
||||
fi
|
||||
done
|
||||
else
|
||||
read -ra packages_to_build <<<"$user_selection"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${#packages_to_build[@]} -eq 0 ]]; then
|
||||
log_info "No packages selected for building"
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\n${CYAN}Packages to build: ${packages_to_build[*]}${NC}"
|
||||
|
||||
if ! safe_read "Proceed with building these packages? (Y/n): " confirm "Y"; then
|
||||
log_warning "Failed to read input. Skipping package builds."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$confirm" =~ ^[Nn]$ ]]; then
|
||||
log_info "Package building cancelled by user"
|
||||
return
|
||||
fi
|
||||
|
||||
for pkg_name in "${packages_to_build[@]}"; do
|
||||
local pkg_dir="${ARCH_PACKAGES_DIR}/${pkg_name}"
|
||||
|
||||
if [[ ! -d "$pkg_dir" || ! -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
log_error "Package not found or missing PKGBUILD: $pkg_name"
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Building package: $pkg_name"
|
||||
cd "$pkg_dir" || continue
|
||||
|
||||
if makepkg -si --noconfirm; then
|
||||
log_success "Successfully built and installed $pkg_name"
|
||||
((rebuilt_packages++))
|
||||
else
|
||||
log_error "Failed to build package $pkg_name"
|
||||
fi
|
||||
|
||||
cd "$REPO_DIR" || die "Failed to return to repository directory"
|
||||
done
|
||||
|
||||
if [[ $rebuilt_packages -eq 0 ]]; then
|
||||
log_warning "No packages were successfully built"
|
||||
else
|
||||
log_success "Successfully rebuilt $rebuilt_packages package(s)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get list of changed files since last pull or all files if force check
|
||||
get_changed_files() {
|
||||
local dir_path="$1"
|
||||
|
||||
if [[ "$FORCE_CHECK" == true ]]; then
|
||||
# Return all files in the directory
|
||||
find "$dir_path" -type f -print0 2>/dev/null
|
||||
else
|
||||
# Get files that changed in the last pull
|
||||
local changed_files=()
|
||||
while IFS= read -r file; do
|
||||
local full_path="${REPO_DIR}/${file}"
|
||||
# Check if file is in the directory we're processing
|
||||
if [[ "$full_path" == "$dir_path"/* ]] && [[ -f "$full_path" ]]; then
|
||||
printf '%s\0' "$full_path"
|
||||
fi
|
||||
done < <(git diff --name-only HEAD@{1} HEAD 2>/dev/null || true)
|
||||
|
||||
# If no files changed via git, but force_check is false, still check all files
|
||||
# This handles the case where there were no new commits but files might differ
|
||||
if ! git diff --quiet HEAD@{1} HEAD 2>/dev/null; then
|
||||
: # Files were found via git diff
|
||||
else
|
||||
# No git changes detected, check all files anyway for local differences
|
||||
find "$dir_path" -type f -print0 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check if we have new commits
|
||||
has_new_commits() {
|
||||
# Check if HEAD@{1} exists (meaning there was a previous commit)
|
||||
if git rev-parse --verify HEAD@{1} &>/dev/null; then
|
||||
# Check if HEAD and HEAD@{1} are different
|
||||
[[ "$(git rev-parse HEAD)" != "$(git rev-parse HEAD@{1})" ]]
|
||||
else
|
||||
# No previous commit reference, assume we have commits
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Main script starts here
|
||||
log_header "Dotfiles Update Script"
|
||||
|
||||
check=true
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-f | --force)
|
||||
FORCE_CHECK=true
|
||||
log_info "Force check mode enabled - will check all files regardless of git changes"
|
||||
shift
|
||||
;;
|
||||
-p | --packages)
|
||||
CHECK_PACKAGES=true
|
||||
log_info "Package checking enabled"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -f, --force Force check all files even if no new commits"
|
||||
echo " -p, --packages Enable package checking and building"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "This script updates your dotfiles by:"
|
||||
echo " 1. Pulling latest changes from git remote"
|
||||
echo " 2. Optionally rebuilding packages (if -p flag is used)"
|
||||
echo " 3. Syncing configuration files"
|
||||
echo " 4. Updating script permissions"
|
||||
echo ""
|
||||
echo "Package modes (when -p is used):"
|
||||
echo " - If no PKGBUILDs changed: asks if you want to check packages anyway"
|
||||
echo " - If PKGBUILDs changed: offers to build changed packages"
|
||||
echo " - Interactive selection of packages to build"
|
||||
exit 0
|
||||
;;
|
||||
--skip-notice)
|
||||
log_warning "Skipping notice about script being untested"
|
||||
check=false
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$check" == true ]]; then
|
||||
log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!"
|
||||
log_warning "It might be safer if you want to preserve your modifications and not delete added files,"
|
||||
log_warning " but this can cause partial updates and therefore unexpected behavior like in #1856."
|
||||
log_warning "In general, prefer install.sh for updates."
|
||||
safe_read "Continue? (y/N): " response "N"
|
||||
|
||||
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||
log_error "Update aborted by user"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if we're in a git repository
|
||||
cd "$REPO_DIR" || die "Failed to change to repository directory"
|
||||
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
log_info "Running in git repository: $(git rev-parse --show-toplevel)"
|
||||
else
|
||||
log_error "Not in a git repository. Please run this script from your dotfiles repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 1: Pull latest commits
|
||||
log_header "Pulling Latest Changes"
|
||||
|
||||
# Check current branch
|
||||
current_branch=$(git branch --show-current)
|
||||
if [[ -z "$current_branch" ]]; then
|
||||
log_warning "In detached HEAD state. Checking out main/master branch..."
|
||||
if git show-ref --verify --quiet refs/heads/main; then
|
||||
git checkout main
|
||||
current_branch="main"
|
||||
elif git show-ref --verify --quiet refs/heads/master; then
|
||||
git checkout master
|
||||
current_branch="master"
|
||||
else
|
||||
die "Could not find main or master branch"
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Current branch: $current_branch"
|
||||
|
||||
# Check for uncommitted changes
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
log_warning "You have uncommitted changes:"
|
||||
git status --short
|
||||
echo
|
||||
|
||||
if ! safe_read "Do you want to continue? This will stash your changes. (y/N): " response "N"; then
|
||||
echo
|
||||
log_error "Failed to read input. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||
die "Aborted by user"
|
||||
fi
|
||||
git stash push -m "Auto-stash before update $(date)"
|
||||
log_info "Changes stashed"
|
||||
fi
|
||||
|
||||
# Check if remote exists
|
||||
if git remote get-url origin &>/dev/null; then
|
||||
# Pull changes
|
||||
log_info "Pulling changes from origin/$current_branch..."
|
||||
if git pull; then
|
||||
log_success "Successfully pulled latest changes"
|
||||
else
|
||||
log_warning "Failed to pull changes from remote. Continuing with local repository..."
|
||||
log_info "You may need to resolve conflicts manually later."
|
||||
fi
|
||||
else
|
||||
log_warning "No remote 'origin' configured. Skipping pull operation."
|
||||
log_info "This appears to be a local-only repository."
|
||||
fi
|
||||
|
||||
# Step 2: Handle package building (only if requested)
|
||||
rebuilt_packages=0
|
||||
|
||||
if [[ "$CHECK_PACKAGES" == true ]]; then
|
||||
log_header "Package Management"
|
||||
|
||||
if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then
|
||||
log_warning "No sdist/arch directory found. Skipping package management."
|
||||
else
|
||||
# Check if any PKGBUILDs have changed
|
||||
changed_pkgbuilds=()
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]]; then
|
||||
local pkg_name=$(basename "$pkg_dir")
|
||||
if check_pkgbuild_changed "$pkg_dir"; then
|
||||
changed_pkgbuilds+=("$pkg_name")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#changed_pkgbuilds[@]} -gt 0 ]]; then
|
||||
log_info "Found ${#changed_pkgbuilds[@]} package(s) with changed PKGBUILDs: ${changed_pkgbuilds[*]}"
|
||||
echo
|
||||
echo "Package build options:"
|
||||
echo "1) Build only packages with changed PKGBUILDs"
|
||||
echo "2) List all packages and select which to build"
|
||||
echo "3) Build all packages"
|
||||
echo "4) Skip package building"
|
||||
echo
|
||||
|
||||
if safe_read "Choose an option (1-4): " pkg_choice "1"; then
|
||||
case $pkg_choice in
|
||||
1)
|
||||
build_packages "changed"
|
||||
;;
|
||||
2)
|
||||
if list_packages; then
|
||||
build_packages "select"
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
build_packages "all"
|
||||
;;
|
||||
4 | *)
|
||||
log_info "Skipping package building"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_warning "Failed to read input. Skipping package building."
|
||||
fi
|
||||
else
|
||||
log_info "No PKGBUILDs have changed since last update."
|
||||
echo
|
||||
if safe_read "Do you want to check and build packages anyway? (y/N): " check_anyway "N"; then
|
||||
if [[ "$check_anyway" =~ ^[Yy]$ ]]; then
|
||||
if list_packages; then
|
||||
echo
|
||||
echo "Package build options:"
|
||||
echo "1) Select specific packages to build"
|
||||
echo "2) Build all packages"
|
||||
echo "3) Skip package building"
|
||||
|
||||
if safe_read "Choose an option (1-3): " build_choice "3"; then
|
||||
case $build_choice in
|
||||
1)
|
||||
build_packages "select"
|
||||
;;
|
||||
2)
|
||||
build_packages "all"
|
||||
;;
|
||||
3 | *)
|
||||
log_info "Skipping package building"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_info "Skipping package building"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_info "Skipping package management"
|
||||
fi
|
||||
else
|
||||
log_info "Skipping package management"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_header "Package Management"
|
||||
log_info "Package checking disabled. Use -p or --packages flag to enable package management."
|
||||
|
||||
# Still show a hint if there are changed PKGBUILDs
|
||||
if [[ -d "$ARCH_PACKAGES_DIR" ]]; then
|
||||
changed_count=0
|
||||
for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do
|
||||
if [[ -f "${pkg_dir}/PKGBUILD" ]] && check_pkgbuild_changed "$pkg_dir"; then
|
||||
((changed_count++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $changed_count -gt 0 ]]; then
|
||||
log_warning "Note: $changed_count package(s) have changed PKGBUILDs. Use -p flag to manage packages."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 3: Update configuration files
|
||||
log_header "Updating Configuration Files"
|
||||
|
||||
# Check if we should process files
|
||||
process_files=false
|
||||
if [[ "$FORCE_CHECK" == true ]]; then
|
||||
process_files=true
|
||||
log_info "Force mode: checking all configuration files"
|
||||
elif has_new_commits; then
|
||||
process_files=true
|
||||
log_info "New commits detected: checking changed configuration files"
|
||||
else
|
||||
log_info "No new commits found: checking for local file differences"
|
||||
process_files=true # Always check for differences even without commits
|
||||
fi
|
||||
|
||||
if [[ "$process_files" == true ]]; then
|
||||
files_processed=0
|
||||
files_updated=0
|
||||
files_created=0
|
||||
|
||||
for dir_name in "${MONITOR_DIRS[@]}"; do
|
||||
repo_dir_path="${REPO_DIR}/${dir_name}"
|
||||
home_dir_path="${HOME}/${dir_name}"
|
||||
|
||||
if [[ ! -d "$repo_dir_path" ]]; then
|
||||
log_warning "Repository directory not found: $repo_dir_path"
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Processing directory: $dir_name"
|
||||
|
||||
# Create home directory if it doesn't exist
|
||||
mkdir -p "$home_dir_path"
|
||||
|
||||
# Get files to process (changed files or all files based on mode)
|
||||
while IFS= read -r -d '' repo_file; do
|
||||
# Calculate relative path and corresponding home file path
|
||||
rel_path="${repo_file#$repo_dir_path/}"
|
||||
home_file="${home_dir_path}/${rel_path}"
|
||||
|
||||
# Check if file should be ignored
|
||||
if should_ignore "$home_file"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
((files_processed++))
|
||||
|
||||
# Create directory structure if needed
|
||||
mkdir -p "$(dirname "$home_file")"
|
||||
|
||||
if [[ -f "$home_file" ]]; then
|
||||
# File exists, check if different
|
||||
if ! cmp -s "$repo_file" "$home_file"; then
|
||||
log_info "Found difference in: $rel_path"
|
||||
handle_file_conflict "$repo_file" "$home_file"
|
||||
((files_updated++))
|
||||
fi
|
||||
else
|
||||
# New file, copy it
|
||||
cp -p "$repo_file" "$home_file"
|
||||
log_success "Created new file: $home_file"
|
||||
((files_created++))
|
||||
fi
|
||||
done < <(get_changed_files "$repo_dir_path")
|
||||
done
|
||||
|
||||
# Show processing summary
|
||||
echo
|
||||
log_info "File processing summary:"
|
||||
log_info "- Files processed: $files_processed"
|
||||
log_info "- Files with conflicts: $files_updated"
|
||||
log_info "- New files created: $files_created"
|
||||
else
|
||||
log_info "Skipping file updates (no changes detected and not in force mode)"
|
||||
fi
|
||||
|
||||
# Step 4: Update script permissions
|
||||
log_header "Updating Script Permissions"
|
||||
|
||||
# Make sure local bin scripts are executable
|
||||
if [[ -d "${HOME}/.local/bin" ]]; then
|
||||
find "${HOME}/.local/bin" -type f -exec chmod +x {} \; 2>/dev/null || true
|
||||
log_success "Updated ~/.local/bin script permissions"
|
||||
fi
|
||||
|
||||
log_header "Update Complete"
|
||||
log_success "Dotfiles update completed successfully!"
|
||||
|
||||
# Show summary
|
||||
echo
|
||||
echo -e "${CYAN}Summary:${NC}"
|
||||
echo "- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)')"
|
||||
echo "- Branch: $current_branch"
|
||||
echo "- Mode: $([ "$FORCE_CHECK" == true ] && echo "Force check" || echo "Normal")"
|
||||
echo "- Package checking: $([ "$CHECK_PACKAGES" == true ] && echo "Enabled" || echo "Disabled")"
|
||||
|
||||
if [[ $rebuilt_packages -gt 0 ]]; then
|
||||
echo "- Packages rebuilt: $rebuilt_packages"
|
||||
fi
|
||||
|
||||
if [[ "$process_files" == true ]]; then
|
||||
echo "- Files processed: $files_processed"
|
||||
echo "- Files updated/conflicted: $files_updated"
|
||||
echo "- New files created: $files_created"
|
||||
fi
|
||||
|
||||
echo "- Configuration directories: ${MONITOR_DIRS[*]}"
|
||||
|
||||
# Remind about ignore files and show examples
|
||||
if [[ ! -f "$HOME_UPDATE_IGNORE_FILE" && ! -f "$UPDATE_IGNORE_FILE" ]]; then
|
||||
echo
|
||||
log_info "Tip: Create ignore files to exclude files from updates:"
|
||||
echo " - Repository ignore: ${REPO_DIR}/.updateignore"
|
||||
echo " - User ignore: ~/.updateignore"
|
||||
echo
|
||||
echo "Example patterns:"
|
||||
echo " *.log # Ignore all .log files"
|
||||
echo " .config/personal/ # Ignore entire directory"
|
||||
echo " secret-config.conf # Ignore specific file"
|
||||
echo " /temp-file # Ignore from root only"
|
||||
echo " *secret* # Ignore files containing 'secret'"
|
||||
fi
|
||||
|
||||
echo
|
||||
+206
-10
@@ -1,6 +1,8 @@
|
||||
# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang.
|
||||
# NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
# The script that use this file should have two lines on its top as follows:
|
||||
# cd "$(dirname "$0")"
|
||||
# export base="$(pwd)"
|
||||
@@ -9,7 +11,7 @@ function try { "$@" || sleep 0; }
|
||||
function v(){
|
||||
echo -e "####################################################"
|
||||
echo -e "${STY_BLUE}[$0]: Next command:${STY_RST}"
|
||||
echo -e "${STY_GREEN}$@${STY_RST}"
|
||||
echo -e "${STY_GREEN}$*${STY_RST}"
|
||||
local execute=true
|
||||
if $ask;then
|
||||
while true;do
|
||||
@@ -29,14 +31,14 @@ function v(){
|
||||
done
|
||||
fi
|
||||
if $execute;then x "$@";else
|
||||
echo -e "${STY_YELLOW}[$0]: Skipped \"$@\"${STY_RST}"
|
||||
echo -e "${STY_YELLOW}[$0]: Skipped \"$*\"${STY_RST}"
|
||||
fi
|
||||
}
|
||||
# When use v() for a defined function, use x() INSIDE its definition to catch errors.
|
||||
function x(){
|
||||
if "$@";then local cmdstatus=0;else local cmdstatus=1;fi # 0=normal; 1=failed; 2=failed but ignored
|
||||
while [ $cmdstatus == 1 ] ;do
|
||||
echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$@${STY_RED}\" has failed."
|
||||
echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed."
|
||||
echo -e "You may need to resolve the problem manually BEFORE repeating this command."
|
||||
echo -e "[Tip] If a certain package is failing to install, try installing it separately in another terminal.${STY_RST}"
|
||||
echo " r = Repeat this command (DEFAULT)"
|
||||
@@ -52,15 +54,15 @@ function x(){
|
||||
esac
|
||||
done
|
||||
case $cmdstatus in
|
||||
0) echo -e "${STY_BLUE}[$0]: Command \"${STY_GREEN}$@${STY_BLUE}\" finished.${STY_RST}";;
|
||||
1) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$@${STY_RED}\" has failed. Exiting...${STY_RST}";exit 1;;
|
||||
2) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$@${STY_RED}\" has failed but ignored by user.${STY_RST}";;
|
||||
0) echo -e "${STY_BLUE}[$0]: Command \"${STY_GREEN}$*${STY_BLUE}\" finished.${STY_RST}";;
|
||||
1) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed. Exiting...${STY_RST}";exit 1;;
|
||||
2) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed but ignored by user.${STY_RST}";;
|
||||
esac
|
||||
}
|
||||
function showfun(){
|
||||
echo -e "${STY_BLUE}[$0]: The definition of function \"$1\" is as follows:${STY_RST}"
|
||||
printf "${STY_GREEN}"
|
||||
type -a $1
|
||||
type -a "$1" 2>/dev/null || return 1
|
||||
printf "${STY_RST}"
|
||||
}
|
||||
function pause(){
|
||||
@@ -71,8 +73,7 @@ function pause(){
|
||||
fi
|
||||
}
|
||||
function remove_bashcomments_emptylines(){
|
||||
mkdir -p $(dirname $2)
|
||||
cat $1 | sed -e '/^[[:blank:]]*#/d;s/#.*//' -e '/^[[:space:]]*$/d' > $2
|
||||
mkdir -p "$(dirname "$2")" && cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2"
|
||||
}
|
||||
function prevent_sudo_or_root(){
|
||||
case $(whoami) in
|
||||
@@ -93,5 +94,200 @@ function latest_commit_timestamp(){
|
||||
echo "[latest_commit_timestamp] The timestamp of \"$target_path\" is empty. Aborting..." >&2
|
||||
return 1
|
||||
fi
|
||||
echo $result
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
function log_info() {
|
||||
echo -e "${STY_BLUE}[INFO]${STY_RST} $1"
|
||||
}
|
||||
function log_success() {
|
||||
echo -e "${STY_GREEN}[SUCCESS]${STY_RST} $1"
|
||||
}
|
||||
function log_warning() {
|
||||
echo -e "${STY_YELLOW}[WARNING]${STY_RST} $1"
|
||||
}
|
||||
function log_error() {
|
||||
echo -e "${STY_RED}[ERROR]${STY_RST} $1" >&2
|
||||
}
|
||||
function log_header() {
|
||||
echo -e "\n${STY_PURPLE}=== $1 ===${STY_RST}"
|
||||
}
|
||||
function log_die() {
|
||||
log_error "$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Enhanced: Check if command exists
|
||||
function command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Enhanced: Require a command or die
|
||||
function require_command() {
|
||||
if ! command_exists "$1"; then
|
||||
log_die "Required command '$1' not found. Please install it first."
|
||||
fi
|
||||
}
|
||||
|
||||
# Enhanced: Sanitize file paths to prevent directory traversal
|
||||
function sanitize_path() {
|
||||
local path="$1"
|
||||
|
||||
# Remove null bytes, newlines, and control characters
|
||||
path=$(echo "$path" | tr -d '\000-\037')
|
||||
|
||||
# Prevent directory traversal beyond current context
|
||||
case "$path" in
|
||||
..|../*|*/../*|*/..|\.\./*)
|
||||
log_die "Invalid path detected (directory traversal attempt): $path"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
# Enhanced: Safe file comparison that checks existence first
|
||||
function files_differ() {
|
||||
local file1="$1"
|
||||
local file2="$2"
|
||||
|
||||
# Check if both files exist
|
||||
if [[ ! -f "$file1" ]] || [[ ! -f "$file2" ]]; then
|
||||
return 0 # Consider them different if either doesn't exist
|
||||
fi
|
||||
|
||||
# Quick size check first (faster than byte comparison)
|
||||
local size1 size2
|
||||
if command -v stat &>/dev/null; then
|
||||
# Try both BSD and GNU stat formats
|
||||
size1=$(stat -f%z "$file1" 2>/dev/null || stat -c%s "$file1" 2>/dev/null)
|
||||
size2=$(stat -f%z "$file2" 2>/dev/null || stat -c%s "$file2" 2>/dev/null)
|
||||
|
||||
if [[ "$size1" != "$size2" ]]; then
|
||||
return 0 # Different sizes = different files
|
||||
fi
|
||||
fi
|
||||
|
||||
# Then byte-by-byte comparison
|
||||
cmp -s "$file1" "$file2" && return 1 || return 0
|
||||
}
|
||||
|
||||
# Enhanced: Create backup of a file with timestamp
|
||||
function backup_file_simple() {
|
||||
local file="$1"
|
||||
local backup_suffix="${2:-.bak}"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
log_warning "Cannot backup non-existent file: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local timestamp
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
local backup_name="${file}${backup_suffix}.${timestamp}"
|
||||
|
||||
if cp -p "$file" "$backup_name" 2>/dev/null; then
|
||||
log_info "Backed up: $file → $backup_name"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to backup: $file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Enhanced: Validate that a file path is within allowed directory
|
||||
function validate_path_in_directory() {
|
||||
local file_path="$1"
|
||||
local allowed_dir="$2"
|
||||
|
||||
# Resolve to absolute paths
|
||||
local abs_file
|
||||
local abs_dir
|
||||
|
||||
abs_file=$(cd "$(dirname "$file_path")" 2>/dev/null && pwd -P)/$(basename "$file_path") || return 1
|
||||
abs_dir=$(cd "$allowed_dir" 2>/dev/null && pwd -P) || return 1
|
||||
|
||||
# Check if file path starts with allowed directory
|
||||
case "$abs_file" in
|
||||
"$abs_dir"/*)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
log_error "Path validation failed: $file_path is not within $allowed_dir"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Enhanced: Check if script is running in a CI/CD environment
|
||||
function is_ci_environment() {
|
||||
[[ -n "${CI:-}" ]] || \
|
||||
[[ -n "${GITHUB_ACTIONS:-}" ]] || \
|
||||
[[ -n "${GITLAB_CI:-}" ]] || \
|
||||
[[ -n "${TRAVIS:-}" ]] || \
|
||||
[[ -n "${CIRCLECI:-}" ]]
|
||||
}
|
||||
|
||||
# Enhanced: Progress bar (optional, for long operations)
|
||||
function show_progress() {
|
||||
local current="$1"
|
||||
local total="$2"
|
||||
local message="${3:-Processing}"
|
||||
|
||||
if ! command_exists tput; then
|
||||
return
|
||||
fi
|
||||
|
||||
local percent=$((current * 100 / total))
|
||||
local bar_length=40
|
||||
local filled=$((bar_length * current / total))
|
||||
local empty=$((bar_length - filled))
|
||||
|
||||
printf "\r${message}: [" >&2
|
||||
printf "%${filled}s" | tr ' ' '=' >&2
|
||||
printf "%${empty}s" | tr ' ' ' ' >&2
|
||||
printf "] %d%%" "$percent" >&2
|
||||
|
||||
if [[ $current -eq $total ]]; then
|
||||
echo >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Enhanced: Cleanup temporary files on exit
|
||||
declare -a TEMP_FILES_TO_CLEANUP=()
|
||||
|
||||
function register_temp_file() {
|
||||
local temp_file="$1"
|
||||
TEMP_FILES_TO_CLEANUP+=("$temp_file")
|
||||
}
|
||||
|
||||
function cleanup_temp_files() {
|
||||
for temp_file in "${TEMP_FILES_TO_CLEANUP[@]}"; do
|
||||
if [[ -f "$temp_file" ]]; then
|
||||
rm -f "$temp_file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
TEMP_FILES_TO_CLEANUP=()
|
||||
}
|
||||
|
||||
# Enhanced: Check disk space before operations
|
||||
function check_disk_space() {
|
||||
local path="${1:-.}"
|
||||
local required_mb="${2:-100}" # Default 100MB
|
||||
|
||||
if ! command_exists df; then
|
||||
log_warning "df command not available, skipping disk space check"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local available_kb
|
||||
available_kb=$(df -k "$path" | awk 'NR==2 {print $4}')
|
||||
local available_mb=$((available_kb / 1024))
|
||||
|
||||
if [[ $available_mb -lt $required_mb ]]; then
|
||||
log_warning "Low disk space: ${available_mb}MB available, ${required_mb}MB recommended"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Handle args for subcmd: exp-update
|
||||
# shellcheck shell=bash
|
||||
|
||||
showhelp(){
|
||||
echo -e "Usage: install.sh exp-update [OPTIONS]...
|
||||
|
||||
Experimental updating without full reinstall.
|
||||
Updates dotfiles by syncing configuration files to home directory.
|
||||
|
||||
Options:
|
||||
-f, --force Force check all files even if no new commits
|
||||
-p, --packages Enable package checking and building
|
||||
-n, --dry-run Show what would be done without making changes
|
||||
-v, --verbose Enable verbose output
|
||||
-h, --help Show this help message
|
||||
-s, --skip-notice Skip notice about script being untested
|
||||
--non-interactive Run without prompting for user input
|
||||
|
||||
This script updates your dotfiles by:
|
||||
1. Auto-detecting repository structure (dots/ prefix or direct)
|
||||
2. Pulling latest changes from git remote
|
||||
3. Optionally rebuilding packages (if -p flag is used)
|
||||
4. Syncing configuration files to home directory
|
||||
5. Updating script permissions
|
||||
|
||||
Ignore file patterns support:
|
||||
- Exact matches (e.g., 'path/to/file')
|
||||
- Directory patterns (e.g., 'path/to/dir/')
|
||||
- Wildcards (e.g., '*.log', 'path/*/file')
|
||||
- Root-relative patterns (e.g., '/.config')
|
||||
- Substring matching (prefix with '**', e.g., '**temp' matches any path containing 'temp')
|
||||
"
|
||||
}
|
||||
# `man getopt` to see more
|
||||
para=$(getopt \
|
||||
-o hfpnv \
|
||||
-l help,force,packages,dry-run,verbose,skip-notice,non-interactive \
|
||||
-n "$0" -- "$@")
|
||||
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
|
||||
#####################################################################################
|
||||
## getopt Phase 1
|
||||
# ignore parameter's order, execute options below first
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
-h|--help) showhelp;exit;;
|
||||
--) break ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
#####################################################################################
|
||||
## getopt Phase 2
|
||||
|
||||
FORCE_CHECK=false
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
VERBOSE=false
|
||||
SKIP_NOTICE=false
|
||||
NON_INTERACTIVE=false
|
||||
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
## Ones without parameter
|
||||
-f|--force) FORCE_CHECK=true;shift;;
|
||||
# log_info "Force check mode enabled - will check all files regardless of git changes"
|
||||
-p|--packages) CHECK_PACKAGES=true;shift;;
|
||||
# log_info "Package checking enabled"
|
||||
-n|--dry-run) DRY_RUN=true;shift;;
|
||||
# log_info "Dry-run mode enabled - no changes will be made"
|
||||
-v|--verbose) VERBOSE=true;shift;;
|
||||
# log_info "Verbose mode enabled"
|
||||
--skip-notice) SKIP_NOTICE=true;shift;;
|
||||
# log_warning "Skipping notice about script being untested"
|
||||
--non-interactive) NON_INTERACTIVE=true;shift;;
|
||||
# log_info "Non-interactive mode enabled"
|
||||
|
||||
## Ending
|
||||
--) break ;;
|
||||
*) echo -e "$0: Wrong parameters.";exit 1;;
|
||||
esac
|
||||
done
|
||||
@@ -0,0 +1,83 @@
|
||||
# Handle args for subcmd: install
|
||||
# shellcheck shell=bash
|
||||
showhelp(){
|
||||
echo -e "Syntax: $0 [OPTIONS]...
|
||||
|
||||
Idempotent installation script for dotfiles.
|
||||
|
||||
Options for install:
|
||||
-h, --help Print this help message and exit
|
||||
-f, --force (Dangerous) Force mode without any confirm
|
||||
-c, --clean Clean the build cache first
|
||||
--skip-allgreeting Skip the whole process greeting
|
||||
--skip-alldeps Skip the whole process installing dependency
|
||||
--skip-allsetups Skip the whole process setting up permissions/services etc
|
||||
--skip-allfiles Skip the whole process copying configuration files
|
||||
-s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\"
|
||||
--skip-hyprland Skip installing the config for Hyprland
|
||||
--skip-fish Skip installing the config for Fish
|
||||
--skip-plasmaintg Skip installing plasma-browser-integration
|
||||
--skip-miscconf Skip copying the dirs and files to \".configs\" except for
|
||||
AGS, Fish and Hyprland
|
||||
--exp-files Use experimental script for the third step copying files
|
||||
--fontset <set> (Unavailable yet) Use a set of pre-defined font and config
|
||||
--via-nix (Unavailable yet) Use Nix to install dependencies
|
||||
"
|
||||
}
|
||||
|
||||
cleancache(){
|
||||
rm -rf "$base/cache"
|
||||
}
|
||||
|
||||
# `man getopt` to see more
|
||||
para=$(getopt \
|
||||
-o hfk:cs \
|
||||
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \
|
||||
-n "$0" -- "$@")
|
||||
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
|
||||
#####################################################################################
|
||||
## getopt Phase 1
|
||||
# ignore parameter's order, execute options below first
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
-h|--help) showhelp;exit;;
|
||||
-c|--clean) cleancache;shift;;
|
||||
--) break ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
#####################################################################################
|
||||
## getopt Phase 2
|
||||
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
## Already processed in phase 1, but not exited
|
||||
-c|--clean) shift;;
|
||||
## Ones without parameter
|
||||
-f|--force) ask=false;shift;;
|
||||
--skip-allgreeting) SKIP_ALLGREETING=true;shift;;
|
||||
--skip-alldeps) SKIP_ALLDEPS=true;shift;;
|
||||
--skip-allsetups) SKIP_ALLSETUPS=true;shift;;
|
||||
--skip-allfiles) SKIP_ALLFILES=true;shift;;
|
||||
-s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;
|
||||
--skip-hyprland) SKIP_HYPRLAND=true;shift;;
|
||||
--skip-fish) SKIP_FISH=true;shift;;
|
||||
--skip-miscconf) SKIP_MISCCONF=true;shift;;
|
||||
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
|
||||
--exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
|
||||
--via-nix) INSTALL_VIA_NIX=true;shift;;
|
||||
|
||||
## Ones with parameter
|
||||
--fontset)
|
||||
case $2 in
|
||||
"default"|"zh-CN"|"vi") fontset="$2";;
|
||||
*) echo -e "Wrong argument for $1.";exit 1;;
|
||||
esac;echo "The fontset is ${fontset}.";shift 2;;
|
||||
|
||||
## Ending
|
||||
--) break ;;
|
||||
*) echo -e "$0: Wrong parameters.";exit 1;;
|
||||
esac
|
||||
done
|
||||
+48
-60
@@ -1,14 +1,19 @@
|
||||
# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang.
|
||||
# NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
# The script that use this file should have two lines on its top as follows:
|
||||
# cd "$(dirname "$0")" export base="$(pwd)"
|
||||
showhelp(){
|
||||
echo -e "Syntax: $0 [Options]...
|
||||
showhelp_global(){
|
||||
echo -e "Syntax: $0 [subcommand] [options]...
|
||||
|
||||
Idempotent installation script for dotfiles.
|
||||
If no option is specified, run default install process.
|
||||
If no option nor subcommand is specified, run default install process.
|
||||
|
||||
Subcommand:
|
||||
install The default subcommand which can be omitted.
|
||||
Options for install:
|
||||
-h, --help Print this help message and exit
|
||||
-f, --force (Dangerous) Force mode without any confirm
|
||||
-c, --clean Clean the build cache first
|
||||
@@ -25,64 +30,47 @@ If no option is specified, run default install process.
|
||||
--exp-files Use experimental script for the third step copying files
|
||||
--fontset <set> (Unavailable yet) Use a set of pre-defined font and config
|
||||
--via-nix (Unavailable yet) Use Nix to install dependencies
|
||||
--exp-uninstall Use experimental uninstall script
|
||||
|
||||
Subcommand:
|
||||
exp-uninstall Using experimental uninstall script.
|
||||
|
||||
Subcommand:
|
||||
exp-update Using experimental update script.
|
||||
Options for exp-update:
|
||||
-f, --force Force check all files even if no new commits (update script)
|
||||
-p, --packages Enable package checking and building (update script)
|
||||
-n, --dry-run Show what would be done without making changes (update script)
|
||||
-v, --verbose Enable verbose output (update script)
|
||||
--skip-notice Skip warning notice (for experimental scripts)
|
||||
--non-interactive Run without prompting for user input
|
||||
"
|
||||
}
|
||||
|
||||
cleancache(){
|
||||
rm -rf "$base/cache"
|
||||
}
|
||||
# Handle subcommand
|
||||
case $1 in
|
||||
# subcommand specified
|
||||
install|exp-uninstall|exp-update)
|
||||
SCRIPT_SUBCOMMAND=$1
|
||||
shift
|
||||
;;
|
||||
# Global help
|
||||
help|--help|-h)
|
||||
showhelp_global;exit
|
||||
;;
|
||||
# no subcommand (has options: -* ; no options: "")
|
||||
-*|"")
|
||||
SCRIPT_SUBCOMMAND=install
|
||||
;;
|
||||
# wrong subcommand
|
||||
*)echo "Unknown subcommand \"$1\", aborting...";exit 1;;
|
||||
esac
|
||||
|
||||
# `man getopt` to see more
|
||||
para=$(getopt \
|
||||
-o hfk:cs \
|
||||
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix,exp-uninstall \
|
||||
-n "$0" -- "$@")
|
||||
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
|
||||
#####################################################################################
|
||||
## getopt Phase 1
|
||||
# ignore parameter's order, execute options below first
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
-h|--help) showhelp;exit;;
|
||||
-c|--clean) cleancache;shift;;
|
||||
--) break ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
#####################################################################################
|
||||
## getopt Phase 2
|
||||
|
||||
eval set -- "$para"
|
||||
while true ; do
|
||||
case "$1" in
|
||||
## Already processed in phase 1, but not exited
|
||||
-c|--clean) shift;;
|
||||
## Ones without parameter
|
||||
-f|--force) ask=false;shift;;
|
||||
--skip-allgreeting) SKIP_ALLGREETING=true;shift;;
|
||||
--skip-alldeps) SKIP_ALLDEPS=true;shift;;
|
||||
--skip-allsetups) SKIP_ALLSETUPS=true;shift;;
|
||||
--skip-allfiles) SKIP_ALLFILES=true;shift;;
|
||||
-s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;
|
||||
--skip-hyprland) SKIP_HYPRLAND=true;shift;;
|
||||
--skip-fish) SKIP_FISH=true;shift;;
|
||||
--skip-miscconf) SKIP_MISCCONF=true;shift;;
|
||||
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
|
||||
--exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
|
||||
--via-nix) INSTALL_VIA_NIX=true;shift;;
|
||||
--exp-uninstall) EXPERIMENTAL_UNINSTALL_SCRIPT=true;shift;;
|
||||
## Ones with parameter
|
||||
|
||||
--fontset)
|
||||
case $2 in
|
||||
"default"|"zh-CN"|"vi") fontset="$2";;
|
||||
*) echo -e "Wrong argument for $1.";exit 1;;
|
||||
esac;echo "The fontset is ${fontset}.";shift 2;;
|
||||
|
||||
## Ending
|
||||
--) break ;;
|
||||
*) echo -e "$0: Wrong parameters.";exit 1;;
|
||||
esac
|
||||
done
|
||||
# Handle options for subcommand
|
||||
case ${SCRIPT_SUBCOMMAND} in
|
||||
install)
|
||||
source ./sdata/lib/options-install.sh
|
||||
;;
|
||||
exp-update)
|
||||
source ./sdata/lib/options-exp-update.sh
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang.
|
||||
# NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
# This file is provided for any distros, mainly non-Arch(based) distros.
|
||||
|
||||
# The script that use this file should have two lines on its top as follows:
|
||||
@@ -9,8 +11,8 @@
|
||||
# export base="$(pwd)"
|
||||
|
||||
install-agsv1(){
|
||||
x mkdir -p $base/cache/agsv1
|
||||
x cd $base/cache/agsv1
|
||||
x mkdir -p $REPO_ROOT/cache/agsv1
|
||||
x cd $REPO_ROOT/cache/agsv1
|
||||
try git init -b main
|
||||
try git remote add origin https://github.com/Aylur/ags.git
|
||||
x git pull origin main && git submodule update --init --recursive
|
||||
@@ -20,12 +22,12 @@ install-agsv1(){
|
||||
x meson setup build # --reconfigure
|
||||
x meson install -C build
|
||||
x sudo mv /usr/local/bin/ags{,v1}
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-Rubik(){
|
||||
x mkdir -p $base/cache/Rubik
|
||||
x cd $base/cache/Rubik
|
||||
x mkdir -p $REPO_ROOT/cache/Rubik
|
||||
x cd $REPO_ROOT/cache/Rubik
|
||||
try git init -b main
|
||||
try git remote add origin https://github.com/googlefonts/rubik.git
|
||||
x git pull origin main && git submodule update --init --recursive
|
||||
@@ -35,12 +37,12 @@ install-Rubik(){
|
||||
x sudo cp OFL.txt /usr/local/share/licenses/ttf-rubik/LICENSE
|
||||
x fc-cache -fv
|
||||
x gsettings set org.gnome.desktop.interface font-name 'Rubik 11'
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-Gabarito(){
|
||||
x mkdir -p $base/cache/Gabarito
|
||||
x cd $base/cache/Gabarito
|
||||
x mkdir -p $REPO_ROOT/cache/Gabarito
|
||||
x cd $REPO_ROOT/cache/Gabarito
|
||||
try git init -b main
|
||||
try git remote add origin https://github.com/naipefoundry/gabarito.git
|
||||
x git pull origin main && git submodule update --init --recursive
|
||||
@@ -49,12 +51,12 @@ install-Gabarito(){
|
||||
x sudo mkdir -p /usr/local/share/licenses/ttf-gabarito/
|
||||
x sudo cp OFL.txt /usr/local/share/licenses/ttf-gabarito/LICENSE
|
||||
x fc-cache -fv
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-OneUI(){
|
||||
x mkdir -p $base/cache/OneUI4-Icons
|
||||
x cd $base/cache/OneUI4-Icons
|
||||
x mkdir -p $REPO_ROOT/cache/OneUI4-Icons
|
||||
x cd $REPO_ROOT/cache/OneUI4-Icons
|
||||
try git init -b main
|
||||
try git remote add origin https://github.com/end-4/OneUI4-Icons.git
|
||||
# try git remote add origin https://github.com/mjkim0727/OneUI4-Icons.git
|
||||
@@ -63,12 +65,12 @@ install-OneUI(){
|
||||
x sudo cp -r OneUI /usr/local/share/icons
|
||||
x sudo cp -r OneUI-dark /usr/local/share/icons
|
||||
x sudo cp -r OneUI-light /usr/local/share/icons
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-bibata(){
|
||||
x mkdir -p $base/cache/bibata-cursor
|
||||
x cd $base/cache/bibata-cursor
|
||||
x mkdir -p $REPO_ROOT/cache/bibata-cursor
|
||||
x cd $REPO_ROOT/cache/bibata-cursor
|
||||
name="Bibata-Modern-Classic"
|
||||
file="$name.tar.xz"
|
||||
# Use axel because `curl -O` always downloads a file with 0 byte size, idk why
|
||||
@@ -76,12 +78,12 @@ install-bibata(){
|
||||
tar -xf $file
|
||||
x sudo mkdir -p /usr/local/share/icons
|
||||
x sudo cp -r $name /usr/local/share/icons
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-MicroTeX(){
|
||||
x mkdir -p $base/cache/MicroTeX
|
||||
x cd $base/cache/MicroTeX
|
||||
x mkdir -p $REPO_ROOT/cache/MicroTeX
|
||||
x cd $REPO_ROOT/cache/MicroTeX
|
||||
try git init -b master
|
||||
try git remote add origin https://github.com/NanoMichael/MicroTeX.git
|
||||
x git pull origin master && git submodule update --init --recursive
|
||||
@@ -92,7 +94,7 @@ install-MicroTeX(){
|
||||
x sudo mkdir -p /opt/MicroTeX
|
||||
x sudo cp ./LaTeX /opt/MicroTeX/
|
||||
x sudo cp -r ./res /opt/MicroTeX/
|
||||
x cd $base
|
||||
x cd $REPO_ROOT
|
||||
}
|
||||
|
||||
install-uv(){
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This script is meant to be sourced.
|
||||
# It's not for directly running.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
#####################################################################################
|
||||
|
||||
printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This script is meant to be sourced.
|
||||
# It's not for directly running.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
####################
|
||||
# Detect distro
|
||||
# Helpful link(s):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This script is meant to be sourced.
|
||||
# It's not for directly running.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
# TODO: https://github.com/end-4/dots-hyprland/issues/2137
|
||||
|
||||
function warning_rsync(){
|
||||
@@ -179,7 +181,7 @@ warn_files_tests+=(/usr/local/share/licenses/ttf-gabarito)
|
||||
warn_files_tests+=(/usr/local/share/icons/OneUI{,-dark,-light})
|
||||
warn_files_tests+=(/usr/local/share/icons/Bibata-Modern-Classic)
|
||||
warn_files_tests+=(/usr/local/bin/{LaTeX,res})
|
||||
for i in ${warn_files_tests[@]}; do
|
||||
for i in "${warn_files_tests[@]}"; do
|
||||
echo $i
|
||||
test -f $i && warn_files+=($i)
|
||||
test -d $i && warn_files+=($i)
|
||||
@@ -223,6 +225,6 @@ if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then
|
||||
printf "\n${STY_RED}[$0]: \!! Important \!! : Please ensure environment variable ${STY_RST} \$ILLOGICAL_IMPULSE_VIRTUAL_ENV ${STY_RED} is set to proper value (by default \"~/.local/state/quickshell/.venv\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.${STY_RST}\n"
|
||||
fi
|
||||
|
||||
if [[ ! -z "${warn_files[@]}" ]]; then
|
||||
if [[ ${#warn_files[@]} -gt 0 ]]; then
|
||||
printf "\n${STY_RED}[$0]: \!! Important \!! : Please delete ${STY_RST} ${warn_files[*]} ${STY_RED} manually as soon as possible, since we\'re now using AUR package or local PKGBUILD to install them for Arch(based) Linux distros, and they'll take precedence over our installation, or at least take up more space.${STY_RST}\n"
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This script is meant to be sourced.
|
||||
# It's not for directly running.
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
printf 'Hi there!\n'
|
||||
printf 'This script 1. will uninstall [end-4/dots-hyprland > illogical-impulse] dotfiles\n'
|
||||
printf ' 2. will try to revert *mostly everything* installed using install.sh, so it'\''s pretty destructive\n'
|
||||
@@ -40,7 +42,7 @@ starship.toml
|
||||
thorium-flags.conf
|
||||
)
|
||||
|
||||
for i in ${dirs[@]}
|
||||
for i in "${dirs[@]}"
|
||||
do v rm -rf "$XDG_CONFIG_HOME/$i"
|
||||
done
|
||||
|
||||
Executable
+847
@@ -0,0 +1,847 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# exp-update-tester.sh - Test suite for update.sh (sourced subcommand)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
TEST_DIR=""
|
||||
ORIGINAL_DIR="$PWD"
|
||||
|
||||
# Helper functions
|
||||
log_test() {
|
||||
echo -e "${BLUE}[TEST]${NC} $1"
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo -e "${GREEN}[PASS]${NC} $1"
|
||||
((TESTS_PASSED++))
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo -e "${RED}[FAIL]${NC} $1"
|
||||
((TESTS_FAILED++))
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Setup test environment
|
||||
setup_test_env() {
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d -t dotfiles-test.XXXXXX)
|
||||
|
||||
cd "$temp_dir" || { echo "Failed to cd to test directory"; return 1; }
|
||||
git init -q
|
||||
git config user.email "test@example.com"
|
||||
git config user.name "Test User"
|
||||
|
||||
git commit --allow-empty -m "Initial commit" -q
|
||||
|
||||
echo "$temp_dir"
|
||||
}
|
||||
|
||||
# Cleanup test environment
|
||||
cleanup_test_env() {
|
||||
if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
|
||||
rm -rf "$TEST_DIR"
|
||||
TEST_DIR=""
|
||||
fi
|
||||
}
|
||||
|
||||
# Run a test and handle cleanup
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
local test_func="$2"
|
||||
|
||||
# Cleanup before test
|
||||
cleanup_test_env
|
||||
|
||||
# Run the test
|
||||
if $test_func; then
|
||||
echo "✓ $test_name passed"
|
||||
return 0
|
||||
else
|
||||
echo "✗ $test_name failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 1: Script exists and is executable
|
||||
test_script_exists() {
|
||||
log_test "Checking if install.sh exists and is executable"
|
||||
|
||||
if [[ ! -f "install.sh" ]]; then
|
||||
log_fail "install.sh not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "install.sh" ]]; then
|
||||
log_fail "install.sh is not executable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_pass "Script exists and is executable"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 2: Script has no syntax errors
|
||||
test_syntax() {
|
||||
log_test "Checking script syntax"
|
||||
|
||||
if bash -n install.sh; then
|
||||
log_pass "No syntax errors found"
|
||||
return 0
|
||||
else
|
||||
log_fail "Syntax errors detected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 3: Help option works
|
||||
test_help_option() {
|
||||
log_test "Testing --help option"
|
||||
|
||||
if ./install.sh exp-update --help 2>&1 | grep -qiE "(Usage|Options|exp-update)"; then
|
||||
log_pass "Help option works"
|
||||
return 0
|
||||
else
|
||||
log_fail "Help option failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 4: Test repository structure detection (dots/ prefix)
|
||||
test_dots_structure() {
|
||||
log_test "Testing dots/ prefix structure detection"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
mkdir -p dots/.config/test-app
|
||||
mkdir -p dots/.local/bin
|
||||
echo "test config" > dots/.config/test-app/config.conf
|
||||
|
||||
git add .
|
||||
git commit -m "Add dots structure" -q
|
||||
|
||||
cat > test_detection.sh << EOF
|
||||
#!/bin/bash
|
||||
# Mock logging and style functions/variables
|
||||
log_info() { :; }
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
log_success() { :; }
|
||||
log_header() { :; }
|
||||
log_die() { echo "ERROR: \$1"; exit 1; }
|
||||
STY_CYAN="" STY_RST="" STY_YELLOW=""
|
||||
|
||||
# Set required environment variables for exp-update.sh
|
||||
SKIP_NOTICE=true
|
||||
REPO_ROOT="\$1"
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
FORCE_CHECK=false
|
||||
VERBOSE=false
|
||||
NON_INTERACTIVE=true
|
||||
SOURCE_ONLY=true
|
||||
|
||||
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
|
||||
detected_dirs=\$(detect_repo_structure)
|
||||
if [[ -n "\$detected_dirs" ]]; then
|
||||
read -ra MONITOR_DIRS <<<"\$detected_dirs"
|
||||
fi
|
||||
echo "Structure: \${MONITOR_DIRS[*]}"
|
||||
EOF
|
||||
|
||||
chmod +x test_detection.sh
|
||||
result=$(./test_detection.sh "$test_repo")
|
||||
|
||||
if [[ "$result" == *"dots/.config"* ]]; then
|
||||
log_pass "Dots structure detected correctly"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
else
|
||||
log_fail "Failed to detect dots structure. Got: $result"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 5: Test flat structure detection
|
||||
test_flat_structure() {
|
||||
log_test "Testing flat structure detection"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
mkdir -p .config/test-app
|
||||
mkdir -p .local/bin
|
||||
echo "test config" > .config/test-app/config.conf
|
||||
|
||||
git add .
|
||||
git commit -m "Add flat structure" -q
|
||||
|
||||
cat > test_detection.sh << EOF
|
||||
#!/bin/bash
|
||||
# Mock logging and style functions/variables
|
||||
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
|
||||
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
|
||||
log_info() { :; }
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
log_success() { :; }
|
||||
log_header() { :; }
|
||||
log_die() { echo "ERROR: \$1"; exit 1; }
|
||||
|
||||
# Set required environment variables for exp-update.sh
|
||||
SKIP_NOTICE=true
|
||||
REPO_ROOT="\$1"
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
FORCE_CHECK=false
|
||||
VERBOSE=false
|
||||
NON_INTERACTIVE=true
|
||||
SOURCE_ONLY=true
|
||||
|
||||
source "$ORIGINAL_DIR/sdata/step/exp-update.sh"
|
||||
detected_dirs=\$(detect_repo_structure)
|
||||
if [[ -n "\$detected_dirs" ]]; then
|
||||
read -ra MONITOR_DIRS <<<"\$detected_dirs"
|
||||
fi
|
||||
echo "Structure: \${MONITOR_DIRS[*]}"
|
||||
EOF
|
||||
|
||||
chmod +x test_detection.sh
|
||||
result=$(./test_detection.sh "$test_repo")
|
||||
|
||||
if [[ "$result" == *".config"* ]] && [[ "$result" != *"dots/"* ]]; then
|
||||
log_pass "Flat structure detected correctly"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
else
|
||||
log_fail "Failed to detect flat structure. Got: $result"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 6: Test dots prefix mapping to home directory
|
||||
test_dots_mapping() {
|
||||
log_test "Testing dots/ prefix home directory mapping"
|
||||
|
||||
dir_name="dots/.config"
|
||||
if [[ "$dir_name" == dots/* ]]; then
|
||||
home_subdir="${dir_name#dots/}"
|
||||
home_dir_path="${HOME}/${home_subdir}"
|
||||
else
|
||||
home_dir_path="${HOME}/${dir_name}"
|
||||
fi
|
||||
|
||||
expected_path="${HOME}/.config"
|
||||
if [[ "$home_dir_path" == "$expected_path" ]]; then
|
||||
log_pass "Dots prefix mapping correct: $dir_name → $home_dir_path"
|
||||
return 0
|
||||
else
|
||||
log_fail "Dots prefix mapping failed: $dir_name → $home_dir_path (expected: $expected_path)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 7: Test ignore file patterns - FIXED
|
||||
test_ignore_patterns() {
|
||||
log_test "Testing ignore file pattern matching"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
cat > .updateignore << 'EOF'
|
||||
*.log
|
||||
secrets/
|
||||
.config/private*
|
||||
*backup*
|
||||
EOF
|
||||
|
||||
mkdir -p .config
|
||||
mkdir -p secrets
|
||||
|
||||
cat > test_ignore.sh << EOF
|
||||
#!/bin/bash
|
||||
# Suppress all output from sourced script
|
||||
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
|
||||
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
|
||||
log_info() { :; }
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
log_success() { :; }
|
||||
log_header() { :; }
|
||||
log_die() { echo "ERROR: \$1" >&2; exit 1; }
|
||||
|
||||
# FIXED: Set REPO_ROOT before sourcing exp-update.sh
|
||||
REPO_ROOT="\$1"
|
||||
export REPO_ROOT
|
||||
|
||||
# Set other required environment variables
|
||||
SKIP_NOTICE=true
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
FORCE_CHECK=false
|
||||
VERBOSE=false
|
||||
NON_INTERACTIVE=true
|
||||
|
||||
UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
|
||||
HOME_UPDATE_IGNORE_FILE="/dev/null"
|
||||
|
||||
# Source the production script to use the real should_ignore function
|
||||
# Redirect all unwanted output to stderr, then to /dev/null
|
||||
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
|
||||
|
||||
test_cases=(
|
||||
"\$REPO_ROOT/app.log:0"
|
||||
"\$REPO_ROOT/secrets/key.txt:0"
|
||||
"\$REPO_ROOT/.config/private-config:0"
|
||||
"\$REPO_ROOT/.config/backup-file:0"
|
||||
"\$REPO_ROOT/normal-config:1"
|
||||
)
|
||||
|
||||
all_passed=true
|
||||
for test_case in "\${test_cases[@]}"; do
|
||||
IFS=":" read -r file expected <<< "\$test_case"
|
||||
mkdir -p "\$(dirname "\$file")"
|
||||
touch "\$file"
|
||||
|
||||
if should_ignore "\$file"; then
|
||||
result=0
|
||||
else
|
||||
result=1
|
||||
fi
|
||||
|
||||
if [[ \$result -ne \$expected ]]; then
|
||||
echo "FAIL: \$file (expected: \$expected, got: \$result)"
|
||||
all_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "\$all_passed" == true ]]; then
|
||||
echo "PASS"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x test_ignore.sh
|
||||
result=$(./test_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")
|
||||
|
||||
if [[ "$result" == "PASS" ]]; then
|
||||
log_pass "All ignore pattern tests passed"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
else
|
||||
log_fail "Some ignore pattern tests failed"
|
||||
echo "$result"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 8: Test safe_read security - FIXED
|
||||
test_safe_read_security() {
|
||||
log_test "Testing safe_read uses secure assignment (printf -v)"
|
||||
|
||||
local safe_read_function
|
||||
safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/step/exp-update.sh")
|
||||
|
||||
if [[ -z "$safe_read_function" ]]; then
|
||||
log_fail "Could not find safe_read function"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# FIXED: Remove comments before checking for eval
|
||||
# The function has a comment mentioning eval, which shouldn't count
|
||||
local function_without_comments
|
||||
function_without_comments=$(echo "$safe_read_function" | sed 's/#.*$//')
|
||||
|
||||
local has_printf_v=false
|
||||
local has_eval=false
|
||||
|
||||
if echo "$safe_read_function" | grep -F 'printf -v' > /dev/null; then
|
||||
has_printf_v=true
|
||||
fi
|
||||
|
||||
# Check for eval in actual code (not comments)
|
||||
if echo "$function_without_comments" | grep -w 'eval' > /dev/null; then
|
||||
has_eval=true
|
||||
fi
|
||||
|
||||
if [[ "$has_printf_v" == true ]] && [[ "$has_eval" == false ]]; then
|
||||
log_pass "safe_read uses secure printf -v assignment and no eval"
|
||||
return 0
|
||||
else
|
||||
log_fail "safe_read does not use secure assignment or contains eval (has_printf_v=$has_printf_v, has_eval=$has_eval)"
|
||||
echo "Function content:"
|
||||
echo "$safe_read_function"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 9: Test dry-run mode - FIXED
|
||||
test_dry_run() {
|
||||
log_test "Testing dry-run mode"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
# Copy necessary files for install.sh to run
|
||||
cp "$ORIGINAL_DIR/install.sh" .
|
||||
cp -r "$ORIGINAL_DIR/sdata" .
|
||||
cp -r "$ORIGINAL_DIR/dots" .
|
||||
chmod +x install.sh
|
||||
|
||||
# Create a test config file in repo
|
||||
mkdir -p dots/.config/test-app
|
||||
echo "test config" > dots/.config/test-app/config.conf
|
||||
|
||||
git add .
|
||||
git commit -m "Add test config" -q
|
||||
|
||||
# FIXED: Clean up any existing test files before running test
|
||||
rm -rf "${HOME}/.config/test-app" 2>/dev/null || true
|
||||
|
||||
# Use non-interactive mode and check for DRY-RUN marker
|
||||
./install.sh exp-update -n --skip-notice --non-interactive 2>&1 | tee dry_run_output.txt
|
||||
|
||||
if grep -q "DRY-RUN" dry_run_output.txt; then
|
||||
log_pass "Dry-run mode detected in output"
|
||||
else
|
||||
log_fail "Dry-run mode not properly indicated"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# FIXED: Check if files were created (they shouldn't be in dry-run)
|
||||
if [[ -f "${HOME}/.config/test-app/config.conf" ]]; then
|
||||
log_fail "Files were created in home during dry-run"
|
||||
rm -rf "${HOME}/.config/test-app"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
else
|
||||
log_pass "No files created in home during dry-run"
|
||||
fi
|
||||
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 10: Test command-line flags
|
||||
test_flags() {
|
||||
log_test "Testing command-line flags"
|
||||
|
||||
# Only test non-interactive flags
|
||||
local flags=("-h" "--help")
|
||||
local all_passed=true
|
||||
|
||||
for flag in "${flags[@]}"; do
|
||||
if ./install.sh exp-update "$flag" 2>&1 | grep -qiE "(Usage|Options|exp-update)"; then
|
||||
log_test " ✓ $flag recognized"
|
||||
else
|
||||
log_test " ✗ $flag not recognized"
|
||||
all_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$all_passed" == true ]]; then
|
||||
log_pass "Help flags recognized correctly"
|
||||
return 0
|
||||
else
|
||||
log_fail "Some flags not recognized properly"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 11: Check for shellcheck
|
||||
test_shellcheck() {
|
||||
log_test "Running shellcheck (if available)"
|
||||
|
||||
if ! command -v shellcheck &>/dev/null; then
|
||||
log_test "shellcheck not found, skipping static analysis"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 install.sh; then
|
||||
log_pass "shellcheck passed"
|
||||
return 0
|
||||
else
|
||||
log_fail "shellcheck found issues"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 12: Test lock file mechanism
|
||||
test_lock_file() {
|
||||
log_test "Testing lock file mechanism"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
# Copy necessary files
|
||||
cp "$ORIGINAL_DIR/install.sh" .
|
||||
cp -r "$ORIGINAL_DIR/sdata" .
|
||||
mkdir -p dots/.config
|
||||
chmod +x install.sh
|
||||
|
||||
git add .
|
||||
git commit -m "Add files" -q
|
||||
|
||||
# Create a fake lock file
|
||||
echo "99999" > .update-lock
|
||||
|
||||
# Try to run update - should fail due to lock
|
||||
if ./install.sh exp-update --skip-notice --non-interactive > lock_test_output.txt 2>&1; then
|
||||
if grep -q "stale lock" lock_test_output.txt; then
|
||||
log_pass "Lock file mechanism works (detected stale lock)"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
log_fail "Lock file mechanism did not work as expected"
|
||||
cat lock_test_output.txt # Show output for debugging
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Test 13: Test ** substring ignore patterns - FIXED
|
||||
test_substring_ignore_patterns() {
|
||||
log_test "Testing ** substring ignore pattern matching"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
cat > .updateignore << 'EOF'
|
||||
**temp**
|
||||
**backup**
|
||||
**testfile**
|
||||
EOF
|
||||
|
||||
mkdir -p .config/test-app
|
||||
mkdir -p temp-backup-dir
|
||||
mkdir -p .local/share/test-temp
|
||||
mkdir -p .config/temp-file
|
||||
|
||||
cat > test_substring_ignore.sh << EOF
|
||||
#!/bin/bash
|
||||
# Suppress all output from sourced script
|
||||
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
|
||||
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
|
||||
log_info() { :; }
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
log_success() { :; }
|
||||
log_header() { :; }
|
||||
log_die() { echo "ERROR: \$1" >&2; exit 1; }
|
||||
|
||||
# FIXED: Set REPO_ROOT before sourcing exp-update.sh
|
||||
REPO_ROOT="\$1"
|
||||
export REPO_ROOT
|
||||
|
||||
# Set other required environment variables
|
||||
SKIP_NOTICE=true
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
FORCE_CHECK=false
|
||||
VERBOSE=false
|
||||
NON_INTERACTIVE=true
|
||||
|
||||
UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore"
|
||||
HOME_UPDATE_IGNORE_FILE="/dev/null"
|
||||
|
||||
# Source the production script to use the real should_ignore function
|
||||
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
|
||||
|
||||
# Load patterns into cache
|
||||
load_ignore_patterns
|
||||
|
||||
test_cases=(
|
||||
"\$REPO_ROOT/temp-backup-dir/file:0"
|
||||
"\$REPO_ROOT/.config/test-app/temp.conf:0"
|
||||
"\$REPO_ROOT/.local/share/test-temp/data:0"
|
||||
"\$REPO_ROOT/.config/temp-file/config:0"
|
||||
"\$REPO_ROOT/normal-config:1"
|
||||
"\$REPO_ROOT/.config/my-testfile.conf:0"
|
||||
)
|
||||
|
||||
all_passed=true
|
||||
for test_case in "\${test_cases[@]}"; do
|
||||
IFS=":" read -r file expected <<< "\$test_case"
|
||||
mkdir -p "\$(dirname "\$file")"
|
||||
touch "\$file"
|
||||
|
||||
if should_ignore "\$file"; then
|
||||
result=0
|
||||
else
|
||||
result=1
|
||||
fi
|
||||
|
||||
if [[ \$result -ne \$expected ]]; then
|
||||
echo "FAIL: \$file (expected: \$expected, got: \$result)"
|
||||
all_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "\$all_passed" == true ]]; then
|
||||
echo "PASS"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x test_substring_ignore.sh
|
||||
result=$(./test_substring_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")
|
||||
|
||||
if [[ "$result" == "PASS" ]]; then
|
||||
log_pass "** substring ignore patterns work correctly"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
else
|
||||
log_fail "** substring ignore patterns failed"
|
||||
echo "$result"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 14: Test ensure_directory caching
|
||||
test_directory_caching() {
|
||||
log_test "Testing directory creation caching"
|
||||
|
||||
local test_repo
|
||||
test_repo=$(setup_test_env)
|
||||
TEST_DIR="$test_repo"
|
||||
|
||||
cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; }
|
||||
|
||||
cat > test_dir_cache.sh << EOF
|
||||
#!/bin/bash
|
||||
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
|
||||
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
|
||||
log_info() { :; }
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
log_success() { :; }
|
||||
log_header() { :; }
|
||||
log_die() { echo "ERROR: \$1" >&2; exit 1; }
|
||||
|
||||
REPO_ROOT="\$1"
|
||||
export REPO_ROOT
|
||||
|
||||
SKIP_NOTICE=true
|
||||
CHECK_PACKAGES=false
|
||||
DRY_RUN=false
|
||||
FORCE_CHECK=false
|
||||
VERBOSE=false
|
||||
NON_INTERACTIVE=true
|
||||
SOURCE_ONLY=true
|
||||
|
||||
source "$ORIGINAL_DIR/sdata/step/exp-update.sh" 2>/dev/null
|
||||
|
||||
test_dir="/tmp/test-ensure-dir-\$\$"
|
||||
|
||||
# First call should create
|
||||
ensure_directory "\$test_dir"
|
||||
result1=\$?
|
||||
|
||||
# Second call should use cache
|
||||
ensure_directory "\$test_dir"
|
||||
result2=\$?
|
||||
|
||||
# Check if CREATED_DIRS has the entry
|
||||
if [[ -n "\${CREATED_DIRS[\$test_dir]:-}" ]] && [[ \$result1 -eq 0 ]] && [[ \$result2 -eq 0 ]]; then
|
||||
echo "PASS"
|
||||
rm -rf "\$test_dir"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x test_dir_cache.sh
|
||||
result=$(./test_dir_cache.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)")
|
||||
|
||||
if [[ "$result" == "PASS" ]]; then
|
||||
log_pass "Directory creation caching works"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 0
|
||||
else
|
||||
log_fail "Directory creation caching failed"
|
||||
cd "$ORIGINAL_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test 15: Test enhanced safe_read with non-interactive mode
|
||||
test_safe_read_noninteractive() {
|
||||
log_test "Testing safe_read in non-interactive mode"
|
||||
|
||||
cat > test_safe_read.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh"
|
||||
source "$ORIGINAL_DIR/sdata/lib/functions.sh"
|
||||
log_warning() { :; }
|
||||
log_error() { :; }
|
||||
|
||||
# Simulate the enhanced safe_read function
|
||||
safe_read() {
|
||||
local prompt="$1"
|
||||
local varname="$2"
|
||||
local default="${3:-}"
|
||||
local input_value=""
|
||||
|
||||
# In non-interactive mode, use default immediately
|
||||
if [[ "$NON_INTERACTIVE" == true ]]; then
|
||||
if [[ -n "$default" ]]; then
|
||||
printf -v "$varname" '%s' "$default"
|
||||
return 0
|
||||
else
|
||||
log_error "Non-interactive mode requires default value for: $prompt"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Regular read logic...
|
||||
printf -v "$varname" '%s' "$default"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 1: With default in non-interactive mode
|
||||
NON_INTERACTIVE=true
|
||||
if safe_read "Test: " result "default_value"; then
|
||||
if [[ "$result" == "default_value" ]]; then
|
||||
echo "TEST1: PASS"
|
||||
else
|
||||
echo "TEST1: FAIL - got '$result'"
|
||||
fi
|
||||
else
|
||||
echo "TEST1: FAIL - returned error"
|
||||
fi
|
||||
|
||||
# Test 2: Without default in non-interactive mode (should fail)
|
||||
if safe_read "Test: " result ""; then
|
||||
echo "TEST2: FAIL - should have failed"
|
||||
else
|
||||
echo "TEST2: PASS - correctly failed"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x test_safe_read.sh
|
||||
result=$(./test_safe_read.sh 2>&1)
|
||||
|
||||
if echo "$result" | grep -q "TEST1: PASS" && echo "$result" | grep -q "TEST2: PASS"; then
|
||||
log_pass "Enhanced safe_read handles non-interactive mode correctly"
|
||||
rm -f test_safe_read.sh
|
||||
return 0
|
||||
else
|
||||
log_fail "Enhanced safe_read non-interactive mode failed"
|
||||
echo "$result"
|
||||
rm -f test_safe_read.sh
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main test runner
|
||||
main() {
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo -e "${BLUE} Update.sh Test Suite (Enhanced)${NC}"
|
||||
echo -e "${BLUE}================================${NC}\n"
|
||||
|
||||
if [[ ! -f "install.sh" ]]; then
|
||||
log_error "Please run this test from the directory containing install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x install.sh 2>/dev/null || true
|
||||
|
||||
# Define tests
|
||||
tests=(
|
||||
"test_script_exists"
|
||||
"test_syntax"
|
||||
"test_help_option"
|
||||
"test_dots_structure"
|
||||
"test_flat_structure"
|
||||
"test_dots_mapping"
|
||||
"test_ignore_patterns"
|
||||
"test_substring_ignore_patterns"
|
||||
"test_safe_read_security"
|
||||
"test_dry_run"
|
||||
"test_flags"
|
||||
"test_shellcheck"
|
||||
"test_lock_file"
|
||||
"test_directory_caching"
|
||||
"test_safe_read_noninteractive"
|
||||
)
|
||||
|
||||
# Run tests
|
||||
for test in "${tests[@]}"; do
|
||||
if $test; then
|
||||
echo "✓ $test passed"
|
||||
else
|
||||
echo "✗ $test failed"
|
||||
fi
|
||||
echo
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo -e "${BLUE} Test Summary${NC}"
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo -e "${GREEN}Passed: $TESTS_PASSED${NC}"
|
||||
echo -e "${RED}Failed: $TESTS_FAILED${NC}"
|
||||
echo -e "${BLUE}Total: ${#tests[@]}${NC}\n"
|
||||
|
||||
if [[ $TESTS_FAILED -eq 0 ]]; then
|
||||
echo -e "${GREEN}All tests passed! 🎉${NC}\n"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}Some tests failed! ❌${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Global cleanup
|
||||
cleanup() {
|
||||
echo "Cleaning up test files..."
|
||||
cleanup_test_env
|
||||
rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh test_substring_ignore.sh dry_run_output.txt 2>/dev/null || true
|
||||
rm -f test_caching.sh test_dir_cache.sh 2>/dev/null || true
|
||||
rm -f lock_test_output.txt 2>/dev/null || true
|
||||
rm -rf "${HOME}/.config/test-app" 2>/dev/null || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user