Skip to content

Instantly share code, notes, and snippets.

@ikskuh
Created September 25, 2023 11:40
Show Gist options
  • Save ikskuh/9f8be7322d1bd05a6c7e3d41caf3ae3d to your computer and use it in GitHub Desktop.
Save ikskuh/9f8be7322d1bd05a6c7e3d41caf3ae3d to your computer and use it in GitHub Desktop.
basic dotfile manager
#!/usr/bin/env bash
set -euo pipefail
which git sed cut 1>/dev/null
timestamp="$(date +'%Y-%m-%d %H:%M')"
c_base=$'\033[0m'
c_red=$'\033[00;31m'
c_green=$'\033[00;32m'
c_yellow=$'\033[00;33m'
c_blue=$'\033[00;34m'
c_purple=$'\033[00;35m'
c_cyan=$'\033[00;36m'
c_lightgray=$'\033[00;37m'
c_lred=$'\033[01;31m'
c_lgreen=$'\033[01;32m'
c_lyellow=$'\033[01;33m'
c_lblue=$'\033[01;34m'
c_lpurple=$'\033[01;35m'
c_lcyan=$'\033[01;36m'
c_white=$'\033[01;37m'
function dotgit()
{
git -c core.excludesFile=$HOME/.shared-config/.gitignore --git-dir=$HOME/.shared-config/.git --work-tree=$HOME "$@"
}
function errlog()
{
echo "$@" >&2
}
function panic()
{
errlog "$@"
exit 255
}
function dotfiles-status()
{
dotgit status "$@" --porcelain --null | (
while IFS= read -r -d $'\0' line; do
if [[ "$line" = \?\?* ]]; then
continue
fi
status="$(echo $(echo "$line" | cut -b 1-3))"
file="$(echo "$line" | cut -b 4-)"
if [ "$status" != "??" ]; then
case "$status" in
M) # modified
echo " ${c_lgreen}~/${file}${c_base}"
;;
D) # deleted
echo " ${c_red}~/${file}${c_base} (deleted)"
;;
A) # added
;&
AM) # added+modified
;&
*)
echo " ${c_lpurple}~/${file}${c_base} (unknown status: '${status}')"
;;
esac
fi
done
)
}
function dotfiles-diff()
{
dotgit diff HEAD "$@"
}
function dotfiles-sync()
{
dotgit status "$@" --porcelain --null | {
declare -a files=()
fault=0
while IFS= read -r -d $'\0' line; do
if [[ "$line" = \?\?* ]]; then
continue
fi
status="$(echo $(echo "$line" | cut -b 1-3))"
file="$(echo "$line" | cut -b 4-)"
if [ "$status" != "??" ]; then
case "$status" in
M) # modified
files+=("$HOME/${file}")
;;
D) # deleted
echo " ${c_red}~/${file}${c_base} (deleted)"
fault=1
;;
*)
echo " ${c_lpurple}~/${file}${c_base} (unknown status: '${status}')"
fault=1
;;
esac
fi
done
if [ $fault -ne 0 ]; then
errlog "Repository unclean, please fix state first before syncing!"
return 1
fi
dotgit add -f "${files[@]}"
dotgit commit -m "[${timestamp}] Synchronizes ${files[@]}" || {
dotgit reset "${files[@]}"
errlog "failed create commit!"
return 1
}
dotgit push
dotgit pull --rebase=false
}
}
function dotfiles-add()
{
if [ "$#" -lt 1 ]; then
panic "add requires files to be added!"
fi
declare -a list=()
for file in "$@"; do
rfile="$(realpath "${file}")"
dotgit add -f "${rfile}"
list+=("$(echo "${rfile}" | sed 's|^'"$HOME"'|~|')")
unset rfile
done
msg="[${timestamp}] Adds/updates ${list[@]}"
dotgit commit -m "msg"
dotgit push
}
function dotfiles-remove()
{
if [ "$#" -lt 1 ]; then
panic "remove requires files to be added!"
fi
declare -a list=()
fault=0
for file in "$@"; do
rfile="$(realpath "${file}")"
if dotgit rm -rf "${rfile}"; then
list+=("$(echo "${rfile}" | sed 's|^'"$HOME"'|~|')")
else
errlog "failed to remove ${file}"
fault=1
fi
done
if [ $fault -eq 0 ]; then
dotgit commit -m "[${timestamp}] Deletes ${list[@]}"
dotgit push
else
errlog "won't delete files with failures!"
fi
}
if [ "$#" -lt 1 ]; then
errlog "usage: dotfiles <command>"
errlog ""
errlog " commands:"
errlog " status - prints which local files have changes."
errlog " diff - lists all active changes."
errlog " sync - commits changes and pulls changes from the remote."
errlog " add <files…> - adds the given files and pushes them to the remote."
errlog " remove <files>… - removes the given files and drops them from the remote."
errlog " git <…> - direct access to git."
errlog ""
exit 1
fi
command="$1"
shift
case "${command}" in
status)
dotfiles-status "$@"
;;
sync)
dotfiles-sync "$@"
;;
add)
dotfiles-add "$@"
;;
remove)
dotfiles-remove "$@"
;;
diff)
dotfiles-diff "$@"
;;
git)
dotgit "$@"
;;
*)
panic "invalid command ${command}"
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment