mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-17 20:27:10 +02:00
Merge remote-tracking branch 'origin/main' into fix/action-dashboard-index-created-unix
# Conflicts: # models/migrations/migrations.go # models/migrations/v1_27/v338.go # models/migrations/v1_27/v338_test.go
This commit is contained in:
commit
030f56d56e
@ -2,12 +2,17 @@
|
||||
|
||||
This document explains how to contribute changes to the Gitea project. Topic-specific guides live in separate files so the essentials are easier to find.
|
||||
|
||||
| Topic | Document |
|
||||
| :---- | :------- |
|
||||
| Backend (Go modules, API v1) | [docs/guideline-backend.md](docs/guideline-backend.md) |
|
||||
| Frontend (npm, UI guidelines) | [docs/guideline-frontend.md](docs/guideline-frontend.md) |
|
||||
| Maintainers, TOC, labels, merge queue, commit format for mergers | [docs/community-governance.md](docs/community-governance.md) |
|
||||
| Release cycle, backports, tagging releases | [docs/release-management.md](docs/release-management.md) |
|
||||
| Topic | Document |
|
||||
|:-----------------------|:-----------------------------------------------------------------|
|
||||
| Setup and requirements | [docs/build-setup.md](docs/build-setup.md) |
|
||||
| Development workflow | [docs/development.md](docs/development.md) |
|
||||
| Build from source | [docs/build-source.md](docs/build-source.md) |
|
||||
| Running the tests | [docs/testing.md](docs/testing.md) |
|
||||
| Frontend guidelines | [docs/guidelines-frontend.md](docs/guidelines-frontend.md) |
|
||||
| Backend guidelines | [docs/guidelines-backend.md](docs/guidelines-backend.md) |
|
||||
| Refactoring | [docs/guidelines-refactoring.md](docs/guidelines-refactoring.md) |
|
||||
| Community Governance | [docs/community-governance.md](docs/community-governance.md) |
|
||||
| Release management | [docs/release-management.md](docs/release-management.md) |
|
||||
|
||||
<details><summary>Table of Contents</summary>
|
||||
|
||||
@ -43,7 +48,7 @@ This document explains how to contribute changes to the Gitea project. Topic-spe
|
||||
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \
|
||||
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
|
||||
|
||||
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
|
||||
For configuring IDEs for Gitea development, see the [IDE setup notes](docs/development.md#ide-configuration) and the [contributed configurations](contrib/development/).
|
||||
|
||||
## AI Contribution Policy
|
||||
|
||||
@ -106,7 +111,8 @@ If further discussion is needed, we encourage you to open a new issue instead an
|
||||
|
||||
## Building Gitea
|
||||
|
||||
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
|
||||
See [docs/setup.md](docs/setup.md) for prerequisites and [docs/development.md](docs/development.md)
|
||||
for building Gitea and the development workflow.
|
||||
|
||||
## Styleguide
|
||||
|
||||
@ -125,33 +131,7 @@ Afterwards, copyright should only be modified when the copyright author changes.
|
||||
|
||||
## Testing
|
||||
|
||||
Before submitting a pull request, run all tests to make sure your changes don't cause a regression elsewhere.
|
||||
|
||||
Here's how to run the test suite:
|
||||
|
||||
- code lint
|
||||
|
||||
| | |
|
||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
||||
|``make lint-frontend`` | lint frontend files |
|
||||
|``make lint-backend`` | lint backend files |
|
||||
|
||||
- run tests (we suggest running them on Linux)
|
||||
|
||||
| Command | Action | |
|
||||
|:----------------------------------------------|:-----------------------------------------------------| ------------------------------------------- |
|
||||
| ``make test-backend[\#SpecificTestName]`` | run unit test(s) | |
|
||||
| ``make test-integration[\#SpecificTestName]`` | run [integration](tests/integration) test(s) | [More details](tests/integration/README.md) |
|
||||
| ``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | |
|
||||
|
||||
- E2E test environment variables
|
||||
|
||||
| Variable | Description |
|
||||
| :-------------------------------- | :---------------------------------------------------------- |
|
||||
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
|
||||
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
|
||||
| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 4 on CI, 1 locally) |
|
||||
Before submitting a pull request, run the linters (`make lint`, or the scoped `make lint-backend` / `make lint-frontend`) and the tests to make sure your changes don't cause a regression elsewhere. See [docs/testing.md](docs/testing.md) for how to run the unit, integration, end-to-end, and migration tests.
|
||||
|
||||
## Translation
|
||||
|
||||
|
||||
53
README.md
53
README.md
@ -14,21 +14,21 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
The goal of this project is to make the easiest, fastest, and most
|
||||
painless way of setting up a self-hosted Git service.
|
||||
The goal of Gitea is to make the easiest, fastest, and most painless way of
|
||||
setting up a self-hosted all-in-one software development service,
|
||||
including Git hosting, code management, code review, issue tracking, project kanban, wiki,
|
||||
team collaboration, package registry and CI/CD which can reuse GitHub Actions.
|
||||
|
||||
As Gitea is written in Go, it works across **all** the platforms and
|
||||
architectures that are supported by Go, including Linux, macOS, and
|
||||
Windows on x86, amd64, ARM and PowerPC architectures.
|
||||
This project has been
|
||||
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
||||
architectures that are supported by Go, including Linux, macOS, FreeBSD/OpenBSD and Windows
|
||||
on x86, amd64, ARM, RISC-V 64 and PowerPC architectures.
|
||||
|
||||
For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com).
|
||||
|
||||
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
||||
|
||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com),
|
||||
or use container (docker/podman/etc) to deploy on your own server with the [official image](https://hub.docker.com/r/gitea/gitea).
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -40,27 +40,12 @@ If you have any suggestions or would like to contribute to it, you can visit the
|
||||
|
||||
## Building
|
||||
|
||||
From the root of the source tree, run:
|
||||
See [docs/build-setup.md](docs/build-setup.md) for prerequisites
|
||||
and [docs/development.md](docs/development.md) for setting up a local development environment, linting, and testing.
|
||||
|
||||
TAGS="bindata" make build
|
||||
If you'd like to build from source or make a distribution package, see [docs/build-source.md](docs/build-source.md) for more information.
|
||||
|
||||
The `build` target is split into two sub-targets:
|
||||
|
||||
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
|
||||
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation).
|
||||
|
||||
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.
|
||||
|
||||
More info: https://docs.gitea.com/installation/install-from-source
|
||||
|
||||
## Using
|
||||
|
||||
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
|
||||
|
||||
./gitea web
|
||||
|
||||
> [!NOTE]
|
||||
> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api).
|
||||
After building, you can run `./gitea web` to start the server, or `./gitea help` to see all available commands.
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -69,7 +54,8 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
||||
> [!NOTE]
|
||||
>
|
||||
> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
|
||||
> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
||||
> 2. New to the codebase? The [development guide](docs/development.md) walks through setting up a local environment and building from source.
|
||||
> 3. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
||||
|
||||
## Translating
|
||||
|
||||
@ -126,14 +112,19 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
||||
|
||||
Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
|
||||
|
||||
**Why is this not hosted on a Gitea instance?**
|
||||
**How do I configure Gitea?**
|
||||
|
||||
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||
For dynamic config options, you can change it on your admin panel's configuration section.
|
||||
|
||||
For static config options, you can edit your `app.ini` file and resart the instance.
|
||||
See [app.example.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) or [configuration documentation](https://docs.gitea.com/administration/config-cheat-sheet) for more details.
|
||||
|
||||
**Where can I find the security patches?**
|
||||
|
||||
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
|
||||
|
||||
(more FAQs are listed in [FAQ documentation](https://docs.gitea.com/help/faq))
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
@ -143,7 +134,7 @@ for the full license text.
|
||||
## Further information
|
||||
|
||||
<details>
|
||||
<summary>Looking for an overview of the interface? Check it out!</summary>
|
||||
<summary>Looking for an overview of the interface? Check it out the screenshots!</summary>
|
||||
|
||||
### Login/Register Page
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
# IDE and code editor configuration
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [IDE and code editor configuration](#ide-and-code-editor-configuration)
|
||||
- [Microsoft Visual Studio Code](#microsoft-visual-studio-code)
|
||||
|
||||
## Microsoft Visual Studio Code
|
||||
|
||||
Download Microsoft Visual Studio Code at https://code.visualstudio.com/ and follow instructions at https://code.visualstudio.com/docs/languages/go to setup Go extension for it.
|
||||
|
||||
Create new directory `.vscode` in Gitea root folder and copy contents of folder [contrib/ide/vscode](vscode/) to it. You can now use `Ctrl`+`Shift`+`B` to build gitea executable and `F5` to run it in debug mode.
|
||||
Create new directory `.vscode` in Gitea root folder and copy contents of folder [contrib/development/vscode](vscode/) to it. You can now use `Ctrl`+`Shift`+`B` to build gitea executable and `F5` to run it in debug mode.
|
||||
|
||||
Supported on Debian, Ubuntu, Red Hat, Fedora, SUSE Linux, MacOS and Microsoft Windows.
|
||||
|
||||
67
docs/build-setup.md
Normal file
67
docs/build-setup.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Setup and requirements
|
||||
|
||||
This document lists the tools you need to build Gitea from source and how to get
|
||||
the code. Once your environment is ready, see [development.md](development.md) for
|
||||
the build and development workflow, and [testing.md](testing.md) for running tests.
|
||||
|
||||
For the contribution workflow and review process, see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Requirements
|
||||
|
||||
### Go
|
||||
|
||||
[Install Go](https://go.dev/doc/install) and set up your Go environment. The
|
||||
required version is the one declared in [`go.mod`](../go.mod); installing the same
|
||||
version your continuous integration uses avoids `gofmt` differences between Go
|
||||
releases.
|
||||
|
||||
> [!NOTE]
|
||||
> Some `make` tasks build external Go tools on demand (for example `make
|
||||
> watch-backend`). To use them, the `"$GOPATH"/bin` directory must be on your
|
||||
> executable `PATH`; otherwise you have to manage those tools yourself.
|
||||
|
||||
### Node.js and pnpm
|
||||
|
||||
[Install Node.js](https://nodejs.org/en/download/) to build the JavaScript and CSS
|
||||
files. The minimum supported version is the one declared in
|
||||
[`package.json`](../package.json) (`engines.node`); the latest LTS is recommended.
|
||||
|
||||
Gitea manages frontend dependencies with [pnpm](https://pnpm.io/). The `make`
|
||||
targets invoke it for you, so installing pnpm manually is only needed if you want
|
||||
to run `pnpm` commands directly.
|
||||
|
||||
### Make
|
||||
|
||||
Gitea uses [Make](https://www.gnu.org/software/make/) to drive builds, linting, and
|
||||
tests. On Windows it can be installed via [MSYS2](https://www.msys2.org/) or
|
||||
[Chocolatey](https://chocolatey.org/packages/make).
|
||||
|
||||
### Python with uv (optional)
|
||||
|
||||
Linting the templates, workflow files, and YAML requires Python tooling that Gitea
|
||||
runs through [uv](https://docs.astral.sh/uv/). After installing uv, `make` creates
|
||||
the environment automatically (`uv sync`); you only need this if you run
|
||||
`make lint-templates`, `make lint-yaml`, or `make lint-actions` locally.
|
||||
|
||||
### Git LFS
|
||||
|
||||
The integration tests require [Git LFS](https://git-lfs.com/) to be installed.
|
||||
|
||||
## Getting the source code
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/go-gitea/gitea
|
||||
```
|
||||
|
||||
To contribute changes, [fork the repository](https://github.com/go-gitea/gitea) on
|
||||
GitHub and add your fork as a git remote so you can push branches and open pull
|
||||
requests. See GitHub's [working with forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks)
|
||||
documentation for the details.
|
||||
|
||||
## Installing dependencies
|
||||
|
||||
Most build and test targets install the dependencies they need on their own. To
|
||||
fetch everything up front, run `make deps` (or the per-group `make deps-frontend`,
|
||||
`make deps-backend`, `make deps-tools`, `make deps-py`).
|
||||
92
docs/build-source.md
Normal file
92
docs/build-source.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Prepare build environment
|
||||
|
||||
Complete the steps in [build-setup.md](build-setup.md) to prepare your environment for building Gitea from source.
|
||||
|
||||
## Choose a branch
|
||||
|
||||
By default, the cloned repository is on main branch (the current development branch for next major release, aka: main nightly).
|
||||
|
||||
You can switch to a versioned branch (the branch for the next minor stable release, aka: stable nightly )
|
||||
or a versioned tag (matches the official releases with version numbers)
|
||||
|
||||
To test a Pull Request, you can fetch its code by its Pull Request number (take `PR #123456` as example):
|
||||
|
||||
```bash
|
||||
git fetch origin pull/123456/head:pr-123456
|
||||
```
|
||||
|
||||
# Build
|
||||
|
||||
Various [make tasks](https://github.com/go-gitea/gitea/blob/main/Makefile)
|
||||
are provided to keep the build process as simple as possible.
|
||||
|
||||
Depending on requirements, the following build tags can be included.
|
||||
|
||||
- `bindata`: Build a single monolithic binary, with all assets included. Required for distribution and production build.
|
||||
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules).
|
||||
Can be used to authenticate local users or extend authentication to methods available to PAM.
|
||||
- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands.
|
||||
|
||||
To include all assets, use the `bindata` tag:
|
||||
|
||||
```bash
|
||||
TAGS="bindata" make build
|
||||
```
|
||||
|
||||
Tag `gogit` is used to try to resolve some Windows-specific performance problems, POSIX systems don't need it.
|
||||
You can build a Windows binary by:
|
||||
|
||||
```bash
|
||||
GOOS=windows TAGS="bindata gogit" make build
|
||||
```
|
||||
|
||||
## Changing default paths
|
||||
|
||||
Gitea will search for a number of things from the _`CustomPath`_.
|
||||
By default, this is the `custom/` directory in the current working directory when running Gitea.
|
||||
It will also look for its configuration file _`CustomConf`_ in `$(CustomPath)/conf/app.ini`,
|
||||
and will use the current working directory as the relative base path _`AppWorkPath`_.
|
||||
|
||||
These values, although useful when developing, may conflict with downstream users preferences.
|
||||
|
||||
For packagers who need to use paths like `/etc/gitea/app.ini`,
|
||||
they should define these values at build time for `make build` by environment variable like
|
||||
`LDFLAGS='-X "module.Var1=Value1" -X "module.Var2=Value2"' TAGS="bindata" make build`.
|
||||
|
||||
- _`CustomConf`_: `-X "code.gitea.io/gitea/modules/setting.CustomConf=/etc/gitea/app.ini"`
|
||||
- _`AppWorkPath`_: `-X "code.gitea.io/gitea/modules/setting.AppWorkPath=/var/lib/gitea"`
|
||||
- _`CustomPath`_: `-X "code.gitea.io/gitea/modules/setting.CustomPath=/var/lib/gitea/custom"`
|
||||
- Default PID file location: `-X "code.gitea.io/gitea/cmd.PIDFile=/run/gitea.pid"`
|
||||
|
||||
Add as many of the strings with their preceding `-X` to the `LDFLAGS` variable and run `make build`
|
||||
with the appropriate `TAGS` as above.
|
||||
|
||||
Running `gitea help` will allow you to review what the computed settings will be for your `gitea`.
|
||||
|
||||
## Cross Build
|
||||
|
||||
Gitea use's Golang's toolchain variables for cross-building.
|
||||
|
||||
For example, to cross build for Linux ARM64:
|
||||
|
||||
```
|
||||
GOOS=linux GOARCH=arm64 TAGS="bindata" make build
|
||||
```
|
||||
|
||||
### Adding shell autocompletion
|
||||
|
||||
Shell completion can be generated directly from binary with:
|
||||
```sh
|
||||
gitea completion <shell>
|
||||
```
|
||||
|
||||
Supported values for `<shell>` are `bash`, `fish`, `pwsh` and `zsh`.
|
||||
Details on how to load the completion for your shell can be found in the completion command help.
|
||||
|
||||
## Source Maps
|
||||
|
||||
By default, gitea generates reduced source maps for frontend files to conserve space. This can be controlled with the `ENABLE_SOURCEMAP` environment variable:
|
||||
|
||||
- `ENABLE_SOURCEMAP=true` generates all source maps, the default for development builds
|
||||
- `ENABLE_SOURCEMAP=reduced` generates limited source maps, the default for production builds
|
||||
- `ENABLE_SOURCEMAP=false` generates no source maps
|
||||
141
docs/development.md
Normal file
141
docs/development.md
Normal file
@ -0,0 +1,141 @@
|
||||
# Development
|
||||
|
||||
This document describes how to build Gitea from source and the day-to-day
|
||||
development workflow. For prerequisites and how to obtain the code, see
|
||||
[build-setup.md](build-setup.md). For running tests, see [testing.md](testing.md). For the
|
||||
contribution workflow and review process, see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
Area-specific guidelines:
|
||||
|
||||
- [Backend development guidelines](guidelines-backend.md)
|
||||
- [Frontend development guidelines](guidelines-frontend.md)
|
||||
- [Refactoring guidelines](guidelines-refactoring.md)
|
||||
|
||||
## Building
|
||||
|
||||
To build Gitea for development, run:
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
No build tags are required: SQLite support is compiled in by default, which is
|
||||
enough for local development. The `build` target runs two sub-targets, `frontend`
|
||||
and `backend`. The `bindata` tag embeds the frontend assets into the binary and is
|
||||
only needed when packaging a self-contained build, so leave it out during
|
||||
development.
|
||||
|
||||
See `make help` for all available targets, and the workflows in
|
||||
[`.github/workflows`](https://github.com/go-gitea/gitea/tree/main/.github/workflows)
|
||||
to see how continuous integration builds and checks Gitea.
|
||||
|
||||
## Building continuously
|
||||
|
||||
To rebuild automatically when source files change:
|
||||
|
||||
```bash
|
||||
# watch both frontend and backend
|
||||
make watch
|
||||
|
||||
# or watch only the frontend (starts the Vite dev server)
|
||||
make watch-frontend
|
||||
|
||||
# or watch only the backend (Go)
|
||||
make watch-backend
|
||||
```
|
||||
|
||||
Watching all backend source files may hit the default open-files limit on macOS or
|
||||
Linux; raise it with `ulimit -n 12288` for the current shell, or in your shell
|
||||
startup file to make it permanent.
|
||||
|
||||
## Formatting, linting and checks
|
||||
|
||||
Continuous integration rejects pull requests that fail formatting, linting, or
|
||||
consistency checks. Format your code first:
|
||||
|
||||
```bash
|
||||
make fmt
|
||||
```
|
||||
|
||||
Then lint:
|
||||
|
||||
```bash
|
||||
# lint everything
|
||||
make lint
|
||||
# or only one side
|
||||
make lint-backend
|
||||
make lint-frontend
|
||||
```
|
||||
|
||||
Many linters can fix issues automatically with `make lint-fix` (or the scoped
|
||||
`make lint-backend-fix` / `make lint-frontend-fix`). The combined consistency
|
||||
checks that CI runs are available as `make checks`.
|
||||
|
||||
## Building and adding SVGs
|
||||
|
||||
SVG icons are built with `make svg`, which compiles the icon sources into
|
||||
`public/assets/img/svg`. Custom icons can be added under `web_src/svg`.
|
||||
|
||||
## Updating the API
|
||||
|
||||
When you create or change API routes, you **must** update the
|
||||
[Swagger](https://swagger.io/docs/specification/2-0/what-is-swagger/) documentation
|
||||
using [go-swagger](https://goswagger.io/) comments. See the
|
||||
[backend development guidelines](guidelines-backend.md) for how API routes,
|
||||
request/response structs, and swagger definitions fit together.
|
||||
|
||||
Regenerate and validate the spec after changing an endpoint, then commit the
|
||||
updated JSON:
|
||||
|
||||
```bash
|
||||
make generate-swagger
|
||||
make swagger-validate
|
||||
```
|
||||
|
||||
CI verifies the committed spec is up to date with:
|
||||
|
||||
```bash
|
||||
make swagger-check
|
||||
```
|
||||
|
||||
## Creating new configuration options
|
||||
|
||||
When adding configuration options it is not enough to add them to the
|
||||
`modules/setting` files. Also update
|
||||
[`custom/conf/app.example.ini`](../custom/conf/app.example.ini), and document them in
|
||||
the [configuration cheat sheet](https://docs.gitea.com/administration/config-cheat-sheet),
|
||||
which lives in the [documentation repository](https://gitea.com/gitea/docs).
|
||||
|
||||
## Database migrations
|
||||
|
||||
If you make breaking changes to a database-persisted struct under `models/`, add a
|
||||
new migration in `models/migrations/`. See [testing.md](testing.md#migration-tests)
|
||||
for running the migration tests.
|
||||
|
||||
## Testing
|
||||
|
||||
For unit, integration, end-to-end, and migration tests, see [testing.md](testing.md).
|
||||
|
||||
## IDE configuration
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
A `launch.json` and `tasks.json` are provided in
|
||||
[`contrib/development/vscode`](../contrib/development/vscode). See
|
||||
[`contrib/development/README.md`](../contrib/development/README.md) for details.
|
||||
|
||||
### GoLand
|
||||
|
||||
Clicking the `Run Application` arrow on `func main()` in `/main.go` starts a
|
||||
debuggable Gitea instance.
|
||||
|
||||
The `Output Directory` in `Run/Debug Configuration` **must** be set to the Gitea
|
||||
project directory (the one containing `main.go` and `go.mod`). Otherwise the working
|
||||
directory is a GoLand temporary directory, which prevents Gitea from loading dynamic
|
||||
resources (such as templates) in development.
|
||||
|
||||
## Submitting your changes
|
||||
|
||||
Push your branch and open a pull request. See [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||
for the review process and PR requirements. For help, join the `#Develop` channel on
|
||||
[Discord](https://discord.gg/gitea).
|
||||
@ -1,63 +0,0 @@
|
||||
# Backend development
|
||||
|
||||
This document covers backend-specific contribution expectations. For general contribution workflow, see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
For coding style and architecture, see also the [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend) on the documentation site.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \
|
||||
You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules).
|
||||
|
||||
Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \
|
||||
Apart from that, these files should only be modified by Pull Requests whose only purpose is to update dependencies.
|
||||
|
||||
The `go.mod`, `go.sum` update needs to be justified as part of the PR description,
|
||||
and must be verified by the reviewers and/or merger to always reference
|
||||
an existing upstream commit.
|
||||
|
||||
## API v1
|
||||
|
||||
The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
|
||||
|
||||
### GitHub API compatibility
|
||||
|
||||
Gitea's API should use the same endpoints and fields as the GitHub API as far as possible, unless there are good reasons to deviate. \
|
||||
If Gitea provides functionality that GitHub does not, a new endpoint can be created. \
|
||||
If information is provided by Gitea that is not provided by the GitHub API, a new field can be used that doesn't collide with any GitHub fields. \
|
||||
Updating an existing API should not remove existing fields unless there is a really good reason to do so. \
|
||||
The same applies to status responses. If you notice a problem, feel free to leave a comment in the code for future refactoring to API v2 (which is currently not planned).
|
||||
|
||||
### Adding/Maintaining API routes
|
||||
|
||||
All expected results (errors, success, fail messages) must be documented ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L319-L327)). \
|
||||
All JSON input types must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L76-L91)) \
|
||||
and referenced in [routers/api/v1/swagger/options.go](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/options.go). \
|
||||
They can then be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L318). \
|
||||
All JSON responses must be defined as a struct in [modules/structs/](modules/structs/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/modules/structs/issue.go#L36-L68)) \
|
||||
and referenced in its category in [routers/api/v1/swagger/](routers/api/v1/swagger/) ([example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/swagger/issue.go#L11-L16)) \
|
||||
They can be used like [this example](https://github.com/go-gitea/gitea/blob/c620eb5b2d0d874da68ebd734d3864c5224f71f7/routers/api/v1/repo/issue.go#L277-L279).
|
||||
|
||||
### When to use what HTTP method
|
||||
|
||||
In general, HTTP methods are chosen as follows:
|
||||
|
||||
- **GET** endpoints return the requested object(s) and status **OK (200)**
|
||||
- **DELETE** endpoints return the status **No Content (204)** and no content either
|
||||
- **POST** endpoints are used to **create** new objects (e.g. a User) and return the status **Created (201)** and the created object
|
||||
- **PUT** endpoints are used to **add/assign** existing Objects (e.g. a user to a team) and return the status **No Content (204)** and no content either
|
||||
- **PATCH** endpoints are used to **edit/change** an existing object and return the changed object and the status **OK (200)**
|
||||
|
||||
### Requirements for API routes
|
||||
|
||||
All parameters of endpoints changing/editing an object must be optional (except the ones to identify the object, which are required).
|
||||
|
||||
Endpoints returning lists must
|
||||
|
||||
- support pagination (`page` & `limit` options in query)
|
||||
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
|
||||
|
||||
### Knowledge
|
||||
|
||||
- Partially database table migration must use `SyncWithOptions(IgnoreDrop...)`
|
||||
- Template variables with "camelCase" or "snake_case" are used for restoring the form values from a submitted form
|
||||
@ -1,17 +0,0 @@
|
||||
# Frontend development
|
||||
|
||||
This document covers frontend-specific contribution expectations. For general contribution workflow, see [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Dependencies
|
||||
|
||||
For the frontend, we use [npm](https://www.npmjs.com/).
|
||||
|
||||
The same restrictions apply for frontend dependencies as for [backend dependencies](guideline-backend.md#dependencies), with the exceptions that the files for it are `package.json` and `package-lock.json`, and that new versions must always reference an existing version.
|
||||
|
||||
## Design guideline
|
||||
|
||||
Depending on your change, please read the
|
||||
|
||||
- [backend development guideline](https://docs.gitea.com/contributing/guidelines-backend)
|
||||
- [frontend development guideline](https://docs.gitea.com/contributing/guidelines-frontend)
|
||||
- [refactoring guideline](https://docs.gitea.com/contributing/guidelines-refactoring)
|
||||
132
docs/guidelines-backend.md
Normal file
132
docs/guidelines-backend.md
Normal file
@ -0,0 +1,132 @@
|
||||
# Backend development guidelines
|
||||
|
||||
This document covers backend-specific architecture and contribution expectations.
|
||||
For the general workflow see [CONTRIBUTING.md](../CONTRIBUTING.md), and for building
|
||||
and testing see [development.md](development.md) and [testing.md](testing.md).
|
||||
|
||||
## Background
|
||||
|
||||
The backend is written in Go. Web routing is handled by
|
||||
[chi](https://github.com/go-chi/chi) and database access goes through the
|
||||
[XORM](https://xorm.io/) ORM. Understanding how the packages depend on each other is
|
||||
essential before contributing backend code.
|
||||
|
||||
## Package design
|
||||
|
||||
### Package layout
|
||||
|
||||
The backend is split into top-level packages, each with a focused responsibility:
|
||||
|
||||
- `build`: helper scripts used at compile time
|
||||
- `cmd`: subcommands such as `web`, `serv`, `hooks`, `doctor`, and admin utilities
|
||||
- `models`: data structures and database operations (XORM); keeps external
|
||||
dependencies to a minimum
|
||||
- `models/db`: core database operations
|
||||
- `models/fixtures`: sample data used by tests
|
||||
- `models/migrations`: schema migration scripts
|
||||
- `modules`: standalone functionality with few dependencies
|
||||
- `modules/setting`: configuration handling
|
||||
- `modules/git`: interaction with the Git command line
|
||||
- `routers`: request handlers, split into `api`, `web`, `install`, and `private`
|
||||
- `services`: business logic that ties routers and models together
|
||||
- `templates`: Go HTML templates
|
||||
- `public`: compiled frontend assets
|
||||
- `tests`: integration and end-to-end test helpers
|
||||
|
||||
### Dependency direction
|
||||
|
||||
Dependencies only flow in one direction:
|
||||
|
||||
```text
|
||||
cmd → routers → services → models → modules
|
||||
```
|
||||
|
||||
A package on the left may import a package on its right, but never the reverse.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- Top-level packages use the plural form: `services`, `models`, `routers`.
|
||||
- Subpackages use the singular form: `services/user`, `models/repository`.
|
||||
|
||||
When packages from different layers share a name, use a snake_case import alias to
|
||||
disambiguate:
|
||||
|
||||
```go
|
||||
import user_service "gitea.dev/services/user"
|
||||
```
|
||||
|
||||
### Database transactions
|
||||
|
||||
Operations that must roll back together should run inside `db.WithTx()` (or
|
||||
`db.WithTx2()` when a value must be returned), defined in `models/db/context.go`.
|
||||
Functions that participate in a transaction take a `context.Context` as their first
|
||||
parameter so the transaction can be propagated.
|
||||
|
||||
### XORM gotchas
|
||||
|
||||
- Never call `x.Update(exemplar)` without an explicit `WHERE` clause — it updates
|
||||
every row in the table.
|
||||
- Partial table migrations must use `SyncWithOptions(IgnoreDrop...)` rather than a
|
||||
plain `Sync`.
|
||||
- When inserting rows with preset IDs, MSSQL requires `SET IDENTITY_INSERT` to be
|
||||
enabled and PostgreSQL requires the sequence to be updated afterwards.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Go dependencies are managed with [Go Modules](https://go.dev/ref/mod).
|
||||
|
||||
Pull requests should only modify `go.mod` and `go.sum` where it relates to the
|
||||
change at hand, be it a bug fix or a new feature. Otherwise, these files should only
|
||||
be touched by pull requests whose sole purpose is updating dependencies. Run
|
||||
`make tidy` after any change to `go.mod`.
|
||||
|
||||
Any `go.mod` / `go.sum` update must be justified in the PR description and must be
|
||||
verified by reviewers and the merger to reference an existing upstream commit.
|
||||
|
||||
## API v1
|
||||
|
||||
The API is documented with [Swagger](https://gitea.com/api/swagger) and is modelled
|
||||
on [the GitHub API](https://docs.github.com/en/rest).
|
||||
|
||||
### GitHub API compatibility
|
||||
|
||||
Gitea's API should use the same endpoints and fields as the GitHub API where
|
||||
possible, unless there is a good reason to deviate.
|
||||
|
||||
- If Gitea offers functionality GitHub does not, a new endpoint may be added.
|
||||
- If Gitea exposes information the GitHub API does not, a new field may be added as
|
||||
long as it does not collide with a GitHub field.
|
||||
- Existing fields should not be removed unless there is a strong reason; the same
|
||||
applies to status responses.
|
||||
|
||||
If you notice a problem that would require a breaking change, leave a comment in the
|
||||
code for a future refactor to API v2 (which is currently not planned) rather than
|
||||
breaking v1.
|
||||
|
||||
### Adding and maintaining API routes
|
||||
|
||||
- All possible results (errors, success, and failure messages) must be documented in
|
||||
the swagger comments on the route.
|
||||
- Every JSON request body must be defined as a struct in `modules/structs/` and
|
||||
registered in [`routers/api/v1/swagger/options.go`](../routers/api/v1/swagger/options.go).
|
||||
- Every JSON response must be defined as a struct in `modules/structs/` and
|
||||
registered with its category under [`routers/api/v1/swagger/`](../routers/api/v1/swagger).
|
||||
|
||||
### HTTP methods and status codes
|
||||
|
||||
In general, choose HTTP methods as follows:
|
||||
|
||||
- **GET** returns the requested object(s) with status **200 OK**.
|
||||
- **POST** creates a new object (e.g. a user) and returns **201 Created** with the
|
||||
created object.
|
||||
- **PUT** adds or assigns an existing object (e.g. a user to a team) and returns
|
||||
**204 No Content** with no body.
|
||||
- **PATCH** edits an existing object and returns the changed object with **200 OK**.
|
||||
- **DELETE** removes an object and returns **204 No Content** with no body.
|
||||
|
||||
### Requirements for API routes
|
||||
|
||||
- All parameters of endpoints that edit an object must be optional, except those
|
||||
needed to identify the object, which are required.
|
||||
- Endpoints returning lists must support pagination (`page` and `limit` query
|
||||
options) and set the `X-Total-Count` header via `ctx.SetTotalCountHeader(...)`.
|
||||
98
docs/guidelines-frontend.md
Normal file
98
docs/guidelines-frontend.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Frontend development guidelines
|
||||
|
||||
This document covers frontend-specific architecture and contribution expectations.
|
||||
For the general workflow see [CONTRIBUTING.md](../CONTRIBUTING.md), and for building
|
||||
and testing see [development.md](development.md) and [testing.md](testing.md).
|
||||
|
||||
## Background
|
||||
|
||||
The frontend uses [Vue 3](https://vuejs.org/), [Fomantic-UI](https://fomantic-ui.com/) (built on jQuery)
|
||||
and [Tailwind CSS](https://tailwindcss.com/). Pages are rendered with Go HTML templates.
|
||||
Source files live in:
|
||||
|
||||
- `web_src/css/`: CSS styles
|
||||
- `web_src/js/`: JavaScript and TypeScript
|
||||
- `web_src/js/components/`: Vue components
|
||||
- `web_src/js/features/`: feature modules wired up at page load
|
||||
- `templates/`: Go HTML templates
|
||||
|
||||
## Dependencies
|
||||
|
||||
Frontend dependencies are managed with [pnpm](https://pnpm.io/). The same rules as
|
||||
for [backend dependencies](guidelines-backend.md#dependencies) apply, except the
|
||||
relevant files are `package.json` and `pnpm-lock.yaml`, and new versions must always
|
||||
reference an existing published version.
|
||||
|
||||
## Framework usage
|
||||
|
||||
Mixing frameworks arbitrarily makes code hard to maintain. Recommended combinations:
|
||||
|
||||
- Vue3
|
||||
- Vanilla JavaScript
|
||||
- Fomantic-UI (jQuery), deprecated, we vendored a specific version with a lot of changes.
|
||||
|
||||
Avoid combinations such as Vue with Fomantic-UI.
|
||||
Vue components may reuse Fomantic-UI CSS classes for visual consistency.
|
||||
Use Go templates for simple or SEO-relevant pages and Vue for complex, interactive pages.
|
||||
Gitea uses Vue 3 **without** JSX to keep HTML and JavaScript separate.
|
||||
|
||||
> [!NOTE]
|
||||
> Fomantic-UI is not an accessibility-friendly framework. Gitea patches some ARIA
|
||||
> behavior, but accessibility work is ongoing — prefer semantic HTML and test
|
||||
> keyboard/screen-reader behavior where you can.
|
||||
|
||||
## Gitea-specific conventions
|
||||
|
||||
- Keep features in their own files or directories.
|
||||
- Use kebab-case for HTML `id`s and classes, ideally with 2-3 feature keywords.
|
||||
- Prefix classes to avoid short-name conflicts between different frameworks.
|
||||
- Create a new class name when overriding framework styles instead of editing the framework's own classes,
|
||||
or fix the framework's source to fix all cases.
|
||||
- Prefer semantic elements such as `<button>` over generic `<div>`s.
|
||||
- Avoid `!important`; when it is unavoidable, document why.
|
||||
- Prefix custom DOM events with `ce-`.
|
||||
|
||||
## CSS
|
||||
|
||||
Prefer Tailwind utility classes with the `tw-` prefix, and the `flex-*` layout
|
||||
helpers over per-child margins. Gitea also ships a small set of custom helpers:
|
||||
`gt-` for general helpers and `g-` for framework-level helpers (see
|
||||
`web_src/css/helpers.css`); use these only when a Tailwind utility does not exist.
|
||||
|
||||
Write class attributes as a single readable unit in templates:
|
||||
|
||||
```html
|
||||
<div class="flex-text-inline {{if .IsFoo}}tw-hidden{{end}}"></div>
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
- Use `import type` for type-only imports.
|
||||
- Prefer `@ts-expect-error` over `@ts-ignore`.
|
||||
- Use the `!` non-null assertion (rather than `?.`/`??`) when a value is known to always exist.
|
||||
- Only mark a function `async` when it actually uses `await` or returns a `Promise`.
|
||||
Avoid async event listeners; if unavoidable, call `e.preventDefault()` before the
|
||||
first `await`. For a deliberately un-awaited call, assign it: `const _promise = asyncFoo()`.
|
||||
|
||||
## Data fetching
|
||||
|
||||
Use the `GET`, `POST`, `PUT`, `PATCH`, and `DELETE` wrappers from
|
||||
[`web_src/js/modules/fetch.ts`](../web_src/js/modules/fetch.ts).
|
||||
|
||||
## DOM attributes
|
||||
|
||||
Avoid `node.dataset` because of its camel-casing behavior; use `node.getAttribute`
|
||||
in new code. Never bind user-provided data directly onto DOM nodes.
|
||||
|
||||
## Showing and hiding elements
|
||||
|
||||
- In Vue, use `v-if` and `v-show`.
|
||||
- In Go templates and plain JavaScript, use the `.tw-hidden` class together with the
|
||||
`showElem()`, `hideElem()`, and `toggleElem()` helpers from
|
||||
[`web_src/js/utils/dom.ts`](../web_src/js/utils/dom.ts).
|
||||
|
||||
## UI component gallery
|
||||
|
||||
When running Gitea in development mode, standardized UI components are available at
|
||||
`/devtest` (for example `http://localhost:3000/devtest`). These pages are also used
|
||||
by the e2e tests.
|
||||
38
docs/guidelines-refactoring.md
Normal file
38
docs/guidelines-refactoring.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Refactoring guidelines
|
||||
|
||||
This document covers expectations for refactoring work. For the general workflow see
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Background
|
||||
|
||||
Gitea is a large, long-lived project. Over time the codebase has accumulated
|
||||
outdated mechanisms, mixed frameworks, and legacy code that can cause bugs or slow
|
||||
down new features. Refactoring keeps the codebase maintainable, but it needs to be
|
||||
done carefully so it improves things without introducing regressions.
|
||||
|
||||
## Writing a refactoring PR
|
||||
|
||||
- Be forward-looking: address the root cause, not just the immediate symptom.
|
||||
- Aim to reduce ambiguity and conflicts and to improve maintainability.
|
||||
- Explain the rationale in the PR description: why the refactor is necessary, how it
|
||||
resolves the legacy problem, and its advantages and disadvantages.
|
||||
- Keep the scope tight: preserve existing behavior where feasible and avoid bundling
|
||||
unrelated changes.
|
||||
- Break large refactors into intermediate steps across multiple PRs so each one is
|
||||
easy to review.
|
||||
- Include tests that verify the behavior stays correct.
|
||||
- Prefer scheduling non-bugfix refactoring early in a milestone, so any issues
|
||||
surface well before a release.
|
||||
- If there is disagreement about a refactor, escalate to the Technical Oversight
|
||||
Committee (TOC) for a decision.
|
||||
|
||||
## Reviewing and merging
|
||||
|
||||
- Keep refactoring PRs short-lived (typically no more than 7 days) with quick review
|
||||
cycles, and merge them promptly so they do not block on unrelated work.
|
||||
- A non-author core member may approve and merge a refactoring PR after 7 days if the
|
||||
TOC has raised no objection.
|
||||
- Accept imperfect intermediate implementations as long as the final result improves
|
||||
the codebase.
|
||||
- A temporary regression caused by a necessary refactor is acceptable if it is fixed
|
||||
promptly afterwards.
|
||||
151
docs/testing.md
Normal file
151
docs/testing.md
Normal file
@ -0,0 +1,151 @@
|
||||
# Testing
|
||||
|
||||
Gitea has four kinds of automated tests: backend unit tests, integration tests,
|
||||
end-to-end (e2e) tests, and migration tests. Local runs default to SQLite, so no
|
||||
extra services are required to get started.
|
||||
|
||||
For prerequisites see [build-setup.md](build-setup.md); for the build workflow see
|
||||
[development.md](development.md).
|
||||
|
||||
## Unit tests
|
||||
|
||||
Backend unit tests live in `*_test.go` files next to the code they cover. Set
|
||||
`GITEA_TEST_LOG_SQL=1` to log all SQL statements executed during the tests.
|
||||
|
||||
```bash
|
||||
make test-backend
|
||||
```
|
||||
|
||||
To run a single backend test, use `go test` directly or the `#` selector:
|
||||
|
||||
```bash
|
||||
go test -run '^TestName$' ./modulepath/
|
||||
make test-backend#TestName
|
||||
```
|
||||
|
||||
Frontend unit tests run with [Vitest](https://vitest.dev/):
|
||||
|
||||
```bash
|
||||
make test-frontend
|
||||
# single file:
|
||||
pnpm exec vitest <path-filter>
|
||||
```
|
||||
|
||||
## Integration tests
|
||||
|
||||
Integration tests exercise Gitea against a real database. They live in
|
||||
`tests/integration/` and require [Git LFS](https://git-lfs.com/) to be installed.
|
||||
The database is selected with `GITEA_TEST_DATABASE`; an empty value defaults to
|
||||
SQLite, which needs no external service:
|
||||
|
||||
```bash
|
||||
make test-integration
|
||||
```
|
||||
|
||||
Run a single integration test with the `#` selector:
|
||||
|
||||
```bash
|
||||
make test-integration#TestName
|
||||
```
|
||||
|
||||
If you hit errors such as a mismatched database version or SSH push failures, try a
|
||||
clean rebuild first:
|
||||
|
||||
```bash
|
||||
make clean build
|
||||
```
|
||||
|
||||
### Running against other databases
|
||||
|
||||
Set `GITEA_TEST_DATABASE` together with the matching `TEST_*` connection variables.
|
||||
The commands below start a throwaway database container (press `Ctrl-C` to stop and
|
||||
remove it) and then run the tests against it.
|
||||
|
||||
#### MySQL
|
||||
|
||||
```bash
|
||||
docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:latest
|
||||
```
|
||||
|
||||
```bash
|
||||
GITEA_TEST_DATABASE=mysql TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-integration
|
||||
```
|
||||
|
||||
#### PostgreSQL
|
||||
|
||||
PostgreSQL tests also use a MinIO container for object storage:
|
||||
|
||||
```bash
|
||||
docker run -e "POSTGRES_DB=test" -e "POSTGRES_USER=postgres" -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 --rm --name pgsql postgres:latest
|
||||
docker run --rm -p 9000:9000 -e MINIO_ROOT_USER=123456 -e MINIO_ROOT_PASSWORD=12345678 --name minio bitnamilegacy/minio:2023.8.31
|
||||
```
|
||||
|
||||
```bash
|
||||
GITEA_TEST_DATABASE=pgsql TEST_MINIO_ENDPOINT=localhost:9000 TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=postgres TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-integration
|
||||
```
|
||||
|
||||
#### MSSQL
|
||||
|
||||
```bash
|
||||
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest
|
||||
```
|
||||
|
||||
```bash
|
||||
GITEA_TEST_DATABASE=mssql TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-integration
|
||||
```
|
||||
|
||||
### Running the database test workflow with Gitea Runner
|
||||
|
||||
The CI database test jobs can be run locally with
|
||||
[Gitea Runner](https://gitea.com/gitea/runner). Running every job is
|
||||
resource-intensive and not recommended:
|
||||
|
||||
```bash
|
||||
gitea-runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest
|
||||
```
|
||||
|
||||
List the available job names, then run a single one:
|
||||
|
||||
```bash
|
||||
gitea-runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -l
|
||||
gitea-runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -j <job_name>
|
||||
```
|
||||
|
||||
## End-to-end tests
|
||||
|
||||
End-to-end tests drive a running Gitea instance with [Playwright](https://playwright.dev/):
|
||||
|
||||
```bash
|
||||
make test-e2e
|
||||
```
|
||||
|
||||
To run a single e2e test file, pass it via `GITEA_TEST_E2E_FLAGS`:
|
||||
|
||||
```bash
|
||||
GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e
|
||||
```
|
||||
|
||||
Useful environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
| :--- | :--- |
|
||||
| `GITEA_TEST_E2E_DEBUG` | When set, show the Gitea server output. |
|
||||
| `GITEA_TEST_E2E_FLAGS` | Additional flags passed to Playwright, e.g. `--ui`. |
|
||||
| `GITEA_TEST_E2E_TIMEOUT_FACTOR` | Timeout multiplier (default: 4 on CI, 1 locally). |
|
||||
|
||||
## Migration tests
|
||||
|
||||
If you change a database-persisted struct under `models/` you will usually need a
|
||||
new migration in `models/migrations/`. Run the migration tests with:
|
||||
|
||||
```bash
|
||||
make test-migration
|
||||
```
|
||||
|
||||
## Continuous integration
|
||||
|
||||
CI runs the unit tests, runs the integration tests against every supported database,
|
||||
and tests migration from several recent Gitea versions. Please submit your pull
|
||||
request with additional unit and integration tests as appropriate. Prefer unit tests
|
||||
when the logic can be tested in isolation, and keep local integration and e2e tests
|
||||
fast (aim for sub-2s runtime).
|
||||
@ -415,7 +415,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(335, "Add reusable workflow fields and action_run_attempt_job_id_index table for ActionRunJob", v1_27.AddReusableWorkflowFieldsToActionRunJob),
|
||||
newMigration(336, "Add ActionRunJobSummary table", v1_27.AddActionRunJobSummaryTable),
|
||||
newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam),
|
||||
newMigration(338, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL),
|
||||
newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@ -4,37 +4,70 @@
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/migrations/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// AddCreatedUnixToActionUserIsDeletedIndex extends the c_u composite index on
|
||||
// the action table to include created_unix, enabling efficient ORDER BY on the
|
||||
// dashboard feed query without a full sort of all matching rows.
|
||||
func AddCreatedUnixToActionUserIsDeletedIndex(x db.EngineMigration) error {
|
||||
// xorm Sync cannot reliably update an index when another index already
|
||||
// covers the same columns in a different order (Equal() is order-insensitive).
|
||||
// Drop the old c_u index explicitly, then recreate it with the new column set.
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
type issueWithLongTextContent struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
}
|
||||
|
||||
func (issueWithLongTextContent) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
type commentWithLongTextFields struct {
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
PatchQuoted string `xorm:"LONGTEXT patch"`
|
||||
}
|
||||
|
||||
func (commentWithLongTextFields) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func isMSSQLMaxTextColumn(column *schemas.Column) bool {
|
||||
if column.Length != -1 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(column.SQLType.Name, schemas.Varchar) || strings.EqualFold(column.SQLType.Name, schemas.NVarchar)
|
||||
}
|
||||
|
||||
func modifyLongTextColumnsForMSSQL(x db.EngineMigration, bean any, columnNames ...string) error {
|
||||
table, err := x.TableInfo(bean)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx.Name == "c_u" {
|
||||
if _, err := x.Exec(x.Dialect().DropIndexSQL("action", idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
|
||||
for _, columnName := range columnNames {
|
||||
column := table.GetColumn(columnName)
|
||||
if column == nil {
|
||||
return fmt.Errorf("column %s does not exist in table %s", columnName, table.Name)
|
||||
}
|
||||
if isMSSQLMaxTextColumn(column) {
|
||||
continue
|
||||
}
|
||||
if err := base.ModifyColumn(x, table.Name, column); err != nil {
|
||||
return fmt.Errorf("modify %s.%s: %w", table.Name, columnName, err)
|
||||
}
|
||||
}
|
||||
|
||||
newIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
newIndex.AddColumn("user_id", "is_deleted", "created_unix")
|
||||
if _, err := x.Exec(x.Dialect().CreateIndexSQL("action", newIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpandIssueAndCommentLongTextFieldsForMSSQL expands legacy MSSQL nvarchar(4000)
|
||||
// columns to nvarchar(max) so PR push comments and long issue content are not truncated.
|
||||
func ExpandIssueAndCommentLongTextFieldsForMSSQL(x db.EngineMigration) error {
|
||||
if x.Dialect().URI().DBType != schemas.MSSQL {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := modifyLongTextColumnsForMSSQL(x, new(issueWithLongTextContent), "content"); err != nil {
|
||||
return err
|
||||
}
|
||||
return modifyLongTextColumnsForMSSQL(x, new(commentWithLongTextFields), "content", "patch")
|
||||
}
|
||||
|
||||
@ -4,69 +4,49 @@
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionBeforeV338 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
type issueBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
}
|
||||
|
||||
func (actionBeforeV338) TableName() string { return "action" }
|
||||
|
||||
func (actionBeforeV338) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
|
||||
// old 2-column index, before the migration
|
||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
cuIndex.AddColumn("user_id", "is_deleted")
|
||||
|
||||
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||
func (issueBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "issue"
|
||||
}
|
||||
|
||||
func Test_AddCreatedUnixToActionUserIsDeletedIndex(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(actionBeforeV338))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
type commentBeforeLongTextMSSQLMigration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"VARCHAR(4000)"`
|
||||
Patch string `xorm:"VARCHAR(4000) patch"`
|
||||
}
|
||||
|
||||
func (commentBeforeLongTextMSSQLMigration) TableName() string {
|
||||
return "comment"
|
||||
}
|
||||
|
||||
func Test_ExpandIssueAndCommentLongTextFieldsForMSSQL(t *testing.T) {
|
||||
if !setting.Database.Type.IsMSSQL() {
|
||||
t.Skip("Only MSSQL needs to expand legacy nvarchar(4000) long-text columns")
|
||||
}
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old c_u index should exist before migration")
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new c_u index should not exist before migration")
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(issueBeforeLongTextMSSQLMigration), new(commentBeforeLongTextMSSQLMigration))
|
||||
defer deferrable()
|
||||
|
||||
require.NoError(t, AddCreatedUnixToActionUserIsDeletedIndex(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
require.NoError(t, ExpandIssueAndCommentLongTextFieldsForMSSQL(x))
|
||||
|
||||
indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
longText := strings.Repeat("x", 5000)
|
||||
_, err := x.Insert(&issueBeforeLongTextMSSQLMigration{Content: longText})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = x.Insert(&commentBeforeLongTextMSSQLMigration{Content: longText, Patch: longText})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old 2-column c_u index should be gone after migration")
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new 3-column c_u index must exist after migration")
|
||||
}
|
||||
|
||||
40
models/migrations/v1_27/v339.go
Normal file
40
models/migrations/v1_27/v339.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// AddCreatedUnixToActionUserIsDeletedIndex extends the c_u composite index on
|
||||
// the action table to include created_unix, enabling efficient ORDER BY on the
|
||||
// dashboard feed query without a full sort of all matching rows.
|
||||
func AddCreatedUnixToActionUserIsDeletedIndex(x db.EngineMigration) error {
|
||||
// xorm Sync cannot reliably update an index when another index already
|
||||
// covers the same columns in a different order (Equal() is order-insensitive).
|
||||
// Drop the old c_u index explicitly, then recreate it with the new column set.
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if idx.Name == "c_u" {
|
||||
if _, err := x.Exec(x.Dialect().DropIndexSQL("action", idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
newIndex.AddColumn("user_id", "is_deleted", "created_unix")
|
||||
if _, err := x.Exec(x.Dialect().CreateIndexSQL("action", newIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
72
models/migrations/v1_27/v339_test.go
Normal file
72
models/migrations/v1_27/v339_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_27
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/migrations/migrationtest"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type actionBeforeV339 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
RepoID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
func (actionBeforeV339) TableName() string { return "action" }
|
||||
|
||||
func (actionBeforeV339) TableIndices() []*schemas.Index {
|
||||
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||
|
||||
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||
|
||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||
|
||||
// old 2-column index, before the migration
|
||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||
cuIndex.AddColumn("user_id", "is_deleted")
|
||||
|
||||
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||
|
||||
return []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||
}
|
||||
|
||||
func Test_AddCreatedUnixToActionUserIsDeletedIndex(t *testing.T) {
|
||||
x, deferable := migrationtest.PrepareTestEnv(t, 0, new(actionBeforeV339))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
indexes, err := x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old c_u index should exist before migration")
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new c_u index should not exist before migration")
|
||||
|
||||
require.NoError(t, AddCreatedUnixToActionUserIsDeletedIndex(x))
|
||||
|
||||
indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), "action")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted"}, false), "old 2-column c_u index should be gone after migration")
|
||||
assert.True(t, hasIndexWithColumns(indexes, []string{"user_id", "is_deleted", "created_unix"}, false), "new 3-column c_u index must exist after migration")
|
||||
}
|
||||
@ -121,6 +121,9 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
|
||||
}
|
||||
|
||||
cmd := gitcmd.NewCommand().AddArguments("clone")
|
||||
// Never follow HTTP redirects: no clone caller needs them, and a remote redirecting to an
|
||||
// otherwise-blocked address would be an SSRF vector (e.g. migrating from an attacker URL).
|
||||
cmd.AddArguments("-c", "http.followRedirects=false")
|
||||
if opts.SkipTLSVerify {
|
||||
cmd.AddArguments("-c", "http.sslVerify=false")
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -19,3 +22,23 @@ func TestRepoIsEmpty(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isEmpty)
|
||||
}
|
||||
|
||||
// TestCloneRefusesRedirects ensures Clone never follows HTTP redirects, so a remote
|
||||
// cannot redirect to an otherwise-blocked address (SSRF, e.g. during migration).
|
||||
func TestCloneRefusesRedirects(t *testing.T) {
|
||||
var targetHit atomic.Bool
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
targetHit.Store(true)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
redirect := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, target.URL+r.URL.Path, http.StatusFound)
|
||||
}))
|
||||
defer redirect.Close()
|
||||
|
||||
err := Clone(t.Context(), redirect.URL, filepath.Join(t.TempDir(), "dst"), CloneRepoOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, targetHit.Load(), "git must not follow the redirect to the target")
|
||||
}
|
||||
|
||||
@ -2865,6 +2865,14 @@
|
||||
"org.teams.all_repositories_read_permission_desc": "此团队授予<strong>读取</strong><strong>所有仓库</strong>的访问权限: 成员可以查看和克隆仓库。",
|
||||
"org.teams.all_repositories_write_permission_desc": "此团队授予<strong>修改</strong><strong>所有仓库</strong>的访问权限: 成员可以查看和推送至仓库。",
|
||||
"org.teams.all_repositories_admin_permission_desc": "该团队拥有 <strong>管理</strong> <strong>所有仓库</strong>的权限:团队成员可以读取、克隆、推送以及添加其它仓库协作者。",
|
||||
"org.teams.visibility": "可见性",
|
||||
"org.teams.visibility_private": "私有",
|
||||
"org.teams.visibility_private_helper": "仅对团队成员和组织所有者可见。",
|
||||
"org.teams.visibility_limited": "受限",
|
||||
"org.teams.visibility_limited_helper": "对组织所有成员可见。",
|
||||
"org.teams.visibility_public": "公开",
|
||||
"org.teams.visibility_public_helper": "对任何登录用户可见。",
|
||||
"org.teams.owners_visibility_fixed": "所有者的团队可见性无法更改。",
|
||||
"org.teams.invite.title": "您已被邀请加入组织 <strong>%s</strong> 中的团队 <strong>%s</strong>。",
|
||||
"org.teams.invite.by": "邀请人 %s",
|
||||
"org.teams.invite.description": "请点击下面的按钮加入团队。",
|
||||
|
||||
@ -15,6 +15,9 @@ import (
|
||||
|
||||
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
||||
if !checkRepoFeedTokenScope(ctx) {
|
||||
return
|
||||
}
|
||||
var commits []*git.Commit
|
||||
var err error
|
||||
if ctx.Repo.Commit != nil {
|
||||
|
||||
@ -16,6 +16,9 @@ import (
|
||||
|
||||
// ShowFileFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||
func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
||||
if !checkRepoFeedTokenScope(ctx) {
|
||||
return
|
||||
}
|
||||
fileName := ctx.Repo.TreePath
|
||||
if len(fileName) == 0 {
|
||||
return
|
||||
|
||||
@ -15,6 +15,9 @@ import (
|
||||
|
||||
// shows tags and/or releases on the repo as RSS / Atom feed
|
||||
func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) {
|
||||
if !checkRepoFeedTokenScope(ctx) {
|
||||
return
|
||||
}
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
IncludeTags: !isReleasesOnly,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
|
||||
@ -4,9 +4,18 @@
|
||||
package feed
|
||||
|
||||
import (
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// checkRepoFeedTokenScope ensures an API token has repository read scope before a
|
||||
// feed serves private repository content, mirroring checkDownloadTokenScope for
|
||||
// downloads. Returns false (and writes the response) when the token is denied.
|
||||
func checkRepoFeedTokenScope(ctx *context.Context) bool {
|
||||
context.CheckRepoScopedToken(ctx, ctx.Repo.Repository, auth_model.Read)
|
||||
return !ctx.Written()
|
||||
}
|
||||
|
||||
// RenderBranchFeed render format for branch or file
|
||||
func RenderBranchFeed(ctx *context.Context, feedType string) {
|
||||
if ctx.Repo.TreePath == "" {
|
||||
|
||||
@ -16,6 +16,9 @@ import (
|
||||
|
||||
// ShowRepoFeed shows user activity on the repo as RSS / Atom feed
|
||||
func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType string) {
|
||||
if !checkRepoFeedTokenScope(ctx) {
|
||||
return
|
||||
}
|
||||
actions, _, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
|
||||
RequestedRepo: repo,
|
||||
Actor: ctx.Doer,
|
||||
|
||||
@ -24,19 +24,22 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
|
||||
}
|
||||
|
||||
// since user only get notifications when he has access to use minimal access mode
|
||||
if n.Repository != nil {
|
||||
perm, err := access_model.GetIndividualUserRepoPermission(ctx, n.Repository, n.User)
|
||||
if err != nil {
|
||||
log.Error("GetIndividualUserRepoPermission failed: %v", err)
|
||||
return result
|
||||
}
|
||||
if perm.HasAnyUnitAccessOrPublicAccess() { // if user has been revoked access to repo, do not show repo info
|
||||
result.Repository = ToRepo(ctx, n.Repository, perm)
|
||||
// This permission is not correct and we should not be reporting it
|
||||
for repository := result.Repository; repository != nil; repository = repository.Parent {
|
||||
repository.Permissions = nil
|
||||
}
|
||||
}
|
||||
if n.Repository == nil {
|
||||
return result
|
||||
}
|
||||
perm, err := access_model.GetIndividualUserRepoPermission(ctx, n.Repository, n.User)
|
||||
if err != nil {
|
||||
log.Error("GetIndividualUserRepoPermission failed: %v", err)
|
||||
return result
|
||||
}
|
||||
// if the user has been revoked access to the repo, do not leak repo or subject info
|
||||
if !perm.HasAnyUnitAccessOrPublicAccess() {
|
||||
return result
|
||||
}
|
||||
result.Repository = ToRepo(ctx, n.Repository, perm)
|
||||
// This permission is not correct and we should not be reporting it
|
||||
for repository := result.Repository; repository != nil; repository = repository.Parent {
|
||||
repository.Permissions = nil
|
||||
}
|
||||
|
||||
// handle Subject
|
||||
|
||||
@ -39,6 +39,36 @@ func TestToNotificationThreadOmitsRepoWhenAccessRevoked(t *testing.T) {
|
||||
assert.Nil(t, thread.Repository)
|
||||
}
|
||||
|
||||
func TestToNotificationThreadOmitsSubjectWhenAccessRevoked(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx := t.Context()
|
||||
// repo 2 is private; user 4 has no access to it
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
assert.NoError(t, repo.LoadOwner(ctx))
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4, RepoID: repo.ID})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
n := &activities_model.Notification{
|
||||
ID: 12345,
|
||||
UserID: user.ID,
|
||||
RepoID: repo.ID,
|
||||
Status: activities_model.NotificationStatusUnread,
|
||||
Source: activities_model.NotificationSourceIssue,
|
||||
IssueID: issue.ID,
|
||||
UpdatedUnix: timeutil.TimeStampNow(),
|
||||
Issue: issue,
|
||||
Repository: repo,
|
||||
User: user,
|
||||
}
|
||||
|
||||
thread := ToNotificationThread(ctx, n)
|
||||
|
||||
// must not leak private issue metadata once access is revoked
|
||||
assert.Nil(t, thread.Repository)
|
||||
assert.Nil(t, thread.Subject)
|
||||
}
|
||||
|
||||
func TestToNotificationThread(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
# Integration tests
|
||||
|
||||
Integration tests can be run with command `make test-integration`.
|
||||
Environment variable `GITEA_TEST_DATABASE` can be used to specify the database type for testing.
|
||||
|
||||
If you encounter some errors like mismatched database version, SSH push errors, etc.,
|
||||
you can try to perform a clean build by: `make clean build`.
|
||||
|
||||
## Run sqlite integration tests
|
||||
|
||||
Start tests directly (empty `GITEA_TEST_DATABASE` defaults to sqlite):
|
||||
|
||||
```
|
||||
make test-integration
|
||||
```
|
||||
|
||||
## Run MySQL integration tests
|
||||
|
||||
Set up a MySQL database inside docker:
|
||||
|
||||
```
|
||||
docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:latest #(just ctrl-c to stop db and clean the container)
|
||||
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" --rm --name elasticsearch elasticsearch:7.6.0 #(in a second terminal, just ctrl-c to stop db and clean the container)
|
||||
```
|
||||
|
||||
Start tests based on the database container:
|
||||
|
||||
```
|
||||
GITEA_TEST_DATABASE=mysql TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-integration
|
||||
```
|
||||
|
||||
## Run pgsql integration tests
|
||||
|
||||
Set up a pgsql database inside docker:
|
||||
|
||||
```
|
||||
docker run -e "POSTGRES_DB=test" -e "POSTGRES_USER=postgres" -e "POSTGRES_PASSWORD=postgres" -p 5432:5432 --rm --name pgsql postgres:latest #(just ctrl-c to stop db and clean the container)
|
||||
```
|
||||
|
||||
Set up minio inside docker:
|
||||
|
||||
```
|
||||
docker run --rm -p 9000:9000 -e MINIO_ROOT_USER=123456 -e MINIO_ROOT_PASSWORD=12345678 --name minio bitnamilegacy/minio:2023.8.31
|
||||
```
|
||||
|
||||
Start tests based on the database container:
|
||||
|
||||
```
|
||||
GITEA_TEST_DATABASE=pgsql TEST_MINIO_ENDPOINT=localhost:9000 TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=postgres TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-integration
|
||||
```
|
||||
|
||||
## Run mssql integration tests
|
||||
|
||||
Set up a mssql database inside docker:
|
||||
|
||||
```
|
||||
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container)
|
||||
```
|
||||
|
||||
Start tests based on the database container:
|
||||
|
||||
```
|
||||
GITEA_TEST_DATABASE=mssql TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-integration
|
||||
```
|
||||
|
||||
## Running individual tests
|
||||
|
||||
Example command to run GPG test:
|
||||
|
||||
```
|
||||
GITEA_TEST_DATABASE=... make test-integration#GPG
|
||||
```
|
||||
|
||||
## Run Gitea Actions tests via local act_runner
|
||||
|
||||
### Run all jobs
|
||||
|
||||
```
|
||||
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest
|
||||
```
|
||||
|
||||
Warning: This file defines many jobs, so it will be resource-intensive and therefore not recommended.
|
||||
|
||||
### Run single job
|
||||
|
||||
```SHELL
|
||||
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -j <job_name>
|
||||
```
|
||||
|
||||
You can list all job names via:
|
||||
|
||||
```SHELL
|
||||
act_runner exec -W ./.github/workflows/pull-db-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest -l
|
||||
```
|
||||
@ -50,14 +50,8 @@ func TestAPIUpdateOrgAvatar(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
// Test what happens if you use a file that is not an image
|
||||
text, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/README.md"))
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to open README.md")
|
||||
}
|
||||
|
||||
opts = api.UpdateUserAvatarOption{
|
||||
Image: base64.StdEncoding.EncodeToString(text),
|
||||
Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")),
|
||||
}
|
||||
|
||||
req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts).
|
||||
|
||||
@ -54,14 +54,8 @@ func TestAPIUpdateRepoAvatar(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
// Test what happens if you use a file that is not an image
|
||||
text, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/README.md"))
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to open README.md")
|
||||
}
|
||||
|
||||
opts = api.UpdateRepoAvatarOption{
|
||||
Image: base64.StdEncoding.EncodeToString(text),
|
||||
Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")),
|
||||
}
|
||||
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts).
|
||||
|
||||
@ -50,14 +50,8 @@ func TestAPIUpdateUserAvatar(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
// Test what happens if you use a file that is not an image
|
||||
text, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/README.md"))
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Unable to open README.md")
|
||||
}
|
||||
|
||||
opts = api.UpdateUserAvatarOption{
|
||||
Image: base64.StdEncoding.EncodeToString(text),
|
||||
Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")),
|
||||
}
|
||||
|
||||
req = NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts).
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -33,3 +34,41 @@ func TestFeedRepo(t *testing.T) {
|
||||
assert.NotEmpty(t, rss.Channel.Items[0].PubDate)
|
||||
})
|
||||
}
|
||||
|
||||
// TestFeedRepoContentTokenScopes ensures repository feed endpoints enforce the
|
||||
// repository token scope, so a PAT without repository scope cannot read private
|
||||
// repository commit/activity data through RSS/Atom feeds.
|
||||
func TestFeedRepoContentTokenScopes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// user2/repo2 is a private repository owned by user2
|
||||
ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)
|
||||
miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc)
|
||||
|
||||
urls := []string{
|
||||
"/user2/repo2.rss",
|
||||
"/user2/repo2.atom",
|
||||
"/user2/repo2/rss/branch/master",
|
||||
"/user2/repo2/atom/branch/master",
|
||||
"/user2/repo2/rss/branch/master/README.md",
|
||||
"/user2/repo2/tags.rss",
|
||||
"/user2/repo2/tags.atom",
|
||||
"/user2/repo2/releases.rss",
|
||||
"/user2/repo2/releases.atom",
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
t.Run(url, func(t *testing.T) {
|
||||
// feed routes only accept basic auth, so authenticate as the advisory PoC does (user:token)
|
||||
reqDenied := NewRequest(t, "GET", url)
|
||||
reqDenied.SetBasicAuth("user2", miscToken)
|
||||
// a token without repository scope must be denied
|
||||
MakeRequest(t, reqDenied, http.StatusForbidden)
|
||||
|
||||
reqAllowed := NewRequest(t, "GET", url)
|
||||
reqAllowed.SetBasicAuth("user2", ownerReadToken)
|
||||
// a token with repository read scope is allowed
|
||||
MakeRequest(t, reqAllowed, http.StatusOK)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user