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", "name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm", "image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
"containerEnv": { "containerEnv": {
// override "local" from packaged version // override "local" from packaged version
"GOTOOLCHAIN": "auto" "GOTOOLCHAIN": "auto"
@ -8,12 +8,12 @@
"features": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "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/jsburckhardt/devcontainer-features/uv:1": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.12" "version": "3.13"
}, },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },

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

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

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

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

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

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

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

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

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

1
.gitignore vendored

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

@ -1,6 +1,7 @@
audit=false audit=false
fund=false fund=false
update-notifier=false update-notifier=false
package-lock=true
save-exact=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 \ git \
nodejs \ nodejs \
npm \ npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# Setup repo # Setup repo

@ -15,6 +15,7 @@ RUN apk --no-cache add \
git \ git \
nodejs \ nodejs \
npm \ npm \
&& npm install -g pnpm@10 \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
# Setup repo # 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 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.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 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest 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_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 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 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)) $(eval PNPM_MISSING := $(shell hash pnpm > /dev/null 2>&1 || echo 1))
@if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" -o "$(NPM_MISSING)" = "1" ]; then \ @if [ "$(NODE_VERSION)" -lt "$(MIN_NODE_VERSION)" ]; 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/"; \ 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; \ exit 1; \
fi fi
.PHONY: clean-all .PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files 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 .PHONY: clean
clean: ## delete backend and integration files 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 .PHONY: lint-js
lint-js: node_modules ## lint js files lint-js: node_modules ## lint js files
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
npx vue-tsc pnpm exec vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues lint-js-fix: node_modules ## lint js files and fix issues
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix pnpm exec eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
npx vue-tsc pnpm exec vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules ## lint css files 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 .PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues 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 .PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files 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 .PHONY: lint-md
lint-md: node_modules ## lint markdown files lint-md: node_modules ## lint markdown files
npx markdownlint *.md pnpm exec markdownlint *.md
.PHONY: lint-spell .PHONY: lint-spell
lint-spell: ## lint spelling lint-spell: ## lint spelling
@ -417,7 +421,7 @@ watch: ## watch everything and continuously rebuild
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development npx webpack --watch --progress NODE_ENV=development pnpm exec webpack --watch --progress
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild watch-backend: go-check ## watch backend files and continuously rebuild
@ -433,7 +437,7 @@ test-backend: ## test backend files
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules ## test frontend files test-frontend: node_modules ## test frontend files
npx vitest pnpm exec vitest
.PHONY: test-check .PHONY: test-check
test-check: test-check:
@ -576,7 +580,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright .PHONY: playwright
playwright: deps-frontend playwright: deps-frontend
npx playwright install $(PLAYWRIGHT_FLAGS) pnpm exec playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e% .PHONY: test-e2e%
test-e2e%: TEST_TYPE ?= e2e test-e2e%: TEST_TYPE ?= e2e
@ -839,10 +843,14 @@ deps-tools: ## install tool dependencies
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \ $(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait wait
node_modules: package-lock.json node_modules: pnpm-lock.yaml
npm install --no-save pnpm install --frozen-lockfile
@touch node_modules @touch node_modules
tools/node_modules: tools/package.json
cd tools && pnpm install
@touch tools/node_modules
.venv: uv.lock .venv: uv.lock
uv sync uv sync
@touch .venv @touch .venv
@ -852,16 +860,16 @@ update: update-js update-py ## update js and py dependencies
.PHONY: update-js .PHONY: update-js
update-js: node-check | node_modules ## update js dependencies update-js: node-check | node_modules ## update js dependencies
npx updates -u -f package.json pnpm exec updates -u -f package.json
rm -rf node_modules package-lock.json rm -rf node_modules pnpm-lock.yaml
npm install --package-lock pnpm install
npx nolyfill install pnpm exec nolyfill install
npm install --package-lock pnpm install
@touch node_modules @touch node_modules
.PHONY: update-py .PHONY: update-py
update-py: node-check | node_modules ## update py dependencies 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 rm -rf .venv uv.lock
uv sync uv sync
@touch .venv @touch .venv
@ -869,11 +877,11 @@ update-py: node-check | node_modules ## update py dependencies
.PHONY: webpack .PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files 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 @$(MAKE) -s node-check node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..." @echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack @BROWSERSLIST_IGNORE_OLD_DATA=true pnpm exec webpack
@touch $(WEBPACK_DEST) @touch $(WEBPACK_DEST)
.PHONY: svg .PHONY: svg
@ -893,11 +901,11 @@ svg-check: svg
.PHONY: lockfile-check .PHONY: lockfile-check
lockfile-check: lockfile-check:
npm install --package-lock-only pnpm install --frozen-lockfile
@diff=$$(git diff --color=always package-lock.json); \ @diff=$$(git diff --color=always pnpm-lock.yaml); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "package-lock.json is inconsistent with package.json"; \ echo "pnpm-lock.yaml is inconsistent with package.json"; \
echo "Please run 'npm install --package-lock-only' and commit the result:"; \ echo "Please run 'pnpm install --frozen-lockfile' and commit the result:"; \
printf "%s" "$${diff}"; \ printf "%s" "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@ -917,9 +925,8 @@ generate-gitignore: ## update gitignore files
$(GO) run build/generate-gitignores.go $(GO) run build/generate-gitignores.go
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules tools/node_modules ## generate images (requires cairo development packages)
npm install --no-save fabric@6 imagemin-zopfli@7 cd tools && node generate-images.js $(TAGS)
node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: ## 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: 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 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. 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" "code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session" "gitea.com/go-chi/session"
"github.com/mholt/archiver/v3"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
@ -146,22 +145,18 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
return err return err
} }
archiverGeneric, err := archiver.ByExtension("." + outType) dumper, err := dump.NewDumper(ctx, outType, outFile)
if err != nil { if err != nil {
fatal("Unable to get archiver for extension: %v", err) fatal("Failed to create archive %q: %v", outFile, err)
} return 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,
} }
dumper.Verbose = verbose
dumper.GlobalExcludeAbsPath(outFileName) 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") { if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") {
log.Info("Skip dumping local repositories") log.Info("Skip dumping local repositories")
@ -180,7 +175,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath)) return dumper.AddFileByReader(object, info, path.Join("data", "lfs", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump LFS objects: %v", err) 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) 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) fatal("Failed to include gitea-db.sql: %v", err)
} }
} }
log.Info("Adding custom configuration file from %s", setting.CustomConf) 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) 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 { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath)) return dumper.AddFileByReader(object, info, path.Join("data", "attachments", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump attachments: %v", err) fatal("Failed to dump attachments: %v", err)
} }
@ -297,7 +292,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "packages", objPath)) return dumper.AddFileByReader(object, info, path.Join("data", "packages", objPath))
}); err != nil { }); err != nil {
fatal("Failed to dump packages: %v", err) fatal("Failed to dump packages: %v", err)
} }
@ -322,10 +317,6 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if outFileName == "-" { if outFileName == "-" {
log.Info("Finish dumping to stdout") log.Info("Finish dumping to stdout")
} else { } 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 { if err = os.Chmod(outFileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err) log.Info("Can't change file access permissions mask to 0600: %v", err)
} }

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

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

194
go.mod

@ -10,7 +10,7 @@ godebug x509negativeserial=1
require ( require (
code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3 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 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1 connectrpc.com/connect v1.18.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed 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/go-chi/session v0.0.0-20240316035857-16768d98ec96
gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/httpsig v1.2.2 github.com/42wim/httpsig v1.2.3
github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815 github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.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/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/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/alecthomas/chroma/v2 v2.20.0
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 github.com/aws/aws-sdk-go-v2/credentials v1.18.10
github.com/aws/aws-sdk-go-v2/service/codecommit v1.28.2 github.com/aws/aws-sdk-go-v2/service/codecommit v1.32.2
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb 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/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8 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/charmbracelet/git-lfs-transfer v0.2.0
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0 github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1 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/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
@ -49,25 +49,25 @@ require (
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/gliderlabs/ssh v0.3.8 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-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2 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-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-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.2 github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.12.3 github.com/go-webauthn/webauthn v0.13.4
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-github/v71 v71.0.0 github.com/google/go-github/v74 v74.0.0
github.com/google/licenseclassifier/v2 v2.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/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0 github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
@ -79,53 +79,53 @@ require (
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0 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/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-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.32
github.com/meilisearch/meilisearch-go v0.31.0 github.com/meilisearch/meilisearch-go v0.33.2
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archives v0.1.3
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.8.0 github.com/microsoft/go-mssqldb v1.9.3
github.com/minio/minio-go/v7 v7.0.91 github.com/minio/minio-go/v7 v7.0.95
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63 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/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.23.0
github.com/quasoft/websspi v1.1.2 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/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f 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-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/wneessen/go-mail v0.6.2
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 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-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.127.0 gitlab.com/gitlab-org/api/client-go v0.142.4
golang.org/x/crypto v0.39.0 golang.org/x/crypto v0.41.0
golang.org/x/image v0.26.0 golang.org/x/image v0.30.0
golang.org/x/net v0.40.0 golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.29.0 golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0 golang.org/x/sync v0.16.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.35.0
golang.org/x/text v0.26.0 golang.org/x/text v0.28.0
google.golang.org/grpc v1.72.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.8
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0 mvdan.cc/xurls/v2 v2.6.0
@ -135,43 +135,47 @@ require (
) )
require ( require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/compute/metadata v0.8.0 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // 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/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect
github.com/andybalholm/brotli v1.1.1 // 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/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // 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 v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // 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.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/smithy-go v1.22.3 // indirect github.com/aws/smithy-go v1.23.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.8 // indirect github.com/blevesearch/bleve_index_api v1.2.9 // indirect
github.com/blevesearch/geo v0.2.0 // indirect github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // 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/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.3 // indirect github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/boombuler/barcode v1.0.2 // 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/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // 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/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.1.2 // 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/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // 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/dlclark/regexp2 v1.11.5 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // 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/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-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // 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-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // 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-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/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // 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/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-querystring v1.1.0 // 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/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // 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/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/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.65 // indirect github.com/miekg/dns v1.1.68 // indirect
github.com/minio/crc64nvme v1.0.1 // 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/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // 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/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/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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // 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/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/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.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/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // 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/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.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/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.33.0 // indirect golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // 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/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6 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 // 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 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 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 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 return nil, err
} }

@ -8,7 +8,6 @@ import (
"testing" "testing"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
@ -40,7 +39,7 @@ func (source *TestSource) ToDB() ([]byte, error) {
func TestDumpAuthSource(t *testing.T) { func TestDumpAuthSource(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) 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) assert.NoError(t, err)
auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource)) auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource))

