Merge branch 'main' into lunny/refactor_review_request

pull/35337/head
Lunny Xiao 2025-09-04 21:34:40 +07:00
commit 766f7fc4d3
145 changed files with 13559 additions and 15709 deletions

@ -1,6 +1,6 @@
{
"name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
"containerEnv": {
// override "local" from packaged version
"GOTOOLCHAIN": "auto"
@ -8,12 +8,12 @@
"features": {
// installs nodejs into container
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
"version": "latest"
},
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
"version": "3.13"
},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
},

@ -65,6 +65,7 @@ cpu.out
/yarn.lock
/yarn-error.log
/npm-debug.log*
/pnpm-debug.log*
/public/assets/js
/public/assets/css
/public/assets/fonts

@ -59,7 +59,7 @@ modifies/dependencies:
- changed-files:
- any-glob-to-any-file:
- "package.json"
- "package-lock.json"
- "pnpm-lock.yaml"
- "pyproject.toml"
- "uv.lock"
- "go.mod"

@ -58,7 +58,7 @@ jobs:
- "tools/*.ts"
- "assets/emoji.json"
- "package.json"
- "package-lock.json"
- "pnpm-lock.yaml"
- "Makefile"
- ".eslintrc.cjs"
- ".npmrc"
@ -67,7 +67,7 @@ jobs:
- "**/*.md"
- ".markdownlint.yaml"
- "package.json"
- "package-lock.json"
- "pnpm-lock.yaml"
actions:
- ".github/workflows/*"
@ -90,7 +90,7 @@ jobs:
- "templates/swagger/v1_input.json"
- "Makefile"
- "package.json"
- "package-lock.json"
- "pnpm-lock.yaml"
- ".spectral.yaml"
yaml:

@ -34,11 +34,12 @@ jobs:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv python install 3.12
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-py
- run: make deps-frontend
- run: make lint-templates
@ -60,11 +61,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make lint-swagger
@ -131,11 +133,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make lint-frontend
- run: make checks-frontend
@ -180,11 +183,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make lint-md

@ -31,7 +31,7 @@ jobs:
minio:
# as github actions doesn't support "entrypoint", we need to use a non-official image
# that has a custom entrypoint set to "minio server /data"
image: bitnami/minio:2023.8.31
image: bitnamilegacy/minio:2023.8.31
env:
MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678
@ -113,7 +113,7 @@ jobs:
ports:
- 6379:6379
minio:
image: bitnami/minio:2021.3.17
image: bitnamilegacy/minio:2021.3.17
env:
MINIO_ACCESS_KEY: 123456
MINIO_SECRET_KEY: 12345678
@ -155,7 +155,7 @@ jobs:
services:
mysql:
# the bitnami mysql image has more options than the official one, it's easier to customize
image: bitnami/mysql:8.0
image: bitnamilegacy/mysql:8.0
env:
ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: testgitea

@ -23,13 +23,14 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend frontend deps-backend
- run: npx playwright install --with-deps
- run: pnpm exec playwright install --with-deps
- run: make test-e2e-sqlite
timeout-minutes: 40
env:

@ -20,11 +20,12 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release

@ -21,11 +21,12 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release

@ -25,11 +25,12 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release

1
.gitignore vendored

@ -74,6 +74,7 @@ cpu.out
/tests/*.ini
/tests/**/*.git/**/*.sample
/node_modules
/tools/node_modules
/.venv
/yarn.lock
/yarn-error.log

@ -1,6 +1,7 @@
audit=false
fund=false
update-notifier=false
package-lock=true
save-exact=true
lockfile-version=3
auto-install-peers=true
dedupe-peer-dependents=false
enable-pre-post-scripts=true

@ -15,6 +15,7 @@ RUN apk --no-cache add \
git \
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo

