diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 83410dc07c..f2af1709e4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -336,7 +336,7 @@ module.exports = { '@typescript-eslint/no-unsafe-unary-minus': [2], '@typescript-eslint/no-unused-expressions': [0], '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], - '@typescript-eslint/no-use-before-define': [0], + '@typescript-eslint/no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true, typedefs: false, enums: false, ignoreTypeReferences: true}], '@typescript-eslint/no-useless-constructor': [0], '@typescript-eslint/no-useless-empty-export': [0], '@typescript-eslint/no-wrapper-object-types': [2], @@ -693,7 +693,7 @@ module.exports = { 'no-unused-labels': [2], 'no-unused-private-class-members': [2], 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars - 'no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true}], + 'no-use-before-define': [0], // handled by @typescript-eslint/no-use-before-define 'no-use-extend-native/no-use-extend-native': [2], 'no-useless-backreference': [2], 'no-useless-call': [2], diff --git a/.github/labeler.yml b/.github/labeler.yml index 46efbcb194..0af43cd029 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -41,7 +41,7 @@ modifies/internal: - ".dockerignore" - "docker/**" - ".editorconfig" - - ".eslintrc.yaml" + - ".eslintrc.cjs" - ".golangci.yml" - ".gitpod.yml" - ".markdownlint.yaml" @@ -49,7 +49,7 @@ modifies/internal: - "stylelint.config.js" - ".yamllint.yaml" - ".github/**" - - ".gitea/" + - ".gitea/**" - ".devcontainer/**" - "build.go" - "build/**" @@ -73,9 +73,9 @@ modifies/go: modifies/frontend: - changed-files: - any-glob-to-any-file: - - "**/*.js" - - "**/*.ts" - - "**/*.vue" + - "*.js" + - "*.ts" + - "web_src/**" docs-update-needed: - changed-files: diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index b3ee93e6f8..be27537924 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -51,14 +51,16 @@ jobs: - "options/locale/locale_en-US.ini" frontend: - - "**/*.js" + - "*.js" + - "*.ts" - "web_src/**" + - "tools/*.js" + - "tools/*.ts" - "assets/emoji.json" - "package.json" - "package-lock.json" - "Makefile" - - ".eslintrc.yaml" - - "stylelint.config.js" + - ".eslintrc.cjs" - ".npmrc" docs: diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 7e988e0449..64090d6490 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -95,7 +95,7 @@ jobs: go-version-file: go.mod check-latest: true - run: make deps-backend deps-tools - - run: make lint-go-windows lint-go-vet + - run: make lint-go-windows lint-go-gitea-vet env: TAGS: bindata sqlite sqlite_unlock_notify GOOS: windows diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 0b23de0a66..6e879053d3 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -202,12 +202,10 @@ jobs: test-mssql: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed - # specifying the version of ubuntu in use as mssql fails on newer kernels - # pending resolution from vendor - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest services: mssql: - image: mcr.microsoft.com/mssql/server:2017-latest + image: mcr.microsoft.com/mssql/server:2019-latest env: ACCEPT_EULA: Y MSSQL_PID: Standard diff --git a/Makefile b/Makefile index e38fb801c3..0cc9269771 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ EXTRA_GOFLAGS ?= MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1) MAKE_EVIDENCE_DIR := .make_evidence +GOTESTFLAGS ?= -vet=off ifeq ($(RACE_ENABLED),true) GOFLAGS += -race GOTESTFLAGS += -race @@ -311,10 +312,10 @@ lint-frontend: lint-js lint-css ## lint frontend files lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues .PHONY: lint-backend -lint-backend: lint-go lint-go-vet lint-go-gopls lint-editorconfig ## lint backend files +lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files .PHONY: lint-backend-fix -lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig ## lint backend files and fix issues +lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues .PHONY: lint-js lint-js: node_modules ## lint js files @@ -365,9 +366,9 @@ lint-go-windows: @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) golangci-lint run -.PHONY: lint-go-vet -lint-go-vet: ## lint go files with vet - @echo "Running go vet..." +.PHONY: lint-go-gitea-vet +lint-go-gitea-vet: ## lint go files with gitea-vet + @echo "Running gitea-vet..." @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @$(GO) vet -vettool=gitea-vet ./... @@ -470,7 +471,9 @@ tidy-check: tidy go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses $(GO_LICENSE_FILE): go.mod go.sum - -$(GO) run $(GO_LICENSES_PACKAGE) save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null + @rm -rf $(GO_LICENSE_FILE) + $(GO) install $(GO_LICENSES_PACKAGE) + -GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) @rm -rf $(GO_LICENSE_TMP_DIR) diff --git a/cmd/hook.go b/cmd/hook.go index 578380ab40..41e3c3ce34 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -316,7 +316,7 @@ func runHookPostReceive(c *cli.Context) error { setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what - if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { + if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { return fmt.Errorf("Failed to call 'git update-server-info': %w", err) } diff --git a/go.mod b/go.mod index f2213b584e..0a544a9100 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ go 1.24 godebug x509negativeserial=1 require ( - code.gitea.io/actions-proto-go v0.4.0 + code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/gitea-vet v0.2.3 code.gitea.io/sdk/gitea v0.20.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 @@ -24,7 +24,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/ProtonMail/go-crypto v1.1.5 + github.com/ProtonMail/go-crypto v1.1.6 github.com/PuerkitoBio/goquery v1.10.2 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 github.com/alecthomas/chroma/v2 v2.15.0 @@ -119,7 +119,7 @@ require ( gitlab.com/gitlab-org/api/client-go v0.123.0 golang.org/x/crypto v0.35.0 golang.org/x/image v0.24.0 - golang.org/x/net v0.35.0 + golang.org/x/net v0.36.0 golang.org/x/oauth2 v0.27.0 golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 @@ -318,7 +318,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.261.3 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.4 // 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 diff --git a/go.sum b/go.sum index 4047c846e4..6e0b0bde07 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= -code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= +code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls= +code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU= @@ -16,8 +16,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4= -gitea.com/gitea/act v0.261.3/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= +gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8= +gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40= gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= @@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8= github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= @@ -867,8 +867,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/models/actions/runner.go b/models/actions/runner.go index 798a647180..97db0ca7ea 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -337,3 +337,17 @@ func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { } return res.RowsAffected() } + +func CountWrongRepoLevelRunners(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `action_runner` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelRunners(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `action_runner` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/actions/schedule.go b/models/actions/schedule.go index e2cc32eedc..fcdc7c2a4c 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -43,15 +43,12 @@ func init() { // GetSchedulesMapByIDs returns the schedules by given id slice. func GetSchedulesMapByIDs(ctx context.Context, ids []int64) (map[int64]*ActionSchedule, error) { schedules := make(map[int64]*ActionSchedule, len(ids)) + if len(ids) == 0 { + return schedules, nil + } return schedules, db.GetEngine(ctx).In("id", ids).Find(&schedules) } -// GetReposMapByIDs returns the repos by given id slice. -func GetReposMapByIDs(ctx context.Context, ids []int64) (map[int64]*repo_model.Repository, error) { - repos := make(map[int64]*repo_model.Repository, len(ids)) - return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) -} - // CreateScheduleTask creates new schedule task. func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error { // Return early if there are no rows to insert diff --git a/models/actions/schedule_spec_list.go b/models/actions/schedule_spec_list.go index f7dac72f8b..e26b2c1120 100644 --- a/models/actions/schedule_spec_list.go +++ b/models/actions/schedule_spec_list.go @@ -32,7 +32,7 @@ func (specs SpecList) LoadSchedules(ctx context.Context) error { } repoIDs := specs.GetRepoIDs() - repos, err := GetReposMapByIDs(ctx, repoIDs) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) if err != nil { return err } diff --git a/models/actions/variable.go b/models/actions/variable.go index 163bb12c93..1929cffbd8 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -147,3 +147,17 @@ func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, return variables, nil } + +func CountWrongRepoLevelVariables(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `action_variable` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelVariables(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `action_variable` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 2e65d76612..1ed6edd71a 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -25,7 +25,7 @@ func (err ErrKeyUnableVerify) Error() string { } // ErrKeyIsPrivate is returned when the provided key is a private key not a public key -var ErrKeyIsPrivate = util.NewSilentWrapErrorf(util.ErrInvalidArgument, "the provided key is a private key") +var ErrKeyIsPrivate = util.ErrorWrap(util.ErrInvalidArgument, "the provided key is a private key") // ErrKeyNotExist represents a "KeyNotExist" kind of error. type ErrKeyNotExist struct { diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 897ff3fc9e..0e5b2e96e6 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -5,6 +5,7 @@ package auth import ( "fmt" + "slices" "strings" "code.gitea.io/gitea/models/perm" @@ -14,7 +15,7 @@ import ( type AccessTokenScopeCategory int const ( - AccessTokenScopeCategoryActivityPub = iota + AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota AccessTokenScopeCategoryAdmin AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values AccessTokenScopeCategoryNotification @@ -193,6 +194,14 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A }, } +func GetAccessTokenCategories() (res []string) { + for _, cat := range accessTokenScopes[Read] { + res = append(res, strings.TrimPrefix(string(cat), "read:")) + } + slices.Sort(res) + return res +} + // GetRequiredScopes gets the specific scopes for a given level and categories func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope { scopes := make([]AccessTokenScope, 0, len(scopeCategories)) @@ -270,6 +279,9 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) { // StringSlice returns the AccessTokenScope as a []string func (s AccessTokenScope) StringSlice() []string { + if s == "" { + return nil + } return strings.Split(string(s), ",") } diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index a6097e45d7..9e4aa83633 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -17,6 +17,7 @@ type scopeTestNormalize struct { } func TestAccessTokenScope_Normalize(t *testing.T) { + assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories()) tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, @@ -25,7 +26,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) { {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, @@ -59,7 +60,7 @@ func TestAccessTokenScope_HasScope(t *testing.T) { {"public-only", "read:issue", false, nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestHasScope{ AccessTokenScope(fmt.Sprintf("read:%s", scope)), diff --git a/models/db/context.go b/models/db/context.go index 51627712b1..4b98796ef0 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -289,6 +289,9 @@ func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([ // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one // Timestamps of the entities won't be updated func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error { + if len(ids) == 0 { + return nil + } _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean) return err } diff --git a/models/db/name.go b/models/db/name.go index e2165fd76b..0e11c78372 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -77,7 +77,7 @@ func (err ErrNameCharsNotAllowed) Unwrap() error { func IsUsableName(reservedNames, reservedPatterns []string, name string) error { name = strings.TrimSpace(strings.ToLower(name)) if utf8.RuneCountInString(name) == 0 { - return util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument} + return util.NewInvalidArgumentErrorf("name is empty") } for i := range reservedNames { diff --git a/models/issues/issue.go b/models/issues/issue.go index 7e72bb776c..5204f27faf 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -595,6 +595,9 @@ func GetIssueByID(ctx context.Context, id int64) (*Issue, error) { // If keepOrder is true, the order of the returned issues will be the same as the given IDs. func GetIssuesByIDs(ctx context.Context, issueIDs []int64, keepOrder ...bool) (IssueList, error) { issues := make([]*Issue, 0, len(issueIDs)) + if len(issueIDs) == 0 { + return issues, nil + } if err := db.GetEngine(ctx).In("id", issueIDs).Find(&issues); err != nil { return nil, err diff --git a/models/issues/label.go b/models/issues/label.go index b9d24bbe99..8a5d9321cc 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -299,6 +299,9 @@ func GetLabelByID(ctx context.Context, labelID int64) (*Label, error) { // GetLabelsByIDs returns a list of labels by IDs func GetLabelsByIDs(ctx context.Context, labelIDs []int64, cols ...string) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx).Table("label"). In("id", labelIDs). Asc("name"). @@ -375,6 +378,9 @@ func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder { // it silently ignores label IDs that do not belong to the repository. func GetLabelsInRepoByIDs(ctx context.Context, repoID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx). Where("repo_id = ?", repoID). In("id", labelIDs). @@ -447,6 +453,9 @@ func GetLabelInOrgByID(ctx context.Context, orgID, labelID int64) (*Label, error // it silently ignores label IDs that do not belong to the organization. func GetLabelsInOrgByIDs(ctx context.Context, orgID int64, labelIDs []int64) ([]*Label, error) { labels := make([]*Label, 0, len(labelIDs)) + if len(labelIDs) == 0 { + return labels, nil + } return labels, db.GetEngine(ctx). Where("org_id = ?", orgID). In("id", labelIDs). diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 1ddb94e566..b685175f8e 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -28,11 +28,16 @@ type PullRequestsOptions struct { Labels []int64 MilestoneID int64 PosterID int64 + BaseBranch string } func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session { sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID) + if opts.BaseBranch != "" { + sess.And("pull_request.base_branch=?", opts.BaseBranch) + } + sess.Join("INNER", "issue", "pull_request.issue_id = issue.id") switch opts.State { case "closed", "open": @@ -56,7 +61,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR } // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged -func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) sess := db.GetEngine(ctx). Join("INNER", "issue", "issue.id = pull_request.issue_id"). @@ -111,7 +116,7 @@ func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch // GetUnmergedPullRequestsByBaseInfo returns all pull requests that are open and has not been merged // by given base information (repo and branch). -func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) ([]*PullRequest, error) { +func GetUnmergedPullRequestsByBaseInfo(ctx context.Context, repoID int64, branch string) (PullRequestList, error) { prs := make([]*PullRequest, 0, 2) return prs, db.GetEngine(ctx). Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", diff --git a/models/issues/pull_list_test.go b/models/issues/pull_list_test.go index c7a898ca4e..f5553e7885 100644 --- a/models/issues/pull_list_test.go +++ b/models/issues/pull_list_test.go @@ -16,11 +16,11 @@ import ( func TestPullRequestList_LoadAttributes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext)) + assert.NoError(t, prs.LoadAttributes(db.DefaultContext)) for _, pr := range prs { assert.NotNil(t, pr.Issue) assert.Equal(t, pr.IssueID, pr.Issue.ID) @@ -32,11 +32,11 @@ func TestPullRequestList_LoadAttributes(t *testing.T) { func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext) + reviewComments, err := prs.LoadReviewCommentsCounts(db.DefaultContext) assert.NoError(t, err) assert.Len(t, reviewComments, 2) for _, pr := range prs { @@ -47,11 +47,11 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { func TestPullRequestList_LoadReviews(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs := []*issues_model.PullRequest{ + prs := issues_model.PullRequestList{ unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}), unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}), } - reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext) + reviewList, err := prs.LoadReviews(db.DefaultContext) assert.NoError(t, err) // 1, 7, 8, 9, 10, 22 assert.Len(t, reviewList, 6) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 87d674a440..fd291c5692 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -374,6 +374,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312) newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge), newMigration(313, "Move PinOrder from issue table to a new table issue_pin", v1_24.MovePinOrderToTableIssuePin), + newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables), } return preparedMigrations } diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go index 44d44a26c5..cba64711d0 100644 --- a/models/migrations/v1_12/v128.go +++ b/models/migrations/v1_12/v128.go @@ -82,17 +82,17 @@ func FixMergeBase(x *xorm.Engine) error { if !pr.HasMerged { var err error - pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = git.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, gitRefName).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) if err != nil { var err2 error - pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err2 = git.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(git.DefaultContext, &git.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(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) + parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(git.DefaultContext, &git.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 @@ -104,9 +104,9 @@ func FixMergeBase(x *xorm.Engine) error { refs := append([]string{}, parents[1:]...) refs = append(refs, gitRefName) - cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...) + cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.RunStdString(git.DefaultContext, &git.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 diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go index 3d1c82f09e..a918d38757 100644 --- a/models/migrations/v1_12/v134.go +++ b/models/migrations/v1_12/v134.go @@ -79,7 +79,7 @@ func RefixMergeBase(x *xorm.Engine) error { gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) - parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) + parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(git.DefaultContext, &git.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 @@ -92,9 +92,9 @@ func RefixMergeBase(x *xorm.Engine) error { // we should recalculate refs := append([]string{}, parents[1:]...) refs = append(refs, gitRefName) - cmd := git.NewCommand(git.DefaultContext, "merge-base").AddDashesAndList(refs...) + cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = cmd.RunStdString(git.DefaultContext, &git.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 diff --git a/models/migrations/v1_24/v314.go b/models/migrations/v1_24/v314.go new file mode 100644 index 0000000000..e537be13b5 --- /dev/null +++ b/models/migrations/v1_24/v314.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 //nolint + +import ( + "xorm.io/xorm" +) + +func UpdateOwnerIDOfRepoLevelActionsTables(x *xorm.Engine) error { + if _, err := x.Exec("UPDATE `action_runner` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0"); err != nil { + return err + } + if _, err := x.Exec("UPDATE `action_variable` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0"); err != nil { + return err + } + _, err := x.Exec("UPDATE `secret` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + return err +} diff --git a/models/organization/team_list.go b/models/organization/team_list.go index 6f2a922e95..0274f9c5ba 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -133,5 +133,8 @@ func GetTeamsByOrgIDs(ctx context.Context, orgIDs []int64) (TeamList, error) { func GetTeamsByIDs(ctx context.Context, teamIDs []int64) (map[int64]*Team, error) { teams := make(map[int64]*Team, len(teamIDs)) + if len(teamIDs) == 0 { + return teams, nil + } return teams, db.GetEngine(ctx).Where(builder.In("`id`", teamIDs)).Find(&teams) } diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 803b73c968..d251fcc4a9 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -110,9 +110,12 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc if err != nil { return nil, err } - repository, err := repo_model.GetRepositoryByID(ctx, p.RepoID) - if err != nil && !repo_model.IsErrRepoNotExist(err) { - return nil, err + var repository *repo_model.Repository + if p.RepoID > 0 { + repository, err = repo_model.GetRepositoryByID(ctx, p.RepoID) + if err != nil && !repo_model.IsErrRepoNotExist(err) { + return nil, err + } } creator, err := user_model.GetUserByID(ctx, pv.CreatorID) if err != nil { diff --git a/models/project/column.go b/models/project/column.go index 5f581b5880..77ff5ef83e 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -336,6 +336,9 @@ func UpdateColumnSorting(ctx context.Context, cl ColumnList) error { func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { columns := make([]*Column, 0, 5) + if len(columnsIDs) == 0 { + return columns, nil + } if err := db.GetEngine(ctx). Where("project_id =?", projectID). In("id", columnsIDs). diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 5a3eac9f14..d06e94e5ac 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -50,15 +50,15 @@ func (archiver *RepoArchiver) RelativePath() string { func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) { parts := strings.SplitN(relativePath, "/", 3) if len(parts) != 3 { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: must have 3 parts") } repoID, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid repo id") } commitID, archiveType := git.SplitArchiveNameType(parts[2]) if archiveType == git.ArchiveUnknown { - return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument} + return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid archive type") } return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil } diff --git a/models/repo/repo.go b/models/repo/repo.go index 4e27dbaf14..13473699f3 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { type CloneLink struct { SSH string HTTPS string + Tea string } -// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. +// ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name. func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string { return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo)) } +// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name. func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string { sshUser := setting.SSH.User sshDomain := setting.SSH.Domain @@ -686,11 +688,17 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) } +// ComposeTeaCloneCommand returns Tea CLI clone command based on the given owner and repository name. +func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string { + return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo)) +} + func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink { - cl := new(CloneLink) - cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName) - cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName) - return cl + return &CloneLink{ + SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName), + HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName), + Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName), + } } // CloneLink returns clone URLs of repository. @@ -831,6 +839,9 @@ func GetRepositoryByID(ctx context.Context, id int64) (*Repository, error) { // GetRepositoriesMapByIDs returns the repositories by given id slice. func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repository, error) { repos := make(map[int64]*Repository, len(ids)) + if len(ids) == 0 { + return repos, nil + } return repos, db.GetEngine(ctx).In("id", ids).Find(&repos) } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 9bed2e9197..02c228e8a0 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -21,11 +21,6 @@ import ( "xorm.io/builder" ) -// FindReposMapByIDs find repos as map -func FindReposMapByIDs(ctx context.Context, repoIDs []int64, res map[int64]*Repository) error { - return db.GetEngine(ctx).In("id", repoIDs).Find(&res) -} - // RepositoryListDefaultPageSize is the default number of repositories // to load in memory when running administrative tasks on all (or almost // all) of them. diff --git a/models/secret/secret.go b/models/secret/secret.go index ce0ad65a79..eab9cf0712 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -165,3 +165,17 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[ return secrets, nil } + +func CountWrongRepoLevelSecrets(ctx context.Context) (int64, error) { + var result int64 + _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `secret` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result) + return result, err +} + +func UpdateWrongRepoLevelSecrets(ctx context.Context) (int64, error) { + result, err := db.GetEngine(ctx).Exec("UPDATE `secret` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0") + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/models/user/must_change_password.go b/models/user/must_change_password.go index 7eab08de89..686847c7d7 100644 --- a/models/user/must_change_password.go +++ b/models/user/must_change_password.go @@ -34,7 +34,7 @@ func SetMustChangePassword(ctx context.Context, all, mustChangePassword bool, in if !all { include = sliceTrimSpaceDropEmpty(include) if len(include) == 0 { - return 0, util.NewSilentWrapErrorf(util.ErrInvalidArgument, "no users to include provided") + return 0, util.ErrorWrap(util.ErrInvalidArgument, "no users to include provided") } cond = cond.And(builder.In("lower_name", include)) diff --git a/models/user/user_list.go b/models/user/user_list.go index c66d59f0d9..4241905058 100644 --- a/models/user/user_list.go +++ b/models/user/user_list.go @@ -11,6 +11,10 @@ import ( func GetUsersMapByIDs(ctx context.Context, userIDs []int64) (map[int64]*User, error) { userMaps := make(map[int64]*User, len(userIDs)) + if len(userIDs) == 0 { + return userMaps, nil + } + left := len(userIDs) for left > 0 { limit := db.DefaultMaxInSize diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 33e54fe75c..7bbab76bb8 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -29,8 +29,8 @@ 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(ctx, "rev-parse"). - Run(&RunOpts{ + err := NewCommand("rev-parse"). + Run(ctx, &RunOpts{ Dir: repoPath, Stderr: &stderr, }) @@ -61,8 +61,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, go func() { stderr := strings.Builder{} - err := NewCommand(ctx, "cat-file", "--batch-check"). - Run(&RunOpts{ + err := NewCommand("cat-file", "--batch-check"). + Run(ctx, &RunOpts{ Dir: repoPath, Stdin: batchStdinReader, Stdout: batchStdoutWriter, @@ -109,8 +109,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi go func() { stderr := strings.Builder{} - err := NewCommand(ctx, "cat-file", "--batch"). - Run(&RunOpts{ + err := NewCommand("cat-file", "--batch"). + Run(ctx, &RunOpts{ Dir: repoPath, Stdin: batchStdinReader, Stdout: batchStdoutWriter, diff --git a/modules/git/blame.go b/modules/git/blame.go index cad720edf4..d1d732c716 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -135,7 +135,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) } - cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain") + cmd := NewCommandNoGlobals("blame", "--porcelain") if ignoreRevsFile != nil { // Possible improvement: use --ignore-revs-file /dev/stdin on unix // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. @@ -155,7 +155,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(&RunOpts{ + err := cmd.Run(ctx, &RunOpts{ UseContextTimeout: true, Dir: repoPath, Stdout: stdout, diff --git a/modules/git/command.go b/modules/git/command.go index 602d00f027..f46fbea9b5 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -44,7 +44,6 @@ const DefaultLocale = "C" type Command struct { prog string args []string - parentContext context.Context globalArgsLength int brokenArgs []string } @@ -82,7 +81,7 @@ func (c *Command) LogString() string { // NewCommand creates and returns a new Git Command based on given command and arguments. // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. -func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command { +func NewCommand(args ...internal.CmdArg) *Command { // Make an explicit copy of globalCommandArgs, otherwise append might overwrite it cargs := make([]string, 0, len(globalCommandArgs)+len(args)) for _, arg := range globalCommandArgs { @@ -94,31 +93,23 @@ func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command { return &Command{ prog: GitExecutable, args: cargs, - parentContext: ctx, globalArgsLength: len(globalCommandArgs), } } -// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args +// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specified args and don't use global command args // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. -func NewCommandContextNoGlobals(ctx context.Context, args ...internal.CmdArg) *Command { +func NewCommandNoGlobals(args ...internal.CmdArg) *Command { cargs := make([]string, 0, len(args)) for _, arg := range args { cargs = append(cargs, string(arg)) } return &Command{ - prog: GitExecutable, - args: cargs, - parentContext: ctx, + prog: GitExecutable, + args: cargs, } } -// SetParentContext sets the parent context for this command -func (c *Command) SetParentContext(ctx context.Context) *Command { - c.parentContext = ctx - return c -} - // isSafeArgumentValue checks if the argument is safe to be used as a value (not an option) func isSafeArgumentValue(s string) bool { return s == "" || s[0] != '-' @@ -277,11 +268,11 @@ func CommonCmdServEnvs() []string { var ErrBrokenCommand = errors.New("git command is broken") // Run runs the command with the RunOpts -func (c *Command) Run(opts *RunOpts) error { - return c.run(1, opts) +func (c *Command) Run(ctx context.Context, opts *RunOpts) error { + return c.run(ctx, 1, opts) } -func (c *Command) run(skip int, opts *RunOpts) error { +func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error { if len(c.brokenArgs) != 0 { log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) return ErrBrokenCommand @@ -305,19 +296,18 @@ func (c *Command) run(skip int, opts *RunOpts) error { desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString) log.Debug("git.Command: %s", desc) - _, span := gtprof.GetTracer().Start(c.parentContext, gtprof.TraceSpanGitRun) + _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun) defer span.End() span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo) span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString) - var ctx context.Context var cancel context.CancelFunc var finished context.CancelFunc if opts.UseContextTimeout { - ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc) + ctx, cancel, finished = process.GetManager().AddContext(ctx, desc) } else { - ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc) + ctx, cancel, finished = process.GetManager().AddContextTimeout(ctx, timeout, desc) } defer finished() @@ -410,8 +400,8 @@ func IsErrorExitCode(err error, code int) bool { } // RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) { - stdoutBytes, stderrBytes, err := c.runStdBytes(opts) +func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stderr string, runErr RunStdError) { + stdoutBytes, stderrBytes, err := c.runStdBytes(ctx, opts) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { @@ -422,11 +412,11 @@ func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr Run } // RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). -func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { - return c.runStdBytes(opts) +func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { + return c.runStdBytes(ctx, opts) } -func (c *Command) runStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { +func (c *Command) runStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { if opts == nil { opts = &RunOpts{} } @@ -449,7 +439,7 @@ func (c *Command) runStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS PipelineFunc: opts.PipelineFunc, } - err := c.run(2, newOpts) + err := c.run(ctx, 2, newOpts) stderr = stderrBuf.Bytes() if err != nil { return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} diff --git a/modules/git/command_race_test.go b/modules/git/command_race_test.go index faf153d8b5..a6aa3a1580 100644 --- a/modules/git/command_race_test.go +++ b/modules/git/command_race_test.go @@ -15,9 +15,9 @@ func TestRunWithContextNoTimeout(t *testing.T) { maxLoops := 10 // 'git --version' does not block so it must be finished before the timeout triggered. - cmd := NewCommand(t.Context(), "--version") + cmd := NewCommand("--version") for i := 0; i < maxLoops; i++ { - if err := cmd.Run(&RunOpts{}); err != nil { + if err := cmd.Run(t.Context(), &RunOpts{}); err != nil { t.Fatal(err) } } @@ -27,9 +27,9 @@ func TestRunWithContextTimeout(t *testing.T) { maxLoops := 10 // 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered. - cmd := NewCommand(t.Context(), "hash-object", "--stdin") + cmd := NewCommand("hash-object", "--stdin") for i := 0; i < maxLoops; i++ { - if err := cmd.Run(&RunOpts{Timeout: 1 * time.Millisecond}); err != nil { + if err := cmd.Run(t.Context(), &RunOpts{Timeout: 1 * time.Millisecond}); err != nil { if err != context.DeadlineExceeded { t.Fatalf("Testing %d/%d: %v", i, maxLoops, err) } diff --git a/modules/git/command_test.go b/modules/git/command_test.go index 68a84dfb25..005a760ed1 100644 --- a/modules/git/command_test.go +++ b/modules/git/command_test.go @@ -10,14 +10,14 @@ import ( ) func TestRunWithContextStd(t *testing.T) { - cmd := NewCommand(t.Context(), "--version") - stdout, stderr, err := cmd.RunStdString(&RunOpts{}) + cmd := NewCommand("--version") + stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{}) assert.NoError(t, err) assert.Empty(t, stderr) assert.Contains(t, stdout, "git version") - cmd = NewCommand(t.Context(), "--no-such-arg") - stdout, stderr, err = cmd.RunStdString(&RunOpts{}) + cmd = NewCommand("--no-such-arg") + stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{}) if assert.Error(t, err) { assert.Equal(t, stderr, err.Stderr()) assert.Contains(t, err.Stderr(), "unknown option:") @@ -25,17 +25,17 @@ func TestRunWithContextStd(t *testing.T) { assert.Empty(t, stdout) } - cmd = NewCommand(t.Context()) + cmd = NewCommand() cmd.AddDynamicArguments("-test") - assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand) + assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand) - cmd = NewCommand(t.Context()) + cmd = NewCommand() cmd.AddDynamicArguments("--test") - assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand) + assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand) subCmd := "version" - cmd = NewCommand(t.Context()).AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production - stdout, stderr, err = cmd.RunStdString(&RunOpts{}) + cmd = NewCommand().AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production + stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{}) assert.NoError(t, err) assert.Empty(t, stderr) assert.Contains(t, stdout, "git version") @@ -53,9 +53,9 @@ func TestGitArgument(t *testing.T) { } func TestCommandString(t *testing.T) { - cmd := NewCommandContextNoGlobals(t.Context(), "a", "-m msg", "it's a test", `say "hello"`) + cmd := NewCommandNoGlobals("a", "-m msg", "it's a test", `say "hello"`) assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString()) - cmd = NewCommandContextNoGlobals(t.Context(), "url: https://a:b@c/", "/root/dir-a/dir-b") + cmd = NewCommandNoGlobals("url: https://a:b@c/", "/root/dir-a/dir-b") assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString()) } diff --git a/modules/git/commit.go b/modules/git/commit.go index 0a50ba4356..3e790e89d9 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -91,12 +91,12 @@ func AddChanges(repoPath string, all bool, files ...string) error { // AddChangesWithArgs marks local changes to be ready for commit. func AddChangesWithArgs(repoPath string, globalArgs TrustedCmdArgs, all bool, files ...string) error { - cmd := NewCommandContextNoGlobals(DefaultContext, globalArgs...).AddArguments("add") + cmd := NewCommandNoGlobals(globalArgs...).AddArguments("add") if all { cmd.AddArguments("--all") } cmd.AddDashesAndList(files...) - _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + _, _, err := cmd.RunStdString(DefaultContext, &RunOpts{Dir: repoPath}) return err } @@ -118,7 +118,7 @@ func CommitChanges(repoPath string, opts CommitChangesOptions) error { // CommitChangesWithArgs commits local changes with given committer, author and message. // If author is nil, it will be the same as committer. func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChangesOptions) error { - cmd := NewCommandContextNoGlobals(DefaultContext, args...) + cmd := NewCommandNoGlobals(args...) if opts.Committer != nil { cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) @@ -133,7 +133,7 @@ func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChan } cmd.AddOptionFormat("--message=%s", opts.Message) - _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + _, _, err := cmd.RunStdString(DefaultContext, &RunOpts{Dir: repoPath}) // No stderr but exit status 1 means nothing to commit. if err != nil && err.Error() == "exit status 1" { return nil @@ -143,7 +143,7 @@ func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChan // AllCommitsCount returns count of all commits in repository func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) { - cmd := NewCommand(ctx, "rev-list") + cmd := NewCommand("rev-list") if hidePRRefs { cmd.AddArguments("--exclude=" + PullPrefix + "*") } @@ -152,7 +152,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file cmd.AddDashesAndList(files...) } - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return 0, err } @@ -170,7 +170,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(ctx, "rev-list", "--count") + cmd := NewCommand("rev-list", "--count") cmd.AddDynamicArguments(opts.Revision...) @@ -182,7 +182,7 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) cmd.AddDashesAndList(opts.RelPath...) } - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: opts.RepoPath}) + stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: opts.RepoPath}) if err != nil { return 0, err } @@ -217,7 +217,7 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) { return false, nil } - _, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(&RunOpts{Dir: c.repo.Path}) + _, _, err := NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path}) if err == nil { return true, nil } @@ -358,12 +358,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(c.repo.Ctx, "name-rev") + cmd := 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(&RunOpts{Dir: c.repo.Path}) + data, _, err := cmd.RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where git can not describe commit if strings.Contains(err.Error(), "cannot describe") { @@ -441,7 +441,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi }() stderr := new(bytes.Buffer) - err := NewCommand(ctx, "log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(&RunOpts{ + err := NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &RunOpts{ Dir: repoPath, Stdout: w, Stderr: stderr, @@ -457,7 +457,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(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath}) + commitID, _, err := NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { if strings.Contains(err.Error(), "exit status 128") { return "", ErrNotExist{shortID, ""} diff --git a/modules/git/config.go b/modules/git/config.go index 9c36cf1654..234be7b955 100644 --- a/modules/git/config.go +++ b/modules/git/config.go @@ -116,7 +116,7 @@ func syncGitConfig() (err error) { } func configSet(key, value string) error { - stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + stdout, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil) if err != nil && !IsErrorExitCode(err, 1) { return fmt.Errorf("failed to get git config %s, err: %w", key, err) } @@ -126,7 +126,7 @@ func configSet(key, value string) error { return nil } - _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) + _, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil) if err != nil { return fmt.Errorf("failed to set git global config %s, err: %w", key, err) } @@ -135,14 +135,14 @@ func configSet(key, value string) error { } func configSetNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil) if err == nil { // already exist return nil } if IsErrorExitCode(err, 1) { // not exist, set new config - _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil) + _, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil) if err != nil { return fmt.Errorf("failed to set git global config %s, err: %w", key, err) } @@ -153,14 +153,14 @@ func configSetNonExist(key, value string) error { } func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) + _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(DefaultContext, nil) if err == nil { // already exist return nil } if IsErrorExitCode(err, 1) { // not exist, add new config - _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil) + _, _, err = NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil) if err != nil { return fmt.Errorf("failed to add git global config %s, err: %w", key, err) } @@ -170,10 +170,10 @@ func configAddNonExist(key, value string) error { } func configUnsetAll(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) + _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil) if err == nil { // exist, need to remove - _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) + _, _, err = NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(DefaultContext, nil) if err != nil { return fmt.Errorf("failed to unset git global config %s, err: %w", key, err) } diff --git a/modules/git/diff.go b/modules/git/diff.go index da0a2f26ba..c4df6b8063 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -34,8 +34,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(ctx, "show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID) - if err := cmd.Run(&RunOpts{ + cmd := NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID) + if err := cmd.Run(ctx, &RunOpts{ Dir: repoPath, Stdout: writer, Stderr: stderr, @@ -56,7 +56,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff files = append(files, file) } - cmd := NewCommand(repo.Ctx) + cmd := NewCommand() switch diffType { case RawDiffNormal: if len(startCommit) != 0 { @@ -89,7 +89,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff } stderr := new(bytes.Buffer) - if err = cmd.Run(&RunOpts{ + if err = cmd.Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: writer, Stderr: stderr, @@ -301,8 +301,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(repo.Ctx, "diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID). - Run(&RunOpts{ + err = NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID). + Run(repo.Ctx, &RunOpts{ Env: env, Dir: repo.Path, Stdout: stdoutWriter, diff --git a/modules/git/fsck.go b/modules/git/fsck.go index cec27f165b..a52684c84f 100644 --- a/modules/git/fsck.go +++ b/modules/git/fsck.go @@ -10,5 +10,5 @@ import ( // 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(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) + return NewCommand("fsck").AddArguments(args...).Run(ctx, &RunOpts{Timeout: timeout, Dir: repoPath}) } diff --git a/modules/git/git.go b/modules/git/git.go index e3e5b83274..2b593910a2 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -60,7 +60,7 @@ func DefaultFeatures() *Features { } func loadGitVersionFeatures() (*Features, error) { - stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil) + stdout, _, runErr := NewCommand("version").RunStdString(DefaultContext, nil) if runErr != nil { return nil, runErr } diff --git a/modules/git/grep.go b/modules/git/grep.go index bf6b41a886..c7ba862761 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -52,7 +52,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO 2^@repo: go-gitea/gitea */ var results []*GrepResult - cmd := NewCommand(ctx, "grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name") + cmd := NewCommand("grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name") cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) if opts.IsFuzzy { words := strings.Fields(search) @@ -66,7 +66,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(&RunOpts{ + err = cmd.Run(ctx, &RunOpts{ Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr, diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 1fd58abfcd..0e9e22f1dc 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -34,7 +34,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p _ = stdoutWriter.Close() } - cmd := NewCommand(ctx) + cmd := NewCommand() cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head) var files []string @@ -64,7 +64,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p go func() { stderr := strings.Builder{} - err := cmd.Run(&RunOpts{ + err := cmd.Run(ctx, &RunOpts{ Dir: repository, Stdout: stdoutWriter, Stderr: &stderr, diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go index 4677218150..5ddc36cc01 100644 --- a/modules/git/pipeline/catfile.go +++ b/modules/git/pipeline/catfile.go @@ -25,8 +25,8 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca stderr := new(bytes.Buffer) var errbuf strings.Builder - cmd := git.NewCommand(ctx, "cat-file", "--batch-check") - if err := cmd.Run(&git.RunOpts{ + cmd := git.NewCommand("cat-file", "--batch-check") + if err := cmd.Run(ctx, &git.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(ctx, "cat-file", "--batch-check", "--batch-all-objects") - if err := cmd.Run(&git.RunOpts{ + cmd := git.NewCommand("cat-file", "--batch-check", "--batch-all-objects") + if err := cmd.Run(ctx, &git.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(ctx, "cat-file", "--batch").Run(&git.RunOpts{ + if err := git.NewCommand("cat-file", "--batch").Run(ctx, &git.RunOpts{ Dir: tmpBasePath, Stdout: catFileBatchWriter, Stdin: shasToBatchReader, diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 92e35c5a10..c5eed73701 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -32,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err go func() { stderr := strings.Builder{} - err := git.NewCommand(repo.Ctx, "rev-list", "--all").Run(&git.RunOpts{ + err := git.NewCommand("rev-list", "--all").Run(repo.Ctx, &git.RunOpts{ Dir: repo.Path, Stdout: revListWriter, Stderr: &stderr, diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go index ad583a7479..06731c5051 100644 --- a/modules/git/pipeline/namerev.go +++ b/modules/git/pipeline/namerev.go @@ -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(ctx, "name-rev", "--stdin", "--name-only", "--always").Run(&git.RunOpts{ + if err := git.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &git.RunOpts{ Dir: tmpBasePath, Stdout: nameRevStdinWriter, Stdin: shasToNameReader, diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index d88ebe78ef..31627a0f3a 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -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(ctx, "rev-list", "--objects", "--all") - if err := cmd.Run(&git.RunOpts{ + cmd := git.NewCommand("rev-list", "--objects", "--all") + if err := cmd.Run(ctx, &git.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(ctx, "rev-list", "--objects").AddDynamicArguments(headSHA) + cmd := git.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA) if baseSHA != "" { cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA) } - if err := cmd.Run(&git.RunOpts{ + if err := cmd.Run(ctx, &git.RunOpts{ Dir: tmpBasePath, Stdout: revListWriter, Stderr: stderr, diff --git a/modules/git/remote.go b/modules/git/remote.go index ff8c040eb1..876c3d6acb 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -17,12 +17,12 @@ import ( func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { var cmd *Command if DefaultFeatures().CheckVersionAtLeast("2.7") { - cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName) + cmd = NewCommand("remote", "get-url").AddDynamicArguments(remoteName) } else { - cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url") + cmd = NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url") } - result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + result, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return "", err } diff --git a/modules/git/repo.go b/modules/git/repo.go index 0993a4ac37..6459adf851 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -57,7 +57,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(ctx, "ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(nil) + _, _, err := NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil) return err == nil } @@ -68,7 +68,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma return err } - cmd := NewCommand(ctx, "init") + cmd := NewCommand("init") if !IsValidObjectFormat(objectFormatName) { return fmt.Errorf("invalid object format: %s", objectFormatName) @@ -80,15 +80,15 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma if bare { cmd.AddArguments("--bare") } - _, _, err = cmd.RunStdString(&RunOpts{Dir: repoPath}) + _, _, err = cmd.RunStdString(ctx, &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(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all"). - Run(&RunOpts{ + if err := NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all"). + Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: &output, Stderr: &errbuf, @@ -129,7 +129,7 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op return err } - cmd := NewCommandContextNoGlobals(ctx, args...).AddArguments("clone") + cmd := NewCommandNoGlobals(args...).AddArguments("clone") if opts.SkipTLSVerify { cmd.AddArguments("-c", "http.sslVerify=false") } @@ -170,7 +170,7 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op } stderr := new(bytes.Buffer) - if err = cmd.Run(&RunOpts{ + if err = cmd.Run(ctx, &RunOpts{ Timeout: opts.Timeout, Env: envs, Stdout: io.Discard, @@ -193,7 +193,7 @@ type PushOptions struct { // Push pushs local commits to given remote branch. func Push(ctx context.Context, repoPath string, opts PushOptions) error { - cmd := NewCommand(ctx, "push") + cmd := NewCommand("push") if opts.Force { cmd.AddArguments("-f") } @@ -206,7 +206,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { } cmd.AddDashesAndList(remoteBranchArgs...) - stdout, stderr, err := cmd.RunStdString(&RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath}) + stdout, stderr, err := cmd.RunStdString(ctx, &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} @@ -225,8 +225,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(ctx, "for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + cmd := NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") + stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return time.Time{}, err } @@ -242,9 +242,9 @@ type DivergeObject struct { // 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(ctx, "rev-list", "--count", "--left-right"). + cmd := NewCommand("rev-list", "--count", "--left-right"). AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--") - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return do, err } @@ -273,23 +273,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io. defer os.RemoveAll(tmp) env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) - _, _, err = NewCommand(ctx, "init", "--bare").RunStdString(&RunOpts{Dir: tmp, Env: env}) + _, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) if err != nil { return err } - _, _, err = NewCommand(ctx, "reset", "--soft").AddDynamicArguments(commit).RunStdString(&RunOpts{Dir: tmp, Env: env}) + _, _, err = NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) if err != nil { return err } - _, _, err = NewCommand(ctx, "branch", "-m", "bundle").RunStdString(&RunOpts{Dir: tmp, Env: env}) + _, _, err = NewCommand("branch", "-m", "bundle").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) if err != nil { return err } tmpFile := filepath.Join(tmp, "bundle") - _, _, err = NewCommand(ctx, "bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env}) + _, _, err = NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) if err != nil { return err } diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go index 92f3e88f7c..0b2f6f2a45 100644 --- a/modules/git/repo_archive.go +++ b/modules/git/repo_archive.go @@ -53,7 +53,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t return fmt.Errorf("unknown format: %v", format) } - cmd := NewCommand(ctx, "archive") + cmd := NewCommand("archive") if usePrefix { cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/") } @@ -61,7 +61,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t cmd.AddDynamicArguments(commitID) var stderr strings.Builder - err := cmd.Run(&RunOpts{ + err := cmd.Run(ctx, &RunOpts{ Dir: repo.Path, Stdout: target, Stderr: &stderr, diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 90eb783fe8..1b6be0a3fa 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -41,7 +41,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) - cmd := NewCommand(repo.Ctx, "check-attr", "-z") + cmd := NewCommand("check-attr", "-z") if opts.AllAttributes { cmd.AddArguments("-a") @@ -59,7 +59,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ cmd.AddDashesAndList(opts.Filenames...) - if err := cmd.Run(&RunOpts{ + if err := cmd.Run(repo.Ctx, &RunOpts{ Env: env, Dir: repo.Path, Stdout: stdOut, @@ -122,7 +122,7 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { } c.ctx, c.cancel = context.WithCancel(ctx) - c.cmd = NewCommand(c.ctx, "check-attr", "--stdin", "-z") + c.cmd = NewCommand("check-attr", "--stdin", "-z") if len(c.IndexFile) > 0 { c.cmd.AddArguments("--cached") @@ -159,7 +159,7 @@ func (c *CheckAttributeReader) Run() error { _ = c.stdOut.Close() }() stdErr := new(bytes.Buffer) - err := c.cmd.Run(&RunOpts{ + err := c.cmd.Run(c.ctx, &RunOpts{ Env: c.env, Dir: c.Repo.Path, Stdin: c.stdinReader, diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go index 139cdd7be9..6941a76c42 100644 --- a/modules/git/repo_blame.go +++ b/modules/git/repo_blame.go @@ -9,10 +9,10 @@ import ( // LineBlame returns the latest commit at the given line func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { - res, _, err := NewCommand(repo.Ctx, "blame"). + res, _, err := NewCommand("blame"). AddOptionFormat("-L %d,%d", line, line). AddOptionValues("-p", revision). - AddDashesAndList(file).RunStdString(&RunOpts{Dir: path}) + AddDashesAndList(file).RunStdString(repo.Ctx, &RunOpts{Dir: path}) if err != nil { return nil, err } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 552ae2bb8c..916391f167 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -16,7 +16,7 @@ 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(ctx, "show-ref", "--verify").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repoPath}) + _, _, err := NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &RunOpts{Dir: repoPath}) return err == nil } @@ -38,7 +38,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { if repo == nil { return nil, fmt.Errorf("nil repo") } - stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -56,7 +56,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { } func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) { - stdout, _, err := NewCommand(ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repoPath}) + stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return "", err } @@ -105,7 +105,7 @@ type DeleteBranchOptions struct { // DeleteBranch delete a branch by name on repository. func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error { - cmd := NewCommand(repo.Ctx, "branch") + cmd := NewCommand("branch") if opts.Force { cmd.AddArguments("-D") @@ -114,36 +114,36 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro } cmd.AddDashesAndList(name) - _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) + _, _, 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(repo.Ctx, "branch") + cmd := NewCommand("branch") cmd.AddDashesAndList(branch, oldbranchOrCommit) - _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) + _, _, 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(repo.Ctx, "remote", "add") + cmd := NewCommand("remote", "add") if fetch { cmd.AddArguments("-f") } cmd.AddDynamicArguments(name, url) - _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) + _, _, 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(repo.Ctx, "remote", "rm").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("remote", "rm").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } @@ -154,6 +154,6 @@ func (branch *Branch) GetCommit() (*Commit, error) { // RenameBranch rename a branch func (repo *Repository) RenameBranch(from, to string) error { - _, _, err := NewCommand(repo.Ctx, "branch", "-m").AddDynamicArguments(from, to).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index 0d2efd4a6b..0d11198523 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -109,7 +109,7 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs, stderrBuilder := &strings.Builder{} args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} args = append(args, extraArgs...) - err := NewCommand(ctx, args...).Run(&RunOpts{ + err := NewCommand(args...).Run(ctx, &RunOpts{ Dir: repoPath, Stdout: stdoutWriter, Stderr: stderrBuilder, diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 02d8e163e4..72f35711f0 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -59,7 +59,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com relpath = `\` + relpath } - stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr } @@ -74,7 +74,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(repo.Ctx, "log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr } @@ -90,7 +90,7 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { } func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) { - cmd := NewCommand(repo.Ctx, "log"). + cmd := NewCommand("log"). AddOptionFormat("--skip=%d", (page-1)*pageSize). AddOptionFormat("--max-count=%d", pageSize). AddArguments(prettyLogFormat). @@ -100,7 +100,7 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri cmd.AddOptionValues("--not", not) } - stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -134,7 +134,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([ } // create new git log command with limit of 100 commits - cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) + cmd := NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String()) // pretend that all refs along with HEAD were listed on command line as // https://git-scm.com/docs/git-log#Documentation/git-log.txt---all @@ -154,7 +154,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(&RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -168,14 +168,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(repo.Ctx, "log", "-1", prettyLogFormat) + hashCmd := NewCommand("log", "-1", prettyLogFormat) // add previous arguments except for --grep and --all addCommonSearchArgs(hashCmd) // add keyword as hashCmd.AddDynamicArguments(v) // search with given constraints for commit matching sha hash of v - hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil || bytes.Contains(stdout, hashMatching) { continue } @@ -190,7 +190,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(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return false, err } @@ -223,7 +223,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) }() go func() { stderr := strings.Builder{} - gitCmd := NewCommand(repo.Ctx, "rev-list"). + gitCmd := NewCommand("rev-list"). AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize). AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize) gitCmd.AddDynamicArguments(opts.Revision) @@ -233,7 +233,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) } gitCmd.AddDashesAndList(opts.File) - err := gitCmd.Run(&RunOpts{ + err := gitCmd.Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr, @@ -275,11 +275,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(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID + "..." + endCommitID).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &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(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) } if err != nil { return 0, err @@ -293,13 +293,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error) var stdout []byte var err error if before == nil { - stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } else { - stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &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(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } } if err != nil { @@ -313,22 +313,22 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in var stdout []byte var err error if before == nil { - stdout, _, err = NewCommand(repo.Ctx, "rev-list"). + stdout, _, err = NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } else { - stdout, _, err = NewCommand(repo.Ctx, "rev-list"). + stdout, _, err = NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &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(repo.Ctx, "rev-list"). + stdout, _, err = NewCommand("rev-list"). AddOptionValues("--max-count", strconv.Itoa(limit)). AddOptionValues("--skip", strconv.Itoa(skip)). - AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) + AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } } if err != nil { @@ -343,13 +343,13 @@ func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch s var stdout []byte var err error if before == nil { - stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } else { - stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &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(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) } } if err != nil { @@ -395,13 +395,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(repo.Ctx, "log", prettyLogFormat) + cmd := NewCommand("log", prettyLogFormat) if limit > 0 { cmd.AddOptionFormat("-%d", limit) } cmd.AddDynamicArguments(id.String()) - stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr } @@ -438,10 +438,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(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)"). + stdout, _, err := NewCommand("for-each-ref", "--format=%(refname:strip=2)"). AddOptionFormat("--count=%d", limit). AddOptionValues("--contains", commitID, BranchPrefix). - RunStdString(&RunOpts{ + RunStdString(repo.Ctx, &RunOpts{ Dir: repo.Path, Env: env, }) @@ -453,7 +453,7 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([ return branches, nil } - stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commitID).RunStdString(&RunOpts{ + stdout, _, err := NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &RunOpts{ Dir: repo.Path, Env: env, }) @@ -495,7 +495,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(repo.Ctx, "branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return false, err } @@ -521,10 +521,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(repo.Ctx, "log", prettyLogFormat) + cmd := NewCommand("log", prettyLogFormat) cmd.AddDynamicArguments(endCommitID) - stdout, _, runErr := cmd.RunStdBytes(&RunOpts{ + stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{ Dir: repo.Path, Env: env, }) diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index 993013eef7..a88902e209 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -59,7 +59,7 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { } } - actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path}) + actualCommitID, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) actualCommitID = strings.TrimSpace(actualCommitID) if err != nil { if strings.Contains(err.Error(), "unknown revision or path") || diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index f5ed282a45..5aa0e9ec04 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -16,7 +16,7 @@ import ( // ResolveReference resolves a name to a reference func (repo *Repository) ResolveReference(name string) (string, error) { - stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("show-ref", "--hash").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { return "", ErrNotExist{name, ""} @@ -52,13 +52,13 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) { // SetReference sets the commit ID string of given reference (e.g. branch or tag). func (repo *Repository) SetReference(name, commitID string) error { - _, _, err := NewCommand(repo.Ctx, "update-ref").AddDynamicArguments(name, commitID).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("update-ref").AddDynamicArguments(name, commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } // RemoveReference removes the given reference (e.g. branch or tag). func (repo *Repository) RemoveReference(name string) error { - _, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } @@ -68,7 +68,7 @@ func (repo *Repository) IsCommitExist(name string) bool { log.Error("IsCommitExist: %v", err) return false } - _, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("cat-file", "-e").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err == nil } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 087d5bcec4..62c6378054 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -12,7 +12,7 @@ import ( // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { if DefaultFeatures().CheckVersionAtLeast("2.18") { - if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { + if _, _, err := NewCommand("commit-graph", "write").RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil { return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 877a7ff3b8..3995cb9ff5 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -39,13 +39,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri if tmpRemote != "origin" { tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base // Fetch commit into a temporary branch in order to be able to handle commits and tags - _, _, err := NewCommand(repo.Ctx, "fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base + ":" + tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err == nil { base = tmpBaseName } } - stdout, _, err := NewCommand(repo.Ctx, "merge-base").AddDashesAndList(base, head).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("merge-base").AddDashesAndList(base, head).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return strings.TrimSpace(stdout), base, err } @@ -94,9 +94,9 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string, if !fileOnly { // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git [...] -- [...]' var logs []byte - logs, _, err = NewCommand(repo.Ctx, "log").AddArguments(prettyLogFormat). - AddDynamicArguments(baseCommitID + separator + headBranch).AddArguments("--"). - RunStdBytes(&RunOpts{Dir: repo.Path}) + logs, _, err = NewCommand("log").AddArguments(prettyLogFormat). + AddDynamicArguments(baseCommitID+separator+headBranch).AddArguments("--"). + RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -150,8 +150,8 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis } // avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git [...] -- [...]' - if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base + separator + head).AddArguments("--"). - Run(&RunOpts{ + if err := NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base+separator+head).AddArguments("--"). + Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: w, Stderr: stderr, @@ -161,7 +161,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis // previously it would return the results of git diff -z --name-only base head so let's try that... w = &lineCountWriter{} stderr.Reset() - if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(&RunOpts{ + if err = NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: w, Stderr: stderr, @@ -189,8 +189,8 @@ func GetDiffShortStat(ctx context.Context, repoPath string, trustedArgs TrustedC // $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 // we get: // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" - cmd := NewCommand(ctx, "diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + cmd := NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) + stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { return 0, 0, 0, err } @@ -236,8 +236,8 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, // GetDiff generates and returns patch data between given revisions, optimized for human readability func (repo *Repository) GetDiff(compareArg string, w io.Writer) error { stderr := new(bytes.Buffer) - return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(compareArg). - Run(&RunOpts{ + return NewCommand("diff", "-p").AddDynamicArguments(compareArg). + Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: w, Stderr: stderr, @@ -246,7 +246,7 @@ func (repo *Repository) GetDiff(compareArg string, w io.Writer) error { // GetDiffBinary generates and returns patch data between given revisions, including binary diffs. func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error { - return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(&RunOpts{ + return NewCommand("diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: w, }) @@ -255,8 +255,8 @@ func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error { // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` func (repo *Repository) GetPatch(compareArg string, w io.Writer) error { stderr := new(bytes.Buffer) - return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg). - Run(&RunOpts{ + return NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg). + Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: w, Stderr: stderr, @@ -271,13 +271,13 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err if err != nil { return nil, err } - cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z") + cmd := NewCommand("diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z") if base == objectFormat.EmptyObjectID().String() { cmd.AddDynamicArguments(head) } else { cmd.AddDynamicArguments(base, head) } - stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go index e2b45064fd..8f91b4dce5 100644 --- a/modules/git/repo_gpg.go +++ b/modules/git/repo_gpg.go @@ -33,7 +33,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, Sign: true, } - value, _, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunStdString(&RunOpts{Dir: repo.Path}) + value, _, _ := NewCommand("config", "--get", "commit.gpgsign").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) sign, valid := ParseBool(strings.TrimSpace(value)) if !sign || !valid { gpgSettings.Sign = false @@ -41,13 +41,13 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, return gpgSettings, nil } - signingKey, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunStdString(&RunOpts{Dir: repo.Path}) + signingKey, _, _ := NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) gpgSettings.KeyID = strings.TrimSpace(signingKey) - defaultEmail, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunStdString(&RunOpts{Dir: repo.Path}) + defaultEmail, _, _ := NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) gpgSettings.Email = strings.TrimSpace(defaultEmail) - defaultName, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunStdString(&RunOpts{Dir: repo.Path}) + defaultName, _, _ := NewCommand("config", "--get", "user.name").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) gpgSettings.Name = strings.TrimSpace(defaultName) if err := gpgSettings.LoadPublicKeyContent(); err != nil { diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index f45b6e6191..1c7fcc063e 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -22,7 +22,7 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) } if len(treeish) != objectFormat.FullLength() { - res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path}) + res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return err } @@ -42,7 +42,7 @@ func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) er if len(indexFilename) > 0 { env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) } - _, _, err := NewCommand(repo.Ctx, "read-tree").AddDynamicArguments(id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env}) + _, _, err := NewCommand("read-tree").AddDynamicArguments(id.String()).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path, Env: env}) if err != nil { return err } @@ -83,14 +83,14 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena // EmptyIndex empties the index func (repo *Repository) EmptyIndex() error { - _, _, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("read-tree", "--empty").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } // LsFiles checks if the given filenames are in the index func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { - cmd := NewCommand(repo.Ctx, "ls-files", "-z").AddDashesAndList(filenames...) - res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + cmd := NewCommand("ls-files", "-z").AddDashesAndList(filenames...) + res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -108,7 +108,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { if err != nil { return err } - cmd := NewCommand(repo.Ctx, "update-index", "--remove", "-z", "--index-info") + cmd := NewCommand("update-index", "--remove", "-z", "--index-info") stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) buffer := new(bytes.Buffer) @@ -118,7 +118,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { buffer.WriteString("0 blob " + objectFormat.EmptyObjectID().String() + "\t" + file + "\000") } } - return cmd.Run(&RunOpts{ + return cmd.Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdin: bytes.NewReader(buffer.Bytes()), Stdout: stdout, @@ -134,7 +134,7 @@ type IndexObjectInfo struct { // AddObjectsToIndex adds the provided object hashes to the index at the provided filenames func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error { - cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "-z", "--index-info") + cmd := NewCommand("update-index", "--add", "--replace", "-z", "--index-info") stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) buffer := new(bytes.Buffer) @@ -142,7 +142,7 @@ func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error { // using format: mode SP type SP sha1 TAB path buffer.WriteString(object.Mode + " blob " + object.Object.String() + "\t" + object.Filename + "\000") } - return cmd.Run(&RunOpts{ + return cmd.Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdin: bytes.NewReader(buffer.Bytes()), Stdout: stdout, @@ -157,7 +157,7 @@ func (repo *Repository) AddObjectToIndex(mode string, object ObjectID, filename // WriteTree writes the current index as a tree to the object db and returns its hash func (repo *Repository) WriteTree() (*Tree, error) { - stdout, _, runErr := NewCommand(repo.Ctx, "write-tree").RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, runErr := NewCommand("write-tree").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr } diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go index 3d48b91c6d..1d34305270 100644 --- a/modules/git/repo_object.go +++ b/modules/git/repo_object.go @@ -68,13 +68,13 @@ func (repo *Repository) HashObject(reader io.Reader) (ObjectID, error) { func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) { var cmd *Command if save { - cmd = NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") + cmd = NewCommand("hash-object", "-w", "--stdin") } else { - cmd = NewCommand(repo.Ctx, "hash-object", "--stdin") + cmd = NewCommand("hash-object", "--stdin") } stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err := cmd.Run(&RunOpts{ + err := cmd.Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdin: reader, Stdout: stdout, diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 850ec65502..739cfb972c 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -18,7 +18,7 @@ func (repo *Repository) GetRefs() ([]*Reference, error) { // ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC // refType should only be a literal "branch" or "tag" and nothing else func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { - cmd := NewCommand(ctx) + cmd := NewCommand() if refType == "branch" { cmd.AddArguments("branch") } else if refType == "tag" { @@ -26,7 +26,7 @@ func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA } else { return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) } - stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go index ac53d661b5..8d34713eaf 100644 --- a/modules/git/repo_ref_nogogit.go +++ b/modules/git/repo_ref_nogogit.go @@ -21,7 +21,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { go func() { stderrBuilder := &strings.Builder{} - err := NewCommand(repo.Ctx, "for-each-ref").Run(&RunOpts{ + err := NewCommand("for-each-ref").Run(repo.Ctx, &RunOpts{ Dir: repo.Path, Stdout: stdoutWriter, Stderr: stderrBuilder, diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go index 83220104bd..76fe92bb34 100644 --- a/modules/git/repo_stats.go +++ b/modules/git/repo_stats.go @@ -40,7 +40,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) since := fromTime.Format(time.RFC3339) - stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if runErr != nil { return nil, runErr } @@ -60,7 +60,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) _ = stdoutWriter.Close() }() - gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since) + gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since) if len(branch) == 0 { gitCmd.AddArguments("--branches=*") } else { @@ -68,7 +68,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) } stderr := new(strings.Builder) - err = gitCmd.Run(&RunOpts{ + err = gitCmd.Run(repo.Ctx, &RunOpts{ Env: []string{}, Dir: repo.Path, Stdout: stdoutWriter, diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 2026a4c9f5..d653b0e2e6 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -24,13 +24,13 @@ func IsTagExist(ctx context.Context, repoPath, name string) bool { // CreateTag create one tag in the repository func (repo *Repository) CreateTag(name, revision string) error { - _, _, err := NewCommand(repo.Ctx, "tag").AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } // CreateAnnotatedTag create one annotated tag in the repository func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { - _, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path}) + _, _, err := NewCommand("tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) return err } @@ -40,7 +40,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { return "", fmt.Errorf("SHA is too short: %s", sha) } - stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "-d").RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("show-ref", "--tags", "-d").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return "", err } @@ -63,7 +63,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) func (repo *Repository) GetTagID(name string) (string, error) { - stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := NewCommand("show-ref", "--tags").AddDashesAndList(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return "", err } @@ -123,9 +123,9 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} go func() { - err := NewCommand(repo.Ctx, "for-each-ref"). + err := NewCommand("for-each-ref"). AddOptionFormat("--format=%s", forEachRefFmt.Flag()). - AddArguments("--sort", "-*creatordate", "refs/tags").Run(rc) + AddArguments("--sort", "-*creatordate", "refs/tags").Run(repo.Ctx, rc) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String())) } else { diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index ab48d47d13..70e5aee023 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -33,7 +33,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt "GIT_COMMITTER_EMAIL="+committer.Email, "GIT_COMMITTER_DATE="+commitTimeStr, ) - cmd := NewCommand(repo.Ctx, "commit-tree").AddDynamicArguments(tree.ID.String()) + cmd := NewCommand("commit-tree").AddDynamicArguments(tree.ID.String()) for _, parent := range opts.Parents { cmd.AddArguments("-p").AddDynamicArguments(parent) @@ -53,7 +53,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err := cmd.Run(&RunOpts{ + err := cmd.Run(repo.Ctx, &RunOpts{ Env: env, Dir: repo.Path, Stdin: messageBytes, diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index 651794a5aa..f77cd83612 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -36,7 +36,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { } if len(idStr) != objectFormat.FullLength() { - res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path}) + res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/submodule.go b/modules/git/submodule.go index 017b644052..31a32f1a9e 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -45,7 +45,7 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul return scanner.Err() }, } - err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts) + err = NewCommand("ls-tree", "-r", "--", "HEAD").Run(ctx, opts) if err != nil { return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err) } @@ -56,8 +56,8 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul // It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir. func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error { for _, submodule := range submodules { - cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) - if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil { + cmd := NewCommand("update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path) + if stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil { log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err) return err } diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go index aaf5361086..fbf8c32e1e 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/submodule_test.go @@ -30,14 +30,14 @@ func TestAddTemplateSubmoduleIndexes(t *testing.T) { ctx := t.Context() tmpDir := t.TempDir() var err error - _, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir}) + _, _, err = NewCommand("init").RunStdString(ctx, &RunOpts{Dir: tmpDir}) require.NoError(t, err) _ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755) err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}}) require.NoError(t, err) - _, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir}) + _, _, err = NewCommand("add", "--all").RunStdString(ctx, &RunOpts{Dir: tmpDir}) require.NoError(t, err) - _, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir}) + _, _, err = NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(ctx, &RunOpts{Dir: tmpDir}) require.NoError(t, err) submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir) require.NoError(t, err) diff --git a/modules/git/tree.go b/modules/git/tree.go index 5a644f6c87..f6fdff97d0 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -48,10 +48,10 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { // LsTree checks if the given filenames are in the tree func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) { - cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only"). + cmd := NewCommand("ls-tree", "-z", "--name-only"). AddDashesAndList(append([]string{ref}, filenames...)...) - res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -65,9 +65,9 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error // GetTreePathLatestCommit returns the latest commit of a tree path func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { - stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1"). + stdout, _, err := NewCommand("rev-list", "-1"). AddDynamicArguments(refName).AddDashesAndList(treePath). - RunStdString(&RunOpts{Dir: repo.Path}) + RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index 993b98edc2..f88788418e 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -70,7 +70,7 @@ func (t *Tree) ListEntries() (Entries, error) { } } - stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) + stdout, _, runErr := NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path}) if runErr != nil { if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { return nil, ErrNotExist{ @@ -96,10 +96,10 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) { return t.entriesRecursive, nil } - stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r"). + stdout, _, runErr := NewCommand("ls-tree", "-t", "-r"). AddArguments(extraArgs...). AddDynamicArguments(t.ID.String()). - RunStdBytes(&RunOpts{Dir: t.repo.Path}) + RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path}) if runErr != nil { return nil, runErr } diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go index e13a4c82e1..3336036248 100644 --- a/modules/gitrepo/branch.go +++ b/modules/gitrepo/branch.go @@ -33,9 +33,9 @@ func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (str // SetDefaultBranch sets default branch of repository. func SetDefaultBranch(ctx context.Context, repo Repository, name string) error { - _, _, err := git.NewCommand(ctx, "symbolic-ref", "HEAD"). - AddDynamicArguments(git.BranchPrefix + name). - RunStdString(&git.RunOpts{Dir: repoPath(repo)}) + _, _, err := git.NewCommand("symbolic-ref", "HEAD"). + AddDynamicArguments(git.BranchPrefix+name). + RunStdString(ctx, &git.RunOpts{Dir: repoPath(repo)}) return err } diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index 963c151a05..fdb7866145 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -158,7 +158,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro var err error if !update.Sized { var stdout string - stdout, _, err = git.NewCommand(ctx, "cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + stdout, _, err = git.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return err } diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 5e4b2c56f2..9abece921e 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -143,7 +143,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro var err error if !update.Sized { var stdout string - stdout, _, err = git.NewCommand(ctx, "cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + stdout, _, err = git.NewCommand("cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return nil, err } diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index df9783288b..0089dd259f 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -16,7 +16,7 @@ import ( ) func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) { - stdout, _, err := git.NewCommand(ctx, "show-ref", "-s").AddDynamicArguments(git.BranchPrefix + repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + stdout, _, err := git.NewCommand("show-ref", "-s").AddDynamicArguments(git.BranchPrefix+repo.DefaultBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return "", err } @@ -32,8 +32,8 @@ func getRepoChanges(ctx context.Context, repo *repo_model.Repository, revision s needGenesis := len(status.CommitSha) == 0 if !needGenesis { - hasAncestorCmd := git.NewCommand(ctx, "merge-base").AddDynamicArguments(status.CommitSha, revision) - stdout, _, _ := hasAncestorCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + hasAncestorCmd := git.NewCommand("merge-base").AddDynamicArguments(status.CommitSha, revision) + stdout, _, _ := hasAncestorCmd.RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) needGenesis = len(stdout) == 0 } @@ -86,7 +86,7 @@ func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) { // genesisChanges get changes to add repo to the indexer for the first time func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { var changes internal.RepoChanges - stdout, _, runErr := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()}) + stdout, _, runErr := git.NewCommand("ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if runErr != nil { return nil, runErr } @@ -98,8 +98,8 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s // nonGenesisChanges get changes since the previous indexer update func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*internal.RepoChanges, error) { - diffCmd := git.NewCommand(ctx, "diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision) - stdout, _, runErr := diffCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + diffCmd := git.NewCommand("diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision) + stdout, _, runErr := diffCmd.RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if runErr != nil { // previous commit sha may have been removed by a force push, so // try rebuilding from scratch @@ -115,9 +115,9 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio updatedFilenames := make([]string, 0, 10) updateChanges := func() error { - cmd := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l").AddDynamicArguments(revision). + cmd := git.NewCommand("ls-tree", "--full-tree", "-l").AddDynamicArguments(revision). AddDashesAndList(updatedFilenames...) - lsTreeStdout, _, err := cmd.RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()}) + lsTreeStdout, _, err := cmd.RunStdBytes(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return err } diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go index e6fbae5056..5282916944 100644 --- a/modules/markup/sanitizer_default_test.go +++ b/modules/markup/sanitizer_default_test.go @@ -62,6 +62,10 @@ func TestSanitizer(t *testing.T) { `bad`, `bad`, `bad`, `bad`, `bad`, `bad`, + + // Some classes and attributes are used by the frontend framework and will execute JS code, so make sure they are removed + ``, `
txt
`, + `
txt
`, `
txt
`, } for i := 0; i < len(testCases); i += 2 { diff --git a/modules/packages/conda/metadata.go b/modules/packages/conda/metadata.go index 76ba95eace..5eb2890115 100644 --- a/modules/packages/conda/metadata.go +++ b/modules/packages/conda/metadata.go @@ -17,9 +17,9 @@ import ( ) var ( - ErrInvalidStructure = util.SilentWrap{Message: "package structure is invalid", Err: util.ErrInvalidArgument} - ErrInvalidName = util.SilentWrap{Message: "package name is invalid", Err: util.ErrInvalidArgument} - ErrInvalidVersion = util.SilentWrap{Message: "package version is invalid", Err: util.ErrInvalidArgument} + ErrInvalidStructure = util.NewInvalidArgumentErrorf("package structure is invalid") + ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") + ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") ) const ( diff --git a/modules/translation/i18n/errors.go b/modules/translation/i18n/errors.go deleted file mode 100644 index 7f64ccf908..0000000000 --- a/modules/translation/i18n/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package i18n - -import ( - "code.gitea.io/gitea/modules/util" -) - -var ( - ErrLocaleAlreadyExist = util.SilentWrap{Message: "lang already exists", Err: util.ErrAlreadyExist} - ErrUncertainArguments = util.SilentWrap{Message: "arguments to i18n should not contain uncertain slices", Err: util.ErrInvalidArgument} -) diff --git a/modules/translation/i18n/format.go b/modules/translation/i18n/format.go index e5e221831f..450509333f 100644 --- a/modules/translation/i18n/format.go +++ b/modules/translation/i18n/format.go @@ -4,6 +4,7 @@ package i18n import ( + "errors" "fmt" "reflect" ) @@ -30,7 +31,7 @@ func Format(format string, args ...any) (msg string, err error) { fmtArgs = append(fmtArgs, val.Index(i).Interface()) } } else { - err = ErrUncertainArguments + err = errors.New("arguments to i18n should not contain uncertain slices") break } } else { diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index b422996984..4f1ae7e13d 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -4,6 +4,7 @@ package i18n import ( + "errors" "fmt" "html/template" "slices" @@ -41,7 +42,7 @@ func NewLocaleStore() LocaleStore { // AddLocaleByIni adds locale by ini into the store func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error { if _, ok := store.localeMap[langName]; ok { - return ErrLocaleAlreadyExist + return errors.New("lang has already been added") } store.langNames = append(store.langNames, langName) diff --git a/modules/util/error.go b/modules/util/error.go index 13b592a9f8..8e67d5a82f 100644 --- a/modules/util/error.go +++ b/modules/util/error.go @@ -22,74 +22,74 @@ var ( ErrUnprocessableContent = errors.New("unprocessable content") ) -// SilentWrap provides a simple wrapper for a wrapped error where the wrapped error message plays no part in the error message +// errorWrapper provides a simple wrapper for a wrapped error where the wrapped error message plays no part in the error message // Especially useful for "untyped" errors created with "errors.New(…)" that can be classified as 'invalid argument', 'permission denied', 'exists already', or 'does not exist' -type SilentWrap struct { +type errorWrapper struct { Message string Err error } // Error returns the message -func (w SilentWrap) Error() string { +func (w errorWrapper) Error() string { return w.Message } // Unwrap returns the underlying error -func (w SilentWrap) Unwrap() error { +func (w errorWrapper) Unwrap() error { return w.Err } -type LocaleWrap struct { +type LocaleWrapper struct { err error TrKey string TrArgs []any } // Error returns the message -func (w LocaleWrap) Error() string { +func (w LocaleWrapper) Error() string { return w.err.Error() } // Unwrap returns the underlying error -func (w LocaleWrap) Unwrap() error { +func (w LocaleWrapper) Unwrap() error { return w.err } -// NewSilentWrapErrorf returns an error that formats as the given text but unwraps as the provided error -func NewSilentWrapErrorf(unwrap error, message string, args ...any) error { +// ErrorWrap returns an error that formats as the given text but unwraps as the provided error +func ErrorWrap(unwrap error, message string, args ...any) error { if len(args) == 0 { - return SilentWrap{Message: message, Err: unwrap} + return errorWrapper{Message: message, Err: unwrap} } - return SilentWrap{Message: fmt.Sprintf(message, args...), Err: unwrap} + return errorWrapper{Message: fmt.Sprintf(message, args...), Err: unwrap} } // NewInvalidArgumentErrorf returns an error that formats as the given text but unwraps as an ErrInvalidArgument func NewInvalidArgumentErrorf(message string, args ...any) error { - return NewSilentWrapErrorf(ErrInvalidArgument, message, args...) + return ErrorWrap(ErrInvalidArgument, message, args...) } // NewPermissionDeniedErrorf returns an error that formats as the given text but unwraps as an ErrPermissionDenied func NewPermissionDeniedErrorf(message string, args ...any) error { - return NewSilentWrapErrorf(ErrPermissionDenied, message, args...) + return ErrorWrap(ErrPermissionDenied, message, args...) } // NewAlreadyExistErrorf returns an error that formats as the given text but unwraps as an ErrAlreadyExist func NewAlreadyExistErrorf(message string, args ...any) error { - return NewSilentWrapErrorf(ErrAlreadyExist, message, args...) + return ErrorWrap(ErrAlreadyExist, message, args...) } // NewNotExistErrorf returns an error that formats as the given text but unwraps as an ErrNotExist func NewNotExistErrorf(message string, args ...any) error { - return NewSilentWrapErrorf(ErrNotExist, message, args...) + return ErrorWrap(ErrNotExist, message, args...) } -// ErrWrapLocale wraps an err with a translation key and arguments -func ErrWrapLocale(err error, trKey string, trArgs ...any) error { - return LocaleWrap{err: err, TrKey: trKey, TrArgs: trArgs} +// ErrorWrapLocale wraps an err with a translation key and arguments +func ErrorWrapLocale(err error, trKey string, trArgs ...any) error { + return LocaleWrapper{err: err, TrKey: trKey, TrArgs: trArgs} } -func ErrAsLocale(err error) *LocaleWrap { - var e LocaleWrap +func ErrorAsLocale(err error) *LocaleWrapper { + var e LocaleWrapper if errors.As(err, &e) { return &e } diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 3f2ac68802..ec914d2b2e 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -911,7 +911,6 @@ delete_token_success=Token byl odstraněn. Aplikace, které jej používají ji repo_and_org_access=Repozitář a přístup organizace permissions_public_only=Pouze veřejnost permissions_access_all=Vše (veřejné, soukromé a omezené) -select_permissions=Vyberte oprávnění permission_not_set=Není nastaveno permission_no_access=Bez přístupu permission_read=Přečtené @@ -2580,7 +2579,6 @@ diff.commit=revize diff.git-notes=Poznámky diff.data_not_available=Rozdílový obsah není dostupný diff.options_button=Možnosti rozdílového porovnání -diff.show_diff_stats=Zobrazit statistiky diff.download_patch=Stáhněte soubor záplaty diff.download_diff=Stáhněte rozdílový soubor diff.show_split_view=Rozdělené zobrazení diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f1eada3990..0bec9305aa 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -910,7 +910,6 @@ delete_token_success=Der Zugriffstoken wurde gelöscht. Anwendungen die diesen T repo_and_org_access=Repository- und Organisationszugriff permissions_public_only=Nur öffentlich permissions_access_all=Alle (öffentlich, privat und begrenzt) -select_permissions=Berechtigungen auswählen permission_not_set=Nicht festgelegt permission_no_access=Kein Zugriff permission_read=Lesen @@ -2569,7 +2568,6 @@ diff.commit=Commit diff.git-notes=Hinweise diff.data_not_available=Keine Diff-Daten verfügbar diff.options_button=Diff-Optionen -diff.show_diff_stats=Statistiken anzeigen diff.download_patch=Patch-Datei herunterladen diff.download_diff=Vergleichs-Datei herunterladen diff.show_split_view=Geteilte Ansicht diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 7fb4151f17..fe338d8906 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -810,7 +810,6 @@ delete_token_success=Το διακριτικό έχει διαγραφεί. Οι repo_and_org_access=Πρόσβαση στο Αποθετήριο και Οργανισμό permissions_public_only=Δημόσια μόνο permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περιορισμένα) -select_permissions=Επιλέξτε δικαιώματα permission_no_access=Καμία Πρόσβαση permission_read=Αναγνωσμένες permission_write=Ανάγνωση και Εγγραφή @@ -2317,7 +2316,6 @@ diff.commit=υποβολή diff.git-notes=Σημειώσεις diff.data_not_available=Δεν Υπάρχει Διαθέσιμο Περιεχόμενο Diff diff.options_button=Επιλογές Diff -diff.show_diff_stats=Εμφάνιση Στατιστικών diff.download_patch=Λήψη Αρχείου Patch diff.download_diff=Λήψη Αρχείου Diff diff.show_split_view=Διαιρεμένη Προβολή diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 61a3b9a1f4..aab7387fb8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -917,7 +917,6 @@ delete_token_success = The token has been deleted. Applications using it no long repo_and_org_access = Repository and Organization Access permissions_public_only = Public only permissions_access_all = All (public, private, and limited) -select_permissions = Select permissions permission_not_set = Not set permission_no_access = No Access permission_read = Read @@ -1939,7 +1938,7 @@ pulls.outdated_with_base_branch = This branch is out-of-date with the base branc pulls.close = Close Pull Request pulls.closed_at = `closed this pull request %[2]s` pulls.reopened_at = `reopened this pull request %[2]s` -pulls.cmd_instruction_hint = `View command line instructions.` +pulls.cmd_instruction_hint = View command line instructions pulls.cmd_instruction_checkout_title = Checkout pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes. pulls.cmd_instruction_merge_title = Merge @@ -2597,7 +2596,6 @@ diff.commit = commit diff.git-notes = Notes diff.data_not_available = Diff Content Not Available diff.options_button = Diff Options -diff.show_diff_stats = Show Stats diff.download_patch = Download Patch File diff.download_diff = Download Diff File diff.show_split_view = Split View diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index c399b1209c..f856eaebd6 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -806,7 +806,6 @@ delete_token_success=El token ha sido eliminado. Las aplicaciones que lo usen ya repo_and_org_access=Acceso al Repositorio y a la Organización permissions_public_only=Sólo público permissions_access_all=Todo (público, privado y limitado) -select_permissions=Seleccionar permisos permission_no_access=Sin acceso permission_read=Leídas permission_write=Lectura y Escritura @@ -2298,7 +2297,6 @@ diff.commit=commit diff.git-notes=Notas diff.data_not_available=El contenido del Diff no está disponible diff.options_button=Opciones de diferencias -diff.show_diff_stats=Mostrar estadísticas diff.download_patch=Descargar archivo de parche diff.download_diff=Descargar archivo de diferencias diff.show_split_view=Dividir vista diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 3d34e01722..c82cfc61bd 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1777,7 +1777,6 @@ diff.commit=کامیت diff.git-notes=یادداشت‌ها diff.data_not_available=محتوای تفاوت ها در دسترس نیست diff.options_button=تنظیمات (diff) تغییرات -diff.show_diff_stats=نمایش وضعیت diff.download_patch=دانلود پرونده وصله diff.download_diff=دانلود فایل تغییرات diff diff.show_split_view=مشاهده تقسیم شده diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 9d652fabad..a2e17ef6ed 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -917,7 +917,6 @@ delete_token_success=Ce jeton a été supprimé. Les applications l'utilisant n' repo_and_org_access=Accès aux Organisations et Dépôts permissions_public_only=Publique uniquement permissions_access_all=Tout (public, privé et limité) -select_permissions=Sélectionner les autorisations permission_not_set=Non défini permission_no_access=Aucun accès permission_read=Lecture @@ -1701,7 +1700,9 @@ issues.time_estimate_invalid=Le format du temps estimé est invalide issues.start_tracking_history=`a commencé son travail %s.` issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur un autre ticket !` +issues.stop_tracking=Arrêter le minuteur issues.stop_tracking_history=a travaillé sur %[1]s %[2]s +issues.cancel_tracking=Abandonner issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal issues.add_time_history=a pointé du temps de travail sur %[1]s, %[2]s @@ -2590,7 +2591,6 @@ diff.commit=révision diff.git-notes=Notes diff.data_not_available=Contenu de la comparaison indisponible diff.options_button=Option de Diff -diff.show_diff_stats=Voir les Statistiques diff.download_patch=Télécharger le Fichier Patch diff.download_diff=Télécharger le Fichier des Différences diff.show_split_view=Vue séparée diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index cc7051fb65..1009dbbdca 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -917,7 +917,6 @@ delete_token_success=Tá an comhartha scriosta. Níl rochtain ag iarratais a ús repo_and_org_access=Rochtain Stórála agus Eagraíochta permissions_public_only=Poiblí amháin permissions_access_all=Gach (poiblí, príobháideach agus teoranta) -select_permissions=Roghnaigh ceadanna permission_not_set=Níl leagtha permission_no_access=Gan rochtain permission_read=Léigh @@ -1464,6 +1463,8 @@ issues.filter_milestones=Cloch Mhíle Scagaire issues.filter_projects=Tionscadal Scagaire issues.filter_labels=Lipéad Scagaire issues.filter_reviewers=Athbhreithneoir Scagaire +issues.filter_no_results=Gan torthaí +issues.filter_no_results_placeholder=Bain triail as do scagairí cuardaigh a choigeartú. issues.new=Eagrán Nua issues.new.title_empty=Ní féidir leis an teideal a bheith folamh issues.new.labels=Lipéid @@ -1701,7 +1702,9 @@ issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí issues.start_tracking_history=thosaigh ag obair %s issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar eagrán eile!` +issues.stop_tracking=Stad uaineadóir issues.stop_tracking_history=d'oibrigh do %[1]s %[2]s +issues.cancel_tracking=Caith amach issues.cancel_tracking_history=`rianú ama curtha ar ceal %s` issues.del_time=Scrios an log ama seo issues.add_time_history=cuireadh am caite %[1]s %[2]s leis @@ -2590,7 +2593,6 @@ diff.commit=tiomantas diff.git-notes=Nótaí diff.data_not_available=Níl Ábhar Difríochtaí Ar Fáil diff.options_button=Roghanna Diff -diff.show_diff_stats=Taispeáin Staitisticí diff.download_patch=Íoslódáil an comhad paiste diff.download_diff=Íoslódáil Comhad Diff diff.show_split_view=Amharc Scoilt diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 4767a48547..08a71c27d2 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1098,7 +1098,6 @@ diff.parent=szülő diff.commit=commit diff.git-notes=Megjegyzések diff.data_not_available=A különbségek nem megjeleníthetőek -diff.show_diff_stats=Statisztikák mutatása diff.show_split_view=Osztott nézet diff.show_unified_view=Egyesített nézet diff.stats_desc=%d fájl változott, egészen pontosan %d új sor hozzáadva és %d régi sor törölve diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 29512f47f3..48a43210bf 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1929,7 +1929,6 @@ diff.commit=commit diff.git-notes=Note diff.data_not_available=Dati Diff non disponibili diff.options_button=Opzioni Diff -diff.show_diff_stats=Mostra statistiche diff.download_patch=Scarica il file Patch diff.download_diff=Scarica il file Diff diff.show_split_view=Visualizzazione separata diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index ed2a16cf90..cf0d4bffa7 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -917,7 +917,6 @@ delete_token_success=トークンを削除しました。 削除したトーク repo_and_org_access=リポジトリと組織へのアクセス permissions_public_only=公開のみ permissions_access_all=すべて (公開、プライベート、限定) -select_permissions=許可の選択 permission_not_set=設定なし permission_no_access=アクセス不可 permission_read=読み取り @@ -2592,7 +2591,6 @@ diff.commit=コミット diff.git-notes=Notes diff.data_not_available=差分はありません diff.options_button=差分オプション -diff.show_diff_stats=統計情報を表示 diff.download_patch=Patchファイルをダウンロード diff.download_diff=Diffファイルをダウンロード diff.show_split_view=分割表示 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index d2df0813ae..7c30d0bb3c 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -814,7 +814,6 @@ delete_token_success=Pilnvara tika izdzēsta. Lietojumprogrammām, kas izmantoja repo_and_org_access=Repozitorija un organizācijas piekļuve permissions_public_only=Tikai publiskie permissions_access_all=Visi (publiskie, privātie un ierobežotie) -select_permissions=Norādiet tiesības permission_no_access=Nav piekļuves permission_read=Skatīšanās permission_write=Skatīšanās un raksīšanas @@ -2317,7 +2316,6 @@ diff.commit=revīzija diff.git-notes=Piezīmes diff.data_not_available=Satura salīdzināšana nav pieejama diff.options_button=Salīdzināšanas iespējas -diff.show_diff_stats=Rādīt statistiku diff.download_patch=Lejupielādēt ielāpa failu diff.download_diff=Lejupielādēt izmaiņu failu diff.show_split_view=Dalītais skats diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index c23df29e99..650e8d4e23 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1864,7 +1864,6 @@ diff.commit=commit diff.git-notes=Notities diff.data_not_available=Diff gegevens niet beschikbaar diff.options_button=Diff opties -diff.show_diff_stats=Statistieken weergeven diff.download_patch=Download Patch-bestand diff.download_diff=Download Diff-bestand diff.show_split_view=Zij-aan-zij weergave diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index d03018c0d9..55a82e9629 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1728,7 +1728,6 @@ diff.commit=commit diff.git-notes=Notatki diff.data_not_available=Informacje nt. zmian nie są dostępne diff.options_button=Opcje porównania -diff.show_diff_stats=Pokaż statystyki diff.download_patch=Ściągnij plik aktualizacji diff.download_diff=Ściągnij plik porównania diff.show_split_view=Widok podzielony diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 33aad76023..f4b479344d 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -812,7 +812,6 @@ delete_token_success=O token foi excluído. Os aplicativos que o utilizam já n repo_and_org_access=Acesso ao Repositório e Organização permissions_public_only=Apenas público permissions_access_all=Todos (público, privado e limitado) -select_permissions=Selecionar permissões permission_no_access=Sem acesso permission_read=Ler permission_write=Ler e escrever @@ -2282,7 +2281,6 @@ diff.commit=commit diff.git-notes=Notas diff.data_not_available=Conteúdo de diff não disponível diff.options_button=Opções de diferenças -diff.show_diff_stats=Mostrar estatísticas diff.download_patch=Baixar arquivo de patch diff.download_diff=Baixar arquivo de diferenças diff.show_split_view=Visão dividida diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 3582755d5e..6485fdf73a 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -917,7 +917,6 @@ delete_token_success=O código foi eliminado. Aplicações que o usavam deixaram repo_and_org_access=Acesso aos repositórios e às organizações permissions_public_only=Apenas público permissions_access_all=Tudo (público, privado e limitado) -select_permissions=Escolher permissões permission_not_set=Não definido permission_no_access=Sem acesso permission_read=Lidas @@ -1464,6 +1463,8 @@ issues.filter_milestones=Filtrar etapa issues.filter_projects=Filtrar planeamento issues.filter_labels=Filtrar rótulo issues.filter_reviewers=Filtrar revisor +issues.filter_no_results=Sem resultados +issues.filter_no_results_placeholder=Tente ajustar os seus filtros de pesquisa. issues.new=Questão nova issues.new.title_empty=O título não pode estar vazio issues.new.labels=Rótulos @@ -2592,7 +2593,6 @@ diff.commit=cometimento diff.git-notes=Notas diff.data_not_available=O conteúdo das diferenças não está disponível diff.options_button=Opções das diferenças -diff.show_diff_stats=Mostrar estatísticas diff.download_patch=Descarregar ficheiro patch diff.download_diff=Descarregar ficheiro diff diff.show_split_view=Visualização em 2 colunas diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 0aa776b78a..6140766291 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -807,7 +807,6 @@ delete_token_success=Токен удалён. Приложения, исполь repo_and_org_access=Доступ к репозиторию и организации permissions_public_only=Только публичные permissions_access_all=Все (публичные, приватные и ограниченные) -select_permissions=Выбрать разрешения permission_no_access=Нет доступа permission_read=Прочитанные permission_write=Чтение и запись @@ -2268,7 +2267,6 @@ diff.commit=Коммит diff.git-notes=Заметки diff.data_not_available=Разница недоступна diff.options_button=Опции Diff -diff.show_diff_stats=Показать статистику diff.download_patch=Скачать Patch файл diff.download_diff=Скачать Diff файл diff.show_split_view=Разделённый вид diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 80db8862fe..4857fa8d88 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1739,7 +1739,6 @@ diff.commit=කැප diff.git-notes=සටහන් diff.data_not_available=Diff අන්තර්ගත ලබාගත නොහැක diff.options_button=විවිධ විකල්ප -diff.show_diff_stats=සංඛ්යාන පෙන්වන්න diff.download_patch=පැච් ගොනුව බාගත diff.download_diff=බාගත Dff ගොනුව diff.show_split_view=භේදය දැක්ම diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 0454512402..72e3f4f9c5 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -895,7 +895,6 @@ delete_token_success=Jeton silindi. Onu kullanan uygulamalar artık hesabınıza repo_and_org_access=Depo ve Organizasyon Erişimi permissions_public_only=Yalnızca herkese açık permissions_access_all=Tümü (herkese açık, özel ve sınırlı) -select_permissions=İzinleri seçin permission_not_set=Ayarlanmadı permission_no_access=Erişim Yok permission_read=Okunmuş @@ -2470,7 +2469,6 @@ diff.commit=işleme diff.git-notes=Notlar diff.data_not_available=Farklı İçerik Mevut Değil diff.options_button=Diff Seçenekleri -diff.show_diff_stats=İstatistikleri Göster diff.download_patch=Yama Dosyasını İndir diff.download_diff=Diff Dosyasını İndir diff.show_split_view=Görünümü Böl diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 25ebb843a9..4071659304 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1789,7 +1789,6 @@ diff.commit=коміт diff.git-notes=Примітки diff.data_not_available=Різниця недоступна diff.options_button=Параметри порівняння -diff.show_diff_stats=Показати статистику diff.download_patch=Завантажити патч diff.download_diff=Завантажити файл різниці diff.show_split_view=Розділений перегляд diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3b6aca4e92..fe44b7067b 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -910,7 +910,6 @@ delete_token_success=令牌已经被删除。使用该令牌的应用将不再 repo_and_org_access=仓库和组织访问权限 permissions_public_only=仅公开 permissions_access_all=全部(公开、私有和受限) -select_permissions=选择权限 permission_not_set=未设置 permission_no_access=无访问权限 permission_read=可读 @@ -2569,7 +2568,6 @@ diff.commit=当前提交 diff.git-notes=Notes diff.data_not_available=比较内容不可用 diff.options_button=Diff 选项 -diff.show_diff_stats=显示统计 diff.download_patch=下载 Patch 文件 diff.download_diff=下载 Diff 文件 diff.show_split_view=分列视图 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 737f183f73..4f3cfe20c8 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -907,7 +907,6 @@ delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再 repo_and_org_access=儲存庫和組織存取 permissions_public_only=僅公開 permissions_access_all=全部 (公開、私有與受限) -select_permissions=選擇權限 permission_not_set=尚未設定 permission_no_access=沒有權限 permission_read=讀取 @@ -2560,7 +2559,6 @@ diff.commit=當前提交 diff.git-notes=備註 diff.data_not_available=沒有內容比較可以使用 diff.options_button=差異選項 -diff.show_diff_stats=顯示統計資料 diff.download_patch=下載 Patch 檔 diff.download_diff=下載差異檔 diff.show_split_view=分割檢視 diff --git a/routers/api/packages/composer/api.go b/routers/api/packages/composer/api.go index a3bcf80417..a3ea2c2f9a 100644 --- a/routers/api/packages/composer/api.go +++ b/routers/api/packages/composer/api.go @@ -66,6 +66,7 @@ type PackageMetadataResponse struct { } // PackageVersionMetadata contains package metadata +// https://getcomposer.org/doc/05-repositories.md#package type PackageVersionMetadata struct { *composer_module.Metadata Name string `json:"name"` @@ -73,6 +74,7 @@ type PackageVersionMetadata struct { Type string `json:"type"` Created time.Time `json:"time"` Dist Dist `json:"dist"` + Source Source `json:"source"` } // Dist contains package download information @@ -82,6 +84,13 @@ type Dist struct { Checksum string `json:"shasum"` } +// Source contains package source information +type Source struct { + URL string `json:"url"` + Type string `json:"type"` + Reference string `json:"reference"` +} + func createPackageMetadataResponse(registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse { versions := make([]*PackageVersionMetadata, 0, len(pds)) @@ -94,7 +103,7 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac } } - versions = append(versions, &PackageVersionMetadata{ + pkg := PackageVersionMetadata{ Name: pd.Package.Name, Version: pd.Version.Version, Type: packageType, @@ -105,7 +114,16 @@ func createPackageMetadataResponse(registryURL string, pds []*packages_model.Pac URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)), Checksum: pd.Files[0].Blob.HashSHA1, }, - }) + } + if pd.Repository != nil { + pkg.Source = Source{ + URL: pd.Repository.HTMLURL(), + Type: "git", + Reference: pd.Version.Version, + } + } + + versions = append(versions, &pkg) } return &PackageMetadataResponse{ diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 1f61ac031a..412f2cfb58 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -59,6 +59,10 @@ func ListPullRequests(ctx *context.APIContext) { // description: Name of the repo // type: string // required: true + // - name: base_branch + // in: query + // description: Filter by target base branch of the pull request + // type: string // - name: state // in: query // description: State of pull request @@ -132,6 +136,7 @@ func ListPullRequests(ctx *context.APIContext) { Labels: labelIDs, MilestoneID: ctx.FormInt64("milestone"), PosterID: posterID, + BaseBranch: ctx.FormTrim("base_branch"), }) if err != nil { ctx.APIErrorInternal(err) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index eb7bb2b480..c64033d9e2 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -186,7 +186,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // 2. Disallow force pushes to protected branches if oldCommitID != objectFormat.EmptyObjectID().String() { - output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) + output, _, err := git.NewCommand("rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index 1af875e401..7c06cf8557 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -34,12 +34,12 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] // When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all": // List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits // So, it only lists the new commits received, doesn't list the commits already present in the receiving repository - command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all") + command = git.NewCommand("rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all") } else { - command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID) + command = git.NewCommand("rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID) } // This is safe as force pushes are already forbidden - err = command.Run(&git.RunOpts{ + err = command.Run(repo.Ctx, &git.RunOpts{ Env: env, Dir: repo.Path, Stdout: stdoutWriter, @@ -85,8 +85,8 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { commitID := git.MustIDFromString(sha) - return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha). - Run(&git.RunOpts{ + return git.NewCommand("cat-file", "commit").AddDynamicArguments(sha). + Run(repo.Ctx, &git.RunOpts{ Env: env, Dir: repo.Path, Stdout: stdoutWriter, diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 707a2c06e8..ca48714507 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -813,7 +813,7 @@ func Run(ctx *context_module.Context) { return nil }) if err != nil { - if errLocale := util.ErrAsLocale(err); errLocale != nil { + if errLocale := util.ErrorAsLocale(err); errLocale != nil { ctx.Flash.Error(ctx.Tr(errLocale.TrKey, errLocale.TrArgs...)) ctx.Redirect(redirectURL) } else { diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 9c12ef9297..2728eda8b6 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -344,18 +344,30 @@ func Diff(ctx *context.Context) { ctx.Data["Reponame"] = repoName var parentCommit *git.Commit + var parentCommitID string if commit.ParentCount() > 0 { parentCommit, err = gitRepo.GetCommit(parents[0]) if err != nil { ctx.NotFound(err) return } + parentCommitID = parentCommit.ID.String() } setCompareContext(ctx, parentCommit, commit, userName, repoName) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Commit"] = commit ctx.Data["Diff"] = diff + if !fileOnly { + diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + } + statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) if err != nil { log.Error("GetLatestCommitStatus: %v", err) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 71bce759a9..5165636a52 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -633,6 +633,16 @@ func PrepareCompareDiff( ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 + if !fileOnly { + diffTree, err := gitdiff.GetDiffTree(ctx, ci.HeadGitRepo, false, beforeCommitID, headCommitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return false + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + } + headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID) if err != nil { ctx.ServerError("GetCommit", err) diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 5b7b0188dc..f93d7fc66a 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -320,7 +320,7 @@ func dummyInfoRefs(ctx *context.Context) { return } - refs, _, err := git.NewCommand(ctx, "receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Dir: tmpDir}) + refs, _, err := git.NewCommand("receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(ctx, &git.RunOpts{Dir: tmpDir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } @@ -403,12 +403,12 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string // one or more key=value pairs separated by colons var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) -func prepareGitCmdWithAllowedService(ctx *context.Context, service string) (*git.Command, error) { +func prepareGitCmdWithAllowedService(service string) (*git.Command, error) { if service == "receive-pack" { - return git.NewCommand(ctx, "receive-pack"), nil + return git.NewCommand("receive-pack"), nil } if service == "upload-pack" { - return git.NewCommand(ctx, "upload-pack"), nil + return git.NewCommand("upload-pack"), nil } return nil, fmt.Errorf("service %q is not allowed", service) @@ -428,7 +428,7 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) { return } - cmd, err := prepareGitCmdWithAllowedService(ctx, service) + cmd, err := prepareGitCmdWithAllowedService(service) if err != nil { log.Error("Failed to prepareGitCmdWithService: %v", err) ctx.Resp.WriteHeader(http.StatusUnauthorized) @@ -458,7 +458,7 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) { var stderr bytes.Buffer cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.getRepoDir()) - if err := cmd.Run(&git.RunOpts{ + if err := cmd.Run(ctx, &git.RunOpts{ Dir: h.getRepoDir(), Env: append(os.Environ(), h.environ...), Stdout: ctx.Resp, @@ -498,7 +498,7 @@ func getServiceType(ctx *context.Context) string { } func updateServerInfo(ctx gocontext.Context, dir string) []byte { - out, _, err := git.NewCommand(ctx, "update-server-info").RunStdBytes(&git.RunOpts{Dir: dir}) + out, _, err := git.NewCommand("update-server-info").RunStdBytes(ctx, &git.RunOpts{Dir: dir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(out))) } @@ -521,14 +521,14 @@ func GetInfoRefs(ctx *context.Context) { } setHeaderNoCache(ctx) service := getServiceType(ctx) - cmd, err := prepareGitCmdWithAllowedService(ctx, service) + cmd, err := prepareGitCmdWithAllowedService(service) if err == nil { if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) { h.environ = append(h.environ, "GIT_PROTOCOL="+protocol) } h.environ = append(os.Environ(), h.environ...) - refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.getRepoDir()}) + refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(ctx, &git.RunOpts{Env: h.environ, Dir: h.getRepoDir()}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 223f8d017e..1d37fd8603 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -242,7 +242,7 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri } if commitSHA != "" { // Get immediate parent of the first commit in the patch, grab history back - parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(&git.RunOpts{Dir: ctx.Repo.GitRepo.Path}) + parentCommit, _, err = git.NewCommand("rev-list", "-1", "--skip=1").AddDynamicArguments(commitSHA).RunStdString(ctx, &git.RunOpts{Dir: ctx.Repo.GitRepo.Path}) if err == nil { parentCommit = strings.TrimSpace(parentCommit) } @@ -761,6 +761,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi var methodWithError string var diff *gitdiff.Diff + shouldGetUserSpecificDiff := false // if we're not logged in or only a single commit (or commit range) is shown we // have to load only the diff and not get the viewed information @@ -772,6 +773,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } else { diff, err = gitdiff.SyncAndGetUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diffOptions, files...) methodWithError = "SyncAndGetUserSpecificDiff" + shouldGetUserSpecificDiff = true } if err != nil { ctx.ServerError(methodWithError, err) @@ -816,6 +818,27 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } } + if !fileOnly { + // note: use mergeBase is set to false because we already have the merge base from the pull request info + diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, pull.MergeBase, headCommitID) + if err != nil { + ctx.ServerError("GetDiffTree", err) + return + } + + filesViewedState := make(map[string]pull_model.ViewedState) + if shouldGetUserSpecificDiff { + // This sort of sucks because we already fetch this when getting the diff + review, err := pull_model.GetNewestReviewState(ctx, ctx.Doer.ID, issue.ID) + if err == nil && review != nil && review.UpdatedFiles != nil { + // If there wasn't an error and we have a review with updated files, use that + filesViewedState = review.UpdatedFiles + } + } + + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState) + } + ctx.Data["Diff"] = diff ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index d11af4669f..9ce9f8424d 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -6,9 +6,11 @@ package repo import ( "net/http" + pull_model "code.gitea.io/gitea/models/pull" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/gitdiff" "github.com/go-enry/go-enry/v2" ) @@ -52,3 +54,33 @@ func isExcludedEntry(entry *git.TreeEntry) bool { return false } + +type FileDiffFile struct { + Name string + NameHash string + IsSubmodule bool + IsViewed bool + Status string +} + +// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering +// it also takes a map of file names to their viewed state, which is used to mark files as viewed +func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile { + files := make([]FileDiffFile, 0, len(diffTree.Files)) + + for _, file := range diffTree.Files { + nameHash := git.HashFilePathForWebUI(file.HeadPath) + isSubmodule := file.HeadMode == git.EntryModeCommit + isViewed := filesViewedState[file.HeadPath] == pull_model.Viewed + + files = append(files, FileDiffFile{ + Name: file.HeadPath, + NameHash: nameHash, + IsSubmodule: isSubmodule, + IsViewed: isViewed, + Status: file.Status, + }) + } + + return files +} diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index cf71d01dc1..1f6c97a5cc 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -6,12 +6,14 @@ package setting import ( "net/http" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -39,18 +41,29 @@ func ApplicationsPost(ctx *context.Context) { ctx.Data["PageIsSettingsApplications"] = true ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) - if ctx.HasError() { - loadApplicationsData(ctx) - - ctx.HTML(http.StatusOK, tplSettingsApplications) - return + _ = ctx.Req.ParseForm() + var scopeNames []string + for k, v := range ctx.Req.Form { + if strings.HasPrefix(k, "scope-") { + scopeNames = append(scopeNames, v...) + } } - scope, err := form.GetScope() + scope, err := auth_model.AccessTokenScope(strings.Join(scopeNames, ",")).Normalize() if err != nil { ctx.ServerError("GetScope", err) return } + if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { + ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) + } + + if ctx.HasError() { + loadApplicationsData(ctx) + ctx.HTML(http.StatusOK, tplSettingsApplications) + return + } + t := &auth_model.AccessToken{ UID: ctx.Doer.ID, Name: form.Name, @@ -99,7 +112,14 @@ func loadApplicationsData(ctx *context.Context) { } ctx.Data["Tokens"] = tokens ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled - ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin + + // Handle specific ordered token categories for admin or non-admin users + tokenCategoryNames := auth_model.GetAccessTokenCategories() + if !ctx.Doer.IsAdmin { + tokenCategoryNames = util.SliceRemoveAll(tokenCategoryNames, "admin") + } + ctx.Data["TokenCategories"] = tokenCategoryNames + if setting.OAuth2.Enabled { ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{ OwnerID: ctx.Doer.ID, diff --git a/services/actions/workflow.go b/services/actions/workflow.go index 4d24f7e2d7..e9dc3215c5 100644 --- a/services/actions/workflow.go +++ b/services/actions/workflow.go @@ -142,14 +142,14 @@ func GetActionWorkflow(ctx *context.APIContext, workflowID string) (*api.ActionW func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error { if workflowID == "" { - return util.ErrWrapLocale( + return util.ErrorWrapLocale( util.NewNotExistErrorf("workflowID is empty"), "actions.workflow.not_found", workflowID, ) } if ref == "" { - return util.ErrWrapLocale( + return util.ErrorWrapLocale( util.NewNotExistErrorf("ref is empty"), "form.target_ref_not_exist", ref, ) @@ -159,7 +159,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() if cfg.IsWorkflowDisabled(workflowID) { - return util.ErrWrapLocale( + return util.ErrorWrapLocale( util.NewPermissionDeniedErrorf("workflow is disabled"), "actions.workflow.disabled", ) @@ -178,7 +178,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re runTargetCommit, err = gitRepo.GetBranchCommit(ref) } if err != nil { - return util.ErrWrapLocale( + return util.ErrorWrapLocale( util.NewNotExistErrorf("ref %q doesn't exist", ref), "form.target_ref_not_exist", ref, ) @@ -209,7 +209,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re } if len(workflows) == 0 { - return util.ErrWrapLocale( + return util.ErrorWrapLocale( util.NewNotExistErrorf("workflow %q doesn't exist", workflowID), "actions.workflow.not_found", workflowID, ) diff --git a/services/agit/agit.go b/services/agit/agit.go index 897e825012..7d2ccbd0c2 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -182,9 +182,9 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. } if !forcePush.Value() { - output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1"). + output, _, err := git.NewCommand("rev-list", "--max-count=1"). AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]). - RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) + RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) if err != nil { return nil, fmt.Errorf("failed to detect force push: %w", err) } else if len(output) > 0 { diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 4fef91ac15..da265dec27 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -92,15 +92,15 @@ func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) { if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { // Can ignore the error here as it means that commit.gpgsign is not set - value, _, _ := git.NewCommand(ctx, "config", "--get", "commit.gpgsign").RunStdString(&git.RunOpts{Dir: repoPath}) + value, _, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { return "", nil } - signingKey, _, _ := git.NewCommand(ctx, "config", "--get", "user.signingkey").RunStdString(&git.RunOpts{Dir: repoPath}) - signingName, _, _ := git.NewCommand(ctx, "config", "--get", "user.name").RunStdString(&git.RunOpts{Dir: repoPath}) - signingEmail, _, _ := git.NewCommand(ctx, "config", "--get", "user.email").RunStdString(&git.RunOpts{Dir: repoPath}) + signingKey, _, _ := git.NewCommand("config", "--get", "user.signingkey").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) + signingName, _, _ := git.NewCommand("config", "--get", "user.name").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) + signingEmail, _, _ := git.NewCommand("config", "--get", "user.email").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) return strings.TrimSpace(signingKey), &git.Signature{ Name: strings.TrimSpace(signingName), Email: strings.TrimSpace(signingEmail), diff --git a/services/context/context.go b/services/context/context.go index 5e08fba442..f3a0f0bb5f 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -213,13 +213,16 @@ func Contexter() func(next http.Handler) http.Handler { // Attention: this function changes ctx.Data and ctx.Flash // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. func (ctx *Context) HasError() bool { - hasErr, ok := ctx.Data["HasError"] - if !ok { + hasErr, _ := ctx.Data["HasError"].(bool) + hasErr = hasErr || ctx.Flash.ErrorMsg != "" + if !hasErr { return false } - ctx.Flash.ErrorMsg = ctx.GetErrMsg() + if ctx.Flash.ErrorMsg == "" { + ctx.Flash.ErrorMsg = ctx.GetErrMsg() + } ctx.Data["Flash"] = ctx.Flash - return hasErr.(bool) + return hasErr } // GetErrMsg returns error message in form validation. diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index 7cb7445148..62326ed07c 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -12,6 +12,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/migrations" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) @@ -164,6 +165,24 @@ func prepareDBConsistencyChecks() []consistencyCheck { Fixer: repo_model.DeleteOrphanedTopics, FixedMessage: "Removed", }, + { + Name: "Repository level Runners with non-zero owner_id", + Counter: actions_model.CountWrongRepoLevelRunners, + Fixer: actions_model.UpdateWrongRepoLevelRunners, + FixedMessage: "Corrected", + }, + { + Name: "Repository level Variables with non-zero owner_id", + Counter: actions_model.CountWrongRepoLevelVariables, + Fixer: actions_model.UpdateWrongRepoLevelVariables, + FixedMessage: "Corrected", + }, + { + Name: "Repository level Secrets with non-zero owner_id", + Counter: secret_model.CountWrongRepoLevelSecrets, + Fixer: secret_model.UpdateWrongRepoLevelSecrets, + FixedMessage: "Corrected", + }, } // TODO: function to recalc all counters diff --git a/services/doctor/heads.go b/services/doctor/heads.go index 41fca01d57..bbfd40da5e 100644 --- a/services/doctor/heads.go +++ b/services/doctor/heads.go @@ -18,9 +18,9 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) numReposUpdated := 0 err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { numRepos++ - _, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + _, _, defaultBranchErr := git.NewCommand("rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) - head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + head, _, headErr := git.NewCommand("symbolic-ref", "--short", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) // what we expect: default branch is valid, and HEAD points to it if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch { @@ -46,7 +46,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) } // otherwise, let's try fixing HEAD - err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()}) + err := git.NewCommand("symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(ctx, &git.RunOpts{Dir: repo.RepoPath()}) if err != nil { logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err) return nil diff --git a/services/doctor/mergebase.go b/services/doctor/mergebase.go index de460c4190..482bcd0a46 100644 --- a/services/doctor/mergebase.go +++ b/services/doctor/mergebase.go @@ -42,17 +42,17 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro if !pr.HasMerged { var err error - pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err = git.NewCommand("merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitRefName()).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil { var err2 error - pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) + pr.MergeBase, _, err2 = git.NewCommand("rev-parse").AddDynamicArguments(git.BranchPrefix+pr.BaseBranch).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err2 != nil { logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2) return nil } } } else { - parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) + parentsString, _, err := git.NewCommand("rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil { logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil @@ -64,8 +64,8 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro refs := append([]string{}, parents[1:]...) refs = append(refs, pr.GetGitRefName()) - cmd := git.NewCommand(ctx, "merge-base").AddDashesAndList(refs...) - pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + cmd := git.NewCommand("merge-base").AddDashesAndList(refs...) + pr.MergeBase, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil { logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil diff --git a/services/doctor/misc.go b/services/doctor/misc.go index 9300c3a25c..f36a9ab478 100644 --- a/services/doctor/misc.go +++ b/services/doctor/misc.go @@ -99,11 +99,11 @@ func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool defer r.Close() if autofix { - _, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path}) + _, _, err := git.NewCommand("config", "receive.advertisePushOptions", "true").RunStdString(ctx, &git.RunOpts{Dir: r.Path}) return err } - value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path}) + value, _, err := git.NewCommand("config", "receive.advertisePushOptions").RunStdString(ctx, &git.RunOpts{Dir: r.Path}) if err != nil { return err } diff --git a/services/forms/user_form.go b/services/forms/user_form.go index ed79936add..c9ce71e886 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -7,9 +7,7 @@ package forms import ( "mime/multipart" "net/http" - "strings" - auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" @@ -347,8 +345,7 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind // NewAccessTokenForm form for creating access token type NewAccessTokenForm struct { - Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` - Scope []string + Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` } // Validate validates the fields @@ -357,12 +354,6 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) { - scope := strings.Join(f.Scope, ",") - s, err := auth_model.AccessTokenScope(scope).Normalize() - return s, err -} - // EditOAuth2ApplicationForm form for editing oauth2 applications type EditOAuth2ApplicationForm struct { Name string `binding:"Required;MaxSize(255)" form:"application_name"` diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go index 66050187c9..b4120f20ed 100644 --- a/services/forms/user_form_test.go +++ b/services/forms/user_form_test.go @@ -4,10 +4,8 @@ package forms import ( - "strconv" "testing" - auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/setting" "github.com/gobwas/glob" @@ -104,28 +102,3 @@ func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) { assert.Equal(t, v.valid, form.IsEmailDomainAllowed()) } } - -func TestNewAccessTokenForm_GetScope(t *testing.T) { - tests := []struct { - form NewAccessTokenForm - scope auth_model.AccessTokenScope - expectedErr error - }{ - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}}, - scope: "read:repository", - }, - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}}, - scope: "read:repository,write:user", - }, - } - - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - scope, err := test.form.GetScope() - assert.Equal(t, test.expectedErr, err) - assert.Equal(t, test.scope, scope) - }) - } -} diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go index 8039de145d..035210a31d 100644 --- a/services/gitdiff/git_diff_tree.go +++ b/services/gitdiff/git_diff_tree.go @@ -54,12 +54,12 @@ func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase b return nil, err } - cmd := git.NewCommand(ctx, "diff-tree", "--raw", "-r", "--find-renames", "--root") + cmd := git.NewCommand("diff-tree", "--raw", "-r", "--find-renames", "--root") if useMergeBase { cmd.AddArguments("--merge-base") } cmd.AddDynamicArguments(baseCommitID, headCommitID) - stdout, _, runErr := cmd.RunStdString(&git.RunOpts{Dir: gitRepo.Path}) + stdout, _, runErr := cmd.RunStdString(ctx, &git.RunOpts{Dir: gitRepo.Path}) if runErr != nil { log.Warn("git diff-tree: %v", runErr) return nil, runErr diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 064f05cfce..fbfdf42e19 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1122,7 +1122,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi cmdCtx, cmdCancel := context.WithCancel(ctx) defer cmdCancel() - cmdDiff := git.NewCommand(cmdCtx) + cmdDiff := git.NewCommand() objectFormat, err := gitRepo.GetObjectFormat() if err != nil { return nil, err @@ -1173,7 +1173,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi go func() { stderr := &bytes.Buffer{} - if err := cmdDiff.Run(&git.RunOpts{ + if err := cmdDiff.Run(cmdCtx, &git.RunOpts{ Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, Dir: repoPath, Stdout: writer, diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 52e19bde6f..7db259ac2c 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -6,46 +6,19 @@ package mailer import ( "bytes" - "context" - "fmt" "html/template" "mime" "regexp" - "strconv" "strings" texttmpl "text/template" - "time" - activities_model "code.gitea.io/gitea/models/activities" - issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/models/renderhelper" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/templates" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/translation" - incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload" sender_service "code.gitea.io/gitea/services/mailer/sender" - "code.gitea.io/gitea/services/mailer/token" ) -const ( - mailAuthActivate templates.TplName = "auth/activate" - mailAuthActivateEmail templates.TplName = "auth/activate_email" - mailAuthResetPassword templates.TplName = "auth/reset_passwd" - mailAuthRegisterNotify templates.TplName = "auth/register_notify" - - mailNotifyCollaborator templates.TplName = "notify/collaborator" - - mailRepoTransferNotify templates.TplName = "notify/repo_transfer" - - // There's no actual limit for subject in RFC 5322 - mailMaxSubjectRunes = 256 -) +const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322 var ( bodyTemplates *template.Template @@ -62,367 +35,6 @@ func SendTestMail(email string) error { return sender_service.Send(sender, sender_service.NewMessage(email, "Gitea Test Email!", "Gitea Test Email!")) } -// sendUserMail sends a mail to the user -func sendUserMail(language string, u *user_model.User, tpl templates.TplName, code, subject, info string) { - locale := translation.NewLocale(language) - data := map[string]any{ - "locale": locale, - "DisplayName": u.DisplayName(), - "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), - "ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, locale), - "Code": code, - "Language": locale.Language(), - } - - var content bytes.Buffer - - if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil { - log.Error("Template: %v", err) - return - } - - msg := sender_service.NewMessage(u.EmailTo(), subject, content.String()) - msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info) - - SendAsync(msg) -} - -// SendActivateAccountMail sends an activation mail to the user (new user registration) -func SendActivateAccountMail(locale translation.Locale, u *user_model.User) { - if setting.MailService == nil { - // No mail service configured - return - } - opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount} - sendUserMail(locale.Language(), u, mailAuthActivate, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.activate_account"), "activate account") -} - -// SendResetPasswordMail sends a password reset mail to the user -func SendResetPasswordMail(u *user_model.User) { - if setting.MailService == nil { - // No mail service configured - return - } - locale := translation.NewLocale(u.Language) - opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword} - sendUserMail(u.Language, u, mailAuthResetPassword, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.reset_password"), "recover account") -} - -// SendActivateEmailMail sends confirmation email to confirm new email address -func SendActivateEmailMail(u *user_model.User, email string) { - if setting.MailService == nil { - // No mail service configured - return - } - locale := translation.NewLocale(u.Language) - opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateEmail, NewEmail: email} - data := map[string]any{ - "locale": locale, - "DisplayName": u.DisplayName(), - "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), - "Code": user_model.GenerateUserTimeLimitCode(opts, u), - "Email": email, - "Language": locale.Language(), - } - - var content bytes.Buffer - - if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil { - log.Error("Template: %v", err) - return - } - - msg := sender_service.NewMessage(email, locale.TrString("mail.activate_email"), content.String()) - msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) - - SendAsync(msg) -} - -// SendRegisterNotifyMail triggers a notify e-mail by admin created a account. -func SendRegisterNotifyMail(u *user_model.User) { - if setting.MailService == nil || !u.IsActive { - // No mail service configured OR user is inactive - return - } - locale := translation.NewLocale(u.Language) - - data := map[string]any{ - "locale": locale, - "DisplayName": u.DisplayName(), - "Username": u.Name, - "Language": locale.Language(), - } - - var content bytes.Buffer - - if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil { - log.Error("Template: %v", err) - return - } - - msg := sender_service.NewMessage(u.EmailTo(), locale.TrString("mail.register_notify", setting.AppName), content.String()) - msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) - - SendAsync(msg) -} - -// SendCollaboratorMail sends mail notification to new collaborator. -func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) { - if setting.MailService == nil || !u.IsActive { - // No mail service configured OR the user is inactive - return - } - locale := translation.NewLocale(u.Language) - repoName := repo.FullName() - - subject := locale.TrString("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName) - data := map[string]any{ - "locale": locale, - "Subject": subject, - "RepoName": repoName, - "Link": repo.HTMLURL(), - "Language": locale.Language(), - } - - var content bytes.Buffer - - if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil { - log.Error("Template: %v", err) - return - } - - msg := sender_service.NewMessage(u.EmailTo(), subject, content.String()) - msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) - - SendAsync(msg) -} - -func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipients []*user_model.User, fromMention bool, info string) ([]*sender_service.Message, error) { - var ( - subject string - link string - prefix string - // Fall back subject for bad templates, make sure subject is never empty - fallback string - reviewComments []*issues_model.Comment - ) - - commentType := issues_model.CommentTypeComment - if ctx.Comment != nil { - commentType = ctx.Comment.Type - link = ctx.Issue.HTMLURL() + "#" + ctx.Comment.HashTag() - } else { - link = ctx.Issue.HTMLURL() - } - - reviewType := issues_model.ReviewTypeComment - if ctx.Comment != nil && ctx.Comment.Review != nil { - reviewType = ctx.Comment.Review.Type - } - - // This is the body of the new issue or comment, not the mail body - rctx := renderhelper.NewRenderContextRepoComment(ctx.Context, ctx.Issue.Repo).WithUseAbsoluteLink(true) - body, err := markdown.RenderString(rctx, - ctx.Content) - if err != nil { - return nil, err - } - - actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType) - - if actName != "new" { - prefix = "Re: " - } - fallback = prefix + fallbackMailSubject(ctx.Issue) - - if ctx.Comment != nil && ctx.Comment.Review != nil { - reviewComments = make([]*issues_model.Comment, 0, 10) - for _, lines := range ctx.Comment.Review.CodeComments { - for _, comments := range lines { - reviewComments = append(reviewComments, comments...) - } - } - } - locale := translation.NewLocale(lang) - - mailMeta := map[string]any{ - "locale": locale, - "FallbackSubject": fallback, - "Body": body, - "Link": link, - "Issue": ctx.Issue, - "Comment": ctx.Comment, - "IsPull": ctx.Issue.IsPull, - "User": ctx.Issue.Repo.MustOwner(ctx), - "Repo": ctx.Issue.Repo.FullName(), - "Doer": ctx.Doer, - "IsMention": fromMention, - "SubjectPrefix": prefix, - "ActionType": actType, - "ActionName": actName, - "ReviewComments": reviewComments, - "Language": locale.Language(), - "CanReply": setting.IncomingEmail.Enabled && commentType != issues_model.CommentTypePullRequestPush, - } - - var mailSubject bytes.Buffer - if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { - subject = sanitizeSubject(mailSubject.String()) - if subject == "" { - subject = fallback - } - } else { - log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err) - } - - subject = emoji.ReplaceAliases(subject) - - mailMeta["Subject"] = subject - - var mailBody bytes.Buffer - - if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { - log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) - } - - // Make sure to compose independent messages to avoid leaking user emails - msgID := generateMessageIDForIssue(ctx.Issue, ctx.Comment, ctx.ActionType) - reference := generateMessageIDForIssue(ctx.Issue, nil, activities_model.ActionType(0)) - - var replyPayload []byte - if ctx.Comment != nil { - if ctx.Comment.Type.HasMailReplySupport() { - replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Comment) - } - } else { - replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Issue) - } - if err != nil { - return nil, err - } - - unsubscribePayload, err := incoming_payload.CreateReferencePayload(ctx.Issue) - if err != nil { - return nil, err - } - - msgs := make([]*sender_service.Message, 0, len(recipients)) - for _, recipient := range recipients { - msg := sender_service.NewMessageFrom( - recipient.Email, - fromDisplayName(ctx.Doer), - setting.MailService.FromEmail, - subject, - mailBody.String(), - ) - msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) - - msg.SetHeader("Message-ID", msgID) - msg.SetHeader("In-Reply-To", reference) - - references := []string{reference} - listUnsubscribe := []string{"<" + ctx.Issue.HTMLURL() + ">"} - - if setting.IncomingEmail.Enabled { - if replyPayload != nil { - token, err := token.CreateToken(token.ReplyHandlerType, recipient, replyPayload) - if err != nil { - log.Error("CreateToken failed: %v", err) - } else { - replyAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1) - msg.ReplyTo = replyAddress - msg.SetHeader("List-Post", fmt.Sprintf("", replyAddress)) - - references = append(references, fmt.Sprintf("", token, setting.Domain)) - } - } - - token, err := token.CreateToken(token.UnsubscribeHandlerType, recipient, unsubscribePayload) - if err != nil { - log.Error("CreateToken failed: %v", err) - } else { - unsubAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1) - listUnsubscribe = append(listUnsubscribe, "") - } - } - - msg.SetHeader("References", references...) - msg.SetHeader("List-Unsubscribe", listUnsubscribe...) - - for key, value := range generateAdditionalHeaders(ctx, actType, recipient) { - msg.SetHeader(key, value) - } - - msgs = append(msgs, msg) - } - - return msgs, nil -} - -func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string { - var path string - if issue.IsPull { - path = "pulls" - } else { - path = "issues" - } - - var extra string - if comment != nil { - extra = fmt.Sprintf("/comment/%d", comment.ID) - } else { - switch actionType { - case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: - extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6) - case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: - extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6) - case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: - extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6) - case activities_model.ActionPullRequestReadyForReview: - extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6) - } - } - - return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain) -} - -func generateMessageIDForRelease(release *repo_model.Release) string { - return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain) -} - -func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string { - repo := ctx.Issue.Repo - - return map[string]string{ - // https://datatracker.ietf.org/doc/html/rfc2919 - "List-ID": fmt.Sprintf("%s <%s.%s.%s>", repo.FullName(), repo.Name, repo.OwnerName, setting.Domain), - - // https://datatracker.ietf.org/doc/html/rfc2369 - "List-Archive": fmt.Sprintf("<%s>", repo.HTMLURL()), - - "X-Mailer": "Gitea", - "X-Gitea-Reason": reason, - "X-Gitea-Sender": ctx.Doer.Name, - "X-Gitea-Recipient": recipient.Name, - "X-Gitea-Recipient-Address": recipient.Email, - "X-Gitea-Repository": repo.Name, - "X-Gitea-Repository-Path": repo.FullName(), - "X-Gitea-Repository-Link": repo.HTMLURL(), - "X-Gitea-Issue-ID": strconv.FormatInt(ctx.Issue.Index, 10), - "X-Gitea-Issue-Link": ctx.Issue.HTMLURL(), - - "X-GitHub-Reason": reason, - "X-GitHub-Sender": ctx.Doer.Name, - "X-GitHub-Recipient": recipient.Name, - "X-GitHub-Recipient-Address": recipient.Email, - - "X-GitLab-NotificationReason": reason, - "X-GitLab-Project": repo.Name, - "X-GitLab-Project-Path": repo.FullName(), - "X-GitLab-Issue-IID": strconv.FormatInt(ctx.Issue.Index, 10), - } -} - func sanitizeSubject(subject string) string { runes := []rune(strings.TrimSpace(subjectRemoveSpaces.ReplaceAllLiteralString(subject, " "))) if len(runes) > mailMaxSubjectRunes { @@ -432,107 +44,6 @@ func sanitizeSubject(subject string) string { return mime.QEncoding.Encode("utf-8", string(runes)) } -// SendIssueAssignedMail composes and sends issue assigned email -func SendIssueAssignedMail(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string, comment *issues_model.Comment, recipients []*user_model.User) error { - if setting.MailService == nil { - // No mail service configured - return nil - } - - if err := issue.LoadRepo(ctx); err != nil { - log.Error("Unable to load repo [%d] for issue #%d [%d]. Error: %v", issue.RepoID, issue.Index, issue.ID, err) - return err - } - - langMap := make(map[string][]*user_model.User) - for _, user := range recipients { - if !user.IsActive { - // don't send emails to inactive users - continue - } - langMap[user.Language] = append(langMap[user.Language], user) - } - - for lang, tos := range langMap { - msgs, err := composeIssueCommentMessages(&mailCommentContext{ - Context: ctx, - Issue: issue, - Doer: doer, - ActionType: activities_model.ActionType(0), - Content: content, - Comment: comment, - }, lang, tos, false, "issue assigned") - if err != nil { - return err - } - SendAsync(msgs...) - } - return nil -} - -// actionToTemplate returns the type and name of the action facing the user -// (slightly different from activities_model.ActionType) and the name of the template to use (based on availability) -func actionToTemplate(issue *issues_model.Issue, actionType activities_model.ActionType, - commentType issues_model.CommentType, reviewType issues_model.ReviewType, -) (typeName, name, template string) { - if issue.IsPull { - typeName = "pull" - } else { - typeName = "issue" - } - switch actionType { - case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest: - name = "new" - case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: - name = "comment" - case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: - name = "close" - case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: - name = "reopen" - case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: - name = "merge" - case activities_model.ActionPullReviewDismissed: - name = "review_dismissed" - case activities_model.ActionPullRequestReadyForReview: - name = "ready_for_review" - default: - switch commentType { - case issues_model.CommentTypeReview: - switch reviewType { - case issues_model.ReviewTypeApprove: - name = "approve" - case issues_model.ReviewTypeReject: - name = "reject" - default: - name = "review" - } - case issues_model.CommentTypeCode: - name = "code" - case issues_model.CommentTypeAssignees: - name = "assigned" - case issues_model.CommentTypePullRequestPush: - name = "push" - default: - name = "default" - } - } - - template = typeName + "/" + name - ok := bodyTemplates.Lookup(template) != nil - if !ok && typeName != "issue" { - template = "issue/" + name - ok = bodyTemplates.Lookup(template) != nil - } - if !ok { - template = typeName + "/default" - ok = bodyTemplates.Lookup(template) != nil - } - if !ok { - template = "issue/default" - } - return typeName, name, template -} - func fromDisplayName(u *user_model.User) string { if setting.MailService.FromDisplayNameFormatTemplate != nil { var ctx bytes.Buffer diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index e269b1ca1e..8582185fd2 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -18,24 +18,7 @@ import ( "code.gitea.io/gitea/modules/setting" ) -func fallbackMailSubject(issue *issues_model.Issue) string { - return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index) -} - -type mailCommentContext struct { - context.Context - Issue *issues_model.Issue - Doer *user_model.User - ActionType activities_model.ActionType - Content string - Comment *issues_model.Comment - ForceDoerNotification bool -} - -const ( - // MailBatchSize set the batch size used in mailIssueCommentBatch - MailBatchSize = 100 -) +const MailBatchSize = 100 // batch size used in mailIssueCommentBatch // mailIssueCommentToParticipants can be used for both new issue creation and comment. // This function sends two list of emails: @@ -199,3 +182,41 @@ func MailParticipants(ctx context.Context, issue *issues_model.Issue, doer *user } return nil } + +// SendIssueAssignedMail composes and sends issue assigned email +func SendIssueAssignedMail(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, content string, comment *issues_model.Comment, recipients []*user_model.User) error { + if setting.MailService == nil { + // No mail service configured + return nil + } + + if err := issue.LoadRepo(ctx); err != nil { + log.Error("Unable to load repo [%d] for issue #%d [%d]. Error: %v", issue.RepoID, issue.Index, issue.ID, err) + return err + } + + langMap := make(map[string][]*user_model.User) + for _, user := range recipients { + if !user.IsActive { + // don't send emails to inactive users + continue + } + langMap[user.Language] = append(langMap[user.Language], user) + } + + for lang, tos := range langMap { + msgs, err := composeIssueCommentMessages(&mailCommentContext{ + Context: ctx, + Issue: issue, + Doer: doer, + ActionType: activities_model.ActionType(0), + Content: content, + Comment: comment, + }, lang, tos, false, "issue assigned") + if err != nil { + return err + } + SendAsync(msgs...) + } + return nil +} diff --git a/services/mailer/mail_issue_common.go b/services/mailer/mail_issue_common.go new file mode 100644 index 0000000000..23ca4c3f15 --- /dev/null +++ b/services/mailer/mail_issue_common.go @@ -0,0 +1,325 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package mailer + +import ( + "bytes" + "context" + "fmt" + "strconv" + "strings" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/renderhelper" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/emoji" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup/markdown" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload" + sender_service "code.gitea.io/gitea/services/mailer/sender" + "code.gitea.io/gitea/services/mailer/token" +) + +func fallbackMailSubject(issue *issues_model.Issue) string { + return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index) +} + +type mailCommentContext struct { + context.Context + Issue *issues_model.Issue + Doer *user_model.User + ActionType activities_model.ActionType + Content string + Comment *issues_model.Comment + ForceDoerNotification bool +} + +func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipients []*user_model.User, fromMention bool, info string) ([]*sender_service.Message, error) { + var ( + subject string + link string + prefix string + // Fall back subject for bad templates, make sure subject is never empty + fallback string + reviewComments []*issues_model.Comment + ) + + commentType := issues_model.CommentTypeComment + if ctx.Comment != nil { + commentType = ctx.Comment.Type + link = ctx.Issue.HTMLURL() + "#" + ctx.Comment.HashTag() + } else { + link = ctx.Issue.HTMLURL() + } + + reviewType := issues_model.ReviewTypeComment + if ctx.Comment != nil && ctx.Comment.Review != nil { + reviewType = ctx.Comment.Review.Type + } + + // This is the body of the new issue or comment, not the mail body + rctx := renderhelper.NewRenderContextRepoComment(ctx.Context, ctx.Issue.Repo).WithUseAbsoluteLink(true) + body, err := markdown.RenderString(rctx, + ctx.Content) + if err != nil { + return nil, err + } + + actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType) + + if actName != "new" { + prefix = "Re: " + } + fallback = prefix + fallbackMailSubject(ctx.Issue) + + if ctx.Comment != nil && ctx.Comment.Review != nil { + reviewComments = make([]*issues_model.Comment, 0, 10) + for _, lines := range ctx.Comment.Review.CodeComments { + for _, comments := range lines { + reviewComments = append(reviewComments, comments...) + } + } + } + locale := translation.NewLocale(lang) + + mailMeta := map[string]any{ + "locale": locale, + "FallbackSubject": fallback, + "Body": body, + "Link": link, + "Issue": ctx.Issue, + "Comment": ctx.Comment, + "IsPull": ctx.Issue.IsPull, + "User": ctx.Issue.Repo.MustOwner(ctx), + "Repo": ctx.Issue.Repo.FullName(), + "Doer": ctx.Doer, + "IsMention": fromMention, + "SubjectPrefix": prefix, + "ActionType": actType, + "ActionName": actName, + "ReviewComments": reviewComments, + "Language": locale.Language(), + "CanReply": setting.IncomingEmail.Enabled && commentType != issues_model.CommentTypePullRequestPush, + } + + var mailSubject bytes.Buffer + if err := subjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil { + subject = sanitizeSubject(mailSubject.String()) + if subject == "" { + subject = fallback + } + } else { + log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err) + } + + subject = emoji.ReplaceAliases(subject) + + mailMeta["Subject"] = subject + + var mailBody bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&mailBody, tplName, mailMeta); err != nil { + log.Error("ExecuteTemplate [%s]: %v", tplName+"/body", err) + } + + // Make sure to compose independent messages to avoid leaking user emails + msgID := generateMessageIDForIssue(ctx.Issue, ctx.Comment, ctx.ActionType) + reference := generateMessageIDForIssue(ctx.Issue, nil, activities_model.ActionType(0)) + + var replyPayload []byte + if ctx.Comment != nil { + if ctx.Comment.Type.HasMailReplySupport() { + replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Comment) + } + } else { + replyPayload, err = incoming_payload.CreateReferencePayload(ctx.Issue) + } + if err != nil { + return nil, err + } + + unsubscribePayload, err := incoming_payload.CreateReferencePayload(ctx.Issue) + if err != nil { + return nil, err + } + + msgs := make([]*sender_service.Message, 0, len(recipients)) + for _, recipient := range recipients { + msg := sender_service.NewMessageFrom( + recipient.Email, + fromDisplayName(ctx.Doer), + setting.MailService.FromEmail, + subject, + mailBody.String(), + ) + msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) + + msg.SetHeader("Message-ID", msgID) + msg.SetHeader("In-Reply-To", reference) + + references := []string{reference} + listUnsubscribe := []string{"<" + ctx.Issue.HTMLURL() + ">"} + + if setting.IncomingEmail.Enabled { + if replyPayload != nil { + token, err := token.CreateToken(token.ReplyHandlerType, recipient, replyPayload) + if err != nil { + log.Error("CreateToken failed: %v", err) + } else { + replyAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1) + msg.ReplyTo = replyAddress + msg.SetHeader("List-Post", fmt.Sprintf("", replyAddress)) + + references = append(references, fmt.Sprintf("", token, setting.Domain)) + } + } + + token, err := token.CreateToken(token.UnsubscribeHandlerType, recipient, unsubscribePayload) + if err != nil { + log.Error("CreateToken failed: %v", err) + } else { + unsubAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1) + listUnsubscribe = append(listUnsubscribe, "") + } + } + + msg.SetHeader("References", references...) + msg.SetHeader("List-Unsubscribe", listUnsubscribe...) + + for key, value := range generateAdditionalHeaders(ctx, actType, recipient) { + msg.SetHeader(key, value) + } + + msgs = append(msgs, msg) + } + + return msgs, nil +} + +// actionToTemplate returns the type and name of the action facing the user +// (slightly different from activities_model.ActionType) and the name of the template to use (based on availability) +func actionToTemplate(issue *issues_model.Issue, actionType activities_model.ActionType, + commentType issues_model.CommentType, reviewType issues_model.ReviewType, +) (typeName, name, template string) { + if issue.IsPull { + typeName = "pull" + } else { + typeName = "issue" + } + switch actionType { + case activities_model.ActionCreateIssue, activities_model.ActionCreatePullRequest: + name = "new" + case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: + name = "comment" + case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: + name = "close" + case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: + name = "reopen" + case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: + name = "merge" + case activities_model.ActionPullReviewDismissed: + name = "review_dismissed" + case activities_model.ActionPullRequestReadyForReview: + name = "ready_for_review" + default: + switch commentType { + case issues_model.CommentTypeReview: + switch reviewType { + case issues_model.ReviewTypeApprove: + name = "approve" + case issues_model.ReviewTypeReject: + name = "reject" + default: + name = "review" + } + case issues_model.CommentTypeCode: + name = "code" + case issues_model.CommentTypeAssignees: + name = "assigned" + case issues_model.CommentTypePullRequestPush: + name = "push" + default: + name = "default" + } + } + + template = typeName + "/" + name + ok := bodyTemplates.Lookup(template) != nil + if !ok && typeName != "issue" { + template = "issue/" + name + ok = bodyTemplates.Lookup(template) != nil + } + if !ok { + template = typeName + "/default" + ok = bodyTemplates.Lookup(template) != nil + } + if !ok { + template = "issue/default" + } + return typeName, name, template +} + +func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string { + var path string + if issue.IsPull { + path = "pulls" + } else { + path = "issues" + } + + var extra string + if comment != nil { + extra = fmt.Sprintf("/comment/%d", comment.ID) + } else { + switch actionType { + case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: + extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6) + case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: + extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6) + case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: + extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6) + case activities_model.ActionPullRequestReadyForReview: + extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6) + } + } + + return fmt.Sprintf("<%s/%s/%d%s@%s>", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain) +} + +func generateAdditionalHeaders(ctx *mailCommentContext, reason string, recipient *user_model.User) map[string]string { + repo := ctx.Issue.Repo + + return map[string]string{ + // https://datatracker.ietf.org/doc/html/rfc2919 + "List-ID": fmt.Sprintf("%s <%s.%s.%s>", repo.FullName(), repo.Name, repo.OwnerName, setting.Domain), + + // https://datatracker.ietf.org/doc/html/rfc2369 + "List-Archive": fmt.Sprintf("<%s>", repo.HTMLURL()), + + "X-Mailer": "Gitea", + "X-Gitea-Reason": reason, + "X-Gitea-Sender": ctx.Doer.Name, + "X-Gitea-Recipient": recipient.Name, + "X-Gitea-Recipient-Address": recipient.Email, + "X-Gitea-Repository": repo.Name, + "X-Gitea-Repository-Path": repo.FullName(), + "X-Gitea-Repository-Link": repo.HTMLURL(), + "X-Gitea-Issue-ID": strconv.FormatInt(ctx.Issue.Index, 10), + "X-Gitea-Issue-Link": ctx.Issue.HTMLURL(), + + "X-GitHub-Reason": reason, + "X-GitHub-Sender": ctx.Doer.Name, + "X-GitHub-Recipient": recipient.Name, + "X-GitHub-Recipient-Address": recipient.Email, + + "X-GitLab-NotificationReason": reason, + "X-GitLab-Project": repo.Name, + "X-GitLab-Project-Path": repo.FullName(), + "X-GitLab-Issue-IID": strconv.FormatInt(ctx.Issue.Index, 10), + } +} diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 31316b0053..bfff73c39c 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -6,6 +6,7 @@ package mailer import ( "bytes" "context" + "fmt" "code.gitea.io/gitea/models/renderhelper" repo_model "code.gitea.io/gitea/models/repo" @@ -18,9 +19,11 @@ import ( sender_service "code.gitea.io/gitea/services/mailer/sender" ) -const ( - tplNewReleaseMail templates.TplName = "release" -) +const tplNewReleaseMail templates.TplName = "release" + +func generateMessageIDForRelease(release *repo_model.Release) string { + return fmt.Sprintf("<%s/releases/%d@%s>", release.Repo.FullName(), release.ID, setting.Domain) +} // MailNewRelease send new release notify to all repo watchers. func MailNewRelease(ctx context.Context, rel *repo_model.Release) { diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 5f80654bcd..b6b2d5ca07 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -12,10 +12,13 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" sender_service "code.gitea.io/gitea/services/mailer/sender" ) +const mailRepoTransferNotify templates.TplName = "notify/repo_transfer" + // SendRepoTransferNotifyMail triggers a notification e-mail when a pending repository transfer was created func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error { if setting.MailService == nil { diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go index 5ca44442f3..1fbade7e23 100644 --- a/services/mailer/mail_team_invite.go +++ b/services/mailer/mail_team_invite.go @@ -18,9 +18,7 @@ import ( sender_service "code.gitea.io/gitea/services/mailer/sender" ) -const ( - tplTeamInviteMail templates.TplName = "team_invite" -) +const tplTeamInviteMail templates.TplName = "team_invite" // MailTeamInvite sends team invites func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_model.Team, invite *org_model.TeamInvite) error { diff --git a/services/mailer/mail_user.go b/services/mailer/mail_user.go new file mode 100644 index 0000000000..5a200a5fa7 --- /dev/null +++ b/services/mailer/mail_user.go @@ -0,0 +1,161 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package mailer + +import ( + "bytes" + "fmt" + + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/translation" + sender_service "code.gitea.io/gitea/services/mailer/sender" +) + +const ( + mailAuthActivate templates.TplName = "auth/activate" + mailAuthActivateEmail templates.TplName = "auth/activate_email" + mailAuthResetPassword templates.TplName = "auth/reset_passwd" + mailAuthRegisterNotify templates.TplName = "auth/register_notify" + mailNotifyCollaborator templates.TplName = "notify/collaborator" +) + +// sendUserMail sends a mail to the user +func sendUserMail(language string, u *user_model.User, tpl templates.TplName, code, subject, info string) { + locale := translation.NewLocale(language) + data := map[string]any{ + "locale": locale, + "DisplayName": u.DisplayName(), + "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), + "ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, locale), + "Code": code, + "Language": locale.Language(), + } + + var content bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil { + log.Error("Template: %v", err) + return + } + + msg := sender_service.NewMessage(u.EmailTo(), subject, content.String()) + msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info) + + SendAsync(msg) +} + +// SendActivateAccountMail sends an activation mail to the user (new user registration) +func SendActivateAccountMail(locale translation.Locale, u *user_model.User) { + if setting.MailService == nil { + // No mail service configured + return + } + opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount} + sendUserMail(locale.Language(), u, mailAuthActivate, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.activate_account"), "activate account") +} + +// SendResetPasswordMail sends a password reset mail to the user +func SendResetPasswordMail(u *user_model.User) { + if setting.MailService == nil { + // No mail service configured + return + } + locale := translation.NewLocale(u.Language) + opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword} + sendUserMail(u.Language, u, mailAuthResetPassword, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.reset_password"), "recover account") +} + +// SendActivateEmailMail sends confirmation email to confirm new email address +func SendActivateEmailMail(u *user_model.User, email string) { + if setting.MailService == nil { + // No mail service configured + return + } + locale := translation.NewLocale(u.Language) + opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateEmail, NewEmail: email} + data := map[string]any{ + "locale": locale, + "DisplayName": u.DisplayName(), + "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale), + "Code": user_model.GenerateUserTimeLimitCode(opts, u), + "Email": email, + "Language": locale.Language(), + } + + var content bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil { + log.Error("Template: %v", err) + return + } + + msg := sender_service.NewMessage(email, locale.TrString("mail.activate_email"), content.String()) + msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) + + SendAsync(msg) +} + +// SendRegisterNotifyMail triggers a notify e-mail by admin created a account. +func SendRegisterNotifyMail(u *user_model.User) { + if setting.MailService == nil || !u.IsActive { + // No mail service configured OR user is inactive + return + } + locale := translation.NewLocale(u.Language) + + data := map[string]any{ + "locale": locale, + "DisplayName": u.DisplayName(), + "Username": u.Name, + "Language": locale.Language(), + } + + var content bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil { + log.Error("Template: %v", err) + return + } + + msg := sender_service.NewMessage(u.EmailTo(), locale.TrString("mail.register_notify", setting.AppName), content.String()) + msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) + + SendAsync(msg) +} + +// SendCollaboratorMail sends mail notification to new collaborator. +func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) { + if setting.MailService == nil || !u.IsActive { + // No mail service configured OR the user is inactive + return + } + locale := translation.NewLocale(u.Language) + repoName := repo.FullName() + + subject := locale.TrString("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName) + data := map[string]any{ + "locale": locale, + "Subject": subject, + "RepoName": repoName, + "Link": repo.HTMLURL(), + "Language": locale.Language(), + } + + var content bytes.Buffer + + if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil { + log.Error("Template: %v", err) + return + } + + msg := sender_service.NewMessage(u.EmailTo(), subject, content.String()) + msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID) + + SendAsync(msg) +} diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 11efc18163..b4ca1e41e0 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -488,7 +488,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR if pr.Head.CloneURL == "" || pr.Head.Ref == "" { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -518,7 +518,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR if !ok { // Set head information if pr.Head.SHA is available if pr.Head.SHA != "" { - _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("PR #%d in %s/%s unable to update-ref for pr HEAD: %v", pr.Number, g.repoOwner, g.repoName, err) } @@ -553,7 +553,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR fetchArg = git.BranchPrefix + fetchArg } - _, _, err = git.NewCommand(ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(ctx, &git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) // We need to continue here so that the Head.Ref is reset and we attempt to set the gitref for the PR @@ -577,7 +577,7 @@ func (g *RepositoryDumper) handlePullRequest(ctx context.Context, pr *base.PullR pr.Head.SHA = headSha } if pr.Head.SHA != "" { - _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.gitPath()}) + _, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) } diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index eb16d6cb42..b17cc3ce41 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -662,7 +662,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba fetchArg = git.BranchPrefix + fetchArg } - _, _, err = git.NewCommand(ctx, "fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand("fetch", "--no-tags").AddDashesAndList(remote, fetchArg).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) return head, nil @@ -681,7 +681,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba pr.Head.SHA = headSha } - _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { return "", err } @@ -698,13 +698,13 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba // The SHA is empty log.Warn("Empty reference, no pull head for PR #%d in %s/%s", pr.Number, g.repoOwner, g.repoName) } else { - _, _, err = git.NewCommand(ctx, "rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand("rev-list", "--quiet", "-1").AddDynamicArguments(pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { // Git update-ref remove bad references with a relative path log.Warn("Deprecated local head %s for PR #%d in %s/%s, removing %s", pr.Head.SHA, pr.Number, g.repoOwner, g.repoName, pr.GetGitRefName()) } else { // set head information - _, _, err = git.NewCommand(ctx, "update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) + _, _, err = git.NewCommand("update-ref", "--no-deref").AddDynamicArguments(pr.GetGitRefName(), pr.Head.SHA).RunStdString(ctx, &git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("unable to set %s as the local head for PR #%d from %s in %s/%s. Error: %v", pr.Head.SHA, pr.Number, pr.Head.Ref, g.repoOwner, g.repoName, err) } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 79356ddf5b..f52d4157c8 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -237,7 +237,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) baseRef := "master" assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName)) - err := git.NewCommand(git.DefaultContext, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()}) + err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) @@ -261,7 +261,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { // fromRepo branch1 // headRef := "branch1" - _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(headRef).RunStdString(&git.RunOpts{Dir: fromRepo.RepoPath()}) + _, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(headRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) @@ -285,7 +285,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, git.CloneWithArgs(git.DefaultContext, nil, fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{ Branch: headRef, })) - _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(&git.RunOpts{Dir: forkRepo.RepoPath()}) + _, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: forkRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644)) assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true)) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index 4f40913f67..57dde25b3b 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -40,13 +40,13 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error remoteName := m.GetRemoteName() repoPath := m.GetRepository(ctx).RepoPath() // Remove old remote - _, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("remote", "rm").AddDynamicArguments(remoteName).RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil && !git.IsRemoteNotExistError(err) { return err } - cmd := git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr) - _, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) + cmd := git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr) + _, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil && !git.IsRemoteNotExistError(err) { return err } @@ -55,13 +55,13 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error wikiPath := m.Repo.WikiPath() wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr) // Remove old remote of wiki - _, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: wikiPath}) + _, _, err = git.NewCommand("remote", "rm").AddDynamicArguments(remoteName).RunStdString(ctx, &git.RunOpts{Dir: wikiPath}) if err != nil && !git.IsRemoteNotExistError(err) { return err } - cmd = git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath) - _, _, err = cmd.RunStdString(&git.RunOpts{Dir: wikiPath}) + cmd = git.NewCommand("remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath) + _, _, err = cmd.RunStdString(ctx, &git.RunOpts{Dir: wikiPath}) if err != nil && !git.IsRemoteNotExistError(err) { return err } @@ -208,8 +208,8 @@ func pruneBrokenReferences(ctx context.Context, stderrBuilder.Reset() stdoutBuilder.Reset() - pruneErr := git.NewCommand(ctx, "remote", "prune").AddDynamicArguments(m.GetRemoteName()). - Run(&git.RunOpts{ + pruneErr := git.NewCommand("remote", "prune").AddDynamicArguments(m.GetRemoteName()). + Run(ctx, &git.RunOpts{ Timeout: timeout, Dir: repoPath, Stdout: stdoutBuilder, @@ -243,7 +243,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo log.Trace("SyncMirrors [repo: %-v]: running git remote update...", m.Repo) // use fetch but not remote update because git fetch support --tags but remote update doesn't - cmd := git.NewCommand(ctx, "fetch") + cmd := git.NewCommand("fetch") if m.EnablePrune { cmd.AddArguments("--prune") } @@ -259,7 +259,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder := strings.Builder{} stderrBuilder := strings.Builder{} - if err := cmd.Run(&git.RunOpts{ + if err := cmd.Run(ctx, &git.RunOpts{ Timeout: timeout, Dir: repoPath, Env: envs, @@ -284,7 +284,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo // Successful prune - reattempt mirror stderrBuilder.Reset() stdoutBuilder.Reset() - if err = cmd.Run(&git.RunOpts{ + if err = cmd.Run(ctx, &git.RunOpts{ Timeout: timeout, Dir: repoPath, Stdout: &stdoutBuilder, @@ -352,8 +352,8 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo log.Trace("SyncMirrors [repo: %-v Wiki]: running git remote update...", m.Repo) stderrBuilder.Reset() stdoutBuilder.Reset() - if err := git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). - Run(&git.RunOpts{ + if err := git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). + Run(ctx, &git.RunOpts{ Timeout: timeout, Dir: wikiPath, Stdout: &stdoutBuilder, @@ -378,8 +378,8 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stderrBuilder.Reset() stdoutBuilder.Reset() - if err = git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). - Run(&git.RunOpts{ + if err = git.NewCommand("remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). + Run(ctx, &git.RunOpts{ Timeout: timeout, Dir: wikiPath, Stdout: &stdoutBuilder, diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index 89ebda60f2..6e72876893 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -30,14 +30,14 @@ var stripExitStatus = regexp.MustCompile(`exit status \d+ - `) // AddPushMirrorRemote registers the push mirror remote. func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) error { addRemoteAndConfig := func(addr, path string) error { - cmd := git.NewCommand(ctx, "remote", "add", "--mirror=push").AddDynamicArguments(m.RemoteName, addr) - if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: path}); err != nil { + cmd := git.NewCommand("remote", "add", "--mirror=push").AddDynamicArguments(m.RemoteName, addr) + if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil { return err } - if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { + if _, _, err := git.NewCommand("config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil { return err } - if _, _, err := git.NewCommand(ctx, "config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { + if _, _, err := git.NewCommand("config", "--add").AddDynamicArguments("remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(ctx, &git.RunOpts{Dir: path}); err != nil { return err } return nil @@ -61,15 +61,15 @@ func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr str // RemovePushMirrorRemote removes the push mirror remote. func RemovePushMirrorRemote(ctx context.Context, m *repo_model.PushMirror) error { - cmd := git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(m.RemoteName) + cmd := git.NewCommand("remote", "rm").AddDynamicArguments(m.RemoteName) _ = m.GetRepository(ctx) - if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: m.Repo.RepoPath()}); err != nil { + if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: m.Repo.RepoPath()}); err != nil { return err } if m.Repo.HasWiki() { - if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: m.Repo.WikiPath()}); err != nil { + if _, _, err := cmd.RunStdString(ctx, &git.RunOpts{Dir: m.Repo.WikiPath()}); err != nil { // The wiki remote may not exist log.Warn("Wiki Remote[%d] could not be removed: %v", m.ID, err) } diff --git a/services/packages/package_update.go b/services/packages/package_update.go index 8d851fac53..4a22ee7a62 100644 --- a/services/packages/package_update.go +++ b/services/packages/package_update.go @@ -44,16 +44,17 @@ func UnlinkFromRepository(ctx context.Context, pkg *packages_model.Package, doer } repo, err := repo_model.GetRepositoryByID(ctx, pkg.RepoID) - if err != nil { + if err != nil && !repo_model.IsErrRepoNotExist(err) { return fmt.Errorf("error getting repository %d: %w", pkg.RepoID, err) } - - perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) - if err != nil { - return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) - } - if !perms.CanWrite(unit.TypePackages) { - return util.ErrPermissionDenied + if err == nil { + perms, err := access_model.GetUserRepoPermission(ctx, repo, doer) + if err != nil { + return fmt.Errorf("error getting permissions for user %d on repository %d: %w", doer.ID, repo.ID, err) + } + if !perms.CanWrite(unit.TypePackages) { + return util.ErrPermissionDenied + } } user, err := user_model.GetUserByID(ctx, pkg.OwnerID) diff --git a/services/pull/check.go b/services/pull/check.go index e1adc3ca3b..9b159891d7 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -206,9 +206,9 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com prHeadRef := pr.GetGitRefName() // Check if the pull request is merged into BaseBranch - if _, _, err := git.NewCommand(ctx, "merge-base", "--is-ancestor"). + if _, _, err := git.NewCommand("merge-base", "--is-ancestor"). AddDynamicArguments(prHeadRef, pr.BaseBranch). - RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}); err != nil { + RunStdString(ctx, &git.RunOpts{Dir: pr.BaseRepo.RepoPath()}); err != nil { if strings.Contains(err.Error(), "exit status 1") { // prHeadRef is not an ancestor of the base branch return nil, nil @@ -234,9 +234,9 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) // Get the commit from BaseBranch where the pull request got merged - mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse"). - AddDynamicArguments(prHeadCommitID + ".." + pr.BaseBranch). - RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) + mergeCommit, _, err := git.NewCommand("rev-list", "--ancestry-path", "--merges", "--reverse"). + AddDynamicArguments(prHeadCommitID+".."+pr.BaseBranch). + RunStdString(ctx, &git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %w", err) } else if len(mergeCommit) < objectFormat.FullLength() { diff --git a/services/pull/merge.go b/services/pull/merge.go index 9c909ef795..665909e99a 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -356,12 +356,12 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use ) mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger)) - pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch) + pushCmd := git.NewCommand("push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch) // Push back to upstream. // This cause an api call to "/api/internal/hook/post-receive/...", // If it's merge, all db transaction and operations should be there but not here to prevent deadlock. - if err := pushCmd.Run(mergeCtx.RunOpts()); err != nil { + if err := pushCmd.Run(ctx, mergeCtx.RunOpts()); err != nil { if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") { return "", &git.ErrPushOutOfDate{ StdOut: mergeCtx.outbuf.String(), @@ -386,13 +386,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use } func commitAndSignNoAuthor(ctx *mergeContext, message string) error { - cmdCommit := git.NewCommand(ctx, "commit").AddOptionFormat("--message=%s", message) + cmdCommit := git.NewCommand("commit").AddOptionFormat("--message=%s", message) if ctx.signKeyID == "" { cmdCommit.AddArguments("--no-gpg-sign") } else { cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) } - if err := cmdCommit.Run(ctx.RunOpts()); err != nil { + if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -453,7 +453,7 @@ func (err ErrMergeDivergingFastForwardOnly) Error() string { } func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *git.Command) error { - if err := cmd.Run(ctx.RunOpts()); err != nil { + if err := cmd.Run(ctx, ctx.RunOpts()); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { // We have a merge conflict error diff --git a/services/pull/merge_ff_only.go b/services/pull/merge_ff_only.go index f57c732104..6c3a68b95b 100644 --- a/services/pull/merge_ff_only.go +++ b/services/pull/merge_ff_only.go @@ -11,7 +11,7 @@ import ( // doMergeStyleFastForwardOnly merges the tracking into the current HEAD - which is assumed to be staging branch (equal to the pr.BaseBranch) func doMergeStyleFastForwardOnly(ctx *mergeContext) error { - cmd := git.NewCommand(ctx, "merge", "--ff-only").AddDynamicArguments(trackingBranch) + cmd := git.NewCommand("merge", "--ff-only").AddDynamicArguments(trackingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleFastForwardOnly, cmd); err != nil { log.Error("%-v Unable to merge tracking into base: %v", ctx.pr, err) return err diff --git a/services/pull/merge_merge.go b/services/pull/merge_merge.go index bf56c071db..118d21c7cd 100644 --- a/services/pull/merge_merge.go +++ b/services/pull/merge_merge.go @@ -11,7 +11,7 @@ import ( // doMergeStyleMerge merges the tracking branch into the current HEAD - which is assumed to be the staging branch (equal to the pr.BaseBranch) func doMergeStyleMerge(ctx *mergeContext, message string) error { - cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) + cmd := git.NewCommand("merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleMerge, cmd); err != nil { log.Error("%-v Unable to merge tracking into base: %v", ctx.pr, err) return err diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 2e1cc8cf85..593cba550a 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -73,7 +73,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } if expectedHeadCommitID != "" { - trackingCommitID, _, err := git.NewCommand(ctx, "show-ref", "--hash").AddDynamicArguments(git.BranchPrefix + trackingBranch).RunStdString(&git.RunOpts{Dir: mergeCtx.tmpBasePath}) + trackingCommitID, _, err := git.NewCommand("show-ref", "--hash").AddDynamicArguments(git.BranchPrefix+trackingBranch).RunStdString(ctx, &git.RunOpts{Dir: mergeCtx.tmpBasePath}) if err != nil { defer cancel() log.Error("failed to get sha of head branch in %-v: show-ref[%s] --hash refs/heads/tracking: %v", mergeCtx.pr, mergeCtx.tmpBasePath, err) @@ -151,8 +151,8 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } setConfig := func(key, value string) error { - if err := git.NewCommand(ctx, "config", "--local").AddDynamicArguments(key, value). - Run(ctx.RunOpts()); err != nil { + if err := git.NewCommand("config", "--local").AddDynamicArguments(key, value). + Run(ctx, ctx.RunOpts()); err != nil { log.Error("git config [%s -> %q]: %v\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git config [%s -> %q]: %w\n%s\n%s", key, value, err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -184,8 +184,8 @@ func prepareTemporaryRepoForMerge(ctx *mergeContext) error { } // Read base branch index - if err := git.NewCommand(ctx, "read-tree", "HEAD"). - Run(ctx.RunOpts()); err != nil { + if err := git.NewCommand("read-tree", "HEAD"). + Run(ctx, ctx.RunOpts()); err != nil { log.Error("git read-tree HEAD: %v\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("Unable to read base branch in to the index: %w\n%s\n%s", err, ctx.outbuf.String(), ctx.errbuf.String()) } @@ -221,8 +221,8 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o return 0, nil, nil } - err = git.NewCommand(ctx, "diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root").AddDynamicArguments(baseBranch, headBranch). - Run(&git.RunOpts{ + err = git.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-r", "-z", "--root").AddDynamicArguments(baseBranch, headBranch). + Run(ctx, &git.RunOpts{ Dir: repoPath, Stdout: diffOutWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { @@ -272,16 +272,16 @@ func (err ErrRebaseConflicts) Error() string { // if there is a conflict it will return an ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { // Checkout head branch - if err := git.NewCommand(ctx, "checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). - Run(ctx.RunOpts()); err != nil { + if err := git.NewCommand("checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). + Run(ctx, ctx.RunOpts()); err != nil { return fmt.Errorf("unable to git checkout tracking as staging in temp repo for %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } ctx.outbuf.Reset() ctx.errbuf.Reset() // Rebase before merging - if err := git.NewCommand(ctx, "rebase").AddDynamicArguments(baseBranch). - Run(ctx.RunOpts()); err != nil { + if err := git.NewCommand("rebase").AddDynamicArguments(baseBranch). + Run(ctx, ctx.RunOpts()); err != nil { // Rebase will leave a REBASE_HEAD file in .git if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil { var commitSha string diff --git a/services/pull/merge_rebase.go b/services/pull/merge_rebase.go index ecf376220e..dd7c8761f0 100644 --- a/services/pull/merge_rebase.go +++ b/services/pull/merge_rebase.go @@ -16,7 +16,7 @@ import ( // getRebaseAmendMessage composes the message to amend commits in rebase merge of a pull request. func getRebaseAmendMessage(ctx *mergeContext, baseGitRepo *git.Repository) (message string, err error) { // Get existing commit message. - commitMessage, _, err := git.NewCommand(ctx, "show", "--format=%B", "-s").RunStdString(&git.RunOpts{Dir: ctx.tmpBasePath}) + commitMessage, _, err := git.NewCommand("show", "--format=%B", "-s").RunStdString(ctx, &git.RunOpts{Dir: ctx.tmpBasePath}) if err != nil { return "", err } @@ -42,7 +42,7 @@ func doMergeRebaseFastForward(ctx *mergeContext) error { return fmt.Errorf("Failed to get full commit id for HEAD: %w", err) } - cmd := git.NewCommand(ctx, "merge", "--ff-only").AddDynamicArguments(stagingBranch) + cmd := git.NewCommand("merge", "--ff-only").AddDynamicArguments(stagingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleRebase, cmd); err != nil { log.Error("Unable to merge staging into base: %v", err) return err @@ -73,7 +73,7 @@ func doMergeRebaseFastForward(ctx *mergeContext) error { } if newMessage != "" { - if err := git.NewCommand(ctx, "commit", "--amend").AddOptionFormat("--message=%s", newMessage).Run(&git.RunOpts{Dir: ctx.tmpBasePath}); err != nil { + if err := git.NewCommand("commit", "--amend").AddOptionFormat("--message=%s", newMessage).Run(ctx, &git.RunOpts{Dir: ctx.tmpBasePath}); err != nil { log.Error("Unable to amend commit message: %v", err) return err } @@ -84,7 +84,7 @@ func doMergeRebaseFastForward(ctx *mergeContext) error { // Perform rebase merge with merge commit. func doMergeRebaseMergeCommit(ctx *mergeContext, message string) error { - cmd := git.NewCommand(ctx, "merge").AddArguments("--no-ff", "--no-commit").AddDynamicArguments(stagingBranch) + cmd := git.NewCommand("merge").AddArguments("--no-ff", "--no-commit").AddDynamicArguments(stagingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleRebaseMerge, cmd); err != nil { log.Error("Unable to merge staging into base: %v", err) @@ -105,8 +105,8 @@ func doMergeStyleRebase(ctx *mergeContext, mergeStyle repo_model.MergeStyle, mes } // Checkout base branch again - if err := git.NewCommand(ctx, "checkout").AddDynamicArguments(baseBranch). - Run(ctx.RunOpts()); err != nil { + if err := git.NewCommand("checkout").AddDynamicArguments(baseBranch). + Run(ctx, ctx.RunOpts()); err != nil { log.Error("git checkout base prior to merge post staging rebase %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git checkout base prior to merge post staging rebase %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 7258671888..076189fd7a 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -58,7 +58,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { return fmt.Errorf("getAuthorSignatureSquash: %w", err) } - cmdMerge := git.NewCommand(ctx, "merge", "--squash").AddDynamicArguments(trackingBranch) + cmdMerge := git.NewCommand("merge", "--squash").AddDynamicArguments(trackingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleSquash, cmdMerge); err != nil { log.Error("%-v Unable to merge --squash tracking into base: %v", ctx.pr, err) return err @@ -71,7 +71,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String()) } - cmdCommit := git.NewCommand(ctx, "commit"). + cmdCommit := git.NewCommand("commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). AddOptionFormat("--message=%s", message) if ctx.signKeyID == "" { @@ -79,7 +79,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } else { cmdCommit.AddOptionFormat("-S%s", ctx.signKeyID) } - if err := cmdCommit.Run(ctx.RunOpts()); err != nil { + if err := cmdCommit.Run(ctx, ctx.RunOpts()); err != nil { log.Error("git commit %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %w\n%s\n%s", ctx.pr.HeadRepo.FullName(), ctx.pr.HeadBranch, ctx.pr.BaseRepo.FullName(), ctx.pr.BaseBranch, err, ctx.outbuf.String(), ctx.errbuf.String()) } diff --git a/services/pull/patch.go b/services/pull/patch.go index 13623d73c6..29f2f992ab 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -92,7 +92,7 @@ func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullReque defer gitRepo.Close() // 1. update merge base - pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base", "--", "base", "tracking").RunStdString(&git.RunOpts{Dir: prCtx.tmpBasePath}) + pr.MergeBase, _, err = git.NewCommand("merge-base", "--", "base", "tracking").RunStdString(ctx, &git.RunOpts{Dir: prCtx.tmpBasePath}) if err != nil { var err2 error pr.MergeBase, err2 = gitRepo.GetRefCommitID(git.BranchPrefix + "base") @@ -192,7 +192,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f } // Need to get the objects from the object db to attempt to merge - root, _, err := git.NewCommand(ctx, "unpack-file").AddDynamicArguments(file.stage1.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) + root, _, err := git.NewCommand("unpack-file").AddDynamicArguments(file.stage1.sha).RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get root object: %s at path: %s for merging. Error: %w", file.stage1.sha, file.stage1.path, err) } @@ -201,7 +201,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f _ = util.Remove(filepath.Join(tmpBasePath, root)) }() - base, _, err := git.NewCommand(ctx, "unpack-file").AddDynamicArguments(file.stage2.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) + base, _, err := git.NewCommand("unpack-file").AddDynamicArguments(file.stage2.sha).RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get base object: %s at path: %s for merging. Error: %w", file.stage2.sha, file.stage2.path, err) } @@ -209,7 +209,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f defer func() { _ = util.Remove(base) }() - head, _, err := git.NewCommand(ctx, "unpack-file").AddDynamicArguments(file.stage3.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) + head, _, err := git.NewCommand("unpack-file").AddDynamicArguments(file.stage3.sha).RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get head object:%s at path: %s for merging. Error: %w", file.stage3.sha, file.stage3.path, err) } @@ -219,13 +219,13 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, f }() // now git merge-file annoyingly takes a different order to the merge-tree ... - _, _, conflictErr := git.NewCommand(ctx, "merge-file").AddDynamicArguments(base, root, head).RunStdString(&git.RunOpts{Dir: tmpBasePath}) + _, _, conflictErr := git.NewCommand("merge-file").AddDynamicArguments(base, root, head).RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if conflictErr != nil { return &errMergeConflict{file.stage2.path} } // base now contains the merged data - hash, _, err := git.NewCommand(ctx, "hash-object", "-w", "--path").AddDynamicArguments(file.stage2.path, base).RunStdString(&git.RunOpts{Dir: tmpBasePath}) + hash, _, err := git.NewCommand("hash-object", "-w", "--path").AddDynamicArguments(file.stage2.path, base).RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { return err } @@ -250,7 +250,7 @@ func AttemptThreeWayMerge(ctx context.Context, gitPath string, gitRepo *git.Repo defer cancel() // First we use read-tree to do a simple three-way merge - if _, _, err := git.NewCommand(ctx, "read-tree", "-m").AddDynamicArguments(base, ours, theirs).RunStdString(&git.RunOpts{Dir: gitPath}); err != nil { + if _, _, err := git.NewCommand("read-tree", "-m").AddDynamicArguments(base, ours, theirs).RunStdString(ctx, &git.RunOpts{Dir: gitPath}); err != nil { log.Error("Unable to run read-tree -m! Error: %v", err) return false, nil, fmt.Errorf("unable to run read-tree -m! Error: %w", err) } @@ -324,9 +324,9 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * // No conflicts detected so we need to check if the patch is empty... // a. Write the newly merged tree and check the new tree-hash var treeHash string - treeHash, _, err = git.NewCommand(ctx, "write-tree").RunStdString(&git.RunOpts{Dir: tmpBasePath}) + treeHash, _, err = git.NewCommand("write-tree").RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { - lsfiles, _, _ := git.NewCommand(ctx, "ls-files", "-u").RunStdString(&git.RunOpts{Dir: tmpBasePath}) + lsfiles, _, _ := git.NewCommand("ls-files", "-u").RunStdString(ctx, &git.RunOpts{Dir: tmpBasePath}) return false, fmt.Errorf("unable to write unconflicted tree: %w\n`git ls-files -u`:\n%s", err, lsfiles) } treeHash = strings.TrimSpace(treeHash) @@ -387,7 +387,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath) // 4. Read the base branch in to the index of the temporary repository - _, _, err = git.NewCommand(gitRepo.Ctx, "read-tree", "base").RunStdString(&git.RunOpts{Dir: tmpBasePath}) + _, _, err = git.NewCommand("read-tree", "base").RunStdString(gitRepo.Ctx, &git.RunOpts{Dir: tmpBasePath}) if err != nil { return false, fmt.Errorf("git read-tree %s: %w", pr.BaseBranch, err) } @@ -400,7 +400,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * prConfig := prUnit.PullRequestsConfig() // 6. Prepare the arguments to apply the patch against the index - cmdApply := git.NewCommand(gitRepo.Ctx, "apply", "--check", "--cached") + cmdApply := git.NewCommand("apply", "--check", "--cached") if prConfig.IgnoreWhitespaceConflicts { cmdApply.AddArguments("--ignore-whitespace") } @@ -431,7 +431,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * // 8. Run the check command conflict = false - err = cmdApply.Run(&git.RunOpts{ + err = cmdApply.Run(gitRepo.Ctx, &git.RunOpts{ Dir: tmpBasePath, Stderr: stderrWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { diff --git a/services/pull/patch_unmerged.go b/services/pull/patch_unmerged.go index c60c48d923..200d2233e9 100644 --- a/services/pull/patch_unmerged.go +++ b/services/pull/patch_unmerged.go @@ -72,8 +72,8 @@ func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan }() stderr := &strings.Builder{} - err = git.NewCommand(ctx, "ls-files", "-u", "-z"). - Run(&git.RunOpts{ + err = git.NewCommand("ls-files", "-u", "-z"). + Run(ctx, &git.RunOpts{ Dir: tmpBasePath, Stdout: lsFilesWriter, Stderr: stderr, diff --git a/services/pull/pull.go b/services/pull/pull.go index 5d3758eca6..1f06d6b182 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -407,11 +407,10 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, } if isSync { - requests := issues_model.PullRequestList(prs) - if err = requests.LoadAttributes(ctx); err != nil { + if err = prs.LoadAttributes(ctx); err != nil { log.Error("PullRequestList.LoadAttributes: %v", err) } - if invalidationErr := checkForInvalidation(ctx, requests, repoID, doer, branch); invalidationErr != nil { + if invalidationErr := checkForInvalidation(ctx, prs, repoID, doer, branch); invalidationErr != nil { log.Error("checkForInvalidation: %v", invalidationErr) } if err == nil { @@ -505,14 +504,14 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, return false, fmt.Errorf("GetMergeBase: %w", err) } - cmd := git.NewCommand(ctx, "diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) + cmd := git.NewCommand("diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return false, fmt.Errorf("unable to open pipe for to run diff: %w", err) } stderr := new(bytes.Buffer) - if err := cmd.Run(&git.RunOpts{ + if err := cmd.Run(ctx, &git.RunOpts{ Dir: prCtx.tmpBasePath, Stdout: stdoutWriter, Stderr: stderr, @@ -629,7 +628,7 @@ func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) { return err } - _, _, err = git.NewCommand(ctx, "update-ref").AddDynamicArguments(pr.GetGitRefName(), pr.HeadCommitID).RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) + _, _, err = git.NewCommand("update-ref").AddDynamicArguments(pr.GetGitRefName(), pr.HeadCommitID).RunStdString(ctx, &git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) if err != nil { log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err) } @@ -645,7 +644,7 @@ func retargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6 return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } @@ -672,11 +671,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } - issues_model.PullRequestList(prs).SetHeadRepo(repo) - if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { + prs.SetHeadRepo(repo) + if err := prs.LoadRepositories(ctx); err != nil { return err } @@ -707,11 +706,11 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User return err } - if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err := prs.LoadAttributes(ctx); err != nil { return err } - issues_model.PullRequestList(prs).SetBaseRepo(repo) - if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil { + prs.SetBaseRepo(repo) + if err := prs.LoadRepositories(ctx); err != nil { return err } @@ -744,7 +743,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re return err } - if err = issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil { + if err = prs.LoadAttributes(ctx); err != nil { return err } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index e5753178b8..911db85585 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -133,22 +133,22 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add base repository to temporary repo [%s -> tmpBasePath]: %w", pr.BaseRepo.FullName(), err) } - if err := git.NewCommand(ctx, "remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath). - Run(prCtx.RunOpts()); err != nil { + if err := git.NewCommand("remote", "add", "-t").AddDynamicArguments(pr.BaseBranch).AddArguments("-m").AddDynamicArguments(pr.BaseBranch).AddDynamicArguments("origin", baseRepoPath). + Run(ctx, prCtx.RunOpts()); err != nil { log.Error("%-v Unable to add base repository as origin [%s -> %s]: %v\n%s\n%s", pr, pr.BaseRepo.FullName(), tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String()) cancel() return nil, nil, fmt.Errorf("Unable to add base repository as origin [%s -> tmpBasePath]: %w\n%s\n%s", pr.BaseRepo.FullName(), err, prCtx.outbuf.String(), prCtx.errbuf.String()) } - if err := git.NewCommand(ctx, "fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). - Run(prCtx.RunOpts()); err != nil { + if err := git.NewCommand("fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). + Run(ctx, prCtx.RunOpts()); err != nil { log.Error("%-v Unable to fetch origin base branch [%s:%s -> base, original_base in %s]: %v:\n%s\n%s", pr, pr.BaseRepo.FullName(), pr.BaseBranch, tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String()) cancel() return nil, nil, fmt.Errorf("Unable to fetch origin base branch [%s:%s -> base, original_base in tmpBasePath]: %w\n%s\n%s", pr.BaseRepo.FullName(), pr.BaseBranch, err, prCtx.outbuf.String(), prCtx.errbuf.String()) } - if err := git.NewCommand(ctx, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch). - Run(prCtx.RunOpts()); err != nil { + if err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseBranch). + Run(ctx, prCtx.RunOpts()); err != nil { log.Error("%-v Unable to set HEAD as base branch in [%s]: %v\n%s\n%s", pr, tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String()) cancel() return nil, nil, fmt.Errorf("Unable to set HEAD as base branch in tmpBasePath: %w\n%s\n%s", err, prCtx.outbuf.String(), prCtx.errbuf.String()) @@ -160,8 +160,8 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) return nil, nil, fmt.Errorf("Unable to add head base repository to temporary repo [%s -> tmpBasePath]: %w", pr.HeadRepo.FullName(), err) } - if err := git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath). - Run(prCtx.RunOpts()); err != nil { + if err := git.NewCommand("remote", "add").AddDynamicArguments(remoteRepoName, headRepoPath). + Run(ctx, prCtx.RunOpts()); err != nil { log.Error("%-v Unable to add head repository as head_repo [%s -> %s]: %v\n%s\n%s", pr, pr.HeadRepo.FullName(), tmpBasePath, err, prCtx.outbuf.String(), prCtx.errbuf.String()) cancel() return nil, nil, fmt.Errorf("Unable to add head repository as head_repo [%s -> tmpBasePath]: %w\n%s\n%s", pr.HeadRepo.FullName(), err, prCtx.outbuf.String(), prCtx.errbuf.String()) @@ -178,8 +178,8 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } else { headBranch = pr.GetGitRefName() } - if err := git.NewCommand(ctx, "fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). - Run(prCtx.RunOpts()); err != nil { + if err := git.NewCommand("fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). + Run(ctx, prCtx.RunOpts()); err != nil { cancel() if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) { return nil, nil, git_model.ErrBranchNotExist{ diff --git a/services/pull/update_rebase.go b/services/pull/update_rebase.go index 3e2a7be132..9ff062f99c 100644 --- a/services/pull/update_rebase.go +++ b/services/pull/update_rebase.go @@ -27,7 +27,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques defer cancel() // Determine the old merge-base before the rebase - we use this for LFS push later on - oldMergeBase, _, _ := git.NewCommand(ctx, "merge-base").AddDashesAndList(baseBranch, trackingBranch).RunStdString(&git.RunOpts{Dir: mergeCtx.tmpBasePath}) + oldMergeBase, _, _ := git.NewCommand("merge-base").AddDashesAndList(baseBranch, trackingBranch).RunStdString(ctx, &git.RunOpts{Dir: mergeCtx.tmpBasePath}) oldMergeBase = strings.TrimSpace(oldMergeBase) // Rebase the tracking branch on to the base as the staging branch @@ -62,7 +62,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques headUser = pr.HeadRepo.Owner } - pushCmd := git.NewCommand(ctx, "push", "-f", "head_repo"). + pushCmd := git.NewCommand("push", "-f", "head_repo"). AddDynamicArguments(stagingBranch + ":" + git.BranchPrefix + pr.HeadBranch) // Push back to the head repository. @@ -71,7 +71,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques mergeCtx.outbuf.Reset() mergeCtx.errbuf.Reset() - if err := pushCmd.Run(&git.RunOpts{ + if err := pushCmd.Run(ctx, &git.RunOpts{ Env: repo_module.FullPushingEnvironment( headUser, doer, diff --git a/services/release/release.go b/services/release/release.go index 835a5943b1..d1dbb11ea1 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -296,10 +296,7 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo } for _, attach := range attachments { if attach.ReleaseID != rel.ID { - return util.SilentWrap{ - Message: "delete attachment of release permission denied", - Err: util.ErrPermissionDenied, - } + return util.NewPermissionDeniedErrorf("delete attachment of release permission denied") } deletedUUIDs.Add(attach.UUID) } @@ -321,10 +318,7 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo } for _, attach := range attachments { if attach.ReleaseID != rel.ID { - return util.SilentWrap{ - Message: "update attachment of release permission denied", - Err: util.ErrPermissionDenied, - } + return util.NewPermissionDeniedErrorf("update attachment of release permission denied") } } @@ -381,8 +375,8 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re } } - if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName). - RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { + if stdout, _, err := git.NewCommand("tag", "-d").AddDashesAndList(rel.TagName). + RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err) return fmt.Errorf("git tag -d: %w", err) } diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 0038d9525a..40a1327e5e 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -90,8 +90,8 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR return fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("CreateRepository(git update-server-info): %w", err) } diff --git a/services/repository/check.go b/services/repository/check.go index acca15daf2..b475fbc487 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -86,10 +86,10 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdA // GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Running git gc on %-v", repo) - command := git.NewCommand(ctx, "gc").AddArguments(args...) + command := git.NewCommand("gc").AddArguments(args...) var stdout string var err error - stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) + stdout, _, err = command.RunStdString(ctx, &git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) if err != nil { log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) diff --git a/services/repository/contributors_graph.go b/services/repository/contributors_graph.go index b0748f8ee3..a4ae505313 100644 --- a/services/repository/contributors_graph.go +++ b/services/repository/contributors_graph.go @@ -125,13 +125,13 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int _ = stdoutWriter.Close() }() - gitCmd := git.NewCommand(repo.Ctx, "log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse") + gitCmd := git.NewCommand("log", "--shortstat", "--no-merges", "--pretty=format:---%n%aN%n%aE%n%as", "--reverse") // AddOptionFormat("--max-count=%d", limit) gitCmd.AddDynamicArguments(baseCommit.ID.String()) var extendedCommitStats []*ExtendedCommitStats stderr := new(strings.Builder) - err = gitCmd.Run(&git.RunOpts{ + err = gitCmd.Run(repo.Ctx, &git.RunOpts{ Dir: repo.Path, Stdout: stdoutWriter, Stderr: stderr, diff --git a/services/repository/create.go b/services/repository/create.go index 23aacd6f95..58912bdcd2 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -67,8 +67,8 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, ) // Clone to temporary path and do the init commit. - if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir). - RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil { + if stdout, _, err := git.NewCommand("clone").AddDynamicArguments(repoPath, tmpDir). + RunStdString(ctx, &git.RunOpts{Dir: "", Env: env}); err != nil { log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git clone: %w", err) } @@ -299,8 +299,8 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt return fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) rollbackRepo = repo rollbackRepo.OwnerID = u.ID @@ -312,7 +312,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt if len(opts.License) > 0 { licenses = append(licenses, ConvertLicenseName(opts.License)) - stdout, _, err := git.NewCommand(ctx, "rev-parse", "HEAD").RunStdString(&git.RunOpts{Dir: repoPath}) + stdout, _, err := git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil { log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) rollbackRepo = repo diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 78c275f01c..4de70cb368 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -164,12 +164,12 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user stdout := &strings.Builder{} stderr := &strings.Builder{} - cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") + cmdApply := git.NewCommand("apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") if git.DefaultFeatures().CheckVersionAtLeast("2.32") { cmdApply.AddArguments("-3") } - if err := cmdApply.Run(&git.RunOpts{ + if err := cmdApply.Run(ctx, &git.RunOpts{ Dir: t.basePath, Stdout: stdout, Stderr: stderr, diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index cf1402397b..4c4a85c5b1 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -52,12 +52,12 @@ func (t *TemporaryUploadRepository) Close() { // Clone the base repository to our path and set branch as the HEAD func (t *TemporaryUploadRepository) Clone(branch string, bare bool) error { - cmd := git.NewCommand(t.ctx, "clone", "-s", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath) + cmd := git.NewCommand("clone", "-s", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath) if bare { cmd.AddArguments("--bare") } - if _, _, err := cmd.RunStdString(nil); err != nil { + if _, _, err := cmd.RunStdString(t.ctx, nil); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched { return git.ErrBranchNotExist{ @@ -96,7 +96,7 @@ func (t *TemporaryUploadRepository) Init(objectFormatName string) error { // SetDefaultIndex sets the git index to our HEAD func (t *TemporaryUploadRepository) SetDefaultIndex() error { - if _, _, err := git.NewCommand(t.ctx, "read-tree", "HEAD").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := git.NewCommand("read-tree", "HEAD").RunStdString(t.ctx, &git.RunOpts{Dir: t.basePath}); err != nil { return fmt.Errorf("SetDefaultIndex: %w", err) } return nil @@ -104,7 +104,7 @@ func (t *TemporaryUploadRepository) SetDefaultIndex() error { // RefreshIndex looks at the current index and checks to see if merges or updates are needed by checking stat() information. func (t *TemporaryUploadRepository) RefreshIndex() error { - if _, _, err := git.NewCommand(t.ctx, "update-index", "--refresh").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := git.NewCommand("update-index", "--refresh").RunStdString(t.ctx, &git.RunOpts{Dir: t.basePath}); err != nil { return fmt.Errorf("RefreshIndex: %w", err) } return nil @@ -115,8 +115,8 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) - if err := git.NewCommand(t.ctx, "ls-files", "-z").AddDashesAndList(filenames...). - Run(&git.RunOpts{ + if err := git.NewCommand("ls-files", "-z").AddDashesAndList(filenames...). + Run(t.ctx, &git.RunOpts{ Dir: t.basePath, Stdout: stdOut, Stderr: stdErr, @@ -151,8 +151,8 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) er } } - if err := git.NewCommand(t.ctx, "update-index", "--remove", "-z", "--index-info"). - Run(&git.RunOpts{ + if err := git.NewCommand("update-index", "--remove", "-z", "--index-info"). + Run(t.ctx, &git.RunOpts{ Dir: t.basePath, Stdin: stdIn, Stdout: stdOut, @@ -168,8 +168,8 @@ func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) - if err := git.NewCommand(t.ctx, "hash-object", "-w", "--stdin"). - Run(&git.RunOpts{ + if err := git.NewCommand("hash-object", "-w", "--stdin"). + Run(t.ctx, &git.RunOpts{ Dir: t.basePath, Stdin: content, Stdout: stdOut, @@ -184,7 +184,7 @@ func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error // AddObjectToIndex adds the provided object hash to the index with the provided mode and path func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPath string) error { - if _, _, err := git.NewCommand(t.ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, objectHash, objectPath).RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { + if _, _, err := git.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, objectHash, objectPath).RunStdString(t.ctx, &git.RunOpts{Dir: t.basePath}); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched { return ErrFilePathInvalid{ @@ -200,7 +200,7 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPat // WriteTree writes the current index as a tree to the object db and returns its hash func (t *TemporaryUploadRepository) WriteTree() (string, error) { - stdout, _, err := git.NewCommand(t.ctx, "write-tree").RunStdString(&git.RunOpts{Dir: t.basePath}) + stdout, _, err := git.NewCommand("write-tree").RunStdString(t.ctx, &git.RunOpts{Dir: t.basePath}) if err != nil { log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %w", t.repo.FullName(), err) @@ -218,7 +218,7 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro if ref == "" { ref = "HEAD" } - stdout, _, err := git.NewCommand(t.ctx, "rev-parse").AddDynamicArguments(ref).RunStdString(&git.RunOpts{Dir: t.basePath}) + stdout, _, err := git.NewCommand("rev-parse").AddDynamicArguments(ref).RunStdString(t.ctx, &git.RunOpts{Dir: t.basePath}) if err != nil { log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %w", ref, t.repo.FullName(), err) @@ -286,7 +286,7 @@ func (t *TemporaryUploadRepository) CommitTree(opts *CommitTreeUserOptions) (str _, _ = messageBytes.WriteString(opts.CommitMessage) _, _ = messageBytes.WriteString("\n") - cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(opts.TreeHash) + cmdCommitTree := git.NewCommand("commit-tree").AddDynamicArguments(opts.TreeHash) if opts.ParentCommitID != "" { cmdCommitTree.AddOptionValues("-p", opts.ParentCommitID) } @@ -333,7 +333,7 @@ func (t *TemporaryUploadRepository) CommitTree(opts *CommitTreeUserOptions) (str stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) if err := cmdCommitTree. - Run(&git.RunOpts{ + Run(t.ctx, &git.RunOpts{ Env: env, Dir: t.basePath, Stdin: messageBytes, @@ -385,8 +385,8 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) { }() stderr := new(bytes.Buffer) var diff *gitdiff.Diff - err = git.NewCommand(t.ctx, "diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD"). - Run(&git.RunOpts{ + err = git.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD"). + Run(t.ctx, &git.RunOpts{ Timeout: 30 * time.Second, Dir: t.basePath, Stdout: stdoutWriter, diff --git a/services/repository/fork.go b/services/repository/fork.go index 8d89c2b0b0..a79dc62ef1 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -152,13 +152,13 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork needsRollback = true - cloneCmd := git.NewCommand(txCtx, "clone", "--bare") + cloneCmd := git.NewCommand("clone", "--bare") if opts.SingleBranch != "" { cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) } repoPath := repo_model.RepoPath(owner.Name, repo.Name) if stdout, _, err := cloneCmd.AddDynamicArguments(oldRepoPath, repoPath). - RunStdBytes(&git.RunOpts{Timeout: 10 * time.Minute}); err != nil { + RunStdBytes(txCtx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) return fmt.Errorf("git clone: %w", err) } @@ -167,8 +167,8 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork return fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := git.NewCommand(txCtx, "update-server-info"). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(txCtx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("git update-server-info: %w", err) } diff --git a/services/repository/generate.go b/services/repository/generate.go index d5c07e9800..a47ab7db53 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -236,8 +236,8 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r return err } - if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repo.RepoPath()). - RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { + if stdout, _, err := git.NewCommand("remote", "add", "origin").AddDynamicArguments(repo.RepoPath()). + RunStdString(ctx, &git.RunOpts{Dir: tmpDir, Env: env}); err != nil { log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git remote add: %w", err) } @@ -371,8 +371,8 @@ func generateRepository(ctx context.Context, doer, owner *user_model.User, templ return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %w", err) } diff --git a/services/repository/gitgraph/graph.go b/services/repository/gitgraph/graph.go index 2628a1a55a..d06d18c1b4 100644 --- a/services/repository/gitgraph/graph.go +++ b/services/repository/gitgraph/graph.go @@ -22,7 +22,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo page = 1 } - graphCmd := git.NewCommand(r.Ctx, "log", "--graph", "--date-order", "--decorate=full") + graphCmd := git.NewCommand("log", "--graph", "--date-order", "--decorate=full") if hidePRRefs { graphCmd.AddArguments("--exclude=" + git.PullPrefix + "*") @@ -53,7 +53,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo scanner := bufio.NewScanner(stdoutReader) - if err := graphCmd.Run(&git.RunOpts{ + if err := graphCmd.Run(r.Ctx, &git.RunOpts{ Dir: r.Path, Stdout: stdoutWriter, Stderr: stderr, diff --git a/services/repository/init.go b/services/repository/init.go index c719e11786..bd777b8a2f 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -33,13 +33,13 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi committerName := sig.Name committerEmail := sig.Email - if stdout, _, err := git.NewCommand(ctx, "add", "--all"). - RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil { + if stdout, _, err := git.NewCommand("add", "--all"). + RunStdString(ctx, &git.RunOpts{Dir: tmpPath}); err != nil { log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git add --all: %w", err) } - cmd := git.NewCommand(ctx, "commit", "--message=Initial commit"). + cmd := git.NewCommand("commit", "--message=Initial commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) @@ -61,7 +61,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi ) if stdout, _, err := cmd. - RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil { + RunStdString(ctx, &git.RunOpts{Dir: tmpPath, Env: env}); err != nil { log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.LogString(), stdout, err) return fmt.Errorf("git commit: %w", err) } @@ -70,8 +70,8 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi defaultBranch = setting.Repository.DefaultBranch } - if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch). - RunStdString(&git.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil { + if stdout, _, err := git.NewCommand("push", "origin").AddDynamicArguments("HEAD:"+defaultBranch). + RunStdString(ctx, &git.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil { log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git push: %w", err) } diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 6f3a87afa3..50b434f003 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -120,8 +120,8 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("checkDaemonExportOK: %w", err) } - if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err) } @@ -230,9 +230,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // this is necessary for sync local tags from remote configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName()) - if stdout, _, err := git.NewCommand(ctx, "config"). + if stdout, _, err := git.NewCommand("config"). AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`). - RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { + RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { log.Error("MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err) return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add +refs/tags/*:refs/tags/*): %w", err) } @@ -251,9 +251,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // cleanUpMigrateGitConfig removes mirror info which prevents "push --all". // This also removes possible user credentials. func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error { - cmd := git.NewCommand(ctx, "remote", "rm", "origin") + cmd := git.NewCommand("remote", "rm", "origin") // if the origin does not exist - _, _, err := cmd.RunStdString(&git.RunOpts{ + _, _, err := cmd.RunStdString(ctx, &git.RunOpts{ Dir: repoPath, }) if err != nil && !git.IsRemoteNotExistError(err) { @@ -274,7 +274,7 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo } } - _, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err := git.NewCommand("remote", "rm", "origin").RunStdString(ctx, &git.RunOpts{Dir: repoPath}) if err != nil && !git.IsRemoteNotExistError(err) { return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err) } diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 4707602cdf..df32d5741e 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -18,6 +18,7 @@ import ( "sync" "time" + user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/hostmatcher" @@ -92,10 +93,10 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook } body = []byte(t.PayloadContent) - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) } -func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTask, payloadContent []byte) error { +func addDefaultHeaders(req *http.Request, secret []byte, w *webhook_model.Webhook, t *webhook_model.HookTask, payloadContent []byte) error { var signatureSHA1 string var signatureSHA256 string if len(secret) > 0 { @@ -112,10 +113,27 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa event := t.EventType.Event() eventType := string(t.EventType) + targetType := "default" + if w.IsSystemWebhook { + targetType = "system" + } else if w.RepoID != 0 { + targetType = "repository" + } else if w.OwnerID != 0 { + owner, err := user_model.GetUserByID(req.Context(), w.OwnerID) + if owner != nil && err == nil { + if owner.IsOrganization() { + targetType = "organization" + } else { + targetType = "user" + } + } + } + req.Header.Add("X-Gitea-Delivery", t.UUID) req.Header.Add("X-Gitea-Event", event) req.Header.Add("X-Gitea-Event-Type", eventType) req.Header.Add("X-Gitea-Signature", signatureSHA256) + req.Header.Add("X-Gitea-Hook-Installation-Target-Type", targetType) req.Header.Add("X-Gogs-Delivery", t.UUID) req.Header.Add("X-Gogs-Event", event) req.Header.Add("X-Gogs-Event-Type", eventType) @@ -125,6 +143,7 @@ func addDefaultHeaders(req *http.Request, secret []byte, t *webhook_model.HookTa req.Header["X-GitHub-Delivery"] = []string{t.UUID} req.Header["X-GitHub-Event"] = []string{event} req.Header["X-GitHub-Event-Type"] = []string{eventType} + req.Header["X-GitHub-Hook-Installation-Target-Type"] = []string{targetType} return nil } diff --git a/services/webhook/general.go b/services/webhook/general.go index 9f04bd9102..2e1301543c 100644 --- a/services/webhook/general.go +++ b/services/webhook/general.go @@ -9,7 +9,9 @@ import ( "net/url" "strings" + user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -308,12 +310,16 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w } func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { - refLink := linkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description) + refLink := linkFormatter(p.TargetURL, fmt.Sprintf("%s [%s]", p.Context, base.ShortSha(p.SHA))) - text = fmt.Sprintf("Commit Status changed: %s", refLink) + text = fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description) color = greenColor if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + if user_model.IsGiteaActionsUserName(p.Sender.UserName) { + text += fmt.Sprintf(" by %s", p.Sender.FullName) + } else { + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + } } return text, color diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index a065f64a30..5bc7ba097e 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -15,6 +15,7 @@ import ( "strings" webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -56,7 +57,7 @@ func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo } req.Header.Set("Content-Type", "application/json") - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) // likely useless, but has always been sent historially } const matrixPayloadSizeLimit = 1024 * 64 @@ -245,8 +246,8 @@ func (m matrixConvertor) Package(p *api.PackagePayload) (MatrixPayload, error) { } func (m matrixConvertor) Status(p *api.CommitStatusPayload) (MatrixPayload, error) { - refLink := htmlLinkFormatter(p.TargetURL, p.Context+"["+p.SHA+"]:"+p.Description) - text := fmt.Sprintf("Commit Status changed: %s", refLink) + refLink := htmlLinkFormatter(p.TargetURL, fmt.Sprintf("%s [%s]", p.Context, base.ShortSha(p.SHA))) + text := fmt.Sprintf("Commit Status changed: %s - %s", refLink, p.Description) return m.newPayload(text) } diff --git a/services/webhook/payloader.go b/services/webhook/payloader.go index e5f9720f20..adb7243fb1 100644 --- a/services/webhook/payloader.go +++ b/services/webhook/payloader.go @@ -110,7 +110,7 @@ func newJSONRequest[T any](pc payloadConvertor[T], w *webhook_model.Webhook, t * req.Header.Set("Content-Type", "application/json") if withDefaultHeaders { - return req, body, addDefaultHeaders(req, []byte(w.Secret), t, body) + return req, body, addDefaultHeaders(req, []byte(w.Secret), w, t, body) } return req, body, nil } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 7a0419aea7..66f4456185 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -43,7 +43,7 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { return fmt.Errorf("InitRepository: %w", err) } else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { return fmt.Errorf("createDelegateHooks: %w", err) - } else if _, _, err = git.NewCommand(ctx, "symbolic-ref", "HEAD").AddDynamicArguments(git.BranchPrefix + repo.DefaultWikiBranch).RunStdString(&git.RunOpts{Dir: repo.WikiPath()}); err != nil { + } else if _, _, err = git.NewCommand("symbolic-ref", "HEAD").AddDynamicArguments(git.BranchPrefix+repo.DefaultWikiBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.WikiPath()}); err != nil { return fmt.Errorf("unable to set default wiki branch to %q: %w", repo.DefaultWikiBranch, err) } return nil diff --git a/templates/admin/user/view_details.tmpl b/templates/admin/user/view_details.tmpl index be2f32b5ec..db61bc9359 100644 --- a/templates/admin/user/view_details.tmpl +++ b/templates/admin/user/view_details.tmpl @@ -9,30 +9,25 @@ {{if .User.IsAdmin}} {{ctx.Locale.Tr "admin.users.admin"}} {{end}} + {{if .User.IsTypeBot}} + {{ctx.Locale.Tr "admin.users.bot"}} + {{end}}
{{ctx.Locale.Tr "admin.users.auth_source"}}: - {{if eq .LoginSource.ID 0}} - {{ctx.Locale.Tr "admin.users.local"}} - {{else}} - {{.LoginSource.Name}} - {{end}} + {{Iif (eq .LoginSource.ID 0) (ctx.Locale.Tr "admin.users.local") .LoginSource.Name}}
{{ctx.Locale.Tr "admin.users.activated"}}: - {{if .User.IsActive}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .User.IsActive "octicon-check" "octicon-x")}} +
+
+ {{ctx.Locale.Tr "admin.users.prohibit_login"}}: + {{svg (Iif .User.ProhibitLogin "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "admin.users.restricted"}}: - {{if .User.IsRestricted}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .User.IsRestricted "octicon-check" "octicon-x")}}
{{ctx.Locale.Tr "settings.visibility"}}: @@ -42,11 +37,7 @@
{{ctx.Locale.Tr "admin.users.2fa"}}: - {{if .TwoFactorEnabled}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} + {{svg (Iif .TwoFactorEnabled "octicon-check" "octicon-x")}}
{{if .User.Language}}
diff --git a/templates/admin/user/view_emails.tmpl b/templates/admin/user/view_emails.tmpl index 22ce305a88..7e77206f1c 100644 --- a/templates/admin/user/view_emails.tmpl +++ b/templates/admin/user/view_emails.tmpl @@ -3,7 +3,7 @@
- {{.Email}} + {{.Email}} {{if .IsPrimary}}
{{ctx.Locale.Tr "settings.primary"}}
{{end}} diff --git a/templates/base/alert.tmpl b/templates/base/alert.tmpl index 760d3bfa2c..3f6d77a645 100644 --- a/templates/base/alert.tmpl +++ b/templates/base/alert.tmpl @@ -1,20 +1,20 @@ -{{if .Flash.ErrorMsg}} +{{- if .Flash.ErrorMsg -}}

{{.Flash.ErrorMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.SuccessMsg}} +{{- end -}} +{{- if .Flash.SuccessMsg -}}

{{.Flash.SuccessMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.InfoMsg}} +{{- end -}} +{{- if .Flash.InfoMsg -}}

{{.Flash.InfoMsg | SanitizeHTML}}

-{{end}} -{{if .Flash.WarningMsg}} +{{- end -}} +{{- if .Flash.WarningMsg -}}

{{.Flash.WarningMsg | SanitizeHTML}}

-{{end}} +{{- end -}} diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl index 9a7a6322f7..253892c009 100644 --- a/templates/base/paginate.tmpl +++ b/templates/base/paginate.tmpl @@ -17,7 +17,7 @@ {{if eq .Num -1}} ... {{else}} - {{.Num}} + {{.Num}} {{end}} {{end}} diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index 826642db42..cffdfabfaa 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -6,7 +6,7 @@
{{if .ProfileReadmeContent}} -
{{.ProfileReadmeContent}}
+
{{.ProfileReadmeContent}}
{{end}} {{template "shared/repo_search" .}} {{template "explore/repo_list" .}} diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 5d40653dc6..48083811e7 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -74,9 +74,7 @@ {{end}}
{{if .Description}} -
- {{.RenderedContent}} -
+
{{.RenderedContent}}
{{end}} {{end}} diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index 6efed3427f..f679b8744b 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -14,7 +14,8 @@ Search "repo/branch_dropdown" in the template directory to find all occurrences. */}} -
SSH {{end}} +
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 329dc45149..c8405ca748 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -48,7 +48,7 @@ {{end}} {{if IsMultilineCommitMessage .Message}} - + {{end}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} {{if IsMultilineCommitMessage .Message}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index a3b64b8a11..1a0a5c04a8 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -1,6 +1,6 @@ {{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
-
+
{{end}} - -
{{end}}
{{if $showFileTree}} @@ -106,7 +80,7 @@ {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} {{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}} -
+

+ +
{{if .EnableOAuth2}} diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index 61098e118b..418d8e9cfc 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -48,33 +48,33 @@

-
- {{ctx.Locale.Tr "settings.create_oauth2_application"}} -
-
- {{.CsrfTokenHtml}} -
- - -
-
- - -
-
-
- - +
+

{{ctx.Locale.Tr "settings.create_oauth2_application"}}

+ + {{.CsrfTokenHtml}} +
+ +
-
-
-
- - +
+ +
-
- - +
+
+ + +
+
+
+
+ + +
+
+ + +
diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index a29d0ba1d7..b243856127 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -76,7 +76,7 @@ func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) org := api.CreateOrgOption{ UserName: "user2_org", FullName: "User2's organization", diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 66209ee4e0..b42f05fc55 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -76,7 +76,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - token = getUserToken(t, normalUsername) + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -139,7 +139,7 @@ func TestAPIListUsersNotLoggedIn(t *testing.T) { func TestAPIListUsersNonAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername) + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) req := NewRequest(t, "GET", "/api/v1/admin/users"). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 96669b46f0..f3a595540f 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -33,6 +33,10 @@ type APITestContext struct { func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { session := loginUser(t, username) + if len(scope) == 0 { + // FIXME: legacy logic: no scope means all + scope = []auth.AccessTokenScope{auth.AccessTokenScopeAll} + } token := getTokenForLoggedInUser(t, session, scope...) return APITestContext{ Session: session, diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index 51b047ab41..bc858c7476 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" composer_module "code.gitea.io/gitea/modules/packages/composer" @@ -217,5 +218,39 @@ func TestPackageComposer(t *testing.T) { assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) assert.Len(t, pkgs[0].Bin, 1) assert.Equal(t, packageBin, pkgs[0].Bin[0]) + + // Test package linked to repository + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + userPkgs, err := packages.GetPackagesByType(db.DefaultContext, user.ID, packages.TypeComposer) + assert.NoError(t, err) + assert.Len(t, userPkgs, 1) + assert.EqualValues(t, 0, userPkgs[0].RepoID) + + err = packages.SetRepositoryLink(db.DefaultContext, userPkgs[0].ID, repo1.ID) + assert.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + result = composer.PackageMetadataResponse{} + DecodeJSON(t, resp, &result) + + assert.Contains(t, result.Packages, packageName) + pkgs = result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) + assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) + assert.Equal(t, "git", pkgs[0].Source.Type) + assert.Equal(t, packageVersion, pkgs[0].Source.Reference) }) } diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 184362e7e3..9c4be31396 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -72,7 +72,7 @@ func TestAPIReposGitBlobs(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index c5883a8058..5a66337589 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -30,8 +30,8 @@ func TestAPIGitTags(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Set up git config for the tagger - _ = git.NewCommand(git.DefaultContext, "config", "user.name").AddDynamicArguments(user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()}) - _ = git.NewCommand(git.DefaultContext, "config", "user.email").AddDynamicArguments(user.Email).Run(&git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand("config", "user.name").AddDynamicArguments(user.Name).Run(git.DefaultContext, &git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand("config", "user.email").AddDynamicArguments(user.Email).Run(git.DefaultContext, &git.RunOpts{Dir: repo.RepoPath()}) gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo) defer gitRepo.Close() diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index 8eec6d8d22..47063d9091 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -69,7 +69,7 @@ func TestAPIReposGitTrees(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index ab2a948fde..95ffe20c35 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -153,9 +153,9 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string, sizes ...int) (pushedFil t.Run("CommitAndPushLFS", func(t *testing.T) { defer tests.PrintCurrentTest(t)() prefix := "lfs-data-file-" - err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("lfs").AddArguments("install").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track").AddDynamicArguments(prefix + "*").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("lfs").AddArguments("track").AddDynamicArguments(prefix+"*").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) err = git.AddChanges(dstPath, false, ".gitattributes") assert.NoError(t, err) @@ -271,20 +271,20 @@ func lockTest(t *testing.T, repoPath string) { } func lockFileTest(t *testing.T, filename, repoPath string) { - _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err := git.NewCommand("lfs").AddArguments("locks").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("lock").AddDynamicArguments(filename).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("locks").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(&git.RunOpts{Dir: repoPath}) + _, _, err = git.NewCommand("lfs").AddArguments("unlock").AddDynamicArguments(filename).RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) assert.NoError(t, err) } func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push + _, _, err = git.NewCommand("push", "origin", "master").RunStdString(git.DefaultContext, &git.RunOpts{Dir: repoPath}) // Push assert.NoError(t, err) return name } @@ -386,7 +386,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes // Try to force push without force push permissions, which should fail t.Run("ForcePushWithoutForcePermissions", func(t *testing.T) { t.Run("CreateDivergentHistory", func(t *testing.T) { - git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath}) + git.NewCommand("reset", "--hard", "HEAD~1").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) _, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new") assert.NoError(t, err) }) @@ -785,7 +785,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+1) @@ -803,7 +803,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.Contains(t, "Testing commit 1", prMsg.Body) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -851,7 +851,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string }) t.Run("Push2", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + headBranch).Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+headBranch).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) @@ -861,7 +861,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/" + headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin").AddDynamicArguments("HEAD:refs/for/master/test/"+headBranch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 9f51f15149..435253c9c7 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -122,7 +122,7 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { // Init repository in dstPath assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false, git.Sha1ObjectFormat.Name())) // forcibly set default branch to master - _, _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) @@ -141,21 +141,21 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("remote", "add").AddDynamicArguments(remoteName, u.String()).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("push", "-u").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("push").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) } } @@ -180,28 +180,28 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { func doGitCreateBranch(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(branch).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("checkout", "-b").AddDynamicArguments(branch).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommandNoGlobals(git.AllowLFSFiltersArgs()...).AddArguments("checkout").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitMerge(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommand(git.DefaultContext, "merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("merge").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPull(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, _, err := git.NewCommandContextNoGlobals(git.DefaultContext, git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommandNoGlobals(git.AllowLFSFiltersArgs()...).AddArguments("pull").AddArguments(git.ToTrustedCmdArgs(args)...).RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } diff --git a/tests/integration/git_lfs_ssh_test.go b/tests/integration/git_lfs_ssh_test.go index 0152f151c2..66c1d1fe5b 100644 --- a/tests/integration/git_lfs_ssh_test.go +++ b/tests/integration/git_lfs_ssh_test.go @@ -45,7 +45,7 @@ func TestGitLFSSSH(t *testing.T) { setting.LFS.AllowPureSSH = true require.NoError(t, cfg.Save()) - _, _, cmdErr := git.NewCommand(t.Context(), "config", "lfs.sshtransfer", "always").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, cmdErr := git.NewCommand("config", "lfs.sshtransfer", "always").RunStdString(t.Context(), &git.RunOpts{Dir: dstPath}) assert.NoError(t, cmdErr) lfsCommitAndPushTest(t, dstPath, 10) }) diff --git a/tests/integration/git_misc_test.go b/tests/integration/git_misc_test.go index e5be8177d1..bf9e2c02ab 100644 --- a/tests/integration/git_misc_test.go +++ b/tests/integration/git_misc_test.go @@ -101,10 +101,10 @@ func TestAgitPullPush(t *testing.T) { assert.NoError(t, err) // push to create an agit pull request - err = git.NewCommand(git.DefaultContext, "push", "origin", + err = git.NewCommand("push", "origin", "-o", "title=test-title", "-o", "description=test-description", "HEAD:refs/for/master/test-agit-push", - ).Run(&git.RunOpts{Dir: dstPath}) + ).Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // check pull request exist @@ -118,20 +118,20 @@ func TestAgitPullPush(t *testing.T) { assert.NoError(t, err) // push 2 - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // reset to first commit - err = git.NewCommand(git.DefaultContext, "reset", "--hard", "HEAD~1").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("reset", "--hard", "HEAD~1").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // test force push without confirm - _, stderr, err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(&git.RunOpts{Dir: dstPath}) + _, stderr, err := git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)") // test force push with confirm - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(&git.RunOpts{Dir: dstPath}) + err = git.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) }) } diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index e68f8bfce2..ee4f203dbe 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -204,8 +204,8 @@ func TestPushPullRefs(t *testing.T) { dstPath := t.TempDir() doGitClone(dstPath, u)(t) - cmd := git.NewCommand(git.DefaultContext, "push", "--delete", "origin", "refs/pull/2/head") - stdout, stderr, err := cmd.RunStdString(&git.RunOpts{ + cmd := git.NewCommand("push", "--delete", "origin", "refs/pull/2/head") + stdout, stderr, err := cmd.RunStdString(git.DefaultContext, &git.RunOpts{ Dir: dstPath, }) assert.Error(t, err) diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 9e487924d1..2f6b7eae31 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -249,55 +249,19 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession // token has to be unique this counter take care of var tokenCounter int64 -// getTokenForLoggedInUser returns a token for a logged in user. -// The scope is an optional list of snake_case strings like the frontend form fields, -// but without the "scope_" prefix. +// getTokenForLoggedInUser returns a token for a logged-in user. func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string { t.Helper() - var token string - req := NewRequest(t, "GET", "/user/settings/applications") - resp := session.MakeRequest(t, req, http.StatusOK) - var csrf string - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != "_csrf" { - continue - } - csrf = cookie.Value - break - } - if csrf == "" { - doc := NewHTMLParser(t, resp.Body) - csrf = doc.GetCSRF() - } - assert.NotEmpty(t, csrf) urlValues := url.Values{} - urlValues.Add("_csrf", csrf) + urlValues.Add("_csrf", GetUserCSRFToken(t, session)) urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1))) for _, scope := range scopes { - urlValues.Add("scope", string(scope)) + urlValues.Add("scope-dummy", string(scope)) // it only needs to start with "scope-" to be accepted } - req = NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // Log the flash values on failure - if !assert.Equal(t, []string{"/user/settings/applications"}, resp.Result().Header["Location"]) { - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != gitea_context.CookieNameFlash { - continue - } - flash, _ := url.ParseQuery(cookie.Value) - for key, value := range flash { - t.Logf("Flash %q: %q", key, value) - } - } - } - - req = NewRequest(t, "GET", "/user/settings/applications") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - token = htmlDoc.doc.Find(".ui.info p").Text() - assert.NotEmpty(t, token) - return token + req := NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) + session.MakeRequest(t, req, http.StatusSeeOther) + flashes := session.GetCookieFlashMessage() + return flashes.InfoMsg } type RequestWrapper struct { diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 162ea532c8..461a1400f7 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -265,7 +265,7 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) { t.Run("add commit", doGitAddSomeCommits(dstPath, "master")) t.Run("do agit pull create", func(t *testing.T) { - err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic=" + "test-topic").Run(&git.RunOpts{Dir: dstPath}) + err := git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) }) }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 0b7ff52c47..1c8318db0d 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -293,12 +293,12 @@ func TestCantMergeUnrelated(t *testing.T) { }) path := repo_model.RepoPath(user1.Name, repo1.Name) - err := git.NewCommand(git.DefaultContext, "read-tree", "--empty").Run(&git.RunOpts{Dir: path}) + err := git.NewCommand("read-tree", "--empty").Run(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) stdin := bytes.NewBufferString("Unrelated File") var stdout strings.Builder - err = git.NewCommand(git.DefaultContext, "hash-object", "-w", "--stdin").Run(&git.RunOpts{ + err = git.NewCommand("hash-object", "-w", "--stdin").Run(git.DefaultContext, &git.RunOpts{ Dir: path, Stdin: stdin, Stdout: &stdout, @@ -307,10 +307,10 @@ func TestCantMergeUnrelated(t *testing.T) { assert.NoError(t, err) sha := strings.TrimSpace(stdout.String()) - _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) + _, _, err = git.NewCommand("update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments("100644", sha, "somewher-over-the-rainbow").RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) - treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path}) + treeSha, _, err := git.NewCommand("write-tree").RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) treeSha = strings.TrimSpace(treeSha) @@ -330,8 +330,8 @@ func TestCantMergeUnrelated(t *testing.T) { _, _ = messageBytes.WriteString("\n") stdout.Reset() - err = git.NewCommand(git.DefaultContext, "commit-tree").AddDynamicArguments(treeSha). - Run(&git.RunOpts{ + err = git.NewCommand("commit-tree").AddDynamicArguments(treeSha). + Run(git.DefaultContext, &git.RunOpts{ Env: env, Dir: path, Stdin: messageBytes, @@ -340,7 +340,7 @@ func TestCantMergeUnrelated(t *testing.T) { assert.NoError(t, err) commitSha := strings.TrimSpace(stdout.String()) - _, _, err = git.NewCommand(git.DefaultContext, "branch", "unrelated").AddDynamicArguments(commitSha).RunStdString(&git.RunOpts{Dir: path}) + _, _, err = git.NewCommand("branch", "unrelated").AddDynamicArguments(commitSha).RunStdString(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") @@ -914,13 +914,13 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing. stderrBuf := &bytes.Buffer{} - err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o"). + err = git.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). AddDynamicArguments(`topic=test/head2`). AddArguments("-o"). AddDynamicArguments(`title="create a test pull request with agit"`). AddArguments("-o"). AddDynamicArguments(`description="This PR is a test pull request which created with agit"`). - Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf}) + Run(git.DefaultContext, &git.RunOpts{Dir: dstPath, Stderr: stderrBuf}) assert.NoError(t, err) assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6") diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go index 5638826ea0..8ea7508559 100644 --- a/tests/integration/repo_tag_test.go +++ b/tests/integration/repo_tag_test.go @@ -55,10 +55,10 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v-2").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v-2").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.Error(t, err) assert.Contains(t, err.Error(), "Tag v-2 is protected") }) @@ -75,20 +75,20 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v-1.1", "-m", "force update", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.Error(t, err) assert.Contains(t, err.Error(), "the tag already exists in the remote") - _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "--tags", "--force").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) require.NoError(t, err) req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName()) resp := MakeRequest(t, req, http.StatusOK) @@ -137,15 +137,15 @@ func TestRepushTag(t *testing.T) { doGitClone(dstPath, u)(t) // create and push a tag - _, _, err := git.NewCommand(git.DefaultContext, "tag", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err := git.NewCommand("tag", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // create a release for the tag createdRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v2.0", "", "Release of v2.0", "desc") assert.False(t, createdRelease.IsDraft) // delete the tag - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--delete", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--delete", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should be a draft req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) @@ -154,7 +154,7 @@ func TestRepushTag(t *testing.T) { DecodeJSON(t, resp, &respRelease) assert.True(t, respRelease.IsDraft) // re-push the tag - _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath}) + _, _, err = git.NewCommand("push", "origin", "--tags", "v2.0").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) // query the release by API and it should not be a draft req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 8c568a1272..38d0e7fe1f 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -130,8 +130,13 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.False(t, exists) + + link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, "tea clone user2/repo1", link) } func TestViewRepo1CloneLinkAuthorized(t *testing.T) { @@ -146,10 +151,15 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link") assert.True(t, exists, "The template has changed") assert.Equal(t, setting.AppURL+"user2/repo1.git", link) + link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") assert.True(t, exists, "The template has changed") sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port) assert.Equal(t, sshURL, link) + + link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link") + assert.True(t, exists, "The template has changed") + assert.Equal(t, "tea clone user2/repo1", link) } func TestViewRepoWithSymlinks(t *testing.T) { diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 6719a2f84c..d30240c84b 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -533,7 +533,9 @@ func Test_WebhookStatus(t *testing.T) { var triggeredEvent string provider := newMockWebhookProvider(func(r *http.Request) { assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status") + assert.Contains(t, r.Header["X-Github-Hook-Installation-Target-Type"], "repository", "X-GitHub-Hook-Installation-Target-Type should contain repository") assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status") + assert.Contains(t, r.Header["X-Gitea-Hook-Installation-Target-Type"], "repository", "X-Gitea-Hook-Installation-Target-Type should contain repository") assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status") content, _ := io.ReadAll(r.Body) var payload api.CommitStatusPayload diff --git a/web_src/css/base.css b/web_src/css/base.css index 76d7d82a5c..68015e4031 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1188,3 +1188,13 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil overflow: hidden !important; text-overflow: ellipsis !important; } + +.ui.dropdown.text-flex-grow { + display: flex; +} + +.ui.dropdown.text-flex-grow > .text { + display: flex; + flex-grow: 1; + justify-content: space-between; +} diff --git a/web_src/css/editor/fileeditor.css b/web_src/css/editor/fileeditor.css index 444ee8c7e7..698efffc99 100644 --- a/web_src/css/editor/fileeditor.css +++ b/web_src/css/editor/fileeditor.css @@ -74,12 +74,3 @@ padding: 1rem; text-align: center; } - -.edit-diff { - padding: 0 !important; -} - -.edit-diff > div > .ui.table { - border-top: none !important; - border-bottom: none !important; -} diff --git a/web_src/css/index.css b/web_src/css/index.css index ce1a23b245..630aa3c2ef 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -19,7 +19,6 @@ @import "./modules/dimmer.css"; @import "./modules/modal.css"; -@import "./modules/select.css"; @import "./modules/tippy.css"; @import "./modules/breadcrumb.css"; @import "./modules/comment.css"; diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index d2dcf2ec6e..fabf5b3a8f 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -535,7 +535,7 @@ user-select: none; } -.markup-render { +.markup-content-iframe { display: block; border: none; width: 100%; diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css index 0a3a71acaa..f7e61ba360 100644 --- a/web_src/css/modules/checkbox.css +++ b/web_src/css/modules/checkbox.css @@ -119,3 +119,13 @@ input[type="radio"] { .ui.toggle.checkbox input:focus:checked ~ label::before { background: var(--color-primary) !important; } + +label.gt-checkbox { + display: inline-flex; + align-items: center; + gap: 0.25em; +} + +.ui.form .field > label.gt-checkbox { + display: flex; +} diff --git a/web_src/css/modules/select.css b/web_src/css/modules/select.css deleted file mode 100644 index 1d7d749d4a..0000000000 --- a/web_src/css/modules/select.css +++ /dev/null @@ -1,25 +0,0 @@ -.gitea-select { - position: relative; -} - -.gitea-select select { - appearance: none; /* hide default triangle */ -} - -/* ::before and ::after pseudo elements don't work on select elements, - so we need to put it on the parent. */ -.gitea-select::after { - position: absolute; - top: 12px; - right: 8px; - pointer-events: none; - content: ""; - width: 14px; - height: 14px; - mask-size: cover; - -webkit-mask-size: cover; - mask-image: var(--octicon-chevron-right); - -webkit-mask-image: var(--octicon-chevron-right); - transform: rotate(90deg); /* point the chevron down */ - background: currentcolor; -} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 2752174f86..87af299dad 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1085,10 +1085,6 @@ td .commit-summary { height: 30px; } -.repository .diff-box .resolved-placeholder .button { - padding: 8px 12px; -} - .repository .diff-file-box .header { background-color: var(--color-box-header); } diff --git a/web_src/css/repo/release-tag.css b/web_src/css/repo/release-tag.css index 32027dd886..bf8c1312f1 100644 --- a/web_src/css/repo/release-tag.css +++ b/web_src/css/repo/release-tag.css @@ -45,7 +45,7 @@ display: flex; align-items: center; } - #release-list .js-branch-tag-selector { + #release-list .release-branch-tag-selector { margin-left: auto; } #release-list .branch-selector-dropdown .menu { /* open menu to left */ diff --git a/web_src/css/shared/milestone.css b/web_src/css/shared/milestone.css index 91e6b5e387..47e822f8d3 100644 --- a/web_src/css/shared/milestone.css +++ b/web_src/css/shared/milestone.css @@ -12,7 +12,7 @@ border-top: 1px solid var(--color-secondary); } -.milestone-card .content { +.milestone-card .render-content { padding-top: 10px; } diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue deleted file mode 100644 index 6570c92781..0000000000 --- a/web_src/js/components/DiffFileList.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index d00d03565f..381a1c3ca4 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -1,75 +1,18 @@ diff --git a/web_src/js/components/DiffFileTreeItem.vue b/web_src/js/components/DiffFileTreeItem.vue index d3be10e3e9..5ee0e5bcaa 100644 --- a/web_src/js/components/DiffFileTreeItem.vue +++ b/web_src/js/components/DiffFileTreeItem.vue @@ -2,21 +2,7 @@ import {SvgIcon, type SvgName} from '../svg.ts'; import {diffTreeStore} from '../modules/stores.ts'; import {ref} from 'vue'; - -type File = { - Name: string; - NameHash: string; - Type: number; - IsViewed: boolean; - IsSubmodule: boolean; -} - -export type Item = { - name: string; - isFile: boolean; - file?: File; - children?: Item[]; -}; +import type {Item, File, FileStatus} from '../utils/filetree.ts'; defineProps<{ item: Item, @@ -25,15 +11,16 @@ defineProps<{ const store = diffTreeStore(); const collapsed = ref(false); -function getIconForDiffType(pType: number) { - const diffTypes: Record}> = { - '1': {name: 'octicon-diff-added', classes: ['text', 'green']}, - '2': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, - '3': {name: 'octicon-diff-removed', classes: ['text', 'red']}, - '4': {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, - '5': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok +function getIconForDiffStatus(pType: FileStatus) { + const diffTypes: Record }> = { + 'added': {name: 'octicon-diff-added', classes: ['text', 'green']}, + 'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, + 'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']}, + 'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']}, + 'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, + 'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok }; - return diffTypes[String(pType)]; + return diffTypes[pType]; } function fileIcon(file: File) { @@ -48,27 +35,37 @@ function fileIcon(file: File) {
{{ item.name }} - + -
- - - - {{ item.name }} -
-
- -
+ ${svg}`; + const iframe = document.createElement('iframe'); + iframe.classList.add('markup-content-iframe', 'tw-invisible'); + iframe.srcdoc = `${svg}`; - const mermaidBlock = document.createElement('div'); - mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden'); - mermaidBlock.append(iframe); + const mermaidBlock = document.createElement('div'); + mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden'); + mermaidBlock.append(iframe); - const btn = makeCodeCopyButton(); - btn.setAttribute('data-clipboard-text', source); - mermaidBlock.append(btn); + const btn = makeCodeCopyButton(); + btn.setAttribute('data-clipboard-text', source); + mermaidBlock.append(btn); - const updateIframeHeight = () => { - const body = iframe.contentWindow?.document?.body; - if (body) { - iframe.style.height = `${body.clientHeight}px`; - } - }; + const updateIframeHeight = () => { + const body = iframe.contentWindow?.document?.body; + if (body) { + iframe.style.height = `${body.clientHeight}px`; + } + }; - iframe.addEventListener('load', () => { - pre.replaceWith(mermaidBlock); - mermaidBlock.classList.remove('tw-hidden'); + iframe.addEventListener('load', () => { + pre.replaceWith(mermaidBlock); + mermaidBlock.classList.remove('tw-hidden'); + updateIframeHeight(); + setTimeout(() => { // avoid flash of iframe background + mermaidBlock.classList.remove('is-loading'); + iframe.classList.remove('tw-invisible'); + }, 0); + + // update height when element's visibility state changes, for example when the diagram is inside + // a
+ block and the
block becomes visible upon user interaction, it + // would initially set a incorrect height and the correct height is set during this callback. + (new IntersectionObserver(() => { updateIframeHeight(); - setTimeout(() => { // avoid flash of iframe background - mermaidBlock.classList.remove('is-loading'); - iframe.classList.remove('tw-invisible'); - }, 0); + }, {root: document.documentElement})).observe(iframe); + }); - // update height when element's visibility state changes, for example when the diagram is inside - // a
+ block and the
block becomes visible upon user interaction, it - // would initially set a incorrect height and the correct height is set during this callback. - (new IntersectionObserver(() => { - updateIframeHeight(); - }, {root: document.documentElement})).observe(iframe); - }); - - document.body.append(mermaidBlock); - } catch (err) { - displayError(pre, err); - } + document.body.append(mermaidBlock); + } catch (err) { + displayError(pre, err); } } diff --git a/web_src/js/markup/tasklist.ts b/web_src/js/markup/tasklist.ts index 95db7fc845..dc4bbd9519 100644 --- a/web_src/js/markup/tasklist.ts +++ b/web_src/js/markup/tasklist.ts @@ -7,80 +7,80 @@ const preventListener = (e: Event) => e.preventDefault(); * Attaches `input` handlers to markdown rendered tasklist checkboxes in comments. * * When a checkbox value changes, the corresponding [ ] or [x] in the markdown string - * is set accordingly and sent to the server. On success it updates the raw-content on + * is set accordingly and sent to the server. On success, it updates the raw-content on * error it resets the checkbox to its original value. */ -export function initMarkupTasklist(): void { - for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) { - const container = el.parentNode; - const checkboxes = el.querySelectorAll(`.task-list-item input[type=checkbox]`); +export function initMarkupTasklist(elMarkup: HTMLElement): void { + if (!elMarkup.matches('[data-can-edit=true]')) return; - for (const checkbox of checkboxes) { - if (checkbox.hasAttribute('data-editable')) { + const container = elMarkup.parentNode; + const checkboxes = elMarkup.querySelectorAll(`.task-list-item input[type=checkbox]`); + + for (const checkbox of checkboxes) { + if (checkbox.hasAttribute('data-editable')) { + return; + } + + checkbox.setAttribute('data-editable', 'true'); + checkbox.addEventListener('input', async () => { + const checkboxCharacter = checkbox.checked ? 'x' : ' '; + const position = parseInt(checkbox.getAttribute('data-source-position')) + 1; + + const rawContent = container.querySelector('.raw-content'); + const oldContent = rawContent.textContent; + + const encoder = new TextEncoder(); + const buffer = encoder.encode(oldContent); + // Indexes may fall off the ends and return undefined. + if (buffer[position - 1] !== '['.codePointAt(0) || + buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) || + buffer[position + 1] !== ']'.codePointAt(0)) { + // Position is probably wrong. Revert and don't allow change. + checkbox.checked = !checkbox.checked; + throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`); + } + buffer.set(encoder.encode(checkboxCharacter), position); + const newContent = new TextDecoder().decode(buffer); + + if (newContent === oldContent) { return; } - checkbox.setAttribute('data-editable', 'true'); - checkbox.addEventListener('input', async () => { - const checkboxCharacter = checkbox.checked ? 'x' : ' '; - const position = parseInt(checkbox.getAttribute('data-source-position')) + 1; + // Prevent further inputs until the request is done. This does not use the + // `disabled` attribute because it causes the border to flash on click. + for (const checkbox of checkboxes) { + checkbox.addEventListener('click', preventListener); + } - const rawContent = container.querySelector('.raw-content'); - const oldContent = rawContent.textContent; + try { + const editContentZone = container.querySelector('.edit-content-zone'); + const updateUrl = editContentZone.getAttribute('data-update-url'); + const context = editContentZone.getAttribute('data-context'); + const contentVersion = editContentZone.getAttribute('data-content-version'); - const encoder = new TextEncoder(); - const buffer = encoder.encode(oldContent); - // Indexes may fall off the ends and return undefined. - if (buffer[position - 1] !== '['.codePointAt(0) || - buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) || - buffer[position + 1] !== ']'.codePointAt(0)) { - // Position is probably wrong. Revert and don't allow change. - checkbox.checked = !checkbox.checked; - throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`); - } - buffer.set(encoder.encode(checkboxCharacter), position); - const newContent = new TextDecoder().decode(buffer); - - if (newContent === oldContent) { + const requestBody = new FormData(); + requestBody.append('ignore_attachments', 'true'); + requestBody.append('content', newContent); + requestBody.append('context', context); + requestBody.append('content_version', contentVersion); + const response = await POST(updateUrl, {data: requestBody}); + const data = await response.json(); + if (response.status === 400) { + showErrorToast(data.errorMessage); return; } + editContentZone.setAttribute('data-content-version', data.contentVersion); + rawContent.textContent = newContent; + } catch (err) { + checkbox.checked = !checkbox.checked; + console.error(err); + } - // Prevent further inputs until the request is done. This does not use the - // `disabled` attribute because it causes the border to flash on click. - for (const checkbox of checkboxes) { - checkbox.addEventListener('click', preventListener); - } - - try { - const editContentZone = container.querySelector('.edit-content-zone'); - const updateUrl = editContentZone.getAttribute('data-update-url'); - const context = editContentZone.getAttribute('data-context'); - const contentVersion = editContentZone.getAttribute('data-content-version'); - - const requestBody = new FormData(); - requestBody.append('ignore_attachments', 'true'); - requestBody.append('content', newContent); - requestBody.append('context', context); - requestBody.append('content_version', contentVersion); - const response = await POST(updateUrl, {data: requestBody}); - const data = await response.json(); - if (response.status === 400) { - showErrorToast(data.errorMessage); - return; - } - editContentZone.setAttribute('data-content-version', data.contentVersion); - rawContent.textContent = newContent; - } catch (err) { - checkbox.checked = !checkbox.checked; - console.error(err); - } - - // Enable input on checkboxes again - for (const checkbox of checkboxes) { - checkbox.removeEventListener('click', preventListener); - } - }); - } + // Enable input on checkboxes again + for (const checkbox of checkboxes) { + checkbox.removeEventListener('click', preventListener); + } + }); // Enable the checkboxes as they are initially disabled by the markdown renderer for (const checkbox of checkboxes) { diff --git a/web_src/js/modules/dirauto.ts b/web_src/js/modules/dirauto.ts deleted file mode 100644 index 7058a59b09..0000000000 --- a/web_src/js/modules/dirauto.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; - -type DirElement = HTMLInputElement | HTMLTextAreaElement; - -// for performance considerations, it only uses performant syntax -function attachDirAuto(el: DirElement) { - if (el.type !== 'hidden' && - el.type !== 'checkbox' && - el.type !== 'radio' && - el.type !== 'range' && - el.type !== 'color') { - el.dir = 'auto'; - } -} - -export function initDirAuto(): void { - const observer = new MutationObserver((mutationList) => { - const len = mutationList.length; - for (let i = 0; i < len; i++) { - const mutation = mutationList[i]; - const len = mutation.addedNodes.length; - for (let i = 0; i < len; i++) { - const addedNode = mutation.addedNodes[i] as HTMLElement; - if (!isDocumentFragmentOrElementNode(addedNode)) continue; - if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') { - attachDirAuto(addedNode as DirElement); - } - const children = addedNode.querySelectorAll('input, textarea'); - const len = children.length; - for (let childIdx = 0; childIdx < len; childIdx++) { - attachDirAuto(children[childIdx]); - } - } - } - }); - - const docNodes = document.querySelectorAll('input, textarea'); - const len = docNodes.length; - for (let i = 0; i < len; i++) { - attachDirAuto(docNodes[i]); - } - - observer.observe(document, {subtree: true, childList: true}); -} diff --git a/web_src/js/modules/init.ts b/web_src/js/modules/init.ts new file mode 100644 index 0000000000..538fafd83f --- /dev/null +++ b/web_src/js/modules/init.ts @@ -0,0 +1,26 @@ +export class InitPerformanceTracer { + results: {name: string, dur: number}[] = []; + recordCall(name: string, func: ()=>void) { + const start = performance.now(); + func(); + this.results.push({name, dur: performance.now() - start}); + } + printResults() { + this.results = this.results.sort((a, b) => b.dur - a.dur); + for (let i = 0; i < 20 && i < this.results.length; i++) { + console.info(`performance trace: ${this.results[i].name} ${this.results[i].dur.toFixed(3)}`); + } + } +} + +export function callInitFunctions(functions: (() => any)[]): InitPerformanceTracer | null { + // Start performance trace by accessing a URL by "https://localhost/?_ui_performance_trace=1" or "https://localhost/?key=value&_ui_performance_trace=1" + // It is a quick check, no side effect so no need to do slow URL parsing. + const perfTracer = !window.location.search.includes('_ui_performance_trace=1') ? null : new InitPerformanceTracer(); + if (perfTracer) { + for (const func of functions) perfTracer.recordCall(func.name, func); + } else { + for (const func of functions) func(); + } + return perfTracer; +} diff --git a/web_src/js/modules/observer.ts b/web_src/js/modules/observer.ts new file mode 100644 index 0000000000..06208d0507 --- /dev/null +++ b/web_src/js/modules/observer.ts @@ -0,0 +1,110 @@ +import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; +import type {Promisable} from 'type-fest'; +import type {InitPerformanceTracer} from './init.ts'; + +let globalSelectorObserverInited = false; + +type SelectorHandler = {selector: string, handler: (el: HTMLElement) => void}; +const selectorHandlers: SelectorHandler[] = []; + +type GlobalEventFunc = (el: T, e: E) => Promisable; +const globalEventFuncs: Record> = {}; + +type GlobalInitFunc = (el: T) => Promisable; +const globalInitFuncs: Record> = {}; + +// It handles the global events for all `
` elements. +export function registerGlobalEventFunc(event: string, name: string, func: GlobalEventFunc) { + globalEventFuncs[`${event}:${name}`] = func as GlobalEventFunc; +} + +// It handles the global init functions by a selector, for example: +// > registerGlobalSelectorObserver('.ui.dropdown:not(.custom)', (el) => { initDropdown(el, ...) }); +// ATTENTION: For most cases, it's recommended to use registerGlobalInitFunc instead, +// Because this selector-based approach is less efficient and less maintainable. +// But if there are already a lot of elements on many pages, this selector-based approach is more convenient for exiting code. +export function registerGlobalSelectorFunc(selector: string, handler: (el: HTMLElement) => void) { + selectorHandlers.push({selector, handler}); + // Then initAddedElementObserver will call this handler for all existing elements after all handlers are added. + // This approach makes the init stage only need to do one "querySelectorAll". + if (!globalSelectorObserverInited) return; + for (const el of document.querySelectorAll(selector)) { + handler(el); + } +} + +// It handles the global init functions for all `
` elements. +export function registerGlobalInitFunc(name: string, handler: GlobalInitFunc) { + globalInitFuncs[name] = handler as GlobalInitFunc; + // The "global init" functions are managed internally and called by callGlobalInitFunc + // They must be ready before initGlobalSelectorObserver is called. + if (globalSelectorObserverInited) throw new Error('registerGlobalInitFunc() must be called before initGlobalSelectorObserver()'); +} + +function callGlobalInitFunc(el: HTMLElement) { + const initFunc = el.getAttribute('data-global-init'); + const func = globalInitFuncs[initFunc]; + if (!func) throw new Error(`Global init function "${initFunc}" not found`); + + type GiteaGlobalInitElement = Partial & {_giteaGlobalInited: boolean}; + if ((el as GiteaGlobalInitElement)._giteaGlobalInited) throw new Error(`Global init function "${initFunc}" already executed`); + (el as GiteaGlobalInitElement)._giteaGlobalInited = true; + func(el); +} + +function attachGlobalEvents() { + // add global "[data-global-click]" event handler + document.addEventListener('click', (e) => { + const elem = (e.target as HTMLElement).closest('[data-global-click]'); + if (!elem) return; + const funcName = elem.getAttribute('data-global-click'); + const func = globalEventFuncs[`click:${funcName}`]; + if (!func) throw new Error(`Global event function "click:${funcName}" not found`); + func(elem, e); + }); +} + +export function initGlobalSelectorObserver(perfTracer?: InitPerformanceTracer): void { + if (globalSelectorObserverInited) throw new Error('initGlobalSelectorObserver() already called'); + globalSelectorObserverInited = true; + + attachGlobalEvents(); + + selectorHandlers.push({selector: '[data-global-init]', handler: callGlobalInitFunc}); + const observer = new MutationObserver((mutationList) => { + const len = mutationList.length; + for (let i = 0; i < len; i++) { + const mutation = mutationList[i]; + const len = mutation.addedNodes.length; + for (let i = 0; i < len; i++) { + const addedNode = mutation.addedNodes[i] as HTMLElement; + if (!isDocumentFragmentOrElementNode(addedNode)) continue; + + for (const {selector, handler} of selectorHandlers) { + if (addedNode.matches(selector)) { + handler(addedNode); + } + for (const el of addedNode.querySelectorAll(selector)) { + handler(el); + } + } + } + } + }); + if (perfTracer) { + for (const {selector, handler} of selectorHandlers) { + perfTracer.recordCall(`initGlobalSelectorObserver ${selector}`, () => { + for (const el of document.querySelectorAll(selector)) { + handler(el); + } + }); + } + } else { + for (const {selector, handler} of selectorHandlers) { + for (const el of document.querySelectorAll(selector)) { + handler(el); + } + } + } + observer.observe(document, {subtree: true, childList: true}); +} diff --git a/web_src/js/modules/stores.ts b/web_src/js/modules/stores.ts index 942a7bc508..65da1e044a 100644 --- a/web_src/js/modules/stores.ts +++ b/web_src/js/modules/stores.ts @@ -1,11 +1,16 @@ import {reactive} from 'vue'; import type {Reactive} from 'vue'; +const {pageData} = window.config; + let diffTreeStoreReactive: Reactive>; export function diffTreeStore() { if (!diffTreeStoreReactive) { - diffTreeStoreReactive = reactive(window.config.pageData.diffFileInfo); - window.config.pageData.diffFileInfo = diffTreeStoreReactive; + diffTreeStoreReactive = reactive({ + files: pageData.DiffFiles, + fileTreeIsVisible: false, + selectedItem: '', + }); } return diffTreeStoreReactive; } diff --git a/web_src/js/render/pdf.ts b/web_src/js/render/pdf.ts index f31f161e6e..283b4ed85c 100644 --- a/web_src/js/render/pdf.ts +++ b/web_src/js/render/pdf.ts @@ -1,12 +1,10 @@ import {htmlEscape} from 'escape-goat'; +import {registerGlobalInitFunc} from '../modules/observer.ts'; export async function initPdfViewer() { - const els = document.querySelectorAll('.pdf-content'); - if (!els.length) return; + registerGlobalInitFunc('initPdfViewer', async (el: HTMLInputElement) => { + const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject'); - const pdfobject = await import(/* webpackChunkName: "pdfobject" */'pdfobject'); - - for (const el of els) { const src = el.getAttribute('data-src'); const fallbackText = el.getAttribute('data-fallback-button-text'); pdfobject.embed(src, el, { @@ -15,5 +13,5 @@ export async function initPdfViewer() { `, }); el.classList.remove('is-loading'); - } + }); } diff --git a/web_src/js/utils.test.ts b/web_src/js/utils.test.ts index ccdbc2dbd7..f1025471a4 100644 --- a/web_src/js/utils.test.ts +++ b/web_src/js/utils.test.ts @@ -1,9 +1,15 @@ import { - basename, extname, isObject, stripTags, parseIssueHref, + dirname, basename, extname, isObject, stripTags, parseIssueHref, parseUrl, translateMonth, translateDay, blobToDataURI, toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseRepoOwnerPathInfo, } from './utils.ts'; +test('dirname', () => { + expect(dirname('/path/to/file.js')).toEqual('/path/to'); + expect(dirname('/path/to')).toEqual('/path'); + expect(dirname('file.js')).toEqual(''); +}); + test('basename', () => { expect(basename('/path/to/file.js')).toEqual('file.js'); expect(basename('/path/to/file')).toEqual('file'); diff --git a/web_src/js/utils.ts b/web_src/js/utils.ts index 54f59a2c03..b825a9339d 100644 --- a/web_src/js/utils.ts +++ b/web_src/js/utils.ts @@ -1,6 +1,12 @@ import {decode, encode} from 'uint8-to-base64'; import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts'; +// transform /path/to/file.ext to /path/to +export function dirname(path: string): string { + const lastSlashIndex = path.lastIndexOf('/'); + return lastSlashIndex < 0 ? '' : path.substring(0, lastSlashIndex); +} + // transform /path/to/file.ext to file.ext export function basename(path: string): string { const lastSlashIndex = path.lastIndexOf('/'); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 603f967b34..4d15784e6e 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -355,7 +355,7 @@ export function querySingleVisibleElem(parent: Element, s return candidates.length ? candidates[0] as T : null; } -export function addDelegatedEventListener(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => void | Promise, options?: boolean | AddEventListenerOptions) { +export function addDelegatedEventListener(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable, options?: boolean | AddEventListenerOptions) { parent.addEventListener(type, (e: Event) => { const elem = (e.target as HTMLElement).closest(selector); if (!elem) return; diff --git a/web_src/js/utils/filetree.test.ts b/web_src/js/utils/filetree.test.ts new file mode 100644 index 0000000000..f561cb75f0 --- /dev/null +++ b/web_src/js/utils/filetree.test.ts @@ -0,0 +1,86 @@ +import {mergeChildIfOnlyOneDir, pathListToTree, type File} from './filetree.ts'; + +const emptyList: File[] = []; +const singleFile = [{Name: 'file1'}] as File[]; +const singleDir = [{Name: 'dir1/file1'}] as File[]; +const nestedDir = [{Name: 'dir1/dir2/file1'}] as File[]; +const multiplePathsDisjoint = [{Name: 'dir1/dir2/file1'}, {Name: 'dir3/file2'}] as File[]; +const multiplePathsShared = [{Name: 'dir1/dir2/dir3/file1'}, {Name: 'dir1/file2'}] as File[]; + +test('pathListToTree', () => { + expect(pathListToTree(emptyList)).toEqual([]); + expect(pathListToTree(singleFile)).toEqual([ + {isFile: true, name: 'file1', path: 'file1', file: {Name: 'file1'}}, + ]); + expect(pathListToTree(singleDir)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: true, name: 'file1', path: 'dir1/file1', file: {Name: 'dir1/file1'}}, + ]}, + ]); + expect(pathListToTree(nestedDir)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, + ]}, + ]}, + ]); + expect(pathListToTree(multiplePathsDisjoint)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, + ]}, + ]}, + {isFile: false, name: 'dir3', path: 'dir3', children: [ + {isFile: true, name: 'file2', path: 'dir3/file2', file: {Name: 'dir3/file2'}}, + ]}, + ]); + expect(pathListToTree(multiplePathsShared)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ + {isFile: false, name: 'dir3', path: 'dir1/dir2/dir3', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/dir3/file1', file: {Name: 'dir1/dir2/dir3/file1'}}, + ]}, + ]}, + {isFile: true, name: 'file2', path: 'dir1/file2', file: {Name: 'dir1/file2'}}, + ]}, + ]); +}); + +const mergeChildWrapper = (testCase: File[]) => { + const tree = pathListToTree(testCase); + mergeChildIfOnlyOneDir(tree); + return tree; +}; + +test('mergeChildIfOnlyOneDir', () => { + expect(mergeChildWrapper(emptyList)).toEqual([]); + expect(mergeChildWrapper(singleFile)).toEqual([ + {isFile: true, name: 'file1', path: 'file1', file: {Name: 'file1'}}, + ]); + expect(mergeChildWrapper(singleDir)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: true, name: 'file1', path: 'dir1/file1', file: {Name: 'dir1/file1'}}, + ]}, + ]); + expect(mergeChildWrapper(nestedDir)).toEqual([ + {isFile: false, name: 'dir1/dir2', path: 'dir1/dir2', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, + ]}, + ]); + expect(mergeChildWrapper(multiplePathsDisjoint)).toEqual([ + {isFile: false, name: 'dir1/dir2', path: 'dir1/dir2', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, + ]}, + {isFile: false, name: 'dir3', path: 'dir3', children: [ + {isFile: true, name: 'file2', path: 'dir3/file2', file: {Name: 'dir3/file2'}}, + ]}, + ]); + expect(mergeChildWrapper(multiplePathsShared)).toEqual([ + {isFile: false, name: 'dir1', path: 'dir1', children: [ + {isFile: false, name: 'dir2/dir3', path: 'dir1/dir2/dir3', children: [ + {isFile: true, name: 'file1', path: 'dir1/dir2/dir3/file1', file: {Name: 'dir1/dir2/dir3/file1'}}, + ]}, + {isFile: true, name: 'file2', path: 'dir1/file2', file: {Name: 'dir1/file2'}}, + ]}, + ]); +}); diff --git a/web_src/js/utils/filetree.ts b/web_src/js/utils/filetree.ts new file mode 100644 index 0000000000..35f9f58189 --- /dev/null +++ b/web_src/js/utils/filetree.ts @@ -0,0 +1,85 @@ +import {dirname, basename} from '../utils.ts'; + +export type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'typechange'; + +export type File = { + Name: string; + NameHash: string; + Status: FileStatus; + IsViewed: boolean; + IsSubmodule: boolean; +} + +type DirItem = { + isFile: false; + name: string; + path: string; + + children: Item[]; +} + +type FileItem = { + isFile: true; + name: string; + path: string; + file: File; +} + +export type Item = DirItem | FileItem; + +export function pathListToTree(fileEntries: File[]): Item[] { + const pathToItem = new Map(); + + // init root node + const root: DirItem = {name: '', path: '', isFile: false, children: []}; + pathToItem.set('', root); + + for (const fileEntry of fileEntries) { + const [parentPath, fileName] = [dirname(fileEntry.Name), basename(fileEntry.Name)]; + + let parentItem = pathToItem.get(parentPath); + if (!parentItem) { + parentItem = constructParents(pathToItem, parentPath); + } + + const fileItem: FileItem = {name: fileName, path: fileEntry.Name, isFile: true, file: fileEntry}; + + parentItem.children.push(fileItem); + } + + return root.children; +} + +function constructParents(pathToItem: Map, dirPath: string): DirItem { + const [dirParentPath, dirName] = [dirname(dirPath), basename(dirPath)]; + + let parentItem = pathToItem.get(dirParentPath); + if (!parentItem) { + // if the parent node does not exist, create it + parentItem = constructParents(pathToItem, dirParentPath); + } + + const dirItem: DirItem = {name: dirName, path: dirPath, isFile: false, children: []}; + parentItem.children.push(dirItem); + pathToItem.set(dirPath, dirItem); + + return dirItem; +} + +export function mergeChildIfOnlyOneDir(nodes: Item[]): void { + for (const node of nodes) { + if (node.isFile) { + continue; + } + const dir = node as DirItem; + + mergeChildIfOnlyOneDir(dir.children); + + if (dir.children.length === 1 && dir.children[0].isFile === false) { + const child = dir.children[0]; + dir.name = `${dir.name}/${child.name}`; + dir.path = child.path; + dir.children = child.children; + } + } +}