0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-07 01:43:24 +02:00

Merge branch 'main' into lunny/fix_delete_code_comment

This commit is contained in:
Lunny Xiao 2025-09-04 21:30:45 -07:00 committed by GitHub
commit 42bcea7b78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
123 changed files with 13310 additions and 15405 deletions

View File

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

View File

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

2
.github/labeler.yml vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

5
.npmrc
View File

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

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ or if SQLite support is required:
The `build` target is split into two sub-targets:
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod).
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater.
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and [pnpm](https://pnpm.io/installation).
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js.

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

192
go.mod
View File

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

587
go.sum

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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})
}

View File

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

View File

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

View File

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

View File

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

48
modules/gitrepo/config.go Normal file
View File

@ -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
})
}

16
modules/gitrepo/fsck.go Normal file
View File

@ -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)})
}

View File

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

85
modules/gitrepo/remote.go Normal file
View File

@ -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,
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2694,7 +2694,7 @@ maintenance=Manutenção
dashboard=Painel
self_check=Auto-verificação
identity_access=Identidade e Acesso
users=Contas de usuário
users=Contas de Usuário
organizations=Organizações
repositories=Repositórios
hooks=Webhooks
@ -2790,33 +2790,33 @@ users.remote=Remoto
users.2fa=2FA
users.repos=Repositórios
users.created=Criado
users.last_login=Último acesso
users.last_login=Último Acesso
users.never_login=Nunca Acessado
users.send_register_notify=Enviar Notificação de Cadastro de Usuário
users.new_success=Usuário "%s" criado.
users.edit=Editar
users.auth_source=Fonte da autenticação
users.auth_source=Fonte da Autenticação
users.local=Local
users.auth_login_name=Nome de acesso da autenticação
users.auth_login_name=Nome de Acesso da Autenticação
users.password_helper=Deixe a senha em branco para mantê-la inalterada.
users.update_profile_success=A conta de usuário foi atualizada.
users.edit_account=Editar Conta de Usuário
users.max_repo_creation=Número máximo de repositórios
users.max_repo_creation=Número Máximo de Repositórios
users.max_repo_creation_desc=(Use -1 para usar o limite padrão global.)
users.is_activated=Conta de usuário está ativada
users.prohibit_login=Desabilitar acesso
users.is_admin=É administrador
users.is_restricted=Está restrito
users.is_activated=Conta de Usuário está Ativada
users.prohibit_login=Desabilitar Acesso
users.is_admin=É Administrador
users.is_restricted=Está Restrito
users.allow_git_hook=Pode criar hooks Git
users.allow_git_hook_tooltip=Hooks Git são executados como o usuário do SO que executa Gitea e terá o mesmo nível de acesso ao servidor. Como resultado, os usuários com esse privilégio especial de Hook do Git podem acessar e modificar todos os repositórios do Gitea, bem como o banco de dados usado pelo Gitea. Por conseguinte, podem também obter privilégios de administrador do Gitea.
users.allow_import_local=Pode importar repositórios locais
users.allow_create_organization=Pode criar organizações
users.allow_import_local=Pode Importar Repositórios Locais
users.allow_create_organization=Pode Criar Organizações
users.update_profile=Atualizar Conta de Usuário
users.delete_account=Excluir conta de usuário
users.delete_account=Excluir Conta de Usuário
users.cannot_delete_self=Você não pode excluir você mesmo
users.still_own_repo=Este usuário ainda possui um ou mais repositórios. Exclua ou transfira esses repositórios primeiro.
users.still_has_org=Este usuário é membro de uma organização. Remova o usuário de qualquer organização primeiro.
users.purge=Eliminar usuário
users.purge=Eliminar Usuário
users.purge_help=Exclua forçosamente o usuário e quaisquer repositórios, organizações e pacotes pertencentes ao usuário. Todos os comentários também serão excluídos.
users.deletion_success=A conta de usuário foi excluída.
users.reset_2fa=Reinicializar 2FA
@ -2827,12 +2827,12 @@ users.list_status_filter.not_active=Inativo
users.list_status_filter.is_admin=Administrador
users.list_status_filter.not_admin=Não Administrador
users.list_status_filter.is_restricted=Restrito
users.list_status_filter.not_restricted=Não restrito
users.list_status_filter.is_prohibit_login=Proibir login
users.list_status_filter.not_prohibit_login=Permitir login
users.list_status_filter.not_restricted=Não Restrito
users.list_status_filter.is_prohibit_login=Proibir Login
users.list_status_filter.not_prohibit_login=Permitir Login
users.list_status_filter.is_2fa_enabled=2FA Ativado
users.list_status_filter.not_2fa_enabled=2FA Desativado
users.details=Detalhes do usuário
users.details=Detalhes do Usuário
emails.email_manage_panel=Gerenciamento de E-mail de Usuário
emails.primary=Principal
@ -2849,13 +2849,13 @@ emails.change_email_text=Tem certeza que deseja atualizar este e-mail?
emails.delete=Excluir E-mail
emails.delete_desc=Tem certeza que deseja excluir este e-mail?
orgs.org_manage_panel=Gerenciamento da organização
orgs.org_manage_panel=Gerenciamento da Organização
orgs.name=Nome
orgs.teams=Equipes
orgs.members=Membros
orgs.new_orga=Nova organização
orgs.new_orga=Nova Organização
repos.repo_manage_panel=Gerenciamento do repositório
repos.repo_manage_panel=Gerenciamento do Repositório
repos.unadopted=Repositórios Não Adotados
repos.unadopted.no_more=Não foram encontrados mais repositórios não adotados
repos.owner=Proprietário
@ -3137,7 +3137,7 @@ monitor.process=Processos em Execução
monitor.processes_count=%d processos
monitor.download_diagnosis_report=Baixar relatório de diagnóstico
monitor.desc=Descrição
monitor.start=Hora de início
monitor.start=Hora de Início
monitor.execute_time=Tempo de Execução
monitor.last_execution_result=Resultado
monitor.process.cancel=Cancelar processo
@ -3149,25 +3149,28 @@ monitor.queue=Fila: %s
monitor.queue.name=Nome
monitor.queue.type=Tipo
monitor.queue.exemplar=Tipo de Modelo
monitor.queue.numberworkers=Número de executores
monitor.queue.maxnumberworkers=Número máximo de executores
monitor.queue.numberworkers=Número de Executores
monitor.queue.activeworkers=Executores Ativos
monitor.queue.maxnumberworkers=Número Máximo de Executores
monitor.queue.numberinqueue=Número na Fila
monitor.queue.settings.title=Configurações do conjunto
monitor.queue.review_add=Revisar / Adicionar Executores
monitor.queue.settings.title=Configurações do Conjunto
monitor.queue.settings.maxnumberworkers=Número máximo de executores
monitor.queue.settings.maxnumberworkers.placeholder=Atualmente %[1]d
monitor.queue.settings.maxnumberworkers.error=Número máximo de executores deve ser um número
monitor.queue.settings.submit=Atualizar Configurações
monitor.queue.settings.changed=Configurações atualizadas
monitor.queue.settings.changed=Configurações Atualizadas
monitor.queue.settings.remove_all_items=Remover tudo
monitor.queue.settings.remove_all_items_done=Todos os itens da fila foram removidos.
notices.system_notice_list=Avisos do Sistema
notices.view_detail_header=Ver detalhes do aviso
notices.view_detail_header=Ver Detalhes do Aviso
notices.operations=Operações
notices.select_all=Marcar todos
notices.deselect_all=Desmarcar todos
notices.inverse_selection=Inverter seleção
notices.select_all=Marcar Todos
notices.deselect_all=Desmarcar Todos
notices.inverse_selection=Inverter Seleção
notices.delete_selected=Excluir Seleção
notices.delete_all=Excluir todos os avisos
notices.delete_all=Excluir Todos os Avisos
notices.type=Tipo
notices.type_1=Repositório
notices.type_2=Tarefa

