Files
carbon-lang/scripts/jj_push.sh
T
Chandler Carruth 652e0ce7d0 Fix the jj_push.sh script to work on macOS (#7820)
It had a few subtle GNU extensions in it that didn't work on macOS.
There are simple portable alternatives so it was easy to adapt.

Assisted-by: Antigravity with Gemini
2026-09-23 22:47:02 +00:00

113 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# Runs prek over the commits `jj git push` would send, and pushes only if they
# pass. Takes the same arguments as `jj git push`.
set -eu
JJ_PREK="$(dirname "${BASH_SOURCE[0]}")/jj_prek.sh"
# Succeeds if the working-copy commit is in the given revset. This snapshots the
# working copy, so it sees anything the hooks rewrote.
working_copy_is() {
[[ -n "$(jj log --no-graph -r "@ & ($1)" --template 'commit_id')" ]]
}
# Succeeds if the working copy is an empty, undescribed child of the target,
# which hooks can run in and write their fixes into.
working_copy_sits_on() {
working_copy_is "empty() & description(exact:\"\") & children($1)"
}
# Returns the working copy to where it started. jj discards an empty,
# undescribed commit when the working copy moves off it, so the original may be
# gone. Build a new one on the same parents in that case.
restore_working_copy() {
if [[ -n "$(jj log --no-graph --ignore-working-copy \
-r "present($ORIG_CHANGE)" --template 'commit_id')" ]]; then
jj edit --quiet "$ORIG_CHANGE"
else
jj new --quiet $ORIG_PARENTS
fi
}
# `--help` describes `jj git push`, and has nothing to check.
for arg in "$@"; do
case "$arg" in
-h | --help)
exec jj git push "$@"
;;
esac
done
# Ask jj what the push would do. This checks the arguments and lists the commits
# being sent, without contacting the remote. A `--dry-run` already in `$@` is
# harmless here, and still suppresses the push at the end.
if ! PLAN="$(jj git push --dry-run "$@" 2>&1)"; then
echo "$PLAN" >&2
exit 1
fi
REMOTE="$(sed -n 's/^Changes to push to \(.*\):$/\1/p' <<<"$PLAN")"
# Each updated bookmark or tag reports the commit it moves to. Deletions have no
# such commit, and send nothing to check.
TARGETS="$(sed -E -n 's/^ (bookmark|tag): .* to ([0-9a-f]{8,})\]$/\2/p' <<<"$PLAN" |
paste -sd '|' -)"
# Nothing to check, so just push.
if [[ -z "$REMOTE" || -z "$TARGETS" ]]; then
exec jj git push "$@"
fi
# Only the heads need checking; a head's range covers everything below it.
HEADS="$(jj log --no-graph --ignore-working-copy -r "heads($TARGETS)" \
--template 'commit_id ++ "\n"')"
ORIG_CHANGE="$(jj log --no-graph -r @ --template 'change_id')"
ORIG_PARENTS="$(jj log --no-graph -r 'parents(@)' --template 'commit_id ++ " "')"
for target in $HEADS; do
# Check with the working copy on top of the target.
made_scratch=0
if ! working_copy_sits_on "$target"; then
jj new --quiet "$target"
made_scratch=1
fi
# Check from the newest ancestor already on the remote. When there is none,
# there is no range to diff, so check every file.
base="$(jj log --no-graph --ignore-working-copy \
-r "heads(::$target & ::remote_bookmarks(remote=exact:$REMOTE))" \
--template 'commit_id ++ "\n"' | head -n 1)"
if [[ -n "$base" ]]; then
check=(--from-ref "$base" --to-ref "$target")
else
check=(--all-files)
fi
result=0
"$JJ_PREK" "${check[@]}" || result=$?
# Discard the scratch commit unless the hooks wrote something into it.
if ((made_scratch)) && working_copy_is 'empty()'; then
restore_working_copy
fi
if ((result)); then
echo >&2
if ! working_copy_is 'empty()'; then
change="$(jj log --no-graph -r @ --template 'change_id.shortest()')"
echo "Hooks changed files. They are in $change." >&2
fi
echo "Error: checks failed, nothing pushed." >&2
exit 1
fi
done
exec jj git push "$@"