fangorn/ex_git_objectstore
public
ref:main
ci passed
manual
Branch:
feat/cross-repo-grant-pin-bump
6b7f58a
Started: Aug 05, 2026 at 06:25 UTC
Completed: Aug 05, 2026 at 06:59 UTC
Duration: 33m 55s
Prepare
cached image
apt-get update && apt-get install -y --no-install-recommends git build-essential curl unzip ca-certificates
curl -fSL -o /tmp/elixir.zip "https://builds.hex.pm/builds/elixir/v1.20.0-otp-29.zip"
echo "eb04bbe0748cfc1414081a005c711a102d0b3f551ff96cc8cff81b0e7d468f19 /tmp/elixir.zip" | sha256sum -c -
unzip -q -o /tmp/elixir.zip -d /usr/local && rm /tmp/elixir.zip
mix local.hex --force && mix local.rebar --force
Pipeline validated
Jobs
compile · format
dialyzer · test
passed
dialyzer
exit: 0
17s
on carl
(linux/aarch64)
set -e
export MIX_HOME=/workspace/.mix
mkdir -p priv/plts
mix dialyzer
depends on
compile
passed
test
exit: 0
4m 27s
on carl
(linux/aarch64)
set -e
git config --global --add safe.directory /workspace
git config --global init.defaultBranch main
git config --global user.email "ci@anvil.test"
git config --global user.name "CI"
export MIX_HOME=/workspace/.mix
mix test --cover --export-coverage default
mix run --no-start -e '
tools_ebin = Path.wildcard("/usr/local/lib/erlang/lib/tools-*/ebin") |> List.first()
if tools_ebin, do: Code.append_path(tools_ebin)
:cover.start()
:cover.import(~c"cover/default.coverdata")
modules = :cover.imported_modules()
lcov = Enum.map_join(modules, "", fn mod ->
case :cover.analyse(mod, :calls, :line) do
{:ok, lines} ->
source = try do
mod.module_info(:compile)[:source] |> to_string()
|> String.replace(File.cwd!() <> "/", "")
rescue _ -> nil end
if source do
data = Enum.filter(lines, fn {{_, l}, _} -> l > 0 end)
da = Enum.map_join(data, "", fn {{_, l}, c} -> "DA:#{l},#{c}\n" end)
h = Enum.count(data, fn {_, c} -> c > 0 end)
"SF:#{source}\n#{da}LH:#{h}\nLF:#{length(data)}\nend_of_record\n"
else "" end
_ -> ""
end
end)
File.write!("cover/lcov.info", lcov)
IO.puts("LCOV written to cover/lcov.info")
'
depends on
compile
release · bump-anvil-pin
passed
release
exit: 0
2s
on carl
(linux/aarch64)
set -e
git config --global --add safe.directory /workspace
export MIX_HOME=/workspace/.mix
# Never release from a tag-triggered run. This step ENDS by pushing a
# tag, and that push starts another run — one whose HEAD is still the
# main commit, so the SHA gate below passes and it releases again, tags
# again, and so on. It is a self-feeding loop, and it ran: 2026.7.6,
# 2026.7.7 and 2026.7.8 are three releases of the identical commit
# a7e0745, stopped only by an unrelated 403 breaking the chain.
#
# The SHA gate cannot catch this on its own — a tag on the main commit
# is indistinguishable from main by SHA. Only the ref tells them apart.
case "${ANVIL_BRANCH:-}" in
refs/tags/*)
echo "Skipping release: tag-triggered run ($ANVIL_BRANCH)"
exit 0
;;
esac
# Only release from main branch
# CI checks out a detached HEAD so git branch name is always "HEAD".
# Compare the checked-out SHA against origin/main instead.
git fetch origin main 2>/dev/null || true
HEAD_SHA=$(git rev-parse HEAD)
MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
if [ "$HEAD_SHA" != "$MAIN_SHA" ]; then
echo "Skipping release: HEAD ($HEAD_SHA) != origin/main ($MAIN_SHA)"
exit 0
fi
# Install anvil CLI. /runner/download serves the unified
# anvil/anvil-runner binary (fangorn/anvil#49 merged the two).
curl -sL "https://anvil.fangorn.io/runner/download?os=$(uname -s)&arch=$(uname -m)" -o /usr/local/bin/anvil
chmod +x /usr/local/bin/anvil
# Credentials are auto-injected by the runner: ANVIL_TOKEN is a
# per-job API token generated at dispatch and auto-revoked when
# this job finishes, and ANVIL_SERVER_URL points at the server
# that dispatched us. Both are part of the job environment; we
# don't need to set up any secrets manually.
#
# The scopes granted to that token are controlled by org/repo
# CI-permission settings — for this repo's release step to work,
# an admin must grant `releases: write` via:
# PUT /api/v1/fangorn/ex_git_objectstore/ci/permissions
# See fangorn/anvil#46 for the full design.
/usr/local/bin/anvil auth status
# Compute CalVer version
VERSION=$(bash ci/release.sh)
echo "Releasing version: $VERSION"
# Idempotency: skip if this version tag already exists
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists, skipping release"
exit 0
fi
# Build with version
export VERSION
mix compile
# Generate changelog from commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD)
CHANGELOG=$(git log --oneline "$LAST_TAG"..HEAD)
# Generate docs
mix docs
tar czf ex_git_objectstore-${VERSION}-docs.tar.gz doc/
# Create Anvil release
anvil release create \
--tag "$VERSION" \
--title "ExGitObjectstore $VERSION" \
--body "$CHANGELOG"
# Tag and push. The runner clones via an auto-embedded clone
# token in origin, which only has read access — `git push origin`
# would 401. Push via an explicit URL that embeds the job API
# token (ANVIL_TOKEN) so auth goes through the repo's
# contents:write scope (see fangorn/anvil#46 / #59).
git tag "$VERSION"
git push \
"https://x-token:${ANVIL_TOKEN}@anvil.fangorn.io/fangorn/ex_git_objectstore.git" \
"$VERSION"
echo "Released $VERSION"
depends on
compile
format
dialyzer
test
passed
bump-anvil-pin
exit: 0
2s
on carl
(linux/aarch64)
set -e
git config --global --add safe.directory /workspace
export MIX_HOME=/workspace/.mix
# A tag-triggered run is not a merge to main, and its HEAD is the main
# commit, so the SHA gate below would wave it through and open a second
# identical pin-bump PR for every tag `release` pushes. Same reasoning
# as the guard in `release` — see the comment there.
case "${ANVIL_BRANCH:-}" in
refs/tags/*)
echo "Skipping pin bump: tag-triggered run ($ANVIL_BRANCH)"
exit 0
;;
esac
# Same gate as `release` above: CI checks out a detached HEAD, so the
# branch name is always "HEAD". Compare the checked-out SHA against
# origin/main instead. Deliberately not a `branch contains 'main'`
# match — that is a substring test and it matched
# feat/324-git-maintenance (fangorn/anvil#234).
git fetch origin main 2>/dev/null || true
HEAD_SHA=$(git rev-parse HEAD)
MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
if [ "$HEAD_SHA" != "$MAIN_SHA" ]; then
echo "Not a merge to main (HEAD $HEAD_SHA != origin/main $MAIN_SHA) — skipping pin bump"
exit 0
fi
# No secret needed: the top-level `permissions:` block asks for
# contents/pull_requests on fangorn/anvil, an admin there approves the
# request once, and the injected ANVIL_TOKEN carries those scopes for
# the life of this job (fangorn/anvil#245).
#
# A refused or unapproved request fails the job loudly — a silent skip
# would leave Anvil pinned to an old commit with nothing to show
# anything was missed. The script prints which approval is missing.
curl -sL "https://anvil.fangorn.io/runner/download?os=$(uname -s)&arch=$(uname -m)" -o /usr/local/bin/anvil
chmod +x /usr/local/bin/anvil
PIN_BUMP_SHA="$HEAD_SHA" mix run --no-start -r ci/pin_bump.exs \
-e 'ExGitObjectstore.CI.PinBump.main()'
depends on
compile
format
dialyzer
test
Artifacts
lcov.info
text/plain
44.6 KB
Download