Skip to content

pacman-helper: inline a vercmp Bash function into the patched repo-add#714

Open
dscho wants to merge 1 commit into
git-for-windows:mainfrom
dscho:dscho/pacman-helper-vercmp
Open

pacman-helper: inline a vercmp Bash function into the patched repo-add#714
dscho wants to merge 1 commit into
git-for-windows:mainfrom
dscho:dscho/pacman-helper-vercmp

Conversation

@dscho

@dscho dscho commented Jun 12, 2026

Copy link
Copy Markdown
Member

When pacman-helper.sh quick_add is run from the pacman-packages action of git-for-windows/git-for-windows-automation, the SDK that the action sets up is the build-installers flavor, which intentionally ships only a small subset of /usr/bin. vercmp.exe is not in that subset, so every repo-add invocation prints two lines of noise of the form:

D:/a/_temp/build-extra/repo-add: line 254: vercmp: command not found
D:/a/_temp/build-extra/repo-add: line 254: ((: > 0 : arithmetic syntax error: operand expected (error token is "> 0 ")

(see e.g. run 27414389819, which produced 64 such pairs before the unrelated db3 leak bit it; that one is already fixed on main as 887f246).

The errors themselves are benign because we invoke repo-add without --prevent-downgrade, so the suppressed "newer version already present" warning has no functional consequence, but I'd rather not pollute release logs with them.

Adding vercmp.exe (or any of the other plausible interpreters that came up while exploring this: awk.exe, perl, expr.exe) to build-installers would defeat the point of that minimal SDK. Instead, this extends the existing sed-based patching of /usr/bin/repo-add to also inject a small Bash function called vercmp. The patched copy is what pacman-helper.sh invokes, and repo-add is itself a Bash script, so the injected function is in scope at the call site with no PATH manipulation, no export -f gymnastics across process boundaries, and pacman-helper.sh itself stays POSIX sh.

The algorithm is essentially the same shape as the prior-art version_compare in git-extra/git-update-git-for-windows, with one fix (strict equality returns 0, which vercmp's contract requires but the prior art got wrong). It splits each version string on runs of non-alphanumeric characters and walks the resulting arrays segment by segment: two numeric segments compare numerically, a numeric vs. alphanumeric segment lets the numeric side win (matching pacman's behavior of treating pre-release suffixes as older), and otherwise it falls back to lexical comparison. When one string runs out of segments first, the longer string wins unless its extra segment starts with a letter, in which case it is treated as a pre-release.

All nine test cases from git-update-git-for-windows --test-version-compare pass, plus the strict-equality case and the exact strings from the failing run:

OK   vercmp 2.32.0.windows.1            2.32.1.windows.1            -> -1
OK   vercmp 2.32.1.windows.1            2.32.0.windows.1            -> 1
OK   vercmp 2.32.1.vfs.0.0              2.32.0.windows.1            -> 1
OK   vercmp 2.32.1.vfs.0.0              2.32.0.vfs.0.0              -> 1
OK   vercmp 2.32.0.vfs.0.1              2.32.0.vfs.0.2              -> -1
OK   vercmp 2.32.0-rc0.windows.1        2.31.1.windows.1            -> 1
OK   vercmp 2.32.0-rc2.windows.1        2.32.0.windows.1            -> -1
OK   vercmp 2.34.0.rc1.windows.1        2.33.1.windows.1            -> 1
OK   vercmp 2.34.0.rc2.windows.1        2.34.0.windows.1            -> -1
OK   vercmp 2.55.0.1-1                  2.55.0.1-1                  -> 0
OK   vercmp 2.54.0.1-1                  2.55.0.rc0.windows.1-1      -> -1
OK   vercmp 2.55.0.rc0.windows.1-1      2.54.0.1-1                  -> 1
OK   vercmp 2.55.0.windows.1-1          2.55.0.windows.1-2          -> -1
OK   vercmp 2.55.0.windows.1-2          2.55.0.windows.1-1          -> 1
OK   vercmp 2.54                        2.54.1                      -> -1
OK   vercmp 2.55.0.rc0                  2.55.0                      -> -1

Known limitation

Within a mixed-alphanumeric segment such as rc10 vs. rc2 the lexical fallback gets the order wrong (would say rc10 < rc2), but Git for Windows has never published an RC past single digits, so a full rpm-style sub-tokenization stays out of scope.

Scope

repo-remove next door is patched the same way but does not call vercmp (it only takes package names, not versions), so its patching block is intentionally left alone.

When `pacman-helper.sh quick_add` is run from the `pacman-packages`
action of `git-for-windows/git-for-windows-automation`, the SDK that
the action sets up is the `build-installers` flavor, which
intentionally ships only a small subset of `/usr/bin`. `vercmp.exe`
is not in that subset, so every `repo-add` invocation prints two lines
of noise of the form:

    D:/a/_temp/build-extra/repo-add: line 254: vercmp: command not found
    D:/a/_temp/build-extra/repo-add: line 254: ((: > 0 : arithmetic syntax error: operand expected (error token is "> 0 ")

(see e.g.
https://github.com/git-for-windows/git-for-windows-automation/actions/runs/27414389819/job/81026080873,
which produced 64 such pairs before the unrelated `db3` leak bit it).
The errors themselves are benign because we invoke `repo-add` without
`--prevent-downgrade`, so the suppressed "newer version already
present" warning has no functional consequence, but I would rather
not pollute release logs with them.

Adding `vercmp.exe` (or any of the other plausible interpreters that
came up while exploring this -- `awk.exe`, `perl`, `expr.exe`) to
`build-installers` would defeat the point of that minimal SDK.
Instead, extend the existing sed-based patching of `/usr/bin/repo-add`
to also inject a small Bash function called `vercmp`. The patched
copy is what `pacman-helper.sh` invokes, and `repo-add` is itself a
Bash script, so the injected function is in scope at the call site
with no PATH manipulation, no `export -f` gymnastics across process
boundaries, and `pacman-helper.sh` itself stays POSIX `sh`.

The algorithm splits each version string on runs of non-alphanumeric
characters and walks the resulting arrays segment by segment: two
numeric segments compare numerically, a numeric vs. alphanumeric
segment lets the numeric side win (matching pacman's behavior of
treating pre-release suffixes as older), and otherwise it falls back
to lexical comparison. When one string runs out of segments first,
the longer string wins unless its extra segment starts with a letter,
in which case it is treated as a pre-release. This is essentially
the same shape as the prior-art `version_compare` in
`git-extra/git-update-git-for-windows`, and it gets the cases that
the `pacman-packages` action ever encounters right; in particular it
places `2.55.0.rc0.windows.1-1` below `2.55.0.1-1`, which matters
because the RC needs to be considered older than the release. All
nine test cases from `git-update-git-for-windows --test-version-compare`
pass, and I have also verified the failing-run strings and the
strict-equality case (which the prior-art function gets wrong but
which `vercmp`'s contract requires to be `0`).

Within a mixed-alphanumeric segment such as `rc10` vs. `rc2` the
lexical fallback gets the order wrong, but Git for Windows has never
published an RC past single digits, so a full rpm-style
sub-tokenization stays out of scope. `repo-remove` next door is
patched the same way but does not call `vercmp`, so its patching
block is intentionally left alone.

Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho dscho self-assigned this Jun 12, 2026
@dscho dscho marked this pull request as ready for review June 12, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant