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
* chore(release): 0.2.1
Cuts the Unreleased section carrying destination pull request mirroring,
the four remaining provider implementations, and the fixes from the review
of #46, and repoints the Action examples at the new tag.
Also corrects the 0.2.0 section, which claimed destination pull request
mirroring. That entry was written under Unreleased on the #44 branch, and
by the time it merged the release PR had already renamed that heading to
0.2.0, so a clean auto-merge filed a feature under a tag that never
contained it. The 0.2.0 section now matches what v0.2.0 actually shipped.
* fix(azuredevops): send a username with the PAT on the git transport
pat mode presented an empty username with the PAT as the password. SyncerD
hands git its credential through a helper that prints "username=<value>",
and git reads an empty value as no username at all: it then tries to ask,
terminal prompts are disabled, and every clone and push failed with "could
not read Username", which names nothing about the provider or the config
that produced it.
Azure DevOps ignores the username on this path and authenticates on the
token alone, so the value is free. The organisation name is sent because it
makes the credential legible in a trace without being a secret.
A basic credential carrying a password with no username is now rejected by
the runner as well, so a future provider with the same gap fails with a
sentence that names the cause rather than a bare git error. A bearer
credential still needs no username, and an empty credential, which is what
a local path uses, stays valid.
The REST API path was never affected: it carries the token in an
Authorization header, where an empty username is what Azure DevOps
documents.
---------
Co-authored-by: Anmol Nagpal <ianmolnagpal@gmail.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+12-1Lines changed: 12 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,10 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
## [0.2.1] - 2026-08-19
11
+
10
12
### Added
13
+
- `git-sync` can recreate pull requests at the destination. With `pull_requests.mirror_objects` on a mirror, each open source pull request becomes a real destination pull request, updated on every run, carrying its discussion comments, inline review comments anchored to the diff, review verdicts, and labels. A merged source pull request closes the destination one with a comment naming the source merge commit rather than merging it, because a destination merge would create a different commit that the branch mirror then overwrites. Review verdicts are mirrored as attributed text, never as real approvals, so a mirror cannot satisfy branch protection that no human satisfied. Mentions and issue references in mirrored text are neutralised, so mirrored content never notifies a destination account or cross links an unrelated issue. Everything SyncerD writes carries a hidden marker, so a lost state file causes re-inspection rather than duplicate pull requests, and only comments SyncerD wrote are ever edited or deleted. With `mirror_objects` on, every mirrored pull request gets a `branch_prefix` branch rather than fork heads alone, so a destination pull request has one uniform head name. Every provider type is supported, see below
11
14
- Pull request mirroring now works for every provider type, in any direction: GitLab, Bitbucket Cloud, Azure DevOps, and AWS CodeCommit join GitHub. Each maps onto the same model, and the runbook carries a table of what each provider cannot represent
12
15
- CodeCommit is in scope after all. It was excluded when the work was planned on the grounds that it had no pull request API worth mirroring, which was wrong: it has a full one, already present in the pinned SDK, and AWS returned the service to general availability on 2025-11-24 after closing it to new customers in 2024
13
16
17
+
### Fixed
18
+
-`git-sync` could not authenticate to Azure DevOps over git in `pat` mode. The provider presented an empty username with the PAT as the password, and SyncerD hands git its credential through a helper that prints `username=<value>`: git reads an empty value as no username at all, tries to ask for one, and fails with `could not read Username` because terminal prompts are disabled. Azure DevOps ignores the username and authenticates on the token alone, so the organisation name is sent now. A basic credential carrying a password with no username is also rejected up front, where the cause can be named, rather than surfacing as a bare git error. The REST API path was never affected: it sends the token in an Authorization header
19
+
- A destination that cannot reopen a pull request, which is Bitbucket Cloud and CodeCommit, failed on every run once its mirror had been closed by hand. Having decided to leave the pull request closed, the engine still attempted an update on it, which both providers reject, so the failure was recorded before the divergence was: the warning fired every run, a failure was reported every run, and the mirror never settled. The same path ran for a merged source whose destination was already closed whenever new activity advanced its timestamp. An update now runs only while the destination is open, and a recorded divergence is re-checked each run so a destination somebody reopened by hand is picked back up
20
+
- A CodeCommit source served the first run's snapshot forever. Its pull request cache was documented as per run but a provider is built once and reused for every tick of the schedule, so later pull requests were never mirrored, closed ones never closed, and the repository was skipped as unchanged because the same stale set fed the ref fingerprint
21
+
- An Azure DevOps source mirrored each pull request exactly once and then froze it. Azure reports no update timestamp, and standing the creation date in for one meant the engine's watermark always matched, so no later edit, comment, or verdict reached the destination. A source that reports no timestamp is no longer watermarked
22
+
- CodeCommit reported "no reviews" when the underlying call failed, and the caller deletes mirrored comments it no longer sees at the source, so a single throttled call deleted every mirrored review verdict. It surfaces the error instead. For the same reason, a transient failure posting an anchored comment is no longer treated as a rejected anchor, which would have downgraded it to a plain comment permanently
23
+
- CodeCommit could not find a destination pull request that had been closed, so a lost state file created a second one for the same branch. GitLab anchored every comment on a deleted line to line 0 on the wrong side of the diff, and read its transient `locked` state as closed, which closed the destination and posted "closed without merging" moments before the source became merged. Bitbucket dropped the draft flag on update and could not find a superseded pull request. GitHub reported no base SHA, so every inline comment mirrored to GitLab or CodeCommit, which both require one, silently became a plain comment. Azure DevOps counted a description limit in bytes rather than characters, truncating non-ASCII text to roughly a third of the allowed length
24
+
- Turning `comments` or `reviews` off deleted everything previously mirrored in that category. Deletion is now scoped to the categories a run actually listed
25
+
14
26
### Changed
15
27
-`vcs.PullRequestSpec` carries `SyncLabels`, so a provider can tell "the source has no labels", where a destination label should be removed, from "label mirroring is off", where a label somebody added at the destination must be left alone. Labels are now reconciled rather than only added: one removed at the source is removed at the destination
16
28
-`vcs.PullRequestWriter` closes a pull request but no longer reopens one. Bitbucket Cloud has no reopen endpoint and CodeCommit permits only the `OPEN` to `CLOSED` transition, so reopening is now an optional `vcs.PullRequestReopener` capability. A destination that lacks it leaves the pull request closed, warns once, and records the divergence rather than opening a second pull request for the same work or retrying on every run
@@ -19,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
31
## [0.2.0] - 2026-08-19
20
32
21
33
### Added
22
-
- `git-sync` can recreate pull requests at the destination. With `pull_requests.mirror_objects` on a mirror, each open source pull request becomes a real destination pull request, updated on every run, carrying its discussion comments, inline review comments anchored to the diff, review verdicts, and labels. A merged source pull request closes the destination one with a comment naming the source merge commit rather than merging it, because a destination merge would create a different commit that the branch mirror then overwrites. Review verdicts are mirrored as attributed text, never as real approvals, so a mirror cannot satisfy branch protection that no human satisfied. Mentions and issue references in mirrored text are neutralised, so mirrored content never notifies a destination account or cross links an unrelated issue. Everything SyncerD writes carries a hidden marker, so a lost state file causes re-inspection rather than duplicate pull requests, and only comments SyncerD wrote are ever edited or deleted. With `mirror_objects` on, every mirrored pull request gets a `branch_prefix` branch rather than fork heads alone, so a destination pull request has one uniform head name. GitHub source and destination for now; other providers are rejected at startup
23
34
-`git-sync` can mirror the commits behind open pull requests. A pull request opened from a fork has no branch in the source repository, so its commits previously reached no destination at all; with `pull_requests.enabled` on a mirror, the head is pushed as an ordinary branch under `branch_prefix` (default `syncerd/pr/<number>`), and the default mirror push mode prunes it once the pull request closes. A head that lives in the source repository already arrives with the ordinary branch mirror and gets no second copy. A repository whose own branches sit under the prefix is refused rather than silently overwritten, and one unreachable fork head is a warning rather than a failed repository. Off by default: a fork head is third party code, and a destination that builds on branch push would run it. GitHub sources only for now; the pull request objects themselves are not recreated at the destination yet
DOCKER_CONFIG: /github/workspace/.docker # same directory, path inside the container
163
163
with:
@@ -181,7 +181,7 @@ steps:
181
181
Since the default push mode deletes destination refs that are absent at the source, **run once with `dry-run: "true"` before trusting a new mirror config**:
182
182
183
183
```yaml
184
-
- uses: clouddrove/syncerd@v0.2.0
184
+
- uses: clouddrove/syncerd@v0.2.1
185
185
with:
186
186
command: git-sync
187
187
config: syncerd.yaml
@@ -195,7 +195,7 @@ Since the default push mode deletes destination refs that are absent at the sour
195
195
Once the dry run output looks right, drop `dry-run` (or set it to `"false"`) to let it write for real:
0 commit comments