Repository create and delete already fire `repository` webhooks, rename
did not. This adds the `renamed` action with `changes.name.from`
carrying the previous name, and renders it in the chat converters.
Actions workflows are unaffected, they still do not trigger on rename.
AI assistance was used for the implementation and tests.
Fixes https://github.com/go-gitea/gitea/issues/34891.
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: silverwind <me@silverwind.io>
UploadHandler creates the LFS meta object only as the last step of
uploadOrVerify, after the content is already in the store, so a request
that errors has never created a row of its own. The removal on the error
path was a real compensating action when it was added in #14726, where
the meta object was created before contentStore.Put, but #16865 moved
creation after the Put and left the removal behind.
Since then it can only ever delete a row created by a different request:
a stalled git-lfs PUT that fails after its own retry has already
succeeded wipes the winner's meta object, leaving the content in the
store unreachable and eligible for orphan cleanup.
Drop the removal and add a regression test asserting that a failing
upload keeps a pre-existing meta object.
Fixes: #38424
Assisted-by: Claude Code:claude-opus-5
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This pull request optimizes code diff highlighting in the PR Files
Changed view, which is a major CPU bottleneck and hot path in
production.
Previously, Gitea executed the expensive `DiffMatchPatch` algorithm
twice for every matching deleted/added line pair (once for the deleted
line, and once for the added line). We now run the diff algorithm once
and cache the resulting `DiffInline` on the `DiffLine` struct itself,
allowing the second line to render via a 0-cost cache lookup.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic
matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel),
fixing issues found reviewing the two features together.
- **A placeholder could stall its run forever.** Its payload keeps the
raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead
of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*)
}}`.
- **An `if:` reading `matrix.*` skipped the whole job**, with or without
the `${{ }}`. It now reduces to the needs gate, except under
`always()`/`failure()`/`cancelled()`, and each combination is decided on
its own values once the matrix expands.
- **Dependents could be skipped before the combinations ran**, since
inserted siblings are absent from the resolver's job set. The pass now
stops after an insert and defers to the re-emit it schedules.
- **Expansion failures stranded the placeholder.** A retryable one is
returned so the queue retries it; a malformed payload fails the job
instead of requeueing forever.
- **Rerun could rewind a pass-through row** into a raw placeholder
keeping its old terminal status, which nothing expands. Now gated on the
anchor itself.
Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from
a non-numeric literal (warn — it silently drops the cap).
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Show only author and co-authors without committer, and deduplicate the
same user with multiple email addresses.
The commit list "Author" column should not show the committer. This is
somewhat misleading, and arguably showing it on the individual commit
page is sufficient and consistent with other forges.
The same user with multiple email addresses often happens when
DEFAULT_KEEP_EMAIL_PRIVATE is enabled and Gitea does not use an actual
email address by default for edits. Showing the same avatar and name
twice is not helpful then.
Ref #37594Fix#38488
---
Before
<img width="4088" height="2138" alt="before"
src="https://github.com/user-attachments/assets/a9e919f4-72d0-495f-a0b5-ec094f148f5a"
/>
After
<img width="4124" height="2142" alt="after"
src="https://github.com/user-attachments/assets/7212e527-ab83-440a-a8a4-95b5a79b3580"
/>
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Unfortunately, we can't completely remove the ctx from git.Repository,
because the CatFileBatch still heavily depends on a parent context.
If we remove the Repository ctx, the CatFileBatch will become a mess and
create a lot of unnecessary git processes.
http://localhost:3000/-/admin/monitor/perftrace
* Before: open a repo home, dozens of git processes (duplicate cat-file)
* After: only a few (no duplicate cat-file)
Adds dynamic matrix evaluation to Gitea Actions: a job's
`strategy.matrix` can be built from the outputs of the jobs it needs.
```yaml
jobs:
generate:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.result }}
steps:
- id: set
run: echo "result=[1,2,3]" >> $GITHUB_OUTPUT
build:
needs: [generate]
runs-on: ubuntu-latest
strategy:
matrix:
version: ${{ fromJson(needs.generate.outputs.matrix) }}
steps:
- run: echo "building ${{ matrix.version }}"
```
Such a matrix cannot be expanded at planning time, so the job is planned
as a single placeholder and expanded by the job emitter once its needs
finish. Each combination is then gated by `if:` and concurrency as
usual.
- A matrix that resolves to no combination fails the job, as on GitHub.
- Expansion is capped at `MaxJobNumPerRun`.
- Workflows without a needs-dependent matrix are unaffected.
Fixes https://github.com/go-gitea/gitea/issues/25179
---------
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Co-authored-by: Claude <claude-sonnet-4-5@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Regression of the ctx removal from "git.Repository" struct (the old code
was already wrong and can still cause 500, the "ctx removal" just makes
the problem easier to reproduce).
Merge duplicate code.
Reviewd by codex: no actionable findings.
`GetBranch` silently returned soft-deleted branches, contradicting
`IsBranchExist` and forcing callers to manually check `branch.IsDeleted`
everywhere.
Refactored it to `GetBranchExisting` to have a clear behavior: it only
returns the existing branch.
---------
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes#38209
OAuth2-linked accounts that sign in via the password form were still
redirected to the provider end_session_endpoint on logout because the
redirect was keyed off account LoginType.
Store the session sign-in method (password vs oauth2) and only use
RP-initiated OIDC logout when this session was authenticated via OAuth2.
Sessions without the new key keep the previous LoginType behavior.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
* Closes#36942
* Fixes#19265
Replaces the SSE-based push channel (`/user/events`) with a WebSocket
endpoint (`/-/ws`).
### What changes
- **New `/-/ws` endpoint** (authenticated). One WebSocket per origin,
shared across tabs via a single `SharedWorker`.
- **Pubsub broker** (`services/pubsub`) for fan-out by topic, behind a
`Broker` interface. `MemoryBroker` is the default (single process); a
Redis backend is available for multi-process setups, configured via
`[websocket].PUBSUB_TYPE` / `PUBSUB_CONN_STR`. The internal Gitea queue
was not usable here because it has FIFO/single-consumer semantics.
- **Push-only event production.** Events are emitted by write-triggered
notifiers — `NotificationCountChange`, `PublishStopwatchesForUser`, and
the logout publisher — wired into the existing `notify.Notifier`
interface. No server-side pollers.
- **Typed pub/sub on the client.** `web_src/js/modules/worker.ts` is a
singleton transport; features subscribe per event type via
`onUserEvent('notification-count', cb)` instead of branching on
`event.data.type`.
- **Wire contract** (`UserEventType` union) is shared between the worker
and consumers via `web_src/js/types.ts`, kept in sync with
`services/websocket/events.go`.
- **Client-side periodic polling fallback** kicks in only when the
WebSocket cannot be established (e.g. proxy blocks WS, browser lacks
module-SharedWorker support).
### What's removed
- `modules/eventsource` (SSE manager, run loop, messenger).
- `/user/events` route and `tests/integration/eventsource_test.go`.
- All server-side polling for stopwatches and notification counts.
### Stopwatch multi-tab fix
The navbar stopwatch icon was previously rendered conditionally on `{{if
$activeStopwatch}}`, so tabs loaded before the timer started had no DOM
element to update. The icon and popup are now always rendered (toggled
with `tw-hidden`), and the start/stop/cancel handlers POST silently so
all open tabs reflect the change in real time.
### Deployment note
WebSocket needs the upgrade headers to pass through a reverse proxy,
e.g. for nginx:
```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
Without them the WebSocket cannot be established, and after 3
consecutive failed opens the shared worker signals `push-unavailable`:
the notification count and stopwatch fall back to periodic polling on
the existing `[ui.notification]` timeouts. Real-time push is lost, the
features keep working. The reverse-proxy docs need the same note (see
the `docs-update-needed` label).
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Epid <rexmrj@gmail.com>
- enable `skip-initialism-name-checks`, so names like `sessionUid` are
allowed
- replace removed `skip-package-name-checks` option with new
`package-naming` with fitting rules for gitea
- drop dead exclusion paths
Adds a shared `[redis]` config section with a single `CONN_STR` key that
acts as the default Redis connection for every redis-backed subsystem
whose own connection string is empty: `[cache].HOST`,
`[session].PROVIDER_CONFIG`, `[queue].CONN_STR` and
`[global_lock].SERVICE_CONN_STR`.
Precedence: per-subsystem value > shared `[redis].CONN_STR` > existing
built-in defaults. Fully backward compatible: when `[redis]` is not set,
every code path behaves exactly as before.
Motivation: today an admin with one Redis instance has to repeat the
same connection string in up to four places (five once WebSocket lands).
Requested by @wxiaoguang in [this review
discussion](https://github.com/go-gitea/gitea/pull/36965#discussion_r3613306976)
as a prerequisite for #36965. This follows the pattern used by GitLab
and Mastodon: one global Redis definition, per-subsystem overrides fall
back to it.
Since #12385 all identical connection strings already share one client
via `nosql.Manager`, so pointing all subsystems at `[redis].CONN_STR`
naturally converges onto a single shared connection pool.
Covered by table-driven unit tests per subsystem (fallback applies, own
value wins, absent `[redis]` = unchanged behavior). A config-cheat-sheet
update for gitea/docs will follow once this is merged.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Rename functions "util.Remove" (remove.go) to "util.RemoveWithRetry"
(file_retry.go) and add comments to clarify their behaviors, also add
tests.
Refactor callers: when no concurrent access (cmd cli, migration, app
init, test), use "os.Xxx" directly.
More details are in `modules/util/file_retry.go`
By the way, clean up OS (windows) detection, make FileURLToPath test
always run
## Problem
Jobs that call a reusable workflow (`uses:`) whose steps contain a `run:
|` block that **starts with blank lines** never start — the child jobs
stay `Blocked` forever, the run never finishes, and nothing is shown to
the user (the error is only logged at DEBUG). The reusable is valid YAML
and worked on 1.26.
## Root cause
`jobparser` serializes each expanded job via `SingleWorkflow.SetJob()`,
which uses `yaml.NewEncoder(...).SetIndent(2)`, but
`SingleWorkflow.Marshal()` used `yaml.Marshal`, whose default
indentation is **4**. Re-emitting a multi-line literal block scalar at a
different indentation makes the encoder write a wrong explicit
indentation indicator (`run: |4`) whose declared indent doesn't match
the actual content indent. The stored `workflow_payload` is then
unparseable:
```
run: |4
while ...
```
`jobparser.Parse` / `model.ReadWorkflow` (both go.yaml.in/yaml/v4)
reject it: `did not find expected key`. This surfaces in
`services/actions/job_emitter.go` `resolve()` →
`updateConcurrencyEvaluationForJobWithNeeds` → `ParseJob`, where the
error is swallowed at `log.Debug` and the job is left `Blocked`.
Encoding at indent 4 triggers the bad indicator; indent 2 does not —
matching the value already used by `SetJob`.
## Fix
Encode `SingleWorkflow.Marshal()` with `SetIndent(2)` so both encoders
agree and the serialized single workflow round-trips. Adds a regression
test (`Parse → Marshal → Parse` on a `run:` block with leading blank
lines) that fails before the change with `did not find expected key`.
---------
Signed-off-by: quasimodo <mohamed.belkamel@intelcia.com>
Co-authored-by: quasimodo <mohamed.belkamel@intelcia.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Corrects the Swagger/OpenAPI schemas for `/user/settings` and
`/topics/search` to match the actual JSON objects returned by the API.
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
This PR adds support for file exclusion into `.gitea/template`
Docs: https://gitea.com/gitea/docs/pulls/470
### Usage
Template owners add a `.gitea/template` file to their template
repository:
```
# Files to include for variable expansion
*.go
text/*.txt
**/modules/*
# Files/directories to exclude from the generated repo
[exclude]
vendor/*
node_modules/
src/build/
```
Resolves#37202
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
by the way, remove some unnecessary "models" imports from model
migration package, fix migration test model init bug
(modelmigration/migrationtest/tests.go)
This PR adjusts the repository README discovery logic to mirror GitHub's
priority order, while retaining support for Gitea's specific `.gitea/`
directory.
Previously, Gitea would prioritize a `README.md` at the root of the
repository over any READMEs in `.gitea/` or `.github/`. With this
change, well-known subdirectories are evaluated first when viewing the
repository root.
**New Priority Order:**
1. `.gitea/README.md`
2. `.github/README.md`
3. `/README.md` (root directory)
4. `docs/README.md`
This allows repository maintainers to use `.github/README.md` (or
`.gitea/README.md`) as the repository homepage without having to remove
or rename generic root `/README.md` files required by package managers
(like NPM, Crates.io, Docker, etc.).
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The "path" details should be hidden to other packages
By the way, fix a resource leaking in gogit's CommitNodeIndex
(the file was not closed in CacheCommit)
before: gitrepo vs git packages
after: git package fully handle all git operations
by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide
path details.
benefits:
1. remove all unnecessary wrappers, developers no need to struggle with
"which package should be used"
2. simplify code, RepositoryFacade can (will) be used everywhere, all
"path" details are (will be) hidden
Removed `gitrepo.RunCmd*` functions, because gitcmd.Command works with
Repository directly.
Move some "local filesystem" related function into "localfs.go"
Blob.Size requires a context after the repository context removal
(5b078f72aa).
Actually the template code should just render, it should not depend on
the fragile dynamic calls to backend functions.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1. use var names "reqOwnerName, reqRepoName", because these values are
from request and should not be really used for path construction
2. simplify enable-pprof logic, don't "log.Fatal"
3. don't make runServe command to guess the repo storage path, instead,
let server return RepoStoragePath
4. don't process lfs verbs when the repo is a wiki
5. construct the request URI path correctly for the lfs transfer backend
(moved to the caller)
6. don't call "owner, err := user_model.GetUserByName", the "owner
rename redirection" has been done before
7. fix incorrect "repo.OwnerName = ownerName", the real owner might have
been "redirected"
8. fix incorrect "inactive owner" check, it should be checked even if
the repo is redirected
Use general error functions to handle responses, error handling code is
hugely simplified.
Fix#38485#37478 moved `if` evaluation from runner to server. But `NewInterpeter`
always provides a nil `JobContext` for the evaluator, which makes
[`cancelled()`](ad967330a8/act/exprparser/functions.go (L299))
panic on `Job.Status` because `Job` is a nil pointer.
Follow-ups from https://github.com/go-gitea/gitea/pull/37038:
- Render the OpenAPI 3.0 spec in the API viewer, it is richer than the
Swagger 2.0 rendering
- Replace the back link with gitea-styled buttons to view both specs and
return to Gitea, flowing above swagger-ui on viewports where they would
overlap the title
- Key `VisibilityModes` by the string enum type, now named
`VisibilityString`, removing the `string()` casts at API call sites
- Drop the raw visibility strings from the `Service` settings struct in
favor of the typed mode fields, which also removes the org default
visibility row from the admin config page
- Fix two pre-existing issues surfaced in review: an invalid
`DEFAULT_USER_VISIBILITY` was silently accepted as public, and the org
visibility error message showed the enum zero value instead of the
submitted input
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1. Storing "ctx" in a long-living object is wrong
2. Make the commit & tree cacheable (for the future performance
optimization)
3. Also fix some bad designs like `// FIXME: bad design, this field can
be nil if the commit is from "last commit cache"`
ref:
* #33893
Addresses a batch of privately reported security issues, grouped by
area:
- **SSRF** - migration PR-patch/asset fetches, OAuth2 avatar & OpenID
discovery, pull-mirror URL re-validation, and the outbound proxy path.
- **Access-token scope** - prevent scope escalation on token creation;
keep public-only tokens confined (feeds, packages, Actions listings,
star/watch lists, limited/private owners).
- **Access control / disclosure** - go-get default-branch leak, webhook
authorization-header leak, watch clearing on private transitions,
label/attachment scoping.
- **Denial of service** - input bounds for npm dist-tags, Debian control
files, Arch file lists, and SSH keys.
### 📌 Attention for site admins
Not breaking - existing configs keep working - but two changes are worth
a look:
- **New SSRF protection** Outbound requests (migrations, OAuth2 avatars,
OpenID discovery, pull mirrors, proxy path) are now validated against
the allow/block host lists. If your instance legitimately reaches
internal hosts, you may need to add them to
`[security].ALLOWED_HOST_LIST` (and the relevant `ALLOW_LOCALNETWORKS`
settings).
- **Deprecation** `[webhook].ALLOWED_HOST_LIST` is deprecated and will
be removed in a future release. Use `[security].ALLOWED_HOST_LIST`
instead; the old key still works for now.
---------
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
### What / why
`TimeEstimateParse` (used by the issue time-estimate form) only checked
that the first token starts at the beginning of the string and the last
token ends at its end, but never checked the gaps between consecutive
tokens. Non-whitespace garbage embedded between two valid units was
silently dropped and the string accepted with a wrong value instead of
being reported as invalid.
Examples that were wrongly accepted before this change:
- `1h 2x 3m` → 3780s (parsed as 1h3m)
- `1h_2m` → 3720s
- `1h,1m` → 3660s
All three now return an "invalid time string" error, while valid inputs
such as `1h 1m 1s` and `1h1m1s` keep working.
### How
Reject any non-whitespace content between two matched units.
---------
Signed-off-by: TowyTowy <towy@airreps.link>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes#38217
## Problem
Turnstile CAPTCHA verification uses `http.DefaultClient`, so the request
to `challenges.cloudflare.com` bypasses Gitea's configured HTTP proxy —
unlike other outbound HTTP clients such as the update checker
(`modules/updatechecker/update_checker.go`) and migrations. In
deployments where egress is only permitted through the configured proxy,
verification fails.
## Fix
Build the client with `proxy.Proxy()` as the transport proxy, mirroring
the update checker:
```go
func httpClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: proxy.Proxy(),
},
}
}
```
The client is built per call (rather than a package-level var) because
`proxy.Proxy()` reads `setting.Proxy` when invoked; building it at
request time ensures it reflects the loaded settings. When no proxy is
configured, behavior is unchanged (`proxy.Proxy()` returns a no-op /
`http.ProxyFromEnvironment`).
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
- **Locale DoS:** the `Locale` middleware passed the raw
`Accept-Language` header to `ParseAcceptLanguage`, whose guard only
counts `-` while the scanner aliases `_` to `-` — a large `_`-separated
header on an unauthenticated request burned CPU. The header is now
length-bounded before parsing.
- **Public-only token scope:** `GET /teams/{id}/repos`,
`.../repos/{org}/{repo}`, `/teams/{id}/activities/feeds`, and
`/users/{username}/orgs/{org}/permissions` still returned private
repo/activity/permission data to a public-only token. They now filter
via `TokenCanAccessRepo` / `ApplyPublicOnly` and reject non-public org
permissions.
- **Push-option visibility:** `repo.private` / `repo.template` push
options were applied to any existing repo, letting an owner/admin
silently flip visibility bypassing audit, webhooks, and notifications.
They are now honored only on push-to-create.
Committer can also be co-author, it should only not be included in the
co-author list if it is not in the "Co-author-by" list.
* Author & Co-author: they changed the code (attribution)
* Committer: they submitted the commit but didn't change the code (e.g.:
maintainer signed a commit)
Fix#38384
The commit page built its "co-authored by" list from
`AllParticipantIdentities()[1:]`, which only drops the author and leaves
the committer in the list even though the committer is already shown
separately as "committed by". This caused the committer to be wrongly
rendered as a co-author (issue #38384): a commit authored by
`silverwind`, committed by `bircni`, with a `Co-authored-by: silverwind`
trailer displayed "co-authored by bircni" instead of the actual trailer
identity. This adds a `Commit.CoAuthorIdentities()` method that excludes
both the author and the committer, uses it on the commit page, and
covers the fix with a regression test.
Closes#38384