Something went wrong. Try again.
A local Git merge queue with content-bound gate verdicts
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194#!/bin/bash# Oracle: the system git. Builds a fixture under a fresh directory ROOT and# records, for each case, what git rev-parse --path-format=absolute prints# from a directory, with GIT_DIR set or not, for --git-common-dir,# --show-toplevel and --is-bare-repository: the repository mq resolves there# (doc/commands.md, mq -C PATH), the work tree it was found from, and whether# it has one; and the main worktree git worktree list names first, the# checkout mq opens the queue at.## The fixture, which test.ml builds again with nox-git-eio:# main/ a repository on branch main, one commit of README and lib/ttl.ml# main/lib/# wt/ a linked worktree of main, on branch topic# wt/lib/# other/ a second repository, with no commit# outside/ a directory in no repository# link a symbolic link to main# bare.git/ a bare repository, with no commit# GIT_CEILING_DIRECTORIES is ROOT's parent, so no repository above ROOT is# found, wherever the temporary directory lies.## discover.csv has one row per case: its name; its directory, relative to# ROOT, or @F for the Git directory the gitfile ROOT/F names; the GIT_DIR it# sets; the common directory git printed, relative to# ROOT, or nothing when git refused; the top of the work tree, relative to# ROOT, or nothing outside a work tree; whether the repository is bare,# or nothing when git refused; and its main worktree, relative to ROOT, or# nothing when git refused or the repository is bare. A GIT_DIR is empty for none, @F for the# absolute Git directory that the gitfile ROOT/F names (as Git exports it to a# hook run in that worktree), and otherwise a path set as written, relative to# the case's directory. Output goes to $1 (default ../traces).set -euo pipefailcommand -v git >/dev/null || { echo "git not on PATH" >&2; exit 1; }out="$(cd "${1:-$(dirname "$0")/../traces}" && pwd)"work="$(mktemp -d "${TMPDIR:-/tmp}/mq-discover.XXXXXX")"trap 'rm -rf "$work"' EXITroot="$(cd "$work" && pwd -P)"
unset $(env | awk -F= '/^GIT_[A-Za-z0-9_]*=/ { print $1 }')export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1export GIT_CEILING_DIRECTORIES="$(dirname "$root")"export GIT_AUTHOR_NAME=Alice GIT_AUTHOR_EMAIL=alice@example.orgexport GIT_COMMITTER_NAME=Alice GIT_COMMITTER_EMAIL=alice@example.orgexport GIT_AUTHOR_DATE=2026-01-01T12:00:00+00:00export GIT_COMMITTER_DATE=2026-01-01T12:00:00+00:00
git init -q --template= -b main "$root/main"mkdir "$root/main/lib"printf 'ttl: the time to live of a DNS record\n' > "$root/main/README"printf 'let of_string = int_of_string\n' > "$root/main/lib/ttl.ml"git -C "$root/main" add README lib/ttl.mlgit -C "$root/main" commit -q -m "ttl: read a TTL"git -C "$root/main" worktree add -q -b topic "$root/wt"git init -q --template= -b main "$root/other"mkdir "$root/outside"ln -s main "$root/link"git init -q --template= --bare -b main "$root/bare.git"
csv="$out/discover.csv"echo "name,dir,git_dir,common_dir,top,bare,checkout" > "$csv"
# gitfile F: the Git directory the gitfile ROOT/F names.gitfile() { sed -n 's/^gitdir: //p' "$root/$1"; }
# ask NAME DIR VALUE ARG: what git rev-parse --path-format=absolute ARG prints# from the directory DIR, with GIT_DIR=VALUE if VALUE is not empty, relative# to ROOT when it is a path; nothing when git refuses.ask() { local name=$1 dir=$2 value=$3 arg=$4 answer rc if [ -n "$value" ]; then answer=$(GIT_DIR=$value git -C "$dir" rev-parse \ --path-format=absolute "$arg" 2>/dev/null) && rc=0 || rc=$? else answer=$(git -C "$dir" rev-parse \ --path-format=absolute "$arg" 2>/dev/null) && rc=0 || rc=$? fi case $rc in 0) case $answer in true | false) echo "$answer" ;; "$root"/*) echo "${answer#"$root"/}" ;; *) echo "$name: git answered $answer, outside the fixture" >&2; exit 1 ;; esac ;; 128) ;; *) echo "$name: git exited $rc" >&2; exit 1 ;; esac}
# main NAME DIR VALUE: the main worktree git worktree list names first from# the directory DIR, with GIT_DIR=VALUE if VALUE is not empty, relative to# ROOT; nothing when git refuses or the repository is bare.main() { local name=$1 dir=$2 value=$3 list if [ -n "$value" ]; then list=$(GIT_DIR=$value git -C "$dir" worktree list --porcelain 2>/dev/null) || return 0 else list=$(git -C "$dir" worktree list --porcelain 2>/dev/null) || return 0 fi case $(printf '%s\n' "$list" | sed -n 2p) in bare) return 0 ;; esac list=$(printf '%s\n' "$list" | sed -n '1s/^worktree //p') case $list in "$root"/*) echo "${list#"$root"/}" ;; *) echo "$name: git listed $list, outside the fixture" >&2; exit 1 ;; esac}
# row NAME DIR [GIT_DIR]: git's answers from ROOT/DIR, with GIT_DIR if given.row() { local name=$1 dir=$2 git_dir=${3-} at value common top bare checkout case $dir in @*) at=$(gitfile "${dir#@}") ;; *) at=$root/$dir ;; esac case $git_dir in "") value= ;; @*) value=$(gitfile "${git_dir#@}") ;; *) value=$git_dir ;; esac common=$(ask "$name" "$at" "$value" --git-common-dir) || exit 1 top=$(ask "$name" "$at" "$value" --show-toplevel) || exit 1 bare=$(ask "$name" "$at" "$value" --is-bare-repository) || exit 1 checkout= [ -n "$common" ] && { checkout=$(main "$name" "$at" "$value") || exit 1; } case "$name$dir$git_dir$common$top$bare$checkout" in *[,\"]*) echo "$name: a field holds a comma or a quote" >&2; exit 1 ;; esac echo "$name,$dir,$git_dir,$common,$top,$bare,$checkout" >> "$csv"}
row checkout mainrow checkout-subdir main/librow worktree-subdir wt/lib# GIT_DIR names the worktree's own Git directory, absolute, as Git exports it# to a hook run in that worktree; the common directory is read from its# commondir file.row worktree-subdir-git-dir wt/lib @wt/.git# GIT_DIR names the worktree's gitfile, relative: it is resolved against the# directory, as git -C does, and followed to the worktree's Git directory.row worktree-subdir-gitfile wt/lib ../.git# GIT_DIR names another repository: nothing is found by walking up.row worktree-subdir-git-dir-other wt/lib ../../other/.git# A hook Git runs from a linked worktree: GIT_DIR names the worktree's own Git# directory, absolute, and the directory is in no repository.row hook-elsewhere outside @wt/.git# Git resolves the directory physically, through the link: the answer names# main, never link.row link linkrow link-subdir link/librow outside outside# Inside a Git directory there is no work tree, and git still names the# repository: the main checkout's Git directory, a directory within it, and a# linked worktree's own Git directory.row git-dir main/.gitrow git-dir-subdir main/.git/refsrow worktree-git-dir @wt/.git# A bare repository has no work tree at all.row bare bare.git
# hooks.csv has one row per value of core.hooksPath in main: its name; the# value, empty for none, @P for the absolute path ROOT/P, and otherwise as# written; and what git rev-parse --path-format=absolute --git-path# hooks/pre-commit prints from the linked worktree wt, where a check runs,# and from main, relative to ROOT (doc/configuration.md, "A check named# after a Git hook is that hook").hooks_csv="$out/hooks.csv"echo "name,hooks_path,from_worktree,from_checkout" > "$hooks_csv"
# hook_row NAME VALUE: git's answers with core.hooksPath set to VALUE.hook_row() { local name=$1 value=$2 wt main case $value in "") git -C "$root/main" config --unset core.hooksPath || true ;; @*) git -C "$root/main" config core.hooksPath "$root/${value#@}" ;; *) git -C "$root/main" config core.hooksPath "$value" ;; esac wt=$(git -C "$root/wt" rev-parse --path-format=absolute \ --git-path hooks/pre-commit) main=$(git -C "$root/main" rev-parse --path-format=absolute \ --git-path hooks/pre-commit) echo "$name,$value,${wt#"$root"/},${main#"$root"/}" >> "$hooks_csv"}
hook_row hooks-of-the-git-directory ""hook_row relative-in-the-work-tree .githookshook_row absolute-outside @hookshook_row absolute-in-the-git-directory @main/.git/customhook_row absolute-in-the-work-tree @main/.githooksgit -C "$root/main" config --unset core.hooksPath
git --version > "$out/git-version"