dotfiles/bin/dotfiles

293 lines
7.2 KiB
Bash
Executable File

#!/bin/bash
##
## binaryDiv's dotfiles management script
## Version 0.4.2
##
usage() {
cat <<- END_OF_HELPTEXT
Usage: $(basename $0) COMMAND [...]
Manage dotfiles via git.
General commands:
help print this help message
Installation:
install run this on first start: install dotfiles script in ~/bin, configure git, ...
(this will NOT create links for all dotfiles, though)
Dotfile management:
ls [DIR] list contents of .dotfiles directory (or DIR relative to it)
diff FILE use vimdiff to compare ~/FILE to ~/.dotfiles/FILE
link FILE... create a symlink from ~/FILE to ~/.dotfiles/FILE (multiple files possible)
(-q: be quiet, only print something if an error occurs)
linkstatus [DIR] list contents of .dotfiles with information about whether there are links to
them in ~/, whether they differ, etc...
createfrom FILE... copy ~/FILE to ~/.dotfiles/FILE and symlink the file
(-n: only copy, leave the original, do not create symlink)
Synchronization:
git ARGS... wrapper for git (all arguments are passed on to git)
pull shortcut for git pull
END_OF_HELPTEXT
}
# Define root dir for dotfiles management (defaults to $HOME, can be overridden
# with environment variables for debugging)
if [[ -z $DOTFILESROOT ]]; then
DOTFILESROOT="$HOME"
fi
# Set variables
homedir="$DOTFILESROOT"
dotfilesdir="$DOTFILESROOT/.dotfiles"
# Get command
[[ $# -gt 0 ]] || { usage; exit 1; }
cmd=$1
shift
# Parse command
case "$cmd" in
help|--help|-h)
usage
exit 1
;;
install)
bin_source="$dotfilesdir/bin/dotfiles"
bin_target="$homedir/bin/dotfiles"
bin_dir="$homedir/bin"
if [[ ! -e $bin_target ]]; then
echo "* Installing dotfiles script in $bin_dir/"
mkdir -pv "$bin_dir"
ln -sr "$bin_source" "$bin_target" || exit 1
elif [[ ! "$(realpath $bin_target)" -ef "$bin_source" ]]; then
echo "! Warning: File $bin_target already exists but is NOT a link to $bin_source"
fi
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
echo "! Warning: $bin_dir is not in your PATH."
fi
show_bash_restart_info=
# Enable user-defined bash completion per directory
bashcompletion_filename=.bash_completion
if [[ ! -e "$homedir/$bashcompletion_filename" ]]; then
echo "* Installing user-defined bash completion (~/.bash_completion.d/)"
"$0" link -q "$bashcompletion_filename" &&
show_bash_restart_info=1
fi
mkdir -p "$homedir/.bash_completion.d"
# Activate dotfiles bash completion
bashcompletion_dotfiles_filename=.bash_completion.d/10-dotfiles.sh
if [[ ! -e "$homedir/$bashcompletion_dotfiles_filename" ]]; then
echo "* Installing bash completion for dotfiles script"
"$0" link -q "$bashcompletion_dotfiles_filename" &&
show_bash_restart_info=1
fi
if [[ -n $show_bash_restart_info ]]; then
echo " Restart bash or run 'source $homedir/.bash_completion' to activate it now"
fi
echo "* Configuring git settings"
git -C "$dotfilesdir" config merge.ff only
;;
ls)
lsdir="$dotfilesdir"
[[ -n $1 ]] && lsdir="$lsdir/$1"
echo "$lsdir:"
ls -lAh --color=auto "$lsdir"
;;
linkstatus)
subdir=""
if [[ -n $1 ]]; then
subdir="${1%/}"
[[ -e $subdir ]] || { echo "$dotfilesdir/$subdir does not exist"; exit 1; }
[[ -d $subdir ]] && subdir="$subdir/"
fi
shopt -s nullglob dotglob
cd $dotfilesdir
dotdirs=()
for file in $subdir*; do
[[ -e $file ]] || continue
[[ $file = ".git" ]] && continue
file_home="$homedir/$file"
file_dotfiles="$dotfilesdir/$file"
if [[ -e $file_home ]]; then
if [[ "$(realpath "$file_home")" -ef "$file_dotfiles" ]]; then
file_status=" LINKED "
elif [[ -d $file_home ]]; then
dotdirs+=($file)
continue
elif [[ -f $file_home ]] && cmp --silent "$file_home" "$file_dotfiles"; then
file_status="== EQUAL "
else
file_status="<> DIFFERS "
fi
else
if [[ -d $file_dotfiles ]]; then
file_status="++ NEW DIR "
else
file_status="++ NEW FILE"
fi
fi
[[ -d $file_dotfiles ]] && file="$file/"
echo "$file_status $file"
done
if [[ $dotdirs ]]; then
echo
for dir in ${dotdirs[@]}; do
echo "[$dir]"
dotfiles linkstatus "$dir" | grep '\(LINKED\|EQUAL\|DIFFERS\|NEW\)'
echo
done
fi
;;
diff)
[[ -n $1 ]] || { echo "$(basename $0) $cmd: Missing argument."; exit 1; }
file_A="$homedir/$1"
file_B="$dotfilesdir/$1"
if [[ "$(realpath "$file_A")" -ef "$file_B" ]]; then
echo "$file_A is a link to $file_B"
else
vimdiff "$file_A" "$file_B"
fi
;;
link)
if [[ $1 = '-q' ]]; then
QUIET=1
shift
fi
[[ -n $1 ]] || { echo "$(basename $0) $cmd: Missing argument."; exit 1; }
for filename in "$@"; do
file_home="$homedir/$filename"
file_dotfiles="$dotfilesdir/$filename"
if [[ ! -e $file_dotfiles ]]; then
echo "! $file_dotfiles: target file does not exist"
exit 1
elif [[ -e $file_home ]]; then
if [[ ! "$(realpath "$file_home")" -ef "$file_dotfiles" ]]; then
echo "! $file_home already exists but is NOT a link to $file_dotfiles, please fix manually"
exit 1
fi
else
[[ $QUIET ]] || echo "* Creating link $file_home -> $file_dotfiles"
ln -sr "$file_dotfiles" "$file_home" || exit 1
fi
done
;;
createfrom)
if [[ $1 = '-n' ]]; then
NOLINK=1
shift
fi
[[ -n $1 ]] || { echo "$(basename $0) $cmd: Missing argument."; exit 1; }
for filename in "$@"; do
file_home="$homedir/$filename"
file_dotfiles="$dotfilesdir/$filename"
if [[ ! -e $file_home ]]; then
echo "! $file_home: source file does not exist"
exit 1
elif [[ -e $file_dotfiles ]]; then
echo "! $file_dotfiles already exists"
exit 1
else
mkdir -p "$(dirname "$file_dotfiles")"
[[ $QUIET ]] || echo "* Copy $file_home -> $file_dotfiles"
cp -n "$file_home" "$file_dotfiles" || exit 1
if [[ ! $NOLINK ]]; then
[[ $QUIET ]] || echo "* Remove original and link $file_home -> $file_dotfiles"
rm "$file_home" || exit 1
ln -sr "$file_dotfiles" "$file_home" || exit 1
fi
fi
done
;;
git)
git -C "$dotfilesdir" "$@"
;;
pull)
git -C "$dotfilesdir" pull "$@"
;;
bash-completion)
cat <<- 'END_OF_BASHCOMPLETION'
_dotfiles_filenames() {
compopt -o filenames
files=($(compgen -f -X '*/@(.|..|.git)' .dotfiles/$cur))
COMPREPLY=(${files[@]#.dotfiles/})
}
_dotfiles() {
COMPREPLY=()
local cur="${COMP_WORDS[COMP_CWORD]}"
local commands="help install ls diff link linkstatus createfrom git pull"
local gitcommands="add commit diff fetch log pull push rebase status"
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=($(compgen -W "$commands" -- $cur))
else
case "${COMP_WORDS[1]}" in
ls|diff)
[[ $COMP_CWORD -eq 2 ]] && _dotfiles_filenames
;;
link|linkstatus)
_dotfiles_filenames
;;
createfrom)
compopt -o filenames
COMPREPLY=($(compgen -f -- ${cur}))
;;
git)
if [[ $COMP_CWORD -eq 2 ]]; then
COMPREPLY=($(compgen -W "$gitcommands" -- ${cur}))
else
_dotfiles_filenames
fi
;;
esac
fi
}
complete -F _dotfiles dotfiles
END_OF_BASHCOMPLETION
;;
*)
echo "Unknown command '$cmd', use 'help' to show all commands."
exit 1
;;
esac