View File

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

14391
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

10440
pnpm-lock.yaml generated Normal file

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,6 +41,7 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull"
)
const (
@ -550,7 +551,7 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
headBranchRef = git.TagPrefix + ci.HeadBranch
}
ci.CompareInfo, err = ci.HeadGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, ci.DirectComparison, fileOnly)
ci.CompareInfo, err = pull_service.GetCompareInfo(ctx, baseRepo, ci.HeadRepo, ci.HeadGitRepo, baseBranchRef, headBranchRef, ci.DirectComparison, fileOnly)
if err != nil {
ctx.ServerError("GetCompareInfo", err)
return nil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,26 +11,28 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
org_model "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
func getMergeBase(repo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) {
func getMergeBase(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) {
// Add a temporary remote
tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano())
if err := repo.AddRemote(tmpRemote, repo.Path, false); err != nil {
return "", fmt.Errorf("AddRemote: %w", err)
if err := gitrepo.GitRemoteAdd(ctx, repo, tmpRemote, gitRepo.Path); err != nil {
return "", fmt.Errorf("GitRemoteAdd: %w", err)
}
defer func() {
if err := repo.RemoveRemote(tmpRemote); err != nil {
log.Error("getMergeBase: RemoveRemote: %v", err)
if err := gitrepo.GitRemoteRemove(graceful.GetManager().ShutdownContext(), repo, tmpRemote); err != nil {
log.Error("getMergeBase: GitRemoteRemove: %v", err)
}
}()
mergeBase, _, err := repo.GetMergeBase(tmpRemote, baseBranch, headBranch)
mergeBase, _, err := gitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch)
return mergeBase, err
}
@ -97,7 +99,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque
}
// get the mergebase
mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName())
mergeBase, err := getMergeBase(ctx, pr.BaseRepo, repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName())
if err != nil {
return nil, err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

95
services/pull/compare.go Normal file
View File

@ -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
}

