214 lines
5.4 KiB
Bash
Executable file
214 lines
5.4 KiB
Bash
Executable file
#!/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"
|