Repository navigation
feat!: hold a repository branch with a GitBranch - #429
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GitBranch is one branch of a repository, written through a GitProvider in its own namespace. GitTarget gains spec.gitBranchRef. Nothing reads either yet; gitProviderRef and branch are optional until they leave the target. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A GitBranch holds its repository branch once its GitProvider's credential opens a receive-pack session on the remote; the oldest such GitBranch holds it, keeps it through a later credential failure, and any other reports BranchConflict. The claim replaces oldest-GitTarget-wins in branch_owner.go, and EnsureWorker, the worker sweep and the GitTarget reconcile read it. A worker now writes for the claiming GitBranch and uses its GitProvider's credential, signing key and metric labels. GitTargets reach it through spec.gitBranchRef; one in another namespace is admitted by the GitBranch's GitProvider accessFrom. status.remote, ParentBranchNotFound and RecoveringParentBranch move to the GitBranch, and its finalizer stops the worker (pushing or refusing every pending write) before the claim goes. GitProvider.status.branches is no longer published. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GitTarget.spec.gitBranchRef is required. spec.gitProviderRef, spec.branch and spec.parentBranch stay in the schema only so that setting one is refused, naming gitBranchRef. GitTarget.status.remote is gone (it is on the GitBranch), and so is GitProvider.status.branches: listing GitBranches answers the same question. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…to it The chart's quick start renders a GitBranch (quickstart.gitBranch), the samples and corpus fixtures name one, and the reference docs describe the branch claim, its reasons, and where status.remote now lives. The e2e suite creates a GitBranch per provider branch; the shared-provider spec becomes a shared-GitBranch spec covering the claim, and the parent-branch and remote refresh specs read the GitBranch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 191 files, which is 91 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. ⚙️ Run configuration
📒 Files selected for processing: 191
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
A GitBranch stalled on BranchConflict is not reconciled again before its steady requeue, so when the holder's finalizer released the branch the next candidate held it in memory and reported BranchConflict for up to five minutes. The finalizer now wakes it. The e2e helpers label the GitBranches they create with their GitProvider, and deleting a GitProvider in a spec's cleanup deletes them, so a spec's GitBranch no longer holds the branch the next spec in the namespace writes. The plan no longer links to a page this branch does not carry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
Phase 1 of docs/design/gitbranch-and-kubeprovider-plan.md: a repository branch is its own object,
GitBranch, and only theGitBranchthat proves it can push holds it. This closes the gap #428 left open: with oldest-GitTarget-wins, any namespace could take a repository branch without a credential.The shape
GitBranch.specis immutable (CEL).GitTarget.spec.gitBranchRefis required and immutable.GitTarget.spec.gitProviderRef,.branchand.parentBranchstay in the schema only so that setting one is refused, with a message naminggitBranchRef(theqps/allowAnySourceNamespacepattern).status.remotemoved toGitBranch.GitProvider.status.branchesis gone;kubectl get gitbranchanswers the same question.status.remote.commit), Ready, Age.-o wideadds Reason, Repository, Targets, Verified and Message. The Commit column moves no faster thanstatus.remote, which is written at most once a minute.The branch claim (
internal/git/branch_owner.go)GitBranchbecomes a candidate once itsGitProvider's credential opens a receive-pack session on the remote (git.ProvePushSession: a handshake, then close, sending nothing).(normalized repo, branch), the oldest bycreationTimestamp(then UID) holds the branch. Every other candidate reportsStalled=True, reasonBranchConflict, naming the holder.Ready=False(PushSessionFailed) and keeps the branch and its pending writes.No admission webhook is involved, by decision: a conflicting
GitBranchis admitted and reports a condition. Following rule 2 literally means an olderGitBranchthat proves itself later takes the branch over; that keeps the outcome the same across restarts.The claim's three readers:
EnsureWorkerrefuses anyGitBranchbut the holder (ErrBranchOwnedElsewhere).workersNeeded) keeps exactly the holders' workers that a liveGitTargetreferences.GitTargetreconciler wires a target only while itsGitBranchholds the branch. Until then the target reportsGitBranchReady=False(GitBranchNotReady) as progress, requeued on the not-Ready cadence.git.BranchKeyis unchanged. A worker now records theGitBranchit writes for.WritesThroughand the event router therefore route a target by itsgitBranchRef, with no read on the event path. Credential, signing key and theprovider_namespace/provider_namemetric labels come from the holder'sGitProvider.What the proof proves
Measured against the e2e Gitea (1.27):
So on Gitea the proof means "a valid credential", not "may push", because of its AGit flow. GitHub and GitLab require push permission; a host that offers receive-pack anonymously proves only reachability. This was accepted as good enough, and configuration.md and UPGRADING.md say so per host.
GitProviderreadiness still uses upload-pack, which passes anonymously on a public repository, so this is a new, stronger check.References are checked in status
GitBranchNotFoundGitBranch(Validated=False)GitBranchNotReadyNamespaceNotAuthorizedGitBranch'sGitProvider.accessFromdoes not admit the target's namespaceGitProviderNotFound,GitProviderNotReady,BranchNotAllowedParentBranchNotFound,RecoveringParentBranchPushSessionFailed,BranchConflictOn the cross-namespace reason I kept
NamespaceNotAuthorized: it is what aGitTargetreports today for exactly this check, and the plan says the existing reasons only change which kind reports them.GitProviderReadyon GitTarget becomesGitBranchReady.WatchRule/ClusterWatchRuleno longer read the GitProvider at all: they take the destination from the target'sgitBranchRef, follow the target's readiness, and no longer reportGitProviderNotFound.Lifecycle
A
GitBranchcarries a finalizer. On deletion it releases the claim first, so noGitTargetreconcile can restart its worker. ThenRetireGitBranchstops the worker; the shutdown path already finalizes the open window, pushes what is pending, and fails each unpushedCommitRequestwith an outcome. Only then does the finalizer go. Nothing deletes the remote branch. The next candidate takes the branch once the old worker is gone.status.remote on the GitBranch
The ledger and the one-minute floor are unchanged (
gitbranch_remote.go, moved fromgittarget_remote.go). What replacesrequeueForRemoteAnswer's "asked" half is that the worker wakes itsGitBranch(GitBranchEvents) every time it records an observation. The "held" half stays: a reconcile that meets news the floor holds back requeues for when it lifts. A successful proof is reused for one steady interval, so a reconcile woken by a push does not open a push session.Commits
docs: the plan.feat(api): the kind, additive (old fields temporarily optional).feat(git)!: claim, worker and reconcilers. The tests move togitBranchRef, andProvePushSessionis tested againststartRealGitServer, notfile://.feat(api)!: old fields refused by name,gitBranchRefrequired, moved status removed.docs: reference docs, chart quick start (quickstart.gitBranch), samples, corpus fixtures, e2e.shared_gitprovider_e2e_test.gobecomesshared_gitbranch_e2e_test.goand covers the "Done when" list.One bug the tests caught along the way: the sweep compared
needed[key]against a worker with noGitBranch, so two zero values compared equal and an orphan was kept. It now checks presence.Validation
task lint,task test: green. Unit coverage is 79.8% against a baseline of 80.2% with 0.5% tolerance.task test-e2e: not run locally. CI's e2e legs are the first run of the new e2e specs.🤖 Generated with Claude Code