View File

@ -141,7 +141,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
return err
}
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
compareInfo, err := GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo,
git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false, false)
if err != nil {
return err
@ -1074,7 +1074,7 @@ func GetPullCommits(ctx context.Context, baseGitRepo *git.Repository, doer *user
if pull.HasMerged {
baseBranch = pull.MergeBase
}
prInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), baseBranch, pull.GetGitHeadRefName(), true, false)
prInfo, err := GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, baseBranch, pull.GetGitHeadRefName(), true, false)
if err != nil {
return nil, "", err
}

View File

@ -14,6 +14,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
@ -47,10 +48,9 @@ func GitFsckRepos(ctx context.Context, timeout time.Duration, args git.TrustedCm
// GitFsckRepo calls 'git fsck' to check an individual repository's health.
func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error {
log.Trace("Running health check on repository %-v", repo)
repoPath := repo.RepoPath()
if err := git.Fsck(ctx, repoPath, timeout, args); err != nil {
log.Warn("Failed to health check repository (%-v): %v", repo, err)
log.Trace("Running health check on repository %-v", repo.FullName())
if err := gitrepo.Fsck(ctx, repo, timeout, args); err != nil {
log.Warn("Failed to health check repository (%-v): %v", repo.FullName(), err)
if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
@ -190,7 +190,7 @@ func ReinitMissingRepositories(ctx context.Context) error {
default:
}
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
if err := git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
if err := gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
log.Error("CreateRepositoryNotice: %v", err2)

View File

@ -142,7 +142,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir
// InitRepository initializes README and .gitignore if needed.
func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) {
// Init git bare new repository.
if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
if err = gitrepo.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
return fmt.Errorf("git.InitRepository: %w", err)
} else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)

View File

@ -315,9 +315,13 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams
}
}
// Remove wiki files
if repo.HasWiki() {
system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
// Remove wiki files if it exists.
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
desc := fmt.Sprintf("Delete wiki repository files [%s]: %v", repo.FullName(), err)
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
}
// Remove archives

View File

@ -32,11 +32,12 @@ func SyncRepositoryHooks(ctx context.Context) error {
}
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return fmt.Errorf("SyncRepositoryHook: %w", err)
return fmt.Errorf("CreateDelegateHooks: %w", err)
}
if repo.HasWiki() {
if HasWiki(ctx, repo) {
if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
return fmt.Errorf("SyncRepositoryHook: %w", err)
return fmt.Errorf("CreateDelegateHooks: %w", err)
}
}
return nil

