Skip to content

feat: support immutable releases - #38673

Open
silverwind wants to merge 9 commits into
go-gitea:mainfrom
silverwind:immutable
Open

feat: support immutable releases#38673
silverwind wants to merge 9 commits into
go-gitea:mainfrom
silverwind:immutable

Conversation

@silverwind

@silverwind silverwind commented Jul 27, 2026

Copy link
Copy Markdown
Member

Implements points 1, 2 and 4 of #37102.

Publishing a release in a repository with the setting enabled locks its tag, target and assets. Existing releases are unaffected, and title, notes and prerelease stay editable.

The tag name is locked in its own table so it outlives the release, the tag and the repository itself: deleting the release frees the tag for deletion, but the name can never back another release or be pushed again, including from a repository later recreated at the same path. Those rows are deliberately never removed, so the table only grows.

Verified against GitHub, with three deliberate differences: the setting lives on EditRepoOption like every other repo toggle rather than a dedicated endpoint, locked names are matched case-insensitively to match Gitea's tag handling, and the lock also survives a rename — GitHub keys its locks by path alone, so renaming a repository there frees the names it locked.

Repo settings:

Screenshot 2026-07-27 at 21 35 53

Create release banner:

Screenshot 2026-07-27 at 21 38 57

Update release banner:

Screenshot 2026-07-27 at 21 38 43

Release view:

Screenshot 2026-07-27 at 21 40 51

Readonly assets on edit:

image

Publishing a release in a repository with the setting enabled locks its
tag, target and assets, and reserves the tag name permanently so it can
never back another release or be pushed again. Existing releases are not
affected, and title, notes and prerelease stay editable.

The tag name is reserved in its own table because it has to outlive both
the release row and the tag itself: deleting the release is allowed and
frees the tag for deletion, but the name must stay claimed. Without that,
deleting the release and re-pushing the tag would defeat the feature.

Closes go-gitea#37102

Assisted-by: Claude Code:Opus 5
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Jul 27, 2026
@silverwind silverwind changed the title Support immutable releases feat: support immutable releases Jul 27, 2026
@github-actions github-actions Bot added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label Jul 27, 2026
@silverwind
silverwind requested a review from Copilot July 27, 2026 20:10

This comment was marked as off-topic.

ServeAttachment resolves the repository itself when the request carries no
repo context, so reading ctx.Repo.Repository.ID panicked on the UUID based
attachment route.

Assisted-by: Claude Code:Opus 5
The repo scoped lookup only belongs in the deletion path, ServeAttachment
resolves its own repository and was never part of this change.

Assisted-by: Claude Code:Opus 5
Replacing the asset name input with plain text left it looking like stray
text in the form, which then needed a section label to explain it. Keeping
it an input without a name renders consistently and submits nothing.

Assisted-by: Claude Code:Opus 5
@silverwind silverwind changed the title feat: support immutable releases feat: support immutable release assets and tags Jul 27, 2026
@silverwind silverwind changed the title feat: support immutable release assets and tags feat: support immutable releases Jul 27, 2026
Comment thread models/repo/repo.go Outdated
Signed-off-by: silverwind <me@silverwind.io>
A tag name locked by an immutable release now survives the repository it
was published in. The lock records the repository twice: by id so it
follows renames and transfers, and by owner and name so a repository
recreated under the same path inherits the locks of its predecessor.
Matching either one is enough, which closes both the rename escape and the
resurrection attack.

Verified against GitHub, whose locks are keyed by path alone: renaming a
repository there frees its locked tag names.

Assisted-by: Claude Code:Opus 5
A tag name locked by an immutable release now survives the repository it
was published in, so deleting a repository and recreating it under the same
path no longer frees its locked names.

The lock records the repository twice: by id so it follows renames and
transfers, and by owner and name so a repository recreated at the same path
inherits it. Matching either one is enough. GitHub keys its locks by path
alone, which is why renaming a repository there frees the names it locked.

The locks are deliberately not removed when a repository is deleted, so the
table only ever grows.

Assisted-by: Claude Code:Opus 5
ErrImmutableTag carried no data once its tag name was dropped, so it is a
sentinel now, which also survives error wrapping that the type assertion
did not. The pre-receive hook asks one question per ref instead of two, and
both branches of the tag lookup are exact index seeks.

Locked tag names outliving their repository is the point of the feature but
was held up only by a bean missing from a list, so it now has a test.

Assisted-by: Claude Code:Opus 5
@silverwind

Copy link
Copy Markdown
Member Author

Added ressurection protection too so I consider #37102 fixed with this. Attestation is a much larger topic and imho out of scope here, Gitea does not have infrastructure for attestation.

…thing

ErrImmutableRelease becomes a sentinel like ErrImmutableTag, so the package
has one error shape instead of two and callers match with errors.Is, which
survives wrapping. The pre-receive check is inlined next to the protected
tag check it sits beside, matching the rest of that file.

The tests drop what they proved twice: the repository deletion case joins
the table that already lists what deletion purges, and the integration test
keeps only what is specific to HTTP and git. It gains the two branches that
were never exercised, publishing a draft and deleting a tag once its
release is gone.

Assisted-by: Claude Code:Opus 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants