The GitLab recipe now runs on a CI runner, and the merge request carries only translations - #18
Merged
pavel-te merged 3 commits intoAug 27, 2026
Conversation
The GitLab recipe this CLI printed could not translate anything. A CI runner checks out a detached HEAD. There `git branch --show-current` SUCCEEDS and prints an empty string, so the `||` fallbacks in get_current_branch were never reached, and the empty file tag was rejected by validate_args before a single request to the API. Every recipe that did not pass --file-tag-name by hand produced a red job, uploaded nothing and opened no merge request. Reproduced against a mock API, on a detached HEAD, with the recipe's exact command: exit 1 and nothing on disk, versus exit 0 and the translations written once a tag is supplied. An empty answer is now treated as no answer: the runner's own branch variable is consulted (GitLab, GitHub, Bitbucket, Jenkins, CircleCI), and only then does it fall back to "main". That fixes the printed recipe, the standalone snippet, and every other CI whose checkout is detached. The merge request also carried more than translations. The recipe curled the CLI into the project root and then ran `git add -A`, so every merge request contained ptc-cli.sh itself, plus whatever an earlier step in the caller's job had left in the working directory. And because that download was always a new file, the `git diff --cached --quiet` guard never short-circuited: each run force-updated the merge request even when no translation had changed. The CLI is now downloaded outside the checkout, and a new --written-manifest FILE records what the run wrote - NUL-separated, repository-root-relative, for `git add --pathspec-from-file=FILE --pathspec-file-nul`. git reads it directly, so the recipe needs no arrays or word splitting; the recipe's own shell is busybox sh, since alpine has no bash until before_script installs it. Paths holding a glob metacharacter are written as :(literal), because `git add --` stops option parsing but not globbing, and a path outside the repository root is not recorded at all - a pathspec matching nothing is fatal and would discard every other translation staged in the same call. The unpack filter dropped documented file types. `.php` is documented as an additional_translation_files companion and `.properties` as a source pattern; neither was in the find filter, so they never reached disk. And `-o` binds looser than the implicit `-a`, so `-type f` applied only to the first `-name`: a DIRECTORY named e.g. "x.po" matched and was moved wholesale. [skip ci] left the commit message. GitLab honours it by creating no pipeline at all, which silenced the translation merge request's own pipeline - leaving the translations untested and, with "Pipelines must succeed" enabled, unmergeable - and a squash carried it into the default branch, silencing the whole project. The loop guard now lives in rules:, which GitLab evaluates without suppressing anything. Tests: tests/test-ci-recipe.sh runs the printed recipe end to end against a mock API on localhost - no docker - with only curl and git push shimmed, and asserts what the merge request would contain. tests/test-git-context.sh now pins the detached-HEAD behaviour it previously documented as a known defect, and tests/test-init.sh checks that the README's copy of the recipe is the one the CLI prints.
On a hosted macOS runner the mock did not come up inside the old ~4s window, so the suite printed "skipped" and the job stayed green - the recipe went uncovered on that platform and nothing said so. The wait is now ~12s, a failure prints the mock's own output, and killing the background process no longer leaves a "Terminated" line that reads like a failure.
Binding 0.0.0.0 is refused by the firewall on a hosted macOS runner: the mock process started, never listened, and the suite skipped itself while the job stayed green. Everything that talks to this mock is on the same host - under act the job container shares the VM's network namespace - so loopback is enough. PTC_MOCK_BIND overrides it.
pavel-te
deleted the
fix/gitlab-recipe-runs-and-commits-only-translations
branch
August 27, 2026 17:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The recipe could not translate anything
A CI runner checks out a detached HEAD. There
git branch --show-currentsucceeds and prints an empty string, so the||fallbacks inget_current_branchwere never reached and the empty file tag was rejected byvalidate_args— before a single request to the API.Reproduced against a mock API, on a detached HEAD, with the recipe's exact command:
So every push to the default branch gave a red job, uploaded nothing and opened no merge request. Four of the five copies of this recipe carried the defect; only the one the product prints passed
--file-tag-nameand worked.An empty answer is now treated as no answer: the runner's own branch variable is consulted (GitLab, GitHub, Bitbucket, Jenkins, CircleCI), then
main. That fixes the printed recipe, the standalone snippet, and every other CI whose checkout is detached.The merge request carried more than translations
The recipe curled the CLI into the project root and then ran
git add -A:And because that download is always a new file,
git diff --cached --quietnever short-circuited — every run force-updated the merge request even when no translation had changed.The CLI now downloads outside the checkout, and
--written-manifest FILErecords what the run wrote: NUL-separated, repository-root-relative, forgit add --pathspec-from-file=FILE --pathspec-file-nul. git reads it directly, so the recipe needs no arrays or word splitting — its shell is busyboxsh, since alpine has no bash untilbefore_scriptinstalls it.Verified in
alpine:3.22(git 2.49.1): a path with a glob metacharacter stages the wrong file unless written as:(literal), and an ignored path makes git exit 1 while still staging the rest — hence|| true, without which GitLab aborts the job.The unpack filter dropped documented types
.phpis documented as anadditional_translation_filescompanion,.propertiesas a source pattern — neither reached disk. And-obinds looser than the implicit-a, so-type fapplied only to the first-name: a directory named*.pomatched and was moved wholesale.[skip ci]left the commit messageGitLab honours it by creating no pipeline at all, which silenced the translation merge request's own pipeline — the translations could not be tested before merge, and with "Pipelines must succeed" enabled the MR could never be merged. A squash carried it into the default branch and silenced the whole project. The guard now lives in
rules:, which GitLab evaluates without suppressing anything.Tests
tests/test-ci-recipe.shexecutes the printed recipe against a mock PTC API on localhost — no docker, ~2 s — with onlycurlandgit pushshimmed, and asserts what the merge request would contain. Eight cases: translation on a detached HEAD, only-translations-committed, the CLI neither committed nor left behind, no push when nothing changed, and the push shape.tests/test-git-context.shnow pins the detached-HEAD behaviour it previously documented as a known defect and deferred.tests/test-init.shgained a check that the README's copy of the recipe is the one the CLI prints — these copies have drifted three ways before.All nine suites green.