adding gitsnips.sh

This commit is contained in:
tengel 2024-03-20 11:04:51 -05:00
parent cd2ed4866b
commit de577c1263

214
gitsnips.sh Executable file
View file

@ -0,0 +1,214 @@
#!/usr/bin/env bash
#
# Backup GitLab snippets to combined repo using git subtrees
#
# Required:
#
# - GitLab personal API token with read_api privilege
# - Automated git project for storing snippets
# - curl, git, jq, find, mktemp
#
# tl;dr
#
# 1. Create GitLab API token with 'read_api' access (private snippets)
# 2. Create a new private gitlab project, then clone and configure
#
# git clone git@gitlab.com:username/snippets.git
# cd snippets
# git config user.name "username"
# git config user.email "username@email.com"
#
# 3. If using multiple GitLab accounts/SSH keys:
#
# git config core.sshCommand "ssh -i ~/.ssh/username -F /dev/null"
#
# 4. Specify the token and project directory with '-t ... -d ...'
#
# ./gitsnips.sh -t ABCDEF1234 -d ../snippets/ -p
#
# 5. Review the above changes and `git push` the results when ready
#
# SPDX-License-Identifier: MIT
_VERSION="0.0.3"
_NAME=$(basename "${0}")
# usage
function usage() {
echo "${_NAME} version ${_VERSION}"
echo
echo "Usage: ${_NAME} [OPTIONS...]"
echo
echo " Options:"
echo " -d <dir> Git project directory (default: ./)"
echo " -t <string> GitLab token (read_api)"
echo " -i <file> Markdown index file (default: INDEX.md)"
echo " -s <file> Snippets JSON file (default: snippets.json)"
echo " -p Prune removed snippets (git rm -r)"
echo " -q Suppress output (quiet)"
echo " -V Version of program"
echo " -h This help text"
echo
}
# defaults
declare _GITDIR="./"
declare _TOKEN=""
declare _MDIDX="INDEX.md"
declare _SNJSON="snippets.json"
declare _PRUNE=0
declare _VERBOSE=1
# user input
while getopts "d:t:i:s:pqVh" opt; do
case "$opt" in
d)
_GITDIR="${OPTARG}" ;;
t)
_TOKEN="${OPTARG}" ;;
i)
_MDIDX="${OPTARG}" ;;
s)
_SNJSON="${OPTARG}" ;;
p)
_PRUNE=1 ;;
q)
_VERBOSE=0 ;;
V)
echo "${_NAME} version ${_VERSION}"
exit 0 ;;
h)
usage
exit 0 ;;
*)
echo "Unrecognized option: $OPTARG (Run '$_NAME -h' for help)"
exit 1 ;;
esac
done
shift $((OPTIND-1))
# handy for verbose/quiet
function noise {
local _MSG="$*"
if [[ ${_VERBOSE} -eq 1 ]]; then
echo "${_MSG}"
fi
}
# loop for checking apps
function checkapp() {
local _XAPPX="$1"
if [[ -z "$(command -v ${_XAPPX} 2>/dev/null)" ]]; then
noise "Application ${_XAPPX} not found, exiting."
exit 99
fi
}
# loop for checking directories
function checkdir() {
local _XDIRX="$1"
if [[ ! -d "${_XDIRX}" ]]; then
noise "Directory ${_XDIRX} not found, exiting."
exit 98
fi
if [[ ! -w "${_XDIRX}" ]]; then
noise "Directory ${_XDIRX} not writable, exiting."
exit 97
fi
_GDIRG="${_XDIRX%/}/.git"
if [[ ! -d "${_GDIRG}" ]]; then
noise "Directory ${_XDIRX} is not a git project, exiting."
exit 96
fi
}
# ensure we can run properly
function preflight() {
# apps
checkapp curl
checkapp jq
# dirs
checkdir "${_GITDIR}"
# token
if [[ -z "${_TOKEN}" ]]; then
noise "Token length zero; valid token required, exiting."
exit 9
fi
}
# this could exit
preflight
# prep for work
noise "Using ${_GITDIR} as git repository"
pushd "${_GITDIR}" >/dev/null
# fetch latest index
noise "Fetching latest snippet index"
curl --silent --header "PRIVATE-TOKEN: ${_TOKEN}" \
"https://gitlab.com/api/v4/snippets?per_page=65534" \
| jq > "${_SNJSON}"
git add "${_SNJSON}"
git commit -q -m "snippet list update" "${_SNJSON}"
# generate list of IDs
noise "Extracting list of snippets with jq"
# create an array string [1234]="title" for each item...
IDLIST=$(jq -r '.[] | "[\(.id)]=\"\(.title)\""' "${_SNJSON}")
# ...adding () around it creates a natural array input string
declare -A IDARRAY=$(echo "(${IDLIST})")
# human friendly index
# - 'git subtree' needs a clean working directory
_TMPIDX=$(mktemp)
echo -e "# Snippet Index\n" > "${_TMPIDX}"
# loop the IDs and either update or add
IDSORTED=$(echo "${!IDARRAY[@]}" | tr ' ' '\n' | sort -n | tr '\n' ' ')
# do not quote IDSORTED below
for id in ${IDSORTED}; do
if [[ -d "./${id}" ]]; then
noise "Updating snippet ${id}"
git subtree pull -q -P "${id}" -m "Updating ${id}" \
"git@gitlab.com:snippets/${id}.git" HEAD
else
noise "Adding snippet ${id}"
git subtree add -q -P "${id}" -m "Adding ${id}" \
"git@gitlab.com:snippets/${id}.git" HEAD
fi
echo " * [${id}](${id}) ${IDARRAY[$id]}" >> "${_TMPIDX}"
# be nice to gitlab
sleep 1
done
# commit the updated Markdown index
noise "Building ${_MDIDX}"
mv -f "${_TMPIDX}" "${_MDIDX}"
git add "${_MDIDX}"
git commit -q -m "markdown index update" "${_MDIDX}"
# prune stale items
if [[ ${_PRUNE} -eq 1 ]]; then
noise "Pruning stale snippets"
_RGX='^[0-9]+$'
# this gives a list of top level dirs without "./" or any hidden subdirs
_LDIRS=$(find ./ -mindepth 1 -maxdepth 1 -type d -not -path '*/\.*' -printf '%P\n')
for dir in ${_LDIRS}; do
if [[ ! ${dir} =~ ${_RGX} ]]; then
# directory is not a number
noise "Directory ${dir} is NaN, skipping"
continue
fi
if [[ ! ${IDARRAY[$dir]+_} ]]; then
# if dir is not an array key, remove it
noise "Pruning ${dir} not in snippet index"
git rm -r ${dir}
git commit -q -m "Removing ${dir}" ${dir}
fi
done
fi
# back to where we started
popd >/dev/null
noise "Done - review results and push updates to origin as appropriate"