View File

@ -261,38 +261,27 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
return repo, committer.Commit()
}
// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
// This also removes possible user credentials.
func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
cmd := git.NewCommand("remote", "rm", "origin")
// if the origin does not exist
_, _, err := cmd.RunStdString(ctx, &git.RunOpts{
Dir: repoPath,
})
if err != nil && !git.IsRemoteNotExistError(err) {
return err
}
return nil
}
// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return repo, fmt.Errorf("createDelegateHooks: %w", err)
}
if repo.HasWiki() {
hasWiki := HasWiki(ctx, repo)
if hasWiki {
if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
}
}
_, _, err := git.NewCommand("remote", "rm", "origin").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()})
err := gitrepo.GitRemoteRemove(ctx, repo, "origin")
if err != nil && !git.IsRemoteNotExistError(err) {
return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
}
if repo.HasWiki() {
if err := cleanUpMigrateGitConfig(ctx, repo.WikiPath()); err != nil {
if hasWiki {
err = gitrepo.GitRemoteRemove(ctx, repo.WikiStorageRepo(), "origin")
if err != nil && !git.IsRemoteNotExistError(err) {
return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
}
}

View File

@ -20,6 +20,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log"
@ -336,3 +337,11 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili
return nil
}
func HasWiki(ctx context.Context, repo *repo_model.Repository) bool {
hasWiki, err := gitrepo.IsRepositoryExist(ctx, repo.WikiStorageRepo())
if err != nil {
log.Error("gitrepo.IsRepositoryExist: %v", err)
}
return hasWiki && err == nil
}

View File

@ -61,3 +61,12 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
assert.NoError(t, err)
assert.True(t, act.IsPrivate)
}
func TestRepository_HasWiki(t *testing.T) {
unittest.PrepareTestEnv(t)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
assert.True(t, HasWiki(t.Context(), repo1))
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.False(t, HasWiki(t.Context(), repo2))
}

View File

