diff --git a/Makefile b/Makefile index 2e3a50e..31c8173 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,11 @@ # make deb : build a .deb in the parent directory (or # $POSTIT_OUT_DIR if set). Equivalent to # dpkg-buildpackage -us -uc -b. +# make test : run the non-regression tests under tests/. +# The fetch test pins the upstream Git mechanics +# that previously regressed on tag-vs-branch +# handling (1.0.1-rc02). Set SKIP_NETWORK_TESTS=1 +# to skip the GitHub-egress parts. # make clean : rm -rf build/ and the per-package staging tree # under debian/postit. # make source : fetch the upstream yavsc source tree under @@ -21,7 +26,7 @@ POSTIT_GIT_TAG ?= 1.0.0 POSTIT_RUNTIME ?= linux-x64 POSTIT_OUT_DIR ?= $(CURDIR)/.. -.PHONY: deb clean source +.PHONY: deb clean source test deb: @echo " POSTIT_GIT_TAG=$(POSTIT_GIT_TAG) POSTIT_RUNTIME=$(POSTIT_RUNTIME) POSTIT_GIT_URL=$(POSTIT_GIT_URL)" @@ -49,3 +54,6 @@ source: mkdir -p build git clone --depth=1 --branch $(POSTIT_GIT_TAG) \ $(POSTIT_GIT_URL) build/yavsc-src + +test: + @bash tests/test-fetch-upstream.sh diff --git a/debian/rules b/debian/rules index 4fe821a..425c4c7 100755 --- a/debian/rules +++ b/debian/rules @@ -55,15 +55,32 @@ override_dh_auto_build: sed 's/@VERSION@/$(POSTIT_GIT_TAG)/g' \ $(CURDIR)/debian/changelog.in > $(CURDIR)/debian/changelog; \ fi - # 1. Fetch the upstream source. + # 1. Fetch the upstream source at POSTIT_GIT_TAG. The ref can + # be either a branch name (e.g. `main`) or a tag (e.g. + # `1.0.1-rc02`); `git fetch origin ` handles both. The + # reference MUST exist on the configured POSTIT_GIT_URL + # remote (default: github.com/pazof/yavsc). If you are + # building from a tag that is only on your local clone + # (typical right after `git tag 1.0.1-rc02` and before + # `git push origin 1.0.1-rc02`), point POSTIT_GIT_URL at + # your local checkout: + # make POSTIT_GIT_URL=file:///home/paul/Workspace/yavsc \ + # POSTIT_GIT_TAG=1.0.1-rc02 mkdir -p $(POSTIT_BUILD_DIR) - if [ -d $(POSTIT_SRC_DIR) ]; then \ - git -C $(POSTIT_SRC_DIR) fetch --depth=1 origin $(POSTIT_GIT_TAG); \ - git -C $(POSTIT_SRC_DIR) reset --hard $(POSTIT_GIT_TAG); \ - else \ - git clone --depth=1 --branch $(POSTIT_GIT_TAG) \ - $(POSTIT_GIT_URL) $(POSTIT_SRC_DIR); \ + if [ ! -d $(POSTIT_SRC_DIR)/.git ]; then \ + git init -q $(POSTIT_SRC_DIR); \ + git -C $(POSTIT_SRC_DIR) remote add origin $(POSTIT_GIT_URL); \ fi + if ! git -C $(POSTIT_SRC_DIR) fetch --depth=1 origin $(POSTIT_GIT_TAG); then \ + echo "" >&2; \ + echo " ERROR: POSTIT_GIT_TAG='$(POSTIT_GIT_TAG)' is not reachable from POSTIT_GIT_URL='$(POSTIT_GIT_URL)'." >&2; \ + echo " Either push the ref to the remote first (e.g. \`git push origin $(POSTIT_GIT_TAG)\`)" >&2; \ + echo " or override POSTIT_GIT_URL with a local path that has the ref, e.g." >&2; \ + echo " make POSTIT_GIT_URL=file:///home/paul/Workspace/yavsc POSTIT_GIT_TAG=$(POSTIT_GIT_TAG)" >&2; \ + echo "" >&2; \ + exit 128; \ + fi + git -C $(POSTIT_SRC_DIR) checkout -q FETCH_HEAD # 2. Publish PostIt.Desktop for the host architecture. # --self-contained false : rely on the system .NET diff --git a/tests/test-fetch-upstream.sh b/tests/test-fetch-upstream.sh new file mode 100755 index 0000000..3786e8b --- /dev/null +++ b/tests/test-fetch-upstream.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# +# Non-regression test for the POSTIT_GIT_TAG handling in debian/rules. +# +# Background: a previous version of override_dh_auto_build used +# `git clone --branch ` to fetch upstream. That only works for +# branch names; feeding it an annotated tag (e.g. 1.0.1-rc02) failed +# with "La branche distante 1.0.1-rc02 n'a pas ete trouvee" and +# aborted the whole package build. +# +# This test pins the upstream-fetch logic against the *real* +# pazof/yavsc remote on GitHub: tags like 1.0.1-rc01 exist there, +# so we can verify both branches and tags resolve to the expected +# commit without spinning up a fake local server (the local Git +# transport handles shallow tag fetches differently from smart-http, +# so a local-only test would be misleading). +# +# Scope: deliberately does NOT run dpkg-buildpackage / dotnet +# publish / dh_install. Those stages are exercised manually per +# release; this test pins the Git mechanics which previously +# regressed silently until the next tag bump. + +set -euo pipefail + +TEST_NAME="$(basename "$0")" + +# Skip on environments without network access (offline CI runners, +# dev sandboxes without GitHub egress). The test is a non-regression +# guard, not a connectivity test; failing because GitHub is down +# would mask the signal we actually care about. +if [ "${SKIP_NETWORK_TESTS:-0}" = "1" ]; then + echo "$TEST_NAME: SKIP -- SKIP_NETWORK_TESTS=1" + exit 0 +fi + +WORK="$(mktemp -d -t postit-fetch-test.XXXXXX)" +trap 'rm -rf "$WORK"' EXIT + +# Real remote. The branch + tag values below MUST exist for the +# test to pass; if pazof retags the repo, bump them. Anchored on +# commits known to be immutable: 1.0.0 is the initial release, +# 1.0.1-rc01 is the rc Paul shipped after the OIDC fixes. +REMOTE_URL="https://github.com/pazof/yavsc.git" +TEST_BRANCH="main" +TEST_TAG="1.0.1-rc01" + +# Expected commits from `git ls-remote`. Computed at test time so we +# don't have to update this script every time the repo moves; we +# just assert "whatever HEAD I end up on must match what `git +# ls-remote` says the ref points at". +EXPECTED_BRANCH_SHA="$(git ls-remote "$REMOTE_URL" "refs/heads/$TEST_BRANCH" | awk '{print $1}')" +EXPECTED_TAG_SHA="$(git ls-remote "$REMOTE_URL" "refs/tags/$TEST_TAG" | awk '{print $1}')" + +if [ -z "$EXPECTED_BRANCH_SHA" ] || [ -z "$EXPECTED_TAG_SHA" ]; then + echo "$TEST_NAME: SKIP -- refs not present on remote (branch=$EXPECTED_BRANCH_SHA tag=$EXPECTED_TAG_SHA)" + exit 0 +fi + +# Helper copied verbatim from debian/rules. If debian/rules drifts, +# this copy must drift with it -- that is the point of pinning the +# logic in a test. +fetch_upstream() { + local url="$1" ref="$2" dest="$3" + mkdir -p "$(dirname "$dest")" + if [ ! -d "$dest/.git" ]; then + git init -q "$dest" + git -C "$dest" remote add origin "$url" + fi + git -C "$dest" fetch --depth=1 origin "$ref" + git -C "$dest" checkout -q FETCH_HEAD +} + +# --- Case 1: POSTIT_GIT_TAG points at an annotated tag. ---------- +# This is the regression case. Before the fix, this failed with +# "La branche distante n'a pas ete trouvee". +DEST="$WORK/yavsc-src-tag" +rm -rf "$DEST" +if ! fetch_upstream "$REMOTE_URL" "$TEST_TAG" "$DEST" 2> "$WORK/fetch-tag.err"; then + echo "$TEST_NAME: FAIL -- fetch_upstream errored on tag '$TEST_TAG'" + sed 's/^/ /' "$WORK/fetch-tag.err" + exit 1 +fi +GOT="$(git -C "$DEST" rev-parse HEAD)" +if [ "$GOT" != "$EXPECTED_TAG_SHA" ]; then + echo "$TEST_NAME: FAIL -- tag '$TEST_TAG' resolved to '$GOT', expected '$EXPECTED_TAG_SHA'" + exit 1 +fi +# A shallow clone cannot always determine "exact tag match" because +# the tag-object might not have been fetched. The commit-hash match +# above is the authoritative check; the describe call is a softer +# sanity signal that we still log but do not gate on. +DESCRIBED="$(git -C "$DEST" describe --tags --exact-match HEAD 2>/dev/null || echo '')" +echo "$TEST_NAME: tag '$TEST_TAG' -> $GOT (describe: $DESCRIBED)" + +# --- Case 2: POSTIT_GIT_TAG points at a branch. ------------------ +# Branches must keep working; this is the path the old code was +# originally written for and we don't want to break it. +DEST="$WORK/yavsc-src-branch" +rm -rf "$DEST" +if ! fetch_upstream "$REMOTE_URL" "$TEST_BRANCH" "$DEST" 2> "$WORK/fetch-branch.err"; then + echo "$TEST_NAME: FAIL -- fetch_upstream errored on branch '$TEST_BRANCH'" + sed 's/^/ /' "$WORK/fetch-branch.err" + exit 1 +fi +GOT="$(git -C "$DEST" rev-parse HEAD)" +if [ "$GOT" != "$EXPECTED_BRANCH_SHA" ]; then + echo "$TEST_NAME: FAIL -- branch '$TEST_BRANCH' resolved to '$GOT', expected '$EXPECTED_BRANCH_SHA'" + exit 1 +fi +echo "$TEST_NAME: branch '$TEST_BRANCH' -> $GOT" + +# --- Case 3: existing .git directory is reused (the second-build +# fast path). The build dir persists across builds, so the helper +# must not re-init when .git is already there. We populate the +# branch first, then refetch to the tag without nuking the +# existing origin remote. +DEST="$WORK/yavsc-src-reuse" +rm -rf "$DEST" +fetch_upstream "$REMOTE_URL" "$TEST_BRANCH" "$DEST" >/dev/null 2>&1 +ORIG_REMOTE="$(git -C "$DEST" remote get-url origin)" +if ! fetch_upstream "$REMOTE_URL" "$TEST_TAG" "$DEST" >/dev/null 2> "$WORK/fetch-reuse.err"; then + echo "$TEST_NAME: FAIL -- second fetch on existing .git errored" + sed 's/^/ /' "$WORK/fetch-reuse.err" + exit 1 +fi +if [ "$(git -C "$DEST" remote get-url origin)" != "$ORIG_REMOTE" ]; then + echo "$TEST_NAME: FAIL -- remote origin was clobbered on reuse" + exit 1 +fi +GOT="$(git -C "$DEST" rev-parse HEAD)" +if [ "$GOT" != "$EXPECTED_TAG_SHA" ]; then + echo "$TEST_NAME: FAIL -- reuse case tag fetch landed on '$GOT', expected '$EXPECTED_TAG_SHA'" + exit 1 +fi +echo "$TEST_NAME: reuse case tag '$TEST_TAG' -> $GOT (remote preserved)" + +# --- Case 4: POSTIT_GIT_TAG is a tag that exists only on a local +# repo, fetched via file://. This is the Paul workflow for staging +# an RC: `git tag 1.0.1-rc02` on ~/Workspace/yavsc, then +# `make POSTIT_GIT_URL=file:///home/paul/Workspace/yavsc +# POSTIT_GIT_TAG=1.0.1-rc02` to build the .deb before +# pushing the tag to github.com/pazof/yavsc. The fix must support +# this without requiring the tag to be on a public remote first. +LOCAL_REPO="$WORK/local-repo" +LOCAL_TAG="test-local-rc-$$" +# Use ^{commit} to dereference the tag-object to the underlying +# commit; that's what git checkout FETCH_HEAD lands on. +LOCAL_TAG_SHA="$(mktemp -u XXXXXXXXXXXX)" +git init -q "$LOCAL_REPO" +git -C "$LOCAL_REPO" -c user.email=test@test.local -c user.name=test \ + commit --allow-empty -q -m "local seed" +git -C "$LOCAL_REPO" tag -a "$LOCAL_TAG" -m "local-only tag" +LOCAL_TAG_SHA="$(git -C "$LOCAL_REPO" rev-parse "$LOCAL_TAG^{commit}")" + +DEST="$WORK/yavsc-src-local" +rm -rf "$DEST" +if ! fetch_upstream "file://$LOCAL_REPO" "$LOCAL_TAG" "$DEST" 2> "$WORK/fetch-local.err"; then + echo "$TEST_NAME: FAIL -- fetch_upstream errored on local-only tag '$LOCAL_TAG'" + sed 's/^/ /' "$WORK/fetch-local.err" + exit 1 +fi +GOT="$(git -C "$DEST" rev-parse HEAD)" +if [ "$GOT" != "$LOCAL_TAG_SHA" ]; then + echo "$TEST_NAME: FAIL -- local tag '$LOCAL_TAG' resolved to '$GOT', expected '$LOCAL_TAG_SHA'" + exit 1 +fi +echo "$TEST_NAME: local tag '$LOCAL_TAG' via file:// -> $GOT" + +echo "$TEST_NAME: PASS -- tag, branch, reuse, and local-only tag all resolve correctly"