test-hermetic.sh
#!/bin/bash
## Hermeticity: hostile repo-local git config and attributes, in the subject
## or the target repo, or hostile git env inherited from a caller,
## must not change what patchette writes or replays.
##
## Nulling global and system config doesn't reach a repo's own .git/config,
## its info/attributes, or a worktree .gitattributes; git reads all of those.
## Nor does it reach env a parent git hands down (hooks, aliases, `git -c`),
## which can inject config or redirect which repo is read and written.
## Each case plants one hostile setup into a copy of a repo, then checks:
## export bytes match a clean export, verify passes, and import reproduces exact tips.
set -euo pipefail
cd "$(dirname "$0")"
source ./lib.sh
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
make_corpus "$work/corpus"
source "$work/corpus/FACTS"
branches=(topic/fancy docs-polish odd-bytes)
want_tips=$(printf '%s\n' "$tip_topic_fancy" "$tip_docs_polish" "$tip_odd_bytes")
"$PATCHETTE" export "$work/corpus/dev" "$work/clean" "$base" "${branches[@]}" >/dev/null
printf 'odd/trailing.txt\n' >"$work/orderfile"
# Env-case fixtures: a tree holding a hostile .gitattributes (in the subject's
# object store), a graft that turns docs-polish's tip into a root,
# and a decoy repo that holds the base, so a redirected import could succeed there.
attrs_tree=$(printf '100644 blob %s\t.gitattributes\n' "$(printf '* -diff\n' | git -C "$work/corpus/dev" hash-object -w --stdin)" |
git -C "$work/corpus/dev" mktree)
printf '%s\n' "$tip_docs_polish" >"$work/grafts"
git clone -q --bare "$work/corpus/upstream" "$work/decoy.git"
# Each case is `|`-separated items:
# `key=value` sets repo-local config; `@line` appends to the repo's info/attributes;
# `%line` appends to an uncommitted worktree .gitattributes;
# `!line` commits a .gitattributes on a new branch and checks it out
# (export bytes must not vary with which branch happens to be checked out);
# `$VAR=value` sets env for the patchette invocations only.
cases=(
diff.context=1
diff.context=8
diff.interHunkContext=10
diff.algorithm=histogram
diff.algorithm=minimal
diff.indentHeuristic=false
diff.renames=copies
diff.noprefix=true
diff.mnemonicPrefix=true
'diff.srcPrefix=old/|diff.dstPrefix=new/'
diff.relative=true
diff.suppressBlankEmpty=true
"diff.orderFile=$work/orderfile"
diff.wsErrorHighlight=all
color.ui=always
color.diff=always
core.abbrev=12
core.quotePath=false
core.bigFileThreshold=1
core.autocrlf=true
core.autocrlf=input
core.eol=crlf
'core.autocrlf=true|core.safecrlf=true'
core.fileMode=false
core.symlinks=false
core.ignoreCase=true
core.precomposeUnicode=true
core.whitespace=trailing-space,space-before-tab,cr-at-eol,blank-at-eof
core.splitIndex=true
index.version=4
apply.whitespace=fix
apply.whitespace=error
apply.ignoreWhitespace=change
i18n.logOutputEncoding=ISO-8859-1
log.showSignature=true
format.pretty=oneline
'@* -diff'
'@* binary'
'@*.c diff=cpp'
'@* diff=hostile|diff.hostile.xfuncname=^/\*.*$'
'@* diff=hostile|diff.hostile.textconv=tr a-z A-Z'
'@* diff=hostile|diff.hostile.binary=true'
'@* filter=hostile|filter.hostile.clean=tr a-z A-Z|filter.hostile.smudge=cat'
'@* text eol=crlf'
'@* text=auto'
'@* whitespace=trailing-space,cr-at-eol'
'%*.c diff=cpp'
'%* -diff'
'!* -diff'
'!*.c diff=cpp'
'$GIT_CONFIG_COUNT=1|$GIT_CONFIG_KEY_0=diff.suppressBlankEmpty|$GIT_CONFIG_VALUE_0=true'
"\$GIT_CONFIG_PARAMETERS='core.quotepath'='false'"
"\$GIT_ATTR_SOURCE=$attrs_tree"
'$GIT_DIFF_OPTS=--unified=1'
"\$GIT_GRAFT_FILE=$work/grafts"
"\$GIT_DIR=$work/decoy.git"
"\$GIT_COMMON_DIR=$work/decoy.git"
"\$GIT_OBJECT_DIRECTORY=$work/decoy.git/objects"
"\$GIT_WORK_TREE=$work"
)
## Plant one hostile case into a repo.
##
## - @param $1 repo path (non-bare)
## - @param $2 case string, as described above the table
## - @state HOSTILE_ENV[] -- `VAR=value` words, for `env` to prefix patchette with
hostilize() {
local repo=$1 item
local -a items
HOSTILE_ENV=()
IFS='|' read -ra items <<<"$2"
for item in "${items[@]}"; do
case $item in
'$'*) HOSTILE_ENV+=("${item#\$}") ;;
@*) printf '%s\n' "${item#@}" >>"$repo/.git/info/attributes" ;;
%*) printf '%s\n' "${item#%}" >>"$repo/.gitattributes" ;;
'!'*)
printf '%s\n' "${item#!}" >>"$repo/.gitattributes"
git -C "$repo" switch -qc hostile-attrs
git -C "$repo" add .gitattributes
git -C "$repo" commit -qm "hostile attributes"
;;
*) git -C "$repo" config "${item%%=*}" "${item#*=}" ;;
esac
done
}
n=0
for c in "${cases[@]}"; do
n=$((n + 1))
d="$work/case$n"
mkdir "$d"
# Command failures here surface on stderr just above the FAIL they cause.
cp -a "$work/corpus/dev" "$d/subject"
hostilize "$d/subject" "$c"
env "${HOSTILE_ENV[@]}" "$PATCHETTE" export "$d/subject" "$d/out" "$base" "${branches[@]}" >/dev/null || true
EXPECT success "subject has $c: export bytes match a clean export" -- \
diff -r "$work/clean" "$d/out"
EXPECT success "subject has $c: verify passes" -- \
env "${HOSTILE_ENV[@]}" "$PATCHETTE" verify "$d/subject" "$work/clean"
git clone -q "$work/corpus/upstream" "$d/target"
hostilize "$d/target" "$c"
env "${HOSTILE_ENV[@]}" "$PATCHETTE" import "$d/target" "$work/clean" >/dev/null || true
EXPECT equal "target has $c: import reproduces exact tips" -- \
"$(git -C "$d/target" rev-parse "${branches[@]/#/refs/heads/}" 2>/dev/null || true)" "$want_tips"
done
exit "$RESULT"
tests/test-hermetic.sh · 5.3 KiB · efdd15f493