0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-10 16:11:36 +02:00

Merge branch 'main' into lunny/issue_dev

This commit is contained in:
Lunny Xiao 2025-09-30 20:40:00 -07:00
commit c32d4aa926
493 changed files with 18974 additions and 20014 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

File diff suppressed because it is too large Load Diff

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,10 @@ jobs:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv python install 3.12
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-py
- run: make deps-frontend
- run: make lint-templates
@ -60,11 +59,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
- run: make lint-swagger
@ -131,11 +129,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
- run: make lint-frontend
- run: make checks-frontend
@ -180,11 +177,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- 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
@ -72,13 +72,13 @@ jobs:
go-version-file: go.mod
check-latest: true
- run: make deps-backend
- run: make backend
- run: GOEXPERIMENT='' make backend
env:
TAGS: bindata gogit sqlite sqlite_unlock_notify
- name: run migration tests
run: make test-sqlite-migration
- name: run tests
run: make test-sqlite
run: GOEXPERIMENT='' make test-sqlite
timeout-minutes: 50
env:
TAGS: bindata gogit sqlite sqlite_unlock_notify
@ -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
@ -142,7 +142,7 @@ jobs:
RACE_ENABLED: true
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- name: unit-tests-gogit
run: make unit-test-coverage test-check
run: GOEXPERIMENT='' make unit-test-coverage test-check
env:
TAGS: bindata gogit
RACE_ENABLED: true
@ -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,12 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- 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,10 @@ jobs:
with:
go-version-file: go.mod
check-latest: true
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
# xgo build
- run: make release

View File

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

View File

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

1
.gitignore vendored
View File

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

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

@ -63,3 +63,4 @@ hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci)
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)
TheFox <thefox0x7@gmail.com> (@TheFox0x7)

View File

@ -18,6 +18,10 @@ DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := code.gitea.io/gitea
# By default use go's 1.25 experimental json v2 library when building
# TODO: remove when no longer experimental
export GOEXPERIMENT ?= jsonv2
GO ?= go
SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
@ -27,11 +31,11 @@ XGO_VERSION := go-1.25.x
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
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1
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
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf00e56953556c6d80f8a01b286
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
@ -96,6 +100,15 @@ STORED_VERSION_FILE := VERSION
GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# Enable typescript support in Node.js before 22.18
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | tr '.' ' '))
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
else
NODE_VARS :=
endif
ifneq ($(GITHUB_REF_TYPE),branch)
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
GITEA_VERSION ?= $(VERSION)
@ -127,7 +140,7 @@ GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/m
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
@ -153,9 +166,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST)
GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.js *.ts *.cjs tests/e2e
ESLINT_FILES := web_src/js tools *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go)
@ -217,10 +230,13 @@ git-check:
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
@ -334,29 +350,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
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES)
$(NODE_VARS) 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
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --flag unstable_native_nodejs_ts_config $(ESLINT_FILES) --fix
$(NODE_VARS) pnpm exec vue-tsc
.PHONY: lint-css
lint-css: node_modules ## lint css files
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
$(NODE_VARS) 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
$(NODE_VARS) 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)
$(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md
lint-md: node_modules ## lint markdown files
npx markdownlint *.md
$(NODE_VARS) pnpm exec markdownlint *.md
.PHONY: lint-spell
lint-spell: ## lint spelling
@ -403,7 +419,7 @@ lint-actions: ## lint action workflow files
.PHONY: lint-templates
lint-templates: .venv node_modules ## lint template files
@node tools/lint-templates-svg.js
@node tools/lint-templates-svg.ts
@uv run --frozen djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml
@ -417,7 +433,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 $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret
.PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild
@ -433,7 +449,7 @@ test-backend: ## test backend files
.PHONY: test-frontend
test-frontend: node_modules ## test frontend files
npx vitest
$(NODE_VARS) pnpm exec vitest
.PHONY: test-check
test-check:
@ -576,7 +592,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
playwright: deps-frontend
npx playwright install $(PLAYWRIGHT_FLAGS)
$(NODE_VARS) pnpm exec playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e%
test-e2e%: TEST_TYPE ?= e2e
@ -754,7 +770,7 @@ generate-go: $(TAGS_PREREQ)
.PHONY: security-check
security-check:
go run $(GOVULNCHECK_PACKAGE) -show color ./...
GOEXPERIMENT= go run $(GOVULNCHECK_PACKAGE) -show color ./...
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
@ -839,8 +855,8 @@ 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
$(NODE_VARS) pnpm install --frozen-lockfile
@touch node_modules
.venv: uv.lock
@ -852,16 +868,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
$(NODE_VARS) pnpm exec updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml
$(NODE_VARS) pnpm install
$(NODE_VARS) pnpm exec nolyfill install
$(NODE_VARS) pnpm install
@touch node_modules
.PHONY: update-py
update-py: node-check | node_modules ## update py dependencies
npx updates -u -f pyproject.toml
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml
rm -rf .venv uv.lock
uv sync
@touch .venv
@ -869,17 +885,17 @@ 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 $(NODE_VARS) pnpm exec webpack --disable-interpret
@touch $(WEBPACK_DEST)
.PHONY: svg
svg: node-check | node_modules ## build svg files
rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.js
node tools/generate-svg.ts
.PHONY: svg-check
svg-check: svg
@ -893,11 +909,11 @@ svg-check: svg
.PHONY: lockfile-check
lockfile-check:
npm install --package-lock-only
@diff=$$(git diff --color=always package-lock.json); \
$(NODE_VARS) 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 +933,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 ## generate images
cd tools && node generate-images.ts $(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

@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string
break
}
}
return
return mainOptions, subCmd, subArgs
}
func showUsage() {

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)
}
@ -270,6 +265,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, setting.LFS.Storage.Path)
excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.RepoArchive.Storage.Path)
excludes = append(excludes, setting.Log.RootPath)
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil {
fatal("Failed to include data directory: %v", err)
@ -283,7 +279,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 +293,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 +318,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

@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/assetfs"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/public"
@ -19,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
"github.com/urfave/cli/v3"
)

View File

@ -15,6 +15,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
@ -312,7 +313,7 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error {
setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what
if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
if _, _, err := gitcmd.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err)
}

