#!/bin/sh self=watch-git-tags # List sorted local tags local_tags() { for repo ; do git -C "$repo" tag --list | LC_COLLATE=C sort done } # List sorted remote tags remote_tags() { for repo ; do git -C "$repo" ls-remote --quiet --refs --tags || printf >&2 'Failed to retrieve tags for repository %s\n' "$PWD" done | while read -r _ tag ; do tag=${tag#refs/tags/} printf '%s\n' "$tag" done } # Create a temporary directory with name in $td, and handle POSIX-ish traps to # remove it when the script exits; requires mktemp(1) (not POSIX) td=$(mktemp -d) || exit cleanup() { [ -n "$td" ] || return rm -fr -- "$td" } for sig in EXIT HUP INT TERM ; do # shellcheck disable=SC2064 trap "cleanup $sig" "$sig" done # Use current directory if no other arguments [ "$#" -gt 0 ] || set -- . # Iterate through each repo in a subshell in parallel for repo ; do ( # Make a temporary directory with a hash in its name for uniqueness df=$(printf %s "$repo" | sed s:/:_:g) cs=$(printf %s "$repo" | cksum) sd=$td/$df.${cs%% *} mkdir -- "$sd" || exit # Step in and write repo path to file cd -- "$sd" || exit printf '%s\n' "$repo" > path || exit # Write local and remote tags to files local_tags "$repo" > "$sd"/a || exit remote_tags "$repo" > "$sd"/b || exit # Write new tags to file LC_COLLATE=C comm -13 -- [ab] > new # Attempt to quietly fetch new tags so that we don't notify about the same # ones next time [ -s new ] || continue git -C "$repo" fetch --quiet --tags ) & done # Wait for all of those to finish wait # Iterate through the temp dirs in order for dir in "$td"/* ; do ( cd -- "$dir" || exit 0 # Look for non-zero "new" files (at least one new tag) [ -s new ] || exit 0 # Print repository path and new tags sed '1!s/^/\t/' -- path new exit 1 ) ; done