|
#!/usr/bin/env sh |
|
# shellcheck disable=SC2065 |
|
version="0.2.0" |
|
path="$HOME/.sources/.vendor" |
|
|
|
usage="$( |
|
cat << EOF |
|
Usage: $(basename "$0") [COMMAND] [REPO] [OPTIONS] |
|
Adds, updates or deletes git source files of third party vendors in the local source storage at $path. |
|
This tool is meant to clone third party sources to compile or use without further modification. |
|
It will try to checkout the latest tag and fallback to the latest branch, unless a prefered branch/tag is given. |
|
If you want to load a workspace, consider forking and cloning using the normal git utility instead. |
|
|
|
COMMAND |
|
ls Lists all locally added repositories/branches. |
|
dir Outputs the directory of the given repository/branch. |
|
sh Switches to the given repository/branch in a new shell. |
|
add Clones the given repository/branch with all its submodules with depth of 1 into the localized source storage and outputs its path. |
|
up Resets and pulls the latest changes on the given repository/branch. Will run ../.hooks/up.pre and ../.hooks/up.post. |
|
up-all Resets and pulls the latest changes on all repositories/branches. Will run ../.hooks/up.pre and ../.hooks/up.post. |
|
audit Reviews net committed changes since the first unconfirmed upgrade using Git difftool. Keeps the pending baseline. |
|
ok Explicitly clears the pending upgrade baseline for the given repository/branch. |
|
del Deletes the given repository/branch from the system. Will run ../.hooks/del.pre and ../.hooks/del.post. |
|
|
|
REPO |
|
Unused on the ls and up-all command. |
|
For the add-command, needs to be a HTTPS URL or SSH destination ending with .git. |
|
For any other command it can also be just the sub path (without branch) on the file system. |
|
|
|
OPTIONS |
|
-V, --version Prints the version of this script. |
|
-v, --verbose Prints verbose information. |
|
-p=*, --path=* Sets the working directory path (root) of the source storage (default: $path). |
|
-b=*, --branch=* Sets the branch/tag of the repo to work with. Tags lock to a specific version. The 'ls' command will accept -b=/ to inline branches into paths. |
|
For up/audit/ok, omit for the rolling baseline; use -b BRANCH or REPO/BRANCH for a branch baseline. |
|
-c=*, --cmd=* Automatically runs the given command expression instead of launching an interactive shell when using the 'sh' command. |
|
-f, --force When using the up/up-all commands forces a hard reset if incoming changes cannot be rebased. When using the del command it ignores a missing repo and cleans up anyways. |
|
--aur REPO is a package name on the AUR and it will be expanded to the actual repo. |
|
--sh When adding a new repo, will automatically run the sh subcommand. |
|
-h, --help Prints this help message. |
|
EOF |
|
)" |
|
|
|
c_red="$(printf '\033[31m')" |
|
c_yellow="$(printf '\033[33m')" |
|
c_green="$(printf '\033[32m')" |
|
c_reset="$(printf '\033[0m')" |
|
|
|
while [ "$#" -gt 0 ]; do |
|
case "$1" in |
|
-V | --version) echo "$version" && exit 0 ;; |
|
-v | --verbose) verbose=1 ;; |
|
-p | --path) shift; path="$1" ;; |
|
-p=* | --path=*) path="${1#*=}" ;; |
|
-b | --branch) shift; branch="$1" ;; |
|
-b=* | --branch=*) branch="${1#*=}" ;; |
|
-c | --cmd) shift; cmd="$1" ;; |
|
-c=* | --cmd=*) cmd="${1#*=}" ;; |
|
-f | --force) force=1 ;; |
|
--aur) aur=1 ;; |
|
--sh) sh=1 ;; |
|
-h | --help) echo "$usage" >&2 && exit 129 ;; |
|
*) |
|
if [ "$command" = "" ]; then |
|
command="$1" |
|
elif [ "$repo" = "" ]; then |
|
repo="$1" |
|
else |
|
echo "Invalid command line flag $1." >&2 |
|
exit 1 |
|
fi |
|
;; |
|
esac |
|
shift |
|
done |
|
|
|
test ! "$aur" = 1 || repo="https://aur.archlinux.org/$repo.git" |
|
lockedbranch="$branch" |
|
test -n "$verbose" && set -x |
|
set -e |
|
|
|
command -v "git" > /dev/null 2>&1 || { |
|
echo "Missing git." >&2 |
|
exit 1 |
|
} |
|
test ! "$command" = "" || { |
|
echo "Missing command." >&2 |
|
exit 1 |
|
} |
|
|
|
linksubpath="share" |
|
|
|
if [ ! -d "$path/$linksubpath" ]; then |
|
mkdir -p "$path" |
|
chown -R "$(whoami)" "$path" |
|
mkdir -p "$path/$linksubpath" |
|
fi |
|
|
|
if [ "$command" = "ls" ]; then |
|
if [ "$branch" = "/" ]; then |
|
find "$path/$linksubpath" -maxdepth 1 -type l -exec basename {} \; | sed 's/,,/\//' | tr , / |
|
find "$path/$linksubpath" -maxdepth 1 -type f -name '*.lnk' -exec basename {} \; | sed 's/^\.\///' | sed 's/,,/\//' | sed 's/\.lnk$//' | tr , / # lnk support for network sync to msys2 |
|
else |
|
find "$path/$linksubpath" -maxdepth 1 -type l -exec basename {} \; | sed 's/,,/ -b=/' | tr , / |
|
find "$path/$linksubpath" -maxdepth 1 -type f -name '*.lnk' -exec basename {} \; | sed 's/^\.\///' | sed 's/,,/ -b=/' | sed 's/\.lnk$//' | tr , / # lnk support for network sync to msys2 |
|
fi |
|
exit |
|
elif [ "$command" = "up-all" ]; then |
|
extra_flags="" |
|
if [ -n "$force" ]; then |
|
extra_flags="--force" |
|
fi |
|
if [ -n "$verbose" ]; then |
|
extra_flags="$extra_flags --verbose" |
|
fi |
|
# shellcheck disable=SC2086 |
|
"$0" -p="$path" ls | xargs -r -L1 "$0" $extra_flags -p="$path" up |
|
exit |
|
fi |
|
|
|
test -n "$repo" || { |
|
echo "Missing repo." >&2 |
|
exit 1 |
|
} |
|
test "$repo" = "${repo% *}" || { |
|
echo "Cannot have spaces in repo." >&2 |
|
exit 1 |
|
} |
|
reposubpath="$repo" |
|
reposubpath="${reposubpath#*//}" # remove ^https:// |
|
reposubpath="${reposubpath#*@}" # remove ^git@ |
|
reposubpath="${reposubpath%.git}" # remove .git$ |
|
reposubpath=$(printf '%s' "$reposubpath" | tr : /) # replace : with / |
|
test ! "$(printf '%s' "$reposubpath" | tr -d / | tr -d '.')" = "" || { |
|
echo "Local reposubpath results in empty or invalid path." >&2 |
|
exit 1 |
|
} |
|
|
|
if [ "$branch" = "" ]; then |
|
reposubpathslashes=$(echo "$reposubpath" | tr -cd '/') |
|
if [ "${#reposubpathslashes}" = "3" ]; then # path has branch/tag, so remove it from reposubpath and set it in branch |
|
branch="${reposubpath##*/}" |
|
reposubpath="${reposubpath%/*}" |
|
fi |
|
fi |
|
# only when the branch is explicitely defined, we remember it. |
|
if [ "$branch" = "" ]; then |
|
link="$path/$linksubpath/$(printf '%s' "$reposubpath" | tr / ,)" # no branch/tag for rolling release |
|
else |
|
link="$path/$linksubpath/$(printf "%s,,%s" "$reposubpath" "$branch" | tr / ,)" |
|
fi |
|
|
|
reviewbranch="${branch:-_}" |
|
|
|
if [ "$command" = "audit" ] || [ "$command" = "ok" ]; then |
|
git check-ref-format "refs/heads/$reviewbranch" > /dev/null 2>&1 || { |
|
echo "Invalid audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
fullauditpath="${path:?}/$reposubpath/.audit" |
|
# Encode percent first: slash branches stay inside .audit without escape collisions. |
|
reviewname="$(printf '%s' "$reviewbranch" | sed 's/%/%25/g; s,/,%2F,g')" || { |
|
rc=$? |
|
echo "Cannot encode audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
auditfile="$fullauditpath/$reviewname.commit" |
|
test ! -L "$fullauditpath" && test ! -L "$auditfile" || { |
|
echo "Pending audit paths must not be symbolic links." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
if [ "$command" = "audit" ]; then |
|
current_checkout="$("$0" -p="$path" dir "$reposubpath" -b="$branch")" || { |
|
rc=$? |
|
echo "Cannot resolve the current checkout for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
fi |
|
test -f "$auditfile" || { |
|
echo "Nothing pending to $command for $repo ($reviewbranch)." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 2 |
|
} |
|
if [ "$command" = "ok" ]; then |
|
rm -f -- "$auditfile" || { |
|
rc=$? |
|
echo "Cannot confirm pending upgrades for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
exit |
|
fi |
|
oldcommit="$(cat "$auditfile")" || { |
|
rc=$? |
|
echo "Cannot read pending baseline $auditfile." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
case "$oldcommit" in |
|
'' | *[!0-9a-fA-F]*) |
|
echo "Invalid pending commit in $auditfile." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
;; |
|
esac |
|
currentcommit="$(git -C "$current_checkout" rev-parse --verify 'HEAD^{commit}')" || { |
|
rc=$? |
|
echo "Cannot resolve the current HEAD for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
origin="$(git -C "$current_checkout" remote get-url origin)" || { |
|
rc=$? |
|
echo "Cannot resolve origin for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
test -n "$origin" || { |
|
echo "Empty origin URL for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
# Local relative URLs are relative to the checkout, not the temporary repository. |
|
case "$origin" in |
|
/* | *://*) ;; |
|
*) |
|
case "${origin%%/*}" in |
|
*:*) ;; |
|
*) origin="$current_checkout/$origin" ;; |
|
esac |
|
;; |
|
esac |
|
objectformat="$(git -C "$current_checkout" rev-parse --show-object-format)" || { |
|
rc=$? |
|
echo "Cannot resolve the Git object format for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
reviewtmp="" |
|
trap 'rc=$?; trap - 0; test -z "$reviewtmp" || rm -rf -- "$reviewtmp" || { test "$rc" -ne 0 || rc=1; }; exit "$rc"' 0 |
|
trap 'exit 129' HUP |
|
trap 'exit 130' INT |
|
trap 'exit 143' TERM |
|
reviewtmp="$(mktemp -d)" || { |
|
rc=$? |
|
echo "Cannot create a temporary audit directory." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git init --bare --object-format="$objectformat" "$reviewtmp/repo" >&2 || { |
|
rc=$? |
|
echo "Cannot initialize the temporary audit repository." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git --git-dir="$reviewtmp/repo" fetch --depth 1 --no-tags --no-recurse-submodules --no-write-fetch-head -- "$origin" "$oldcommit:refs/audit/old" "$currentcommit:refs/audit/current" || { |
|
rc=$? |
|
echo "Cannot fetch audit commits $oldcommit and $currentcommit from origin; objects may no longer be reachable. Pending baseline retained." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
# Preserve effective tool preferences, including checkout-local and conditional config. |
|
rc=0 |
|
toolkeys="$(git -C "$current_checkout" config --name-only --get-regexp '^(diff\.(tool|guitool)|difftool\..*|merge\.(tool|guitool)|mergetool\..*)$')" || rc=$? |
|
test "$rc" -eq 0 || test "$rc" -eq 1 || { |
|
echo "Cannot read Git difftool settings for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
if [ -n "$toolkeys" ]; then |
|
printf '%s\n' "$toolkeys" | while IFS= read -r toolkey; do |
|
toolvalue="$(git -C "$current_checkout" config --get "$toolkey")" || { |
|
rc=$? |
|
echo "Cannot read Git setting $toolkey." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git --git-dir="$reviewtmp/repo" config "$toolkey" "$toolvalue" || { |
|
rc=$? |
|
echo "Cannot configure the temporary difftool." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
done || { |
|
rc=$? |
|
echo "Cannot copy Git difftool settings for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
fi |
|
# Difftool requires a work tree even when comparing two committed trees. |
|
mkdir "$reviewtmp/worktree" || { |
|
rc=$? |
|
echo "Cannot create the temporary difftool work tree." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git --git-dir="$reviewtmp/repo" --work-tree="$reviewtmp/worktree" difftool --trust-exit-code --submodule=short refs/audit/old refs/audit/current -- || { |
|
rc=$? |
|
echo "Git difftool failed for $repo; pending baseline retained." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
exit |
|
fi |
|
|
|
if [ "$command" = "dir" ]; then |
|
if [ -f "$link.lnk" ]; then |
|
# Ugly msys2 work around for VMs with network storage, i.e. Parallels turns symlinks into lnk files. |
|
# Just parse the name, but the branch needs to derived from the default branch, if it has not been supplied. |
|
# This will break things if the default branch changes, which will happen for tagged releases. |
|
# TODO: Probably also create a database file for each repo, which points to the right branch. |
|
if [ "$branch" = "" ]; then |
|
branch="$(git ls-remote --tags "https://$reposubpath.git" | sort -t v -k2,2V | tail -n1 | awk -F '[/\\^]' '{print $3}')" # get latest tag |
|
test ! "$branch" = "" || branch="$(git ls-remote --symref "https://$reposubpath.git" HEAD | awk '/^ref:/ {sub(/refs\/heads\//, "", $2); print $2}')" # or get default branch |
|
test ! "$branch" = "" || { |
|
echo "Repository $repo doesn't exist or it has no tags or no default branch." >&2 |
|
exit 3 |
|
} |
|
fi |
|
echo "$path/$(basename "$link.lnk" | sed 's/^\.\///' | sed 's/,,/ -b=/' | sed 's/\.lnk$//' | tr , /)/$branch" && exit # lnk support for network sync to msys2 |
|
fi |
|
test -h "$link" || { |
|
echo /dev/null |
|
echo "Repository $repo doesn't exist on the local system, because $link doesn't exist." >&2 |
|
exit 2 |
|
} |
|
readlink -f "$link" |
|
exit |
|
elif [ "$command" = "sh" ]; then |
|
( |
|
cd "$("$0" -p="$path" dir "$repo" -b="$lockedbranch")" |
|
{ test "$cmd" = "" && "$SHELL" -i; } || "$SHELL" -c "$cmd" |
|
) |
|
exit |
|
fi |
|
|
|
if [ "$branch" = "" ]; then |
|
branch="$(git ls-remote --tags "https://$reposubpath.git" | sort -t v -k2,2V | tail -n1 | awk -F '[/\\^]' '{print $3}')" # get latest tag |
|
test ! "$branch" = "" || branch="$(git ls-remote --symref "https://$reposubpath.git" HEAD | awk '/^ref:/ {sub(/refs\/heads\//, "", $2); print $2}')" # or get default branch |
|
test ! "$branch" = "" || branch="master" |
|
echo "Autopicked branch $branch" >&2 |
|
fi |
|
|
|
fullrepopath="$path/$reposubpath/$branch" |
|
echo "Full path to repository: $fullrepopath" >&2 |
|
fullhookspath="${path:?}/$reposubpath/.hooks" |
|
echo "Full path to its hooks: $fullhookspath" >&2 |
|
|
|
if [ "$command" = "add" ]; then |
|
if [ -L "$link" ] || [ -d "$fullrepopath" ]; then |
|
echo "Repository $repo already added to the local system." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 2 |
|
fi |
|
echo "Adding $repo ($branch) to $fullrepopath…" >&2 |
|
mkdir -p "$fullrepopath" >&2 |
|
echo "Cloning $repo ($branch) to $fullrepopath…" >&2 |
|
if ! git clone --recursive --depth 1 --branch "$branch" "$repo" "$fullrepopath"; then |
|
rm -rf "$fullrepopath" |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 3 |
|
fi |
|
ln -sf "../$reposubpath/$branch" "$link" >&2 |
|
echo "Linked $fullrepopath to $link." >&2 |
|
test -h "$link" || { |
|
echo /dev/null |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 4 |
|
} |
|
readlink -f "$link" |
|
mkdir -p "$fullhookspath" >&2 # makes it easier to find by the user |
|
rc=0 |
|
if [ "$sh" = 1 ]; then |
|
if [ -n "$cmd" ]; then |
|
"$0" -p="$path" sh "$repo" -b="$lockedbranch" -c="$cmd" || rc=$? |
|
else |
|
"$0" -p="$path" sh "$repo" -b="$lockedbranch" || rc=$? |
|
fi |
|
fi |
|
if [ ! "$rc" -eq 0 ]; then |
|
printf '%s%-10s%s %s\n' "$c_yellow" "INCOMPLETE" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
fi |
|
printf '%s%-10s%s %s\n' "$c_green" "ADDED" "$c_reset" "$repo" >&2 |
|
elif [ "$command" = "up" ]; then |
|
test -L "$link" || { |
|
echo "Repository $repo doesn't exist on the local system." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 2 |
|
} |
|
oldfullrepopath="$(realpath "${path:?}/$linksubpath/$(readlink "$link")")" |
|
test -d "$oldfullrepopath" || { |
|
echo "Repository $repo doesn't exist on the local system, because $oldfullrepopath does not exist." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 2 |
|
} |
|
echo "Updating $oldfullrepopath…" >&2 |
|
if [ "$oldfullrepopath" = "$fullrepopath" ]; then |
|
# same branch |
|
rc=0 |
|
git -C "$fullrepopath" fetch --depth 1 origin "$branch" >&2 || rc=$? |
|
if [ ! "$rc" -eq 0 ]; then |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
fi |
|
if [ -n "$(git -C "$fullrepopath" diff HEAD "origin/$branch" 2> /dev/null || exit 0)" ]; then |
|
# has new changes on remote |
|
( |
|
oldcommit="$(git -C "$oldfullrepopath" rev-parse --verify 'HEAD^{commit}')" || { |
|
rc=$? |
|
echo "Cannot resolve the pre-upgrade HEAD for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git check-ref-format "refs/heads/$reviewbranch" > /dev/null 2>&1 || { |
|
echo "Invalid audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
fullauditpath="${path:?}/$reposubpath/.audit" |
|
# Encode percent first: slash branches stay inside .audit without escape collisions. |
|
reviewname="$(printf '%s' "$reviewbranch" | sed 's/%/%25/g; s,/,%2F,g')" || { |
|
rc=$? |
|
echo "Cannot encode audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
auditfile="$fullauditpath/$reviewname.commit" |
|
test ! -L "$fullauditpath" && test ! -L "$auditfile" || { |
|
echo "Pending audit paths must not be symbolic links." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
mkdir -p "$fullauditpath" || { |
|
rc=$? |
|
echo "Cannot create $fullauditpath." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
test ! -f "$auditfile" || exit 0 |
|
auditpending="" |
|
trap 'rc=$?; trap - 0; test -z "$auditpending" || rm -f -- "$auditpending" || { test "$rc" -ne 0 || rc=1; }; exit "$rc"' 0 |
|
trap 'exit 129' HUP |
|
trap 'exit 130' INT |
|
trap 'exit 143' TERM |
|
auditpending="$(mktemp "$fullauditpath/.pending.XXXXXX")" || { |
|
rc=$? |
|
echo "Cannot create a pending baseline file." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
printf '%s\n' "$oldcommit" > "$auditpending" || { |
|
rc=$? |
|
echo "Cannot write pending audit baseline for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
# Publish complete bytes atomically; a competing creator keeps its oldest baseline. |
|
ln -T -- "$auditpending" "$auditfile" || { |
|
test -f "$auditfile" && test ! -L "$auditfile" || { |
|
echo "Cannot save pending audit baseline $auditfile." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
} |
|
) |
|
test -x "$fullhookspath/up.pre" && "$fullhookspath/up.pre" || true |
|
if ! git -C "$fullrepopath" pull --rebase; then |
|
git -C "$fullrepopath" rebase --abort 2>/dev/null || true |
|
if [ "$force" ]; then |
|
git -C "$fullrepopath" reset --hard |
|
git -C "$fullrepopath" pull --ff-only |
|
else |
|
echo "Update failed, because incoming changes cannot be merged with local changes. Use the -f flag to force a reset of local changes or manually rebase and solve the conflicts." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 3 |
|
fi |
|
fi |
|
test -x "$fullhookspath/up.post" && "$fullhookspath/up.post" || true |
|
printf '%s%-10s%s %s\n' "$c_yellow" "UPGRADED" "$c_reset" "$repo" >&2 |
|
exit |
|
fi |
|
printf '%s%-10s%s %s\n' "$c_green" "SKIPPED" "$c_reset" "$repo" >&2 |
|
else |
|
# new version/tag/branch |
|
if [ -d "$fullrepopath" ]; then |
|
# the new version already exists, because there is also a locked pull |
|
echo "The version you want to upgrade to is already locked in a separate pull." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 3 |
|
fi |
|
( |
|
oldcommit="$(git -C "$oldfullrepopath" rev-parse --verify 'HEAD^{commit}')" || { |
|
rc=$? |
|
echo "Cannot resolve the pre-upgrade HEAD for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
git check-ref-format "refs/heads/$reviewbranch" > /dev/null 2>&1 || { |
|
echo "Invalid audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
fullauditpath="${path:?}/$reposubpath/.audit" |
|
# Encode percent first: slash branches stay inside .audit without escape collisions. |
|
reviewname="$(printf '%s' "$reviewbranch" | sed 's/%/%25/g; s,/,%2F,g')" || { |
|
rc=$? |
|
echo "Cannot encode audit branch $reviewbranch." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
auditfile="$fullauditpath/$reviewname.commit" |
|
test ! -L "$fullauditpath" && test ! -L "$auditfile" || { |
|
echo "Pending audit paths must not be symbolic links." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
mkdir -p "$fullauditpath" || { |
|
rc=$? |
|
echo "Cannot create $fullauditpath." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
test ! -f "$auditfile" || exit 0 |
|
auditpending="" |
|
trap 'rc=$?; trap - 0; test -z "$auditpending" || rm -f -- "$auditpending" || { test "$rc" -ne 0 || rc=1; }; exit "$rc"' 0 |
|
trap 'exit 129' HUP |
|
trap 'exit 130' INT |
|
trap 'exit 143' TERM |
|
auditpending="$(mktemp "$fullauditpath/.pending.XXXXXX")" || { |
|
rc=$? |
|
echo "Cannot create a pending baseline file." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
printf '%s\n' "$oldcommit" > "$auditpending" || { |
|
rc=$? |
|
echo "Cannot write pending audit baseline for $repo." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit $rc |
|
} |
|
# Publish complete bytes atomically; a competing creator keeps its oldest baseline. |
|
ln -T -- "$auditpending" "$auditfile" || { |
|
test -f "$auditfile" && test ! -L "$auditfile" || { |
|
echo "Cannot save pending audit baseline $auditfile." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 1 |
|
} |
|
} |
|
) |
|
test -x "$fullhookspath/up.pre" && "$fullhookspath/up.pre" || true |
|
mkdir -p "$fullrepopath" >&2 |
|
if ! git clone --recursive --depth 1 --branch "$branch" "https://$reposubpath.git" "$fullrepopath"; then |
|
rm -rf "$fullrepopath" |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 3 |
|
fi |
|
ln -sf "../$reposubpath/$branch" "$link" >&2 |
|
test -x "$fullhookspath/up.post" && "$fullhookspath/up.post" || true |
|
rm -rf "$oldfullrepopath" >&2 || { |
|
echo "Failed to remove old version." >&2 |
|
printf '%s%-10s%s %s\n' "$c_yellow" "INCOMPLETE" "$c_reset" "$repo" >&2 |
|
exit 4 |
|
} |
|
printf '%s%-10s%s %s\n' "$c_yellow" "UPGRADED" "$c_reset" "$repo" >&2 |
|
fi |
|
elif [ "$command" = "del" ]; then |
|
test -d "$link" || test -n "$force" || { |
|
echo "Repository $repo doesn't exist on the local system. Use the -f flag to forcefully try delete and clean up anyways." >&2 |
|
printf '%s%-10s%s %s\n' "$c_red" "ERROR" "$c_reset" "$repo" >&2 |
|
exit 2 |
|
} |
|
test -d "$link" && fullrepopath="$path$(readlink "$link" | sed 's/^..//')" # get real fullrepopath in case of older rolling |
|
echo "Deleting $fullrepopath…" >&2 |
|
test -x "$fullhookspath/del.pre" && "$fullhookspath/del.pre" || true |
|
test -z "$fullrepopath" || rm -rf "$fullrepopath" >&2 || test -n "$force" |
|
echo "Cleaning up…" >&2 |
|
rm -rf "$link" || test -n "$force" >&2 |
|
rmdir "${path:?}/$linksubpath" 2> /dev/null || true |
|
test -x "$fullhookspath/del.post" && "$fullhookspath/del.post" || true |
|
find "${path:?}/" -depth -mindepth 1 -maxdepth 2 -type d -exec rmdir {} + 2> /dev/null || true |
|
printf '%s%-10s%s %s\n' "$c_green" "DELETED" "$c_reset" "$repo" >&2 |
|
else |
|
echo "Unknown command $command." >&2 |
|
exit 1 |
|
fi |
|
|
|
set +e && set +x |
// reserved