You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: defects found by a full review of internal/ and main.go (#48)
* fix(deps): patch the high severity brace-expansion advisory
The Azure DevOps extension pinned brace-expansion to 5.0.8 through an
overrides entry, and 5.0.8 is the version the current advisory covers: a
denial of service through unbounded intermediate arrays that bypasses the
earlier CVE-2026-14257 mitigation. The pin that once fixed a problem had
become the problem, and nothing would move it because an override is
absolute.
Bumped to 5.0.9, the first patched release and the current latest, with the
lockfile regenerated to match.
* chore(ci): update the actions CI exercises to their current majors
Every run warned that actions/checkout and actions/setup-go target Node 20,
which GitHub now force-runs on Node 24 and will stop supporting. Moved to
checkout v7, setup-go v7, and upload-artifact v7 in ci.yml and
action-smoke.yml, which this pull request exercises.
The release, helm publish, and Azure DevOps extension workflows are left
alone deliberately. They run only on a tag or a manual dispatch, so no pull
request proves a bump there, and a broken publish workflow is discovered at
the worst possible moment. They are worth updating in a change that can be
dispatched and watched.
* fix: repository paths and shared state in destination pull request sync
Three defects that each made a whole path unusable.
The engine handed prsync the rendered destination name, which is owner
relative because CloneURL and EnsureRepo prepend the owner themselves.
Every destination pull request and comment endpoint interpolates that value
as a fully qualified path, so a GitHub destination built "/repos/widget/pulls"
instead of "/repos/acme/widget/pulls" and answered 404 for every call.
mirror_objects could not work at all against GitHub, GitLab, or Bitbucket.
Remote gains QualifiedPath, each provider implements the convention its own
ListRepos reports, and the engine qualifies the name before passing it on.
The existing tests passed because their fake used the broken convention.
Azure DevOps went wrong the other way: ListRepos reports project/name and
every URL already carries the project as its own segment, so interpolating
the qualified path whole produced a duplicated project and 404ed on every
repository. Its pull request routes now use the repository segment alone.
internal/prsync wrote pull request records into the shared GitState from
inside the engine's worker pool, bypassing the lock the engine uses for its
own access and documents as necessary. Two repositories finishing together
was a concurrent map write, which aborts the process and cannot be
recovered by the worker that caused it. GitState now guards its own maps,
so no caller has to remember.
* fix(registry): use the Docker Hub session token instead of basic auth
Authenticate logged in and threw the response away, so the JWT the Hub API
authenticates with was never kept. Every later listing fell back to HTTP
basic auth, which that API does not accept: a private repository with
watch_tags on answered 404 after three retries, reported against the image
rather than against the credentials.
The session token is stored and sent as a bearer credential, ahead of a
configured personal access token, with basic auth left as a last resort for
any deployment still relying on it. An anonymous registry sends nothing.
* fix(prsync): close vanished pull requests, and stop losing work on failure
Sources are listed open only, so a merged or closed pull request does not
arrive with a finished state: it stops appearing. The close path was
therefore unreachable, and a destination pull request stayed open forever
while the mirror push pruned the branch under it. Records the source no
longer lists are now reconciled and closed with a note that says only what
is knowable from an open listing.
GitHub keeps discussion comments and review comments in separate id spaces
addressed through different routes, and both were updated through the
issues route. Every pull request with a mirrored inline comment failed its
conversation sync with a 404 on every run after the first, or edited an
unrelated comment if the ids collided. The id now carries which space it
belongs to, and a bare id from an older state file still resolves to the
discussion route.
A conversation that failed part way discarded the ids of the comments it
had already posted, because the state write came after the error return, so
the next run reposted all of them, forever. What succeeded is recorded
before giving up.
The repository fingerprint was recorded before the pull request pass ran,
so a failed pass was marked done and skipped on the next run until an
unrelated branch or tag happened to move. It is recorded after, and only
when the pass had no failures.
A pull request head that failed to fetch was dropped from the keep set,
which pruned it from the cache and then deleted it at the destination. One
transient network error removed a correct branch and pulled the head out
from under its mirrored pull request. The previous copy is kept instead.
* fix: cron overlap, signal handling, glob validation, and Slack block limits
The image sync cron had no SkipIfStillRunning, which the git cron has along
with a comment explaining the hazard. A run that outlives its interval
overlapped itself on a Syncer that keeps per run state and a state map with
no locking: the second run's report was voided by the first, and two
writers on that map abort the process. Both cron loops now also register
the schedule before the first run, so a typo is caught immediately instead
of after a full sync of every image, and install the signal handler before
the first run, so a SIGTERM during it lets the run finish and persist
rather than killing the process with nothing saved.
A malformed exclude glob was silently ignored, so a repository somebody
wrote a rule to keep out was mirrored to the destination. Patterns are
validated at config load, and a pattern that cannot be parsed now drops the
repository rather than mirroring it.
A Slack section block could be emitted empty, and could exceed the size
limit, from a single oversized failure line such as a transport error
carrying a multi kilobyte HTML body. Either one makes Slack answer 400 and
the entire notification is lost, which matters most for the failure alert
that is the reason the integration exists. Sections are bounded at both
ends, and truncate now counts characters rather than bytes so it neither
trims non-ASCII text far short of the limit nor splits a rune.
A failed state save on the fail fast path was discarded, so every image
copied before the abort was re-pulled and re-pushed on the next run with
nothing in the output to say why. The same failure is fatal on the normal
exit path; it is at least reported here.
* docs: record the review fixes in the changelog
---------
Co-authored-by: Anmol Nagpal <ianmolnagpal@gmail.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
### Fixed
11
+
-`pull_requests.mirror_objects` could not work against GitHub, GitLab, or Bitbucket. The engine handed the destination side the rendered repository name, which is owner relative because the clone URL and repository creation prepend the owner themselves, while every destination pull request and comment endpoint interpolates that value as a fully qualified path: a GitHub destination asked for `/repos/widget/pulls` rather than `/repos/acme/widget/pulls` and every call answered 404. Azure DevOps failed the other way, duplicating the project into a path that already carried it, so listing pull requests 404d for every repository
12
+
- Mirroring several repositories at once could abort the process. Pull request records were written into the shared git state from inside the engine's worker pool without the lock the engine uses for its own access, and two repositories finishing together is a concurrent map write, which Go turns into an unrecoverable crash. The state type now guards its own maps
13
+
- A private Docker Hub repository with `watch_tags` on failed with a 404 blamed on the image. The login response's session token was discarded, so every listing fell back to HTTP basic auth, which that API does not accept
14
+
- A destination pull request stayed open forever once its source merged or closed. Sources are listed open only, so a finished pull request stops appearing rather than arriving with a final state, and nothing closed the mirror; the branch under it was pruned by the mirror push meanwhile. Records the source no longer lists are reconciled and closed with a note
15
+
- Every pull request carrying a mirrored inline comment failed its conversation sync after the first run. GitHub keeps discussion and review comments in separate id spaces reached through different routes, and both were addressed through the discussion route
16
+
- A conversation that failed part way discarded the ids of the comments it had already posted, so the next run reposted all of them, forever. A repository whose pull request pass failed was also recorded as fully mirrored, so it was skipped on the next run and never retried until an unrelated branch or tag moved
17
+
- A single failed fetch of a pull request head deleted that branch from the destination, pulling the head out from under a mirrored pull request. The copy from the previous run is kept
18
+
- The image sync cron could overlap itself, corrupting the run report and racing on the sync state map. Both cron loops now skip a tick rather than start a second run, validate the schedule before the first run instead of after it, and install the signal handler before the first run so a shutdown during it can still persist state
19
+
- A malformed `exclude` glob was ignored entirely, mirroring a repository that the rule was written to keep out. Filter patterns are validated at config load
20
+
- A Slack notification could be dropped whole. One oversized failure line, such as a transport error carrying a large HTML body, produced a block that was empty or over the size limit, and Slack rejects both
21
+
- A failed state save on the fail-fast path was silent, so everything copied before the abort was copied again on the next run with no explanation
22
+
-`git-sync` could not authenticate to Azure DevOps over git in `pat` mode, fixed in v0.2.1 and described there
23
+
24
+
### Security
25
+
- Patched a high severity advisory in `brace-expansion`, which the Azure DevOps extension pinned to exactly the affected version through an `overrides` entry
// A disabled block is not validated. Its fields have no effect,
361
362
// and rejecting a stale value in one would block a run that does
362
363
// not read it.
364
+
for_, pat:=rangem.Filters.Include {
365
+
if_, err:=path.Match(pat, "probe"); err!=nil {
366
+
returnfmt.Errorf("git.mirrors[%d].filters.include pattern %q is malformed: %w", i, pat, err)
367
+
}
368
+
}
369
+
for_, pat:=rangem.Filters.Exclude {
370
+
if_, err:=path.Match(pat, "probe"); err!=nil {
371
+
returnfmt.Errorf("git.mirrors[%d].filters.exclude pattern %q is malformed: %w; an exclude that cannot be parsed would otherwise be silently ignored", i, pat, err)
returnfmt.Errorf("git.mirrors[%d].pull_requests.mirror_objects is set but enabled is not; the destination pull request needs the head branch that enabled mirrors", i)
0 commit comments