Repository navigation
feat(azuredevops): deliver to Azure DevOps Services - #23
Merged
Merged
Conversation
A provider of type azure-devops is one organization of Azure DevOps Services: its url is https://dev.azure.com/<organization> (required, one provider per organization) and its REST base is that url. Any other url needs an api_url (a test server) and must still name the organization as its only path segment; the old <organization>.visualstudio.com form is refused. Repository paths are project/repository. Web URLs of repositories, https://dev.azure.com/<organization>/<project>/_git/<repository>, resolve to the provider; the organization URL itself is the namespace of an org entry, which selects the whole organization (a project goes through match). Azure DevOps has no topics: check refuses them with a hint, warns about match and exclude patterns deeper than project/repository, and a local run takes any target of the provider as possibly selected by its org entries. The JSON Schemas of hub.yml, targets.yml and the reports follow; the fixtures that used azure-devops as an unknown type use sourcehut now. setup and the hub's key store refuse azure-devops: a hub on Azure Pipelines comes later.
Caps.Marker MarkerInProperties is a platform whose descriptions are too short for the marker (Azure DevOps: 4 000 characters), which keeps it in a pull request property instead. The contract of PR.Body, NewPR.Body and PREdit.Body does not change: the core writes and reads one body that ends with the marker line. A driver of such a platform splits what the core writes with marker.Detach (the description without marker lines, and the last marker line for the property) and joins what it reads with marker.Attach (the description without marker lines, then the stored line), so a marker pasted into a description never counts. Strip and Find answer the same on a body and on its detached and attached form. prbody bounds the description alone there: Caps.MaxBody reserves no room for the marker and does not count the marker line. Every other platform keeps its bodies byte for byte (their goldens are unchanged); the new azure-devops goldens check that each description fits 4 000 bytes and splits back into the body.
… pull requests A new driver, internal/platform/azuredevops, serves plan for providers of type azure-devops; its write driver refuses with ClassUnsupported until the writer comes. The reader authenticates with a personal access token: Basic with an empty user name for the REST API, Basic touchmark:<token> for git, both masked. Every request sends api-version=7.1 and X-TFS-FedAuthRedirect: Suppress; a redirect to the sign-in page or a 203 with HTML is ClassAuth. Identities are their GUIDs: Self reads connectionData (the public-access identity is a refused token), Lookup the Identities API by id and refuses anything else. Probe asks nothing: flavor azure-devops, native drafts, closedBy as the closer, a 4 000-byte description with the marker in a property (MarkerInProperties), closed pull requests immutable, labels by name, branch and push policies known at push time only, conservative limits. Repo reads <project>/<repository>; Resolve lists the organization's repositories in one call (an org entry names the organization; topics are refused), forks only on request, disabled ones included for the core to skip. ReadFile reads the folder through the Items API, the entry's mode, id and size through the Trees API and the bytes through the Blobs API, checking the blob id; only GitItemNotFoundException or a folder without the entry is ErrNotFound, a missing branch or repository is ClassUnknown. PRs lists each head's pull requests in every status, reads the marker property of each, a pull request alone when its listed description may be cut or to learn who abandoned it, the base through the Refs API and an active pull request's head from its branch. OpenPRsBy sweeps each project by creator. Errors follow the typeKey and TF codes of Azure DevOps' error body; 429, TF400733 or a Retry-After on any refusal is rate limited.
NewWriter builds a real writer. Target reads the repository by its id and evaluates the writer's permissions with Permissions - Has Permissions on the Git Repositories namespace (GenericContribute and CreateBranch for contents, PullRequestContribute for pull requests; a missing one is ClassPermission with rule contents or pull-requests). When that API refuses the token, the check is skipped and the writes meet a missing permission instead. The TargetWriter pushes with Basic touchmark:<token>. CreatePR splits the body (marker.Detach): the description without its marker line, at most 4 000 characters, and the marker line in the property touchmark.marker, written right after the pull request; draft is the native flag; labels go by name in the request and those the answer lacks are added one by one. EditPR writes the marker property first, then the labels, then one PATCH of what changes of title, description, base and status abandoned, so a closing body's marker is stored before the pull request is abandoned. It never reactivates or changes an abandoned pull request (ClassUnsupported), and a completed one is ClassConflict. Comments are closed threads, which never wait for a resolution; EnsureLabels has nothing to create. The Checker reports access from the same permissions, rules as unknown, and the token's expiry, scopes and 2fa as unknown. A push Azure Repos refuses with TF402455 (a branch policy) is blocked:rules:policy, one with TF401027 (a missing Git permission) blocked:permission:push. A commit without the user's address is authored by <identity id>@touchmark.invalid.
CapsFor(AzureDevOps) matches the driver's Probe: a 4 000-byte description with the marker in a property, native drafts, labels by name, a known closer, closed pull requests immutable, branch and push policies at push time. Where the marker lives apart the fake stores what the driver reads back: the description without marker lines, then the last marker line of the body; the description alone is held to MaxBody, comments are not, and a person's edit of the description keeps the stored marker. An abandoned pull request is final, as on Bitbucket. The meter counts the property write after a create and before an edit, and one call per label added.
A pull request opens with its labels and a marker kept apart from a description of at most 4 000 bytes; an abandoned one holds as a decline with no write to it, lapses when the opt-in file changes, and a forget_declines entry brings the content back while present; an opt-in change under an open pull request rewrites only its marker; touchmark's own close stores the closed marker before it abandons, and is no decline; a long change set is cut to fit while the marker records every change. The property test runs 15 Azure DevOps scenarios, the crash matrix two Azure DevOps cases.
The providers guide gets an Azure DevOps section: one provider per organization, two users' PATs (Code read; Code read & write), identity ids as logins, project/repository paths and _git web URLs, no topics, the marker in a pull request property beside a 4 000-character description, labels by name, abandoned pull requests never edited or reactivated, branch policies met at push time, and what only a live organization can confirm. Memory, operations, the glossary, the compatibility page and the page for agents follow.
CreatePR tries the idempotent property replace again after a transient failure or a short rate limit (1, 2 and 4 s; a Retry-After over 30 s ends the attempts); should it still fail, the new pull request is abandoned and the call fails with the property's class, so that no own pull request stays active without its marker. EditPR of an active pull request that is not a close sends its PATCH first, then the labels, then the property: a failure in between leaves the previous marker, which the next run sees as an edit still to make. A close keeps "property, then abandon", and abandons even when a description change would not fit (the description stays as it is). A description equal to the current one, marker lines, line endings and trailing blanks aside, is not sent. Also: a disabled repository is ClassUnknown in ReadFile, never a missing file; anonymous reads sent to sign-in say that Azure DevOps wants a token; Retry-After on an answer that went through pauses the throttle; PRs reads the marker of every active pull request and of the 60 newest abandoned and completed ones only.
…re DevOps FailMarkerNext fails the next write of the marker property as Azure DevOps' driver meets it once its attempts are spent: CreatePR leaves the pull request abandoned without a marker (or active, when the abandon fails too), EditPR keeps the previous marker after the rest of an edit and changes nothing of a close. EditPR sends no description equal to the current one and lets a close leave one that would not fit. The comments no longer present pull request properties as protected.
…on Azure DevOps Where the marker lives in a pull request property, an edit that leaves the description as read (a close, an opt-in refresh, a revocation) keeps it byte for byte instead of making it inert: the driver does not send it, and Inert could push a description people filled past the 4 000-character limit, so that the close failed every run. Only its marker line is checked as touchmark's text. Edits that change the description (a consumed rebuild, an ack) are made inert as before, and other platforms write the same bodies as ever. After a create whose outcome is unknown, the open pull request found counts as created on such platforms only once it carries the expected marker; otherwise an edit with the new body and labels stores it.
The providers guide no longer presents the marker property as protected, and says how touchmark keeps it, that kept descriptions are not sent, that the reader needs a token, how Retry-After and the property reads are handled, and adds who can write pull request properties to the live checks. The threat model records the residual risk: a project Reader may block a target or make a decline not count.
# Conflicts: # docs/agents.md # internal/cli/setup.go # internal/hubch/keys.go
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.
Azure DevOps Services as a delivery platform: a hub on GitHub, GitLab, Gitea/Forgejo or Bitbucket can now deliver to repositories on dev.azure.com. A hub on Azure Pipelines comes later.
Provider type
azure-devops.url: https://dev.azure.com/<organization>: one provider per organization. Targets areproject/repo, and web URLs…/<project>/_git/<repo>resolve.topics:failscheck.Driver (
internal/platform/azuredevops).ErrNotFoundonly for a missing path at an existing commit; a missing branch or repository, or a disabled repository, is never one.Retry-Afterpauses the provider even on a 200.rules:policy, and TF401027 ispermission:push.Marker in pull request properties (
MarkerInProperties). Descriptions are limited to 4000 characters, so the marker lives in the propertytouchmark.marker. The description stays the readable part, within 4000.marker.Detach) and joins it back (marker.Attach). A marker pasted into a description never counts.ClosedImmutable, as on Bitbucket). Whether properties of an abandoned pull request can be edited is unknown, and the no-write decline memory already works.GitHub, GitLab, Gitea, Forgejo and Bitbucket are unchanged, and tests prove their bodies and decisions are byte-identical.
Tests.
azure-devopsflavor of the fake platform, delivery scenarios and a property simulation on it, and crash-matrix cases.An independent review found no regressions on the other platforms. Its findings are fixed here: a lost marker after create, long human descriptions, the write order, a disabled repository read, anonymous reads and
Retry-Afteron success.Known risk, documented in the threat model. Project Readers may be able to write pull request properties. If so, a Reader could block a target, or make a decline not count; author, head and fork identity come from the API and cannot be forged. This is on the live-check list. If it is confirmed, the marker moves to a comment by the writer.
Not verified live (no organization). These are listed in the docs: property limits and permissions, the description limit, labels on create, the duplicate pull request error, the closer of an abandoned pull request, the answer to a bad PAT, push refusal texts, and Trees API modes.