Versions-Arithmetik kennt eine Decke
parent
d4a9166c7a
commit
cfae6e990a
|
|
@ -51,7 +51,12 @@ release_version_gt() {
|
|||
[[ "$1" != "$2" ]] && [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1)" == "$1" ]]
|
||||
}
|
||||
|
||||
# release_newest_tag — the highest v* tag by version order, or nothing.
|
||||
# release_newest_tag [CEILING] — the highest v* tag by version order, or nothing.
|
||||
#
|
||||
# Mit CEILING: der hoechste Tag, der NICHT darueber liegt. Das ist die ganze
|
||||
# Mechanik des Festnagelns — der Agent fragt nach dem neuesten Tag, den er
|
||||
# nehmen DARF, und `behind`, der Knopf und das Wartungsfenster fallen alle aus
|
||||
# dieser einen Antwort.
|
||||
#
|
||||
# Deliberately not `git tag … | head -1`. head exits after the first line, git
|
||||
# gets SIGPIPE, and under `set -o pipefail` that non-zero status leaves the
|
||||
|
|
@ -59,9 +64,13 @@ release_version_gt() {
|
|||
# and the same trap install-agent.sh already carries a warning about. A read
|
||||
# loop over a process substitution is not a pipeline and cannot do it.
|
||||
release_newest_tag() {
|
||||
local tag
|
||||
local ceiling="${1-}" tag
|
||||
|
||||
while read -r tag; do
|
||||
[[ -n "$tag" ]] || continue
|
||||
if [[ -n "$ceiling" ]] && release_version_gt "${tag#v}" "${ceiling#v}"; then
|
||||
continue
|
||||
fi
|
||||
printf '%s' "$tag"
|
||||
return 0
|
||||
done < <(git tag --list 'v*' --sort=-v:refname 2>/dev/null)
|
||||
|
|
@ -69,31 +78,70 @@ release_newest_tag() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# release_tags_ahead VERSION — how many v* tags are higher than VERSION.
|
||||
# release_tags_ahead VERSION [CEILING] — how many v* tags are higher than
|
||||
# VERSION, counting only up to CEILING.
|
||||
#
|
||||
# Lives here rather than inline in the agent because it has to be testable. The
|
||||
# inline version killed the update agent on every tick: the loop body was
|
||||
# `release_version_gt … && echo`, so the LAST iteration — almost always a tag
|
||||
# that is not newer — left the loop with a non-zero status, `pipefail` carried
|
||||
# it out of the pipeline, the command substitution inherited it, and `set -e`
|
||||
# ended the agent before it could write a status. The console then showed a
|
||||
# check that never finished and a last-checked time frozen at the deployment.
|
||||
# ended the agent before it could write a status.
|
||||
#
|
||||
# `if` rather than `&&` is the whole fix: an if whose condition is false still
|
||||
# returns 0. Anything added here must keep that property.
|
||||
# returns 0. Anything added here must keep that property — auch die
|
||||
# Decken-Pruefung unten ist deshalb ein verschachteltes `if` und kein `&&`.
|
||||
release_tags_ahead() {
|
||||
local current="$1" tag count=0
|
||||
local current="$1" ceiling="${2-}" tag count=0
|
||||
|
||||
while read -r tag; do
|
||||
[[ -n "$tag" ]] || continue
|
||||
if release_version_gt "${tag#v}" "$current"; then
|
||||
count=$((count + 1))
|
||||
if [[ -z "$ceiling" ]] || ! release_version_gt "${tag#v}" "${ceiling#v}"; then
|
||||
count=$((count + 1))
|
||||
fi
|
||||
fi
|
||||
done < <(git tag --list 'v*' --sort=-v:refname 2>/dev/null)
|
||||
|
||||
printf '%s' "$count"
|
||||
}
|
||||
|
||||
# release_tag_exists TAG — true when TAG is a real tag in this checkout.
|
||||
#
|
||||
# Form und Existenz sind zwei Fragen. Eine Decke, die bloss AUSSIEHT wie eine
|
||||
# Version, ist keine: der Release-Prozess loescht einen falschen Tag und
|
||||
# ueberspringt die Nummer, eine Decke kann also ohne Zutun dessen, der sie
|
||||
# gesetzt hat, ins Leere zeigen.
|
||||
release_tag_exists() {
|
||||
[[ -n "${1-}" ]] || return 1
|
||||
git rev-parse -q --verify "refs/tags/${1}^{commit}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# release_tags_from VERSION [LIMIT] — die Tags, auf die festgenagelt werden
|
||||
# darf: VERSION selbst und alles darueber, neueste zuerst, einer je Zeile.
|
||||
#
|
||||
# VERSION ist EINGESCHLOSSEN, und das ist der haeufigste Fall: „hier
|
||||
# einfrieren, nichts Neues nehmen" ist das, was neun von zehn Servern wollen,
|
||||
# waehrend der zehnte die neue Fassung bekommt.
|
||||
#
|
||||
# LIMIT, weil die Statusdatei sonst mit der Tag-Historie mitwaechst.
|
||||
release_tags_from() {
|
||||
local current="$1" limit="${2:-20}" tag count=0 out=''
|
||||
|
||||
while read -r tag; do
|
||||
[[ -n "$tag" ]] || continue
|
||||
if [[ "${tag#v}" == "$current" ]] || release_version_gt "${tag#v}" "$current"; then
|
||||
out+="${tag}"$'\n'
|
||||
count=$((count + 1))
|
||||
if (( count >= limit )); then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <(git tag --list 'v*' --sort=-v:refname 2>/dev/null)
|
||||
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
release_remember() { # release_remember MODE SOURCE
|
||||
mkdir -p "$(dirname "$MODE_FILE")"
|
||||
printf '%s' "$1" > "$MODE_FILE"
|
||||
|
|
|
|||
|
|
@ -143,3 +143,72 @@ it('answers the pairwise comparison the same way, and exits cleanly either way',
|
|||
->and(trim($process->getOutput()))->toBe($expected, "{$a} > {$b}");
|
||||
}
|
||||
});
|
||||
|
||||
it('stops at the ceiling instead of taking the newest tag', function () {
|
||||
// Der Kern des Festnagelns: v1.8.1 existiert, darf aber nicht genommen
|
||||
// werden, weil die Decke auf v1.8.0 steht.
|
||||
$p = runRelease('release_newest_tag v1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe('v1.8.0');
|
||||
});
|
||||
|
||||
it('takes the newest tag when no ceiling is given', function () {
|
||||
// Das bisherige Verhalten muss unveraendert bleiben — ohne Decke aendert
|
||||
// sich nichts an einem Server, der nie festgenagelt wurde.
|
||||
$p = runRelease('release_newest_tag', ['v1.7.3', 'v1.8.0', 'v1.8.1']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe('v1.8.1');
|
||||
});
|
||||
|
||||
it('answers nothing when every tag is above the ceiling', function () {
|
||||
$p = runRelease('release_newest_tag v1.0.0', ['v1.7.3', 'v1.8.1']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe('');
|
||||
});
|
||||
|
||||
it('counts only the tags up to the ceiling as ahead', function () {
|
||||
// Ausgeliefert 1.7.3, Decke v1.8.0, vorhanden bis v1.8.2: genau EINE
|
||||
// Aktualisierung ist verfuegbar, nicht drei.
|
||||
$p = runRelease('release_tags_ahead 1.7.3 v1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1', 'v1.8.2']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe('1');
|
||||
});
|
||||
|
||||
it('counts nothing ahead when the ceiling equals what is deployed', function () {
|
||||
// Der haeufigste Fall: "hier einfrieren". Muss sich wie "aktuell"
|
||||
// rechnen, sonst bietet die Konsole etwas an, das nie installiert wird.
|
||||
$p = runRelease('release_tags_ahead 1.8.0 v1.8.0', ['v1.8.0', 'v1.8.1', 'v1.8.2']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe('0');
|
||||
});
|
||||
|
||||
it('knows whether a tag really exists', function () {
|
||||
// Form und Existenz sind zwei Fragen. Der Release-Prozess loescht einen
|
||||
// falschen Tag und ueberspringt die Nummer — eine Decke kann also ohne
|
||||
// Zutun ihres Setzers ins Leere zeigen.
|
||||
$p = runRelease('release_tag_exists v1.8.0 && echo ja; release_tag_exists v9.9.9 || echo nein', ['v1.8.0']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe("ja\nnein");
|
||||
});
|
||||
|
||||
it('offers the deployed version itself as a ceiling, newest first', function () {
|
||||
// Die ausgelieferte Version steht MIT in der Liste: "einfrieren, nichts
|
||||
// Neues nehmen" ist das, was neun von zehn Servern brauchen.
|
||||
$p = runRelease('release_tags_from 1.8.0', ['v1.7.3', 'v1.8.0', 'v1.8.1']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe("v1.8.1\nv1.8.0");
|
||||
});
|
||||
|
||||
it('caps the offered list', function () {
|
||||
$p = runRelease('release_tags_from 1.0.0 2', ['v1.0.0', 'v1.1.0', 'v1.2.0', 'v1.3.0']);
|
||||
|
||||
expect($p->getExitCode())->toBe(0)
|
||||
->and(trim($p->getOutput()))->toBe("v1.3.0\nv1.2.0");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue