Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 377 lines (353 sloc) 14.1 KB
#!/bin/bash
################################################################################
# dot-man
#
# Copyright (c) 2015-2016 Charles Neill
# Github: cneill | Twitter: ccneill
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
################################################################################
platform="unknown"
config_file=$HOME/.dotfiles
editor=${EDITOR:-vi}
# Command line arguments
HELP=0
IGNORE_LAST_RUN=0
CHECK_LAST_RUN=0
LOAD_UNSAFE_CONFIG=0
LAZY_PLATFORM_DETECT=0
VERBOSE=0
REVERSE_PROMPTS=0
# Colors / formatting
sep="\033[31m================================================================================\033[0m"
b="\033[1m"
b_="\033[0m"
hi="${b}\033[92m"
hi_="\033[0m${b_}"
f="\033[4m\033[34m"
f_="\033[0m\033[24m"
# Return true if "yes", false if "no"
function yn_prompt {
if [[ -z $1 ]]; then
echo "ERROR! BAD PROMPT"
exit 1
fi
question="${b}$1${b_}"
if [[ $REVERSE_PROMPTS == 0 ]]; then
answers="[y/${hi}N${hi_}]"
elif [[ $REVERSE_PROMPTS == 1 ]]; then
answers="[${hi}Y${hi_}/n]"
fi
echo -e "$question $answers"
read -p " > " y_n
if [[ $REVERSE_PROMPTS == 0 ]]; then
if [[ $y_n == "y" ]] || [[ $y_n == "Y" ]]; then
return 0
else
return 1
fi
elif [[ $REVERSE_PROMPTS == 1 ]]; then
if [[ $y_n == "n" ]] || [[ $y_n == "N" ]]; then
return 1
else
return 0
fi
fi
}
# Load our configuration file
function load_config {
if [[ $LOAD_UNSAFE_CONFIG == 1 ]]; then
. "$config_file"
fi
clean=$(grep -vE '(^[# a-zA-Z]$)|(^[a-z]+=[a-z" ]$)' "$config_file")
if [[ ! -z $clean ]] && [[ $LOAD_UNSAFE_CONFIG == 0 ]]; then
echo -e "Warning: your configuration file may have unsafe lines in it. \n${b}What would you like to do?${b_} [c]ontinue, [o]pen, or [${hi}Q${hi_}]uit:"
read -p " > " continue_reading
if [[ $continue_reading == "c" ]] || [[ $continue_reading == "C" ]]; then
. "$config_file"
elif [[ $continue_reading == "o" ]] || [[ $continue_reading == "O" ]]; then
$editor "$config_file"
if yn_prompt "Continue loading configuration file?"; then
. "$config_file"
else
echo "Unable to load configuration. Quitting."
exit -1
fi
else
echo "Quitting."
exit 0
fi
fi
}
# See when we last ran dot-man
function check_last_run {
thedate=$(date)
thetime=""
if [[ $platform == "Linux" ]]; then
thetime=$(date -d "$thedate" "+%s")
elif [[ $platform == "OS X" ]]; then
thetime=$(date -j -f "%a %b %d %T %Z %Y" "$thedate" "+%s")
fi
if [[ -f $dotfiles_last_run_file ]]; then
raw=$(cat "$dotfiles_last_run_file")
if [[ $platform == "Linux" ]]; then
last_time=$(date -d "@$raw")
elif [[ $platform == "OS X" ]]; then
last_time=$(date -r "$raw")
fi
time_diff=$thetime-$raw
if [[ $CHECK_LAST_RUN == 1 ]]; then
if [[ $time_diff -lt $dotfiles_update_interval ]]; then
exit 0
else
echo "$thetime" > "$dotfiles_last_run_file"
fi
else
if yn_prompt "Last run: $last_time. Continue?"; then
echo "$thetime" > "$dotfiles_last_run_file"
else
echo "Quitting."
exit 0
fi
fi
else
touch "$dotfiles_last_run_file"
echo "$thetime" > "$dotfiles_last_run_file"
fi
}
# check to make sure we have the right platform
function check_platform {
unamestr=$(uname)
if [[ $unamestr == "Linux" ]]; then
platform="Linux"
elif [[ $unamestr == "Darwin" ]]; then
platform="OS X"
fi
if [[ $platform != "unknown" ]]; then
if [[ -z $dotfiles_branch ]]; then
if [[ $platform == "Linux" ]]; then
dotfiles_branch="linux"
elif [[ $platform == "OS X" ]]; then
dotfiles_branch="osx"
fi
elif [[ $VERBOSE == 1 ]]; then
echo "Using branch $dotfiles_branch (defined in $config_file)."
fi
if [[ $LAZY_PLATFORM_DETECT == 0 ]]; then
if ! yn_prompt "It looks like your platform is $platform. Is this correct?"; then
echo "Unknown platform. Quitting."
exit -1
fi
fi
else
echo "Unknown platform. Quitting."
exit -1
fi
}
# check to see if our dotfiles folder exists
function check_dotfiles_exist {
echo -e "Checking dotfiles exist...\n"
if [[ ! -d $dotfiles_repo_loc ]]; then
if [[ -e $dotfiles_repo_loc ]]; then
if yn_prompt "${f}$dotfiles_repo_loc${f_} is not a directory. Delete and create directory?"; then
rm -rf "$dotfiles_repo_loc"
mkdir "$dotfiles_repo_loc"
else
echo -e "Can't continue without a valid \$dotfiles_repo_loc."
exit -1
fi
else
if yn_prompt "No folder named $dotfiles_repo_loc. Create directory ${f}$dotfiles_repo_loc${f_}?"; then
mkdir "$dotfiles_repo_loc"
else
echo -e "Can't continue without a valid \$dotfiles_repo_loc."
exit -1
fi
fi
fi
if (git -C "$dotfiles_repo_loc" status 2>&1 | fgrep -q 'Not a git repository'); then
echo -e "Git repository not found at this location.\n${b}Initialize one or clone?${b_}\n[i]nitialize, [c]lone, or [${hi}Q${hi_}]uit: "
read -p " > " initialize_git
if [[ $initialize_git == "i" ]] || [[ $initialize_git == "I" ]]; then
git -C "$dotfiles_repo_loc" init
git -C "$dotfiles_repo_loc" checkout -b "$dotfiles_branch"
elif [[ $initialize_git == "c" ]] || [[ $initialize_git == "C" ]]; then
is_dirty=$(ls "$dotfiles_repo_loc")
if [[ ! -z $is_dirty ]]; then
echo -e "Existing files in ${f}$dotfiles_repo_loc${f_}:\n$is_dirty\n"
if yn_prompt "Delete files?"; then
rm -rf "$dotfiles_repo_loc"
mkdir "$dotfiles_repo_loc"
else
echo -e "Cannot clone repository without clean folder: ${f}$dotfiles_repo_loc${f_}. Clean up this directory and try again."
exit -1
fi
fi
git clone "$dotfiles_repo" "$dotfiles_repo_loc"
git -C "$dotfiles_repo_loc" checkout -b "$dotfiles_branch"
else
echo "Quitting."
exit -1
fi
else
echo "Found dotfiles repository."
fi
}
# update dotfiles repository from remote
function update_dotfiles {
echo -e "Checking status of repository...\n"
current_branch=$(git -C "$dotfiles_repo_loc" status | grep "On branch " | sed "s/On branch //")
if [[ $current_branch != $dotfiles_branch ]]; then
git -C "$dotfiles_repo_loc" checkout "$dotfiles_branch"
git -C "$dotfiles_repo_loc" branch -u "origin/$dotfiles_branch"
fi
git -C "$dotfiles_repo_loc" remote update
status=$(git -C "$dotfiles_repo_loc" status)
up_to_date=$(grep "Your branch is up-to-date" <<< "$status")
up_to_date2=$(grep -E "nothing (added )*to commit" <<< "$status") # Dirty branch is thrown away here. Maybe we want to update?
if [[ $up_to_date ]] || [[ $up_to_date2 ]]; then
echo "Local repository is up-to-date with remote repository."
else
if yn_prompt "The dotfiles repository has been updated, or has unstaged changes. Pull latest version?"; then
git -C "$dotfiles_repo_loc" pull -u origin "$dotfiles_branch"
else
echo "Not updating repository from remote."
fi
fi
}
# diff directories and copy any updates
function compare_dotfiles {
echo -e "Comparing files from ${f}$dotfiles_repo_loc${f_} with files in ${f}$dotfiles_home${f_}.\n"
for file in $(git -C "$dotfiles_repo_loc" ls-files); do
if [[ $file != "$dotfiles_repo_loc/." ]] && [[ $file != "$dotfiles_repo_loc/.." ]] && [[ $file != ".git" ]] && [[ $file != ".gitignore" ]]; then
if [[ $dotfiles_repo_loc/$file -nt $dotfiles_home/$file ]]; then
if [[ ! -f $dotfiles_home/$file ]]; then
if yn_prompt "File ${f}$file${f_} doesn't exist in ${f}$dotfiles_home${f_}. Copy from repo?"; then
cp "$dotfiles_repo_loc/$file" "$dotfiles_home/$file"
fi
else
difference=$(diff "$dotfiles_repo_loc/$file" "$dotfiles_home/$file")
if [[ ! -z $difference ]]; then
if yn_prompt "Dotfiles repository has newer version of ${f}$file${f_}. Open sdiff?"; then
outfile=$(mktemp TEMP_DOTFILE.XXXXXXXXXX)
sdiff -o "$outfile" "$dotfiles_repo_loc/$file" "$dotfiles_home/$file"
cp "$outfile" "$dotfiles_home/$file"
rm "$outfile"
fi
elif [[ $VERBOSE == 1 ]]; then
echo -e "${f}$file${f_} is up-to-date!"
fi
fi
elif [[ $dotfiles_home/$file -nt $dotfiles_repo_loc/$file ]]; then
difference=$(diff "$dotfiles_home/$file" "$dotfiles_repo_loc/$file")
if [[ ! -z $difference ]]; then
if yn_prompt "$sep\nDifferences found in ${f}$dotfiles_home/$file${f_}. Open sdiff?"; then
outfile=$(mktemp TEMP_DOTFILE.XXXXXXXXXX)
sdiff -o "$outfile" "$dotfiles_home/$file" "$dotfiles_repo_loc/$file"
home_difference=$(diff "$outfile" "$dotfiles_home/$file")
repo_difference=$(diff "$outfile" "$dotfiles_repo_loc/$file")
if [[ ! -z $home_difference ]]; then
if yn_prompt "Copy to $dotfiles_home?"; then
cp "$outfile" "$dotfiles_home/$file"
fi
fi
if [[ ! -z $repo_difference ]]; then
if yn_prompt "Copy to $dotfiles_repo_loc?"; then
cp "$outfile" "$dotfiles_repo_loc/$file"
if yn_prompt "Update dotfiles repository?"; then
git -C "$dotfiles_repo_loc" add "$dotfiles_repo_loc/$file"
commit_message="Updating $file on $(hostname) at $(date)."
if yn_prompt "Custom commit message?"; then
echo "Enter commit message: "
read -p " > " custom_commit_message
commit_message=$custom_commit_message
fi
git -C "$dotfiles_repo_loc" commit -m "$commit_message" -q
git -C "$dotfiles_repo_loc" push -u "$dotfiles_repo"
echo "Pushed updates to $dotfiles_repo!"
fi
fi
fi
rm "$outfile"
else
echo -e "Leaving differences in ${f}$file${f_} unchanged."
fi
elif [[ $VERBOSE == 1 ]]; then
echo -e "${f}$file${f_} is up-to-date!"
fi
fi
fi
done
echo "Finished with all dotfiles!"
}
for arg in "$@"; do
case $arg in
-h)
HELP=1
IGNORE_LAST_RUN=1
;;
-d)
LAZY_PLATFORM_DETECT=1
;;
-f)
LOAD_UNSAFE_CONFIG=1
;;
-i)
IGNORE_LAST_RUN=1
;;
-l)
CHECK_LAST_RUN=1
;;
-r)
REVERSE_PROMPTS=1
;;
-v)
VERBOSE=1
;;
esac
done
if [[ $HELP == 1 ]]; then
echo -e "Usage:\n
$(basename "$0") [-h] [-d] [-f] [-i] [-l] [-r] [-v]
\nOptions:\n
\t-h\tShow this help message
\t-d\tUse best guess for platform
\t-f\tForce auto-load of config file (no check for unsafe values)
\t-i\tIgnore last run check entirely
\t-l\tDon't check for updates unless it's been \$dotfiles_update_interval seconds since last run
\t-r\tReverse prompts (i.e. make enter default-destructive)
\t-v\tVerbose: print more information"
exit
fi
load_config
check_platform
echo -e $sep
printf "dot-man (•‿•)%67s\n" "$USER@$(hostname):$dotfiles_branch"
echo -e $sep
if [[ $IGNORE_LAST_RUN == 0 ]]; then
check_last_run
fi
check_dotfiles_exist
echo -e $sep
update_dotfiles
echo -e $sep
compare_dotfiles
echo -e $sep