@ -13,7 +13,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
notify_service "code.gitea.io/gitea/services/notify"
@ -122,7 +121,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
}
// 3 -Init git bare new repository.
if err = git.InitRepository(ctx, generateRepo.RepoPath(), true, generateRepo.ObjectFormatName); err != nil {
if err = gitrepo.InitRepository(ctx, generateRepo, generateRepo.ObjectFormatName); err != nil {
return nil, fmt.Errorf("git.InitRepository: %w", err)
} else if err = gitrepo.CreateDelegateHooks(ctx, generateRepo); err != nil {
return nil, fmt.Errorf("createDelegateHooks: %w", err)

View File

@ -358,14 +358,9 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
return fmt.Errorf("rename repository directory: %w", err)
}
wikiPath := repo.WikiPath()
isExist, err := util.IsExist(wikiPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", wikiPath, err)
return err
}
if isExist {
if err = util.Rename(wikiPath, repo_model.WikiPath(repo.Owner.Name, newRepoName)); err != nil {
if HasWiki(ctx, repo) {
if err = gitrepo.RenameRepository(ctx, repo.WikiStorageRepo(), repo_model.StorageRepo(
repo_model.RelativeWikiPath(repo.OwnerName, newRepoName))); err != nil {
return fmt.Errorf("rename repository wiki: %w", err)
}
}

View File

@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
@ -35,11 +36,15 @@ func getWikiWorkingLockKey(repoID int64) string {
// InitWiki initializes a wiki for repository,
// it does nothing when repository already has wiki.
func InitWiki(ctx context.Context, repo *repo_model.Repository) error {
if repo.HasWiki() {
// don't use HasWiki because the error should not be ignored.
if exist, err := gitrepo.IsRepositoryExist(ctx, repo.WikiStorageRepo()); err != nil {
return err
} else if exist {
return nil
}
if err := git.InitRepository(ctx, repo.WikiPath(), true, repo.ObjectFormatName); err != nil {
// wiki's object format should be the same as repository's
if err := gitrepo.InitRepository(ctx, repo.WikiStorageRepo(), repo.ObjectFormatName); err != nil {
return fmt.Errorf("InitRepository: %w", err)
} else if err = gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
@ -355,7 +360,14 @@ func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
return err
}
system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
if err := gitrepo.DeleteRepository(ctx, repo.WikiStorageRepo()); err != nil {
desc := fmt.Sprintf("Delete wiki repository files [%s]: %v", repo.FullName(), err)
// Note we use the db.DefaultContext here rather than passing in a context as the context may be cancelled
if err = system_model.CreateNotice(graceful.GetManager().ShutdownContext(), system_model.NoticeRepository, desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
}
return nil
}
@ -369,7 +381,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
return fmt.Errorf("unable to update database: %w", err)
}
if !repo.HasWiki() {
if !repo_service.HasWiki(ctx, repo) {
return nil
}

View File

@ -13,6 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
repo_service "code.gitea.io/gitea/services/repository"
_ "code.gitea.io/gitea/models/actions"
@ -149,7 +150,7 @@ func TestRepository_InitWiki(t *testing.T) {
// repo2 does not already have a wiki
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.NoError(t, InitWiki(t.Context(), repo2))
assert.True(t, repo2.HasWiki())
assert.True(t, repo_service.HasWiki(t.Context(), repo2))
}
func TestRepository_AddWikiPage(t *testing.T) {

View File

@ -0,0 +1,20 @@
<h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.config.picture_config"}}
</h4>
<div class="ui attached table segment">
<dl class="admin-dl-horizontal">
<dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
<dd>
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label>
</div>
</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd>
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
</div>
</dd>
</dl>
</div>

View File

@ -0,0 +1,7 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}}
{{template "admin/config_settings/avatars" .}}
{{template "admin/config_settings/repository" .}}
{{template "admin/layout_footer" .}}

View File

@ -1,25 +1,3 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}}
<h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.config.picture_config"}}
</h4>
<div class="ui attached table segment">
<dl class="admin-dl-horizontal">
<dt>{{ctx.Locale.Tr "admin.config.disable_gravatar"}}</dt>
<dd>
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.disable_gravatar"}}">
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.DisableGravatar.Value ctx}}checked{{end}}><label></label>
</div>
</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd>
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
</div>
</dd>
</dl>
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "repository"}}
</h4>
@ -48,4 +26,3 @@
</div>
</form>
</div>
{{template "admin/layout_footer" .}}

View File

@ -0,0 +1,13 @@
Inviter:
DisplayName: Inviter Display Name
Team:
Name: Team name
Organization:
DisplayName: Organization Display Name
InviteURL: http://localhost/org/team/invite
Invite:
Email: invited@example.com

View File

@ -10,6 +10,6 @@
<p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p>
<p>{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}</p>
<p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p>
<p>© <a href="{{AppUrl}}">{{AppName}}</a></p>
</body>
</html>

View File

@ -1,10 +1,10 @@
RunStatusText: run status text ....
RunStatusText: Jobs status aggregation
Repo:
FullName: RepoName
FullName: Repo/Name
Run:
WorkflowID: WorkflowID
WorkflowID: workflow.yml
HTMLURL: http://localhost/run/1
Jobs:
@ -12,7 +12,9 @@ Jobs:
Status: success
Attempt: 1
HTMLURL: http://localhost/job/1
Duration: 1h2m3s
- Name: Job-Name-2
Status: failed
Status: failure
Attempt: 2
HTMLURL: http://localhost/job/2
Duration: 1h2m3s

View File

@ -15,7 +15,7 @@
{{range $job := .Jobs}}
<li style="background-color: #ffffff; border: 1px solid #ddd; border-radius: 6px; padding: 12px 16px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); transition: box-shadow 0.2s ease;">
<a href="{{$job.HTMLURL}}" style="color: #0073e6; text-decoration: none; font-weight: bold;">
{{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}
{{$job.Status}}: {{$job.Name}}{{if gt $job.Attempt 1}}, Attempt #{{$job.Attempt}}{{end}}, {{$job.Duration}}
</a>
</li>
{{end}}

View File

@ -0,0 +1,3 @@
Subject: Collaborator added
Link: http://localhost
RepoName: Repo/Name

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