@ -21,28 +21,8 @@ type engineContextKeyType struct{}
var engineContextKey = engineContextKeyType{} var engineContextKey = engineContextKeyType{}
type xormContextType struct { func withContextEngine(ctx context.Context, e Engine) context.Context {
context.Context return context.WithValue(ctx, engineContextKey, e)
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))
} }
var ( var (
@ -81,17 +61,19 @@ func contextSafetyCheck(e Engine) {
callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine
for i := range callerNum { for i := range callerNum {
if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) { 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 // GetEngine gets an existing db Engine/Statement or creates a new Session
func GetEngine(ctx context.Context) (e Engine) { func GetEngine(ctx context.Context) Engine {
defer func() { contextSafetyCheck(e) }() if engine, ok := ctx.Value(engineContextKey).(Engine); ok {
if e := getExistingEngine(ctx); e != nil { // if reusing the existing session, need to do "contextSafetyCheck" because the Iterate creates a "autoResetStatement=false" session
return e contextSafetyCheck(engine)
return engine
} }
// no need to do "contextSafetyCheck" because it's a new Session
return xormEngine.Context(ctx) return xormEngine.Context(ctx)
} }
@ -99,17 +81,6 @@ func GetXORMEngineForTesting() *xorm.Engine {
return xormEngine 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 // Committer represents an interface to Commit or Close the Context
type Committer interface { type Committer interface {
Commit() error 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. // 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. // 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) { func TxContext(parentCtx context.Context) (context.Context, Committer, error) {
if sess, ok := inTransaction(parentCtx); ok { if sess := getTransactionSession(parentCtx); sess != nil {
return newContext(parentCtx, sess), &halfCommitter{committer: sess}, nil return withContextEngine(parentCtx, sess), &halfCommitter{committer: sess}, nil
} }
sess := xormEngine.NewSession() sess := xormEngine.NewSession()
@ -161,15 +132,14 @@ func TxContext(parentCtx context.Context) (context.Context, Committer, error) {
_ = sess.Close() _ = sess.Close()
return nil, nil, err return nil, nil, err
} }
return withContextEngine(parentCtx, sess), sess, nil
return newContext(xormContext, sess), sess, nil
} }
// WithTx represents executing database operations on a transaction, if the transaction exist, // 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. // 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 { func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
if sess, ok := inTransaction(parentCtx); ok { if sess := getTransactionSession(parentCtx); sess != nil {
err := f(newContext(parentCtx, sess)) err := f(withContextEngine(parentCtx, sess))
if err != nil { if err != nil {
// rollback immediately, in case the caller ignores returned error and tries to commit the transaction. // rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
_ = sess.Close() _ = sess.Close()
@ -195,7 +165,7 @@ func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error)
return err return err
} }
if err := f(newContext(parentCtx, sess)); err != nil { if err := f(withContextEngine(parentCtx, sess)); err != nil {
return err return err
} }
@ -333,32 +303,15 @@ func CountByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Count(bean) 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 // InTransaction returns true if the engine is in a transaction otherwise return false
func InTransaction(ctx context.Context) bool { func InTransaction(ctx context.Context) bool {
_, ok := inTransaction(ctx) return getTransactionSession(ctx) != nil
return ok
} }
func inTransaction(ctx context.Context) (*xorm.Session, bool) { func getTransactionSession(ctx context.Context) *xorm.Session {
e := getExistingEngine(ctx) e, _ := ctx.Value(engineContextKey).(Engine)
if e == nil { if sess, ok := e.(*xorm.Session); ok && sess.IsInTx() {
return nil, false return sess
}
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
} }
return nil
} }

@ -100,6 +100,7 @@ func TestContextSafety(t *testing.T) {
assert.NoError(t, db.Insert(t.Context(), &TestModel2{ID: int64(-i)})) assert.NoError(t, db.Insert(t.Context(), &TestModel2{ID: int64(-i)}))
} }
t.Run("Show-XORM-Bug", func(t *testing.T) {
actualCount := 0 actualCount := 0
// here: db.GetEngine(t.Context()) is a new *Session created from *Engine // here: db.GetEngine(t.Context()) is a new *Session created from *Engine
_ = db.WithTx(t.Context(), func(ctx context.Context) error { _ = db.WithTx(t.Context(), func(ctx context.Context) error {
@ -119,12 +120,16 @@ func TestContextSafety(t *testing.T) {
return nil return nil
}) })
assert.Equal(t, testCount, actualCount) assert.Equal(t, testCount, actualCount)
})
// deny the bad usages t.Run("DenyBadUsage", func(t *testing.T) {
assert.PanicsWithError(t, "using database context in an iterator would cause corrupted results", func() { assert.PanicsWithError(t, "using session context in an iterator would cause corrupted results", func() {
_ = unittest.GetXORMEngine().Iterate(&TestModel1{}, func(i int, bean any) error { _ = db.WithTx(t.Context(), func(ctx context.Context) error {
_ = db.GetEngine(t.Context()) return db.GetEngine(ctx).Iterate(&TestModel1{}, func(i int, bean any) error {
_ = db.GetEngine(ctx)
return nil return nil
}) })
}) })
})
})
} }

@ -12,7 +12,6 @@ import (
"strings" "strings"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/schemas"
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver _ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver _ "github.com/lib/pq" // Needed for the Postgresql driver
@ -67,11 +66,6 @@ var (
_ Engine = (*xorm.Session)(nil) _ 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 // RegisterModel registers model, if initFuncs provided, it will be invoked after data model sync
func RegisterModel(bean any, initFunc ...func() error) { func RegisterModel(bean any, initFunc ...func() error) {
registeredModels = append(registeredModels, bean) registeredModels = append(registeredModels, bean)

@ -86,7 +86,6 @@ func InitEngine(ctx context.Context) error {
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
xormEngine = eng xormEngine = eng
xormEngine.SetDefaultContext(ctx) xormEngine.SetDefaultContext(ctx)
xormContext = &xormContextType{Context: ctx, engine: xormEngine}
} }
// UnsetDefaultEngine closes and unsets the default engine // UnsetDefaultEngine closes and unsets the default engine
@ -98,7 +97,6 @@ func UnsetDefaultEngine() {
_ = xormEngine.Close() _ = xormEngine.Close()
xormEngine = nil xormEngine = nil
} }
xormContext = nil
} }
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the XORM's default context // 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 { for _, bean := range beans {
table, err := db.TableInfo(bean) table, err := db.GetXORMEngineForTesting().TableInfo(bean)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -19,12 +19,7 @@ type ResourceIndex struct {
MaxIndex int64 `xorm:"index"` MaxIndex int64 `xorm:"index"`
} }
var ( var ErrGetResourceIndexFailed = errors.New("get resource index failed")
// 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")
)
// SyncMaxResourceIndex sync the max index with the resource // SyncMaxResourceIndex sync the max index with the resource
func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) { 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 = 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 " + "WHEN milestone.deadline_unix < issue.deadline_unix OR issue.deadline_unix = 0 THEN milestone.deadline_unix " +
"ELSE issue.deadline_unix END ASC"). "ELSE issue.deadline_unix END ASC").
Desc("issue.created_unix"). Asc("issue.created_unix").
Desc("issue.id") Asc("issue.id")
case "farduedate": case "farduedate":
sess.Join("LEFT", "milestone", "issue.milestone_id = milestone.id"). sess.Join("LEFT", "milestone", "issue.milestone_id = milestone.id").
OrderBy("CASE " + OrderBy("CASE " +

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

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

@ -229,6 +229,10 @@ func RelativePath(ownerName, repoName string) string {
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git" 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 // RelativePath should be an unix style path like username/reponame.git
func (repo *Repository) RelativePath() string { func (repo *Repository) RelativePath() string {
return RelativePath(repo.OwnerName, repo.Name) return RelativePath(repo.OwnerName, repo.Name)
@ -241,8 +245,10 @@ func (sr StorageRepo) RelativePath() string {
return string(sr) 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 { 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 // SanitizedOriginalURL returns a sanitized OriginalURL

@ -11,7 +11,6 @@ import (
"strings" "strings"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -86,12 +85,3 @@ func WikiPath(userName, repoName string) string {
func (repo *Repository) WikiPath() string { func (repo *Repository) WikiPath() string {
return WikiPath(repo.OwnerName, repo.Name) 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") expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
assert.Equal(t, expected, repo.WikiPath()) 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) { func checkForConsistency(t TestingT, bean any) {
tb, err := db.TableInfo(bean) tb, err := GetXORMEngine().TableInfo(bean)
assert.NoError(t, err) assert.NoError(t, err)
f := consistencyCheckMap[tb.Name] f := consistencyCheckMap[tb.Name]
require.NotNil(t, f, "unknown bean type: %#v", bean) 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() xormBeans, _ := db.NamesToBean()
f.xormTableNames = map[string]bool{} f.xormTableNames = map[string]bool{}
for _, bean := range xormBeans { for _, bean := range xormBeans {
f.xormTableNames[db.TableName(bean)] = true f.xormTableNames[x.TableName(bean)] = true
} }
return f, nil return f, nil

@ -159,7 +159,7 @@ func DumpQueryResult(t require.TestingT, sqlOrBean any, sqlArgs ...any) {
goDB := x.DB().DB goDB := x.DB().DB
sql, ok := sqlOrBean.(string) sql, ok := sqlOrBean.(string)
if !ok { if !ok {
sql = "SELECT * FROM " + db.TableName(sqlOrBean) sql = "SELECT * FROM " + x.TableName(sqlOrBean)
} else if !strings.Contains(sql, " ") { } else if !strings.Contains(sql, " ") {
sql = "SELECT * FROM " + 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) // thus would change `\t\t` to just `\t` or ` ` (two spaces) to just ` ` (single space)
rd.TrimLeadingSpace = true 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 return rd
} }

@ -94,6 +94,24 @@ j, ,\x20
}, },
expectedDelimiter: ',', 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 { for n, c := range cases {
@ -119,21 +137,6 @@ func TestDetermineDelimiterShortBufferError(t *testing.T) {
assert.Nil(t, rd, "CSV reader should be mnil") 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) { func TestDetermineDelimiter(t *testing.T) {
cases := []struct { cases := []struct {
csv string csv string

@ -4,8 +4,11 @@
package dump package dump
import ( import (
"context"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -16,7 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "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"} 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 { type Dumper struct {
Writer archiver.Writer
Verbose bool Verbose bool
jobs chan archives.ArchiveAsyncJob
errArchiveAsync chan error
errArchiveJob chan error
globalExcludeAbsPaths []string globalExcludeAbsPaths []string
} }
func (dumper *Dumper) AddReader(r io.ReadCloser, info os.FileInfo, customName string) error { func NewDumper(ctx context.Context, format string, output io.Writer) (*Dumper, error) {
if dumper.Verbose { d := &Dumper{
log.Info("Adding file %s", customName) jobs: make(chan archives.ArchiveAsyncJob, 1),
errArchiveAsync: make(chan error, 1),
errArchiveJob: make(chan error, 1),
} }
return dumper.Writer.Write(archiver.File{ // TODO: in the future, we could completely drop the "mholt/archives" dependency.
FileInfo: archiver.FileInfo{ // Then we only need to support "zip" and ".tar.gz" natively, and let users provide custom command line tools
FileInfo: info, // like "zstd" or "xz" with compression-level arguments.
CustomName: customName, var comp archives.ArchiverAsync
}, switch format {
ReadCloser: r, 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 { func (dumper *Dumper) runArchiveJob(job archives.ArchiveAsyncJob) error {
file, err := os.Open(absPath) dumper.jobs <- job
if err != nil { select {
case err := <-dumper.errArchiveAsync:
if err == nil {
return errors.New("archiver has been closed")
}
return err 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 { if err != nil {
return err 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 { 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()) currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() { if file.IsDir() {
if err := dumper.AddFile(currentInsidePath, currentAbsPath); err != nil { if err := dumper.AddFileByPath(currentInsidePath, currentAbsPath); err != nil {
return err return err
} }
if err = dumper.addFileOrDir(currentInsidePath, currentAbsPath, excludes); err != nil { 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() shouldAdd = targetStat.Mode().IsRegular()
} }
if shouldAdd { if shouldAdd {
if err = dumper.AddFile(currentInsidePath, currentAbsPath); err != nil { if err = dumper.AddFileByPath(currentInsidePath, currentAbsPath); err != nil {
return err return err
} }
} }

@ -4,6 +4,8 @@
package dump package dump
import ( import (
"archive/tar"
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -14,8 +16,8 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"github.com/mholt/archiver/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPrepareFileNameAndType(t *testing.T) { func TestPrepareFileNameAndType(t *testing.T) {
@ -67,28 +69,26 @@ func TestIsSubDir(t *testing.T) {
assert.False(t, isSub) assert.False(t, isSub)
} }
type testWriter struct { func TestDumperIntegration(t *testing.T) {
added []string var buf bytes.Buffer
} dumper, err := NewDumper(t.Context(), "zip", &buf)
require.NoError(t, err)
func (t *testWriter) Create(out io.Writer) error { tmpDir := t.TempDir()
return nil _ = 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 { fi, _ := f.Stat()
t.added = append(t.added, f.Name()) err = dumper.AddFileByReader(f, fi, "test.txt")
return nil require.NoError(t, err)
}
err = dumper.Close()
require.NoError(t, err)
func (t *testWriter) Close() error { assert.Positive(t, buf.Len())
return nil
} }
func TestDumper(t *testing.T) { func TestDumper(t *testing.T) {
sortStrings := func(s []string) []string {
sort.Strings(s)
return s
}
tmpDir := t.TempDir() tmpDir := t.TempDir()
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude1"), 0o755) _ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude1"), 0o755)
_ = os.MkdirAll(filepath.Join(tmpDir, "include/exclude2"), 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/exclude1/a-1"), nil, 0o644)
_ = os.WriteFile(filepath.Join(tmpDir, "include/exclude2/a-2"), nil, 0o644) _ = os.WriteFile(filepath.Join(tmpDir, "include/exclude2/a-2"), nil, 0o644)
tw := &testWriter{} sortStrings := func(s []string) []string {
d := &Dumper{Writer: tw} sort.Strings(s)
d.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1")) return s
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))
tw = &testWriter{} t.Run("IncludesWithExcludes", func(t *testing.T) {
d = &Dumper{Writer: tw} var buf bytes.Buffer
err = d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil) dumper, err := NewDumper(t.Context(), "tar", &buf)
assert.NoError(t, err) require.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)) 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 { func NewParser(r io.Reader, format Format) *Parser {
scanner := bufio.NewScanner(r) 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, // in addition to the reference delimiter we specified in the --format,
// `git for-each-ref` will always add a newline after every reference. // `git for-each-ref` will always add a newline after every reference.
refDelim := make([]byte, 0, len(format.refDelim)+1) 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" } // { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
func (p *Parser) Next() map[string]string { func (p *Parser) Next() map[string]string {
if !p.scanner.Scan() { if !p.scanner.Scan() {
if err := p.scanner.Err(); err != nil {
p.err = err
}
return nil return nil
} }
fields, err := p.parseRef(p.scanner.Text()) 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" "net/url"
"strings" "strings"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -33,15 +32,6 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string,
return result, nil 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. // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
type ErrInvalidCloneAddr struct { type ErrInvalidCloneAddr struct {
Host string Host string

@ -38,6 +38,17 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) {
return AllCommitsCount(repo.Ctx, repo.Path, false) 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) { func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, error) {
var commits []*Commit var commits []*Commit
if len(logs) == 0 { if len(logs) == 0 {

@ -79,12 +79,6 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
return err 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 // RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error { func (repo *Repository) RenameBranch(from, to string) error {
_, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) _, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})

@ -16,20 +16,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "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. // 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) { func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, string, error) {
if tmpRemote == "" { if tmpRemote == "" {
@ -49,83 +37,6 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
return strings.TrimSpace(stdout), base, err 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 { type lineCountWriter struct {
numLines int numLines int
} }

@ -7,8 +7,6 @@
package git package git
import ( import (
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
@ -20,40 +18,6 @@ func (repo *Repository) IsTagExist(name string) bool {
return err == nil 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) // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) { func (repo *Repository) GetTagType(id ObjectID) (string, error) {
// Get tag type // Get tag type

@ -22,13 +22,6 @@ func (repo *Repository) IsTagExist(name string) bool {
return repo.IsReferenceExist(TagPrefix + name) 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) // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) { func (repo *Repository) GetTagType(id ObjectID) (string, error) {
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRepository_GetTags(t *testing.T) { func TestRepository_GetTagInfos(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path) bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path)
if err != nil { 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)) 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 { func DeleteRepository(ctx context.Context, repo Repository) error {
return util.RemoveAll(repoPath(repo)) return util.RemoveAll(repoPath(repo))
} }
@ -81,3 +82,7 @@ func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
} }
return nil 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" indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_meilisearch "code.gitea.io/gitea/modules/indexer/internal/meilisearch" inner_meilisearch "code.gitea.io/gitea/modules/indexer/internal/meilisearch"
"code.gitea.io/gitea/modules/indexer/issues/internal" "code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/json"
"github.com/meilisearch/meilisearch-go" "github.com/meilisearch/meilisearch-go"
) )
@ -106,7 +107,8 @@ func (b *Indexer) Index(_ context.Context, issues ...*internal.IndexerData) erro
return nil return nil
} }
for _, issue := range issues { 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 { if err != nil {
return err return err
} }
@ -299,18 +301,13 @@ func doubleQuoteKeyword(k string) string {
func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) { func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) {
hits := make([]internal.Match, 0, len(searchRes.Hits)) hits := make([]internal.Match, 0, len(searchRes.Hits))
for _, hit := range searchRes.Hits { for _, hit := range searchRes.Hits {
hit, ok := hit.(map[string]any) var issueID int64
if !ok { if err := json.Unmarshal(hit["id"], &issueID); err != nil {
return nil, ErrMalformedResponse
}
issueID, ok := hit["id"].(float64)
if !ok {
return nil, ErrMalformedResponse return nil, ErrMalformedResponse
} }
hits = append(hits, internal.Match{ hits = append(hits, internal.Match{
ID: int64(issueID), ID: issueID,
}) })
} }
return hits, nil return hits, nil

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

@ -282,9 +282,9 @@ type CreateBranchRepoOption struct {
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"` 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 // swagger:model
type UpdateBranchRepoOption struct { type RenameBranchRepoOption struct {
// New branch name // New branch name
// //
// required: true // required: true

@ -14,8 +14,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository" "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 { func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string) remoteAddress {
ret := remoteAddress{} ret := remoteAddress{}
remoteURL, err := git.GetRemoteAddress(ctx, m.RepoPath(), remoteName) u, err := gitrepo.GitRemoteGetURL(ctx, m, remoteName)
if err != nil { if err != nil {
log.Error("GetRemoteURL %v", err) log.Error("GetRemoteURL %v", err)
return ret 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.Scheme != "ssh" && u.Scheme != "file" {
if u.User != nil { if u.User != nil {
ret.Username = u.User.Username() 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.subject = %s added you to %s
repo.collaborator.added.text = You have been added as a collaborator of repository: 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.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_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: 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. error503=Níorbh fhéidir leis an bhfreastalaí diarratas a chomhlánú. Déan iarracht arís ar ball.
go_back=Ar ais go_back=Ar ais
invalid_data=Sonraí neamhbhailí: %v invalid_data=Sonraí neamhbhailí: %v
nothing_has_been_changed=Níl aon rud athraithe.
never=Riamh never=Riamh
unknown=Anaithnid unknown=Anaithnid
@ -2843,6 +2844,11 @@ settings.location=Suíomh
settings.permission=Ceadanna settings.permission=Ceadanna
settings.repoadminchangeteam=Is féidir le riarthóir an stórais rochtain d'fhoirne a chur leis agus a bhaint settings.repoadminchangeteam=Is féidir le riarthóir an stórais rochtain d'fhoirne a chur leis agus a bhaint
settings.visibility=Infheictheacht 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.public=Poiblí
settings.visibility.limited=Teoranta (Infheicthe d'úsáideoirí fíordheimhnithe amháin) settings.visibility.limited=Teoranta (Infheicthe d'úsáideoirí fíordheimhnithe amháin)
settings.visibility.limited_shortname=Teoranta settings.visibility.limited_shortname=Teoranta
@ -3419,6 +3425,7 @@ config.picture_service=Seirbhís Pictiúr
config.disable_gravatar=Díchumasaigh Gravatar config.disable_gravatar=Díchumasaigh Gravatar
config.enable_federated_avatar=Cumasaigh Avatars Cónaidhme 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.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_config=Cumraíocht Git
config.git_disable_diff_highlight=Díchumasaigh Aibhsiú Comhréire Diff config.git_disable_diff_highlight=Díchumasaigh Aibhsiú Comhréire Diff

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

@ -159,7 +159,7 @@ filter.not_archived=Não Arquivados
filter.is_fork=Fork filter.is_fork=Fork
filter.not_fork=Não Fork filter.not_fork=Não Fork
filter.is_mirror=Espelhado filter.is_mirror=Espelhado
filter.not_mirror=Não espelhado filter.not_mirror=Não Espelhado
filter.is_template=Template filter.is_template=Template
filter.not_template=Não Modelo filter.not_template=Não Modelo
filter.public=Pública 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.title=Título: %s
release.note=Nota: release.note=Nota:
release.downloads=Downloads: release.downloads=Downloads:
release.download.zip=Código Fonte (ZIP) release.download.zip=Código-Fonte (ZIP)
release.download.targz=Código Fonte (TAR.GZ) release.download.targz=Código-Fonte (TAR.GZ)
repo.transfer.subject_to=%s gostaria de transferir "%s" para %s repo.transfer.subject_to=%s gostaria de transferir "%s" para %s
repo.transfer.subject_to_you=%s gostaria de transferir "%s" para você 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=Proprietário
owner_helper=Algumas organizações podem não aparecer no menu devido a um limite de contagem dos repositórios. 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=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 repo_size=Tamanho do repositório
template=Modelo template=Modelo
template_select=Selecione um 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_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.) 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>. 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 fork_from=Fork de
already_forked=Você já fez o fork de %s already_forked=Você já fez o fork de %s
fork_to_different_account=Faça um fork para uma conta diferente 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 all_branches=Todos os branches
view_all_branches=Ver todos branches view_all_branches=Ver todos branches
view_all_tags=Ver todas as tags 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 use_template=Usar este modelo
open_with_editor=Abrir com %s open_with_editor=Abrir com %s
download_zip=Baixar ZIP download_zip=Baixar ZIP
download_tar=Baixar TAR.GZ download_tar=Baixar TAR.GZ
download_bundle=Baixar PACOTE download_bundle=Baixar PACOTE
generate_repo=Gerar repositório generate_repo=Gerar Repositório
generate_from=Gerar de generate_from=Gerar de
repo_desc=Descrição repo_desc=Descrição
repo_desc_helper=Digite uma breve descrição (opcional) 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_lang=Linguagens
repo_gitignore_helper=Selecione modelos do .gitignore. 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. 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. issue_labels_helper=Selecione um conjunto de etiquetas de issue.
license=Licença license=Licença
license_helper=Selecione um arquivo de 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_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_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 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=Branch Padrão
default_branch_label=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. 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 reactions_more=e %d mais
unit_disabled=O administrador do site desabilitou esta seção do repositório. unit_disabled=O administrador do site desabilitou esta seção do repositório.
language_other=Outra 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_label=Adotar Arquivos
adopt_preexisting=Adotar arquivos pré-existentes adopt_preexisting=Adotar arquivos pré-existentes
adopt_preexisting_content=Criar repositório a partir de %s 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.one_item=Deve-se selecionar pelo menos um item de modelo
template.invalid=Deve-se selecionar um repositório 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.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. 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.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=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.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=Itens da migração
migrate_items_wiki=Wiki migrate_items_wiki=Wiki
migrate_items_milestones=Marcos migrate_items_milestones=Marcos
@ -1149,11 +1155,13 @@ migrate_items_issues=Issues
migrate_items_pullrequests=Pull Requests migrate_items_pullrequests=Pull Requests
migrate_items_merge_requests=Requisições de merge migrate_items_merge_requests=Requisições de merge
migrate_items_releases=Versões 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=Migrar / Clonar de URL
migrate.clone_address_desc=URL HTTP (S) ou Git 'clone' de um repositório existente 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.clone_local_path=ou um caminho de servidor local
migrate.permission_denied=Você não pode importar repositórios locais. 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_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.invalid_lfs_endpoint=O destino LFS não é válido.
migrate.failed=Migração falhou: %v 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=Migrado de <a href="%[1]s">%[2]s</a>
migrated_from_fake=Migrado de %[1]s migrated_from_fake=Migrado de %[1]s
migrate.migrate=Migrar de %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=Migração a partir de <b>%s</b> falhou.
migrate.migrating_failed.error=Falha ao migrar: %s migrate.migrating_failed.error=Falha ao migrar: %s
migrate.migrating_failed_no_addr=A migração falhou. 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.onedev.description=Migrar dados de code.onedev.io ou de outras instâncias do OneDev.
migrate.codebase.description=Migrar dados de codebasehq.com. migrate.codebase.description=Migrar dados de codebasehq.com.
migrate.gitbucket.description=Migrar dados de instâncias do GitBucket. 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_git=Migrando dados Git
migrate.migrating_topics=Migrando tópicos migrate.migrating_topics=Migrando Tópicos
migrate.migrating_milestones=Migrando Marcos migrate.migrating_milestones=Migrando Marcos
migrate.migrating_labels=Migrando Rótulos migrate.migrating_labels=Migrando Rótulos
migrate.migrating_releases=Migrando Versões migrate.migrating_releases=Migrando Versões
migrate.migrating_issues=Migrando Issues migrate.migrating_issues=Migrando Issues
migrate.migrating_pulls=Migrando Pull Requests 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? migrate.cancel_migrating_confirm=Você quer cancelar essa migração?
migration_status=Status da migração migration_status=Status da migração
@ -1195,7 +1209,8 @@ watch=Observar
unstar=Retirar dos favoritos unstar=Retirar dos favoritos
star=Juntar aos favoritos star=Juntar aos favoritos
fork=Fork 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 more_operations=Mais Operações
quick_guide=Guia Rápido quick_guide=Guia Rápido
@ -1203,6 +1218,7 @@ clone_this_repo=Clonar este repositório
cite_this_repo=Citar este repositório cite_this_repo=Citar este repositório
create_new_repo_command=Criando um novo repositório por linha de comando 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 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. 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 code=Código
@ -1220,6 +1236,7 @@ projects=Projetos
packages=Pacotes packages=Pacotes
actions=Ações actions=Ações
labels=Etiquetas 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 org_labels_desc_manage=gerenciar
milestone=Marco milestone=Marco
@ -1234,9 +1251,9 @@ tagged_this=criou essa tag
file.title=%s em %s file.title=%s em %s
file_raw=Original file_raw=Original
file_history=Histórico 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_rendered=Ver Renderizado
file_view_raw=Ver original file_view_raw=Ver Original
file_permalink=Link permanente file_permalink=Link permanente
file_too_large=O arquivo é muito grande para ser mostrado. file_too_large=O arquivo é muito grande para ser mostrado.
file_is_empty=O arquivo está vazio. 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. 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. audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5.
symbolic_link=Link simbólico symbolic_link=Link simbólico
executable_file=Arquivo executável executable_file=Arquivo Executável
generated=Gerado generated=Gerado
commit_graph=Gráfico de commits commit_graph=Gráfico de Commits
commit_graph.select=Selecionar branches commit_graph.select=Selecionar branches
commit_graph.hide_pr_refs=Esconder Pull Requests commit_graph.hide_pr_refs=Esconder Pull Requests
commit_graph.monochrome=Monocromático commit_graph.monochrome=Monocromático
@ -1272,34 +1289,35 @@ lines=linhas
from_comment=(comentário) from_comment=(comentário)
editor.add_file=Adicionar Arquivo editor.add_file=Adicionar Arquivo
editor.new_file=Novo arquivo editor.new_file=Novo Arquivo
editor.upload_file=Enviar arquivo editor.upload_file=Enviar Arquivo
editor.edit_file=Editar arquivo editor.edit_file=Editar Arquivo
editor.preview_changes=Visualizar alterações editor.preview_changes=Visualizar Alterações
editor.cannot_edit_lfs_files=Arquivos LFS não podem ser editados na interface web. 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_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.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.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.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.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.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.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.file_delete_success=O arquivo "%s" foi excluído.
editor.name_your_file=Nomeie o seu arquivo… 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.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.or=ou
editor.cancel_lower=Cancelar editor.cancel_lower=Cancelar
editor.commit_signed_changes=Commit de alteradores assinadas editor.commit_signed_changes=Criar Commit das Alterações Assinadas
editor.commit_changes=Aplicar commit das alterações editor.commit_changes=Criar Commit das Alterações
editor.add_tmpl=Adicionar '{filename}' editor.add_tmpl=Adicionar '{filename}'
editor.add=Adicionar %s editor.add=Adicionar %s
editor.update=Atualizar %s editor.update=Atualizar %s
editor.delete=Excluir %s editor.delete=Excluir %s
editor.patch=Aplicar Correção editor.patch=Aplicar Correção
editor.patching=Corrigindo: 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.commit_message_desc=Adicione uma descrição detalhada (opcional)...
editor.signoff_desc=Adicione um assinado-por-committer no final do log do commit. 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>. 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.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_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=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.add_subdir=Adicionar um subdiretório...
editor.unable_to_upload_files=Ocorreu um erro ao enviar arquivos para "%s": %v 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. 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: 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.commits=Commits
commits.no_commits=Nenhum commit em comum. "%s" e "%s" tem históricos completamente diferentes. 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.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.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.search_all=Todos os branches
commits.author=Autor commits.author=Autor
commits.message=Mensagem commits.message=Mensagem
@ -1415,40 +1434,40 @@ issues.filter_labels=Filtrar Rótulo
issues.filter_reviewers=Filtrar Revisor issues.filter_reviewers=Filtrar Revisor
issues.filter_no_results=Nenhum resultado issues.filter_no_results=Nenhum resultado
issues.filter_no_results_placeholder=Tente ajustar seus filtros de pesquisa. 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.title_empty=Título não pode ser em branco
issues.new.labels=Etiquetas issues.new.labels=Etiquetas
issues.new.no_label=Sem etiqueta issues.new.no_label=Sem Etiqueta
issues.new.clear_labels=Limpar etiquetas issues.new.clear_labels=Limpar etiquetas
issues.new.projects=Projetos issues.new.projects=Projetos
issues.new.clear_projects=Limpar projetos issues.new.clear_projects=Limpar projetos
issues.new.no_projects=Sem projeto issues.new.no_projects=Sem projeto
issues.new.open_projects=Abrir Projetos 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.no_items=Nenhum item
issues.new.milestone=Marco issues.new.milestone=Marco
issues.new.no_milestone=Sem marco issues.new.no_milestone=Sem Marco
issues.new.clear_milestone=Limpar marco issues.new.clear_milestone=Limpar marco
issues.new.assignees=Responsáveis issues.new.assignees=Responsáveis
issues.new.clear_assignees=Limpar responsáveis issues.new.clear_assignees=Limpar responsáveis
issues.new.no_assignees=Sem responsável issues.new.no_assignees=Sem Responsáveis
issues.choose.get_started=Primeiros passos issues.choose.get_started=Primeiros Passos
issues.choose.open_external_link=Abrir issues.choose.open_external_link=Abrir
issues.choose.blank=Padrão issues.choose.blank=Padrão
issues.choose.blank_about=Criar uma issue a partir do modelo 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.ignore_invalid_templates=Modelos inválidos foram ignorados
issues.choose.invalid_templates=%v modelo(s) inválido(s) encontrado(s) 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.choose.invalid_config=A configuração da issue contém erros:
issues.no_ref=Nenhum branch/tag especificado issues.no_ref=Nenhum Branch/Tag Especificado
issues.create=Criar issue issues.create=Criar Issue
issues.new_label=Nova Etiqueta issues.new_label=Nova Etiqueta
issues.new_label_placeholder=Nome da etiqueta issues.new_label_placeholder=Nome da etiqueta
issues.new_label_desc_placeholder=Descrição 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.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.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.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.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_label=adicionou o rótulo %s %s
issues.add_labels=adicionou os rótulos %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.delete_comment_confirm=Tem certeza que deseja excluir este comentário?
issues.context.copy_link=Copiar Link issues.context.copy_link=Copiar Link
issues.context.quote_reply=Citar Resposta 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.edit=Editar
issues.context.delete=Excluir issues.context.delete=Excluir
issues.no_content=Nenhuma descrição fornecida. 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_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.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 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.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=Remover solicitação de revisão
issues.remove_request_review_block=Não é possível remover a 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.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.sign_in_require_desc=<a href="%s">Acesse</a> para participar desta conversação.
issues.edit=Editar issues.edit=Editar
@ -1579,17 +1598,18 @@ issues.label_description=Descrição da etiqueta
issues.label_color=Cor da etiqueta issues.label_color=Cor da etiqueta
issues.label_color_invalid=Cor inválida issues.label_color_invalid=Cor inválida
issues.label_exclusive=Exclusivo issues.label_exclusive=Exclusivo
issues.label_archive=Arquivar etiqueta issues.label_archive=Arquivar Etiqueta
issues.label_archived_filter=Mostrar etiquetas arquivadas 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_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_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_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_count=%d etiquetas
issues.label_open_issues=%d issues abertas issues.label_open_issues=%d issues abertas
issues.label_edit=Editar issues.label_edit=Editar
issues.label_delete=Excluir issues.label_delete=Excluir
issues.label_modify=Editar etiqueta issues.label_modify=Editar Etiqueta
issues.label_deletion=Excluir 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_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_deletion_success=A etiqueta foi excluída.
issues.label.filter_sort.alphabetically=Alfabeticamente 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_hours=Horas
issues.add_time_minutes=Minutos issues.add_time_minutes=Minutos
issues.add_time_sum_to_small=Nenhum tempo foi inserido. 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.time_spent_from_all_authors=`Tempo total gasto: %s`
issues.due_date=Data Limite 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.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.blocks_short=Bloqueia
issues.dependency.blocked_by_short=Depende de 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.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.pr_remove_text=Isto removerá a dependência deste pull request. Continuar?
issues.dependency.setting=Habilitar Dependências para Issues e Pull Requests 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.open=Reabrir
milestones.close=Fechar milestones.close=Fechar
milestones.new_subheader=Os marcos podem ajudá-lo a organizar os problemas e acompanhar seu progresso. 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.title=Título
milestones.desc=Descrição milestones.desc=Descrição
milestones.due_date=Data limite (opcional) 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.cancel=Cancelar
milestones.modify=Atualizar Marco milestones.modify=Atualizar Marco
milestones.edit_success=O marco "%s" foi atualizado. 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_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.deletion_success=O marco foi excluído.
milestones.filter_sort.name=Nome 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=Atividade
activity.navbar.pulse=Pulso activity.navbar.pulse=Pulso
activity.navbar.contributors=Contribuidores activity.navbar.contributors=Contribuidores
activity.navbar.recent_commits=Commits Recentes
activity.period.filter_label=Período: activity.period.filter_label=Período:
activity.period.daily=1 dia activity.period.daily=1 dia
activity.period.halfweekly=3 dias 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.advanced_settings=Configurações avançadas
settings.wiki_desc=Habilitar a wiki do repositório settings.wiki_desc=Habilitar a wiki do repositório
settings.use_internal_wiki=Usar a wiki nativa 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.use_external_wiki=Usar wiki externa
settings.external_wiki_url=URL externa da wiki settings.external_wiki_url=URL externa da wiki
settings.external_wiki_url_error=A URL da wiki externa não é válida. 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.pulls.default_allow_edits_from_maintainers=Permitir edições de mantenedores por padrão
settings.releases_desc=Habilitar versões do Repositório settings.releases_desc=Habilitar versões do Repositório
settings.packages_desc=Habilitar Registro de Pacotes de 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.projects_mode_all=Todos os projetos
settings.actions_desc=Habilitar ações do repositório settings.actions_desc=Habilitar ações do repositório
settings.admin_settings=Configurações do administrador 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.admin_indexer_unindexed=Não indexado
settings.reindex_button=Adicionar à fila de reindexação settings.reindex_button=Adicionar à fila de reindexação
settings.reindex_requested=Reindexação Requisitada 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.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.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 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.success=A transferência do repositório foi bem sucedida.
settings.transfer_abort=Cancelar transferência 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_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_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_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. 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_to=novo nome do branch
settings.rename_branch=Renomear branch settings.rename_branch=Renomear branch
diff.browse_source=Ver código fonte diff.browse_source=Ver Código-Fonte
diff.parent=pai diff.parent=pai
diff.commit=commit diff.commit=commit
diff.git-notes=Notas diff.git-notes=Notas
diff.data_not_available=Conteúdo de diff não disponível diff.data_not_available=Conteúdo de Diff Não Disponível
diff.options_button=Opções de diferenças diff.options_button=Opções de Diff
diff.download_patch=Baixar arquivo de patch diff.download_patch=Baixar Arquivo de Patch
diff.download_diff=Baixar arquivo de diferenças diff.download_diff=Baixar Arquivo de Diff
diff.show_split_view=Visão dividida diff.show_split_view=Visão Dividida
diff.show_unified_view=Visão unificada diff.show_unified_view=Visão Unificada
diff.whitespace_button=Espaço em branco diff.whitespace_button=Espaço em branco
diff.whitespace_show_everything=Mostrar todas as alterações diff.whitespace_show_everything=Mostrar todas as alterações
diff.whitespace_ignore_all_whitespace=Ignorar todas as alterações de espaço em branco 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.stats_desc_file=%d alterações: %d adições e %d exclusões
diff.bin=BIN diff.bin=BIN
diff.bin_not_shown=Arquivo binário não exibido. 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_before=Antes
diff.file_after=Depois diff.file_after=Depois
diff.file_image_width=Largura 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=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.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.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.load=Carregar Diff
diff.generated=gerado diff.generated=gerado
diff.vendored=externo diff.vendored=externo
@ -2441,7 +2467,7 @@ release.edit=editar
release.ahead.commits=<strong>%d</strong> commits release.ahead.commits=<strong>%d</strong> commits
release.ahead.target=para %s desde esta versão release.ahead.target=para %s desde esta versão
tag.ahead.target=para %s desde esta tag 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.new_subheader=Lançamentos organizam versões do projeto.
release.edit_subheader=Lançamentos organizam versões do projeto. release.edit_subheader=Lançamentos organizam versões do projeto.
release.tag_name=Nome da tag 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 maintenance=Manutenção
dashboard=Painel dashboard=Painel
self_check=Auto-verificação self_check=Auto-verificação
identity_access=Identidade e acesso identity_access=Identidade e Acesso
users=Contas de usuário users=Contas de Usuário
organizations=Organizações organizations=Organizações
repositories=Repositórios repositories=Repositórios
hooks=Webhooks hooks=Webhooks
@ -2687,8 +2713,8 @@ settings=Configurações de Administrador
dashboard.statistic=Resumo dashboard.statistic=Resumo
dashboard.maintenance_operations=Operações de Manutenção dashboard.maintenance_operations=Operações de Manutenção
dashboard.system_status=Status do sistema dashboard.system_status=Status do Sistema
dashboard.operation_name=Nome da operação dashboard.operation_name=Nome da Operação
dashboard.operation_switch=Trocar dashboard.operation_switch=Trocar
dashboard.operation_run=Executar dashboard.operation_run=Executar
dashboard.clean_unbind_oauth=Limpar conexões OAuth não vinculadas 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_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_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.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.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.check_repo_stats=Verificar estatísticas de todos os repositórios
dashboard.archive_cleanup=Apagar arquivos antigos de repositório 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.cleanup_actions=Limpar recursos de actions expiradas
dashboard.server_uptime=Tempo de atividade do Servidor dashboard.server_uptime=Tempo de atividade do Servidor
dashboard.current_goroutine=Goroutines Atuais dashboard.current_goroutine=Goroutines Atuais
dashboard.current_memory_usage=Uso de memória atual dashboard.current_memory_usage=Uso de Memória Atual
dashboard.total_memory_allocated=Total de memória alocada dashboard.total_memory_allocated=Total de Memória Alocada
dashboard.memory_obtained=Memória obtida dashboard.memory_obtained=Memória Obtida
dashboard.pointer_lookup_times=Nº de consultas a ponteiros dashboard.pointer_lookup_times=Nº de Consultas a Ponteiros
dashboard.memory_allocate_times=Alocações de memória dashboard.memory_allocate_times=Alocações de Memória
dashboard.memory_free_times=Liberações de memória dashboard.memory_free_times=Liberações de Memória
dashboard.current_heap_usage=Uso atual da heap dashboard.current_heap_usage=Uso Atual da Heap
dashboard.heap_memory_obtained=Memória de heap obtida dashboard.heap_memory_obtained=Memória de Heap Obtida
dashboard.heap_memory_idle=Memória da heap ociosa dashboard.heap_memory_idle=Memória da Heap Ociosa
dashboard.heap_memory_in_use=Memória da heap em uso dashboard.heap_memory_in_use=Memória da Heap em Uso
dashboard.heap_memory_released=Memória da heap liberada dashboard.heap_memory_released=Memória da Heap Liberada
dashboard.heap_objects=Objetos na heap dashboard.heap_objects=Objetos na Heap
dashboard.bootstrap_stack_usage=Uso de pilha bootstrap dashboard.bootstrap_stack_usage=Uso de Pilha Bootstrap
dashboard.stack_memory_obtained=Memória de pilha obtida dashboard.stack_memory_obtained=Memória de Pilha Obtida
dashboard.mspan_structures_usage=Uso de estruturas de MSpan dashboard.mspan_structures_usage=Uso de estruturas de MSpan
dashboard.mspan_structures_obtained=Estruturas de MSpan obtidas dashboard.mspan_structures_obtained=Estruturas de MSpan obtidas
dashboard.mcache_structures_usage=Uso de estruturas de MCache dashboard.mcache_structures_usage=Uso de estruturas de MCache
dashboard.mcache_structures_obtained=Estruturas de MCache obtidas dashboard.mcache_structures_obtained=Estruturas de MCache obtidas
dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da Bucket Hash Table dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da Bucket Hash Table
dashboard.gc_metadata_obtained=Metadados do GC obtidos 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.next_gc_recycle=Próxima reciclagem do GC
dashboard.last_gc_time=Desde da ultima vez do GC dashboard.last_gc_time=Desde da ultima vez do GC
dashboard.total_gc_time=Pausa total 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.sync_tag.started=Sincronização de Tags iniciada
dashboard.rebuild_issue_indexer=Reconstruir indexador de issues 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.new_account=Criar Conta de Usuário
users.name=Nome de usuário users.name=Nome de usuário
users.full_name=Nome Completo users.full_name=Nome Completo
@ -2764,33 +2790,33 @@ users.remote=Remoto
users.2fa=2FA users.2fa=2FA
users.repos=Repositórios users.repos=Repositórios
users.created=Criado users.created=Criado
users.last_login=Último acesso users.last_login=Último Acesso
users.never_login=Nunca Acessado 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.new_success=Usuário "%s" criado.
users.edit=Editar users.edit=Editar
users.auth_source=Fonte da autenticação users.auth_source=Fonte da Autenticação
users.local=Local 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.password_helper=Deixe a senha em branco para mantê-la inalterada.
users.update_profile_success=A conta de usuário foi atualizada. users.update_profile_success=A conta de usuário foi atualizada.
users.edit_account=Editar Conta de Usuário 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.max_repo_creation_desc=(Use -1 para usar o limite padrão global.)
users.is_activated=Conta de usuário está ativada users.is_activated=Conta de Usuário está Ativada
users.prohibit_login=Desabilitar acesso users.prohibit_login=Desabilitar Acesso
users.is_admin=É administrador users.is_admin=É Administrador
users.is_restricted=Está restrito users.is_restricted=Está Restrito
users.allow_git_hook=Pode criar hooks Git 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_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_import_local=Pode Importar Repositórios Locais
users.allow_create_organization=Pode criar organizações users.allow_create_organization=Pode Criar Organizações
users.update_profile=Atualizar Conta de Usuário 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.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_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.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.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.deletion_success=A conta de usuário foi excluída.
users.reset_2fa=Reinicializar 2FA 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.is_admin=Administrador
users.list_status_filter.not_admin=Não Administrador users.list_status_filter.not_admin=Não Administrador
users.list_status_filter.is_restricted=Restrito users.list_status_filter.is_restricted=Restrito
users.list_status_filter.not_restricted=Não restrito users.list_status_filter.not_restricted=Não Restrito
users.list_status_filter.is_prohibit_login=Proibir login users.list_status_filter.is_prohibit_login=Proibir Login
users.list_status_filter.not_prohibit_login=Permitir login users.list_status_filter.not_prohibit_login=Permitir Login
users.list_status_filter.is_2fa_enabled=2FA Ativado users.list_status_filter.is_2fa_enabled=2FA Ativado
users.list_status_filter.not_2fa_enabled=2FA Desativado 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.email_manage_panel=Gerenciamento de E-mail de Usuário
emails.primary=Principal emails.primary=Principal
emails.activated=Ativado 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.updated=Endereço de e-mail atualizado
emails.not_updated=Falha ao atualizar o endereço de e-mail solicitado: %v 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. 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=Excluir E-mail
emails.delete_desc=Tem certeza que deseja excluir este 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.name=Nome
orgs.teams=Equipes orgs.teams=Equipes
orgs.members=Membros 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=Repositórios Não Adotados
repos.unadopted.no_more=Não foram encontrados mais repositórios não adotados repos.unadopted.no_more=Não foram encontrados mais repositórios não adotados
repos.owner=Proprietário 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.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://) 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_name=Nome do Site
config.app_ver=Versão do Gitea config.app_ver=Versão do Gitea
config.app_url=URL base do Gitea config.app_url=URL base do Gitea
config.custom_conf=Caminho do Arquivo de Configuração config.custom_conf=Caminho do Arquivo de Configuração
config.custom_file_root_path=Caminho Raiz para Arquivo Personalizado config.custom_file_root_path=Caminho Raiz para Arquivo Personalizado
config.domain=Domínio do Servidor 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.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.run_mode=Modo de Execução
config.git_version=Versão do Git 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.lfs_root_path=Caminho raiz do LFS
config.log_file_root_path=Caminho do log config.log_file_root_path=Caminho do Log
config.script_type=Tipo de script config.script_type=Tipo de Script
config.reverse_auth_user=Usuário de autenticação reversa config.reverse_auth_user=Usuário de Autenticação Reversa
config.ssh_config=Configuração de SSH config.ssh_config=Configuração de SSH
config.ssh_enabled=Habilitado 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_domain=Domínio do servidor SSH
config.ssh_port=Porta config.ssh_port=Porta
config.ssh_listen_port=Porta de escuta config.ssh_listen_port=Porta de Escuta
config.ssh_root_path=Caminho da raiz config.ssh_root_path=Caminho da Raiz
config.ssh_minimum_key_size_check=Verificar tamanho mínimo da chave config.ssh_minimum_key_size_check=Verificar Tamanho Mínimo da Chave
config.ssh_minimum_key_sizes=Tamanhos mínimos da chave config.ssh_minimum_key_sizes=Tamanhos Mínimos da Chave
config.lfs_config=Configuração de LFS config.lfs_config=Configuração de LFS
config.lfs_enabled=Habilitado config.lfs_enabled=Habilitado
@ -3007,30 +3036,30 @@ config.db_schema=Esquema
config.db_ssl_mode=SSL config.db_ssl_mode=SSL
config.db_path=Caminho config.db_path=Caminho
config.service_config=Configuração do serviço config.service_config=Configuração do Serviço
config.register_email_confirm=Exigir confirmação de e-mail para se cadastrar config.register_email_confirm=Exigir Confirmação de E-mail para se Cadastrar
config.disable_register=Desabilitar Auto-Cadastro config.disable_register=Desabilitar Auto-Cadastro
config.allow_only_internal_registration=Permitir Registro Somente Através do Próprio Gitea 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.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_signup=Habilitar o auto-cadastro via OpenID
config.enable_openid_signin=Habilitar acesso via OpenID config.enable_openid_signin=Habilitar acesso via OpenID
config.show_registration_button=Mostrar botão de cadastro 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.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.mail_notify=Habilitar Notificações de E-mail
config.enable_captcha=Habilitar o CAPTCHA config.enable_captcha=Habilitar o CAPTCHA
config.active_code_lives=Ativar Code Lives 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_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.default_allow_create_organization=Permitir a Criação de Organizações por Padrão
config.enable_timetracking=Habilitar Cronômetro config.enable_timetracking=Habilitar Cronômetro
config.default_enable_timetracking=Habilitar o Cronômetro por Padrão 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.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.no_reply_address=Ocultar Domínio de E-mail
config.default_visibility_organization=Visibilidade padrão para novas organizações config.default_visibility_organization=Visibilidade padrão para novas organizações
config.webhook_config=Configuração de Hook da Web config.webhook_config=Configuração de Hook da Web
config.queue_length=Tamanho da fila config.queue_length=Tamanho da Fila
config.deliver_timeout=Intervalo de entrega config.deliver_timeout=Intervalo de Entrega
config.skip_tls_verify=Ignorar verificação de TLS config.skip_tls_verify=Ignorar verificação de TLS
config.mailer_config=Configuração de Envio de E-mail 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_sendmail_timeout=Tempo limite do Sendmail
config.mailer_use_dummy=Dummy config.mailer_use_dummy=Dummy
config.test_email_placeholder=E-mail (por exemplo, teste@exemplo.com.br) 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.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_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". 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_conn=Conexão de Cache
config.cache_item_ttl=Item de cache TTL config.cache_item_ttl=Item de cache TTL
config.session_config=Configuração da sessão config.session_config=Configuração da Sessão
config.session_provider=Provedor da sessão config.session_provider=Provedor da Sessão
config.provider_config=Configuração do provedor config.provider_config=Configuração do Provedor
config.cookie_name=Nome do Cookie config.cookie_name=Nome do Cookie
config.gc_interval_time=Tempo de Intervalo do GC 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.https_only=Apenas HTTPS
config.cookie_life_time=Tempo de Vida do Cookie config.cookie_life_time=Tempo de Vida do Cookie
config.picture_config=Configuração de imagem e avatar config.picture_config=Configuração de Imagem e Avatar
config.picture_service=Serviço de imagens config.picture_service=Serviço de Imagens
config.disable_gravatar=Desabilitar o Gravatar 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_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_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_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_gc_args=Argumentos do GC
config.git_migrate_timeout=Tempo limite de migração config.git_migrate_timeout=Tempo Limite de Migração
config.git_mirror_timeout=Tempo limite de atualização de espelhamento config.git_mirror_timeout=Tempo Limite de Atualização de Espelhamento
config.git_clone_timeout=Tempo limite para operação de clone config.git_clone_timeout=Tempo Limite para Operação de Clone
config.git_pull_timeout=Tempo limite para operação de pull config.git_pull_timeout=Tempo Limite para Operação de Pull
config.git_gc_timeout=Tempo limite para execução do GC 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.logger_name_fmt=Logger: %s
config.disabled_logger=Desabilitado config.disabled_logger=Desabilitado
config.access_log_mode=Modo log Access 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.stats=Estatísticas
monitor.cron=Tarefas cron monitor.cron=Tarefas Cron
monitor.name=Nome monitor.name=Nome
monitor.schedule=Cronograma monitor.schedule=Cronograma
monitor.next=Próxima vez monitor.next=Próxima Vez
monitor.previous=Vez anterior monitor.previous=Vez Anterior
monitor.execute_times=Execuções monitor.execute_times=Execuções
monitor.process=Processos em execução monitor.process=Processos em Execução
monitor.processes_count=%d processos monitor.processes_count=%d processos
monitor.download_diagnosis_report=Baixar relatório de diagnóstico monitor.download_diagnosis_report=Baixar relatório de diagnóstico
monitor.desc=Descrição monitor.desc=Descrição
monitor.start=Hora de início monitor.start=Hora de Início
monitor.execute_time=Tempo de execução monitor.execute_time=Tempo de Execução
monitor.last_execution_result=Resultado monitor.last_execution_result=Resultado
monitor.process.cancel=Cancelar processo monitor.process.cancel=Cancelar processo
monitor.process.cancel_desc=Cancelar um processo pode causar perda de dados 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=Fila: %s
monitor.queue.name=Nome monitor.queue.name=Nome
monitor.queue.type=Tipo monitor.queue.type=Tipo
monitor.queue.exemplar=Tipo de modelo monitor.queue.exemplar=Tipo de Modelo
monitor.queue.numberworkers=Número de executores monitor.queue.numberworkers=Número de Executores
monitor.queue.maxnumberworkers=Número máximo 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.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.placeholder=Atualmente %[1]d
monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve ser um número 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.submit=Atualizar Configurações
monitor.queue.settings.changed=Configurações atualizadas monitor.queue.settings.changed=Configurações Atualizadas
monitor.queue.settings.remove_all_items=Remover tudo monitor.queue.settings.remove_all_items=Remover tudo
monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos. monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos.
notices.system_notice_list=Avisos do Sistema 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.operations=Operações
notices.select_all=Marcar todos notices.select_all=Marcar Todos
notices.deselect_all=Desmarcar todos notices.deselect_all=Desmarcar Todos
notices.inverse_selection=Inverter seleção notices.inverse_selection=Inverter Seleção
notices.delete_selected=Excluir 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=Tipo
notices.type_1=Repositório notices.type_1=Repositório
notices.type_2=Tarefa notices.type_2=Tarefa

@ -120,6 +120,7 @@ error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您
error503=服务器无法完成您的请求,请稍后重试。 error503=服务器无法完成您的请求,请稍后重试。
go_back=返回 go_back=返回
invalid_data=无效数据: %v invalid_data=无效数据: %v
nothing_has_been_changed=没有任何更改。
never=从不 never=从不
unknown=未知 unknown=未知
@ -2844,6 +2845,11 @@ settings.location=所在地区
settings.permission=权限 settings.permission=权限
settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问权限 settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问权限
settings.visibility=可见性 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.public=公开
settings.visibility.limited=受限 (仅对认证用户可见) settings.visibility.limited=受限 (仅对认证用户可见)
settings.visibility.limited_shortname=受限 settings.visibility.limited_shortname=受限
@ -3420,6 +3426,7 @@ config.picture_service=图片服务
config.disable_gravatar=禁用 Gravatar 头像 config.disable_gravatar=禁用 Gravatar 头像
config.enable_federated_avatar=启用 Federated 头像 config.enable_federated_avatar=启用 Federated 头像
config.open_with_editor_app_help=用于克隆菜单的编辑器。如果为空将使用默认值。展开可以查看默认值。 config.open_with_editor_app_help=用于克隆菜单的编辑器。如果为空将使用默认值。展开可以查看默认值。
config.git_guide_remote_name=指南中 git 命令使用的仓库远程名称
config.git_config=Git 配置 config.git_config=Git 配置
config.git_disable_diff_highlight=禁用差异对比语法高亮 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", "type": "module",
"packageManager": "pnpm@10.0.0",
"engines": { "engines": {
"node": ">= 20.0.0" "node": ">= 20.0.0",
"pnpm": ">=10.0.0"
}, },
"dependencies": { "dependencies": {
"@citation-js/core": "0.7.18", "@citation-js/core": "0.7.18",
@ -14,6 +16,7 @@
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.15.5", "@primer/octicons": "19.15.5",
"@silverwind/vue3-calendar-heatmap": "2.0.6", "@silverwind/vue3-calendar-heatmap": "2.0.6",
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
"add-asset-webpack-plugin": "3.0.0", "add-asset-webpack-plugin": "3.0.0",
"ansi_up": "6.0.6", "ansi_up": "6.0.6",
"asciinema-player": "3.10.0", "asciinema-player": "3.10.0",
@ -32,7 +35,6 @@
"idiomorph": "0.7.3", "idiomorph": "0.7.3",
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.22", "katex": "0.16.22",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "11.10.0", "mermaid": "11.10.0",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"minimatch": "10.0.3", "minimatch": "10.0.3",
@ -69,6 +71,7 @@
"@stoplight/spectral-cli": "6.15.0", "@stoplight/spectral-cli": "6.15.0",
"@stylistic/eslint-plugin-js": "3.1.0", "@stylistic/eslint-plugin-js": "3.1.0",
"@stylistic/stylelint-plugin": "4.0.0", "@stylistic/stylelint-plugin": "4.0.0",
"@types/codemirror": "5.60.16",
"@types/dropzone": "5.7.9", "@types/dropzone": "5.7.9",
"@types/jquery": "3.5.32", "@types/jquery": "3.5.32",
"@types/katex": "0.16.7", "@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.Get("/*", repo.GetBranch)
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch) m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch) 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)) }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
m.Group("/branch_protections", func() { m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections) m.Get("", repo.ListBranchProtections)

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

@ -1085,7 +1085,7 @@ func MergePullRequest(ctx *context.APIContext) {
type parseCompareInfoResult struct { type parseCompareInfoResult struct {
headRepo *repo_model.Repository headRepo *repo_model.Repository
headGitRepo *git.Repository headGitRepo *git.Repository
compareInfo *git.CompareInfo compareInfo *pull_service.CompareInfo
baseRef git.RefName baseRef git.RefName
headRef git.RefName headRef git.RefName
} }
@ -1201,7 +1201,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
return nil, nil 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 { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return nil, nil return nil, nil
@ -1452,7 +1452,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
return return
} }
var prInfo *git.CompareInfo var prInfo *pull_service.CompareInfo
baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo) baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@ -1461,9 +1461,9 @@ func GetPullRequestCommits(ctx *context.APIContext) {
defer closer.Close() defer closer.Close()
if pr.HasMerged { 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 { } 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 { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
@ -1582,11 +1582,11 @@ func GetPullRequestFiles(ctx *context.APIContext) {
baseGitRepo := ctx.Repo.GitRepo baseGitRepo := ctx.Repo.GitRepo
var prInfo *git.CompareInfo var prInfo *pull_service.CompareInfo
if pr.HasMerged { 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 { } 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 { if err != nil {
ctx.APIErrorInternal(err) 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 // Default branch only updated if changed and exist or the repository is empty
updateRepoLicense := false updateRepoLicense := false
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, *opts.DefaultBranch)) { 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 !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) ctx.APIErrorInternal(err)
return err return err
} }
updateRepoLicense = true updateRepoLicense = true
} }
repo.DefaultBranch = *opts.DefaultBranch
} }
if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {

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

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

@ -27,7 +27,7 @@ import (
const ( const (
tplConfig templates.TplName = "admin/config" 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 // 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"
"code.gitea.io/gitea/services/context/upload" "code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/gitdiff" "code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull"
) )
const ( const (
@ -550,7 +551,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
headBranchRef = git.TagPrefix + ci.HeadBranch 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 { if err != nil {
ctx.ServerError("GetCompareInfo", err) ctx.ServerError("GetCompareInfo", err)
return nil return nil
@ -707,12 +708,6 @@ func PrepareCompareDiff(
} }
func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repository) (branches, tags []string, err error) { 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{ branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID, RepoID: repo.ID,
ListOptions: db.ListOptionsAll, ListOptions: db.ListOptionsAll,
@ -721,7 +716,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
tags, err = gitRepo.GetTags(0, 0) tags, err = repo_model.GetTagNamesByRepoID(ctx, repo.ID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -254,7 +254,7 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri
return baseCommit 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 { if !issue.IsPull {
return nil 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 // 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 pull := issue.PullRequest
setMergeTarget(ctx, pull) setMergeTarget(ctx, pull)
@ -273,7 +273,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
baseCommit := GetMergedBaseCommitID(ctx, 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) baseCommit, pull.GetGitHeadRefName(), false, false)
if err != nil { 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") { 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 // 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 ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
repo := ctx.Repo.Repository 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) 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) pull.MergeBase, pull.GetGitHeadRefName(), false, false)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") { 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) git.BranchPrefix+pull.BaseBranch, pull.GetGitHeadRefName(), false, false)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") { if strings.Contains(err.Error(), "fatal: Not a valid object name") {

@ -16,6 +16,7 @@ import (
unit_model "code.gitea.io/gitea/models/unit" unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/indexer/code"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues" issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/indexer/stats" "code.gitea.io/gitea/modules/indexer/stats"
@ -258,7 +259,7 @@ func handleSettingsPostMirror(ctx *context.Context) {
return 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 { if err != nil {
ctx.Data["Err_MirrorAddress"] = true ctx.Data["Err_MirrorAddress"] = true
handleSettingRemoteAddrError(ctx, err, form) handleSettingRemoteAddrError(ctx, err, form)

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

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/contexttest" "code.gitea.io/gitea/services/contexttest"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki" wiki_service "code.gitea.io/gitea/services/wiki"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -240,7 +241,7 @@ func TestDefaultWikiBranch(t *testing.T) {
// repo with no wiki // repo with no wiki
repoWithNoWiki := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) 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")) assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(t.Context(), repoWithNoWiki, "main"))
// repo with wiki // 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 { if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++ numRepos++
r, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
return err
}
defer r.Close()
if autofix { if autofix {
_, _, err := git.NewCommand("config", "receive.advertisePushOptions", "true").RunStdString(ctx, &git.RunOpts{Dir: r.Path}) return gitrepo.GitConfigSet(ctx, repo, "receive.advertisePushOptions", "true")
return err
} }
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 { if err != nil {
return err 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 ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil
if !ok && typeName != "issue" { if !ok && typeName != "issue" {
template = "issue/" + name template = "repo/issue/" + name
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
} }
if !ok { if !ok {
template = typeName + "/default" template = "repo/" + typeName + "/default"
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
} }
if !ok { if !ok {
template = "issue/default" template = "repo/issue/default"
} }
return typeName, name, template return typeName, name, template
} }

@ -19,7 +19,7 @@ import (
sender_service "code.gitea.io/gitea/services/mailer/sender" 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 { func generateMessageIDForRelease(release *repo_model.Release) string {
return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain) 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" "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" 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/setting"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
sender_service "code.gitea.io/gitea/services/mailer/sender" 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 // 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 { 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 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" 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 // MailTeamInvite sends team invites
func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_model.Team, invite *org_model.TeamInvite) error { 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 setting.IncomingEmail.Enabled = true
defer func() { setting.IncomingEmail.Enabled = false }() 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"}} recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
@ -160,7 +160,7 @@ func TestComposeIssueComment(t *testing.T) {
func TestMailMentionsComment(t *testing.T) { func TestMailMentionsComment(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t) doer, _, issue, comment := prepareMailerTest(t)
comment.Poster = doer comment.Poster = doer
prepareMailTemplates("issue/comment", subjectTpl, bodyTpl) prepareMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)
mails := 0 mails := 0
defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) { defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
@ -175,7 +175,7 @@ func TestMailMentionsComment(t *testing.T) {
func TestComposeIssueMessage(t *testing.T) { func TestComposeIssueMessage(t *testing.T) {
doer, _, issue, _ := prepareMailerTest(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"}} recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{ msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue, Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
@ -204,14 +204,14 @@ func TestTemplateSelection(t *testing.T) {
doer, repo, issue, comment := prepareMailerTest(t) doer, repo, issue, comment := prepareMailerTest(t)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}} 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("repo/issue/new").Parse("repo/issue/new/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("pull/comment").Parse("pull/comment/subject")) texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/pull/comment").Parse("repo/pull/comment/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("issue/close").Parse("")) // Must default to a fallback subject texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/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("repo/issue/new").Parse("repo/issue/new/body"))
template.Must(LoadedTemplates().BodyTemplates.New("pull/comment").Parse("pull/comment/body")) template.Must(LoadedTemplates().BodyTemplates.New("repo/pull/comment").Parse("repo/pull/comment/body"))
template.Must(LoadedTemplates().BodyTemplates.New("issue/close").Parse("issue/close/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) { expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
subject := msg.ToMessage().GetGenHeader("Subject") subject := msg.ToMessage().GetGenHeader("Subject")
@ -226,13 +226,13 @@ func TestTemplateSelection(t *testing.T) {
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue, Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
Content: "test body", Content: "test body",
}, recipients, false, "TestTemplateSelection") }, 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{ msg = testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue, Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue,
Content: "test body", Comment: comment, Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection") }, 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}) pull := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2, Repo: repo, Poster: doer})
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 4, Issue: pull}) 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, Issue: pull, Doer: doer, ActionType: activities_model.ActionCommentPull,
Content: "test body", Comment: comment, Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection") }, 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{ msg = testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCloseIssue, Issue: issue, Doer: doer, ActionType: activities_model.ActionCloseIssue,
Content: "test body", Comment: comment, Content: "test body", Comment: comment,
}, recipients, false, "TestTemplateSelection") }, 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) { 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, 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, 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"}} recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
msg := testComposeIssueCommentMessage(t, &mailComment{ msg := testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: actionType, Issue: issue, Doer: doer, ActionType: actionType,
@ -523,7 +523,7 @@ func TestEmbedBase64Images(t *testing.T) {
att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64) att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
t.Run("ComposeMessage", func(t *testing.T) { 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) issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content")) require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))

@ -7,7 +7,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -18,11 +17,10 @@ import (
) )
const ( const (
mailAuthActivate templates.TplName = "auth/activate" mailAuthActivate templates.TplName = "user/auth/activate"
mailAuthActivateEmail templates.TplName = "auth/activate_email" mailAuthActivateEmail templates.TplName = "user/auth/activate_email"
mailAuthResetPassword templates.TplName = "auth/reset_passwd" mailAuthResetPassword templates.TplName = "user/auth/reset_passwd"
mailAuthRegisterNotify templates.TplName = "auth/register_notify" mailAuthRegisterNotify templates.TplName = "user/auth/register_notify"
mailNotifyCollaborator templates.TplName = "notify/collaborator"
) )
// sendUserMail sends a mail to the user // sendUserMail sends a mail to the user
@ -128,34 +126,3 @@ func SendRegisterNotifyMail(u *user_model.User) {
SendAsync(msg) 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" "context"
"fmt" "fmt"
"sort" "sort"
"time"
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -15,18 +16,20 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
sender_service "code.gitea.io/gitea/services/mailer/sender" 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 { type convertedWorkflowJob struct {
HTMLURL string HTMLURL string
Status actions_model.Status
Name string Name string
Status actions_model.Status
Attempt int64 Attempt int64
Duration time.Duration
} }
func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Repository, run *actions_model.ActionRun) string { 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 { switch run.Status {
case actions_model.StatusFailure: case actions_model.StatusFailure:
subject += " failed" subjectTrString = "mail.repo.actions.run.failed"
case actions_model.StatusCancelled: case actions_model.StatusCancelled:
subject += " cancelled" subjectTrString = "mail.repo.actions.run.cancelled"
case actions_model.StatusSuccess: 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) displayName := fromDisplayName(sender)
messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run) messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
metadataHeaders := generateMetadataHeaders(repo) metadataHeaders := generateMetadataHeaders(repo)
@ -84,6 +86,7 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
Name: converted0.Name, Name: converted0.Name,
Status: job.Status, Status: job.Status,
Attempt: converted0.RunAttempt, Attempt: converted0.RunAttempt,
Duration: job.Duration(),
}) })
} }
@ -93,27 +96,28 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
} }
for lang, tos := range langMap { for lang, tos := range langMap {
locale := translation.NewLocale(lang) locale := translation.NewLocale(lang)
var runStatusText string var runStatusTrString string
switch run.Status { switch run.Status {
case actions_model.StatusSuccess: case actions_model.StatusSuccess:
runStatusText = "All jobs have succeeded" runStatusTrString = "mail.repo.actions.jobs.all_succeeded"
case actions_model.StatusFailure: case actions_model.StatusFailure:
runStatusText = "All jobs have failed" runStatusTrString = "mail.repo.actions.jobs.all_failed"
for _, job := range jobs { for _, job := range jobs {
if !job.Status.IsFailure() { if !job.Status.IsFailure() {
runStatusText = "Some jobs were not successful" runStatusTrString = "mail.repo.actions.jobs.some_not_successful"
break break
} }
} }
case actions_model.StatusCancelled: 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 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, "Subject": subject,
"Repo": repo, "Repo": repo,
"Run": run, "Run": run,
"RunStatusText": runStatusText, "RunStatusText": locale.TrString(runStatusTrString),
"Jobs": convertedJobs, "Jobs": convertedJobs,
"locale": locale, "locale": locale,
}); err != nil { }); err != nil {

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

@ -63,7 +63,7 @@ func TestGiteaUploadRepo(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName}) 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) assert.Equal(t, repo_model.RepositoryReady, repo.Status)
milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ 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}) fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
baseRef := "master" 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)) 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()}) err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(t.Context(), &git.RunOpts{Dir: fromRepo.RepoPath()})
assert.NoError(t, err) assert.NoError(t, err)

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"github.com/google/go-github/v71/github" "github.com/google/go-github/v74/github"
"golang.org/x/oauth2" "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) 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 // 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 // Use either a username/password, personal token entered into the username field, or anonymous/public access
// Note: Public access only allows very basic 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())) 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 { if err != nil {
log.Trace("Error logging into gitlab: %v", err) log.Trace("Error logging into gitlab: %v", err)
return nil, err return nil, err
@ -206,7 +200,7 @@ func (g *GitlabDownloader) GetTopics(ctx context.Context) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return gr.TagList, err return gr.Topics, err
} }
// GetMilestones returns milestones // GetMilestones returns milestones

@ -31,7 +31,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
t.Skipf("Can't access test repo, skipping %s", t.Name()) t.Skipf("Can't access test repo, skipping %s", t.Name())
} }
ctx := t.Context() 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 { if err != nil {
t.Fatalf("NewGitlabDownloader is nil: %v", err) 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() remoteName := m.GetRemoteName()
repoPath := m.GetRepository(ctx).RepoPath() repo := m.GetRepository(ctx)
// Remove old remote // 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) { if err != nil && !git.IsRemoteNotExistError(err) {
return err return err
} }
cmd := git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr) err = gitrepo.GitRemoteAdd(ctx, repo, remoteName, addr, gitrepo.RemoteOptionMirrorFetch)
_, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath})
if err != nil && !git.IsRemoteNotExistError(err) { if err != nil && !git.IsRemoteNotExistError(err) {
return err return err
} }
if m.Repo.HasWiki() { if repo_service.HasWiki(ctx, m.Repo) {
wikiPath := m.Repo.WikiPath()
wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr) wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr)
// Remove old remote of wiki // 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) { if err != nil && !git.IsRemoteNotExistError(err) {
return err return err
} }
cmd = git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath) err = gitrepo.GitRemoteAdd(ctx, repo.WikiStorageRepo(), remoteName, wikiRemotePath, gitrepo.RemoteOptionMirrorFetch)
_, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: wikiPath})
if err != nil && !git.IsRemoteNotExistError(err) { if err != nil && !git.IsRemoteNotExistError(err) {
return err return err
} }
@ -197,25 +194,21 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult {
func pruneBrokenReferences(ctx context.Context, func pruneBrokenReferences(ctx context.Context,
m *repo_model.Mirror, m *repo_model.Mirror,
repoPath string,
timeout time.Duration, timeout time.Duration,
stdoutBuilder, stderrBuilder *strings.Builder, stdoutBuilder, stderrBuilder *strings.Builder,
isWiki bool, isWiki bool,
) error { ) error {
wiki := "" wiki := ""
var storageRepo gitrepo.Repository = m.Repo
if isWiki { if isWiki {
wiki = "Wiki " wiki = "Wiki "
storageRepo = m.Repo.WikiStorageRepo()
} }
stderrBuilder.Reset() stderrBuilder.Reset()
stdoutBuilder.Reset() stdoutBuilder.Reset()
pruneErr := git.NewCommand("remote", "prune").AddDynamicArguments(m.GetRemoteName()).
Run(ctx, &git.RunOpts{ pruneErr := gitrepo.GitRemotePrune(ctx, storageRepo, m.GetRemoteName(), timeout, stdoutBuilder, stderrBuilder)
Timeout: timeout,
Dir: repoPath,
Stdout: stdoutBuilder,
Stderr: stderrBuilder,
})
if pruneErr != nil { if pruneErr != nil {
stdout := stdoutBuilder.String() stdout := stdoutBuilder.String()
stderr := stderrBuilder.String() stderr := stderrBuilder.String()
@ -226,7 +219,7 @@ func pruneBrokenReferences(ctx context.Context,
stdoutMessage := util.SanitizeCredentialURLs(stdout) 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) 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 { if err := system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err) 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()) 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 { 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 return nil, false
} }
@ -298,7 +291,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
err = nil err = nil
// Attempt prune // Attempt prune
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, false) pruneErr := pruneBrokenReferences(ctx, m, timeout, &stdoutBuilder, &stderrBuilder, false)
if pruneErr == nil { if pruneErr == nil {
// Successful prune - reattempt mirror // Successful prune - reattempt mirror
stderrBuilder.Reset() stderrBuilder.Reset()
@ -347,7 +340,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint) endpoint := lfs.DetermineEndpoint(remoteURL.String(), m.LFSEndpoint)
lfsClient := lfs.NewClient(endpoint, nil) lfsClient := lfs.NewClient(endpoint, nil)
if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, m.Repo, gitRepo, lfsClient); err != 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) log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo)
if err := repo_module.UpdateRepoSize(ctx, m.Repo); err != nil { 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) log.Trace("SyncMirrors [repo: %-v Wiki]: running git remote update...", m.Repo)
stderrBuilder.Reset() stderrBuilder.Reset()
stdoutBuilder.Reset() stdoutBuilder.Reset()
if err := git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()).
Run(ctx, &git.RunOpts{ if err := gitrepo.GitRemoteUpdatePrune(ctx, m.Repo.WikiStorageRepo(), m.GetRemoteName(),
Timeout: timeout, timeout, &stdoutBuilder, &stderrBuilder); err != nil {
Dir: wikiPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
stdout := stdoutBuilder.String() stdout := stdoutBuilder.String()
stderr := stderrBuilder.String() stderr := stderrBuilder.String()
@ -391,19 +380,14 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
err = nil err = nil
// Attempt prune // Attempt prune
pruneErr := pruneBrokenReferences(ctx, m, repoPath, timeout, &stdoutBuilder, &stderrBuilder, true) pruneErr := pruneBrokenReferences(ctx, m, timeout, &stdoutBuilder, &stderrBuilder, true)
if pruneErr == nil { if pruneErr == nil {
// Successful prune - reattempt mirror // Successful prune - reattempt mirror
stderrBuilder.Reset() stderrBuilder.Reset()
stdoutBuilder.Reset() stdoutBuilder.Reset()
if err = git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). if err = gitrepo.GitRemoteUpdatePrune(ctx, m.Repo.WikiStorageRepo(), m.GetRemoteName(),
Run(ctx, &git.RunOpts{ timeout, &stdoutBuilder, &stderrBuilder); err != nil {
Timeout: timeout,
Dir: wikiPath,
Stdout: &stdoutBuilder,
Stderr: &stderrBuilder,
}); err != nil {
stdout := stdoutBuilder.String() stdout := stdoutBuilder.String()
stderr := stderrBuilder.String() stderr := stderrBuilder.String()
stderrMessage = util.SanitizeCredentialURLs(stderr) stderrMessage = util.SanitizeCredentialURLs(stderr)

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