@ -15,6 +15,7 @@ RUN apk --no-cache add \
git \
nodejs \
npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/*
# Setup repo

@ -29,7 +29,7 @@ AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
@ -218,15 +218,19 @@ node-check:
$(eval MIN_NODE_VERSION_STR := $(shell grep -Eo '"node":.*[0-9.]+"' package.json | sed -n 's/.*[^0-9.]\([0-9.]*\)"/\1/p'))
$(eval MIN_NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell echo '$(MIN_NODE_VERSION_STR)' | tr '.' ' ')))
$(eval NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v | cut -c2- | tr '.' ' ');))
$(eval NPM_MISSING := $(shell hash npm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater and npm to build. You can get it at https://nodejs.org/en/download/"; \
$(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; then \
echo "Gitea requires Node.js $(MIN_NODE_VERSION_STR) or greater to build. You can get it at https://nodejs.org/en/download/"; \
exit 1; \
fi
@if [ "$(PNPM_MISSING)" = "1" ]; then \
echo "Gitea requires pnpm to build. You can install it at https://pnpm.io/installation"; \
exit 1; \
fi
.PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules tools/node_modules
.PHONY: clean
clean: ## delete backend and integration files
@ -334,29 +338,29 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen
.PHONY: lint-js
lint-js: node_modules ## lint js files
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
npx vue-tsc
pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
pnpm exec vue-tsc
.PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
npx vue-tsc
pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
pnpm exec vue-tsc
.PHONY: lint-css
lint-css: node_modules ## lint css files
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files
npx spectral lint -q -F hint $(SWAGGER_SPEC)
pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md
lint-md: node_modules ## lint markdown files
npx markdownlint *.md
pnpm exec markdownlint *.md
.PHONY: lint-spell
lint-spell: ## lint spelling
@ -417,7 +421,7 @@ watch: ## watch everything and continuously rebuild
.PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development npx webpack --watch --progress
NODE_ENV=development pnpm exec webpack --watch --progress
.PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild
@ -433,7 +437,7 @@ test-backend: ## test backend files
.PHONY: test-frontend
test-frontend: node_modules ## test frontend files
npx vitest
pnpm exec vitest
.PHONY: test-check
test-check:
@ -576,7 +580,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
playwright: deps-frontend
npx playwright install $(PLAYWRIGHT_FLAGS)
pnpm exec playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e%
test-e2e%: TEST_TYPE ?= e2e
@ -839,10 +843,14 @@ deps-tools: ## install tool dependencies
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait
node_modules: package-lock.json
npm install --no-save
node_modules: pnpm-lock.yaml
pnpm install --frozen-lockfile
@touch node_modules
tools/node_modules: tools/package.json
cd tools && pnpm install
@touch tools/node_modules
.venv: uv.lock
uv sync
@touch .venv
@ -852,16 +860,16 @@ update: update-js update-py ## update js and py dependencies
.PHONY: update-js
update-js: node-check | node_modules ## update js dependencies
npx updates -u -f package.json
rm -rf node_modules package-lock.json
npm install --package-lock
npx nolyfill install
npm install --package-lock
pnpm exec updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml
pnpm install
pnpm exec nolyfill install
pnpm install
@touch node_modules
.PHONY: update-py
update-py: node-check | node_modules ## update py dependencies
npx updates -u -f pyproject.toml
pnpm exec updates -u -f pyproject.toml
rm -rf .venv uv.lock
uv sync
@touch .venv
@ -869,11 +877,11 @@ update-py: node-check | node_modules ## update py dependencies
.PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml
@$(MAKE) -s node-check node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
@BROWSERSLIST_IGNORE_OLD_DATA=true pnpm exec webpack
@touch $(WEBPACK_DEST)
.PHONY: svg
@ -893,11 +901,11 @@ svg-check: svg
.PHONY: lockfile-check
lockfile-check:
npm install --package-lock-only
@diff=$$(git diff --color=always package-lock.json); \
pnpm install --frozen-lockfile
@diff=$$(git diff --color=always pnpm-lock.yaml); \
if [ -n "$$diff" ]; then \
echo "package-lock.json is inconsistent with package.json"; \
echo "Please run 'npm install --package-lock-only' and commit the result:"; \
echo "pnpm-lock.yaml is inconsistent with package.json"; \
echo "Please run 'pnpm install --frozen-lockfile' and commit the result:"; \
printf "%s" "$${diff}"; \
exit 1; \
fi
@ -917,9 +925,8 @@ generate-gitignore: ## update gitignore files
$(GO) run build/generate-gitignores.go
.PHONY: generate-images
generate-images: | node_modules
npm install --no-save fabric@6 imagemin-zopfli@7
node tools/generate-images.js $(TAGS)
generate-images: | node_modules tools/node_modules ## generate images (requires cairo development packages)
cd tools && node generate-images.js $(TAGS)
.PHONY: generate-manpage
generate-manpage: ## generate manpage

@ -52,7 +52,7 @@ or if SQLite support is required:
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.
- `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.

File diff suppressed because one or more lines are too long

@ -20,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session"
"github.com/mholt/archiver/v3"
"github.com/urfave/cli/v3"
)
@ -146,22 +145,18 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
return err
}
archiverGeneric, err := archiver.ByExtension("." + outType)
dumper, err := dump.NewDumper(ctx, outType, outFile)
if err != nil {
fatal("Unable to get archiver for extension: %v", err)
}
archiverWriter := archiverGeneric.(archiver.Writer)
if err := archiverWriter.Create(outFile); err != nil {
fatal("Creating archiver.Writer failed: %v", err)
}
defer archiverWriter.Close()
dumper := &dump.Dumper{
Writer: archiverWriter,
Verbose: verbose,
fatal("Failed to create archive %q: %v", outFile, err)
return err
}
dumper.Verbose = verbose
dumper.GlobalExcludeAbsPath(outFileName)
defer func() {
if err := dumper.Close(); err != nil {
fatal("Failed to save archive %q: %v", outFileName, err)
}
}()
if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") {
log.Info("Skip dumping local repositories")
@ -180,7 +175,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
return dumper.AddFileByReader(object, info, path.Join("data", "lfs", objPath))
}); err != nil {
fatal("Failed to dump LFS objects: %v", err)
}
@ -218,13 +213,13 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to dump database: %v", err)
}
if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
if err = dumper.AddFileByPath("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
}
}
log.Info("Adding custom configuration file from %s", setting.CustomConf)
if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
if err = dumper.AddFileByPath("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
@ -283,7 +278,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
return dumper.AddFileByReader(object, info, path.Join("data", "attachments", objPath))
}); err != nil {
fatal("Failed to dump attachments: %v", err)
}
@ -297,7 +292,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
return dumper.AddFileByReader(object, info, path.Join("data", "packages", objPath))
}); err != nil {
fatal("Failed to dump packages: %v", err)
}
@ -322,10 +317,6 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if outFileName == "-" {
log.Info("Finish dumping to stdout")
} else {
if err = archiverWriter.Close(); err != nil {
_ = os.Remove(outFileName)
fatal("Failed to save %q: %v", outFileName, err)
}
if err = os.Chmod(outFileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err)
}

@ -16,7 +16,7 @@ import (
"strconv"
"strings"
"github.com/google/go-github/v71/github"
"github.com/google/go-github/v74/github"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
)

@ -18,6 +18,7 @@
go = go_1_25;
nodejs = nodejs_24;
python3 = python312;
pnpm = pnpm_10;
# Platform-specific dependencies
linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [
@ -43,6 +44,10 @@
# frontend
nodejs
pnpm
cairo
pixman
pkg-config
# linting
python3

194
go.mod

@ -10,7 +10,7 @@ godebug x509negativeserial=1
require (
code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.21.0
code.gitea.io/sdk/gitea v0.22.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
@ -19,28 +19,28 @@ require (
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/httpsig v1.2.2
github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
github.com/42wim/httpsig v1.2.3
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.2.0
github.com/ProtonMail/go-crypto v1.3.0
github.com/PuerkitoBio/goquery v1.10.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.20.0
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
github.com/aws/aws-sdk-go-v2/service/codecommit v1.28.2
github.com/aws/aws-sdk-go-v2/credentials v1.18.10
github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.0
github.com/blevesearch/bleve/v2 v2.5.3
github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.23.0
github.com/caddyserver/certmagic v0.24.0
github.com/charmbracelet/git-lfs-transfer v0.2.0
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1
@ -49,25 +49,25 @@ require (
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.9.0
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d
github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.0
github.com/go-git/go-git/v5 v5.16.2
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.2
github.com/go-webauthn/webauthn v0.12.3
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-github/v71 v71.0.0
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-github/v74 v74.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0
@ -79,53 +79,53 @@ require (
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.2.10
github.com/klauspost/cpuid/v2 v2.3.0
github.com/lib/pq v1.10.9
github.com/markbates/goth v1.81.0
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.28
github.com/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archiver/v3 v3.5.1
github.com/mattn/go-sqlite3 v1.14.32
github.com/meilisearch/meilisearch-go v0.33.2
github.com/mholt/archives v0.1.3
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.8.0
github.com/minio/minio-go/v7 v7.0.91
github.com/microsoft/go-mssqldb v1.9.3
github.com/minio/minio-go/v7 v7.0.95
github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.8.0
github.com/niklasfasching/go-org v1.9.1
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.22.0
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.0
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.7.3
github.com/redis/go-redis/v9 v9.12.1
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/stretchr/testify v1.10.0
github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.11.1
github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.12
github.com/ulikunitz/xz v0.5.15
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
github.com/urfave/cli/v3 v3.3.3
github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.6.2
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.10
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.127.0
golang.org/x/crypto v0.39.0
golang.org/x/image v0.26.0
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.15.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.26.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
gitlab.com/gitlab-org/api/client-go v0.142.4
golang.org/x/crypto v0.41.0
golang.org/x/image v0.30.0
golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0
@ -135,43 +135,47 @@ require (
)
require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.1 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.8 // indirect
github.com/blevesearch/geo v0.2.0 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.9 // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.11 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
github.com/blevesearch/zapx/v16 v16.2.3 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
@ -180,7 +184,7 @@ require (
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
@ -188,15 +192,15 @@ require (
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect
github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.20 // indirect
github.com/go-webauthn/x v0.1.24 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
@ -207,50 +211,58 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect
github.com/google/go-tpm v0.9.5 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.65 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.0 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.4.0 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
@ -260,20 +272,23 @@ require (
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
@ -281,9 +296,6 @@ replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
exclude github.com/gofrs/uuid v3.2.0+incompatible

591
go.sum

File diff suppressed because it is too large Load Diff

@ -86,7 +86,7 @@ func RegenerateSession(ctx context.Context, oldKey, newKey string) (*Session, er
}
}
if _, err := db.Exec(ctx, "UPDATE "+db.TableName(&Session{})+" SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
if _, err := db.Exec(ctx, "UPDATE `session` SET `key` = ? WHERE `key`=?", newKey, oldKey); err != nil {
return nil, err
}

@ -8,7 +8,6 @@ import (
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json"
@ -40,7 +39,7 @@ func (source *TestSource) ToDB() ([]byte, error) {
func TestDumpAuthSource(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
authSourceSchema, err := db.TableInfo(new(auth_model.Source))
authSourceSchema, err := unittest.GetXORMEngine().TableInfo(new(auth_model.Source))
assert.NoError(t, err)
auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource))

@ -21,28 +21,8 @@ type engineContextKeyType struct{}
var engineContextKey = engineContextKeyType{}
type xormContextType struct {
context.Context
engine Engine
}
var xormContext *xormContextType
func newContext(ctx context.Context, e Engine) *xormContextType {
return &xormContextType{Context: ctx, engine: e}
}
// Value shadows Value for context.Context but allows us to get ourselves and an Engined object
func (ctx *xormContextType) Value(key any) any {
if key == engineContextKey {
return ctx
}
return ctx.Context.Value(key)
}
// WithContext returns this engine tied to this context
func (ctx *xormContextType) WithContext(other context.Context) *xormContextType {
return newContext(ctx, ctx.engine.Context(other))
func withContextEngine(ctx context.Context, e Engine) context.Context {
return context.WithValue(ctx, engineContextKey, e)
}
var (
@ -81,17 +61,19 @@ func contextSafetyCheck(e Engine) {
callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine
for i := range callerNum {
if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) {
panic(errors.New("using database context in an iterator would cause corrupted results"))
panic(errors.New("using session context in an iterator would cause corrupted results"))
}
}
}
// GetEngine gets an existing db Engine/Statement or creates a new Session
func GetEngine(ctx context.Context) (e Engine) {
defer func() { contextSafetyCheck(e) }()
if e := getExistingEngine(ctx); e != nil {
return e
func GetEngine(ctx context.Context) Engine {
if engine, ok := ctx.Value(engineContextKey).(Engine); ok {
// if reusing the existing session, need to do "contextSafetyCheck" because the Iterate creates a "autoResetStatement=false" session
contextSafetyCheck(engine)
return engine
}
// no need to do "contextSafetyCheck" because it's a new Session
return xormEngine.Context(ctx)
}
@ -99,17 +81,6 @@ func GetXORMEngineForTesting() *xorm.Engine {
return xormEngine
}
// getExistingEngine gets an existing db Engine/Statement from this context or returns nil
func getExistingEngine(ctx context.Context) (e Engine) {
if engined, ok := ctx.(*xormContextType); ok {
return engined.engine
}
if engined, ok := ctx.Value(engineContextKey).(*xormContextType); ok {
return engined.engine
}
return nil
}
// Committer represents an interface to Commit or Close the Context
type Committer interface {
Commit() error
@ -152,8 +123,8 @@ func (c *halfCommitter) Close() error {
// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
func TxContext(parentCtx context.Context) (context.Context, Committer, error) {
if sess, ok := inTransaction(parentCtx); ok {
return newContext(parentCtx, sess), &halfCommitter{committer: sess}, nil
if sess := getTransactionSession(parentCtx); sess != nil {
return withContextEngine(parentCtx, sess), &halfCommitter{committer: sess}, nil
}
sess := xormEngine.NewSession()
@ -161,15 +132,14 @@ func TxContext(parentCtx context.Context) (context.Context, Committer, error) {
_ = sess.Close()
return nil, nil, err
}
return newContext(xormContext, sess), sess, nil
return withContextEngine(parentCtx, sess), sess, nil
}
// WithTx represents executing database operations on a transaction, if the transaction exist,
// this function will reuse it otherwise will create a new one and close it when finished.
func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
if sess, ok := inTransaction(parentCtx); ok {
err := f(newContext(parentCtx, sess))
if sess := getTransactionSession(parentCtx); sess != nil {
err := f(withContextEngine(parentCtx, sess))
if err != nil {
// rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
_ = sess.Close()
@ -195,7 +165,7 @@ func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error)
return err
}
if err := f(newContext(parentCtx, sess)); err != nil {
if err := f(withContextEngine(parentCtx, sess)); err != nil {
return err
}
@ -333,32 +303,15 @@ func CountByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Count(bean)
}
// TableName returns the table name according a bean object
func TableName(bean any) string {
return xormEngine.TableName(bean)
}
// InTransaction returns true if the engine is in a transaction otherwise return false
func InTransaction(ctx context.Context) bool {
_, ok := inTransaction(ctx)
return ok
return getTransactionSession(ctx) != nil
}
func inTransaction(ctx context.Context) (*xorm.Session, bool) {
e := getExistingEngine(ctx)
if e == nil {
return nil, false
}
switch t := e.(type) {
case *xorm.Engine:
return nil, false
case *xorm.Session:
if t.IsInTx() {
return t, true
}
return nil, false
default:
return nil, false
func getTransactionSession(ctx context.Context) *xorm.Session {
e, _ := ctx.Value(engineContextKey).(Engine)
if sess, ok := e.(*xorm.Session); ok && sess.IsInTx() {
return sess
}
return nil
}

@ -100,31 +100,36 @@ func TestContextSafety(t *testing.T) {
assert.NoError(t, db.Insert(t.Context(), &TestModel2{ID: int64(-i)}))
}
actualCount := 0
// here: db.GetEngine(t.Context()) is a new *Session created from *Engine
_ = db.WithTx(t.Context(), func(ctx context.Context) error {
_ = db.GetEngine(ctx).Iterate(&TestModel1{}, func(i int, bean any) error {
// here: db.GetEngine(ctx) is always the unclosed "Iterate" *Session with autoResetStatement=false,
// and the internal states (including "cond" and others) are always there and not be reset in this callback.
m1 := bean.(*TestModel1)
assert.EqualValues(t, i+1, m1.ID)
// here: XORM bug, it fails because the SQL becomes "WHERE id=-1", "WHERE id=-1 AND id=-2", "WHERE id=-1 AND id=-2 AND id=-3" ...
// and it conflicts with the "Iterate"'s internal states.
// has, err := db.GetEngine(ctx).Get(&TestModel2{ID: -m1.ID})
actualCount++
t.Run("Show-XORM-Bug", func(t *testing.T) {
actualCount := 0
// here: db.GetEngine(t.Context()) is a new *Session created from *Engine
_ = db.WithTx(t.Context(), func(ctx context.Context) error {
_ = db.GetEngine(ctx).Iterate(&TestModel1{}, func(i int, bean any) error {
// here: db.GetEngine(ctx) is always the unclosed "Iterate" *Session with autoResetStatement=false,
// and the internal states (including "cond" and others) are always there and not be reset in this callback.
m1 := bean.(*TestModel1)
assert.EqualValues(t, i+1, m1.ID)
// here: XORM bug, it fails because the SQL becomes "WHERE id=-1", "WHERE id=-1 AND id=-2", "WHERE id=-1 AND id=-2 AND id=-3" ...
// and it conflicts with the "Iterate"'s internal states.
// has, err := db.GetEngine(ctx).Get(&TestModel2{ID: -m1.ID})
actualCount++
return nil
})
return nil
})
return nil
assert.Equal(t, testCount, actualCount)
})
assert.Equal(t, testCount, actualCount)
// deny the bad usages
assert.PanicsWithError(t, "using database context in an iterator would cause corrupted results", func() {
_ = unittest.GetXORMEngine().Iterate(&TestModel1{}, func(i int, bean any) error {
_ = db.GetEngine(t.Context())
return nil
t.Run("DenyBadUsage", func(t *testing.T) {
assert.PanicsWithError(t, "using session context in an iterator would cause corrupted results", func() {
_ = db.WithTx(t.Context(), func(ctx context.Context) error {
return db.GetEngine(ctx).Iterate(&TestModel1{}, func(i int, bean any) error {
_ = db.GetEngine(ctx)
return nil
})
})
})
})
}

@ -12,7 +12,6 @@ import (
"strings"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver
@ -67,11 +66,6 @@ var (
_ Engine = (*xorm.Session)(nil)
)
// TableInfo returns table's information via an object
func TableInfo(v any) (*schemas.Table, error) {
return xormEngine.TableInfo(v)
}
// RegisterModel registers model, if initFuncs provided, it will be invoked after data model sync
func RegisterModel(bean any, initFunc ...func() error) {
registeredModels = append(registeredModels, bean)

@ -86,7 +86,6 @@ func InitEngine(ctx context.Context) error {
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
xormEngine = eng
xormEngine.SetDefaultContext(ctx)
xormContext = &xormContextType{Context: ctx, engine: xormEngine}
}
// UnsetDefaultEngine closes and unsets the default engine
@ -98,7 +97,6 @@ func UnsetDefaultEngine() {
_ = xormEngine.Close()
xormEngine = nil
}
xormContext = nil
}
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the XORM's default context

@ -70,7 +70,7 @@ func TestPrimaryKeys(t *testing.T) {
}
for _, bean := range beans {
table, err := db.TableInfo(bean)
table, err := db.GetXORMEngineForTesting().TableInfo(bean)
if err != nil {
t.Fatal(err)
}

@ -19,12 +19,7 @@ type ResourceIndex struct {
MaxIndex int64 `xorm:"index"`
}
var (
// ErrResouceOutdated represents an error when request resource outdated
ErrResouceOutdated = errors.New("resource outdated")
// ErrGetResourceIndexFailed represents an error when resource index retries 3 times
ErrGetResourceIndexFailed = errors.New("get resource index failed")
)
var ErrGetResourceIndexFailed = errors.New("get resource index failed")
// SyncMaxResourceIndex sync the max index with the resource
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {

@ -106,8 +106,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
"WHEN milestone.deadline_unix = 0 OR milestone.deadline_unix IS NULL THEN issue.deadline_unix " +
"WHEN milestone.deadline_unix < issue.deadline_unix OR issue.deadline_unix = 0 THEN milestone.deadline_unix " +
"ELSE issue.deadline_unix END ASC").
Desc("issue.created_unix").
Desc("issue.id")
Asc("issue.created_unix").
Asc("issue.id")
case "farduedate":
sess.Join("LEFT", "milestone", "issue.milestone_id = milestone.id").
OrderBy("CASE " +

@ -105,11 +105,6 @@ type MinimalOrg = Organization
// GetUserOrgsList returns all organizations the given user has access to
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
schema, err := db.TableInfo(new(user_model.User))
if err != nil {
return nil, err
}
outputCols := []string{
"id",
"name",
@ -122,7 +117,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg,
selectColumns := &strings.Builder{}
for i, col := range outputCols {
fmt.Fprintf(selectColumns, "`%s`.%s", schema.Name, col)
_, _ = fmt.Fprintf(selectColumns, "`user`.%s", col)
if i < len(outputCols)-1 {
selectColumns.WriteString(", ")
}

@ -282,11 +282,8 @@ func (opts FindReleasesOptions) ToOrders() string {
// GetTagNamesByRepoID returns a list of release tag names of repository.
func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
listOptions := db.ListOptions{
ListAll: true,
}
opts := FindReleasesOptions{
ListOptions: listOptions,
ListOptions: db.ListOptionsAll,
IncludeDrafts: true,
IncludeTags: true,
HasSha1: optional.Some(true),

@ -229,6 +229,10 @@ func RelativePath(ownerName, repoName string) string {
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
}
func RelativeWikiPath(ownerName, repoName string) string {
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
}
// RelativePath should be an unix style path like username/reponame.git
func (repo *Repository) RelativePath() string {
return RelativePath(repo.OwnerName, repo.Name)
@ -241,8 +245,10 @@ func (sr StorageRepo) RelativePath() string {
return string(sr)
}
// WikiStorageRepo returns the storage repo for the wiki
// The wiki repository should have the same object format as the code repository
func (repo *Repository) WikiStorageRepo() StorageRepo {
return StorageRepo(strings.ToLower(repo.OwnerName) + "/" + strings.ToLower(repo.Name) + ".wiki.git")
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
}
// SanitizedOriginalURL returns a sanitized OriginalURL

@ -11,7 +11,6 @@ import (
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
@ -86,12 +85,3 @@ func WikiPath(userName, repoName string) string {
func (repo *Repository) WikiPath() string {
return WikiPath(repo.OwnerName, repo.Name)
}
// HasWiki returns true if repository has wiki.
func (repo *Repository) HasWiki() bool {
isDir, err := util.IsDir(repo.WikiPath())
if err != nil {
log.Error("Unable to check if %s is a directory: %v", repo.WikiPath(), err)
}
return isDir
}

@ -35,11 +35,3 @@ func TestRepository_WikiPath(t *testing.T) {
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
assert.Equal(t, expected, repo.WikiPath())
}
func TestRepository_HasWiki(t *testing.T) {
unittest.PrepareTestEnv(t)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
assert.True(t, repo1.HasWiki())
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, repo2.HasWiki())
}

@ -45,7 +45,7 @@ func CheckConsistencyFor(t TestingT, beansToCheck ...any) {
}
func checkForConsistency(t TestingT, bean any) {
tb, err := db.TableInfo(bean)
tb, err := GetXORMEngine().TableInfo(bean)
assert.NoError(t, err)
f := consistencyCheckMap[tb.Name]
require.NotNil(t, f, "unknown bean type: %#v", bean)

@ -218,7 +218,7 @@ func NewFixturesLoader(x *xorm.Engine, opts FixturesOptions) (FixturesLoader, er
xormBeans, _ := db.NamesToBean()
f.xormTableNames = map[string]bool{}
for _, bean := range xormBeans {
f.xormTableNames[db.TableName(bean)] = true
f.xormTableNames[x.TableName(bean)] = true
}
return f, nil

@ -159,7 +159,7 @@ func DumpQueryResult(t require.TestingT, sqlOrBean any, sqlArgs ...any) {
goDB := x.DB().DB
sql, ok := sqlOrBean.(string)
if !ok {
sql = "SELECT * FROM " + db.TableName(sqlOrBean)
sql = "SELECT * FROM " + x.TableName(sqlOrBean)
} else if !strings.Contains(sql, " ") {
sql = "SELECT * FROM " + sql
}

@ -30,6 +30,8 @@ func CreateReader(input io.Reader, delimiter rune) *stdcsv.Reader {
// thus would change `\t\t` to just `\t` or ` ` (two spaces) to just ` ` (single space)
rd.TrimLeadingSpace = true
}
// Don't force validation of every row to have the same number of entries as the first row.
rd.FieldsPerRecord = -1
return rd
}

@ -94,6 +94,24 @@ j, ,\x20
},
expectedDelimiter: ',',
},
// case 3 - every delimiter used, default to comma and handle differing number of fields per record
{
csv: `col1,col2
a;b
c@e
f g
h|i
jkl`,
expectedRows: [][]string{
{"col1", "col2"},
{"a;b"},
{"c@e"},
{"f g"},
{"h|i"},
{"jkl"},
},
expectedDelimiter: ',',
},
}
for n, c := range cases {
@ -119,21 +137,6 @@ func TestDetermineDelimiterShortBufferError(t *testing.T) {
assert.Nil(t, rd, "CSV reader should be mnil")
}
func TestDetermineDelimiterReadAllError(t *testing.T) {
rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(`col1,col2
a;b
c@e
f g
h|i
jkl`))
assert.NoError(t, err, "CreateReaderAndDetermineDelimiter() shouldn't throw error")
assert.NotNil(t, rd, "CSV reader should not be mnil")
rows, err := rd.ReadAll()
assert.Error(t, err, "RaadAll() should throw error")
assert.ErrorIs(t, err, csv.ErrFieldCount)
assert.Empty(t, rows, "rows should be empty")
}
func TestDetermineDelimiter(t *testing.T) {
cases := []struct {
csv string

@ -4,8 +4,11 @@
package dump
import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
@ -16,7 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
"github.com/mholt/archives"
)
var SupportedOutputTypes = []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"}
@ -60,37 +63,122 @@ func IsSubdir(upper, lower string) (bool, error) {
}
type Dumper struct {
Writer archiver.Writer
Verbose bool
jobs chan archives.ArchiveAsyncJob
errArchiveAsync chan error
errArchiveJob chan error
globalExcludeAbsPaths []string
}
func (dumper *Dumper) AddReader(r io.ReadCloser, info os.FileInfo, customName string) error {
if dumper.Verbose {
log.Info("Adding file %s", customName)
func NewDumper(ctx context.Context, format string, output io.Writer) (*Dumper, error) {
d := &Dumper{
jobs: make(chan archives.ArchiveAsyncJob, 1),
errArchiveAsync: make(chan error, 1),
errArchiveJob: make(chan error, 1),
}
return dumper.Writer.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
// TODO: in the future, we could completely drop the "mholt/archives" dependency.
// Then we only need to support "zip" and ".tar.gz" natively, and let users provide custom command line tools
// like "zstd" or "xz" with compression-level arguments.
var comp archives.ArchiverAsync
switch format {
case "zip":
comp = archives.Zip{}
case "tar":
comp = archives.Tar{}
case "tar.sz":
comp = archives.CompressedArchive{Compression: archives.Sz{}, Archival: archives.Tar{}}
case "tar.gz":
comp = archives.CompressedArchive{Compression: archives.Gz{}, Archival: archives.Tar{}}
case "tar.xz":
comp = archives.CompressedArchive{Compression: archives.Xz{}, Archival: archives.Tar{}}
case "tar.bz2":
comp = archives.CompressedArchive{Compression: archives.Bz2{}, Archival: archives.Tar{}}
case "tar.br":
comp = archives.CompressedArchive{Compression: archives.Brotli{}, Archival: archives.Tar{}}
case "tar.lz4":
comp = archives.CompressedArchive{Compression: archives.Lz4{}, Archival: archives.Tar{}}
case "tar.zst":
comp = archives.CompressedArchive{Compression: archives.Zstd{}, Archival: archives.Tar{}}
default:
return nil, fmt.Errorf("unsupported format: %s", format)
}
go func() {
d.errArchiveAsync <- comp.ArchiveAsync(ctx, output, d.jobs)
close(d.errArchiveAsync)
}()
return d, nil
}
func (dumper *Dumper) AddFile(filePath, absPath string) error {
file, err := os.Open(absPath)
if err != nil {
func (dumper *Dumper) runArchiveJob(job archives.ArchiveAsyncJob) error {
dumper.jobs <- job
select {
case err := <-dumper.errArchiveAsync:
if err == nil {
return errors.New("archiver has been closed")
}
return err
case err := <-dumper.errArchiveJob:
return err
}
defer file.Close()
fileInfo, err := file.Stat()
}
// AddFileByPath adds a file by its filesystem path
func (dumper *Dumper) AddFileByPath(filePath, absPath string) error {
if dumper.Verbose {
log.Info("Adding local file %s", filePath)
}
fileInfo, err := os.Stat(absPath)
if err != nil {
return err
}
return dumper.AddReader(file, fileInfo, filePath)
archiveFileInfo := archives.FileInfo{
FileInfo: fileInfo,
NameInArchive: filePath,
Open: func() (fs.File, error) { return os.Open(absPath) },
}
return dumper.runArchiveJob(archives.ArchiveAsyncJob{
File: archiveFileInfo,
Result: dumper.errArchiveJob,
})
}
type readerFile struct {
r io.Reader
info os.FileInfo
}
var _ fs.File = (*readerFile)(nil)
func (f *readerFile) Stat() (fs.FileInfo, error) { return f.info, nil }
func (f *readerFile) Read(bytes []byte) (int, error) { return f.r.Read(bytes) }
func (f *readerFile) Close() error { return nil }
// AddFileByReader adds a file's contents from a Reader
func (dumper *Dumper) AddFileByReader(r io.Reader, info os.FileInfo, customName string) error {
if dumper.Verbose {
log.Info("Adding storage file %s", customName)
}
fileInfo := archives.FileInfo{
FileInfo: info,
NameInArchive: customName,
Open: func() (fs.File, error) { return &readerFile{r, info}, nil },
}
return dumper.runArchiveJob(archives.ArchiveAsyncJob{
File: fileInfo,
Result: dumper.errArchiveJob,
})
}
func (dumper *Dumper) Close() error {
close(dumper.jobs)
return <-dumper.errArchiveAsync
}
func (dumper *Dumper) normalizeFilePath(absPath string) string {
@ -143,7 +231,7 @@ func (dumper *Dumper) addFileOrDir(insidePath, absPath string, excludes []string
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if err := dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
if err := dumper.AddFileByPath(currentInsidePath, currentAbsPath); err != nil {
return err
}
if err = dumper.addFileOrDir(currentInsidePath, currentAbsPath, excludes); err != nil {
@ -164,7 +252,7 @@ func (dumper *Dumper) addFileOrDir(insidePath, absPath string, excludes []string
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = dumper.AddFile(currentInsidePath, currentAbsPath); err != nil {
if err = dumper.AddFileByPath(currentInsidePath, currentAbsPath); err != nil {
return err
}
}

@ -4,6 +4,8 @@
package dump
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
@ -14,8 +16,8 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPrepareFileNameAndType(t *testing.T) {
@ -67,28 +69,26 @@ func TestIsSubDir(t *testing.T) {
assert.False(t, isSub)
}
type testWriter struct {
added []string
}
func TestDumperIntegration(t *testing.T) {
var buf bytes.Buffer
dumper, err := NewDumper(t.Context(), "zip", &buf)
require.NoError(t, err)
func (t *testWriter) Create(out io.Writer) error {
return nil
}
tmpDir := t.TempDir()
_ = os.WriteFile(filepath.Join(tmpDir, "test.txt"), nil, 0o644)
f, _ := os.Open(filepath.Join(tmpDir, "test.txt"))
func (t *testWriter) Write(f archiver.File) error {
t.added = append(t.added, f.Name())
return nil
}
fi, _ := f.Stat()
err = dumper.AddFileByReader(f, fi, "test.txt")
require.NoError(t, err)
err = dumper.Close()
require.NoError(t, err)
func (t *testWriter) Close() error {
return nil
assert.Positive(t, buf.Len())
}
func TestDumper(t *testing.T) {
sortStrings := func(s []string) []string {
sort.Strings(s)
return s
}
tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude1"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude2"), 0o755)
@ -98,16 +98,54 @@ func TestDumper(t *testing.T) {
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude1/a-1"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude2/a-2"), nil, 0o644)
tw := &testWriter{}
d := &Dumper{Writer: tw}
d.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1"))
err := d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), []string{filepath.Join(tmpDir, "include/exclude2")})
assert.NoError(t, err)
assert.Equal(t, sortStrings([]string{"include/a", "include/sub", "include/sub/b"}), sortStrings(tw.added))
sortStrings := func(s []string) []string {
sort.Strings(s)
return s
}
tw = &testWriter{}
d = &Dumper{Writer: tw}
err = d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil)
assert.NoError(t, err)
assert.Equal(t, sortStrings([]string{"include/exclude2", "include/exclude2/a-2", "include/a", "include/sub", "include/sub/b", "include/exclude1", "include/exclude1/a-1"}), sortStrings(tw.added))
t.Run("IncludesWithExcludes", func(t *testing.T) {
var buf bytes.Buffer
dumper, err := NewDumper(t.Context(), "tar", &buf)
require.NoError(t, err)
dumper.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1"))
err = dumper.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), []string{filepath.Join(tmpDir, "include/exclude2")})
require.NoError(t, err)
err = dumper.Close()
require.NoError(t, err)
files := extractTarFileNames(t, &buf)
expected := []string{"include/a", "include/sub", "include/sub/b"}
assert.Equal(t, sortStrings(expected), sortStrings(files))
})
t.Run("IncludesAll", func(t *testing.T) {
var buf bytes.Buffer
dumper, err := NewDumper(t.Context(), "tar", &buf)
require.NoError(t, err)
err = dumper.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil)
require.NoError(t, err)
err = dumper.Close()
require.NoError(t, err)
files := extractTarFileNames(t, &buf)
expected := []string{
"include/exclude2", "include/exclude2/a-2",
"include/a", "include/sub", "include/sub/b",
"include/exclude1", "include/exclude1/a-1",
}
assert.Equal(t, sortStrings(expected), sortStrings(files))
})
}
func extractTarFileNames(t *testing.T, buf *bytes.Buffer) (fileNames []string) {
tr := tar.NewReader(buf)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
require.NoError(t, err, "Error reading tar archive")
fileNames = append(fileNames, hdr.Name)
}
return fileNames
}

@ -30,6 +30,10 @@ type Parser struct {
func NewParser(r io.Reader, format Format) *Parser {
scanner := bufio.NewScanner(r)
// default MaxScanTokenSize = 64 kiB may be too small for some references,
// so allow the buffer to grow up to 4x if needed
scanner.Buffer(nil, 4*bufio.MaxScanTokenSize)
// in addition to the reference delimiter we specified in the --format,
// `git for-each-ref` will always add a newline after every reference.
refDelim := make([]byte, 0, len(format.refDelim)+1)
@ -70,6 +74,9 @@ func NewParser(r io.Reader, format Format) *Parser {
// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
func (p *Parser) Next() map[string]string {
if !p.scanner.Scan() {
if err := p.scanner.Err(); err != nil {
p.err = err
}
return nil
}
fields, err := p.parseRef(p.scanner.Text())

@ -1,14 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"time"
)
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
return NewCommand("fsck").AddArguments(args...).Run(ctx, &RunOpts{Timeout: timeout, Dir: repoPath})
}

@ -9,7 +9,6 @@ import (
"net/url"
"strings"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/util"
)
@ -33,15 +32,6 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string,
return result, nil
}
// GetRemoteURL returns the url of a specific remote of the repository.
func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.GitURL, error) {
addr, err := GetRemoteAddress(ctx, repoPath, remoteName)
if err != nil {
return nil, err
}
return giturl.ParseGitURL(addr)
}
// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
type ErrInvalidCloneAddr struct {
Host string

@ -38,6 +38,17 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) {
return AllCommitsCount(repo.Ctx, repo.Path, false)
}
func (repo *Repository) ShowPrettyFormatLogToList(ctx context.Context, revisionRange string) ([]*Commit, error) {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
logs, _, err := NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(revisionRange).AddArguments("--").
RunStdBytes(ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return repo.parsePrettyFormatLogToList(logs)
}
func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, error) {
var commits []*Commit
if len(logs) == 0 {

@ -79,12 +79,6 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
return err
}
// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
_, _, err := NewCommand("remote", "rm").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error {
_, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})

@ -16,20 +16,8 @@ import (
"regexp"
"strconv"
"strings"
"time"
logger "code.gitea.io/gitea/modules/log"
)
// CompareInfo represents needed information for comparing references.
type CompareInfo struct {
MergeBase string
BaseCommitID string
HeadCommitID string
Commits []*Commit
NumFiles int
}
// GetMergeBase checks and returns merge base of two branches and the reference used as base.
func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, string, error) {
if tmpRemote == "" {
@ -49,83 +37,6 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
return strings.TrimSpace(stdout), base, err
}
// GetCompareInfo generates and returns compare information between base and head branches of repositories.
func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string, directComparison, fileOnly bool) (_ *CompareInfo, err error) {
var (
remoteBranch string
tmpRemote string
)
// We don't need a temporary remote for same repository.
if repo.Path != basePath {
// Add a temporary remote
tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10)
if err = repo.AddRemote(tmpRemote, basePath, false); err != nil {
return nil, fmt.Errorf("AddRemote: %w", err)
}
defer func() {
if err := repo.RemoveRemote(tmpRemote); err != nil {
logger.Error("GetPullRequestInfo: RemoveRemote: %v", err)
}
}()
}
compareInfo := new(CompareInfo)
compareInfo.HeadCommitID, err = GetFullCommitID(repo.Ctx, repo.Path, headBranch)
if err != nil {
compareInfo.HeadCommitID = headBranch
}
compareInfo.MergeBase, remoteBranch, err = repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
if err == nil {
compareInfo.BaseCommitID, err = GetFullCommitID(repo.Ctx, repo.Path, remoteBranch)
if err != nil {
compareInfo.BaseCommitID = remoteBranch
}
separator := "..."
baseCommitID := compareInfo.MergeBase
if directComparison {
separator = ".."
baseCommitID = compareInfo.BaseCommitID
}
// We have a common base - therefore we know that ... should work
if !fileOnly {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
var logs []byte
logs, _, err = NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(baseCommitID+separator+headBranch).AddArguments("--").
RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
compareInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
if err != nil {
return nil, fmt.Errorf("parsePrettyFormatLogToList: %w", err)
}
} else {
compareInfo.Commits = []*Commit{}
}
} else {
compareInfo.Commits = []*Commit{}
compareInfo.MergeBase, err = GetFullCommitID(repo.Ctx, repo.Path, remoteBranch)
if err != nil {
compareInfo.MergeBase = remoteBranch
}
compareInfo.BaseCommitID = compareInfo.MergeBase
}
// Count number of changed files.
// This probably should be removed as we need to use shortstat elsewhere
// Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly
compareInfo.NumFiles, err = repo.GetDiffNumChangedFiles(remoteBranch, headBranch, directComparison)
if err != nil {
return nil, err
}
return compareInfo, nil
}
type lineCountWriter struct {
numLines int
}

@ -7,8 +7,6 @@
package git
import (
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/go-git/go-git/v5/plumbing"
@ -20,40 +18,6 @@ func (repo *Repository) IsTagExist(name string) bool {
return err == nil
}
// GetTags returns all tags of the repository.
// returning at most limit tags, or all if limit is 0.
func (repo *Repository) GetTags(skip, limit int) ([]string, error) {
var tagNames []string
tags, err := repo.gogitRepo.Tags()
if err != nil {
return nil, err
}
_ = tags.ForEach(func(tag *plumbing.Reference) error {
tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
return nil
})
// Reverse order
for i := 0; i < len(tagNames)/2; i++ {
j := len(tagNames) - i - 1
tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
}
// since we have to reverse order we can paginate only afterwards
if len(tagNames) < skip {
tagNames = []string{}
} else {
tagNames = tagNames[skip:]
}
if limit != 0 && len(tagNames) > limit {
tagNames = tagNames[:limit]
}
return tagNames, nil
}
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
// Get tag type

@ -22,13 +22,6 @@ func (repo *Repository) IsTagExist(name string) bool {
return repo.IsReferenceExist(TagPrefix + name)
}
// GetTags returns all tags of the repository.
// returning at most limit tags, or all if limit is 0.
func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}, skip, limit)
return tags, err
}
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestRepository_GetTags(t *testing.T) {
func TestRepository_GetTagInfos(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path)
if err != nil {

@ -0,0 +1,48 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/globallock"
)
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
result, _, err := git.NewCommand("config", "--get").
AddDynamicArguments(key).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)})
if err != nil {
return "", err
}
return strings.TrimSpace(result), nil
}
func getRepoConfigLockKey(repoStoragePath string) string {
return "repo-config:" + repoStoragePath
}
// GitConfigAdd add a git configuration key to a specific value for the given repository.
func GitConfigAdd(ctx context.Context, repo Repository, key, value string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, _, err := git.NewCommand("config", "--add").
AddDynamicArguments(key, value).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)})
return err
})
}
// GitConfigSet updates a git configuration key to a specific value for the given repository.
// If the key does not exist, it will be created.
// If the key exists, it will be updated to the new value.
func GitConfigSet(ctx context.Context, repo Repository, key, value string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
_, _, err := git.NewCommand("config").
AddDynamicArguments(key, value).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)})
return err
})
}

@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"time"
"code.gitea.io/gitea/modules/git"
)
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repo Repository, timeout time.Duration, args git.TrustedCmdArgs) error {
return git.NewCommand("fsck").AddArguments(args...).Run(ctx, &git.RunOpts{Timeout: timeout, Dir: repoPath(repo)})
}

@ -69,7 +69,8 @@ func IsRepositoryExist(ctx context.Context, repo Repository) (bool, error) {
return util.IsExist(repoPath(repo))
}
// DeleteRepository deletes the repository directory from the disk
// DeleteRepository deletes the repository directory from the disk, it will return
// nil if the repository does not exist.
func DeleteRepository(ctx context.Context, repo Repository) error {
return util.RemoveAll(repoPath(repo))
}
@ -81,3 +82,7 @@ func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
}
return nil
}
func InitRepository(ctx context.Context, repo Repository, objectFormatName string) error {
return git.InitRepository(ctx, repoPath(repo), true, objectFormatName)
}

@ -0,0 +1,85 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"errors"
"io"
"time"
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/util"
)
type RemoteOption string
const (
RemoteOptionMirrorPush RemoteOption = "--mirror=push"
RemoteOptionMirrorFetch RemoteOption = "--mirror=fetch"
)
func GitRemoteAdd(ctx context.Context, repo Repository, remoteName, remoteURL string, options ...RemoteOption) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := git.NewCommand("remote", "add")
if len(options) > 0 {
switch options[0] {
case RemoteOptionMirrorPush:
cmd.AddArguments("--mirror=push")
case RemoteOptionMirrorFetch:
cmd.AddArguments("--mirror=fetch")
default:
return errors.New("unknown remote option: " + string(options[0]))
}
}
_, _, err := cmd.
AddDynamicArguments(remoteName, remoteURL).
RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)})
return err
})
}
func GitRemoteRemove(ctx context.Context, repo Repository, remoteName string) error {
return globallock.LockAndDo(ctx, getRepoConfigLockKey(repo.RelativePath()), func(ctx context.Context) error {
cmd := git.NewCommand("remote", "rm").AddDynamicArguments(remoteName)
_, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)})
return err
})
}
// GitRemoteGetURL returns the url of a specific remote of the repository.
func GitRemoteGetURL(ctx context.Context, repo Repository, remoteName string) (*giturl.GitURL, error) {
addr, err := git.GetRemoteAddress(ctx, repoPath(repo), remoteName)
if err != nil {
return nil, err
}
if addr == "" {
return nil, util.NewNotExistErrorf("remote '%s' does not exist", remoteName)
}
return giturl.ParseGitURL(addr)
}
// GitRemotePrune prunes the remote branches that no longer exist in the remote repository.
func GitRemotePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return git.NewCommand("remote", "prune").AddDynamicArguments(remoteName).
Run(ctx, &git.RunOpts{
Timeout: timeout,
Dir: repoPath(repo),
Stdout: stdout,
Stderr: stderr,
})
}
// GitRemoteUpdatePrune updates the remote branches and prunes the ones that no longer exist in the remote repository.
func GitRemoteUpdatePrune(ctx context.Context, repo Repository, remoteName string, timeout time.Duration, stdout, stderr io.Writer) error {
return git.NewCommand("remote", "update", "--prune").AddDynamicArguments(remoteName).
Run(ctx, &git.RunOpts{
Timeout: timeout,
Dir: repoPath(repo),
Stdout: stdout,
Stderr: stderr,
})
}

@ -14,6 +14,7 @@ import (
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_meilisearch "code.gitea.io/gitea/modules/indexer/internal/meilisearch"
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/json"
"github.com/meilisearch/meilisearch-go"
)
@ -106,7 +107,8 @@ func (b *Indexer) Index(_ context.Context, issues ...*internal.IndexerData) erro
return nil
}
for _, issue := range issues {
_, err := b.inner.Client.Index(b.inner.VersionedIndexName()).AddDocuments(issue)
// use default primary key which should be "id"
_, err := b.inner.Client.Index(b.inner.VersionedIndexName()).AddDocuments(issue, nil)
if err != nil {
return err
}
@ -299,18 +301,13 @@ func doubleQuoteKeyword(k string) string {
func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) {
hits := make([]internal.Match, 0, len(searchRes.Hits))
for _, hit := range searchRes.Hits {
hit, ok := hit.(map[string]any)
if !ok {
return nil, ErrMalformedResponse
}
issueID, ok := hit["id"].(float64)
if !ok {
var issueID int64
if err := json.Unmarshal(hit["id"], &issueID); err != nil {
return nil, ErrMalformedResponse
}
hits = append(hits, internal.Match{
ID: int64(issueID),
ID: issueID,
})
}
return hits, nil

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/indexer/issues/internal/tests"
"code.gitea.io/gitea/modules/json"
"github.com/meilisearch/meilisearch-go"
"github.com/stretchr/testify/assert"
@ -45,30 +46,42 @@ func TestMeilisearchIndexer(t *testing.T) {
}
func TestConvertHits(t *testing.T) {
convert := func(d any) []byte {
b, _ := json.Marshal(d)
return b
}
_, err := convertHits(&meilisearch.SearchResponse{
Hits: []any{"aa", "bb", "cc", "dd"},
Hits: []meilisearch.Hit{
{
"aa": convert(1),
"bb": convert(2),
"cc": convert(3),
"dd": convert(4),
},
},
})
assert.ErrorIs(t, err, ErrMalformedResponse)
validResponse := &meilisearch.SearchResponse{
Hits: []any{
map[string]any{
"id": float64(11),
"title": "a title",
"content": "issue body with no match",
"comments": []any{"hey whats up?", "I'm currently bowling", "nice"},
Hits: []meilisearch.Hit{
{
"id": convert(float64(11)),
"title": convert("a title"),
"content": convert("issue body with no match"),
"comments": convert([]any{"hey whats up?", "I'm currently bowling", "nice"}),
},
map[string]any{
"id": float64(22),
"title": "Bowling as title",
"content": "",
"comments": []any{},
{
"id": convert(float64(22)),
"title": convert("Bowling as title"),
"content": convert(""),
"comments": convert([]any{}),
},
map[string]any{
"id": float64(33),
"title": "Bowl-ing as fuzzy match",
"content": "",
"comments": []any{},
{
"id": convert(float64(33)),
"title": convert("Bowl-ing as fuzzy match"),
"content": convert(""),
"comments": convert([]any{}),
},
},
}

@ -282,9 +282,9 @@ type CreateBranchRepoOption struct {
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
}
// UpdateBranchRepoOption options when updating a branch in a repository
// RenameBranchRepoOption options when renaming a branch in a repository
// swagger:model
type UpdateBranchRepoOption struct {
type RenameBranchRepoOption struct {
// New branch name
//
// required: true

@ -14,8 +14,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository"
@ -145,18 +144,12 @@ type remoteAddress struct {
func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
u, err := gitrepo.GitRemoteGetURL(ctx, m, remoteName)
if err != nil {
log.Error("GetRemoteURL %v", err)
return ret
}
u, err := giturl.ParseGitURL(remoteURL)
if err != nil {
log.Error("giturl.Parse %v", err)
return ret
}
if u.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil {
ret.Username = u.User.Username()

@ -551,6 +551,14 @@ repo.transfer.body = To accept or reject it, visit %s or just ignore it.
repo.collaborator.added.subject = %s added you to %s
repo.collaborator.added.text = You have been added as a collaborator of repository:
repo.actions.run.failed = Run failed
repo.actions.run.succeeded = Run succeeded
repo.actions.run.cancelled = Run cancelled
repo.actions.jobs.all_succeeded = All jobs have succeeded
repo.actions.jobs.all_failed = All jobs have failed
repo.actions.jobs.some_not_successful = Some jobs were not successful
repo.actions.jobs.all_cancelled = All jobs have been cancelled
team_invite.subject = %[1]s has invited you to join the %[2]s organization
team_invite.text_1 = %[1]s has invited you to join team %[2]s in organization %[3]s.
team_invite.text_2 = Please click the following link to join the team:

@ -120,6 +120,7 @@ error404=Níl an leathanach atá tú ag iarraidh a bhaint amach <strong>ann</str
error503=Níorbh fhéidir leis an bhfreastalaí diarratas a chomhlánú. Déan iarracht arís ar ball.
go_back=Ar ais
invalid_data=Sonraí neamhbhailí: %v
nothing_has_been_changed=Níl aon rud athraithe.
never=Riamh
unknown=Anaithnid
@ -2843,6 +2844,11 @@ settings.location=Suíomh
settings.permission=Ceadanna
settings.repoadminchangeteam=Is féidir le riarthóir an stórais rochtain d'fhoirne a chur leis agus a bhaint
settings.visibility=Infheictheacht
settings.change_visibility=Athraigh Infheictheacht
settings.change_visibility_notices_1=Má dhéantar an eagraíocht a thiontú go príobháideach, bainfear réaltaí an stórais agus ní féidir iad a athchóiriú.
settings.change_visibility_notices_2=Caillfidh daoine nach baill iad rochtain ar stórtha na heagraíochta má athraítear an infheictheacht go príobháideach.
settings.change_visibility_success=Tá infheictheacht na heagraíochta %s athraithe go rathúil.
settings.visibility_desc=Athraigh cé a fhéadfaidh an eagraíocht agus a stórtha a fheiceáil.
settings.visibility.public=Poiblí
settings.visibility.limited=Teoranta (Infheicthe d'úsáideoirí fíordheimhnithe amháin)
settings.visibility.limited_shortname=Teoranta
@ -3419,6 +3425,7 @@ config.picture_service=Seirbhís Pictiúr
config.disable_gravatar=Díchumasaigh Gravatar
config.enable_federated_avatar=Cumasaigh Avatars Cónaidhme
config.open_with_editor_app_help=Na heagarthóirí "Oscailte le" don roghchlár Clón. Má fhágtar folamh é, úsáidfear an réamhshocrú. Leathnaigh chun an réamhshocrú a fheiceáil.
config.git_guide_remote_name=Ainm iargúlta stórais le haghaidh orduithe git sa treoir
config.git_config=Cumraíocht Git
config.git_disable_diff_highlight=Díchumasaigh Aibhsiú Comhréire Diff

@ -120,6 +120,7 @@ error404=アクセスしようとしたページは<strong>存在しない</stro
error503=サーバーはリクエストを完了できませんでした。 後でもう一度お試しください。
go_back=戻る
invalid_data=無効なデータ: %v
nothing_has_been_changed=何も変更されていません。
never=無し
unknown=不明
@ -1322,6 +1323,7 @@ commit_graph.color=カラー
commit.contained_in=このコミットが含まれているのは:
commit.contained_in_default_branch=このコミットはデフォルトブランチに含まれています
commit.load_referencing_branches_and_tags=このコミットを参照しているブランチやタグを取得
commit.merged_in_pr=このコミットはプルリクエスト %s でマージされました。
blame=Blame
download_file=ファイルをダウンロード
normal_view=通常表示
@ -2842,6 +2844,11 @@ settings.location=場所
settings.permission=許可
settings.repoadminchangeteam=リポジトリ管理者はチームのアクセス権の追加・削除が可能
settings.visibility=表示
settings.change_visibility=公開範囲を変更
settings.change_visibility_notices_1=組織をプライベートに変換すると、リポジトリのスターが削除され、復元することはできません。
settings.change_visibility_notices_2=公開範囲をプライベートに変更すると、非メンバーは組織のリポジトリにアクセスできなくなります。
settings.change_visibility_success=組織 %s の公開範囲を変更しました。
settings.visibility_desc=組織とそのリポジトリを誰が閲覧できるかを変更します。
settings.visibility.public=公開
settings.visibility.limited=限定 (認証済みユーザーにのみ表示)
settings.visibility.limited_shortname=限定
@ -3418,6 +3425,7 @@ config.picture_service=画像サービス
config.disable_gravatar=Gravatarが無効
config.enable_federated_avatar=フェデレーテッド・アバター有効
config.open_with_editor_app_help=クローンメニューの「~で開く」に表示するエディタ。 空白のままにするとデフォルトが使用されます。 展開するとデフォルトを確認できます。
config.git_guide_remote_name=操作説明内のgitコマンドで使うリポジトリリモート名
config.git_config=Git設定
config.git_disable_diff_highlight=Diffのシンタックスハイライトが無効

@ -159,7 +159,7 @@ filter.not_archived=Não Arquivados
filter.is_fork=Fork
filter.not_fork=Não Fork
filter.is_mirror=Espelhado
filter.not_mirror=Não espelhado
filter.not_mirror=Não Espelhado
filter.is_template=Template
filter.not_template=Não Modelo
filter.public=Pública
@ -529,8 +529,8 @@ release.new.text=<b>@%[1]s</b> lançou a versão %[2]s em %[3]s
release.title=Título: %s
release.note=Nota:
release.downloads=Downloads:
release.download.zip=Código Fonte (ZIP)
release.download.targz=Código Fonte (TAR.GZ)
release.download.zip=Código-Fonte (ZIP)
release.download.targz=Código-Fonte (TAR.GZ)
repo.transfer.subject_to=%s gostaria de transferir "%s" para %s
repo.transfer.subject_to_you=%s gostaria de transferir "%s" para você
@ -1010,6 +1010,7 @@ new_repo_helper=Um repositório contém todos os arquivos do projeto, inclusive
owner=Proprietário
owner_helper=Algumas organizações podem não aparecer no menu devido a um limite de contagem dos repositórios.
repo_name=Nome do repositório
repo_name_helper=Bons nomes de repositórios usam palavras-chave curtas, memorizáveis e únicas. Um repositório chamado ".profile" ou ".profile-private" pode ser usado para adicionar um README.md para seu perfil de usuário/organização.
repo_size=Tamanho do repositório
template=Modelo
template_select=Selecione um modelo.
@ -1021,7 +1022,7 @@ visibility_helper=Tornar o repositório privado
visibility_helper_forced=O administrador do site força novos repositórios a serem privados.
visibility_fork_helper=(Esta alteração irá afetar todos os forks.)
clone_helper=Precisa de ajuda com o clone? Visite a <a target="_blank" rel="noopener noreferrer" href="%s">Ajuda</a>.
fork_repo=Fork do repositório
fork_repo=Fork do Repositório
fork_from=Fork de
already_forked=Você já fez o fork de %s
fork_to_different_account=Faça um fork para uma conta diferente
@ -1030,12 +1031,13 @@ fork_branch=Branch a ser clonado para o fork
all_branches=Todos os branches
view_all_branches=Ver todos branches
view_all_tags=Ver todas as tags
fork_no_valid_owners=Não é possível fazer um fork desse repositório porque não há proprietários validos.
use_template=Usar este modelo
open_with_editor=Abrir com %s
download_zip=Baixar ZIP
download_tar=Baixar TAR.GZ
download_bundle=Baixar PACOTE
generate_repo=Gerar repositório
generate_repo=Gerar Repositório
generate_from=Gerar de
repo_desc=Descrição
repo_desc_helper=Digite uma breve descrição (opcional)
@ -1043,7 +1045,7 @@ repo_no_desc=Descrição não fornecida
repo_lang=Linguagens
repo_gitignore_helper=Selecione modelos do .gitignore.
repo_gitignore_helper_desc=Escolha os arquivos que não serão rastreados da lista de modelos para linguagens comuns. Artefatos típicos gerados pelos compiladores de cada linguagem estão incluídos no .gitignore por padrão.
issue_labels=Etiquetas de issue
issue_labels=Etiquetas de Issue
issue_labels_helper=Selecione um conjunto de etiquetas de issue.
license=Licença
license_helper=Selecione um arquivo de licença.
@ -1058,7 +1060,7 @@ trust_model_helper_collaborator=Colaborador: Confiar em assinaturas de colaborad
trust_model_helper_committer=Committer: Confiar em assinaturas que correspondem aos committers
trust_model_helper_collaborator_committer=Colaborador+Committer: Confiar em assinaturas dos colaboradores que correspondem ao committer
trust_model_helper_default=Padrão: Usar o modelo de confiança padrão para esta instalação
create_repo=Criar repositório
create_repo=Criar Repositório
default_branch=Branch Padrão
default_branch_label=padrão
default_branch_helper=O branch padrão é o branch base para pull requests e commits de código.
@ -1088,6 +1090,7 @@ stars=Favoritos
reactions_more=e %d mais
unit_disabled=O administrador do site desabilitou esta seção do repositório.
language_other=Outra
adopt_search=Digite o nome de usuário para pesquisar por repositórios órfãos… (deixe em branco para encontrar todos)
adopt_preexisting_label=Adotar Arquivos
adopt_preexisting=Adotar arquivos pré-existentes
adopt_preexisting_content=Criar repositório a partir de %s
@ -1125,6 +1128,8 @@ template.issue_labels=Etiquetas de issue
template.one_item=Deve-se selecionar pelo menos um item de modelo
template.invalid=Deve-se selecionar um repositório de modelo
archive.title=Este repositório está arquivado. Você pode visualizar arquivos e cloná-lo. Você não pode abrir issues ou pull requests ou fazer push de commits.
archive.title_date=Este repositório foi arquivado em %s. Você pode visualizar arquivos e cloná-lo. Você não pode abrir issues ou pull requests ou fazer push de commits.
archive.issue.nocomment=Este repositório está arquivado. Você não pode comentar nas issues.
archive.pull.nocomment=Este repositório está arquivado. Você não pode comentar nos pull requests.
@ -1141,6 +1146,7 @@ migrate_options_lfs=Migrar arquivos LFS
migrate_options_lfs_endpoint.label=Destino LFS
migrate_options_lfs_endpoint.description=A migração tentará usar seu controle remoto Git para <a target="_blank" rel="noopener noreferrer" href="%s">determinar o servidor LFS</a>. Você também pode especificar um destino personalizado se os dados do repositório LFS forem armazenados em outro lugar.
migrate_options_lfs_endpoint.description.local=Um caminho de servidor local também é suportado.
migrate_options_lfs_endpoint.placeholder=Se for deixado em branco, o destino será derivado do URL de clone.
migrate_items=Itens da migração
migrate_items_wiki=Wiki
migrate_items_milestones=Marcos
@ -1149,11 +1155,13 @@ migrate_items_issues=Issues
migrate_items_pullrequests=Pull Requests
migrate_items_merge_requests=Requisições de merge
migrate_items_releases=Versões
migrate_repo=Migrar repositório
migrate_repo=Migrar Repositório
migrate.clone_address=Migrar / Clonar de URL
migrate.clone_address_desc=URL HTTP (S) ou Git 'clone' de um repositório existente
migrate.github_token_desc=Você pode colocar um ou mais tokens aqui, separados por vírgulas, para tornar a migração mais rápida, contornando os limites da API do GitHub. AVISO: abusar deste recurso pode violar a política do provedor de serviços e pode levar a que a sua(s) conta(s) seja bloqueada.
migrate.clone_local_path=ou um caminho de servidor local
migrate.permission_denied=Você não pode importar repositórios locais.
migrate.permission_denied_blocked=Você não pode importar dos hosts não permitidos, por favor peça ao administrador para verificar as configurações ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS.
migrate.invalid_local_path=O caminho local é inválido. Ele não existe ou não é um diretório.
migrate.invalid_lfs_endpoint=O destino LFS não é válido.
migrate.failed=Migração falhou: %v
@ -1161,6 +1169,7 @@ migrate.migrate_items_options=Um Token de Acesso é necessário para migrar iten
migrated_from=Migrado de <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migrado de %[1]s
migrate.migrate=Migrar de %s
migrate.migrating=Migrando de <b>%s</b>…
migrate.migrating_failed=Migração a partir de <b>%s</b> falhou.
migrate.migrating_failed.error=Falha ao migrar: %s
migrate.migrating_failed_no_addr=A migração falhou.
@ -1172,14 +1181,19 @@ migrate.gogs.description=Migrar dados de notabug.org ou de outras instâncias do
migrate.onedev.description=Migrar dados de code.onedev.io ou de outras instâncias do OneDev.
migrate.codebase.description=Migrar dados de codebasehq.com.
migrate.gitbucket.description=Migrar dados de instâncias do GitBucket.
migrate.codecommit.description=Migrar dados do AWS CodeCommit.
migrate.codecommit.aws_access_key_id=Chave ID AWS (Access Key ID)
migrate.codecommit.aws_secret_access_key=Chave Secreta AWS (Secret Access Key)
migrate.codecommit.https_git_credentials_username=Nome de Usuário para Git HTTPS
migrate.codecommit.https_git_credentials_password=Senha para Git HTTPS
migrate.migrating_git=Migrando dados Git
migrate.migrating_topics=Migrando tópicos
migrate.migrating_topics=Migrando Tópicos
migrate.migrating_milestones=Migrando Marcos
migrate.migrating_labels=Migrando Rótulos
migrate.migrating_releases=Migrando Versões
migrate.migrating_issues=Migrando Issues
migrate.migrating_pulls=Migrando Pull Requests
migrate.cancel_migrating_title=Cancelar migração
migrate.cancel_migrating_title=Cancelar Migração
migrate.cancel_migrating_confirm=Você quer cancelar essa migração?
migration_status=Status da migração
@ -1195,7 +1209,8 @@ watch=Observar
unstar=Retirar dos favoritos
star=Juntar aos favoritos
fork=Fork
download_archive=Baixar repositório
action.blocked_user=Não é possível executar a ação porque você está bloqueado pelo proprietário do repositório.
download_archive=Baixar Repositório
more_operations=Mais Operações
quick_guide=Guia Rápido
@ -1203,6 +1218,7 @@ clone_this_repo=Clonar este repositório
cite_this_repo=Citar este repositório
create_new_repo_command=Criando um novo repositório por linha de comando
push_exist_repo=Realizando push para um repositório existente por linha de comando
empty_message=Este repositório está vazio.
broken_message=Os dados Git subjacentes a este repositório não podem ser lidos. Entre em contato com o administrador desta instância ou exclua este repositório.
code=Código
@ -1220,6 +1236,7 @@ projects=Projetos
packages=Pacotes
actions=Ações
labels=Etiquetas
org_labels_desc=Etiquetas a nível de organização que podem ser usadas com <strong>todos os repositórios</strong> sob esta organização
org_labels_desc_manage=gerenciar
milestone=Marco
@ -1234,9 +1251,9 @@ tagged_this=criou essa tag
file.title=%s em %s
file_raw=Original
file_history=Histórico
file_view_source=Exibir código-fonte
file_view_source=Exibir Código-Fonte
file_view_rendered=Ver Renderizado
file_view_raw=Ver original
file_view_raw=Ver Original
file_permalink=Link permanente
file_too_large=O arquivo é muito grande para ser mostrado.
file_is_empty=O arquivo está vazio.
@ -1254,9 +1271,9 @@ view_git_blame=Ver Git Blame
video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5.
audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5.
symbolic_link=Link simbólico
executable_file=Arquivo executável
executable_file=Arquivo Executável
generated=Gerado
commit_graph=Gráfico de commits
commit_graph=Gráfico de Commits
commit_graph.select=Selecionar branches
commit_graph.hide_pr_refs=Esconder Pull Requests
commit_graph.monochrome=Monocromático
@ -1272,34 +1289,35 @@ lines=linhas
from_comment=(comentário)
editor.add_file=Adicionar Arquivo
editor.new_file=Novo arquivo
editor.upload_file=Enviar arquivo
editor.edit_file=Editar arquivo
editor.preview_changes=Visualizar alterações
editor.new_file=Novo Arquivo
editor.upload_file=Enviar Arquivo
editor.edit_file=Editar Arquivo
editor.preview_changes=Visualizar Alterações
editor.cannot_edit_lfs_files=Arquivos LFS não podem ser editados na interface web.
editor.cannot_edit_too_large_file=O arquivo é muito grande para ser editado.
editor.cannot_edit_non_text_files=Arquivos binários não podem ser editados na interface web.
editor.file_not_editable_hint=Mas você ainda pode renomear ou movê-lo.
editor.edit_this_file=Editar arquivo
editor.edit_this_file=Editar Arquivo
editor.this_file_locked=Arquivo está bloqueado
editor.must_be_on_a_branch=Você deve estar em um branch para propor alterações neste arquivo.
editor.fork_before_edit=Você deve fazer um fork desse repositório para fazer ou propor alterações neste arquivo.
editor.delete_this_file=Excluir arquivo
editor.delete_this_file=Excluir Arquivo
editor.must_have_write_access=Você deve ter permissão de escrita para fazer ou propor alterações neste arquivo.
editor.file_delete_success=O arquivo "%s" foi excluído.
editor.name_your_file=Nomeie o seu arquivo…
editor.filename_help=Adicione um diretório digitando seu nome seguido por uma barra ('/'). Remova um diretório digitando o backspace no início do campo de entrada.
editor.or=ou
editor.cancel_lower=Cancelar
editor.commit_signed_changes=Commit de alteradores assinadas
editor.commit_changes=Aplicar commit das alterações
editor.commit_signed_changes=Criar Commit das Alterações Assinadas
editor.commit_changes=Criar Commit das Alterações
editor.add_tmpl=Adicionar '{filename}'
editor.add=Adicionar %s
editor.update=Atualizar %s
editor.delete=Excluir %s
editor.patch=Aplicar Correção
editor.patching=Corrigindo:
editor.new_patch=Nova correção
editor.fail_to_apply_patch=Não foi possível aplicar correção
editor.new_patch=Nova Correção
editor.commit_message_desc=Adicione uma descrição detalhada (opcional)...
editor.signoff_desc=Adicione um assinado-por-committer no final do log do commit.
editor.commit_directly_to_this_branch=Commit diretamente no branch <strong class="branch-name">%s</strong>.
@ -1323,7 +1341,7 @@ editor.commit_empty_file_text=O arquivo que você está prestes fazer commit est
editor.no_changes_to_show=Nenhuma alteração a mostrar.
editor.push_rejected_no_message=A alteração foi rejeitada pelo servidor sem uma mensagem. Por favor, verifique os Hooks Git.
editor.push_rejected=A alteração foi rejeitada pelo servidor. Por favor, verifique os Hooks Git.
editor.push_rejected_summary=Mensagem completa de rejeição:
editor.push_rejected_summary=Mensagem Completa de Rejeição:
editor.add_subdir=Adicionar um subdiretório...
editor.unable_to_upload_files=Ocorreu um erro ao enviar arquivos para "%s": %v
editor.upload_file_is_locked=Arquivo "%s" está bloqueado por %s.
@ -1338,11 +1356,12 @@ editor.failed_to_commit=Falha ao criar commit das alterações.
editor.failed_to_commit_summary=Mensagem de Erro:
commits.desc=Veja o histórico de alterações do código de fonte.
commits.desc=Veja o histórico de alterações do código-fonte.
commits.commits=Commits
commits.no_commits=Nenhum commit em comum. "%s" e "%s" tem históricos completamente diferentes.
commits.nothing_to_compare=Estes branches são iguais.
commits.search.tooltip=Você pode prefixar as palavras-chave com "author:" (autor da mudança), "committer:" (autor do commit), "after:" (depois) ou "before:" (antes). Por exemplo: "revert author:Ana before:2019-01-13".\
commits.search_branch=Este Branch
commits.search_all=Todos os branches
commits.author=Autor
commits.message=Mensagem
@ -1415,40 +1434,40 @@ issues.filter_labels=Filtrar Rótulo
issues.filter_reviewers=Filtrar Revisor
issues.filter_no_results=Nenhum resultado
issues.filter_no_results_placeholder=Tente ajustar seus filtros de pesquisa.
issues.new=Nova issue
issues.new=Nova Issue
issues.new.title_empty=Título não pode ser em branco
issues.new.labels=Etiquetas
issues.new.no_label=Sem etiqueta
issues.new.no_label=Sem Etiqueta
issues.new.clear_labels=Limpar etiquetas
issues.new.projects=Projetos
issues.new.clear_projects=Limpar projetos
issues.new.no_projects=Sem projeto
issues.new.open_projects=Abrir Projetos
issues.new.closed_projects=Projetos fechados
issues.new.closed_projects=Projetos Fechados
issues.new.no_items=Nenhum item
issues.new.milestone=Marco
issues.new.no_milestone=Sem marco
issues.new.no_milestone=Sem Marco
issues.new.clear_milestone=Limpar marco
issues.new.assignees=Responsáveis
issues.new.clear_assignees=Limpar responsáveis
issues.new.no_assignees=Sem responsável
issues.choose.get_started=Primeiros passos
issues.new.no_assignees=Sem Responsáveis
issues.choose.get_started=Primeiros Passos
issues.choose.open_external_link=Abrir
issues.choose.blank=Padrão
issues.choose.blank_about=Criar uma issue a partir do modelo padrão.
issues.choose.ignore_invalid_templates=Modelos inválidos foram ignorados
issues.choose.invalid_templates=%v modelo(s) inválido(s) encontrado(s)
issues.choose.invalid_config=A configuração da issue contém erros:
issues.no_ref=Nenhum branch/tag especificado
issues.create=Criar issue
issues.no_ref=Nenhum Branch/Tag Especificado
issues.create=Criar Issue
issues.new_label=Nova Etiqueta
issues.new_label_placeholder=Nome da etiqueta
issues.new_label_desc_placeholder=Descrição
issues.create_label=Criar etiqueta
issues.create_label=Criar Etiqueta
issues.label_templates.title=Carregue um conjunto de etiquetas pré-definidas
issues.label_templates.info=Ainda não existem etiquetas. Crie uma etiqueta em 'Nova etiqueta' ou use um conjunto de etiquetas predefinida:
issues.label_templates.helper=Selecione um conjunto de etiquetas
issues.label_templates.use=Use o conjunto de etiquetas
issues.label_templates.use=Usar Conjunto de Etiquetas
issues.label_templates.fail_to_load_file=Falha ao carregar o modelo de etiquetas "%s": %v
issues.add_label=adicionou o rótulo %s %s
issues.add_labels=adicionou os rótulos %s %s
@ -1531,11 +1550,11 @@ issues.commented_at=`comentou <a href="#%s">%s</a>`
issues.delete_comment_confirm=Tem certeza que deseja excluir este comentário?
issues.context.copy_link=Copiar Link
issues.context.quote_reply=Citar Resposta
issues.context.reference_issue=Referência em uma nova issue
issues.context.reference_issue=Referência em uma Nova Issue
issues.context.edit=Editar
issues.context.delete=Excluir
issues.no_content=Nenhuma descrição fornecida.
issues.close=Fechar issue
issues.close=Fechar Issue
issues.comment_pull_merged_at=aplicou o merge do commit %[1]s em %[2]s %[3]s
issues.comment_manually_pull_merged_at=aplicou o merge manual do commit %[1]s em %[2]s %[3]s
issues.close_comment_issue=Comentar e Fechar
@ -1568,7 +1587,7 @@ issues.re_request_review=Re-solicitar revisão
issues.is_stale=Houve alterações nessa PR desde essa revisão
issues.remove_request_review=Remover solicitação de revisão
issues.remove_request_review_block=Não é possível remover a solicitação de revisão
issues.dismiss_review=Descartar revisão
issues.dismiss_review=Descartar Revisão
issues.dismiss_review_warning=Tem certeza de que deseja descartar esta revisão?
issues.sign_in_require_desc=<a href="%s">Acesse</a> para participar desta conversação.
issues.edit=Editar
@ -1579,17 +1598,18 @@ issues.label_description=Descrição da etiqueta
issues.label_color=Cor da etiqueta
issues.label_color_invalid=Cor inválida
issues.label_exclusive=Exclusivo
issues.label_archive=Arquivar etiqueta
issues.label_archive=Arquivar Etiqueta
issues.label_archived_filter=Mostrar etiquetas arquivadas
issues.label_archive_tooltip=Etiquetas arquivadas são excluídas, por padrão, das sugestões ao pesquisar por etiqueta.
issues.label_exclusive_desc=Nomeie o rótulo <code>escopo/item</code> para torná-lo mutuamente exclusivo com outros rótulos do <code>escopo/</code>.
issues.label_exclusive_warning=Quaisquer rótulos com escopo conflitantes serão removidos ao editar os rótulos de uma issue ou pull request.
issues.label_exclusive_order=Ordem
issues.label_count=%d etiquetas
issues.label_open_issues=%d issues abertas
issues.label_edit=Editar
issues.label_delete=Excluir
issues.label_modify=Editar etiqueta
issues.label_deletion=Excluir etiqueta
issues.label_modify=Editar Etiqueta
issues.label_deletion=Excluir Etiqueta
issues.label_deletion_desc=A exclusão desta etiqueta irá removê-la de todas as issues. Tem certeza que deseja continuar?
issues.label_deletion_success=A etiqueta foi excluída.
issues.label.filter_sort.alphabetically=Alfabeticamente
@ -1643,7 +1663,7 @@ issues.del_time_history=`removeu tempo gasto %s`
issues.add_time_hours=Horas
issues.add_time_minutes=Minutos
issues.add_time_sum_to_small=Nenhum tempo foi inserido.
issues.time_spent_total=Tempo total gasto
issues.time_spent_total=Tempo Total Gasto
issues.time_spent_from_all_authors=`Tempo total gasto: %s`
issues.due_date=Data Limite
@ -1683,7 +1703,7 @@ issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das se
issues.dependency.issue_batch_close_blocked=Não é possível fechar as issues que você escolheu, porque a issue #%d ainda tem dependências abertas
issues.dependency.blocks_short=Bloqueia
issues.dependency.blocked_by_short=Depende de
issues.dependency.remove_header=Remover dependência
issues.dependency.remove_header=Remover Dependência
issues.dependency.issue_remove_text=Isto removerá a dependência desta issue. Continuar?
issues.dependency.pr_remove_text=Isto removerá a dependência deste pull request. Continuar?
issues.dependency.setting=Habilitar Dependências para Issues e Pull Requests
@ -1871,7 +1891,8 @@ milestones.no_due_date=Sem data limite
milestones.open=Reabrir
milestones.close=Fechar
milestones.new_subheader=Os marcos podem ajudá-lo a organizar os problemas e acompanhar seu progresso.
milestones.create=Criar marco
milestones.completeness=<strong>%d%%</strong> Concluído
milestones.create=Criar Marco
milestones.title=Título
milestones.desc=Descrição
milestones.due_date=Data limite (opcional)
@ -1883,7 +1904,7 @@ milestones.edit_subheader=Marcos organizam as issues e acompanham o progresso.
milestones.cancel=Cancelar
milestones.modify=Atualizar Marco
milestones.edit_success=O marco "%s" foi atualizado.
milestones.deletion=Excluir marco
milestones.deletion=Excluir Marco
milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar?
milestones.deletion_success=O marco foi excluído.
milestones.filter_sort.name=Nome
@ -1938,6 +1959,7 @@ wiki.original_git_entry_tooltip=Ver o arquivo Git original em vez de usar o link
activity=Atividade
activity.navbar.pulse=Pulso
activity.navbar.contributors=Contribuidores
activity.navbar.recent_commits=Commits Recentes
activity.period.filter_label=Período:
activity.period.daily=1 dia
activity.period.halfweekly=3 dias
@ -2044,6 +2066,7 @@ settings.branches.add_new_rule=Adicionar Nova Regra
settings.advanced_settings=Configurações avançadas
settings.wiki_desc=Habilitar a wiki do repositório
settings.use_internal_wiki=Usar a wiki nativa
settings.default_wiki_branch_name=Nome Padrão do Branch da Wiki
settings.use_external_wiki=Usar wiki externa
settings.external_wiki_url=URL externa da wiki
settings.external_wiki_url_error=A URL da wiki externa não é válida.
@ -2073,6 +2096,7 @@ settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull reques
settings.pulls.default_allow_edits_from_maintainers=Permitir edições de mantenedores por padrão
settings.releases_desc=Habilitar versões do Repositório
settings.packages_desc=Habilitar Registro de Pacotes de Repositório
settings.projects_desc=Habilitar Projetos
settings.projects_mode_all=Todos os projetos
settings.actions_desc=Habilitar ações do repositório
settings.admin_settings=Configurações do administrador
@ -2083,6 +2107,7 @@ settings.admin_indexer_commit_sha=Último SHA indexado
settings.admin_indexer_unindexed=Não indexado
settings.reindex_button=Adicionar à fila de reindexação
settings.reindex_requested=Reindexação Requisitada
settings.admin_enable_close_issues_via_commit_in_any_branch=Fechar issue via commit em um branch não padrão
settings.danger_zone=Zona de perigo
settings.new_owner_has_same_repo=O novo proprietário já tem um repositório com o mesmo nome. Por favor, escolha outro nome.
settings.convert=Converter para repositório tradicional
@ -2100,6 +2125,7 @@ settings.transfer.rejected=A transferência do repositório foi rejeitada.
settings.transfer.success=A transferência do repositório foi bem sucedida.
settings.transfer_abort=Cancelar transferência
settings.transfer_abort_invalid=Não é possível cancelar uma transferência de repositório não existente.
settings.transfer_abort_success=A transferência do repositório para %s foi cancelada com sucesso.
settings.transfer_desc=Transferir este repositório para outro usuário ou para uma organização onde você tem direitos de administrador.
settings.transfer_form_title=Digite o nome do repositório para confirmar:
settings.transfer_notices_1=- Você perderá o acesso ao repositório se transferir para um usuário individual.
@ -2373,16 +2399,16 @@ settings.rename_branch_from=nome antigo do branch
settings.rename_branch_to=novo nome do branch
settings.rename_branch=Renomear branch
diff.browse_source=Ver código fonte
diff.browse_source=Ver Código-Fonte
diff.parent=pai
diff.commit=commit
diff.git-notes=Notas
diff.data_not_available=Conteúdo de diff não disponível
diff.options_button=Opções de diferenças
diff.download_patch=Baixar arquivo de patch
diff.download_diff=Baixar arquivo de diferenças
diff.show_split_view=Visão dividida
diff.show_unified_view=Visão unificada
diff.data_not_available=Conteúdo de Diff Não Disponível
diff.options_button=Opções de Diff
diff.download_patch=Baixar Arquivo de Patch
diff.download_diff=Baixar Arquivo de Diff
diff.show_split_view=Visão Dividida
diff.show_unified_view=Visão Unificada
diff.whitespace_button=Espaço em branco
diff.whitespace_show_everything=Mostrar todas as alterações
diff.whitespace_ignore_all_whitespace=Ignorar todas as alterações de espaço em branco
@ -2392,7 +2418,7 @@ diff.stats_desc=<strong> %d arquivos alterados</strong> com <strong>%d adições
diff.stats_desc_file=%d alterações: %d adições e %d exclusões
diff.bin=BIN
diff.bin_not_shown=Arquivo binário não exibido.
diff.view_file=Ver arquivo
diff.view_file=Ver Arquivo
diff.file_before=Antes
diff.file_after=Depois
diff.file_image_width=Largura
@ -2401,7 +2427,7 @@ diff.file_byte_size=Tamanho
diff.file_suppressed=Diferenças do arquivo suprimidas por serem muito extensas
diff.file_suppressed_line_too_long=Diff do arquivo suprimido porque uma ou mais linhas são muito longas
diff.too_many_files=Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff
diff.show_more=Mostrar mais
diff.show_more=Mostrar Mais
diff.load=Carregar Diff
diff.generated=gerado
diff.vendored=externo
@ -2441,7 +2467,7 @@ release.edit=editar
release.ahead.commits=<strong>%d</strong> commits
release.ahead.target=para %s desde esta versão
tag.ahead.target=para %s desde esta tag
release.source_code=Código Fonte
release.source_code=Código-Fonte
release.new_subheader=Lançamentos organizam versões do projeto.
release.edit_subheader=Lançamentos organizam versões do projeto.
release.tag_name=Nome da tag
@ -2667,8 +2693,8 @@ view_as_member_hint=Você está vendo o README como um membro desta organizaçã
maintenance=Manutenção
dashboard=Painel
self_check=Auto-verificação
identity_access=Identidade e acesso
users=Contas de usuário
identity_access=Identidade e Acesso
users=Contas de Usuário
organizations=Organizações
repositories=Repositórios
hooks=Webhooks
@ -2687,8 +2713,8 @@ settings=Configurações de Administrador
dashboard.statistic=Resumo
dashboard.maintenance_operations=Operações de Manutenção
dashboard.system_status=Status do sistema
dashboard.operation_name=Nome da operação
dashboard.system_status=Status do Sistema
dashboard.operation_name=Nome da Operação
dashboard.operation_switch=Trocar
dashboard.operation_run=Executar
dashboard.clean_unbind_oauth=Limpar conexões OAuth não vinculadas
@ -2706,7 +2732,7 @@ dashboard.delete_inactive_accounts=Excluir todas as contas não ativadas
dashboard.delete_repo_archives=Excluir todos os arquivos dos repositórios (ZIP, TAR.GZ, etc..)
dashboard.delete_missing_repos=Excluir todos os repositórios que não possuem seus arquivos Git
dashboard.delete_generated_repository_avatars=Excluir avatares gerados do repositório
dashboard.update_mirrors=Atualizar espelhamentos
dashboard.update_mirrors=Atualizar Espelhamentos
dashboard.repo_health_check=Verificar estado de saúde de todos os repositórios
dashboard.check_repo_stats=Verificar estatísticas de todos os repositórios
dashboard.archive_cleanup=Apagar arquivos antigos de repositório
@ -2720,27 +2746,27 @@ dashboard.cleanup_packages=Limpar pacotes expirados
dashboard.cleanup_actions=Limpar recursos de actions expiradas
dashboard.server_uptime=Tempo de atividade do Servidor
dashboard.current_goroutine=Goroutines Atuais
dashboard.current_memory_usage=Uso de memória atual
dashboard.total_memory_allocated=Total de memória alocada
dashboard.memory_obtained=Memória obtida
dashboard.pointer_lookup_times=Nº de consultas a ponteiros
dashboard.memory_allocate_times=Alocações de memória
dashboard.memory_free_times=Liberações de memória
dashboard.current_heap_usage=Uso atual da heap
dashboard.heap_memory_obtained=Memória de heap obtida
dashboard.heap_memory_idle=Memória da heap ociosa
dashboard.heap_memory_in_use=Memória da heap em uso
dashboard.heap_memory_released=Memória da heap liberada
dashboard.heap_objects=Objetos na heap
dashboard.bootstrap_stack_usage=Uso de pilha bootstrap
dashboard.stack_memory_obtained=Memória de pilha obtida
dashboard.current_memory_usage=Uso de Memória Atual
dashboard.total_memory_allocated=Total de Memória Alocada
dashboard.memory_obtained=Memória Obtida
dashboard.pointer_lookup_times=Nº de Consultas a Ponteiros
dashboard.memory_allocate_times=Alocações de Memória
dashboard.memory_free_times=Liberações de Memória
dashboard.current_heap_usage=Uso Atual da Heap
dashboard.heap_memory_obtained=Memória de Heap Obtida
dashboard.heap_memory_idle=Memória da Heap Ociosa
dashboard.heap_memory_in_use=Memória da Heap em Uso
dashboard.heap_memory_released=Memória da Heap Liberada
dashboard.heap_objects=Objetos na Heap
dashboard.bootstrap_stack_usage=Uso de Pilha Bootstrap
dashboard.stack_memory_obtained=Memória de Pilha Obtida
dashboard.mspan_structures_usage=Uso de estruturas de MSpan
dashboard.mspan_structures_obtained=Estruturas de MSpan obtidas
dashboard.mcache_structures_usage=Uso de estruturas de MCache
dashboard.mcache_structures_obtained=Estruturas de MCache obtidas
dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da Bucket Hash Table
dashboard.gc_metadata_obtained=Metadados do GC obtidos
dashboard.other_system_allocation_obtained=Outra alocação de sistema obtida
dashboard.other_system_allocation_obtained=Outra Alocação de Sistema Obtida
dashboard.next_gc_recycle=Próxima reciclagem do GC
dashboard.last_gc_time=Desde da ultima vez do GC
dashboard.total_gc_time=Pausa total do GC
@ -2752,7 +2778,7 @@ dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos d
dashboard.sync_tag.started=Sincronização de Tags iniciada
dashboard.rebuild_issue_indexer=Reconstruir indexador de issues
users.user_manage_panel=Gerenciamento de conta de usuário
users.user_manage_panel=Gerenciamento de Conta de Usuário
users.new_account=Criar Conta de Usuário
users.name=Nome de usuário
users.full_name=Nome Completo
@ -2764,33 +2790,33 @@ users.remote=Remoto
users.2fa=2FA
users.repos=Repositórios
users.created=Criado
users.last_login=Último acesso
users.last_login=Último Acesso
users.never_login=Nunca Acessado
users.send_register_notify=Enviar notificação de cadastro de usuário
users.send_register_notify=Enviar Notificação de Cadastro de Usuário
users.new_success=Usuário "%s" criado.
users.edit=Editar
users.auth_source=Fonte da autenticação
users.auth_source=Fonte da Autenticação
users.local=Local
users.auth_login_name=Nome de acesso da autenticação
users.auth_login_name=Nome de Acesso da Autenticação
users.password_helper=Deixe a senha em branco para mantê-la inalterada.
users.update_profile_success=A conta de usuário foi atualizada.
users.edit_account=Editar Conta de Usuário
users.max_repo_creation=Número máximo de repositórios
users.max_repo_creation=Número Máximo de Repositórios
users.max_repo_creation_desc=(Use -1 para usar o limite padrão global.)
users.is_activated=Conta de usuário está ativada
users.prohibit_login=Desabilitar acesso
users.is_admin=É administrador
users.is_restricted=Está restrito
users.is_activated=Conta de Usuário está Ativada
users.prohibit_login=Desabilitar Acesso
users.is_admin=É Administrador
users.is_restricted=Está Restrito
users.allow_git_hook=Pode criar hooks Git
users.allow_git_hook_tooltip=Hooks Git são executados como o usuário do SO que executa Gitea e terá o mesmo nível de acesso ao servidor. Como resultado, os usuários com esse privilégio especial de Hook do Git podem acessar e modificar todos os repositórios do Gitea, bem como o banco de dados usado pelo Gitea. Por conseguinte, podem também obter privilégios de administrador do Gitea.
users.allow_import_local=Pode importar repositórios locais
users.allow_create_organization=Pode criar organizações
users.allow_import_local=Pode Importar Repositórios Locais
users.allow_create_organization=Pode Criar Organizações
users.update_profile=Atualizar Conta de Usuário
users.delete_account=Excluir conta de usuário
users.delete_account=Excluir Conta de Usuário
users.cannot_delete_self=Você não pode excluir você mesmo
users.still_own_repo=Este usuário ainda possui um ou mais repositórios. Exclua ou transfira esses repositórios primeiro.
users.still_has_org=Este usuário é membro de uma organização. Remova o usuário de qualquer organização primeiro.
users.purge=Eliminar usuário
users.purge=Eliminar Usuário
users.purge_help=Exclua forçosamente o usuário e quaisquer repositórios, organizações e pacotes pertencentes ao usuário. Todos os comentários também serão excluídos.
users.deletion_success=A conta de usuário foi excluída.
users.reset_2fa=Reinicializar 2FA
@ -2801,17 +2827,20 @@ users.list_status_filter.not_active=Inativo
users.list_status_filter.is_admin=Administrador
users.list_status_filter.not_admin=Não Administrador
users.list_status_filter.is_restricted=Restrito
users.list_status_filter.not_restricted=Não restrito
users.list_status_filter.is_prohibit_login=Proibir login
users.list_status_filter.not_prohibit_login=Permitir login
users.list_status_filter.not_restricted=Não Restrito
users.list_status_filter.is_prohibit_login=Proibir Login
users.list_status_filter.not_prohibit_login=Permitir Login
users.list_status_filter.is_2fa_enabled=2FA Ativado
users.list_status_filter.not_2fa_enabled=2FA Desativado
users.details=Detalhes do usuário
users.details=Detalhes do Usuário
emails.email_manage_panel=Gerenciamento de E-mail de Usuário
emails.primary=Principal
emails.activated=Ativado
emails.filter_sort.name=Nome de usuário
emails.filter_sort.email=E-mail
emails.filter_sort.email_reverse=E-mail (reverso)
emails.filter_sort.name=Nome de Usuário
emails.filter_sort.name_reverse=Nome de Usuário (reverso)
emails.updated=Endereço de e-mail atualizado
emails.not_updated=Falha ao atualizar o endereço de e-mail solicitado: %v
emails.duplicate_active=Este endereço de e-mail já está ativo para um usuário diferente.
@ -2820,13 +2849,13 @@ emails.change_email_text=Tem certeza que deseja atualizar este e-mail?
emails.delete=Excluir E-mail
emails.delete_desc=Tem certeza que deseja excluir este e-mail?
orgs.org_manage_panel=Gerenciamento da organização
orgs.org_manage_panel=Gerenciamento da Organização
orgs.name=Nome
orgs.teams=Equipes
orgs.members=Membros
orgs.new_orga=Nova organização
orgs.new_orga=Nova Organização
repos.repo_manage_panel=Gerenciamento do repositório
repos.repo_manage_panel=Gerenciamento do Repositório
repos.unadopted=Repositórios Não Adotados
repos.unadopted.no_more=Não foram encontrados mais repositórios não adotados
repos.owner=Proprietário
@ -2965,33 +2994,33 @@ auths.login_source_of_type_exist=Uma fonte de autenticação deste tipo já exis
auths.unable_to_initialize_openid=Não é possível inicializar o Provedor OpenID Connect: %s
auths.invalid_openIdConnectAutoDiscoveryURL=URL do Auto Discovery inválida (deve ser uma URL válida, começando com http:// ou https://)
config.server_config=Configuração do servidor
config.server_config=Configuração do Servidor
config.app_name=Nome do Site
config.app_ver=Versão do Gitea
config.app_url=URL base do Gitea
config.custom_conf=Caminho do Arquivo de Configuração
config.custom_file_root_path=Caminho Raiz para Arquivo Personalizado
config.domain=Domínio do Servidor
config.offline_mode=Modo local
config.offline_mode=Modo Local
config.disable_router_log=Desabilitar o Log do roteador
config.run_user=Executar como nome de usuário
config.run_user=Executar como Usuário
config.run_mode=Modo de Execução
config.git_version=Versão do Git
config.repo_root_path=Caminho raiz do repositório
config.repo_root_path=Caminho Raiz do Repositório
config.lfs_root_path=Caminho raiz do LFS
config.log_file_root_path=Caminho do log
config.script_type=Tipo de script
config.reverse_auth_user=Usuário de autenticação reversa
config.log_file_root_path=Caminho do Log
config.script_type=Tipo de Script
config.reverse_auth_user=Usuário de Autenticação Reversa
config.ssh_config=Configuração de SSH
config.ssh_enabled=Habilitado
config.ssh_start_builtin_server=Usar o servidor embutido
config.ssh_start_builtin_server=Usar o Servidor Embutido
config.ssh_domain=Domínio do servidor SSH
config.ssh_port=Porta
config.ssh_listen_port=Porta de escuta
config.ssh_root_path=Caminho da raiz
config.ssh_minimum_key_size_check=Verificar tamanho mínimo da chave
config.ssh_minimum_key_sizes=Tamanhos mínimos da chave
config.ssh_listen_port=Porta de Escuta
config.ssh_root_path=Caminho da Raiz
config.ssh_minimum_key_size_check=Verificar Tamanho Mínimo da Chave
config.ssh_minimum_key_sizes=Tamanhos Mínimos da Chave
config.lfs_config=Configuração de LFS
config.lfs_enabled=Habilitado
@ -3007,30 +3036,30 @@ config.db_schema=Esquema
config.db_ssl_mode=SSL
config.db_path=Caminho
config.service_config=Configuração do serviço
config.register_email_confirm=Exigir confirmação de e-mail para se cadastrar
config.service_config=Configuração do Serviço
config.register_email_confirm=Exigir Confirmação de E-mail para se Cadastrar
config.disable_register=Desabilitar Auto-Cadastro
config.allow_only_internal_registration=Permitir Registro Somente Através do Próprio Gitea
config.allow_only_external_registration=Permitir Cadastro Somente por Meio de Serviços Externos
config.enable_openid_signup=Habilitar o auto-cadastro via OpenID
config.enable_openid_signin=Habilitar acesso via OpenID
config.show_registration_button=Mostrar botão de cadastro
config.require_sign_in_view=Exigir acesso do usuário para a visualização de páginas
config.mail_notify=Habilitar notificações de e-mail
config.show_registration_button=Mostrar Botão de Cadastro
config.require_sign_in_view=Exigir Acesso do Usuário para a Visualização de Páginas
config.mail_notify=Habilitar Notificações de E-mail
config.enable_captcha=Habilitar o CAPTCHA
config.active_code_lives=Ativar Code Lives
config.reset_password_code_lives=Tempo de expiração do código de recuperação de conta
config.reset_password_code_lives=Tempo de Expiração do Código de Recuperação de Conta
config.default_keep_email_private=Ocultar Endereços de E-mail por Padrão
config.default_allow_create_organization=Permitir a Criação de Organizações por Padrão
config.enable_timetracking=Habilitar Cronômetro
config.default_enable_timetracking=Habilitar o Cronômetro por Padrão
config.default_allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o cronômetro
config.no_reply_address=Ocultar domínio de e-mail
config.default_allow_only_contributors_to_track_time=Permitir que Apenas os Colaboradores Acompanhem o Cronômetro
config.no_reply_address=Ocultar Domínio de E-mail
config.default_visibility_organization=Visibilidade padrão para novas organizações
config.webhook_config=Configuração de Hook da Web
config.queue_length=Tamanho da fila
config.deliver_timeout=Intervalo de entrega
config.queue_length=Tamanho da Fila
config.deliver_timeout=Intervalo de Entrega
config.skip_tls_verify=Ignorar verificação de TLS
config.mailer_config=Configuração de Envio de E-mail
@ -3047,7 +3076,7 @@ config.mailer_sendmail_args=Argumentos extras para o Sendmail
config.mailer_sendmail_timeout=Tempo limite do Sendmail
config.mailer_use_dummy=Dummy
config.test_email_placeholder=E-mail (por exemplo, teste@exemplo.com.br)
config.send_test_mail=Enviar e-mail de teste
config.send_test_mail=Enviar E-mail de Teste
config.send_test_mail_submit=Enviar
config.test_mail_failed=Ocorreu um erro ao enviar um e-mail de teste para "%s": %v
config.test_mail_sent=Um e-mail de teste foi enviado para "%s".
@ -3061,33 +3090,33 @@ config.cache_interval=Intervalo de Cache
config.cache_conn=Conexão de Cache
config.cache_item_ttl=Item de cache TTL
config.session_config=Configuração da sessão
config.session_provider=Provedor da sessão
config.provider_config=Configuração do provedor
config.session_config=Configuração da Sessão
config.session_provider=Provedor da Sessão
config.provider_config=Configuração do Provedor
config.cookie_name=Nome do Cookie
config.gc_interval_time=Tempo de Intervalo do GC
config.session_life_time=Tempo de vida da sessão
config.session_life_time=Tempo de Vida da Sessão
config.https_only=Apenas HTTPS
config.cookie_life_time=Tempo de Vida do Cookie
config.picture_config=Configuração de imagem e avatar
config.picture_service=Serviço de imagens
config.picture_config=Configuração de Imagem e Avatar
config.picture_service=Serviço de Imagens
config.disable_gravatar=Desabilitar o Gravatar
config.enable_federated_avatar=Habilitar avatares federativos
config.enable_federated_avatar=Habilitar Avatares Federativos
config.git_config=Configuração do Git
config.git_disable_diff_highlight=Desabilitar realce de mudanças no diff
config.git_disable_diff_highlight=Desabilitar Realce de Mudanças no Diff
config.git_max_diff_lines=Máximo de linhas mostradas no diff (para um único arquivo)
config.git_max_diff_line_characters=Máximo de caracteres mostrados no diff (para uma única linha)
config.git_max_diff_files=Máximo de arquivos a serem mostrados no diff
config.git_max_diff_files=Máximo de Arquivos a Serem Mostrados no Diff
config.git_gc_args=Argumentos do GC
config.git_migrate_timeout=Tempo limite de migração
config.git_mirror_timeout=Tempo limite de atualização de espelhamento
config.git_clone_timeout=Tempo limite para operação de clone
config.git_pull_timeout=Tempo limite para operação de pull
config.git_migrate_timeout=Tempo Limite de Migração
config.git_mirror_timeout=Tempo Limite de Atualização de Espelhamento
config.git_clone_timeout=Tempo Limite para Operação de Clone
config.git_pull_timeout=Tempo Limite para Operação de Pull
config.git_gc_timeout=Tempo limite para execução do GC
config.log_config=Configuração de log
config.log_config=Configuração de Log
config.logger_name_fmt=Logger: %s
config.disabled_logger=Desabilitado
config.access_log_mode=Modo log Access
@ -3098,18 +3127,18 @@ config.set_setting_failed=Falha ao definir configuração %s
monitor.stats=Estatísticas
monitor.cron=Tarefas cron
monitor.cron=Tarefas Cron
monitor.name=Nome
monitor.schedule=Cronograma
monitor.next=Próxima vez
monitor.previous=Vez anterior
monitor.next=Próxima Vez
monitor.previous=Vez Anterior
monitor.execute_times=Execuções
monitor.process=Processos em execução
monitor.process=Processos em Execução
monitor.processes_count=%d processos
monitor.download_diagnosis_report=Baixar relatório de diagnóstico
monitor.desc=Descrição
monitor.start=Hora de início
monitor.execute_time=Tempo de execução
monitor.start=Hora de Início
monitor.execute_time=Tempo de Execução
monitor.last_execution_result=Resultado
monitor.process.cancel=Cancelar processo
monitor.process.cancel_desc=Cancelar um processo pode causar perda de dados
@ -3119,26 +3148,29 @@ monitor.queues=Filas
monitor.queue=Fila: %s
monitor.queue.name=Nome
monitor.queue.type=Tipo
monitor.queue.exemplar=Tipo de modelo
monitor.queue.numberworkers=Número de executores
monitor.queue.maxnumberworkers=Número máximo de executores
monitor.queue.exemplar=Tipo de Modelo
monitor.queue.numberworkers=Número de Executores
monitor.queue.activeworkers=Executores Ativos
monitor.queue.maxnumberworkers=Número Máximo de Executores
monitor.queue.numberinqueue=Número na Fila
monitor.queue.settings.title=Configurações do conjunto
monitor.queue.review_add=Revisar / Adicionar Executores
monitor.queue.settings.title=Configurações do Conjunto
monitor.queue.settings.maxnumberworkers=Número máximo de executores
monitor.queue.settings.maxnumberworkers.placeholder=Atualmente %[1]d
monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve ser um número
monitor.queue.settings.submit=Atualizar configurações
monitor.queue.settings.changed=Configurações atualizadas
monitor.queue.settings.submit=Atualizar Configurações
monitor.queue.settings.changed=Configurações Atualizadas
monitor.queue.settings.remove_all_items=Remover tudo
monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos.
notices.system_notice_list=Avisos do Sistema
notices.view_detail_header=Ver detalhes do aviso
notices.view_detail_header=Ver Detalhes do Aviso
notices.operations=Operações
notices.select_all=Marcar todos
notices.deselect_all=Desmarcar todos
notices.inverse_selection=Inverter seleção
notices.select_all=Marcar Todos
notices.deselect_all=Desmarcar Todos
notices.inverse_selection=Inverter Seleção
notices.delete_selected=Excluir Seleção
notices.delete_all=Excluir todos os avisos
notices.delete_all=Excluir Todos os Avisos
notices.type=Tipo
notices.type_1=Repositório
notices.type_2=Tarefa

@ -120,6 +120,7 @@ error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您
error503=服务器无法完成您的请求,请稍后重试。
go_back=返回
invalid_data=无效数据: %v
nothing_has_been_changed=没有任何更改。
never=从不
unknown=未知
@ -2844,6 +2845,11 @@ settings.location=所在地区
settings.permission=权限
settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问权限
settings.visibility=可见性
settings.change_visibility=更改可见性
settings.change_visibility_notices_1=如果组织被转换为私有,仓库的所有点赞将被删除且无法恢复。
settings.change_visibility_notices_2=如果可见性更改为私有,非成员将无法访问该组织的仓库。
settings.change_visibility_success=组织 %s 的可见性已成功更改。
settings.visibility_desc=更改谁可以查看组织及其仓库。
settings.visibility.public=公开
settings.visibility.limited=受限 (仅对认证用户可见)
settings.visibility.limited_shortname=受限
@ -3420,6 +3426,7 @@ config.picture_service=图片服务
config.disable_gravatar=禁用 Gravatar 头像
config.enable_federated_avatar=启用 Federated 头像
config.open_with_editor_app_help=用于克隆菜单的编辑器。如果为空将使用默认值。展开可以查看默认值。
config.git_guide_remote_name=指南中 git 命令使用的仓库远程名称
config.git_config=Git 配置
config.git_disable_diff_highlight=禁用差异对比语法高亮

14391
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,7 +1,9 @@
{
"type": "module",
"packageManager": "pnpm@10.0.0",
"engines": {
"node": ">= 20.0.0"
"node": ">= 20.0.0",
"pnpm": ">=10.0.0"
},
"dependencies": {
"@citation-js/core": "0.7.18",
@ -14,6 +16,7 @@
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.15.5",
"@silverwind/vue3-calendar-heatmap": "2.0.6",
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
"add-asset-webpack-plugin": "3.0.0",
"ansi_up": "6.0.6",
"asciinema-player": "3.10.0",
@ -32,7 +35,6 @@
"idiomorph": "0.7.3",
"jquery": "3.7.1",
"katex": "0.16.22",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "11.10.0",
"mini-css-extract-plugin": "2.9.2",
"minimatch": "10.0.3",
@ -69,6 +71,7 @@
"@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "3.1.0",
"@stylistic/stylelint-plugin": "4.0.0",
"@types/codemirror": "5.60.16",
"@types/dropzone": "5.7.9",
"@types/jquery": "3.5.32",
"@types/katex": "0.16.7",

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

@ -1256,7 +1256,7 @@ func Routes() *web.Router {
m.Get("/*", repo.GetBranch)
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections)

@ -380,11 +380,11 @@ func ListBranches(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, apiBranches)
}
// UpdateBranch updates a repository's branch.
func UpdateBranch(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
// RenameBranch renames a repository's branch.
func RenameBranch(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
// ---
// summary: Update a branch
// summary: Rename a branch
// consumes:
// - application/json
// produces:
@ -408,7 +408,7 @@ func UpdateBranch(ctx *context.APIContext) {
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateBranchRepoOption"
// "$ref": "#/definitions/RenameBranchRepoOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
@ -419,7 +419,7 @@ func UpdateBranch(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
opt := web.GetForm(ctx).(*api.RenameBranchRepoOption)
oldName := ctx.PathParam("*")
repo := ctx.Repo.Repository

@ -1085,7 +1085,7 @@ func MergePullRequest(ctx *context.APIContext) {
type parseCompareInfoResult struct {
headRepo *repo_model.Repository
headGitRepo *git.Repository
compareInfo *git.CompareInfo
compareInfo *pull_service.CompareInfo
baseRef git.RefName
headRef git.RefName
}
@ -1201,7 +1201,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
return nil, nil
}
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false)
compareInfo, err := pull_service.GetCompareInfo(ctx, baseRepo, headRepo, headGitRepo, baseRef.ShortName(), headRef.ShortName(), false, false)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
@ -1452,7 +1452,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
return
}
var prInfo *git.CompareInfo
var prInfo *pull_service.CompareInfo
baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
if err != nil {
ctx.APIErrorInternal(err)
@ -1461,9 +1461,9 @@ func GetPullRequestCommits(ctx *context.APIContext) {
defer closer.Close()
if pr.HasMerged {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitHeadRefName(), false, false)
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.MergeBase, pr.GetGitHeadRefName(), false, false)
} else {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitHeadRefName(), false, false)
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.BaseBranch, pr.GetGitHeadRefName(), false, false)
}
if err != nil {
ctx.APIErrorInternal(err)
@ -1582,11 +1582,11 @@ func GetPullRequestFiles(ctx *context.APIContext) {
baseGitRepo := ctx.Repo.GitRepo
var prInfo *git.CompareInfo
var prInfo *pull_service.CompareInfo
if pr.HasMerged {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitHeadRefName(), true, false)
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.MergeBase, pr.GetGitHeadRefName(), true, false)
} else {
prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitHeadRefName(), true, false)
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.BaseBranch, pr.GetGitHeadRefName(), true, false)
}
if err != nil {
ctx.APIErrorInternal(err)

@ -736,14 +736,14 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
// Default branch only updated if changed and exist or the repository is empty
updateRepoLicense := false
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, *opts.DefaultBranch)) {
repo.DefaultBranch = *opts.DefaultBranch
if !repo.IsEmpty {
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
ctx.APIErrorInternal(err)
return err
}
updateRepoLicense = true
}
repo.DefaultBranch = *opts.DefaultBranch
}
if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {

@ -90,7 +90,7 @@ type swaggerParameterBodies struct {
// in:body
EditRepoOption api.EditRepoOption
// in:body
UpdateBranchRepoOption api.UpdateBranchRepoOption
RenameBranchRepoOption api.RenameBranchRepoOption
// in:body
TransferRepoOption api.TransferRepoOption
// in:body

@ -7,6 +7,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
pull_service "code.gitea.io/gitea/services/pull"
)
// CompareInfo represents the collected results from ParseCompareInfo
@ -14,7 +15,7 @@ type CompareInfo struct {
HeadUser *user_model.User
HeadRepo *repo_model.Repository
HeadGitRepo *git.Repository
CompareInfo *git.CompareInfo
CompareInfo *pull_service.CompareInfo
BaseBranch string
HeadBranch string
DirectComparison bool

@ -27,7 +27,7 @@ import (
const (
tplConfig templates.TplName = "admin/config"
tplConfigSettings templates.TplName = "admin/config_settings"
tplConfigSettings templates.TplName = "admin/config_settings/config_settings"
)
// SendTestMail send test mail to confirm mail service is OK

@ -41,6 +41,7 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull"
)
const (
@ -550,7 +551,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
headBranchRef = git.TagPrefix + ci.HeadBranch
}
ci.CompareInfo, err = ci.HeadGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, ci.DirectComparison, fileOnly)
ci.CompareInfo, err = pull_service.GetCompareInfo(ctx, baseRepo, ci.HeadRepo, ci.HeadGitRepo, baseBranchRef, headBranchRef, ci.DirectComparison, fileOnly)
if err != nil {
ctx.ServerError("GetCompareInfo", err)
return nil
@ -707,12 +708,6 @@ func PrepareCompareDiff(
}
func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repository) (branches, tags []string, err error) {
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return nil, nil, err
}
defer gitRepo.Close()
branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID,
ListOptions: db.ListOptionsAll,
@ -721,7 +716,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
if err != nil {
return nil, nil, err
}
tags, err = gitRepo.GetTags(0, 0)
tags, err = repo_model.GetTagNamesByRepoID(ctx, repo.ID)
if err != nil {
return nil, nil, err
}

@ -254,7 +254,7 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri
return baseCommit
}
func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
if !issue.IsPull {
return nil
}
@ -265,7 +265,7 @@ func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *g
}
// prepareMergedViewPullInfo show meta information for a merged pull request view page
func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
pull := issue.PullRequest
setMergeTarget(ctx, pull)
@ -273,7 +273,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
baseCommit := GetMergedBaseCommitID(ctx, issue)
compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
compareInfo, err := pull_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo,
baseCommit, pull.GetGitHeadRefName(), false, false)
if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
@ -311,7 +311,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
}
// prepareViewPullInfo show meta information for a pull request preview page
func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.CompareInfo {
func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo {
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
repo := ctx.Repo.Repository
@ -373,7 +373,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
}
compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
compareInfo, err := pull_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
pull.MergeBase, pull.GetGitHeadRefName(), false, false)
if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
@ -521,7 +521,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
}
}
compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
compareInfo, err := pull_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
git.BranchPrefix+pull.BaseBranch, pull.GetGitHeadRefName(), false, false)
if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") {

@ -16,6 +16,7 @@ import (
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/indexer/stats"
@ -258,7 +259,7 @@ func handleSettingsPostMirror(ctx *context.Context) {
return
}
u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName())
u, err := gitrepo.GitRemoteGetURL(ctx, ctx.Repo.Repository, pullMirror.GetRemoteName())
if err != nil {
ctx.Data["Err_MirrorAddress"] = true
handleSettingRemoteAddrError(ctx, err, form)

@ -33,6 +33,7 @@ import (
"code.gitea.io/gitea/services/forms"
git_service "code.gitea.io/gitea/services/git"
notify_service "code.gitea.io/gitea/services/notify"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
)
@ -474,7 +475,7 @@ func Wiki(ctx *context.Context) {
return
}
if !ctx.Repo.Repository.HasWiki() {
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(http.StatusOK, tplWikiStart)
return
@ -510,7 +511,7 @@ func Wiki(ctx *context.Context) {
func WikiRevision(ctx *context.Context) {
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
if !ctx.Repo.Repository.HasWiki() {
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(http.StatusOK, tplWikiStart)
return
@ -540,7 +541,7 @@ func WikiRevision(ctx *context.Context) {
// WikiPages render wiki pages list page
func WikiPages(ctx *context.Context) {
if !ctx.Repo.Repository.HasWiki() {
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
return
}
@ -648,7 +649,7 @@ func WikiRaw(ctx *context.Context) {
func NewWiki(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
if !ctx.Repo.Repository.HasWiki() {
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
ctx.Data["title"] = "Home"
}
if ctx.FormString("title") != "" {
@ -701,7 +702,7 @@ func NewWikiPost(ctx *context.Context) {
func EditWiki(ctx *context.Context) {
ctx.Data["PageIsWikiEdit"] = true
if !ctx.Repo.Repository.HasWiki() {
if !repo_service.HasWiki(ctx, ctx.Repo.Repository) {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
return
}

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
"github.com/stretchr/testify/assert"
@ -240,7 +241,7 @@ func TestDefaultWikiBranch(t *testing.T) {
// repo with no wiki
repoWithNoWiki := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, repoWithNoWiki.HasWiki())
assert.False(t, repo_service.HasWiki(t.Context(), repoWithNoWiki))
assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repoWithNoWiki, "main"))
// repo with wiki

@ -91,18 +91,12 @@ func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++
r, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return err
}
defer r.Close()
if autofix {
_, _, err := git.NewCommand("config", "receive.advertisePushOptions", "true").RunStdString(ctx, &git.RunOpts{Dir: r.Path})
return err
return gitrepo.GitConfigSet(ctx, repo, "receive.advertisePushOptions", "true")
}
value, _, err := git.NewCommand("config", "receive.advertisePushOptions").RunStdString(ctx, &git.RunOpts{Dir: r.Path})
value, err := gitrepo.GitConfigGet(ctx, repo, "receive.advertisePushOptions")
if err != nil {
return err
}

@ -260,18 +260,18 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
}
}
template = typeName + "/" + name
template = "repo/" + typeName + "/" + name
ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil
if !ok && typeName != "issue" {
template = "issue/" + name
template = "repo/issue/" + name
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
}
if !ok {
template = typeName + "/default"
template = "repo/" + typeName + "/default"
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
}
if !ok {
template = "issue/default"
template = "repo/issue/default"
}
return typeName, name, template
}

@ -19,7 +19,7 @@ import (
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const tplNewReleaseMail templates.TplName = "release"
const tplNewReleaseMail templates.TplName = "repo/release"
func generateMessageIDForRelease(release *repo_model.Release) string {
return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain)

@ -11,13 +11,17 @@ import (
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const mailRepoTransferNotify templates.TplName = "notify/repo_transfer"
const (
mailNotifyCollaborator templates.TplName = "repo/collaborator"
mailRepoTransferNotify templates.TplName = "repo/transfer"
)
// SendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created
func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error {
@ -91,3 +95,33 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
return nil
}
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) {
if setting.MailService == nil || !u.IsActive {
return
}
locale := translation.NewLocale(u.Language)
repoName := repo.FullName()
subject := locale.TrString("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName)
data := map[string]any{
"locale": locale,
"Subject": subject,
"RepoName": repoName,
"Link": repo.HTMLURL(),
"Language": locale.Language(),
}
var content bytes.Buffer
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
log.Error("Template: %v", err)
return
}
msg := sender_service.NewMessage(u.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
SendAsync(msg)
}

@ -19,7 +19,7 @@ import (
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const tplTeamInviteMail templates.TplName = "team_invite"
const tplTeamInviteMail templates.TplName = "org/team_invite"
// MailTeamInvite sends team invites
func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_model.Team, invite *org_model.TeamInvite) error {

@ -115,7 +115,7 @@ func TestComposeIssueComment(t *testing.T) {
setting.IncomingEmail.Enabled = true
defer func() { setting.IncomingEmail.Enabled = false }()
prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
prepareMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
@ -160,7 +160,7 @@ func TestComposeIssueComment(t *testing.T) {
func TestMailMentionsComment(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t)
comment.Poster = doer
prepareMailTemplates("issue/comment", subjectTpl, bodyTpl)
prepareMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)
mails := 0
defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
@ -175,7 +175,7 @@ func TestMailMentionsComment(t *testing.T) {
func TestComposeIssueMessage(t *testing.T) {
doer, _, issue, _ := prepareMailerTest(t)
prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
prepareMailTemplates("repo/issue/new", subjectTpl, bodyTpl)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
@ -204,14 +204,14 @@ func TestTemplateSelection(t *testing.T) {
doer, repo, issue, comment := prepareMailerTest(t)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
prepareMailTemplates("issue/default", "issue/default/subject", "issue/default/body")
prepareMailTemplates("repo/issue/default", "repo/issue/default/subject", "repo/issue/default/body")
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/new").Parse("issue/new/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("pull/comment").Parse("pull/comment/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/close").Parse("")) // Must default to a fallback subject
template.Must(LoadedTemplates().BodyTemplates.New("issue/new").Parse("issue/new/body"))
template.Must(LoadedTemplates().BodyTemplates.New("pull/comment").Parse("pull/comment/body"))
template.Must(LoadedTemplates().BodyTemplates.New("issue/close").Parse("issue/close/body"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/issue/new").Parse("repo/issue/new/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/pull/comment").Parse("repo/pull/comment/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/issue/close").Parse("")) // Must default to a fallback subject
template.Must(LoadedTemplates().BodyTemplates.New("repo/issue/new").Parse("repo/issue/new/body"))
template.Must(LoadedTemplates().BodyTemplates.New("repo/pull/comment").Parse("repo/pull/comment/body"))
template.Must(LoadedTemplates().BodyTemplates.New("repo/issue/close").Parse("repo/issue/close/body"))
expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
subject := msg.ToMessage().GetGenHeader("Subject")
@ -226,13 +226,13 @@ func TestTemplateSelection(t *testing.T) {
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
Content: "test body",
}, recipients, false, "TestTemplateSelection")
expect(t, msg, "issue/new/subject", "issue/new/body")
expect(t, msg, "repo/issue/new/subject", "repo/issue/new/body")
msg = testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue,
Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection")
expect(t, msg, "issue/default/subject", "issue/default/body")
expect(t, msg, "repo/issue/default/subject", "repo/issue/default/body")
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2, Repo: repo, Poster: doer})
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 4, Issue: pull})
@ -240,13 +240,13 @@ func TestTemplateSelection(t *testing.T) {
Issue: pull, Doer: doer, ActionType: activities_model.ActionCommentPull,
Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection")
expect(t, msg, "pull/comment/subject", "pull/comment/body")
expect(t, msg, "repo/pull/comment/subject", "repo/pull/comment/body")
msg = testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCloseIssue,
Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection")
expect(t, msg, "Re: [user2/repo1] issue1 (#1)", "issue/close/body")
expect(t, msg, "Re: [user2/repo1] issue1 (#1)", "repo/issue/close/body")
}
func TestTemplateServices(t *testing.T) {
@ -256,7 +256,7 @@ func TestTemplateServices(t *testing.T) {
expect := func(t *testing.T, issue *issues_model.Issue, comment *issues_model.Comment, doer *user_model.User,
actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string,
) {
prepareMailTemplates("issue/default", tplSubject, tplBody)
prepareMailTemplates("repo/issue/default", tplSubject, tplBody)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
msg := testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: actionType,
@ -523,7 +523,7 @@ func TestEmbedBase64Images(t *testing.T) {
att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
t.Run("ComposeMessage", func(t *testing.T) {
prepareMailTemplates("issue/new", subjectTpl, bodyTpl)
prepareMailTemplates("repo/issue/new", subjectTpl, bodyTpl)
issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))

@ -7,7 +7,6 @@ import (
"bytes"
"fmt"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -18,11 +17,10 @@ import (
)
const (
mailAuthActivate templates.TplName = "auth/activate"
mailAuthActivateEmail templates.TplName = "auth/activate_email"
mailAuthResetPassword templates.TplName = "auth/reset_passwd"
mailAuthRegisterNotify templates.TplName = "auth/register_notify"
mailNotifyCollaborator templates.TplName = "notify/collaborator"
mailAuthActivate templates.TplName = "user/auth/activate"
mailAuthActivateEmail templates.TplName = "user/auth/activate_email"
mailAuthResetPassword templates.TplName = "user/auth/reset_passwd"
mailAuthRegisterNotify templates.TplName = "user/auth/register_notify"
)
// sendUserMail sends a mail to the user
@ -128,34 +126,3 @@ func SendRegisterNotifyMail(u *user_model.User) {
SendAsync(msg)
}
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) {
if setting.MailService == nil || !u.IsActive {
// No mail service configured OR the user is inactive
return
}
locale := translation.NewLocale(u.Language)
repoName := repo.FullName()
subject := locale.TrString("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName)
data := map[string]any{
"locale": locale,
"Subject": subject,
"RepoName": repoName,
"Link": repo.HTMLURL(),
"Language": locale.Language(),
}
var content bytes.Buffer
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
log.Error("Template: %v", err)
return
}
msg := sender_service.NewMessage(u.EmailTo(), subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
SendAsync(msg)
}

@ -8,6 +8,7 @@ import (
"context"
"fmt"
"sort"
"time"
actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
@ -15,18 +16,20 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/convert"
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const tplWorkflowRun = "notify/workflow_run"
const tplWorkflowRun templates.TplName = "repo/actions/workflow_run"
type convertedWorkflowJob struct {
HTMLURL string
Status actions_model.Status
Name string
Attempt int64
HTMLURL string
Name string
Status actions_model.Status
Attempt int64
Duration time.Duration
}
func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Repository, run *actions_model.ActionRun) string {
@ -45,16 +48,15 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
}
}
subject := "Run"
var subjectTrString string
switch run.Status {
case actions_model.StatusFailure:
subject += " failed"
subjectTrString = "mail.repo.actions.run.failed"
case actions_model.StatusCancelled:
subject += " cancelled"
subjectTrString = "mail.repo.actions.run.cancelled"
case actions_model.StatusSuccess:
subject += " succeeded"
subjectTrString = "mail.repo.actions.run.succeeded"
}
subject = fmt.Sprintf("%s: %s (%s)", subject, run.WorkflowID, base.ShortSha(run.CommitSHA))
displayName := fromDisplayName(sender)
messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
metadataHeaders := generateMetadataHeaders(repo)
@ -80,10 +82,11 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
continue
}
convertedJobs = append(convertedJobs, convertedWorkflowJob{
HTMLURL: converted0.HTMLURL,
Name: converted0.Name,
Status: job.Status,
Attempt: converted0.RunAttempt,
HTMLURL: converted0.HTMLURL,
Name: converted0.Name,
Status: job.Status,
Attempt: converted0.RunAttempt,
Duration: job.Duration(),
})
}
@ -93,27 +96,28 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
}
for lang, tos := range langMap {
locale := translation.NewLocale(lang)
var runStatusText string
var runStatusTrString string
switch run.Status {
case actions_model.StatusSuccess:
runStatusText = "All jobs have succeeded"
runStatusTrString = "mail.repo.actions.jobs.all_succeeded"
case actions_model.StatusFailure:
runStatusText = "All jobs have failed"
runStatusTrString = "mail.repo.actions.jobs.all_failed"
for _, job := range jobs {
if !job.Status.IsFailure() {
runStatusText = "Some jobs were not successful"
runStatusTrString = "mail.repo.actions.jobs.some_not_successful"
break
}
}
case actions_model.StatusCancelled:
runStatusText = "All jobs have been cancelled"
runStatusTrString = "mail.repo.actions.jobs.all_cancelled"
}
subject := fmt.Sprintf("%s: %s (%s)", locale.TrString(subjectTrString), run.WorkflowID, base.ShortSha(run.CommitSHA))
var mailBody bytes.Buffer
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, tplWorkflowRun, map[string]any{
if err := LoadedTemplates().BodyTemplates.ExecuteTemplate(&mailBody, string(tplWorkflowRun), map[string]any{
"Subject": subject,
"Repo": repo,
"Run": run,
"RunStatusText": runStatusText,
"RunStatusText": locale.TrString(runStatusTrString),
"Jobs": convertedJobs,
"locale": locale,
}); err != nil {

@ -7,7 +7,7 @@ package migrations
import (
"errors"
"github.com/google/go-github/v71/github"
"github.com/google/go-github/v74/github"
)
// ErrRepoNotCreated returns the error that repository not created

@ -63,7 +63,7 @@ func TestGiteaUploadRepo(t *testing.T) {
assert.NoError(t, err)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName})
assert.True(t, repo.HasWiki())
assert.True(t, repo_service.HasWiki(ctx, repo))
assert.Equal(t, repo_model.RepositoryReady, repo.Status)
milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{
@ -236,6 +236,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
//
fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
baseRef := "master"
// this is very different from the real situation. It should be a bare repository for all the Gitea managed repositories
assert.NoError(t, git.InitRepository(t.Context(), fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(t.Context(), &git.RunOpts{Dir: fromRepo.RepoPath()})
assert.NoError(t, err)

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/structs"
"github.com/google/go-github/v71/github"
"github.com/google/go-github/v74/github"
"golang.org/x/oauth2"
)

@ -50,7 +50,7 @@ func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOpti
log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword, opts.AuthToken)
return NewGitlabDownloader(ctx, baseURL, repoNameSpace, opts.AuthToken)
}
// GitServiceType returns the type of git service
@ -93,14 +93,8 @@ type GitlabDownloader struct {
//
// Use either a username/password, personal token entered into the username field, or anonymous/public access
// Note: Public access only allows very basic access
func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GitlabDownloader, error) {
func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, token string) (*GitlabDownloader, error) {
gitlabClient, err := gitlab.NewClient(token, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
// Only use basic auth if token is blank and password is NOT
// Basic auth will fail with empty strings, but empty token will allow anonymous public API usage
if token == "" && password != "" {
gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient()))
}
if err != nil {
log.Trace("Error logging into gitlab: %v", err)
return nil, err
@ -206,7 +200,7 @@ func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) {
if err != nil {
return nil, err
}
return gr.TagList, err
return gr.Topics, err
}
// GetMilestones returns milestones

@ -31,7 +31,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
t.Skipf("Can't access test repo, skipping %s", t.Name())
}
ctx := t.Context()
downloader, err := NewGitlabDownloader(ctx, "https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken)
downloader, err := NewGitlabDownloader(ctx, "https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken)
if err != nil {
t.Fatalf("NewGitlabDownloader is nil: %v", err)
}

@ -39,30 +39,27 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
}
remoteName := m.GetRemoteName()
repoPath := m.GetRepository(ctx).RepoPath()
repo := m.GetRepository(ctx)
// Remove old remote
_, _, err = git.NewCommand("remote", "rm").AddDynamicArguments(remoteName).RunStdString(ctx, &git.RunOpts{Dir: repoPath})
err = gitrepo.GitRemoteRemove(ctx, repo, remoteName)
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
cmd := git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr)
_, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath})
err = gitrepo.GitRemoteAdd(ctx, repo, remoteName, addr, gitrepo.RemoteOptionMirrorFetch)
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
if m.Repo.HasWiki() {
wikiPath := m.Repo.WikiPath()
if repo_service.HasWiki(ctx, m.Repo) {
wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr)
// Remove old remote of wiki
_, _, err = git.NewCommand("remote", "rm").AddDynamicArguments(remoteName).RunStdString(ctx, &git.RunOpts{Dir: wikiPath})
err = gitrepo.GitRemoteRemove(ctx, repo.WikiStorageRepo(), remoteName)
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
cmd = git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath)
_, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: wikiPath})
err = gitrepo.GitRemoteAdd(ctx, repo.WikiStorageRepo(), remoteName, wikiRemotePath, gitrepo.RemoteOptionMirrorFetch)
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
@ -197,25 +194,21 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
func pruneBrokenReferences(ctx context.Context,
m *repo_model.Mirror,
repoPath string,
timeout time.Duration,
stdoutBuilder, stderrBuilder *strings.Builder,
isWiki bool,
) error {
wiki := ""
var storageRepo gitrepo.Repository = m.Repo
if isWiki {
wiki = "Wiki "
storageRepo = m.Repo.WikiStorageRepo()
}
stderrBuilder.Reset()
stdoutBuilder.Reset()
pruneErr := git.NewCommand("remote", "prune").AddDynamicArguments(m.GetRemoteName()).
Run(ctx, &git.RunOpts{
Timeout: timeout,
Dir: repoPath,
Stdout: stdoutBuilder,
Stderr: stderrBuilder,
})
pruneErr := gitrepo.GitRemotePrune(ctx, storageRepo, m.GetRemoteName(), timeout, stdoutBuilder, stderrBuilder)
if pruneErr != nil {
stdout := stdoutBuilder.String()
stderr := stderrBuilder.String()
@ -226,7 +219,7 @@ func pruneBrokenReferences(ctx context.Context,
stdoutMessage := util.SanitizeCredentialURLs(stdout)
log.Error("Failed to prune mirror repository %s%-v references:\nStdout: %s\nStderr: %s\nErr: %v", wiki, m.Repo, stdoutMessage, stderrMessage, pruneErr)
desc := fmt.Sprintf("Failed to prune mirror repository %s'%s' references: %s", wiki, repoPath, stderrMessage)
desc := fmt.Sprintf("Failed to prune mirror repository %s'%s' references: %s", wiki, storageRepo.RelativePath(), stderrMessage)
if err := system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
@ -268,9 +261,9 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
}
cmd.AddArguments("--tags").AddDynamicArguments(m.GetRemoteName())
remoteURL, remoteErr := git.GetRemoteURL(ctx, repoPath, m.GetRemoteName())
remoteURL, remoteErr := gitrepo.GitRemoteGetURL(ctx, m.Repo, m.GetRemoteName())
if remoteErr != nil {
log.Error("SyncMirrors [repo: %-v]: GetRemoteAddress Error %v", m.Repo, remoteErr)
log.Error("SyncMirrors [repo: %-v]: GetRemoteURL Error %v", m.Repo, remoteErr)
return nil, false
}
@ -298,7 +291,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
err = nil
// Attempt prune
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, false)
pruneErr := pruneBrokenReferences(ctx, m, timeout, &stdoutBuilder, &stderrBuilder, false)
if pruneErr == nil {
// Successful prune - reattempt mirror
stderrBuilder.Reset()
@ -347,7 +340,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint)
lfsClient := lfs.NewClient(endpoint, nil)
if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != nil {
log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo, err)
log.Error("SyncMirrors [repo: %-v]: failed to synchronize LFS objects for repository: %v", m.Repo.FullName(), err)
}
}
@ -364,20 +357,16 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo)
if err := repo_module.UpdateRepoSize(ctx, m.Repo); err != nil {
log.Error("SyncMirrors [repo: %-v]: failed to update size for mirror repository: %v", m.Repo, err)
log.Error("SyncMirrors [repo: %-v]: failed to update size for mirror repository: %v", m.Repo.FullName(), err)
}
if m.Repo.HasWiki() {
if repo_service.HasWiki(ctx, m.Repo) {
log.Trace("SyncMirrors [repo: %-v Wiki]: running git remote update...", m.Repo)
stderrBuilder.Reset()
stdoutBuilder.Reset()
if err := git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).
Run(ctx, &git.RunOpts{
Timeout: timeout,
Dir: wikiPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
if err := gitrepo.GitRemoteUpdatePrune(ctx, m.Repo.WikiStorageRepo(), m.GetRemoteName(),
timeout, &stdoutBuilder, &stderrBuilder); err != nil {
stdout := stdoutBuilder.String()
stderr := stderrBuilder.String()
@ -391,19 +380,14 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
err = nil
// Attempt prune
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, true)
pruneErr := pruneBrokenReferences(ctx, m, timeout, &stdoutBuilder, &stderrBuilder, true)
if pruneErr == nil {
// Successful prune - reattempt mirror
stderrBuilder.Reset()
stdoutBuilder.Reset()
if err = git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).
Run(ctx, &git.RunOpts{
Timeout: timeout,
Dir: wikiPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
if err = gitrepo.GitRemoteUpdatePrune(ctx, m.Repo.WikiStorageRepo(), m.GetRemoteName(),
timeout, &stdoutBuilder, &stderrBuilder); err != nil {
stdout := stdoutBuilder.String()
stderr := stderrBuilder.String()
stderrMessage = util.SanitizeCredentialURLs(stderr)

@ -23,34 +23,31 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
)
var stripExitStatus = regexp.MustCompile(`exit status \d+ - `)
// AddPushMirrorRemote registers the push mirror remote.
func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) error {
addRemoteAndConfig := func(addr, path string) error {
cmd := git.NewCommand("remote", "add", "--mirror=push").AddDynamicArguments(m.RemoteName, addr)
if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil {
addRemoteAndConfig := func(storageRepo gitrepo.Repository, addr string) error {
if err := gitrepo.GitRemoteAdd(ctx, storageRepo, m.RemoteName, addr, gitrepo.RemoteOptionMirrorPush); err != nil {
return err
}
if _, _, err := git.NewCommand("config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil {
if err := gitrepo.GitConfigAdd(ctx, storageRepo, "remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*"); err != nil {
return err
}
if _, _, err := git.NewCommand("config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil {
return err
}
return nil
return gitrepo.GitConfigAdd(ctx, storageRepo, "remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*")
}
if err := addRemoteAndConfig(addr, m.Repo.RepoPath()); err != nil {
if err := addRemoteAndConfig(m.Repo, addr); err != nil {
return err
}
if m.Repo.HasWiki() {
if repo_service.HasWiki(ctx, m.Repo) {
wikiRemoteURL := repository.WikiRemoteURL(ctx, addr)
if len(wikiRemoteURL) > 0 {
if err := addRemoteAndConfig(wikiRemoteURL, m.Repo.WikiPath()); err != nil {
if err := addRemoteAndConfig(m.Repo.WikiStorageRepo(), wikiRemoteURL); err != nil {
return err
}
}
@ -61,15 +58,13 @@ func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr str
// RemovePushMirrorRemote removes the push mirror remote.
func RemovePushMirrorRemote(ctx context.Context, m *repo_model.PushMirror) error {
cmd := git.NewCommand("remote", "rm").AddDynamicArguments(m.RemoteName)
_ = m.GetRepository(ctx)
if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: m.Repo.RepoPath()}); err != nil {
if err := gitrepo.GitRemoteRemove(ctx, m.Repo, m.RemoteName); err != nil {
return err
}
if m.Repo.HasWiki() {
if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: m.Repo.WikiPath()}); err != nil {
if repo_service.HasWiki(ctx, m.Repo) {
if err := gitrepo.GitRemoteRemove(ctx, m.Repo.WikiStorageRepo(), m.RemoteName); err != nil {
// The wiki remote may not exist
log.Warn("Wiki Remote[%d] could not be removed: %v", m.ID, err)
}
@ -128,25 +123,22 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
performPush := func(repo *repo_model.Repository, isWiki bool) error {
var storageRepo gitrepo.Repository = repo
path := repo.RepoPath()
if isWiki {
storageRepo = repo.WikiStorageRepo()
path = repo.WikiPath()
}
remoteURL, err := git.GetRemoteURL(ctx, path, m.RemoteName)
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, storageRepo, m.RemoteName)
if err != nil {
log.Error("GetRemoteAddress(%s) Error %v", path, err)
log.Error("GetRemoteURL(%s) Error %v", path, err)
return errors.New("Unexpected error")
}
if setting.LFS.StartServer {
log.Trace("SyncMirrors [repo: %-v]: syncing LFS objects...", m.Repo)
var gitRepo *git.Repository
if isWiki {
gitRepo, err = gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
} else {
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
}
gitRepo, err := gitrepo.OpenRepository(ctx, storageRepo)
if err != nil {
log.Error("OpenRepository: %v", err)
return errors.New("Unexpected error")
@ -183,15 +175,14 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
return err
}
if m.Repo.HasWiki() {
_, err := git.GetRemoteAddress(ctx, m.Repo.WikiPath(), m.RemoteName)
if err == nil {
if repo_service.HasWiki(ctx, m.Repo) {
if _, err := gitrepo.GitRemoteGetURL(ctx, m.Repo.WikiStorageRepo(), m.RemoteName); err == nil {
err := performPush(m.Repo, true)
if err != nil {
return err
}
} else {
log.Trace("Skipping wiki: No remote configured")
} else if !errors.Is(err, util.ErrNotExist) {
log.Error("GetRemote of wiki failed: %v", err)
}
}

@ -0,0 +1,95 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package pull
import (
"context"
"fmt"
"strconv"
"time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
logger "code.gitea.io/gitea/modules/log"
)
// CompareInfo represents needed information for comparing references.
type CompareInfo struct {
MergeBase string
BaseCommitID string
HeadCommitID string
Commits []*git.Commit
NumFiles int
}
// GetCompareInfo generates and returns compare information between base and head branches of repositories.
func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Repository, headGitRepo *git.Repository, baseBranch, headBranch string, directComparison, fileOnly bool) (_ *CompareInfo, err error) {
var (
remoteBranch string
tmpRemote string
)
// We don't need a temporary remote for same repository.
if headGitRepo.Path != baseRepo.RepoPath() {
// Add a temporary remote
tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10)
if err = gitrepo.GitRemoteAdd(ctx, headRepo, tmpRemote, baseRepo.RepoPath()); err != nil {
return nil, fmt.Errorf("GitRemoteAdd: %w", err)
}
defer func() {
if err := gitrepo.GitRemoteRemove(graceful.GetManager().ShutdownContext(), headRepo, tmpRemote); err != nil {
logger.Error("GetPullRequestInfo: GitRemoteRemove: %v", err)
}
}()
}
compareInfo := new(CompareInfo)
compareInfo.HeadCommitID, err = git.GetFullCommitID(ctx, headGitRepo.Path, headBranch)
if err != nil {
compareInfo.HeadCommitID = headBranch
}
compareInfo.MergeBase, remoteBranch, err = headGitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch)
if err == nil {
compareInfo.BaseCommitID, err = git.GetFullCommitID(ctx, headGitRepo.Path, remoteBranch)
if err != nil {
compareInfo.BaseCommitID = remoteBranch
}
separator := "..."
baseCommitID := compareInfo.MergeBase
if directComparison {
separator = ".."
baseCommitID = compareInfo.BaseCommitID
}
// We have a common base - therefore we know that ... should work
if !fileOnly {
compareInfo.Commits, err = headGitRepo.ShowPrettyFormatLogToList(ctx, baseCommitID+separator+headBranch)
if err != nil {
return nil, fmt.Errorf("ShowPrettyFormatLogToList: %w", err)
}
} else {
compareInfo.Commits = []*git.Commit{}
}
} else {
compareInfo.Commits = []*git.Commit{}
compareInfo.MergeBase, err = git.GetFullCommitID(ctx, headGitRepo.Path, remoteBranch)
if err != nil {
compareInfo.MergeBase = remoteBranch
}
compareInfo.BaseCommitID = compareInfo.MergeBase
}
// Count number of changed files.
// This probably should be removed as we need to use shortstat elsewhere
// Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly
compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(remoteBranch, headBranch, directComparison)
if err != nil {
return nil, err
}
return compareInfo, nil
}

Some files were not shown because too many files have changed in this diff Show More