git-wad

Manage files via git but not their content
git clone git://git.meso-star.fr/git-wad.git
Log | Files | Refs | README | LICENSE

commit 343d353a3713b48bd56eaa382ab96b1be309a265
parent ea75f9332dffb3753a289100700ac783e4eed0c5
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Tue, 20 Aug 2024 15:16:58 +0200

Updated error handling

The "set -e" option has been used to handle execution failures. However,
this option does not handle the failure of an individual command in a
multi-command pipeline. As a result, several errors were not handled,
such as the one returned by the wad_objects function when analyzing an
invalid option. Furthermore, in the event of failure, temporary files
might not be deleted.

Temporary files are now grouped together in a temporary directory
created each time the git-wad command is invoked. The "die" function has
been added to delete this directory before exiting with an optional
user-defined status code (its default value is 1). Functions that can
fail are no longer executed in a multi-command pipeline; they are
executed separately and their result is written to a temporary file,
while their failure is returned to the "die" function.  Commands that
may fail are also checked to execute "die" if necessary.

So, from now on, errors should be handled correctly. In any case, the
"set -e" command is still used to take into account any unexpected
errors.

Diffstat:
Mgit-wad | 135+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 81 insertions(+), 54 deletions(-)

diff --git a/git-wad b/git-wad @@ -17,8 +17,17 @@ set -e +die() +{ + rm -rf "${git_wad_tmpdir}" # cleanup temporary files + exit "${1:-1}" # return status code (default is 1) +} + +# shellcheck disable=SC2310 +working_tree="$(git rev-parse --show-toplevel)" || die "$?" +git_wad_tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/git_wad_XXXXXX")" || die "$?" + # Load local config file -working_tree="$(git rev-parse --show-toplevel)" config_file="${working_tree}/.git_wad.cfg" if [ -f "${config_file}" ]; then # shellcheck disable=SC1090 @@ -27,8 +36,7 @@ fi GIT_WAD_HEADER="#\$# git-wad" GIT_WAD_OBJDIR="${working_tree}/.git/wad" - -[ -z "${GIT_WAD_VERBOSE}" ] && GIT_WAD_VERBOSE=0 +GIT_WAD_VERBOSE="${GIT_WAD_VERBOSE:-0}" if [ -z "${GIT_WAD_REMOTE_FETCH}" ] \ && git remote | grep -qe "^origin$"; then @@ -88,7 +96,7 @@ wad_hashes() wad_paths() { # Store the hash of WAD file - hashes="$(mktemp -p "${GIT_WAD_OBJDIR}")" + hashes="${git_wad_tmpdir}/hashes" wad_hashes | sort -b > "${hashes}" # Lists the current tree, printing the hash and path of its files @@ -96,9 +104,7 @@ wad_paths() # | Print path for WAD files only git ls-tree -r --format="%(objectname) %(path)" HEAD "${working_tree}" \ | sort -b \ - | join -o 2.2 "${hashes}" - \ - - rm "${hashes}" # Remove temporary file + | join -o 2.2 "${hashes}" - } # List WAD objects from the current working tree @@ -106,12 +112,13 @@ wad_objects() # [-1a] { rev="HEAD" # Revision - while getopts a1 opt; do + OPTIND=1 + while getopts ":a1" opt; do case "${opt}" in 1) rev="HEAD -1" ;; # Last commit only a) rev="--all" ;; *) - >&2 printf "Invalid option %s\n" "$1" + >&2 printf "Invalid option -- %s\n" "${OPTARG}" return 1 ;; esac @@ -149,30 +156,39 @@ all_objects() unreferenced_objects() # [-1a] { # Lists all locally stored WAD objects in tmpfile - tmpfile="$(mktemp -p "${GIT_WAD_OBJDIR}")" - all_objects | sort > "${tmpfile}" + local_wads="${git_wad_tmpdir}/local_wads" + all_objects | sort > "${local_wads}" + + # List WAD objects in the current working tree or to commit + wads="${git_wad_tmpdir}/wads" + # shellcheck disable=SC2310 + { wad_objects "$@" && wad_objects_staged; } > "${wads}" || die "$?" # The following command line is translated as follows: - # List of WAD objects in the current work tree or to commit - # | Sort them in ascending order + # Sort WAD objects in ascending order # | Subtract them from the list of locally stored WAD objects - { wad_objects "$@"; wad_objects_staged; } \ - | sort \ - | comm -23 "${tmpfile}" - - - rm -f "${tmpfile}" + sort "${wads}" | comm -23 "${local_wads}" - } objects_to_push() # [-1a] { - wad_objects "$@" | xargs -I {} sh -c \ - "if [ -f \"${GIT_WAD_OBJDIR}/{}\" ]; then echo \"{}\"; fi" + wads="${git_wad_tmpdir}/wad_objects" + # shellcheck disable=SC2310 + wad_objects "$@" > "${wads}" || die "$?" + + xargs -I {} sh -c \ + "if [ -f \"${GIT_WAD_OBJDIR}/{}\" ]; then echo \"{}\"; fi" \ + < "${wads}" } objects_to_fetch() # [-1a] { - wad_objects "$@" | xargs -I {} sh -c \ - "if [ ! -f \"${GIT_WAD_OBJDIR}/{}\" ]; then echo \"{}\"; fi" + wads="${git_wad_tmpdir}/wad_objects" + # shellcheck disable=SC2310 + wad_objects "$@" > "${wads}" || die "$?" + + xargs -I {} sh -c \ + "if [ ! -f \"${GIT_WAD_OBJDIR}/{}\" ]; then echo \"{}\"; fi" < "${wads}" } # Return the current timestamp plus one second @@ -227,9 +243,9 @@ log() # str [, arg...] clean() # stdin { - tmpfile="$(mktemp -p "${GIT_WAD_OBJDIR}")" - digest=$(cat - | tee "${tmpfile}" | sha256sum | cut -d' ' -f1) - size=$(wc -c < "${tmpfile}") + tmpclean="${git_wad_tmpdir}/tmpclean" + digest=$(cat - | tee "${tmpclean}" | sha256sum | cut -d' ' -f1) + size=$(wc -c < "${tmpclean}") # Copy all bytes that could correspond to a WAD header. Note that null # bytes are replaced by a 0 to avoid a warning about ignored null @@ -240,13 +256,12 @@ clean() # stdin # bytes being mistakenly translated into a WAD header, as searched for # in the following. header_size="$(sizeof_header)" - header="$(dd ibs=1 count="${header_size}" if="${tmpfile}" 2>/dev/null\ + header="$(dd ibs=1 count="${header_size}" if="${tmpclean}" 2>/dev/null\ | tr '\0' '0')" # Do not clean input stream if it is an un-smudged WAD if [ "${header}" = "${GIT_WAD_HEADER}" ]; then - cat "${tmpfile}" - rm "${tmpfile}" + cat "${tmpclean}" return 0 fi @@ -254,12 +269,11 @@ clean() # stdin if [ -f "${GIT_WAD_OBJDIR}/${digest}" ]; then log "git-wad:filter-clean: cache already exists %s\n"\ "${GIT_WAD_OBJDIR}/${digest}" - rm "${tmpfile}" # Store input stream to a file whose name is the stream digest else - chmod 444 "${tmpfile}" - mv "${tmpfile}" "${GIT_WAD_OBJDIR}/${digest}" + chmod 444 "${tmpclean}" + mv "${tmpclean}" "${GIT_WAD_OBJDIR}/${digest}" log "git-wad:filter-clean: caching to %s\n" \ "${GIT_WAD_OBJDIR}/${digest}" fi @@ -316,14 +330,19 @@ push() # [-1a] { if [ -z "${GIT_WAD_REMOTE_PUSH}" ]; then >&2 printf "Remote undefined, i.e. variable GIT_WAD_REMOTE_PUSH is empty\n" - return 1 + die 1 fi >&2 printf "Pushing to %s\n" "${GIT_WAD_REMOTE_PUSH}" remote="${GIT_WAD_REMOTE_PUSH#file://}" - objects_to_push "$@" | rsync -av --progress --ignore-existing \ - --files-from=- "${GIT_WAD_OBJDIR}" "${remote}" + + objects_to_push="${git_wad_tmpdir}/objects_to_push" + # shellcheck disable=SC2310 + objects_to_push "$@" > "${objects_to_push}" || die "$?" + + rsync -av --progress --ignore-existing \ + --files-from=- "${GIT_WAD_OBJDIR}" "${remote}" < "${objects_to_push}" } # Download WAD objects from remote server @@ -331,23 +350,28 @@ fetch() # [-1a] { if [ -z "${GIT_WAD_REMOTE_FETCH}" ]; then >&2 printf "Remote undefined, i.e. variable GIT_WAD_REMOTE_FETCH is empty\n" - return 1 + die 1 fi + objects_to_fetch="${git_wad_tmpdir}/objects_to_fetch}" + objects_to_fetch "$@" > "${objects_to_fetch}" + >&2 printf "Fetching from %s\n" "${GIT_WAD_REMOTE_FETCH}" # Use curl to download WAD objects via http[s] or gopher[s] protocol if echo "${GIT_WAD_REMOTE_FETCH}" | grep -q \ -e "^http[s]\{0,1\}://" \ -e "^gopher[s]\{0,1\}://"; then - objects_to_fetch "$@" | xargs -I {} curl --remove-on-error -w "{}\n" \ - -o "${GIT_WAD_OBJDIR}/{}" "${GIT_WAD_REMOTE_FETCH}/{}" + xargs -I {} curl --remove-on-error -w "{}\n" \ + -o "${GIT_WAD_OBJDIR}/{}" "${GIT_WAD_REMOTE_FETCH}/{}" \ + < "${objects_to_fetch}" # By default transfert WAD objects by rsync else remote="${GIT_WAD_REMOTE_FETCH#file://}" - objects_to_fetch "$@" | rsync -av --progress --ignore-existing \ - --files-from=- "${remote}" "${GIT_WAD_OBJDIR}" + rsync -av --progress --ignore-existing \ + --files-from=- "${remote}" "${GIT_WAD_OBJDIR}" \ + < "${objects_to_fetch}" fi } @@ -358,7 +382,7 @@ checkout() if ! is_init; then >&2 printf "\e[0;31mgit-wad is not initialized\e[0m\n" >&2 printf " (use \"git wad init\" to enable WAD management)\n" - return 1 + die 1 fi git ls-files "${working_tree}" \ @@ -380,10 +404,12 @@ pull() # [-1a] # storage prune() # [-1a] { - unreferenced_objects "$@" \ - | xargs -I {} sh -c \ + unreferenced="${git_wad_tmpdir}/unreferenced_objects" + unreferenced_objects "$@" > "${unreferenced}" + + xargs -I {} sh -c \ ">&2 printf \"Removing %s\n\" \"${GIT_WAD_OBJDIR}/{}\"; \ - rm -f \"${GIT_WAD_OBJDIR}/{}\"" + rm -f \"${GIT_WAD_OBJDIR}/{}\"" < "${unreferenced}" } # Print WAD management status @@ -398,9 +424,9 @@ status() # [-1a] fi # Create 3 temp files to list resolved, unrestored and orphaned WADs - resolved="$(mktemp -p "${GIT_WAD_OBJDIR}")" - unrestored="$(mktemp -p "${GIT_WAD_OBJDIR}")" - orphaned="$(mktemp -p "${GIT_WAD_OBJDIR}")" + resolved="${git_wad_tmpdir}/resolved" + unrestored="${git_wad_tmpdir}/unrestored" + orphaned="${git_wad_tmpdir}/orphaned" wad_paths | while read -r wad; do # Read the WAD bytes corresponding to its header before it is restored header_size="$(sizeof_header)" @@ -460,31 +486,32 @@ status() # [-1a] fi # Print number of WADs not referenced in current work tree - n="$(unreferenced_objects "$@" | wc -l)" + unreferenced="${git_wad_tmpdir}/unreferenced" + unreferenced_objects "$@" > "${unreferenced}" + n="$(wc -l < "${unreferenced}")" if [ "${n}" -gt 0 ]; then printf "There are %d WADs not use in the current working tree\n" "${n}" printf " (use \"git wad prune\" to remove them)\n" fi - - rm -f "${resolved}" "${orphaned}" "${unrestored}" } ######################################################################## # The command ######################################################################## -sub_cmd="$1" - mkdir -p "${GIT_WAD_OBJDIR}" +sub_cmd="$1" case "${sub_cmd}" in - "checkout") shift 1; checkout "$@" ;; + "checkout") shift 1; checkout ;; "fetch") shift 1; fetch "$@" ;; "filter-clean") shift 1; clean "$@" ;; "filter-smudge") shift 1; smudge "$@" ;; - "init") shift 1; init "$@" ;; + "init") shift 1; init ;; "prune") shift 1; prune "$@" ;; "push") shift 1; push "$@" ;; "pull") shift 1; pull "$@" ;; "status") shift 1; status "$@" ;; - *) synopsis; exit 1 ;; + *) synopsis; die 1 ;; esac + +die 0