View File

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer"
"code.gitea.io/gitea/modules/log"
@ -229,11 +230,6 @@ func runServ(ctx context.Context, c *cli.Command) error {
username := repoPathFields[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if !repo.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@ -280,6 +276,11 @@ func runServ(ctx context.Context, c *cli.Command) error {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git")
// LFS SSH protocol
if verb == git.CmdVerbLfsTransfer {
token, err := getLFSAuthToken(ctx, lfsVerb, results)
@ -312,30 +313,30 @@ func runServ(ctx context.Context, c *cli.Command) error {
return nil
}
var gitcmd *exec.Cmd
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
var command *exec.Cmd
gitBinPath := filepath.Dir(gitcmd.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
command = exec.CommandContext(ctx, gitcmd.GitExecutable, verbFields[1], repoPath)
}
}
if gitcmd == nil {
if command == nil {
// by default, use the verb (it has been checked above by allowedCommands)
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
command = exec.CommandContext(ctx, gitBinVerb, repoPath)
}
process.SetSysProcAttribute(gitcmd)
gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr
gitcmd.Env = append(gitcmd.Env, os.Environ()...)
gitcmd.Env = append(gitcmd.Env,
process.SetSysProcAttribute(command)
command.Dir = setting.RepoRootPath
command.Stdout = os.Stdout
command.Stdin = os.Stdin
command.Stderr = os.Stderr
command.Env = append(command.Env, os.Environ()...)
command.Env = append(command.Env,
repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
repo_module.EnvRepoName+"="+results.RepoName,
repo_module.EnvRepoUsername+"="+results.OwnerName,
@ -350,9 +351,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
)
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
// it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
command.Env = append(command.Env, gitcmd.CommonCmdServEnvs()...)
if err = gitcmd.Run(); err != nil {
if err = command.Run(); err != nil {
return fail(ctx, "Failed to execute git command", "Failed to execute git command: %v", err)
}

View File

@ -11,7 +11,7 @@ The default version will read from `docs/config.yml`. You can override this
using the option `--version`.
The upstream branches will be fetched, using the remote `origin`. This can
be overrided using `--upstream`, and fetching can be avoided using
be overridden using `--upstream`, and fetching can be avoided using
`--no-fetch`.
By default the branch created will be called `backport-$PR-$VERSION`. You

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

@ -150,7 +150,7 @@
<p>In general, Your Gitea Instance retains User Personal Information for as long as your account is active, or as needed to provide you service.</p>
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the bassis of consent within 30 days.</p>
<p>If you would like to cancel your account or delete your User Personal Information, you may do so in your user profile. We retain and use your information as necessary to comply with our legal obligations, resolve disputes, and enforce our agreements, but barring legal requirements, we will delete your full profile (within reason) within 90 days of your request. Feel free to contact our support to request erasure of the data we process on the basis of consent within 30 days.</p>
<p>After an account has been deleted, certain data, such as contributions to other Users' repositories and comments in others' issues, will remain. However, we will delete or de-identify your User Personal Information, including your username and email address, from the author field of issues, pull requests, and comments by associating them with a ghost user.</p>

1029
eslint.config.ts Normal file

File diff suppressed because it is too large Load Diff

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

210
go.mod
View File

@ -1,6 +1,6 @@
module code.gitea.io/gitea
go 1.24.6
go 1.25.1
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:
@ -10,37 +10,37 @@ 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
gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15
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/gobwas/glob v0.2.3
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4
github.com/goccy/go-json v0.10.5
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
@ -76,56 +76,55 @@ require (
github.com/huandu/xstrings v1.5.0
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jhillyerd/enmime v1.3.0
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/wneessen/go-mail v0.6.2
github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.7.1
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.17.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.29.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 +134,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 +183,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,16 +191,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/goccy/go-json v0.10.5 // indirect
github.com/go-webauthn/x v0.1.24 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
@ -207,50 +209,59 @@ 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/json-iterator/go v1.1.12 // 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 +271,28 @@ 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
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
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // 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
)
ignore (
./.venv
./node_modules
)
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 +300,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

606
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -72,96 +72,90 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
return nil, err
}
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
return db.WithTx2(ctx, func(ctx context.Context) ([]*GPGKey, error) {
keys := make([]*GPGKey, 0, len(ekeys))
keys := make([]*GPGKey, 0, len(ekeys))
verified := false
// Handle provided signature
if signature != "" {
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
if err != nil {
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
}
if err != nil {
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
}
if err != nil {
log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
return nil, ErrGPGInvalidTokenSignature{
ID: ekeys[0].PrimaryKey.KeyIdString(),
Wrapped: err,
verified := false
// Handle provided signature
if signature != "" {
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
if err != nil {
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
}
if err != nil {
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
}
if err != nil {
log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
return nil, ErrGPGInvalidTokenSignature{
ID: ekeys[0].PrimaryKey.KeyIdString(),
Wrapped: err,
}
}
ekeys = []*openpgp.Entity{signer}
verified = true
}
ekeys = []*openpgp.Entity{signer}
verified = true
}
if len(ekeys) > 1 {
id2key := map[string]*openpgp.Entity{}
newEKeys := make([]*openpgp.Entity, 0, len(ekeys))
for _, ekey := range ekeys {
id := ekey.PrimaryKey.KeyIdString()
if original, has := id2key[id]; has {
// Coalesce this with the other one
for _, subkey := range ekey.Subkeys {
if subkey.PublicKey == nil {
continue
}
found := false
for _, originalSubkey := range original.Subkeys {
if originalSubkey.PublicKey == nil {
if len(ekeys) > 1 {
id2key := map[string]*openpgp.Entity{}
newEKeys := make([]*openpgp.Entity, 0, len(ekeys))
for _, ekey := range ekeys {
id := ekey.PrimaryKey.KeyIdString()
if original, has := id2key[id]; has {
// Coalesce this with the other one
for _, subkey := range ekey.Subkeys {
if subkey.PublicKey == nil {
continue
}
if originalSubkey.PublicKey.KeyId == subkey.PublicKey.KeyId {
found = true
break
found := false
for _, originalSubkey := range original.Subkeys {
if originalSubkey.PublicKey == nil {
continue
}
if originalSubkey.PublicKey.KeyId == subkey.PublicKey.KeyId {
found = true
break
}
}
if !found {
original.Subkeys = append(original.Subkeys, subkey)
}
}
if !found {
original.Subkeys = append(original.Subkeys, subkey)
for name, identity := range ekey.Identities {
if _, has := original.Identities[name]; has {
continue
}
original.Identities[name] = identity
}
continue
}
for name, identity := range ekey.Identities {
if _, has := original.Identities[name]; has {
continue
}
original.Identities[name] = identity
}
continue
id2key[id] = ekey
newEKeys = append(newEKeys, ekey)
}
id2key[id] = ekey
newEKeys = append(newEKeys, ekey)
}
ekeys = newEKeys
}
for _, ekey := range ekeys {
// Key ID cannot be duplicated.
has, err := db.GetEngine(ctx).Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
Get(new(GPGKey))
if err != nil {
return nil, err
} else if has {
return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
ekeys = newEKeys
}
// Get DB session
for _, ekey := range ekeys {
// Key ID cannot be duplicated.
has, err := db.GetEngine(ctx).Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
Get(new(GPGKey))
if err != nil {
return nil, err
} else if has {
return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
}
key, err := parseGPGKey(ctx, ownerID, ekey, verified)
if err != nil {
return nil, err
}
key, err := parseGPGKey(ctx, ownerID, ekey, verified)
if err != nil {
return nil, err
}
if err = addGPGKey(ctx, key, content); err != nil {
return nil, err
if err = addGPGKey(ctx, key, content); err != nil {
return nil, err
}
keys = append(keys, key)
}
keys = append(keys, key)
}
return keys, committer.Commit()
return keys, nil
})
}

View File

@ -25,7 +25,7 @@ type CommitVerification struct {
SigningUser *user_model.User // if Verified, then SigningUser is non-nil
CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil
SigningEmail string
SigningKey *GPGKey
SigningKey *GPGKey // FIXME: need to refactor it to a new name like "SigningGPGKey", it is also used in some templates
SigningSSHKey *PublicKey
TrustStatus string
}

View File

@ -0,0 +1,37 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package asymkey
import (
"os"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
func GetDisplaySigningKey(key *git.SigningKey) string {
if key == nil || key.Format == "" {
return ""
}
switch key.Format {
case git.SigningKeyFormatOpenPGP:
return key.KeyID
case git.SigningKeyFormatSSH:
content, err := os.ReadFile(key.KeyID)
if err != nil {
log.Error("Unable to read SSH key %s: %v", key.KeyID, err)
return "(Unable to read SSH key)"
}
display, err := CalcFingerprint(string(content))
if err != nil {
log.Error("Unable to calculate fingerprint for SSH key %s: %v", key.KeyID, err)
return "(Unable to calculate fingerprint for SSH key)"
}
return display
}
setting.PanicInDevOrTesting("Unknown signing key format: %s", key.Format)
return "(Unknown key format)"
}

View File

@ -67,7 +67,7 @@ func (l *XORMLogBridge) Warn(v ...any) {
l.Log(stackLevel, log.WARN, "%s", fmt.Sprint(v...))
}
// Warnf show warnning log
// Warnf show warning log
func (l *XORMLogBridge) Warnf(format string, v ...any) {
l.Log(stackLevel, log.WARN, format, v...)
}

View File

@ -2,3 +2,7 @@
id: 1
lower_name: olduser1
redirect_user_id: 1
-
id: 2
lower_name: olduser2
redirect_user_id: 2

View File

@ -347,122 +347,111 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *
// RenameBranch rename a branch
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
return db.WithTx(ctx, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
sess := db.GetEngine(ctx)
// check whether from branch exist
var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil {
return err
} else if !exist || branch.IsDeleted {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// check whether to branch exist or is_deleted
var dstBranch Branch
exist, err = db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, to).Get(&dstBranch)
if err != nil {
return err
}
if exist {
if !dstBranch.IsDeleted {
return ErrBranchAlreadyExists{
BranchName: to,
// check whether from branch exist
var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil {
return err
} else if !exist || branch.IsDeleted {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
if _, err := db.GetEngine(ctx).ID(dstBranch.ID).NoAutoCondition().Delete(&dstBranch); err != nil {
return err
}
}
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to,
}); err != nil {
return err
} else if n <= 0 {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// 2. update default branch if needed
isDefault := repo.DefaultBranch == from
if isDefault {
repo.DefaultBranch = to
_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
// check whether to branch exist or is_deleted
var dstBranch Branch
exist, err = db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, to).Get(&dstBranch)
if err != nil {
return err
}
}
if exist {
if !dstBranch.IsDeleted {
return ErrBranchAlreadyExists{
BranchName: to,
}
}
// 3. Update protected branch if needed
protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
if err != nil {
return err
}
if _, err := db.GetEngine(ctx).ID(dstBranch.ID).NoAutoCondition().Delete(&dstBranch); err != nil {
return err
}
}
if protectedBranch != nil {
// there is a protect rule for this branch
protectedBranch.RuleName = to
_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to,
}); err != nil {
return err
} else if n <= 0 {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// 2. update default branch if needed
isDefault := repo.DefaultBranch == from
if isDefault {
repo.DefaultBranch = to
_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
if err != nil {
return err
}
}
// 3. Update protected branch if needed
protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
if err != nil {
return err
}
} else {
// some glob protect rules may match this branch
protected, err := IsBranchProtected(ctx, repo.ID, from)
if protectedBranch != nil {
// there is a protect rule for this branch
protectedBranch.RuleName = to
if _, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch); err != nil {
return err
}
} else {
// some glob protect rules may match this branch
protected, err := IsBranchProtected(ctx, repo.ID, from)
if err != nil {
return err
}
if protected {
return ErrBranchIsProtected
}
}
// 4. Update all not merged pull request base branch name
_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]any{"base_branch": to})
if err != nil {
return err
}
if protected {
return ErrBranchIsProtected
// 4.1 Update all not merged pull request head branch name
if _, err = sess.Table("pull_request").Where("head_repo_id=? AND head_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]any{"head_branch": to}); err != nil {
return err
}
}
// 4. Update all not merged pull request base branch name
_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]any{"base_branch": to})
if err != nil {
return err
}
// 5. insert renamed branch record
if err = db.Insert(ctx, &RenamedBranch{
RepoID: repo.ID,
From: from,
To: to,
}); err != nil {
return err
}
// 4.1 Update all not merged pull request head branch name
if _, err = sess.Table("pull_request").Where("head_repo_id=? AND head_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]any{"head_branch": to}); err != nil {
return err
}
// 5. insert renamed branch record
renamedBranch := &RenamedBranch{
RepoID: repo.ID,
From: from,
To: to,
}
err = db.Insert(ctx, renamedBranch)
if err != nil {
return err
}
// 6. do git action
if err = gitAction(ctx, isDefault); err != nil {
return err
}
return committer.Commit()
// 6. do git action
return gitAction(ctx, isDefault)
})
}
type FindRecentlyPushedNewBranchesOptions struct {

View File

@ -180,29 +180,25 @@ func RemoveLFSMetaObjectByOidFn(ctx context.Context, repoID int64, oid string, f
return 0, ErrLFSObjectNotExist
}
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return 0, err
}
defer committer.Close()
return db.WithTx2(ctx, func(ctx context.Context) (int64, error) {
m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repoID}
if _, err := db.DeleteByBean(ctx, m); err != nil {
return -1, err
}
m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repoID}
if _, err := db.DeleteByBean(ctx, m); err != nil {
return -1, err
}
count, err := db.CountByBean(ctx, &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
if err != nil {
return count, err
}
if fn != nil {
if err := fn(count); err != nil {
count, err := db.CountByBean(ctx, &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
if err != nil {
return count, err
}
}
return count, committer.Commit()
if fn != nil {
if err := fn(count); err != nil {
return count, err
}
}
return count, nil
})
}
// GetLFSMetaObjects returns all LFSMetaObjects associated with a repository
@ -243,56 +239,46 @@ func ExistsLFSObject(ctx context.Context, oid string) (bool, error) {
// LFSAutoAssociate auto associates accessible LFSMetaObjects
func LFSAutoAssociate(ctx context.Context, metas []*LFSMetaObject, user *user_model.User, repoID int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
return db.WithTx(ctx, func(ctx context.Context) error {
oids := make([]any, len(metas))
oidMap := make(map[string]*LFSMetaObject, len(metas))
for i, meta := range metas {
oids[i] = meta.Oid
oidMap[meta.Oid] = meta
}
sess := db.GetEngine(ctx)
if !user.IsAdmin {
newMetas := make([]*LFSMetaObject, 0, len(metas))
cond := builder.In(
"`lfs_meta_object`.repository_id",
builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)),
)
if err := db.GetEngine(ctx).Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas); err != nil {
return err
}
if len(newMetas) != len(oidMap) {
return fmt.Errorf("unable collect all LFS objects from database, expected %d, actually %d", len(oidMap), len(newMetas))
}
for i := range newMetas {
newMetas[i].Size = oidMap[newMetas[i].Oid].Size
newMetas[i].RepositoryID = repoID
}
return db.Insert(ctx, newMetas)
}
oids := make([]any, len(metas))
oidMap := make(map[string]*LFSMetaObject, len(metas))
for i, meta := range metas {
oids[i] = meta.Oid
oidMap[meta.Oid] = meta
}
if !user.IsAdmin {
newMetas := make([]*LFSMetaObject, 0, len(metas))
cond := builder.In(
"`lfs_meta_object`.repository_id",
builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)),
)
err = sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas)
if err != nil {
return err
}
if len(newMetas) != len(oidMap) {
return fmt.Errorf("unable collect all LFS objects from database, expected %d, actually %d", len(oidMap), len(newMetas))
}
for i := range newMetas {
newMetas[i].Size = oidMap[newMetas[i].Oid].Size
newMetas[i].RepositoryID = repoID
}
if err = db.Insert(ctx, newMetas); err != nil {
return err
}
} else {
// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
// even if error occurs, it won't hurt users and won't make things worse
for i := range metas {
p := lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size}
_, err = sess.Insert(&LFSMetaObject{
if err := db.Insert(ctx, &LFSMetaObject{
Pointer: p,
RepositoryID: repoID,
})
if err != nil {
}); err != nil {
log.Warn("failed to insert LFS meta object %-v for repo_id: %d into database, err=%v", p, repoID, err)
}
}
}
return committer.Commit()
return nil
})
}
// CopyLFS copies LFS data from one repo to another

View File

@ -17,12 +17,11 @@ 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/glob"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
"github.com/gobwas/glob/syntax"
"xorm.io/builder"
)
@ -77,7 +76,7 @@ func init() {
// IsRuleNameSpecial return true if it contains special character
func IsRuleNameSpecial(ruleName string) bool {
for i := 0; i < len(ruleName); i++ {
if syntax.Special(ruleName[i]) {
if glob.IsSpecialByte(ruleName[i]) {
return true
}
}

View File

@ -8,9 +8,8 @@ import (
"sort"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/optional"
"github.com/gobwas/glob"
)
type ProtectedBranchRules []*ProtectedBranch

View File

@ -11,9 +11,8 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/timeutil"
"github.com/gobwas/glob"
)
// ProtectedTag struct

View File

@ -91,18 +91,10 @@ func GetAssignedIssues(ctx context.Context, opts *AssignedIssuesOptions) ([]*Iss
// ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return false, nil, err
}
defer committer.Close()
removed, comment, err = toggleIssueAssignee(ctx, issue, doer, assigneeID, false)
if err != nil {
return false, nil, err
}
if err := committer.Commit(); err != nil {
if err := db.WithTx(ctx, func(ctx context.Context) error {
removed, comment, err = toggleIssueAssignee(ctx, issue, doer, assigneeID, false)
return err
}); err != nil {
return false, nil, err
}

View File

@ -279,8 +279,8 @@ type Comment struct {
DependentIssue *Issue `xorm:"-"`
CommitID int64
Line int64 // - previous line / + proposed line
TreePath string
Line int64 // - previous line / + proposed line
TreePath string `xorm:"VARCHAR(4000)"` // SQLServer only supports up to 4000
Content string `xorm:"LONGTEXT"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
RenderedContent template.HTML `xorm:"-"`

View File

@ -415,37 +415,28 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
// NewIssue creates new issue with labels for repository.
// The title will be cut off at 255 characters if it's longer than 255 characters.
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate issue index failed: %w", err)
}
issue.Index = idx
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
Attachments: uuids,
}); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
return err
return db.WithTx(ctx, func(ctx context.Context) error {
idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
if err != nil {
return fmt.Errorf("generate issue index failed: %w", err)
}
return fmt.Errorf("newIssue: %w", err)
}
if err = committer.Commit(); err != nil {
return fmt.Errorf("Commit: %w", err)
}
issue.Index = idx
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
return nil
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
Attachments: uuids,
}); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
return err
}
return fmt.Errorf("newIssue: %w", err)
}
return nil
})
}
// UpdateIssueMentions updates issue-user relations for mentioned users.

View File

@ -417,10 +417,6 @@ func (pr *PullRequest) GetGitHeadRefName() string {
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
}
func (pr *PullRequest) GetGitHeadBranchRefName() string {
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
}
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
opts := FindCommentsOptions{
@ -646,9 +642,8 @@ func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
}
// UpdateColsIfNotMerged updates specific fields of a pull request if it has not been merged
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) error {
_, err := db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
return err
func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string) (int64, error) {
return db.GetEngine(ctx).Where("id = ? AND has_merged = ?", pr.ID, false).Cols(cols...).Update(pr)
}
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title

View File

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/models/migrations/v1_23"
"code.gitea.io/gitea/models/migrations/v1_24"
"code.gitea.io/gitea/models/migrations/v1_25"
"code.gitea.io/gitea/models/migrations/v1_26"
"code.gitea.io/gitea/models/migrations/v1_6"
"code.gitea.io/gitea/models/migrations/v1_7"
"code.gitea.io/gitea/models/migrations/v1_8"
@ -393,7 +394,10 @@ func prepareMigrationTasks() []*migration {
// Gitea 1.24.0 ends at database version 321
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
newMigration(322, "Add table issue_dev_link", v1_25.CreateTableIssueDevLink),
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
// Gitea 1.25.0 ends at database version 323
newMigration(323, "Add table issue_dev_link", v1_26.CreateTableIssueDevLink),
}
return preparedMigrations
}

View File

@ -408,7 +408,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
official, err := isOfficialReviewer(sess, review.IssueID, reviewer)
if err != nil {
// Branch might not be proteced or other error, ignore it.
// Branch might not be protected or other error, ignore it.
continue
}
review.Official = official

View File

@ -12,6 +12,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -83,17 +84,17 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
if !pr.HasMerged {
var err error
pr.MergeBase, _, err = git.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &git.RunOpts{Dir: repoPath})
pr.MergeBase, _, err = gitcmd.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
var err2 error
pr.MergeBase, _, err2 = git.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &git.RunOpts{Dir: repoPath})
pr.MergeBase, _, err2 = gitcmd.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err2 != nil {
log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2)
continue
}
}
} else {
parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &git.RunOpts{Dir: repoPath})
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue
@ -105,9 +106,9 @@ func FixMergeBase(ctx context.Context, x *xorm.Engine) error {
refs := append([]string{}, parents[1:]...)
refs = append(refs, gitRefName)
cmd := git.NewCommand("merge-base").AddDashesAndList(refs...)
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath})
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue

View File

@ -11,7 +11,7 @@ import (
"strings"
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -80,7 +80,7 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &git.RunOpts{Dir: repoPath})
parentsString, _, err := gitcmd.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue
@ -93,9 +93,9 @@ func RefixMergeBase(ctx context.Context, x *xorm.Engine) error {
// we should recalculate
refs := append([]string{}, parents[1:]...)
refs = append(refs, gitRefName)
cmd := git.NewCommand("merge-base").AddDashesAndList(refs...)
cmd := gitcmd.NewCommand("merge-base").AddDashesAndList(refs...)
pr.MergeBase, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath})
pr.MergeBase, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err)
continue

View File

@ -6,11 +6,10 @@ package v1_12
import (
"fmt"
"math"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/modules/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -85,12 +84,9 @@ func AddCommitDivergenceToPulls(x *xorm.Engine) error {
log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
continue
}
userPath := filepath.Join(setting.RepoRootPath, strings.ToLower(baseRepo.OwnerName))
repoPath := filepath.Join(userPath, strings.ToLower(baseRepo.Name)+".git")
repoStore := repo_model.StorageRepo(repo_model.RelativePath(baseRepo.OwnerName, baseRepo.Name))
gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index)
divergence, err := git.GetDivergingCommits(graceful.GetManager().HammerContext(), repoPath, pr.BaseBranch, gitRefName)
divergence, err := gitrepo.GetDivergingCommits(graceful.GetManager().HammerContext(), repoStore, pr.BaseBranch, gitRefName)
if err != nil {
log.Warn("Could not recalculate Divergence for pull: %d", pr.ID)
pr.CommitsAhead = 0

View File

@ -4,19 +4,25 @@
package v1_25
import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/models/migrations/base"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
func CreateTableIssueDevLink(x *xorm.Engine) error {
type IssueDevLink struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
LinkType int
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
LinkID int64 // branch id in branch table or pull request id
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
func ExtendCommentTreePathLength(x *xorm.Engine) error {
dbType := x.Dialect().URI().DBType
if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT
return nil
}
return x.Sync(new(IssueDevLink))
return base.ModifyColumn(x, "comment", &schemas.Column{
Name: "tree_path",
SQLType: schemas.SQLType{
Name: "VARCHAR",
},
Length: 4000,
Nullable: true, // To keep compatible as nullable
DefaultIsEmpty: true,
})
}

View File

@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_26
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
)
func TestMain(m *testing.M) {
base.MainTest(m)
}

View File

@ -0,0 +1,22 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_26
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func CreateTableIssueDevLink(x *xorm.Engine) error {
type IssueDevLink struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
LinkType int
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
LinkID int64 // branch id in branch table or pull request id
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
return x.Sync(new(IssueDevLink))
}

View File

@ -0,0 +1,20 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_26
import (
"testing"
"code.gitea.io/gitea/models/migrations/base"
"github.com/stretchr/testify/assert"
)
func Test_CreateTableIssueDevLink(t *testing.T) {
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0)
defer deferable()
assert.NoError(t, CreateTableIssueDevLink(x))
}

View File

@ -348,10 +348,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
for _, u := range repo.Units {
for _, team := range teams {
unitAccessMode := minAccessMode
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode)
}
teamMode, _ := team.UnitAccessModeEx(ctx, u.Type)
unitAccessMode := max(perm.unitsMode[u.Type], minAccessMode, teamMode)
perm.unitsMode[u.Type] = unitAccessMode
}
}

View File

@ -197,4 +197,37 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
})
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // org private repo, same org as repo 32
require.NoError(t, repo3.LoadOwner(ctx))
require.True(t, repo3.Owner.IsOrganization())
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeIssues])
})
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
})
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
})
}

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

@ -442,58 +442,53 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail
// ActivateUserEmail will change the activated state of an email address,
// either primary or secondary (all in the email_address table)
func ActivateUserEmail(ctx context.Context, userID int64, email string, activate bool) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
// Activate/deactivate a user's secondary email address
// First check if there's another user active with the same address
addr, exist, err := db.Get[EmailAddress](ctx, builder.Eq{"uid": userID, "lower_email": strings.ToLower(email)})
if err != nil {
return err
} else if !exist {
return fmt.Errorf("no such email: %d (%s)", userID, email)
}
if addr.IsActivated == activate {
// Already in the desired state; no action
return nil
}
if activate {
if used, err := IsEmailActive(ctx, email, addr.ID); err != nil {
return fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err)
} else if used {
return ErrEmailAlreadyUsed{Email: email}
}
}
if err = updateActivation(ctx, addr, activate); err != nil {
return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err)
}
// Activate/deactivate a user's primary email address and account
if addr.IsPrimary {
user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
return db.WithTx(ctx, func(ctx context.Context) error {
// Activate/deactivate a user's secondary email address
// First check if there's another user active with the same address
addr, exist, err := db.Get[EmailAddress](ctx, builder.Eq{"uid": userID, "lower_email": strings.ToLower(email)})
if err != nil {
return err
} else if !exist || !strings.EqualFold(user.Email, email) {
return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
} else if !exist {
return fmt.Errorf("no such email: %d (%s)", userID, email)
}
// The user's activation state should be synchronized with the primary email
if user.IsActive != activate {
user.IsActive = activate
if user.Rands, err = GetUserSalt(); err != nil {
return fmt.Errorf("unable to generate salt: %w", err)
}
if err = UpdateUserCols(ctx, user, "is_active", "rands"); err != nil {
return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err)
if addr.IsActivated == activate {
// Already in the desired state; no action
return nil
}
if activate {
if used, err := IsEmailActive(ctx, email, addr.ID); err != nil {
return fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err)
} else if used {
return ErrEmailAlreadyUsed{Email: email}
}
}
}
if err = updateActivation(ctx, addr, activate); err != nil {
return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err)
}
return committer.Commit()
// Activate/deactivate a user's primary email address and account
if addr.IsPrimary {
user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
if err != nil {
return err
} else if !exist || !strings.EqualFold(user.Email, email) {
return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
}
// The user's activation state should be synchronized with the primary email
if user.IsActive != activate {
user.IsActive = activate
if user.Rands, err = GetUserSalt(); err != nil {
return fmt.Errorf("unable to generate salt: %w", err)
}
if err = UpdateUserCols(ctx, user, "is_active", "rands"); err != nil {
return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err)
}
}
}
return nil
})
}
// validateEmailBasic checks whether the email complies with the rules

View File

@ -8,7 +8,7 @@ const (
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
// SettingsKeyShowOutdatedComments is the setting key wether or not to show outdated comments in PRs
// SettingsKeyShowOutdatedComments is the setting key whether or not to show outdated comments in PRs
SettingsKeyShowOutdatedComments = "comment_code.show_outdated"
// UserActivityPubPrivPem is user's private key

View File

@ -716,90 +716,82 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o
}
}
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
isExist, err := IsUserExist(ctx, 0, u.Name)
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist{u.Name}
}
isExist, err = IsEmailUsed(ctx, u.Email)
if err != nil {
return err
} else if isExist {
return ErrEmailAlreadyUsed{
Email: u.Email,
return db.WithTx(ctx, func(ctx context.Context) error {
isExist, err := IsUserExist(ctx, 0, u.Name)
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist{u.Name}
}
}
// prepare for database
isExist, err = IsEmailUsed(ctx, u.Email)
if err != nil {
return err
} else if isExist {
return ErrEmailAlreadyUsed{
Email: u.Email,
}
}
u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email
if u.Rands, err = GetUserSalt(); err != nil {
return err
}
if u.Passwd != "" {
if err = u.SetPassword(u.Passwd); err != nil {
// prepare for database
u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email
if u.Rands, err = GetUserSalt(); err != nil {
return err
}
} else {
u.Salt = ""
u.PasswdHashAlgo = ""
}
if u.Passwd != "" {
if err = u.SetPassword(u.Passwd); err != nil {
return err
}
} else {
u.Salt = ""
u.PasswdHashAlgo = ""
}
// save changes to database
// save changes to database
if err = DeleteUserRedirect(ctx, u.Name); err != nil {
return err
}
if u.CreatedUnix == 0 {
// Caller expects auto-time for creation & update timestamps.
err = db.Insert(ctx, u)
} else {
// Caller sets the timestamps themselves. They are responsible for ensuring
// both `CreatedUnix` and `UpdatedUnix` are set appropriately.
_, err = db.GetEngine(ctx).NoAutoTime().Insert(u)
}
if err != nil {
return err
}
if setting.RecordUserSignupMetadata {
// insert initial IP and UserAgent
if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil {
if err = DeleteUserRedirect(ctx, u.Name); err != nil {
return err
}
// trim user agent string to a reasonable length, if necessary
userAgent := strings.TrimSpace(meta.InitialUserAgent)
if len(userAgent) > 255 {
userAgent = userAgent[:255]
if u.CreatedUnix == 0 {
// Caller expects auto-time for creation & update timestamps.
err = db.Insert(ctx, u)
} else {
// Caller sets the timestamps themselves. They are responsible for ensuring
// both `CreatedUnix` and `UpdatedUnix` are set appropriately.
_, err = db.GetEngine(ctx).NoAutoTime().Insert(u)
}
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
if err != nil {
return err
}
}
// insert email address
if err := db.Insert(ctx, &EmailAddress{
UID: u.ID,
Email: u.Email,
LowerEmail: strings.ToLower(u.Email),
IsActivated: u.IsActive,
IsPrimary: true,
}); err != nil {
return err
}
if setting.RecordUserSignupMetadata {
// insert initial IP and UserAgent
if err = SetUserSetting(ctx, u.ID, SignupIP, meta.InitialIP); err != nil {
return err
}
return committer.Commit()
// trim user agent string to a reasonable length, if necessary
userAgent := strings.TrimSpace(meta.InitialUserAgent)
if len(userAgent) > 255 {
userAgent = userAgent[:255]
}
if err = SetUserSetting(ctx, u.ID, SignupUserAgent, userAgent); err != nil {
return err
}
}
// insert email address
return db.Insert(ctx, &EmailAddress{
UID: u.ID,
Email: u.Email,
LowerEmail: strings.ToLower(u.Email),
IsActivated: u.IsActive,
IsPrimary: true,
})
})
}
// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error.

View File

@ -10,11 +10,11 @@ import (
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/gobwas/glob"
"github.com/nektos/act/pkg/jobparser"
"github.com/nektos/act/pkg/model"
"github.com/nektos/act/pkg/workflowpattern"
@ -377,20 +377,28 @@ func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool
// Actions with the same name:
// opened, edited, closed, reopened, assigned, unassigned, milestoned, demilestoned
// Actions need to be converted:
// label_updated -> labeled
// label_updated -> labeled (when adding) or unlabeled (when removing)
// label_cleared -> unlabeled
// Unsupported activity types:
// deleted, transferred, pinned, unpinned, locked, unlocked
action := issuePayload.Action
switch action {
actions := []string{}
switch issuePayload.Action {
case api.HookIssueLabelUpdated:
action = "labeled"
if len(issuePayload.Changes.AddedLabels) > 0 {
actions = append(actions, "labeled")
}
if len(issuePayload.Changes.RemovedLabels) > 0 {
actions = append(actions, "unlabeled")
}
case api.HookIssueLabelCleared:
action = "unlabeled"
actions = append(actions, "unlabeled")
default:
actions = append(actions, string(issuePayload.Action))
}
for _, val := range vals {
if glob.MustCompile(val, '/').Match(string(action)) {
if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) {
matchTimes++
break
}

View File

@ -154,3 +154,184 @@ func TestDetectMatched(t *testing.T) {
})
}
}
func TestMatchIssuesEvent(t *testing.T) {
testCases := []struct {
desc string
payload *api.IssuePayload
yamlOn string
expected bool
eventType string
}{
{
desc: "Label deletion should trigger unlabeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{},
},
Changes: &api.ChangesPayload{
RemovedLabels: []*api.Label{
{ID: 123, Name: "deleted-label"},
},
},
},
yamlOn: "on:\n issues:\n types: [unlabeled]",
expected: true,
eventType: "unlabeled",
},
{
desc: "Label deletion with existing labels should trigger unlabeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{
{ID: 456, Name: "existing-label"},
},
},
Changes: &api.ChangesPayload{
AddedLabels: nil,
RemovedLabels: []*api.Label{
{ID: 123, Name: "deleted-label"},
},
},
},
yamlOn: "on:\n issues:\n types: [unlabeled]",
expected: true,
eventType: "unlabeled",
},
{
desc: "Label addition should trigger labeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{
{ID: 123, Name: "new-label"},
},
},
Changes: &api.ChangesPayload{
AddedLabels: []*api.Label{
{ID: 123, Name: "new-label"},
},
RemovedLabels: []*api.Label{}, // Empty array, no labels removed
},
},
yamlOn: "on:\n issues:\n types: [labeled]",
expected: true,
eventType: "labeled",
},
{
desc: "Label clear should trigger unlabeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelCleared,
Issue: &api.Issue{
Labels: []*api.Label{},
},
},
yamlOn: "on:\n issues:\n types: [unlabeled]",
expected: true,
eventType: "unlabeled",
},
{
desc: "Both adding and removing labels should trigger labeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{
{ID: 789, Name: "new-label"},
},
},
Changes: &api.ChangesPayload{
AddedLabels: []*api.Label{
{ID: 789, Name: "new-label"},
},
RemovedLabels: []*api.Label{
{ID: 123, Name: "deleted-label"},
},
},
},
yamlOn: "on:\n issues:\n types: [labeled]",
expected: true,
eventType: "labeled",
},
{
desc: "Both adding and removing labels should trigger unlabeled event",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{
{ID: 789, Name: "new-label"},
},
},
Changes: &api.ChangesPayload{
AddedLabels: []*api.Label{
{ID: 789, Name: "new-label"},
},
RemovedLabels: []*api.Label{
{ID: 123, Name: "deleted-label"},
},
},
},
yamlOn: "on:\n issues:\n types: [unlabeled]",
expected: true,
eventType: "unlabeled",
},
{
desc: "Both adding and removing labels should trigger both events",
payload: &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Issue: &api.Issue{
Labels: []*api.Label{
{ID: 789, Name: "new-label"},
},
},
Changes: &api.ChangesPayload{
AddedLabels: []*api.Label{
{ID: 789, Name: "new-label"},
},
RemovedLabels: []*api.Label{
{ID: 123, Name: "deleted-label"},
},
},
},
yamlOn: "on:\n issues:\n types: [labeled, unlabeled]",
expected: true,
eventType: "multiple",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
assert.NoError(t, err)
assert.Len(t, evts, 1)
// Test if the event matches as expected
assert.Equal(t, tc.expected, matchIssuesEvent(tc.payload, evts[0]))
// For extra validation, check that action mapping works correctly
if tc.eventType == "multiple" {
// Skip direct action mapping validation for multiple events case
// as one action can map to multiple event types
return
}
// Determine expected action for single event case
var expectedAction string
switch tc.payload.Action {
case api.HookIssueLabelUpdated:
if tc.eventType == "labeled" {
expectedAction = "labeled"
} else if tc.eventType == "unlabeled" {
expectedAction = "unlabeled"
}
case api.HookIssueLabelCleared:
expectedAction = "unlabeled"
default:
expectedAction = string(tc.payload.Action)
}
assert.Equal(t, expectedAction, tc.eventType, "Event type should match expected")
})
}
}

View File

@ -365,11 +365,11 @@ func GenerateEmbedBindata(fsRootPath, outputFile string) error {
if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
return err
}
jsonBuf, err := json.Marshal(meta) // can't use json.NewEncoder here because it writes extra EOL
jsonBuf, err := json.Marshal(meta)
if err != nil {
return err
}
_, _ = output.Write([]byte{'\n'})
_, err = output.Write(jsonBuf)
_, err = output.Write(bytes.TrimSpace(jsonBuf))
return err
}

View File

@ -70,7 +70,7 @@ func (i *Identicon) render(c, b1, b2, b1Angle, b2Angle, foreColor int) image.Ima
/*
# Algorithm
Origin: An image is splitted into 9 areas
Origin: An image is split into 9 areas
```
-------------

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

@ -12,6 +12,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
@ -23,7 +24,7 @@ type BatchChecker struct {
stdOut *nulSeparatedAttributeWriter
ctx context.Context
cancel context.CancelFunc
cmd *git.Command
cmd *gitcmd.Command
}
// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
@ -76,7 +77,7 @@ func NewBatchChecker(repo *git.Repository, treeish string, attributes []string)
_ = lw.Close()
}()
stdErr := new(bytes.Buffer)
err := cmd.Run(ctx, &git.RunOpts{
err := cmd.Run(ctx, &gitcmd.RunOpts{
Env: envs,
Dir: repo.Path,
Stdin: stdinReader,

View File

@ -11,12 +11,13 @@ import (
"os"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) {
func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*gitcmd.Command, []string, func(), error) {
cancel := func() {}
envs := []string{"GIT_FLUSH=1"}
cmd := git.NewCommand("check-attr", "-z")
cmd := gitcmd.NewCommand("check-attr", "-z")
if len(attributes) == 0 {
cmd.AddArguments("--all")
}
@ -70,7 +71,7 @@ func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish strin
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)
if err := cmd.Run(ctx, &git.RunOpts{
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Env: append(os.Environ(), envs...),
Dir: gitRepo.Path,
Stdout: stdOut,

View File

@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"github.com/djherbis/buffer"
@ -29,13 +30,13 @@ type WriteCloserError interface {
// This is needed otherwise the git cat-file will hang for invalid repositories.
func ensureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{}
err := NewCommand("rev-parse").
Run(ctx, &RunOpts{
err := gitcmd.NewCommand("rev-parse").
Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stderr: &stderr,
})
if err != nil {
return ConcatenateError(err, (&stderr).String())
return gitcmd.ConcatenateError(err, (&stderr).String())
}
return nil
}
@ -61,8 +62,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
go func() {
stderr := strings.Builder{}
err := NewCommand("cat-file", "--batch-check").
Run(ctx, &RunOpts{
err := gitcmd.NewCommand("cat-file", "--batch-check").
Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
@ -71,8 +72,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
UseContextTimeout: true,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else {
_ = batchStdoutWriter.Close()
_ = batchStdinReader.Close()
@ -109,8 +110,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
go func() {
stderr := strings.Builder{}
err := NewCommand("cat-file", "--batch").
Run(ctx, &RunOpts{
err := gitcmd.NewCommand("cat-file", "--batch").
Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
@ -119,8 +120,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
UseContextTimeout: true,
})
if err != nil {
_ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = batchStdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
_ = batchStdinReader.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else {
_ = batchStdoutWriter.Close()
_ = batchStdinReader.Close()

View File

@ -10,6 +10,7 @@ import (
"io"
"os"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@ -141,7 +142,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
}
}()
cmd := NewCommand("blame", "--porcelain")
cmd := gitcmd.NewCommand("blame", "--porcelain")
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
@ -165,7 +166,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
go func() {
stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
err := cmd.Run(ctx, &RunOpts{
err := cmd.Run(ctx, &gitcmd.RunOpts{
UseContextTimeout: true,
Dir: repoPath,
Stdout: stdout,

View File

@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
@ -87,12 +88,12 @@ func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
// AddChanges marks local changes to be ready for commit.
func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error {
cmd := NewCommand().AddArguments("add")
cmd := gitcmd.NewCommand().AddArguments("add")
if all {
cmd.AddArguments("--all")
}
cmd.AddDashesAndList(files...)
_, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err
}
@ -106,7 +107,7 @@ type CommitChangesOptions struct {
// CommitChanges commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer.
func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error {
cmd := NewCommand()
cmd := gitcmd.NewCommand()
if opts.Committer != nil {
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
@ -121,7 +122,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
}
cmd.AddOptionFormat("--message=%s", opts.Message)
_, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
_, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
// No stderr but exit status 1 means nothing to commit.
if err != nil && err.Error() == "exit status 1" {
return nil
@ -131,7 +132,7 @@ func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptio
// AllCommitsCount returns count of all commits in repository
func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
cmd := NewCommand("rev-list")
cmd := gitcmd.NewCommand("rev-list")
if hidePRRefs {
cmd.AddArguments("--exclude=" + PullPrefix + "*")
}
@ -140,7 +141,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
cmd.AddDashesAndList(files...)
}
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return 0, err
}
@ -160,7 +161,7 @@ type CommitsCountOptions struct {
// CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
cmd := NewCommand("rev-list", "--count")
cmd := gitcmd.NewCommand("rev-list", "--count")
cmd.AddDynamicArguments(opts.Revision...)
@ -172,7 +173,7 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error)
cmd.AddDashesAndList(opts.RelPath...)
}
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: opts.RepoPath})
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: opts.RepoPath})
if err != nil {
return 0, err
}
@ -207,7 +208,7 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
return false, nil
}
_, _, err := NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path})
_, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
if err == nil {
return true, nil
}
@ -348,12 +349,12 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) {
cmd := NewCommand("name-rev")
cmd := gitcmd.NewCommand("name-rev")
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*")
}
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
data, _, err := cmd.RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path})
data, _, err := cmd.RunStdString(c.repo.Ctx, &gitcmd.RunOpts{Dir: c.repo.Path})
if err != nil {
// handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") {
@ -431,14 +432,14 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
}()
stderr := new(bytes.Buffer)
err := NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &RunOpts{
err := gitcmd.NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: stderr,
})
w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, ConcatenateError(err, stderr.String())
return nil, gitcmd.ConcatenateError(err, stderr.String())
}
<-done
@ -447,7 +448,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &RunOpts{Dir: repoPath})
commitID, _, err := gitcmd.NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
if strings.Contains(err.Error(), "exit status 128") {
return "", ErrNotExist{shortID, ""}

View File

@ -11,13 +11,14 @@ import (
"runtime"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting"
)
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
func syncGitConfig(ctx context.Context) (err error) {
if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
if err = os.MkdirAll(gitcmd.HomeDir(), os.ModePerm); err != nil {
return fmt.Errorf("unable to prepare git home directory %s, err: %w", gitcmd.HomeDir(), err)
}
// first, write user's git config options to git config file
@ -117,8 +118,8 @@ func syncGitConfig(ctx context.Context) (err error) {
}
func configSet(ctx context.Context, key, value string) error {
stdout, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err != nil && !IsErrorExitCode(err, 1) {
stdout, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err != nil && !gitcmd.IsErrorExitCode(err, 1) {
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
@ -127,7 +128,7 @@ func configSet(ctx context.Context, key, value string) error {
return nil
}
_, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
_, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
@ -136,14 +137,14 @@ func configSet(ctx context.Context, key, value string) error {
}
func configSetNonExist(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err == nil {
// already exist
return nil
}
if IsErrorExitCode(err, 1) {
if gitcmd.IsErrorExitCode(err, 1) {
// not exist, set new config
_, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
_, _, err = gitcmd.NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
@ -154,14 +155,14 @@ func configSetNonExist(ctx context.Context, key, value string) error {
}
func configAddNonExist(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
if err == nil {
// already exist
return nil
}
if IsErrorExitCode(err, 1) {
if gitcmd.IsErrorExitCode(err, 1) {
// not exist, add new config
_, _, err = NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx, nil)
_, _, err = gitcmd.NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(ctx, nil)
if err != nil {
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
}
@ -171,16 +172,16 @@ func configAddNonExist(ctx context.Context, key, value string) error {
}
func configUnsetAll(ctx context.Context, key, value string) error {
_, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
_, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx, nil)
if err == nil {
// exist, need to remove
_, _, err = NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
_, _, err = gitcmd.NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(ctx, nil)
if err != nil {
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
}
return nil
}
if IsErrorExitCode(err, 1) {
if gitcmd.IsErrorExitCode(err, 1) {
// not exist
return nil
}

View File

@ -8,13 +8,14 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func gitConfigContains(sub string) bool {
if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
if b, err := os.ReadFile(gitcmd.HomeDir() + "/.gitconfig"); err == nil {
return strings.Contains(string(b), sub)
}
return false

View File

@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
@ -34,8 +35,8 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer
// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error {
stderr := new(bytes.Buffer)
cmd := NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
if err := cmd.Run(ctx, &RunOpts{
cmd := gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stdout: writer,
Stderr: stderr,
@ -56,7 +57,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
files = append(files, file)
}
cmd := NewCommand()
cmd := gitcmd.NewCommand()
switch diffType {
case RawDiffNormal:
if len(startCommit) != 0 {
@ -89,7 +90,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
}
stderr := new(bytes.Buffer)
if err = cmd.Run(repo.Ctx, &RunOpts{
if err = cmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: writer,
Stderr: stderr,
@ -312,8 +313,8 @@ func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID str
affectedFiles := make([]string, 0, 32)
// Run `git diff --name-only` to get the names of the changed files
err = NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
Run(repo.Ctx, &RunOpts{
err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
Run(repo.Ctx, &gitcmd.RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdoutWriter,

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,12 +9,12 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -33,10 +33,7 @@ type Features struct {
SupportCheckAttrOnBare bool // >= 2.40
}
var (
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
defaultFeatures *Features
)
var defaultFeatures *Features
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
@ -60,7 +57,7 @@ func DefaultFeatures() *Features {
}
func loadGitVersionFeatures() (*Features, error) {
stdout, _, runErr := NewCommand("version").RunStdString(context.Background(), nil)
stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background(), nil)
if runErr != nil {
return nil, runErr
}
@ -129,32 +126,6 @@ func ensureGitVersion() error {
return nil
}
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath(path string) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
return fmt.Errorf("git not found: %w", err)
}
GitExecutable = absPath
return nil
}
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir() string {
if setting.Git.HomePath == "" {
// strict check, make sure the git module is initialized correctly.
// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
return ""
}
return setting.Git.HomePath
}
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
func InitSimple() error {
@ -167,10 +138,10 @@ func InitSimple() error {
}
if setting.Git.Timeout.Default > 0 {
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
gitcmd.SetDefaultCommandExecutionTimeout(time.Duration(setting.Git.Timeout.Default) * time.Second)
}
if err := SetExecutablePath(setting.Git.Path); err != nil {
if err := gitcmd.SetExecutablePath(setting.Git.Path); err != nil {
return err
}
@ -185,7 +156,7 @@ func InitSimple() error {
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
_ = os.Setenv("GNUPGHOME", filepath.Join(gitcmd.HomeDir(), ".gnupg"))
}
return nil
}

View File

@ -2,7 +2,7 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
package gitcmd
import (
"bytes"
@ -32,6 +32,10 @@ type TrustedCmdArgs []internal.CmdArg
// defaultCommandExecutionTimeout default command execution timeout duration
var defaultCommandExecutionTimeout = 360 * time.Second
func SetDefaultCommandExecutionTimeout(timeout time.Duration) {
defaultCommandExecutionTimeout = timeout
}
// DefaultLocale is the default LC_ALL to run git commands in.
const DefaultLocale = "C"

View File

@ -3,7 +3,7 @@
//go:build race
package git
package gitcmd
import (
"context"

View File

@ -1,14 +1,30 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
package gitcmd
import (
"fmt"
"os"
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/tempdir"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "unable to create temp dir: %v", err)
os.Exit(1)
}
defer cleanup()
setting.Git.HomePath = gitHomePath
}
func TestRunWithContextStd(t *testing.T) {
cmd := NewCommand("--version")
stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{})

40
modules/git/gitcmd/env.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import (
"fmt"
"os/exec"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
var GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath(path string) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath, err := exec.LookPath(GitExecutable)
if err != nil {
return fmt.Errorf("git not found: %w", err)
}
GitExecutable = absPath
return nil
}
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir() string {
if setting.Git.HomePath == "" {
// strict check, make sure the git module is initialized correctly.
// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
// for example: if there is gitea git hook code calling NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
return ""
}
return setting.Git.HomePath
}

View File

@ -0,0 +1,14 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitcmd
import "fmt"
// ConcatenateError concatenats an error with stderr string
func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 {
return err
}
return fmt.Errorf("%w - %s", err, stderr)
}

View File

@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util"
)
@ -60,7 +61,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
2^@repo: go-gitea/gitea
*/
var results []*GrepResult
cmd := NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name")
cmd := gitcmd.NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name")
cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber))
switch opts.GrepMode {
case GrepModeExact:
@ -83,7 +84,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
cmd.AddDashesAndList(opts.PathspecList...)
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
stderr := bytes.Buffer{}
err = cmd.Run(ctx, &RunOpts{
err = cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
@ -135,11 +136,11 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
},
})
// git grep exits by cancel (killed), usually it is caused by the limit of results
if IsErrorExitCode(err, -1) && stderr.Len() == 0 {
if gitcmd.IsErrorExitCode(err, -1) && stderr.Len() == 0 {
return results, nil
}
// git grep exits with 1 if no results are found
if IsErrorExitCode(err, 1) && stderr.Len() == 0 {
if gitcmd.IsErrorExitCode(err, 1) && stderr.Len() == 0 {
return nil, nil
}
if err != nil && !errors.Is(err, context.Canceled) {

View File

@ -3,13 +3,24 @@
package git
import "code.gitea.io/gitea/modules/setting"
// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat
const (
SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli
SigningKeyFormatSSH = "ssh"
)
// SigningKey represents an instance key info which will be used to sign git commits.
// FIXME: need to refactor it to a new name, this name conflicts with the variable names for "asymkey.GPGKey" in many places.
type SigningKey struct {
KeyID string
Format string
}
func (s *SigningKey) String() string {
// Do not expose KeyID
// In case the key is a file path and the struct is rendered in a template, then the server path will be exposed.
setting.PanicInDevOrTesting("don't call SigningKey.String() - it exposes the KeyID which might be a local file path")
return "SigningKey:" + s.Format
}

View File

@ -14,6 +14,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/djherbis/buffer"
"github.com/djherbis/nio/v3"
@ -34,7 +35,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
_ = stdoutWriter.Close()
}
cmd := NewCommand()
cmd := gitcmd.NewCommand()
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
var files []string
@ -64,13 +65,13 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
go func() {
stderr := strings.Builder{}
err := cmd.Run(ctx, &RunOpts{
err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repository,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
return
}

View File

@ -13,7 +13,7 @@ import (
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
@ -25,8 +25,8 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand("cat-file", "--batch-check")
if err := cmd.Run(ctx, &git.RunOpts{
cmd := gitcmd.NewCommand("cat-file", "--batch-check")
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath,
Stdin: shasToCheckReader,
Stdout: catFileCheckWriter,
@ -43,8 +43,8 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand("cat-file", "--batch-check", "--batch-all-objects")
if err := cmd.Run(ctx, &git.RunOpts{
cmd := gitcmd.NewCommand("cat-file", "--batch-check", "--batch-all-objects")
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath,
Stdout: catFileCheckWriter,
Stderr: stderr,
@ -64,7 +64,7 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile
stderr := new(bytes.Buffer)
var errbuf strings.Builder
if err := git.NewCommand("cat-file", "--batch").Run(ctx, &git.RunOpts{
if err := gitcmd.NewCommand("cat-file", "--batch").Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath,
Stdout: catFileBatchWriter,
Stdin: shasToBatchReader,

View File

@ -14,6 +14,7 @@ import (
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// FindLFSFile finds commits that contain a provided pointer file hash
@ -32,13 +33,13 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
go func() {
stderr := strings.Builder{}
err := git.NewCommand("rev-list", "--all").Run(repo.Ctx, &git.RunOpts{
err := gitcmd.NewCommand("rev-list", "--all").Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: revListWriter,
Stderr: &stderr,
})
if err != nil {
_ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String()))
_ = revListWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else {
_ = revListWriter.Close()
}

View File

@ -11,7 +11,7 @@ import (
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// NameRevStdin runs name-rev --stdin
@ -22,7 +22,7 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS
stderr := new(bytes.Buffer)
var errbuf strings.Builder
if err := git.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &git.RunOpts{
if err := gitcmd.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath,
Stdout: nameRevStdinWriter,
Stdin: shasToNameReader,

View File

@ -12,7 +12,7 @@ import (
"strings"
"sync"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
@ -23,8 +23,8 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand("rev-list", "--objects", "--all")
if err := cmd.Run(ctx, &git.RunOpts{
cmd := gitcmd.NewCommand("rev-list", "--objects", "--all")
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: basePath,
Stdout: revListWriter,
Stderr: stderr,
@ -42,11 +42,11 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
defer revListWriter.Close()
stderr := new(bytes.Buffer)
var errbuf strings.Builder
cmd := git.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA)
cmd := gitcmd.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA)
if baseSHA != "" {
cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA)
}
if err := cmd.Run(ctx, &git.RunOpts{
if err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: tmpBasePath,
Stdout: revListWriter,
Stderr: stderr,

View File

@ -9,20 +9,20 @@ import (
"net/url"
"strings"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/util"
)
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
var cmd *Command
var cmd *gitcmd.Command
if DefaultFeatures().CheckVersionAtLeast("2.7") {
cmd = NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
cmd = gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
} else {
cmd = NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
cmd = gitcmd.NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
}
result, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
result, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
@ -33,15 +33,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

@ -17,6 +17,7 @@ import (
"strings"
"time"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/setting"
)
@ -38,6 +39,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 := gitcmd.NewCommand("log").AddArguments(prettyLogFormat).
AddDynamicArguments(revisionRange).AddArguments("--").
RunStdBytes(ctx, &gitcmd.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 {
@ -59,7 +71,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
// IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(ctx context.Context, url string) bool {
_, _, err := NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil)
_, _, err := gitcmd.NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil)
return err == nil
}
@ -70,7 +82,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
return err
}
cmd := NewCommand("init")
cmd := gitcmd.NewCommand("init")
if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName)
@ -82,15 +94,15 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
if bare {
cmd.AddArguments("--bare")
}
_, _, err = cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
_, _, err = cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
return err
}
// IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder
if err := NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
Run(repo.Ctx, &RunOpts{
if err := gitcmd.NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: &output,
Stderr: &errbuf,
@ -126,7 +138,7 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
return err
}
cmd := NewCommand().AddArguments("clone")
cmd := gitcmd.NewCommand().AddArguments("clone")
if opts.SkipTLSVerify {
cmd.AddArguments("-c", "http.sslVerify=false")
}
@ -167,13 +179,13 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
}
stderr := new(bytes.Buffer)
if err = cmd.Run(ctx, &RunOpts{
if err = cmd.Run(ctx, &gitcmd.RunOpts{
Timeout: opts.Timeout,
Env: envs,
Stdout: io.Discard,
Stderr: stderr,
}); err != nil {
return ConcatenateError(err, stderr.String())
return gitcmd.ConcatenateError(err, stderr.String())
}
return nil
}
@ -190,7 +202,7 @@ type PushOptions struct {
// Push pushs local commits to given remote branch.
func Push(ctx context.Context, repoPath string, opts PushOptions) error {
cmd := NewCommand("push")
cmd := gitcmd.NewCommand("push")
if opts.Force {
cmd.AddArguments("-f")
}
@ -203,7 +215,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
}
cmd.AddDashesAndList(remoteBranchArgs...)
stdout, stderr, err := cmd.RunStdString(ctx, &RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
stdout, stderr, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
if err != nil {
if strings.Contains(stderr, "non-fast-forward") {
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
@ -222,8 +234,8 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
// GetLatestCommitTime returns time for latest commit in repository (across all branches)
func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) {
cmd := NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
cmd := gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
stdout, _, err := cmd.RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath})
if err != nil {
return time.Time{}, err
}
@ -231,36 +243,6 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error
return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime)
}
// DivergeObject represents commit count diverging commits
type DivergeObject struct {
Ahead int
Behind int
}
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
cmd := NewCommand("rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return do, err
}
left, right, found := strings.Cut(strings.Trim(stdout, "\n"), "\t")
if !found {
return do, fmt.Errorf("git rev-list output is missing a tab: %q", stdout)
}
do.Behind, err = strconv.Atoi(left)
if err != nil {
return do, err
}
do.Ahead, err = strconv.Atoi(right)
if err != nil {
return do, err
}
return do, nil
}
// CreateBundle create bundle content to the target path
func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error {
tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle")
@ -270,23 +252,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.
defer cleanup()
env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects"))
_, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
_, _, err = gitcmd.NewCommand("init", "--bare").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
_, _, err = NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
_, _, err = gitcmd.NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
_, _, err = NewCommand("branch", "-m", "bundle").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
_, _, err = gitcmd.NewCommand("branch", "-m", "bundle").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
tmpFile := filepath.Join(tmp, "bundle")
_, _, err = NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
_, _, err = gitcmd.NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &gitcmd.RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}

View File

@ -10,6 +10,8 @@ import (
"io"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// ArchiveType archive types
@ -53,7 +55,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
return fmt.Errorf("unknown format: %v", format)
}
cmd := NewCommand("archive")
cmd := gitcmd.NewCommand("archive")
if usePrefix {
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/")
}
@ -61,13 +63,13 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
cmd.AddDynamicArguments(commitID)
var stderr strings.Builder
err := cmd.Run(ctx, &RunOpts{
err := cmd.Run(ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: target,
Stderr: &stderr,
})
if err != nil {
return ConcatenateError(err, stderr.String())
return gitcmd.ConcatenateError(err, stderr.String())
}
return nil
}

View File

@ -1,23 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"fmt"
)
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, _, err := NewCommand("blame").
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
AddDashesAndList(file).RunStdString(repo.Ctx, &RunOpts{Dir: path})
if err != nil {
return nil, err
}
if len(res) < 40 {
return nil, fmt.Errorf("invalid result of blame: %s", res)
}
return repo.GetCommit(res[:40])
}

View File

@ -5,88 +5,20 @@
package git
import (
"context"
"errors"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
)
// BranchPrefix base dir of the branch information file store on git
const BranchPrefix = "refs/heads/"
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
_, _, err := NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &RunOpts{Dir: repoPath})
return err == nil
}
// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
}
func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
stdout = strings.TrimSpace(stdout)
if !strings.HasPrefix(stdout, BranchPrefix) {
return "", errors.New("the HEAD is not a branch: " + stdout)
}
return strings.TrimPrefix(stdout, BranchPrefix), nil
}
// DeleteBranchOptions Option(s) for delete branch
type DeleteBranchOptions struct {
Force bool
}
// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
cmd := NewCommand("branch")
if opts.Force {
cmd.AddArguments("-D")
} else {
cmd.AddArguments("-d")
}
cmd.AddDashesAndList(name)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
cmd := NewCommand("branch")
cmd.AddDashesAndList(branch, oldbranchOrCommit)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
cmd := NewCommand("remote", "add")
cmd := gitcmd.NewCommand("remote", "add")
if fetch {
cmd.AddArguments("-f")
}
cmd.AddDynamicArguments(name, url)
_, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
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})
_, _, err := cmd.RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
return err
}

View File

@ -13,6 +13,7 @@ import (
"io"
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/log"
)
@ -70,25 +71,25 @@ func (repo *Repository) IsBranchExist(name string) bool {
// GetBranchNames returns branches from the repository, skipping "skip" initial branches and
// returning at most "limit" branches, or all branches if "limit" is 0.
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit)
return callShowRef(repo.Ctx, repo.Path, BranchPrefix, gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit)
}
// WalkReferences walks all the references from the repository
// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty.
func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) {
var args TrustedCmdArgs
var args gitcmd.TrustedCmdArgs
switch refType {
case ObjectTag:
args = TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}
args = gitcmd.TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"}
case ObjectBranch:
args = TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}
args = gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}
}
return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn)
}
// callShowRef return refs, if limit = 0 it will not limit
func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) {
func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) {
countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error {
branchName = strings.TrimPrefix(branchName, trimPrefix)
branchNames = append(branchNames, branchName)
@ -98,7 +99,7 @@ func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs Tru
return branchNames, countAll, err
}
func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
func WalkShowRef(ctx context.Context, repoPath string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
@ -107,9 +108,9 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs,
go func() {
stderrBuilder := &strings.Builder{}
args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args = append(args, extraArgs...)
err := NewCommand(args...).Run(ctx, &RunOpts{
err := gitcmd.NewCommand(args...).Run(ctx, &gitcmd.RunOpts{
Dir: repoPath,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
@ -119,7 +120,7 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs,
_ = stdoutWriter.Close()
return
}
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String()))
_ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String()))
} else {
_ = stdoutWriter.Close()
}

View File

@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/setting"
)
@ -59,7 +60,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
relpath = `\` + relpath
}
stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@ -74,7 +75,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
// GetCommitByPath returns the last commit of relative path.
func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, runErr := gitcmd.NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@ -91,7 +92,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
cmd := NewCommand("log").
cmd := gitcmd.NewCommand("log").
AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", pageSize).
AddArguments(prettyLogFormat).
@ -107,7 +108,7 @@ func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int,
cmd.AddOptionFormat("--until=%s", until)
}
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -117,7 +118,7 @@ func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int,
func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([]*Commit, error) {
// add common arguments to git command
addCommonSearchArgs := func(c *Command) {
addCommonSearchArgs := func(c *gitcmd.Command) {
// ignore case
c.AddArguments("-i")
@ -141,7 +142,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
}
// create new git log command with limit of 100 commits
cmd := NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
cmd := gitcmd.NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
// pretend that all refs along with HEAD were listed on command line as <commis>
// https://git-scm.com/docs/git-log#Documentation/git-log.txt---all
@ -161,7 +162,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// search for commits matching given constraints and keywords in commit msg
addCommonSearchArgs(cmd)
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@ -175,14 +176,14 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// ignore anything not matching a valid sha pattern
if id.Type().IsValid(v) {
// create new git log command with 1 commit limit
hashCmd := NewCommand("log", "-1", prettyLogFormat)
hashCmd := gitcmd.NewCommand("log", "-1", prettyLogFormat)
// add previous arguments except for --grep and --all
addCommonSearchArgs(hashCmd)
// add keyword as <commit>
hashCmd.AddDynamicArguments(v)
// search with given constraints for commit matching sha hash of v
hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil || bytes.Contains(stdout, hashMatching) {
continue
}
@ -197,7 +198,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
// You must ensure that id1 and id2 are valid commit ids.
func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
stdout, _, err := NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err := gitcmd.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}
@ -232,7 +233,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}()
go func() {
stderr := strings.Builder{}
gitCmd := NewCommand("rev-list").
gitCmd := gitcmd.NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
@ -248,13 +249,13 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}
gitCmd.AddDashesAndList(opts.File)
err := gitCmd.Run(repo.Ctx, &RunOpts{
err := gitCmd.Run(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
_ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
} else {
_ = stdoutWriter.Close()
}
@ -290,11 +291,11 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
// FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, _, err := NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err := gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
stdout, _, err = NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
}
if err != nil {
return 0, err
@ -308,13 +309,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
var stdout []byte
var err error
if before == nil {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
}
}
if err != nil {
@ -328,22 +329,22 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
var stdout []byte
var err error
if before == nil {
stdout, _, err = NewCommand("rev-list").
stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else {
stdout, _, err = NewCommand("rev-list").
stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list --max-count n before last so let's try that...
stdout, _, err = NewCommand("rev-list").
stdout, _, err = gitcmd.NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
}
}
if err != nil {
@ -358,13 +359,13 @@ func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch s
var stdout []byte
var err error
if before == nil {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
} else {
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err = gitcmd.NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
}
}
if err != nil {
@ -410,13 +411,13 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
// commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
cmd := NewCommand("log", prettyLogFormat)
cmd := gitcmd.NewCommand("log", prettyLogFormat)
if limit > 0 {
cmd.AddOptionFormat("-%d", limit)
}
cmd.AddDynamicArguments(id.String())
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@ -453,10 +454,10 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) {
if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
stdout, _, err := NewCommand("for-each-ref", "--format=%(refname:strip=2)").
stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commitID, BranchPrefix).
RunStdString(repo.Ctx, &RunOpts{
RunStdString(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Env: env,
})
@ -468,7 +469,7 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
return branches, nil
}
stdout, _, err := NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &RunOpts{
stdout, _, err := gitcmd.NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Env: env,
})
@ -510,7 +511,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch
func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
stdout, _, err := NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
stdout, _, err := gitcmd.NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}
@ -536,10 +537,10 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
// GetCommitBranchStart returns the commit where the branch diverged
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
cmd := NewCommand("log", prettyLogFormat)
cmd := gitcmd.NewCommand("log", prettyLogFormat)
cmd.AddDynamicArguments(endCommitID)
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{
stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &gitcmd.RunOpts{
Dir: repo.Path,
Env: env,
})

View File

@ -9,6 +9,8 @@ package git
import (
"strings"
"code.gitea.io/gitea/modules/git/gitcmd"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/plumbing/object"
@ -36,16 +38,6 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
return ref.Hash().String(), nil
}
// SetReference sets the commit ID string of given reference (e.g. branch or tag).
func (repo *Repository) SetReference(name, commitID string) error {
return repo.gogitRepo.Storer.SetReference(plumbing.NewReferenceFromStrings(name, commitID))
}
// RemoveReference removes the given reference (e.g. branch or tag).
func (repo *Repository) RemoveReference(name string) error {
return repo.gogitRepo.Storer.RemoveReference(plumbing.ReferenceName(name))
}
// ConvertToHash returns a Hash object from a potential ID string
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
objectFormat, err := repo.GetObjectFormat()
@ -59,7 +51,7 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
}
}
actualCommitID, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
actualCommitID, _, err := gitcmd.NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &gitcmd.RunOpts{Dir: repo.Path})
actualCommitID = strings.TrimSpace(actualCommitID)
if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") ||

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