Merge remote-tracking branch 'origin/main' into richmahn-11835-api-for-git-refs
This commit is contained in:
commit
49ab440cef
187 changed files with 4140 additions and 2783 deletions
20
.drone.yml
20
.drone.yml
|
@ -100,7 +100,7 @@ steps:
|
|||
- name: checks-backend
|
||||
image: golang:1.19
|
||||
commands:
|
||||
- make checks-backend
|
||||
- make --always-make checks-backend # ensure the 'go-licenses' make target runs
|
||||
depends_on: [deps-backend]
|
||||
volumes:
|
||||
- name: deps
|
||||
|
@ -112,16 +112,11 @@ steps:
|
|||
- make test-frontend
|
||||
depends_on: [lint-frontend]
|
||||
|
||||
- name: generate-frontend
|
||||
image: golang:1.19
|
||||
commands:
|
||||
- make generate-frontend
|
||||
|
||||
- name: build-frontend
|
||||
image: node:18
|
||||
commands:
|
||||
- make frontend
|
||||
depends_on: [deps-frontend, generate-frontend]
|
||||
depends_on: [deps-frontend]
|
||||
|
||||
- name: build-backend-no-gcc
|
||||
image: golang:1.18 # this step is kept as the lowest version of golang that we support
|
||||
|
@ -549,16 +544,11 @@ steps:
|
|||
commands:
|
||||
- make deps-frontend
|
||||
|
||||
- name: generate-frontend
|
||||
image: golang:1.18
|
||||
commands:
|
||||
- make generate-frontend
|
||||
|
||||
- name: build-frontend
|
||||
image: node:18
|
||||
commands:
|
||||
- make frontend
|
||||
depends_on: [deps-frontend, generate-frontend]
|
||||
depends_on: [deps-frontend]
|
||||
|
||||
- name: deps-backend
|
||||
image: golang:1.18
|
||||
|
@ -571,9 +561,9 @@ steps:
|
|||
|
||||
# TODO: We should probably build all dependencies into a test image
|
||||
- name: test-e2e
|
||||
image: mcr.microsoft.com/playwright:v1.24.0-focal
|
||||
image: mcr.microsoft.com/playwright:v1.27.0-focal
|
||||
commands:
|
||||
- curl -sLO https://go.dev/dl/go1.18.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
|
||||
- curl -sLO https://go.dev/dl/go1.19.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
|
||||
- groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea
|
||||
- apt-get -qq update && apt-get -qqy install build-essential
|
||||
- export TEST_PGSQL_SCHEMA=''
|
||||
|
|
|
@ -185,6 +185,7 @@ rules:
|
|||
linebreak-style: [2, unix]
|
||||
lines-around-comment: [0]
|
||||
lines-between-class-members: [0]
|
||||
logical-assignment-operators: [0]
|
||||
max-classes-per-file: [0]
|
||||
max-depth: [0]
|
||||
max-len: [0]
|
||||
|
@ -245,7 +246,7 @@ rules:
|
|||
no-floating-decimal: [0]
|
||||
no-func-assign: [2]
|
||||
no-global-assign: [2]
|
||||
no-implicit-coercion: [0]
|
||||
no-implicit-coercion: [2]
|
||||
no-implicit-globals: [0]
|
||||
no-implied-eval: [2]
|
||||
no-import-assign: [2]
|
||||
|
@ -322,7 +323,7 @@ rules:
|
|||
no-unused-private-class-members: [2]
|
||||
no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_, ignoreRestSiblings: false}]
|
||||
no-use-before-define: [2, {functions: false, classes: true, variables: true, allowNamedExports: true}]
|
||||
no-useless-backreference: [0]
|
||||
no-useless-backreference: [2]
|
||||
no-useless-call: [2]
|
||||
no-useless-catch: [2]
|
||||
no-useless-computed-key: [2]
|
||||
|
@ -353,7 +354,7 @@ rules:
|
|||
prefer-named-capture-group: [0]
|
||||
prefer-numeric-literals: [2]
|
||||
prefer-object-has-own: [0]
|
||||
prefer-object-spread: [0]
|
||||
prefer-object-spread: [2]
|
||||
prefer-promise-reject-errors: [2, {allowEmptyReject: false}]
|
||||
prefer-regex-literals: [2]
|
||||
prefer-rest-params: [2]
|
||||
|
@ -455,6 +456,7 @@ rules:
|
|||
unicorn/no-static-only-class: [2]
|
||||
unicorn/no-thenable: [2]
|
||||
unicorn/no-this-assignment: [2]
|
||||
unicorn/no-unnecessary-await: [2]
|
||||
unicorn/no-unreadable-array-destructuring: [0]
|
||||
unicorn/no-unreadable-iife: [2]
|
||||
unicorn/no-unsafe-regex: [0]
|
||||
|
@ -519,6 +521,7 @@ rules:
|
|||
unicorn/require-number-to-fixed-digits-argument: [2]
|
||||
unicorn/require-post-message-target-origin: [0]
|
||||
unicorn/string-content: [0]
|
||||
unicorn/switch-case-braces: [0]
|
||||
unicorn/template-indent: [2]
|
||||
unicorn/text-encoding-identifier-case: [0]
|
||||
unicorn/throw-new-error: [2]
|
||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,5 +1,6 @@
|
|||
* text=auto eol=lf
|
||||
*.tmpl linguist-language=Handlebars
|
||||
/assets/*.json linguist-generated
|
||||
/public/vendor/** -text -eol linguist-vendored
|
||||
/vendor/** -text -eol linguist-vendored
|
||||
/web_src/fomantic/build/** linguist-generated
|
||||
|
|
39
.gitpod.yml
Normal file
39
.gitpod.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
tasks:
|
||||
- name: Setup
|
||||
init: |
|
||||
make deps
|
||||
make build
|
||||
command: |
|
||||
gp sync-done setup
|
||||
exit 0
|
||||
- name: Run frontend
|
||||
command: |
|
||||
gp sync-await setup
|
||||
make watch-frontend
|
||||
- name: Run backend
|
||||
command: |
|
||||
gp sync-await setup
|
||||
mkdir -p custom/conf/
|
||||
echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini
|
||||
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
|
||||
export TAGS="sqlite sqlite_unlock_notify"
|
||||
make watch-backend
|
||||
- name: Run docs
|
||||
before: sudo bash -c "$(grep 'https://github.com/gohugoio/hugo/releases/download' Makefile | tr -d '\')" # install hugo
|
||||
command: cd docs && make clean update && hugo server -D -F --baseUrl $(gp url 1313) --liveReloadPort=443 --appendPort=false --bind=0.0.0.0
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- editorconfig.editorconfig
|
||||
- dbaeumer.vscode-eslint
|
||||
- golang.go
|
||||
- stylelint.vscode-stylelint
|
||||
- DavidAnson.vscode-markdownlint
|
||||
- johnsoncodehk.volar
|
||||
- ms-azuretools.vscode-docker
|
||||
|
||||
ports:
|
||||
- name: Gitea
|
||||
port: 3000
|
||||
- name: Docs
|
||||
port: 1313
|
|
@ -170,17 +170,22 @@ import (
|
|||
|
||||
To maintain understandable code and avoid circular dependencies it is important to have a good structure of the code. The Gitea code is divided into the following parts:
|
||||
|
||||
- **integration:** Integration tests
|
||||
- **models:** Contains the data structures used by xorm to construct database tables. It also contains supporting functions to query and update the database. Dependencies to other code in Gitea should be avoided although some modules might be needed (for example for logging).
|
||||
- **models/fixtures:** Sample model data used in integration tests.
|
||||
- **models/migrations:** Handling of database migrations between versions. PRs that changes a database structure shall also have a migration step.
|
||||
- **modules:** Different modules to handle specific functionality in Gitea.
|
||||
- **modules:** Different modules to handle specific functionality in Gitea. Shall only depend on other modules but not other packages (models, services).
|
||||
- **public:** Frontend files (javascript, images, css, etc.)
|
||||
- **routers:** Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers
|
||||
- **routers:** Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
|
||||
- **services:** Support functions for common routing operations. Uses models and modules to handle the request.
|
||||
- **templates:** Golang templates for generating the html output.
|
||||
- **tests/e2e:** End to end tests
|
||||
- **tests/integration:** Integration tests
|
||||
- **vendor:** External code that Gitea depends on.
|
||||
|
||||
## Documentation
|
||||
|
||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated.
|
||||
|
||||
## API v1
|
||||
|
||||
The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [GitHub API v3](https://developer.github.com/v3/).
|
||||
|
@ -229,27 +234,6 @@ An endpoint which changes/edits an object expects all fields to be optional (exc
|
|||
- support pagination (`page` & `limit` options in query)
|
||||
- set `X-Total-Count` header via **SetTotalCountHeader** ([example](https://github.com/go-gitea/gitea/blob/7aae98cc5d4113f1e9918b7ee7dd09f67c189e3e/routers/api/v1/repo/issue.go#L444))
|
||||
|
||||
## Large Character Comments
|
||||
|
||||
Throughout the codebase there are large-text comments for sections of code, e.g.:
|
||||
|
||||
```go
|
||||
// __________ .__
|
||||
// \______ \ _______ _|__| ______ _ __
|
||||
// | _// __ \ \/ / |/ __ \ \/ \/ /
|
||||
// | | \ ___/\ /| \ ___/\ /
|
||||
// |____|_ /\___ >\_/ |__|\___ >\/\_/
|
||||
// \/ \/ \/
|
||||
```
|
||||
|
||||
These were created using the `figlet` tool with the `graffiti` font.
|
||||
|
||||
A simple way of creating these is to use the following:
|
||||
|
||||
```bash
|
||||
figlet -f graffiti Review | sed -e's+^+// +' - | xclip -sel clip -in
|
||||
```
|
||||
|
||||
## Backports and Frontports
|
||||
|
||||
Occasionally backports of PRs are required.
|
||||
|
|
12
Makefile
12
Makefile
|
@ -341,7 +341,7 @@ lint: lint-frontend lint-backend
|
|||
|
||||
.PHONY: lint-frontend
|
||||
lint-frontend: node_modules
|
||||
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js docs/assets/js tests/e2e/*.test.e2e.js tests/e2e/utils_e2e.js
|
||||
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js docs/assets/js tests/e2e
|
||||
npx stylelint --color --max-warnings=0 web_src/less
|
||||
npx spectral lint -q -F hint $(SWAGGER_SPEC)
|
||||
npx markdownlint docs *.md
|
||||
|
@ -406,6 +406,7 @@ unit-test-coverage:
|
|||
tidy:
|
||||
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
|
||||
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
|
||||
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
|
||||
|
||||
vendor: go.mod go.sum
|
||||
$(GO) mod vendor
|
||||
|
@ -413,7 +414,7 @@ vendor: go.mod go.sum
|
|||
|
||||
.PHONY: tidy-check
|
||||
tidy-check: tidy
|
||||
@diff=$$(git diff go.mod go.sum); \
|
||||
@diff=$$(git diff go.mod go.sum $(GO_LICENSE_FILE)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make tidy' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
|
@ -709,17 +710,14 @@ install: $(wildcard *.go)
|
|||
build: frontend backend
|
||||
|
||||
.PHONY: frontend
|
||||
frontend: generate-frontend $(WEBPACK_DEST)
|
||||
frontend: $(WEBPACK_DEST)
|
||||
|
||||
.PHONY: backend
|
||||
backend: go-check generate-backend $(EXECUTABLE)
|
||||
|
||||
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
|
||||
.PHONY: generate
|
||||
generate: generate-backend generate-frontend
|
||||
|
||||
.PHONY: generate-frontend
|
||||
generate-frontend: $(GO_LICENSE_FILE)
|
||||
generate: generate-backend
|
||||
|
||||
.PHONY: generate-backend
|
||||
generate-backend: $(TAGS_PREREQ) generate-go
|
||||
|
|
|
@ -33,6 +33,12 @@
|
|||
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
|
||||
</a>
|
||||
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
|
||||
alt="Contribute with Gitpod"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/gitea" title="Crowdin">
|
||||
<img src="https://badges.crowdin.net/gitea/localized.svg">
|
||||
</a>
|
||||
|
|
|
@ -33,6 +33,12 @@
|
|||
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
|
||||
</a>
|
||||
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
|
||||
alt="Contribute with Gitpod"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/gitea" title="Crowdin">
|
||||
<img src="https://badges.crowdin.net/gitea/localized.svg">
|
||||
</a>
|
||||
|
|
18
assets/go-licenses.json
generated
18
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
|
@ -49,12 +49,8 @@ After=network.target
|
|||
###
|
||||
|
||||
[Service]
|
||||
# Modify these two values and uncomment them if you have
|
||||
# repos with lots of files and get an HTTP error 500 because
|
||||
# of that
|
||||
###
|
||||
#LimitMEMLOCK=infinity
|
||||
#LimitNOFILE=65535
|
||||
# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that
|
||||
# LimitNOFILE=524288:524288
|
||||
RestartSec=2s
|
||||
Type=simple
|
||||
User=git
|
||||
|
|
|
@ -887,7 +887,7 @@ ROUTER = console
|
|||
;USE_COMPAT_SSH_URI = false
|
||||
;;
|
||||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki
|
||||
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects
|
||||
;DISABLED_REPO_UNITS =
|
||||
;;
|
||||
;; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects.
|
||||
|
|
|
@ -537,9 +537,9 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
|
|||
## Camo (`camo`)
|
||||
|
||||
- `ENABLED`: **false**: Enable media proxy, we support images only at the moment.
|
||||
- `SERVER_URL`: **<empty>**: url of camo server, it **is required** if camo is enabled.
|
||||
- `HMAC_KEY`: **<empty>**: Provide the HMAC key for encoding urls, it **is required** if camo is enabled.
|
||||
- `ALLWAYS`: **false**: Set to true to use camo for https too lese only non https urls are proxyed
|
||||
- `SERVER_URL`: **<empty>**: URL of camo server, it **is required** if camo is enabled.
|
||||
- `HMAC_KEY`: **<empty>**: Provide the HMAC key for encoding URLs, it **is required** if camo is enabled.
|
||||
- `ALLWAYS`: **false**: Set to true to use camo for both HTTP and HTTPS content, otherwise only non-HTTPS URLs are proxied
|
||||
|
||||
## OpenID (`openid`)
|
||||
|
||||
|
|
|
@ -67,22 +67,18 @@ Some actions should allow for rollback when database record insertion/update/del
|
|||
So services must be allowed to create a database transaction. Here is some example,
|
||||
|
||||
```go
|
||||
// servcies/repository/repo.go
|
||||
func CreateXXXX() error {\
|
||||
ctx, committer, err := db.TxContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
// do something, if return err, it will rollback automatically when `committer.Close()` is invoked.
|
||||
if err := issues.UpdateIssue(ctx, repoID); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ......
|
||||
|
||||
return committer.Commit()
|
||||
// services/repository/repository.go
|
||||
func CreateXXXX() error {
|
||||
return db.WithTx(func(ctx context.Context) error {
|
||||
e := db.GetEngine(ctx)
|
||||
// do something, if err is returned, it will rollback automatically
|
||||
if err := issues.UpdateIssue(ctx, repoID); err != nil {
|
||||
// ...
|
||||
return err
|
||||
}
|
||||
// ...
|
||||
return nil
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -94,14 +90,14 @@ If the function will be used in the transaction, just let `context.Context` as t
|
|||
func UpdateIssue(ctx context.Context, repoID int64) error {
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
// ......
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Package Name
|
||||
|
||||
For the top level package, use a plural as package name, i.e. `services`, `models`, for sub packages, use singular,
|
||||
i.e. `servcies/user`, `models/repository`.
|
||||
i.e. `services/user`, `models/repository`.
|
||||
|
||||
### Import Alias
|
||||
|
||||
|
|
|
@ -19,6 +19,12 @@ menu:
|
|||
|
||||
{{< toc >}}
|
||||
|
||||
## Quickstart
|
||||
|
||||
To get a quick working development environment you could use Gitpod.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
|
||||
## Installing go
|
||||
|
||||
You should [install go](https://golang.org/doc/install) and set up your go
|
||||
|
@ -171,7 +177,7 @@ server as mentioned above.
|
|||
|
||||
### Working on JS and CSS
|
||||
|
||||
Frontend development should follow [Guidelines for Frontend Development](./guidelines-frontend.md)
|
||||
Frontend development should follow [Guidelines for Frontend Development]({{< relref "doc/developers/guidelines-frontend.en-us.md" >}})
|
||||
|
||||
To build with frontend resources, either use the `watch-frontend` target mentioned above or just build once:
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
---
|
||||
date: "2021-09-02T16:00:00+08:00"
|
||||
title: "Upgrade from an old Gitea"
|
||||
aliases:
|
||||
- /en-us/upgrade/
|
||||
slug: "upgrade-from-gitea"
|
||||
weight: 10
|
||||
toc: false
|
||||
|
|
4
docs/static/_redirects
vendored
4
docs/static/_redirects
vendored
|
@ -10,3 +10,7 @@ https://gitea-docs.netlify.com/* https://docs.gitea.io/:splat 302!
|
|||
/en-us/ci-cd/ /en-us/integrations/ 302!
|
||||
/en-us/third-party-tools/ /en-us/integrations/ 302!
|
||||
/en-us/make/ /en-us/hacking-on-gitea/ 302!
|
||||
/en-us/upgrade/ /en-us/upgrade-from-gitea/ 302!
|
||||
/fr-fr/upgrade/ /fr-fr/upgrade-from-gitea/ 302!
|
||||
/zh-cn/upgrade/ /zh-cn/upgrade-from-gitea/ 302!
|
||||
/zh-tw/upgrade/ /zh-tw/upgrade-from-gitea/ 302!
|
||||
|
|
4
go.mod
4
go.mod
|
@ -35,7 +35,7 @@ require (
|
|||
github.com/go-ap/jsonld v0.0.0-20220917142617-76bf51585778
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-enry/go-enry/v2 v2.8.2
|
||||
github.com/go-enry/go-enry/v2 v2.8.3
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
||||
github.com/go-git/go-billy/v5 v5.3.1
|
||||
github.com/go-git/go-git/v5 v5.4.3-0.20220529141257-bc1f419cebcf
|
||||
|
@ -98,7 +98,7 @@ require (
|
|||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/text v0.3.8
|
||||
golang.org/x/tools v0.1.12
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
|
7
go.sum
7
go.sum
|
@ -485,8 +485,8 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
|||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-enry/go-enry/v2 v2.8.2 h1:uiGmC+3K8sVd/6DOe2AOJEOihJdqda83nPyJNtMR8RI=
|
||||
github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
|
||||
github.com/go-enry/go-enry/v2 v2.8.3 h1:BwvNrN58JqBJhyyVdZSl5QD3xoxEEGYUrRyPh31FGhw=
|
||||
github.com/go-enry/go-enry/v2 v2.8.3/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
|
||||
|
@ -1891,8 +1891,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// to run tests with ES6 module, node must run with "--experimental-vm-modules", or see Makefile's "test-frontend" for reference
|
||||
export default {
|
||||
rootDir: 'web_src',
|
||||
setupFilesAfterEnv: ['jest-extended/all'],
|
||||
|
@ -7,6 +8,8 @@ export default {
|
|||
transform: {
|
||||
'\\.svg$': '<rootDir>/js/testUtils/jestRawLoader.js',
|
||||
},
|
||||
setupFiles: [
|
||||
'./js/testUtils/jestSetup.js', // prepare global variables used by our code (eg: window.config)
|
||||
],
|
||||
verbose: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -18,13 +18,11 @@ import (
|
|||
type ActionList []*Action
|
||||
|
||||
func (actions ActionList) getUserIDs() []int64 {
|
||||
userIDs := make(map[int64]struct{}, len(actions))
|
||||
userIDs := make(container.Set[int64], len(actions))
|
||||
for _, action := range actions {
|
||||
if _, ok := userIDs[action.ActUserID]; !ok {
|
||||
userIDs[action.ActUserID] = struct{}{}
|
||||
}
|
||||
userIDs.Add(action.ActUserID)
|
||||
}
|
||||
return container.KeysInt64(userIDs)
|
||||
return userIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||
|
@ -48,13 +46,11 @@ func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.
|
|||
}
|
||||
|
||||
func (actions ActionList) getRepoIDs() []int64 {
|
||||
repoIDs := make(map[int64]struct{}, len(actions))
|
||||
repoIDs := make(container.Set[int64], len(actions))
|
||||
for _, action := range actions {
|
||||
if _, ok := repoIDs[action.RepoID]; !ok {
|
||||
repoIDs[action.RepoID] = struct{}{}
|
||||
}
|
||||
repoIDs.Add(action.RepoID)
|
||||
}
|
||||
return container.KeysInt64(repoIDs)
|
||||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadRepositories(ctx context.Context) error {
|
||||
|
|
|
@ -200,7 +200,7 @@ func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID,
|
|||
|
||||
func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
||||
// init
|
||||
var toNotify map[int64]struct{}
|
||||
var toNotify container.Set[int64]
|
||||
notifications, err := getNotificationsByIssueID(ctx, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -212,33 +212,27 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
|
|||
}
|
||||
|
||||
if receiverID > 0 {
|
||||
toNotify = make(map[int64]struct{}, 1)
|
||||
toNotify[receiverID] = struct{}{}
|
||||
toNotify = make(container.Set[int64], 1)
|
||||
toNotify.Add(receiverID)
|
||||
} else {
|
||||
toNotify = make(map[int64]struct{}, 32)
|
||||
toNotify = make(container.Set[int64], 32)
|
||||
issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range issueWatches {
|
||||
toNotify[id] = struct{}{}
|
||||
}
|
||||
toNotify.AddMultiple(issueWatches...)
|
||||
if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) {
|
||||
repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range repoWatches {
|
||||
toNotify[id] = struct{}{}
|
||||
}
|
||||
toNotify.AddMultiple(repoWatches...)
|
||||
}
|
||||
issueParticipants, err := issue.GetParticipantIDsByIssue(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range issueParticipants {
|
||||
toNotify[id] = struct{}{}
|
||||
}
|
||||
toNotify.AddMultiple(issueParticipants...)
|
||||
|
||||
// dont notify user who cause notification
|
||||
delete(toNotify, notificationAuthorID)
|
||||
|
@ -248,7 +242,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
|
|||
return err
|
||||
}
|
||||
for _, id := range issueUnWatches {
|
||||
delete(toNotify, id)
|
||||
toNotify.Remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,16 +493,14 @@ func (nl NotificationList) LoadAttributes() error {
|
|||
}
|
||||
|
||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(nl))
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Repository != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.RepoID]; !ok {
|
||||
ids[notification.RepoID] = struct{}{}
|
||||
}
|
||||
ids.Add(notification.RepoID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadRepos loads repositories from database
|
||||
|
@ -575,16 +567,14 @@ func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error)
|
|||
}
|
||||
|
||||
func (nl NotificationList) getPendingIssueIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(nl))
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.Issue != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.IssueID]; !ok {
|
||||
ids[notification.IssueID] = struct{}{}
|
||||
}
|
||||
ids.Add(notification.IssueID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadIssues loads issues from database
|
||||
|
@ -661,16 +651,14 @@ func (nl NotificationList) Without(failures []int) NotificationList {
|
|||
}
|
||||
|
||||
func (nl NotificationList) getPendingCommentIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(nl))
|
||||
ids := make(container.Set[int64], len(nl))
|
||||
for _, notification := range nl {
|
||||
if notification.CommentID == 0 || notification.Comment != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[notification.CommentID]; !ok {
|
||||
ids[notification.CommentID] = struct{}{}
|
||||
}
|
||||
ids.Add(notification.CommentID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadComments loads comments from database
|
||||
|
|
|
@ -225,7 +225,8 @@ func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error
|
|||
|
||||
func deleteOAuth2Application(ctx context.Context, id, userid int64) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
if deleted, err := sess.Delete(&OAuth2Application{ID: id, UID: userid}); err != nil {
|
||||
// the userid could be 0 if the app is instance-wide
|
||||
if deleted, err := sess.Where(builder.Eq{"id": id, "uid": userid}).Delete(&OAuth2Application{}); err != nil {
|
||||
return err
|
||||
} else if deleted == 0 {
|
||||
return ErrOAuthApplicationNotFound{ID: id}
|
||||
|
@ -476,7 +477,7 @@ func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, er
|
|||
|
||||
// RevokeOAuth2Grant deletes the grant with grantID and userID
|
||||
func RevokeOAuth2Grant(ctx context.Context, grantID, userID int64) error {
|
||||
_, err := db.DeleteByBean(ctx, &OAuth2Grant{ID: grantID, UserID: userID})
|
||||
_, err := db.GetEngine(ctx).Where(builder.Eq{"id": grantID, "user_id": userID}).Delete(&OAuth2Grant{})
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
name: "Test"
|
||||
client_id: "da7da3ba-9a13-4167-856f-3899de0b0138"
|
||||
client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
|
||||
redirect_uris: '["a"]'
|
||||
redirect_uris: '["a", "https://example.com/xyzzy"]'
|
||||
created_unix: 1546869730
|
||||
updated_unix: 1546869730
|
||||
|
|
|
@ -17,13 +17,11 @@ import (
|
|||
type CommentList []*Comment
|
||||
|
||||
func (comments CommentList) getPosterIDs() []int64 {
|
||||
posterIDs := make(map[int64]struct{}, len(comments))
|
||||
posterIDs := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := posterIDs[comment.PosterID]; !ok {
|
||||
posterIDs[comment.PosterID] = struct{}{}
|
||||
}
|
||||
posterIDs.Add(comment.PosterID)
|
||||
}
|
||||
return container.KeysInt64(posterIDs)
|
||||
return posterIDs.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadPosters(ctx context.Context) error {
|
||||
|
@ -70,13 +68,11 @@ func (comments CommentList) getCommentIDs() []int64 {
|
|||
}
|
||||
|
||||
func (comments CommentList) getLabelIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := ids[comment.LabelID]; !ok {
|
||||
ids[comment.LabelID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.LabelID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
|
||||
|
@ -120,13 +116,11 @@ func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
|
|||
}
|
||||
|
||||
func (comments CommentList) getMilestoneIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := ids[comment.MilestoneID]; !ok {
|
||||
ids[comment.MilestoneID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.MilestoneID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadMilestones(ctx context.Context) error {
|
||||
|
@ -163,13 +157,11 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (comments CommentList) getOldMilestoneIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := ids[comment.OldMilestoneID]; !ok {
|
||||
ids[comment.OldMilestoneID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.OldMilestoneID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
||||
|
@ -206,13 +198,11 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (comments CommentList) getAssigneeIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := ids[comment.AssigneeID]; !ok {
|
||||
ids[comment.AssigneeID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.AssigneeID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadAssignees(ctx context.Context) error {
|
||||
|
@ -259,16 +249,14 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
|
|||
|
||||
// getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
|
||||
func (comments CommentList) getIssueIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if comment.Issue != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[comment.IssueID]; !ok {
|
||||
ids[comment.IssueID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.IssueID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// Issues returns all the issues of comments
|
||||
|
@ -334,16 +322,14 @@ func (comments CommentList) loadIssues(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (comments CommentList) getDependentIssueIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if comment.DependentIssue != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := ids[comment.DependentIssueID]; !ok {
|
||||
ids[comment.DependentIssueID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.DependentIssueID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
||||
|
@ -439,13 +425,11 @@ func (comments CommentList) loadAttachments(ctx context.Context) (err error) {
|
|||
}
|
||||
|
||||
func (comments CommentList) getReviewIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(comments))
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if _, ok := ids[comment.ReviewID]; !ok {
|
||||
ids[comment.ReviewID] = struct{}{}
|
||||
}
|
||||
ids.Add(comment.ReviewID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (comments CommentList) loadReviews(ctx context.Context) error { //nolint
|
||||
|
|
|
@ -22,16 +22,16 @@ type IssueList []*Issue
|
|||
|
||||
// get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo
|
||||
func (issues IssueList) getRepoIDs() []int64 {
|
||||
repoIDs := make(map[int64]struct{}, len(issues))
|
||||
repoIDs := make(container.Set[int64], len(issues))
|
||||
for _, issue := range issues {
|
||||
if issue.Repo == nil {
|
||||
repoIDs[issue.RepoID] = struct{}{}
|
||||
repoIDs.Add(issue.RepoID)
|
||||
}
|
||||
if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil {
|
||||
repoIDs[issue.PullRequest.HeadRepoID] = struct{}{}
|
||||
repoIDs.Add(issue.PullRequest.HeadRepoID)
|
||||
}
|
||||
}
|
||||
return container.KeysInt64(repoIDs)
|
||||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Repository, error) {
|
||||
|
@ -79,13 +79,11 @@ func (issues IssueList) LoadRepositories() ([]*repo_model.Repository, error) {
|
|||
}
|
||||
|
||||
func (issues IssueList) getPosterIDs() []int64 {
|
||||
posterIDs := make(map[int64]struct{}, len(issues))
|
||||
posterIDs := make(container.Set[int64], len(issues))
|
||||
for _, issue := range issues {
|
||||
if _, ok := posterIDs[issue.PosterID]; !ok {
|
||||
posterIDs[issue.PosterID] = struct{}{}
|
||||
}
|
||||
posterIDs.Add(issue.PosterID)
|
||||
}
|
||||
return container.KeysInt64(posterIDs)
|
||||
return posterIDs.Values()
|
||||
}
|
||||
|
||||
func (issues IssueList) loadPosters(ctx context.Context) error {
|
||||
|
@ -185,13 +183,11 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (issues IssueList) getMilestoneIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(issues))
|
||||
ids := make(container.Set[int64], len(issues))
|
||||
for _, issue := range issues {
|
||||
if _, ok := ids[issue.MilestoneID]; !ok {
|
||||
ids[issue.MilestoneID] = struct{}{}
|
||||
}
|
||||
ids.Add(issue.MilestoneID)
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (issues IssueList) loadMilestones(ctx context.Context) error {
|
||||
|
@ -224,14 +220,11 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (issues IssueList) getProjectIDs() []int64 {
|
||||
ids := make(map[int64]struct{}, len(issues))
|
||||
ids := make(container.Set[int64], len(issues))
|
||||
for _, issue := range issues {
|
||||
projectID := issue.ProjectID()
|
||||
if _, ok := ids[projectID]; !ok {
|
||||
ids[projectID] = struct{}{}
|
||||
}
|
||||
ids.Add(issue.ProjectID())
|
||||
}
|
||||
return container.KeysInt64(ids)
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
func (issues IssueList) loadProjects(ctx context.Context) error {
|
||||
|
|
|
@ -211,7 +211,7 @@ type ReactionOptions struct {
|
|||
|
||||
// CreateReaction creates reaction for issue or comment.
|
||||
func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
|
||||
if !setting.UI.ReactionsMap[opts.Type] {
|
||||
if !setting.UI.ReactionsLookup.Contains(opts.Type) {
|
||||
return nil, ErrForbiddenIssueReaction{opts.Type}
|
||||
}
|
||||
|
||||
|
@ -316,16 +316,14 @@ func (list ReactionList) GroupByType() map[string]ReactionList {
|
|||
}
|
||||
|
||||
func (list ReactionList) getUserIDs() []int64 {
|
||||
userIDs := make(map[int64]struct{}, len(list))
|
||||
userIDs := make(container.Set[int64], len(list))
|
||||
for _, reaction := range list {
|
||||
if reaction.OriginalAuthor != "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := userIDs[reaction.UserID]; !ok {
|
||||
userIDs[reaction.UserID] = struct{}{}
|
||||
}
|
||||
userIDs.Add(reaction.UserID)
|
||||
}
|
||||
return container.KeysInt64(userIDs)
|
||||
return userIDs.Values()
|
||||
}
|
||||
|
||||
func valuesUser(m map[int64]*user_model.User) []*user_model.User {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
|
@ -99,9 +100,9 @@ func InsertIssueComments(comments []*issues_model.Comment) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
issueIDs := make(map[int64]bool)
|
||||
issueIDs := make(container.Set[int64])
|
||||
for _, comment := range comments {
|
||||
issueIDs[comment.IssueID] = true
|
||||
issueIDs.Add(comment.IssueID)
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext()
|
||||
|
|
|
@ -413,6 +413,8 @@ var migrations = []Migration{
|
|||
NewMigration("Add badges to users", createUserBadgesTable),
|
||||
// v225 -> v226
|
||||
NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText),
|
||||
// v226 -> v227
|
||||
NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -39,7 +40,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
|
|||
}
|
||||
log.Info("%d User Avatar(s) to migrate ...", count)
|
||||
|
||||
deleteList := make(map[string]struct{})
|
||||
deleteList := make(container.Set[string])
|
||||
start := 0
|
||||
migrated := 0
|
||||
for {
|
||||
|
@ -86,7 +87,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
|
|||
return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err)
|
||||
}
|
||||
|
||||
deleteList[filepath.Join(setting.Avatar.Path, oldAvatar)] = struct{}{}
|
||||
deleteList.Add(filepath.Join(setting.Avatar.Path, oldAvatar))
|
||||
migrated++
|
||||
select {
|
||||
case <-ticker.C:
|
||||
|
|
15
models/migrations/v226.go
Normal file
15
models/migrations/v226.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func fixPackageSemverField(x *xorm.Engine) error {
|
||||
_, err := x.Exec(builder.Update(builder.Eq{"semver_compatible": false}).From("`package`").Where(builder.In("`type`", "conan", "generic")))
|
||||
return err
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
conan_module "code.gitea.io/gitea/modules/packages/conan"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
@ -88,7 +89,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
|
|||
return nil, err
|
||||
}
|
||||
|
||||
unique := make(map[string]bool)
|
||||
unique := make(container.Set[string])
|
||||
for _, info := range results {
|
||||
recipe := fmt.Sprintf("%s/%s", info.Name, info.Version)
|
||||
|
||||
|
@ -111,7 +112,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
|
|||
}
|
||||
}
|
||||
|
||||
unique[recipe] = true
|
||||
unique.Add(recipe)
|
||||
}
|
||||
|
||||
recipes := make([]string, 0, len(unique))
|
||||
|
|
|
@ -438,15 +438,27 @@ func CheckRepoStats(ctx context.Context) error {
|
|||
repoStatsCorrectNumStars,
|
||||
"repository count 'num_stars'",
|
||||
},
|
||||
// Repository.NumIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, false),
|
||||
repoStatsCorrectNumIssues,
|
||||
"repository count 'num_issues'",
|
||||
},
|
||||
// Repository.NumClosedIssues
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false),
|
||||
repoStatsCorrectNumClosedIssues,
|
||||
"repository count 'num_closed_issues'",
|
||||
},
|
||||
// Repository.NumPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", false, true),
|
||||
repoStatsCorrectNumPulls,
|
||||
"repository count 'num_pulls'",
|
||||
},
|
||||
// Repository.NumClosedPulls
|
||||
{
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
|
||||
statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
|
||||
repoStatsCorrectNumClosedPulls,
|
||||
"repository count 'num_closed_pulls'",
|
||||
},
|
||||
|
|
|
@ -68,10 +68,10 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
set := make(map[int64]struct{})
|
||||
set := make(container.Set[int64])
|
||||
repoIDs := make([]int64, len(repos))
|
||||
for i := range repos {
|
||||
set[repos[i].OwnerID] = struct{}{}
|
||||
set.Add(repos[i].OwnerID)
|
||||
repoIDs[i] = repos[i].ID
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
|
|||
users := make(map[int64]*user_model.User, len(set))
|
||||
if err := db.GetEngine(ctx).
|
||||
Where("id > 0").
|
||||
In("id", container.KeysInt64(set)).
|
||||
In("id", set.Values()).
|
||||
Find(&users); err != nil {
|
||||
return fmt.Errorf("find users: %v", err)
|
||||
}
|
||||
|
@ -593,6 +593,16 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
|
|||
return sess, count, nil
|
||||
}
|
||||
|
||||
// SearchRepositoryIDsByCondition search repository IDs by given condition.
|
||||
func SearchRepositoryIDsByCondition(ctx context.Context, cond builder.Cond) ([]int64, error) {
|
||||
repoIDs := make([]int64, 0, 10)
|
||||
return repoIDs, db.GetEngine(ctx).
|
||||
Table("repository").
|
||||
Cols("id").
|
||||
Where(cond).
|
||||
Find(&repoIDs)
|
||||
}
|
||||
|
||||
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
|
||||
func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
|
@ -680,16 +690,16 @@ func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder {
|
|||
}
|
||||
|
||||
// FindUserCodeAccessibleRepoIDs finds all at Code level accessible repositories' ID by the user's id
|
||||
func FindUserCodeAccessibleRepoIDs(user *user_model.User) ([]int64, error) {
|
||||
repoIDs := make([]int64, 0, 10)
|
||||
if err := db.GetEngine(db.DefaultContext).
|
||||
Table("repository").
|
||||
Cols("id").
|
||||
Where(AccessibleRepositoryCondition(user, unit.TypeCode)).
|
||||
Find(&repoIDs); err != nil {
|
||||
return nil, fmt.Errorf("FindUserCodeAccesibleRepoIDs: %v", err)
|
||||
}
|
||||
return repoIDs, nil
|
||||
func FindUserCodeAccessibleRepoIDs(ctx context.Context, user *user_model.User) ([]int64, error) {
|
||||
return SearchRepositoryIDsByCondition(ctx, AccessibleRepositoryCondition(user, unit.TypeCode))
|
||||
}
|
||||
|
||||
// FindUserCodeAccessibleOwnerRepoIDs finds all repository IDs for the given owner whose code the user can see.
|
||||
func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user *user_model.User) ([]int64, error) {
|
||||
return SearchRepositoryIDsByCondition(ctx, builder.NewCond().And(
|
||||
builder.Eq{"owner_id": ownerID},
|
||||
AccessibleRepositoryCondition(user, unit.TypeCode),
|
||||
))
|
||||
}
|
||||
|
||||
// GetUserRepositories returns a list of repositories of given user.
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
@ -62,7 +63,7 @@ func ValidateTopic(topic string) bool {
|
|||
// SanitizeAndValidateTopics sanitizes and checks an array or topics
|
||||
func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []string) {
|
||||
validTopics = make([]string, 0)
|
||||
mValidTopics := make(map[string]struct{})
|
||||
mValidTopics := make(container.Set[string])
|
||||
invalidTopics = make([]string, 0)
|
||||
|
||||
for _, topic := range topics {
|
||||
|
@ -72,12 +73,12 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st
|
|||
continue
|
||||
}
|
||||
// ignore same topic twice
|
||||
if _, ok := mValidTopics[topic]; ok {
|
||||
if mValidTopics.Contains(topic) {
|
||||
continue
|
||||
}
|
||||
if ValidateTopic(topic) {
|
||||
validTopics = append(validTopics, topic)
|
||||
mValidTopics[topic] = struct{}{}
|
||||
mValidTopics.Add(topic)
|
||||
} else {
|
||||
invalidTopics = append(invalidTopics, topic)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
@ -83,37 +84,19 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||
return nil, err
|
||||
}
|
||||
|
||||
uidMap := map[int64]bool{}
|
||||
i := 0
|
||||
for _, uid := range userIDs {
|
||||
if uidMap[uid] {
|
||||
continue
|
||||
}
|
||||
uidMap[uid] = true
|
||||
userIDs[i] = uid
|
||||
i++
|
||||
}
|
||||
userIDs = userIDs[:i]
|
||||
userIDs = append(userIDs, additionalUserIDs...)
|
||||
|
||||
for _, uid := range additionalUserIDs {
|
||||
if uidMap[uid] {
|
||||
continue
|
||||
}
|
||||
userIDs[i] = uid
|
||||
i++
|
||||
}
|
||||
userIDs = userIDs[:i]
|
||||
uniqueUserIDs := make(container.Set[int64])
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(userIDs)+1)
|
||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||
if len(userIDs) > 0 {
|
||||
if err = e.In("id", userIDs).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
|
||||
if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
|
||||
if !repo.Owner.IsOrganization() && !uniqueUserIDs.Contains(repo.OwnerID) {
|
||||
users = append(users, repo.Owner)
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ func (err ErrEmailCharIsNotSupported) Error() string {
|
|||
}
|
||||
|
||||
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
|
||||
// or has a leading '-' character
|
||||
type ErrEmailInvalid struct {
|
||||
Email string
|
||||
}
|
||||
|
@ -134,9 +135,7 @@ func ValidateEmail(email string) error {
|
|||
return ErrEmailCharIsNotSupported{email}
|
||||
}
|
||||
|
||||
if !(email[0] >= 'a' && email[0] <= 'z') &&
|
||||
!(email[0] >= 'A' && email[0] <= 'Z') &&
|
||||
!(email[0] >= '0' && email[0] <= '9') {
|
||||
if email[0] == '-' {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
|
|
|
@ -281,23 +281,25 @@ func TestEmailAddressValidate(t *testing.T) {
|
|||
`first~last@iana.org`: nil,
|
||||
`first;last@iana.org`: user_model.ErrEmailCharIsNotSupported{`first;last@iana.org`},
|
||||
".233@qq.com": user_model.ErrEmailInvalid{".233@qq.com"},
|
||||
"!233@qq.com": user_model.ErrEmailInvalid{"!233@qq.com"},
|
||||
"#233@qq.com": user_model.ErrEmailInvalid{"#233@qq.com"},
|
||||
"$233@qq.com": user_model.ErrEmailInvalid{"$233@qq.com"},
|
||||
"%233@qq.com": user_model.ErrEmailInvalid{"%233@qq.com"},
|
||||
"&233@qq.com": user_model.ErrEmailInvalid{"&233@qq.com"},
|
||||
"'233@qq.com": user_model.ErrEmailInvalid{"'233@qq.com"},
|
||||
"*233@qq.com": user_model.ErrEmailInvalid{"*233@qq.com"},
|
||||
"+233@qq.com": user_model.ErrEmailInvalid{"+233@qq.com"},
|
||||
"/233@qq.com": user_model.ErrEmailInvalid{"/233@qq.com"},
|
||||
"=233@qq.com": user_model.ErrEmailInvalid{"=233@qq.com"},
|
||||
"?233@qq.com": user_model.ErrEmailInvalid{"?233@qq.com"},
|
||||
"^233@qq.com": user_model.ErrEmailInvalid{"^233@qq.com"},
|
||||
"`233@qq.com": user_model.ErrEmailInvalid{"`233@qq.com"},
|
||||
"{233@qq.com": user_model.ErrEmailInvalid{"{233@qq.com"},
|
||||
"|233@qq.com": user_model.ErrEmailInvalid{"|233@qq.com"},
|
||||
"}233@qq.com": user_model.ErrEmailInvalid{"}233@qq.com"},
|
||||
"~233@qq.com": user_model.ErrEmailInvalid{"~233@qq.com"},
|
||||
"!233@qq.com": nil,
|
||||
"#233@qq.com": nil,
|
||||
"$233@qq.com": nil,
|
||||
"%233@qq.com": nil,
|
||||
"&233@qq.com": nil,
|
||||
"'233@qq.com": nil,
|
||||
"*233@qq.com": nil,
|
||||
"+233@qq.com": nil,
|
||||
"-233@qq.com": user_model.ErrEmailInvalid{"-233@qq.com"},
|
||||
"/233@qq.com": nil,
|
||||
"=233@qq.com": nil,
|
||||
"?233@qq.com": nil,
|
||||
"^233@qq.com": nil,
|
||||
"_233@qq.com": nil,
|
||||
"`233@qq.com": nil,
|
||||
"{233@qq.com": nil,
|
||||
"|233@qq.com": nil,
|
||||
"}233@qq.com": nil,
|
||||
"~233@qq.com": nil,
|
||||
";233@qq.com": user_model.ErrEmailCharIsNotSupported{";233@qq.com"},
|
||||
"Foo <foo@bar.com>": user_model.ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
|
||||
string([]byte{0xE2, 0x84, 0xAA}): user_model.ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
|
||||
|
|
|
@ -241,15 +241,6 @@ func Int64sToStrings(ints []int64) []string {
|
|||
return strs
|
||||
}
|
||||
|
||||
// Int64sToMap converts a slice of int64 to a int64 map.
|
||||
func Int64sToMap(ints []int64) map[int64]bool {
|
||||
m := make(map[int64]bool)
|
||||
for _, i := range ints {
|
||||
m[i] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Int64sContains returns if a int64 in a slice of int64
|
||||
func Int64sContains(intsSlice []int64, a int64) bool {
|
||||
for _, c := range intsSlice {
|
||||
|
|
|
@ -214,16 +214,7 @@ func TestInt64sToStrings(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
func TestInt64sToMap(t *testing.T) {
|
||||
assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
|
||||
assert.Equal(t,
|
||||
map[int64]bool{1: true, 4: true, 16: true},
|
||||
Int64sToMap([]int64{1, 4, 16}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestInt64sContains(t *testing.T) {
|
||||
assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
|
||||
assert.True(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 1))
|
||||
assert.True(t, Int64sContains([]int64{2323}, 2323))
|
||||
assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232))
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package container
|
||||
|
||||
// KeysInt64 returns keys slice for a map with int64 key
|
||||
func KeysInt64(m map[int64]struct{}) []int64 {
|
||||
keys := make([]int64, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
57
modules/container/set.go
Normal file
57
modules/container/set.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package container
|
||||
|
||||
type Set[T comparable] map[T]struct{}
|
||||
|
||||
// SetOf creates a set and adds the specified elements to it.
|
||||
func SetOf[T comparable](values ...T) Set[T] {
|
||||
s := make(Set[T], len(values))
|
||||
s.AddMultiple(values...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Add adds the specified element to a set.
|
||||
// Returns true if the element is added; false if the element is already present.
|
||||
func (s Set[T]) Add(value T) bool {
|
||||
if _, has := s[value]; !has {
|
||||
s[value] = struct{}{}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddMultiple adds the specified elements to a set.
|
||||
func (s Set[T]) AddMultiple(values ...T) {
|
||||
for _, value := range values {
|
||||
s.Add(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains determines whether a set contains the specified element.
|
||||
// Returns true if the set contains the specified element; otherwise, false.
|
||||
func (s Set[T]) Contains(value T) bool {
|
||||
_, has := s[value]
|
||||
return has
|
||||
}
|
||||
|
||||
// Remove removes the specified element.
|
||||
// Returns true if the element is successfully found and removed; otherwise, false.
|
||||
func (s Set[T]) Remove(value T) bool {
|
||||
if _, has := s[value]; has {
|
||||
delete(s, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Values gets a list of all elements in the set.
|
||||
func (s Set[T]) Values() []T {
|
||||
keys := make([]T, 0, len(s))
|
||||
for k := range s {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
37
modules/container/set_test.go
Normal file
37
modules/container/set_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
s := make(Set[string])
|
||||
|
||||
assert.True(t, s.Add("key1"))
|
||||
assert.False(t, s.Add("key1"))
|
||||
assert.True(t, s.Add("key2"))
|
||||
|
||||
assert.True(t, s.Contains("key1"))
|
||||
assert.True(t, s.Contains("key2"))
|
||||
assert.False(t, s.Contains("key3"))
|
||||
|
||||
assert.True(t, s.Remove("key2"))
|
||||
assert.False(t, s.Contains("key2"))
|
||||
|
||||
assert.False(t, s.Remove("key3"))
|
||||
|
||||
s.AddMultiple("key4", "key5")
|
||||
assert.True(t, s.Contains("key4"))
|
||||
assert.True(t, s.Contains("key5"))
|
||||
|
||||
s = SetOf("key6", "key7")
|
||||
assert.False(t, s.Contains("key1"))
|
||||
assert.True(t, s.Contains("key6"))
|
||||
assert.True(t, s.Contains("key7"))
|
||||
}
|
|
@ -130,6 +130,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
||||
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
||||
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
||||
is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid)
|
||||
return is
|
||||
|
|
|
@ -451,11 +451,20 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
|
|||
owner, err = user_model.GetUserByName(ctx, userName)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
// go-get does not support redirects
|
||||
// https://github.com/golang/go/issues/19760
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.NotFound("GetUserByName", nil)
|
||||
|
||||
if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
|
||||
RedirectToUser(ctx, userName, redirectUserID)
|
||||
} else if user_model.IsErrUserRedirectNotExist(err) {
|
||||
ctx.NotFound("GetUserByName", nil)
|
||||
} else {
|
||||
ctx.ServerError("LookupUserRedirect", err)
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
|
|
|
@ -412,7 +412,7 @@ func ToLFSLock(l *git_model.LFSLock) *api.LFSLock {
|
|||
Path: l.Path,
|
||||
LockedAt: l.Created.Round(time.Second),
|
||||
Owner: &api.LFSLockOwner{
|
||||
Name: u.DisplayName(),
|
||||
Name: u.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func ToPayloadCommit(repo *repo_model.Repository, c *git.Commit) *api.PayloadCom
|
|||
}
|
||||
|
||||
// ToCommit convert a git.Commit to api.Commit
|
||||
func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User) (*api.Commit, error) {
|
||||
func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, stat bool) (*api.Commit, error) {
|
||||
var apiAuthor, apiCommitter *api.User
|
||||
|
||||
// Retrieve author and committer information
|
||||
|
@ -133,28 +133,7 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
|
|||
}
|
||||
}
|
||||
|
||||
// Retrieve files affected by the commit
|
||||
fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
|
||||
for _, files := range [][]string{fileStatus.Added, fileStatus.Removed, fileStatus.Modified} {
|
||||
for _, filename := range files {
|
||||
affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
|
||||
Filename: filename,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
|
||||
AfterCommitID: commit.ID.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.Commit{
|
||||
res := &api.Commit{
|
||||
CommitMeta: &api.CommitMeta{
|
||||
URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
|
||||
SHA: commit.ID.String(),
|
||||
|
@ -188,11 +167,37 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
|
|||
Author: apiAuthor,
|
||||
Committer: apiCommitter,
|
||||
Parents: apiParents,
|
||||
Files: affectedFileList,
|
||||
Stats: &api.CommitStats{
|
||||
}
|
||||
|
||||
// Retrieve files affected by the commit
|
||||
if stat {
|
||||
fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
|
||||
for _, files := range [][]string{fileStatus.Added, fileStatus.Removed, fileStatus.Modified} {
|
||||
for _, filename := range files {
|
||||
affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
|
||||
Filename: filename,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
|
||||
AfterCommitID: commit.ID.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Files = affectedFileList
|
||||
res.Stats = &api.CommitStats{
|
||||
Total: diff.TotalAddition + diff.TotalDeletion,
|
||||
Additions: diff.TotalAddition,
|
||||
Deletions: diff.TotalDeletion,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -56,9 +56,10 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
|
|||
config := unit.ExternalTrackerConfig()
|
||||
hasIssues = true
|
||||
externalTracker = &api.ExternalTracker{
|
||||
ExternalTrackerURL: config.ExternalTrackerURL,
|
||||
ExternalTrackerFormat: config.ExternalTrackerFormat,
|
||||
ExternalTrackerStyle: config.ExternalTrackerStyle,
|
||||
ExternalTrackerURL: config.ExternalTrackerURL,
|
||||
ExternalTrackerFormat: config.ExternalTrackerFormat,
|
||||
ExternalTrackerStyle: config.ExternalTrackerStyle,
|
||||
ExternalTrackerRegexpPattern: config.ExternalTrackerRegexpPattern,
|
||||
}
|
||||
}
|
||||
hasWiki := false
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
@ -40,7 +41,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
linesInAuthorizedKeys := map[string]bool{}
|
||||
linesInAuthorizedKeys := make(container.Set[string])
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
|
@ -48,7 +49,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
|
|||
if strings.HasPrefix(line, tplCommentPrefix) {
|
||||
continue
|
||||
}
|
||||
linesInAuthorizedKeys[line] = true
|
||||
linesInAuthorizedKeys.Add(line)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
|
@ -64,7 +65,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
|
|||
if strings.HasPrefix(line, tplCommentPrefix) {
|
||||
continue
|
||||
}
|
||||
if ok := linesInAuthorizedKeys[line]; ok {
|
||||
if linesInAuthorizedKeys.Contains(line) {
|
||||
continue
|
||||
}
|
||||
if !autofix {
|
||||
|
|
91
modules/doctor/heads.go
Normal file
91
modules/doctor/heads.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||
numRepos := 0
|
||||
numHeadsBroken := 0
|
||||
numDefaultBranchesBroken := 0
|
||||
numReposUpdated := 0
|
||||
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||
numRepos++
|
||||
runOpts := &git.RunOpts{Dir: repo.RepoPath()}
|
||||
|
||||
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse", "--", repo.DefaultBranch).RunStdString(runOpts)
|
||||
|
||||
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(runOpts)
|
||||
|
||||
// what we expect: default branch is valid, and HEAD points to it
|
||||
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
|
||||
return nil
|
||||
}
|
||||
|
||||
if headErr != nil {
|
||||
numHeadsBroken++
|
||||
}
|
||||
if defaultBranchErr != nil {
|
||||
numDefaultBranchesBroken++
|
||||
}
|
||||
|
||||
// if default branch is broken, let the user fix that in the UI
|
||||
if defaultBranchErr != nil {
|
||||
logger.Warn("Default branch for %s/%s doesn't point to a valid commit", repo.OwnerName, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we're not autofixing, that's all we can do
|
||||
if !autofix {
|
||||
return nil
|
||||
}
|
||||
|
||||
// otherwise, let's try fixing HEAD
|
||||
err := git.NewCommand(ctx, "symbolic-ref", "--", "HEAD", repo.DefaultBranch).Run(runOpts)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
|
||||
return nil
|
||||
}
|
||||
numReposUpdated++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Critical("Error when fixing repo HEADs: %v", err)
|
||||
}
|
||||
|
||||
if autofix {
|
||||
logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Info("All %d repos have their HEADs in the correct state")
|
||||
} else {
|
||||
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
|
||||
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
|
||||
} else if numHeadsBroken != 0 && numDefaultBranchesBroken == 0 {
|
||||
logger.Warn("HEADs are broken for %d/%d repos", numHeadsBroken, numRepos)
|
||||
} else {
|
||||
logger.Critical("Out of %d repos, HEADS are broken for %d and default branches are broken for %d", numRepos, numHeadsBroken, numDefaultBranchesBroken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&Check{
|
||||
Title: "Synchronize repo HEADs",
|
||||
Name: "synchronize-repo-heads",
|
||||
IsDefault: true,
|
||||
Run: synchronizeRepoHeads,
|
||||
Priority: 7,
|
||||
})
|
||||
}
|
|
@ -14,6 +14,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
|
||||
"github.com/djherbis/buffer"
|
||||
"github.com/djherbis/nio/v3"
|
||||
)
|
||||
|
@ -339,7 +341,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st
|
|||
lastEmptyParent := head.ID.String()
|
||||
commitSinceLastEmptyParent := uint64(0)
|
||||
commitSinceNextRestart := uint64(0)
|
||||
parentRemaining := map[string]bool{}
|
||||
parentRemaining := make(container.Set[string])
|
||||
|
||||
changed := make([]bool, len(paths))
|
||||
|
||||
|
@ -365,7 +367,7 @@ heaploop:
|
|||
if current == nil {
|
||||
break heaploop
|
||||
}
|
||||
delete(parentRemaining, current.CommitID)
|
||||
parentRemaining.Remove(current.CommitID)
|
||||
if current.Paths != nil {
|
||||
for i, found := range current.Paths {
|
||||
if !found {
|
||||
|
@ -410,14 +412,12 @@ heaploop:
|
|||
}
|
||||
}
|
||||
g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...)
|
||||
parentRemaining = map[string]bool{}
|
||||
parentRemaining = make(container.Set[string])
|
||||
nextRestart = (remaining * 3) / 4
|
||||
continue heaploop
|
||||
}
|
||||
}
|
||||
for _, parent := range current.ParentIDs {
|
||||
parentRemaining[parent] = true
|
||||
}
|
||||
parentRemaining.AddMultiple(current.ParentIDs...)
|
||||
}
|
||||
g.Close()
|
||||
|
||||
|
|
|
@ -22,70 +22,72 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
|
|||
return parseTreeEntries(data, nil)
|
||||
}
|
||||
|
||||
var sepSpace = []byte{' '}
|
||||
|
||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
|
||||
entries := make([]*TreeEntry, 0, 10)
|
||||
var err error
|
||||
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
|
||||
for pos := 0; pos < len(data); {
|
||||
// expect line to be of the form "<mode> <type> <sha> <space-padded-size>\t<filename>"
|
||||
// expect line to be of the form:
|
||||
// <mode> <type> <sha> <space-padded-size>\t<filename>
|
||||
// <mode> <type> <sha>\t<filename>
|
||||
posEnd := bytes.IndexByte(data[pos:], '\n')
|
||||
if posEnd == -1 {
|
||||
posEnd = len(data)
|
||||
} else {
|
||||
posEnd += pos
|
||||
}
|
||||
line := data[pos:posEnd]
|
||||
posTab := bytes.IndexByte(line, '\t')
|
||||
if posTab == -1 {
|
||||
return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
|
||||
}
|
||||
|
||||
entry := new(TreeEntry)
|
||||
entry.ptree = ptree
|
||||
if pos+6 > len(data) {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
|
||||
entryAttrs := line[:posTab]
|
||||
entryName := line[posTab+1:]
|
||||
|
||||
entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
|
||||
_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
|
||||
entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
|
||||
if len(entryAttrs) > 0 {
|
||||
entrySize := entryAttrs // the last field is the space-padded-size
|
||||
entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
|
||||
entry.sized = true
|
||||
}
|
||||
switch string(data[pos : pos+6]) {
|
||||
|
||||
switch string(entryMode) {
|
||||
case "100644":
|
||||
entry.entryMode = EntryModeBlob
|
||||
pos += 12 // skip over "100644 blob "
|
||||
case "100755":
|
||||
entry.entryMode = EntryModeExec
|
||||
pos += 12 // skip over "100755 blob "
|
||||
case "120000":
|
||||
entry.entryMode = EntryModeSymlink
|
||||
pos += 12 // skip over "120000 blob "
|
||||
case "160000":
|
||||
entry.entryMode = EntryModeCommit
|
||||
pos += 14 // skip over "160000 object "
|
||||
case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
|
||||
entry.entryMode = EntryModeTree
|
||||
pos += 12 // skip over "040000 tree "
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
|
||||
return nil, fmt.Errorf("unknown type: %v", string(entryMode))
|
||||
}
|
||||
|
||||
if pos+40 > len(data) {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
}
|
||||
id, err := NewIDFromString(string(data[pos : pos+40]))
|
||||
entry.ID, err = NewIDFromString(string(entryObjectID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
|
||||
}
|
||||
entry.ID = id
|
||||
pos += 41 // skip over sha and trailing space
|
||||
|
||||
end := pos + bytes.IndexByte(data[pos:], '\t')
|
||||
if end < pos {
|
||||
return nil, fmt.Errorf("Invalid ls-tree -l output: %s", string(data))
|
||||
}
|
||||
entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(data[pos:end])), 10, 64)
|
||||
entry.sized = true
|
||||
|
||||
pos = end + 1
|
||||
|
||||
end = pos + bytes.IndexByte(data[pos:], '\n')
|
||||
if end < pos {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
|
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
|
||||
}
|
||||
|
||||
// In case entry name is surrounded by double quotes(it happens only in git-shell).
|
||||
if data[pos] == '"' {
|
||||
entry.name, err = strconv.Unquote(string(data[pos:end]))
|
||||
if len(entryName) > 0 && entryName[0] == '"' {
|
||||
entry.name, err = strconv.Unquote(string(entryName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
|
||||
return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
|
||||
}
|
||||
} else {
|
||||
entry.name = string(data[pos:end])
|
||||
entry.name = string(entryName)
|
||||
}
|
||||
|
||||
pos = end + 1
|
||||
pos = posEnd + 1
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, nil
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseTreeEntries(t *testing.T) {
|
||||
func TestParseTreeEntriesLong(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Expected []*TreeEntry
|
||||
|
@ -59,11 +59,47 @@ func TestParseTreeEntries(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, len(testCase.Expected))
|
||||
for i, entry := range entries {
|
||||
assert.EqualValues(t, testCase.Expected[i].ID, entry.ID)
|
||||
assert.EqualValues(t, testCase.Expected[i].name, entry.name)
|
||||
assert.EqualValues(t, testCase.Expected[i].entryMode, entry.entryMode)
|
||||
assert.EqualValues(t, testCase.Expected[i].sized, entry.sized)
|
||||
assert.EqualValues(t, testCase.Expected[i].size, entry.size)
|
||||
assert.EqualValues(t, testCase.Expected[i], entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTreeEntriesShort(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Expected []*TreeEntry
|
||||
}{
|
||||
{
|
||||
Input: `100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af README.md
|
||||
040000 tree 84b90550547016f73c5dd3f50dea662389e67b6d assets
|
||||
`,
|
||||
Expected: []*TreeEntry{
|
||||
{
|
||||
ID: MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
|
||||
name: "README.md",
|
||||
entryMode: EntryModeBlob,
|
||||
},
|
||||
{
|
||||
ID: MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
|
||||
name: "assets",
|
||||
entryMode: EntryModeTree,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
entries, err := ParseTreeEntries([]byte(testCase.Input))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, len(testCase.Expected))
|
||||
for i, entry := range entries {
|
||||
assert.EqualValues(t, testCase.Expected[i], entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTreeEntriesInvalid(t *testing.T) {
|
||||
// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
|
||||
entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, entries, 0)
|
||||
}
|
||||
|
|
|
@ -191,8 +191,8 @@ func (c *CheckAttributeReader) Run() error {
|
|||
// CheckPath check attr for given path
|
||||
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Error("CheckPath returns error: %v", err)
|
||||
if err != nil && err != c.ctx.Err() {
|
||||
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
|||
|
||||
tree := commit.Tree
|
||||
|
||||
entries, err := tree.ListEntriesRecursive()
|
||||
entries, err := tree.ListEntriesRecursiveWithSize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
)
|
||||
|
||||
// CodeActivityStats represents git statistics data
|
||||
|
@ -80,7 +82,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
|||
stats.Additions = 0
|
||||
stats.Deletions = 0
|
||||
authors := make(map[string]*CodeActivityAuthor)
|
||||
files := make(map[string]bool)
|
||||
files := make(container.Set[string])
|
||||
var author string
|
||||
p := 0
|
||||
for scanner.Scan() {
|
||||
|
@ -119,9 +121,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
|||
stats.Deletions += c
|
||||
}
|
||||
}
|
||||
if _, ok := files[parts[2]]; !ok {
|
||||
files[parts[2]] = true
|
||||
}
|
||||
files.Add(parts[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,8 @@ func (t *Tree) ListEntries() (Entries, error) {
|
|||
return entries, nil
|
||||
}
|
||||
|
||||
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
|
||||
func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
||||
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees
|
||||
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
if t.gogitTree == nil {
|
||||
err := t.loadTreeObject()
|
||||
if err != nil {
|
||||
|
@ -92,3 +92,8 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
|||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// ListEntriesRecursiveFast is the alias of ListEntriesRecursiveWithSize for the gogit version
|
||||
func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
|
||||
return t.ListEntriesRecursiveWithSize()
|
||||
}
|
||||
|
|
|
@ -99,13 +99,16 @@ func (t *Tree) ListEntries() (Entries, error) {
|
|||
return t.entries, err
|
||||
}
|
||||
|
||||
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
|
||||
func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
||||
// listEntriesRecursive returns all entries of current tree recursively including all subtrees
|
||||
// extraArgs could be "-l" to get the size, which is slower
|
||||
func (t *Tree) listEntriesRecursive(extraArgs ...string) (Entries, error) {
|
||||
if t.entriesRecursiveParsed {
|
||||
return t.entriesRecursive, nil
|
||||
}
|
||||
|
||||
stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-l", "-r", t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
|
||||
args := append([]string{"ls-tree", "-t", "-r"}, extraArgs...)
|
||||
args = append(args, t.ID.String())
|
||||
stdout, _, runErr := NewCommand(t.repo.Ctx, args...).RunStdBytes(&RunOpts{Dir: t.repo.Path})
|
||||
if runErr != nil {
|
||||
return nil, runErr
|
||||
}
|
||||
|
@ -118,3 +121,13 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
|
|||
|
||||
return t.entriesRecursive, err
|
||||
}
|
||||
|
||||
// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size
|
||||
func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
|
||||
return t.listEntriesRecursive()
|
||||
}
|
||||
|
||||
// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size
|
||||
func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
return t.listEntriesRecursive("--long")
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
|
@ -43,7 +44,7 @@ func validateYaml(template *api.IssueTemplate) error {
|
|||
if len(template.Fields) == 0 {
|
||||
return fmt.Errorf("'body' is required")
|
||||
}
|
||||
ids := map[string]struct{}{}
|
||||
ids := make(container.Set[string])
|
||||
for idx, field := range template.Fields {
|
||||
if err := validateID(field, idx, ids); err != nil {
|
||||
return err
|
||||
|
@ -125,7 +126,7 @@ func validateRequired(field *api.IssueFormField, idx int) error {
|
|||
return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required")
|
||||
}
|
||||
|
||||
func validateID(field *api.IssueFormField, idx int, ids map[string]struct{}) error {
|
||||
func validateID(field *api.IssueFormField, idx int, ids container.Set[string]) error {
|
||||
if field.Type == api.IssueFormFieldTypeMarkdown {
|
||||
// The ID is not required for a markdown field
|
||||
return nil
|
||||
|
@ -139,10 +140,9 @@ func validateID(field *api.IssueFormField, idx int, ids map[string]struct{}) err
|
|||
if binding.AlphaDashPattern.MatchString(field.ID) {
|
||||
return position.Errorf("'id' should contain only alphanumeric, '-' and '_'")
|
||||
}
|
||||
if _, ok := ids[field.ID]; ok {
|
||||
if !ids.Add(field.ID) {
|
||||
return position.Errorf("'id' should be unique")
|
||||
}
|
||||
ids[field.ID] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package markup_test
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -32,6 +33,7 @@ func TestMain(m *testing.M) {
|
|||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRender_Commits(t *testing.T) {
|
||||
|
@ -336,7 +338,7 @@ func TestRender_emoji(t *testing.T) {
|
|||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`)
|
||||
test(
|
||||
"😎🤪🔐🤑❓",
|
||||
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="question mark">❓</span></p>`)
|
||||
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
|
||||
|
||||
// should match nothing
|
||||
test(
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/common"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -198,7 +199,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
|||
}
|
||||
|
||||
type prefixedIDs struct {
|
||||
values map[string]bool
|
||||
values container.Set[string]
|
||||
}
|
||||
|
||||
// Generate generates a new element id.
|
||||
|
@ -219,14 +220,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
|||
if !bytes.HasPrefix(result, []byte("user-content-")) {
|
||||
result = append([]byte("user-content-"), result...)
|
||||
}
|
||||
if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
|
||||
p.values[util.BytesToReadOnlyString(result)] = true
|
||||
if p.values.Add(util.BytesToReadOnlyString(result)) {
|
||||
return result
|
||||
}
|
||||
for i := 1; ; i++ {
|
||||
newResult := fmt.Sprintf("%s-%d", result, i)
|
||||
if _, ok := p.values[newResult]; !ok {
|
||||
p.values[newResult] = true
|
||||
if p.values.Add(newResult) {
|
||||
return []byte(newResult)
|
||||
}
|
||||
}
|
||||
|
@ -234,12 +233,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
|||
|
||||
// Put puts a given element id to the used ids table.
|
||||
func (p *prefixedIDs) Put(value []byte) {
|
||||
p.values[util.BytesToReadOnlyString(value)] = true
|
||||
p.values.Add(util.BytesToReadOnlyString(value))
|
||||
}
|
||||
|
||||
func newPrefixedIDs() *prefixedIDs {
|
||||
return &prefixedIDs{
|
||||
values: map[string]bool{},
|
||||
values: make(container.Set[string]),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ package markdown_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -37,6 +38,7 @@ func TestMain(m *testing.M) {
|
|||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
|
@ -426,3 +428,51 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, res)
|
||||
}
|
||||
|
||||
func TestMathBlock(t *testing.T) {
|
||||
const nl = "\n"
|
||||
testcases := []struct {
|
||||
testcase string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"$a$",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$ a $",
|
||||
`<p><code class="language-math is-loading">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ $b$",
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`\(a\) \(b\)`,
|
||||
`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`$a a$b b$`,
|
||||
`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
`a a$b b`,
|
||||
`<p>a a$b b</p>` + nl,
|
||||
},
|
||||
{
|
||||
`a$b $a a$b b$`,
|
||||
`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$",
|
||||
`<pre class="code-block is-loading"><code class="chroma language-math display">a</code></pre>` + nl,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testcases {
|
||||
res, err := RenderString(&markup.RenderContext{}, test.testcase)
|
||||
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
|
||||
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ func NewInlineBracketParser() parser.InlineParser {
|
|||
return defaultInlineBracketParser
|
||||
}
|
||||
|
||||
// Trigger triggers this parser on $
|
||||
// Trigger triggers this parser on $ or \
|
||||
func (parser *inlineParser) Trigger() []byte {
|
||||
return parser.start[0:1]
|
||||
}
|
||||
|
@ -50,29 +50,50 @@ func isAlphanumeric(b byte) bool {
|
|||
// Parse parses the current line and returns a result of parsing.
|
||||
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
opener := bytes.Index(line, parser.start)
|
||||
if opener < 0 {
|
||||
return nil
|
||||
}
|
||||
if opener != 0 && isAlphanumeric(line[opener-1]) {
|
||||
|
||||
if !bytes.HasPrefix(line, parser.start) {
|
||||
// We'll catch this one on the next time round
|
||||
return nil
|
||||
}
|
||||
|
||||
opener += len(parser.start)
|
||||
ender := bytes.Index(line[opener:], parser.end)
|
||||
if ender < 0 {
|
||||
precedingCharacter := block.PrecendingCharacter()
|
||||
if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
|
||||
// need to exclude things like `a$` from being considered a start
|
||||
return nil
|
||||
}
|
||||
if len(line) > opener+ender+len(parser.end) && isAlphanumeric(line[opener+ender+len(parser.end)]) {
|
||||
return nil
|
||||
|
||||
// move the opener marker point at the start of the text
|
||||
opener := len(parser.start)
|
||||
|
||||
// Now look for an ending line
|
||||
ender := opener
|
||||
for {
|
||||
pos := bytes.Index(line[ender:], parser.end)
|
||||
if pos < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ender += pos
|
||||
|
||||
// Now we want to check the character at the end of our parser section
|
||||
// that is ender + len(parser.end)
|
||||
pos = ender + len(parser.end)
|
||||
if len(line) <= pos {
|
||||
break
|
||||
}
|
||||
if !isAlphanumeric(line[pos]) {
|
||||
break
|
||||
}
|
||||
// move the pointer onwards
|
||||
ender += len(parser.end)
|
||||
}
|
||||
|
||||
block.Advance(opener)
|
||||
_, pos := block.Position()
|
||||
node := NewInline()
|
||||
segment := pos.WithStop(pos.Start + ender)
|
||||
segment := pos.WithStop(pos.Start + ender - opener)
|
||||
node.AppendChild(node, ast.NewRawTextSegment(segment))
|
||||
block.Advance(ender + len(parser.end))
|
||||
block.Advance(ender - opener + len(parser.end))
|
||||
|
||||
trimBlock(node, block)
|
||||
return node
|
||||
|
|
|
@ -88,7 +88,9 @@ func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) {
|
|||
line := contents[start:end]
|
||||
if isYAMLSeparator(line) {
|
||||
front = contents[frontMatterStart:start]
|
||||
body = contents[end+1:]
|
||||
if end+1 < len(contents) {
|
||||
body = contents[end+1:]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestExtractMetadataBytes(t *testing.T) {
|
|||
var meta structs.IssueTemplate
|
||||
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bodyTest, body)
|
||||
assert.Equal(t, bodyTest, string(body))
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
})
|
||||
|
@ -82,7 +82,7 @@ func TestExtractMetadataBytes(t *testing.T) {
|
|||
var meta structs.IssueTemplate
|
||||
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", body)
|
||||
assert.Equal(t, "", string(body))
|
||||
assert.Equal(t, metaTest, meta)
|
||||
assert.True(t, validateMetadata(meta))
|
||||
})
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
@ -33,17 +32,13 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||
}
|
||||
rc.yamlNode = value
|
||||
|
||||
type basicRenderConfig struct {
|
||||
Gitea *yaml.Node `yaml:"gitea"`
|
||||
TOC bool `yaml:"include_toc"`
|
||||
Lang string `yaml:"lang"`
|
||||
type commonRenderConfig struct {
|
||||
TOC bool `yaml:"include_toc"`
|
||||
Lang string `yaml:"lang"`
|
||||
}
|
||||
|
||||
var basic basicRenderConfig
|
||||
|
||||
err := value.Decode(&basic)
|
||||
if err != nil {
|
||||
return err
|
||||
var basic commonRenderConfig
|
||||
if err := value.Decode(&basic); err != nil {
|
||||
return fmt.Errorf("unable to decode into commonRenderConfig %w", err)
|
||||
}
|
||||
|
||||
if basic.Lang != "" {
|
||||
|
@ -51,14 +46,48 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||
}
|
||||
|
||||
rc.TOC = basic.TOC
|
||||
if basic.Gitea == nil {
|
||||
|
||||
type controlStringRenderConfig struct {
|
||||
Gitea string `yaml:"gitea"`
|
||||
}
|
||||
|
||||
var stringBasic controlStringRenderConfig
|
||||
|
||||
if err := value.Decode(&stringBasic); err == nil {
|
||||
if stringBasic.Gitea != "" {
|
||||
switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) {
|
||||
case "none":
|
||||
rc.Meta = "none"
|
||||
case "table":
|
||||
rc.Meta = "table"
|
||||
default: // "details"
|
||||
rc.Meta = "details"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var control *string
|
||||
if err := basic.Gitea.Decode(&control); err == nil && control != nil {
|
||||
log.Info("control %v", control)
|
||||
switch strings.TrimSpace(strings.ToLower(*control)) {
|
||||
type giteaControl struct {
|
||||
Meta *string `yaml:"meta"`
|
||||
Icon *string `yaml:"details_icon"`
|
||||
TOC *bool `yaml:"include_toc"`
|
||||
Lang *string `yaml:"lang"`
|
||||
}
|
||||
|
||||
type complexGiteaConfig struct {
|
||||
Gitea *giteaControl `yaml:"gitea"`
|
||||
}
|
||||
var complex complexGiteaConfig
|
||||
if err := value.Decode(&complex); err != nil {
|
||||
return fmt.Errorf("unable to decode into complexRenderConfig %w", err)
|
||||
}
|
||||
|
||||
if complex.Gitea == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if complex.Gitea.Meta != nil {
|
||||
switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) {
|
||||
case "none":
|
||||
rc.Meta = "none"
|
||||
case "table":
|
||||
|
@ -66,39 +95,18 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||
default: // "details"
|
||||
rc.Meta = "details"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type giteaControl struct {
|
||||
Meta string `yaml:"meta"`
|
||||
Icon string `yaml:"details_icon"`
|
||||
TOC *yaml.Node `yaml:"include_toc"`
|
||||
Lang string `yaml:"lang"`
|
||||
if complex.Gitea.Icon != nil {
|
||||
rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon))
|
||||
}
|
||||
|
||||
var controlStruct *giteaControl
|
||||
if err := basic.Gitea.Decode(controlStruct); err != nil || controlStruct == nil {
|
||||
return err
|
||||
if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" {
|
||||
rc.Lang = *complex.Gitea.Lang
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(strings.ToLower(controlStruct.Meta)) {
|
||||
case "none":
|
||||
rc.Meta = "none"
|
||||
case "table":
|
||||
rc.Meta = "table"
|
||||
default: // "details"
|
||||
rc.Meta = "details"
|
||||
}
|
||||
|
||||
rc.Icon = strings.TrimSpace(strings.ToLower(controlStruct.Icon))
|
||||
|
||||
if controlStruct.Lang != "" {
|
||||
rc.Lang = controlStruct.Lang
|
||||
}
|
||||
|
||||
var toc bool
|
||||
if err := controlStruct.TOC.Decode(&toc); err == nil {
|
||||
rc.TOC = toc
|
||||
if complex.Gitea.TOC != nil {
|
||||
rc.TOC = *complex.Gitea.TOC
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
@ -81,9 +82,9 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||
TOC: true,
|
||||
Lang: "testlang",
|
||||
}, `
|
||||
include_toc: true
|
||||
lang: testlang
|
||||
`,
|
||||
include_toc: true
|
||||
lang: testlang
|
||||
`,
|
||||
},
|
||||
{
|
||||
"complexlang", &RenderConfig{
|
||||
|
@ -91,9 +92,9 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||
Icon: "table",
|
||||
Lang: "testlang",
|
||||
}, `
|
||||
gitea:
|
||||
lang: testlang
|
||||
`,
|
||||
gitea:
|
||||
lang: testlang
|
||||
`,
|
||||
},
|
||||
{
|
||||
"complexlang2", &RenderConfig{
|
||||
|
@ -140,8 +141,8 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
|||
Icon: "table",
|
||||
Lang: "",
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(tt.args), got); err != nil {
|
||||
t.Errorf("RenderConfig.UnmarshalYAML() error = %v", err)
|
||||
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
||||
t.Errorf("RenderConfig.UnmarshalYAML() error = %v\n%q", err, tt.args)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification/base"
|
||||
|
@ -123,14 +124,14 @@ func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest
|
|||
log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
return
|
||||
}
|
||||
toNotify := make(map[int64]struct{}, 32)
|
||||
toNotify := make(container.Set[int64], 32)
|
||||
repoWatchers, err := repo_model.GetRepoWatchersIDs(db.DefaultContext, pr.Issue.RepoID)
|
||||
if err != nil {
|
||||
log.Error("GetRepoWatchersIDs: %v", err)
|
||||
return
|
||||
}
|
||||
for _, id := range repoWatchers {
|
||||
toNotify[id] = struct{}{}
|
||||
toNotify.Add(id)
|
||||
}
|
||||
issueParticipants, err := issues_model.GetParticipantsIDsByIssueID(pr.IssueID)
|
||||
if err != nil {
|
||||
|
@ -138,11 +139,11 @@ func (ns *notificationService) NotifyNewPullRequest(pr *issues_model.PullRequest
|
|||
return
|
||||
}
|
||||
for _, id := range issueParticipants {
|
||||
toNotify[id] = struct{}{}
|
||||
toNotify.Add(id)
|
||||
}
|
||||
delete(toNotify, pr.Issue.PosterID)
|
||||
for _, mention := range mentions {
|
||||
toNotify[mention.ID] = struct{}{}
|
||||
toNotify.Add(mention.ID)
|
||||
}
|
||||
for receiverID := range toNotify {
|
||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -32,12 +32,12 @@ func Dir(name string) ([]string, error) {
|
|||
customDir := path.Join(setting.CustomPath, "options", name)
|
||||
isDir, err := util.IsDir(customDir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Failed to check if custom directory %s is a directory. %v", err)
|
||||
return []string{}, fmt.Errorf("unable to check if custom directory %q is a directory. %w", customDir, err)
|
||||
}
|
||||
if isDir {
|
||||
files, err := util.StatDir(customDir, true)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Failed to read custom directory. %v", err)
|
||||
return []string{}, fmt.Errorf("unable to read custom directory %q. %w", customDir, err)
|
||||
}
|
||||
|
||||
result = append(result, files...)
|
||||
|
@ -45,11 +45,10 @@ func Dir(name string) ([]string, error) {
|
|||
|
||||
files, err := AssetDir(name)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Failed to read embedded directory. %v", err)
|
||||
return []string{}, fmt.Errorf("unable to read embedded directory %q. %w", name, err)
|
||||
}
|
||||
|
||||
result = append(result, files...)
|
||||
|
||||
return directories.AddAndGet(name, result), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,8 @@ type PackageMetadata struct {
|
|||
License string `json:"license,omitempty"`
|
||||
}
|
||||
|
||||
// PackageMetadataVersion https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
|
||||
// PackageMetadataVersion response: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
|
||||
type PackageMetadataVersion struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -80,6 +81,7 @@ type PackageMetadataVersion struct {
|
|||
Dependencies map[string]string `json:"dependencies,omitempty"`
|
||||
DevDependencies map[string]string `json:"devDependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
Dist PackageDistribution `json:"dist"`
|
||||
|
@ -220,6 +222,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
|||
DevelopmentDependencies: meta.DevDependencies,
|
||||
PeerDependencies: meta.PeerDependencies,
|
||||
OptionalDependencies: meta.OptionalDependencies,
|
||||
Bin: meta.Bin,
|
||||
Readme: meta.Readme,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ func TestParsePackage(t *testing.T) {
|
|||
packageVersion := "1.0.1-pre"
|
||||
packageTag := "latest"
|
||||
packageAuthor := "KN4CK3R"
|
||||
packageBin := "gitea"
|
||||
packageDescription := "Test Description"
|
||||
data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
|
||||
integrity := "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg=="
|
||||
|
@ -236,6 +237,9 @@ func TestParsePackage(t *testing.T) {
|
|||
Dependencies: map[string]string{
|
||||
"package": "1.2.0",
|
||||
},
|
||||
Bin: map[string]string{
|
||||
"bin": packageBin,
|
||||
},
|
||||
Dist: PackageDistribution{
|
||||
Integrity: integrity,
|
||||
},
|
||||
|
@ -264,6 +268,7 @@ func TestParsePackage(t *testing.T) {
|
|||
assert.Equal(t, packageDescription, p.Metadata.Description)
|
||||
assert.Equal(t, packageDescription, p.Metadata.Readme)
|
||||
assert.Equal(t, packageAuthor, p.Metadata.Author)
|
||||
assert.Equal(t, packageBin, p.Metadata.Bin["bin"])
|
||||
assert.Equal(t, "MIT", p.Metadata.License)
|
||||
assert.Equal(t, "https://gitea.io/", p.Metadata.ProjectURL)
|
||||
assert.Contains(t, p.Metadata.Dependencies, "package")
|
||||
|
|
|
@ -20,5 +20,6 @@ type Metadata struct {
|
|||
DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
|
||||
PeerDependencies map[string]string `json:"peer_dependencies,omitempty"`
|
||||
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`
|
||||
Bin map[string]string `json:"bin,omitempty"`
|
||||
Readme string `json:"readme,omitempty"`
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
|
@ -18,13 +20,14 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func newRequest(ctx context.Context, url, method string) *httplib.Request {
|
||||
func newRequest(ctx context.Context, url, method, sourceIP string) *httplib.Request {
|
||||
if setting.InternalToken == "" {
|
||||
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
|
||||
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
||||
}
|
||||
return httplib.NewRequest(url, method).
|
||||
SetContext(ctx).
|
||||
Header("X-Real-IP", sourceIP).
|
||||
Header("Authorization", fmt.Sprintf("Bearer %s", setting.InternalToken))
|
||||
}
|
||||
|
||||
|
@ -42,8 +45,16 @@ func decodeJSONError(resp *http.Response) *Response {
|
|||
return &res
|
||||
}
|
||||
|
||||
func getClientIP() string {
|
||||
sshConnEnv := strings.TrimSpace(os.Getenv("SSH_CONNECTION"))
|
||||
if len(sshConnEnv) == 0 {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
return strings.Fields(sshConnEnv)[0]
|
||||
}
|
||||
|
||||
func newInternalRequest(ctx context.Context, url, method string) *httplib.Request {
|
||||
req := newRequest(ctx, url, method).SetTLSClientConfig(&tls.Config{
|
||||
req := newRequest(ctx, url, method, getClientIP()).SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: setting.Domain,
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -83,11 +84,11 @@ func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
|
|||
}
|
||||
|
||||
// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
|
||||
func parseAcceptEncoding(val string) map[string]bool {
|
||||
func parseAcceptEncoding(val string) container.Set[string] {
|
||||
parts := strings.Split(val, ";")
|
||||
types := make(map[string]bool)
|
||||
types := make(container.Set[string])
|
||||
for _, v := range strings.Split(parts[0], ",") {
|
||||
types[strings.TrimSpace(v)] = true
|
||||
types.Add(strings.TrimSpace(v))
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
|
|
@ -7,28 +7,23 @@ package public
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseAcceptEncoding(t *testing.T) {
|
||||
kases := []struct {
|
||||
Header string
|
||||
Expected map[string]bool
|
||||
Expected container.Set[string]
|
||||
}{
|
||||
{
|
||||
Header: "deflate, gzip;q=1.0, *;q=0.5",
|
||||
Expected: map[string]bool{
|
||||
"deflate": true,
|
||||
"gzip": true,
|
||||
},
|
||||
Header: "deflate, gzip;q=1.0, *;q=0.5",
|
||||
Expected: container.SetOf("deflate", "gzip"),
|
||||
},
|
||||
{
|
||||
Header: " gzip, deflate, br",
|
||||
Expected: map[string]bool{
|
||||
"deflate": true,
|
||||
"gzip": true,
|
||||
"br": true,
|
||||
},
|
||||
Header: " gzip, deflate, br",
|
||||
Expected: container.SetOf("deflate", "gzip", "br"),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ func AssetIsDir(name string) (bool, error) {
|
|||
// serveContent serve http content
|
||||
func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) {
|
||||
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
|
||||
if encodings["gzip"] {
|
||||
if encodings.Contains("gzip") {
|
||||
if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
|
||||
rdGzip := bytes.NewReader(cf.GzipBytes())
|
||||
// all static files are managed by Gitea, so we can make sure every file has the correct ext name
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
@ -33,7 +34,7 @@ type ChannelUniqueQueueConfiguration ChannelQueueConfiguration
|
|||
type ChannelUniqueQueue struct {
|
||||
*WorkerPool
|
||||
lock sync.Mutex
|
||||
table map[string]bool
|
||||
table container.Set[string]
|
||||
shutdownCtx context.Context
|
||||
shutdownCtxCancel context.CancelFunc
|
||||
terminateCtx context.Context
|
||||
|
@ -58,7 +59,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue
|
|||
shutdownCtx, shutdownCtxCancel := context.WithCancel(terminateCtx)
|
||||
|
||||
queue := &ChannelUniqueQueue{
|
||||
table: map[string]bool{},
|
||||
table: make(container.Set[string]),
|
||||
shutdownCtx: shutdownCtx,
|
||||
shutdownCtxCancel: shutdownCtxCancel,
|
||||
terminateCtx: terminateCtx,
|
||||
|
@ -73,7 +74,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue
|
|||
bs, _ := json.Marshal(datum)
|
||||
|
||||
queue.lock.Lock()
|
||||
delete(queue.table, string(bs))
|
||||
queue.table.Remove(string(bs))
|
||||
queue.lock.Unlock()
|
||||
|
||||
if u := handle(datum); u != nil {
|
||||
|
@ -127,16 +128,15 @@ func (q *ChannelUniqueQueue) PushFunc(data Data, fn func() error) error {
|
|||
q.lock.Unlock()
|
||||
}
|
||||
}()
|
||||
if _, ok := q.table[string(bs)]; ok {
|
||||
if !q.table.Add(string(bs)) {
|
||||
return ErrAlreadyInQueue
|
||||
}
|
||||
// FIXME: We probably need to implement some sort of limit here
|
||||
// If the downstream queue blocks this table will grow without limit
|
||||
q.table[string(bs)] = true
|
||||
if fn != nil {
|
||||
err := fn()
|
||||
if err != nil {
|
||||
delete(q.table, string(bs))
|
||||
q.table.Remove(string(bs))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -155,8 +155,7 @@ func (q *ChannelUniqueQueue) Has(data Data) (bool, error) {
|
|||
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
_, has := q.table[string(bs)]
|
||||
return has, nil
|
||||
return q.table.Contains(string(bs)), nil
|
||||
}
|
||||
|
||||
// Flush flushes the channel with a timeout - the Flush worker will be registered as a flush worker with the manager
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -275,7 +276,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
|
|||
return pullMirrorReleaseSync(repo, gitRepo)
|
||||
}
|
||||
|
||||
existingRelTags := make(map[string]struct{})
|
||||
existingRelTags := make(container.Set[string])
|
||||
opts := repo_model.FindReleasesOptions{
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
|
@ -303,14 +304,14 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
|
|||
return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
||||
}
|
||||
} else {
|
||||
existingRelTags[strings.ToLower(rel.TagName)] = struct{}{}
|
||||
existingRelTags.Add(strings.ToLower(rel.TagName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := gitRepo.WalkReferences(git.ObjectTag, 0, 0, func(sha1, refname string) error {
|
||||
tagName := strings.TrimPrefix(refname, git.TagPrefix)
|
||||
if _, ok := existingRelTags[strings.ToLower(tagName)]; ok {
|
||||
if existingRelTags.Contains(strings.ToLower(tagName)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
ini "gopkg.in/ini.v1"
|
||||
|
@ -109,8 +110,8 @@ func NewQueueService() {
|
|||
// Now handle the old issue_indexer configuration
|
||||
// FIXME: DEPRECATED to be removed in v1.18.0
|
||||
section := Cfg.Section("queue.issue_indexer")
|
||||
directlySet := toDirectlySetKeysMap(section)
|
||||
if !directlySet["TYPE"] && defaultType == "" {
|
||||
directlySet := toDirectlySetKeysSet(section)
|
||||
if !directlySet.Contains("TYPE") && defaultType == "" {
|
||||
switch typ := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(""); typ {
|
||||
case "levelqueue":
|
||||
_, _ = section.NewKey("TYPE", "level")
|
||||
|
@ -124,25 +125,25 @@ func NewQueueService() {
|
|||
log.Fatal("Unsupported indexer queue type: %v", typ)
|
||||
}
|
||||
}
|
||||
if !directlySet["LENGTH"] {
|
||||
if !directlySet.Contains("LENGTH") {
|
||||
length := Cfg.Section("indexer").Key("UPDATE_BUFFER_LEN").MustInt(0)
|
||||
if length != 0 {
|
||||
_, _ = section.NewKey("LENGTH", strconv.Itoa(length))
|
||||
}
|
||||
}
|
||||
if !directlySet["BATCH_LENGTH"] {
|
||||
if !directlySet.Contains("BATCH_LENGTH") {
|
||||
fallback := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(0)
|
||||
if fallback != 0 {
|
||||
_, _ = section.NewKey("BATCH_LENGTH", strconv.Itoa(fallback))
|
||||
}
|
||||
}
|
||||
if !directlySet["DATADIR"] {
|
||||
if !directlySet.Contains("DATADIR") {
|
||||
queueDir := filepath.ToSlash(Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_DIR").MustString(""))
|
||||
if queueDir != "" {
|
||||
_, _ = section.NewKey("DATADIR", queueDir)
|
||||
}
|
||||
}
|
||||
if !directlySet["CONN_STR"] {
|
||||
if !directlySet.Contains("CONN_STR") {
|
||||
connStr := Cfg.Section("indexer").Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString("")
|
||||
if connStr != "" {
|
||||
_, _ = section.NewKey("CONN_STR", connStr)
|
||||
|
@ -178,19 +179,19 @@ func handleOldLengthConfiguration(queueName, oldSection, oldKey string, defaultV
|
|||
}
|
||||
|
||||
section := Cfg.Section("queue." + queueName)
|
||||
directlySet := toDirectlySetKeysMap(section)
|
||||
if !directlySet["LENGTH"] {
|
||||
directlySet := toDirectlySetKeysSet(section)
|
||||
if !directlySet.Contains("LENGTH") {
|
||||
_, _ = section.NewKey("LENGTH", strconv.Itoa(value))
|
||||
}
|
||||
}
|
||||
|
||||
// toDirectlySetKeysMap returns a bool map of keys directly set by this section
|
||||
// toDirectlySetKeysSet returns a set of keys directly set by this section
|
||||
// Note: we cannot use section.HasKey(...) as that will immediately set the Key if a parent section has the Key
|
||||
// but this section does not.
|
||||
func toDirectlySetKeysMap(section *ini.Section) map[string]bool {
|
||||
sectionMap := map[string]bool{}
|
||||
func toDirectlySetKeysSet(section *ini.Section) container.Set[string] {
|
||||
sections := make(container.Set[string])
|
||||
for _, key := range section.Keys() {
|
||||
sectionMap[key.Name()] = true
|
||||
sections.Add(key.Name())
|
||||
}
|
||||
return sectionMap
|
||||
return sections
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
|
@ -234,7 +235,7 @@ var (
|
|||
DefaultTheme string
|
||||
Themes []string
|
||||
Reactions []string
|
||||
ReactionsMap map[string]bool `ini:"-"`
|
||||
ReactionsLookup container.Set[string] `ini:"-"`
|
||||
CustomEmojis []string
|
||||
CustomEmojisMap map[string]string `ini:"-"`
|
||||
SearchRepoDescription bool
|
||||
|
@ -1100,9 +1101,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
|||
|
||||
newMarkup()
|
||||
|
||||
UI.ReactionsMap = make(map[string]bool)
|
||||
UI.ReactionsLookup = make(container.Set[string])
|
||||
for _, reaction := range UI.Reactions {
|
||||
UI.ReactionsMap[reaction] = true
|
||||
UI.ReactionsLookup.Add(reaction)
|
||||
}
|
||||
UI.CustomEmojisMap = make(map[string]string)
|
||||
for _, emoji := range UI.CustomEmojis {
|
||||
|
|
|
@ -17,7 +17,7 @@ type Team struct {
|
|||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ type CreateTeamOption struct {
|
|||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ type EditTeamOption struct {
|
|||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
|
||||
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
|
||||
Units []string `json:"units"`
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
|
||||
// example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
|
||||
UnitsMap map[string]string `json:"units_map"`
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
}
|
||||
|
|
|
@ -34,8 +34,10 @@ type ExternalTracker struct {
|
|||
ExternalTrackerURL string `json:"external_tracker_url"`
|
||||
// External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
||||
ExternalTrackerFormat string `json:"external_tracker_format"`
|
||||
// External Issue Tracker Number Format, either `numeric` or `alphanumeric`
|
||||
// External Issue Tracker Number Format, either `numeric`, `alphanumeric`, or `regexp`
|
||||
ExternalTrackerStyle string `json:"external_tracker_style"`
|
||||
// External Issue Tracker issue regular expression
|
||||
ExternalTrackerRegexpPattern string `json:"external_tracker_regexp_pattern"`
|
||||
}
|
||||
|
||||
// ExternalWiki represents setting for external wiki
|
||||
|
|
|
@ -6,6 +6,8 @@ package sync
|
|||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
)
|
||||
|
||||
// StatusTable is a table maintains true/false values.
|
||||
|
@ -14,13 +16,13 @@ import (
|
|||
// in different goroutines.
|
||||
type StatusTable struct {
|
||||
lock sync.RWMutex
|
||||
pool map[string]struct{}
|
||||
pool container.Set[string]
|
||||
}
|
||||
|
||||
// NewStatusTable initializes and returns a new StatusTable object.
|
||||
func NewStatusTable() *StatusTable {
|
||||
return &StatusTable{
|
||||
pool: make(map[string]struct{}),
|
||||
pool: make(container.Set[string]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,32 +30,29 @@ func NewStatusTable() *StatusTable {
|
|||
// Returns whether set value was set to true
|
||||
func (p *StatusTable) StartIfNotRunning(name string) bool {
|
||||
p.lock.Lock()
|
||||
_, ok := p.pool[name]
|
||||
if !ok {
|
||||
p.pool[name] = struct{}{}
|
||||
}
|
||||
added := p.pool.Add(name)
|
||||
p.lock.Unlock()
|
||||
return !ok
|
||||
return added
|
||||
}
|
||||
|
||||
// Start sets value of given name to true in the pool.
|
||||
func (p *StatusTable) Start(name string) {
|
||||
p.lock.Lock()
|
||||
p.pool[name] = struct{}{}
|
||||
p.pool.Add(name)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// Stop sets value of given name to false in the pool.
|
||||
func (p *StatusTable) Stop(name string) {
|
||||
p.lock.Lock()
|
||||
delete(p.pool, name)
|
||||
p.pool.Remove(name)
|
||||
p.lock.Unlock()
|
||||
}
|
||||
|
||||
// IsRunning checks if value of given name is set to true in the pool.
|
||||
func (p *StatusTable) IsRunning(name string) bool {
|
||||
p.lock.RLock()
|
||||
_, ok := p.pool[name]
|
||||
exists := p.pool.Contains(name)
|
||||
p.lock.RUnlock()
|
||||
return ok
|
||||
return exists
|
||||
}
|
||||
|
|
|
@ -33,6 +33,21 @@ func GetAsset(name string) ([]byte, error) {
|
|||
return os.ReadFile(filepath.Join(setting.StaticRootPath, name))
|
||||
}
|
||||
|
||||
// GetAssetFilename returns the filename of the provided asset
|
||||
func GetAssetFilename(name string) (string, error) {
|
||||
filename := filepath.Join(setting.CustomPath, name)
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return filename, err
|
||||
} else if err == nil {
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
filename = filepath.Join(setting.StaticRootPath, name)
|
||||
_, err = os.Stat(filename)
|
||||
return filename, err
|
||||
}
|
||||
|
||||
// walkTemplateFiles calls a callback for each template asset
|
||||
func walkTemplateFiles(callback func(path, name string, d fs.DirEntry, err error) error) error {
|
||||
if err := walkAssetDir(filepath.Join(setting.CustomPath, "templates"), true, callback); err != nil && !os.IsNotExist(err) {
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -14,7 +19,14 @@ import (
|
|||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
var rendererKey interface{} = "templatesHtmlRendereer"
|
||||
var (
|
||||
rendererKey interface{} = "templatesHtmlRenderer"
|
||||
|
||||
templateError = regexp.MustCompile(`^template: (.*):([0-9]+): (.*)`)
|
||||
notDefinedError = regexp.MustCompile(`^template: (.*):([0-9]+): function "(.*)" not defined`)
|
||||
unexpectedError = regexp.MustCompile(`^template: (.*):([0-9]+): unexpected "(.*)" in operand`)
|
||||
expectedEndError = regexp.MustCompile(`^template: (.*):([0-9]+): expected end; found (.*)`)
|
||||
)
|
||||
|
||||
// HTMLRenderer returns the current html renderer for the context or creates and stores one within the context for future use
|
||||
func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
||||
|
@ -32,6 +44,25 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
|||
}
|
||||
log.Log(1, log.DEBUG, "Creating "+rendererType+" HTML Renderer")
|
||||
|
||||
compilingTemplates := true
|
||||
defer func() {
|
||||
if !compilingTemplates {
|
||||
return
|
||||
}
|
||||
|
||||
panicked := recover()
|
||||
if panicked == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// OK try to handle the panic...
|
||||
err, ok := panicked.(error)
|
||||
if ok {
|
||||
handlePanicError(err)
|
||||
}
|
||||
log.Fatal("PANIC: Unable to compile templates!\n%v\n\nStacktrace:\n%s", panicked, log.Stack(2))
|
||||
}()
|
||||
|
||||
renderer := render.New(render.Options{
|
||||
Extensions: []string{".tmpl"},
|
||||
Directory: "templates",
|
||||
|
@ -42,6 +73,7 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
|||
IsDevelopment: false,
|
||||
DisableHTTPErrorRendering: true,
|
||||
})
|
||||
compilingTemplates = false
|
||||
if !setting.IsProd {
|
||||
watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{
|
||||
PathsCallback: walkTemplateFiles,
|
||||
|
@ -50,3 +82,168 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
|||
}
|
||||
return context.WithValue(ctx, rendererKey, renderer), renderer
|
||||
}
|
||||
|
||||
func handlePanicError(err error) {
|
||||
wrapFatal(handleNotDefinedPanicError(err))
|
||||
wrapFatal(handleUnexpected(err))
|
||||
wrapFatal(handleExpectedEnd(err))
|
||||
wrapFatal(handleGenericTemplateError(err))
|
||||
}
|
||||
|
||||
func wrapFatal(format string, args []interface{}) {
|
||||
if format == "" {
|
||||
return
|
||||
}
|
||||
log.FatalWithSkip(1, format, args...)
|
||||
}
|
||||
|
||||
func handleGenericTemplateError(err error) (string, []interface{}) {
|
||||
groups := templateError.FindStringSubmatch(err.Error())
|
||||
if len(groups) != 4 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
templateName, lineNumberStr, message := groups[1], groups[2], groups[3]
|
||||
|
||||
filename, assetErr := GetAssetFilename("templates/" + templateName + ".tmpl")
|
||||
if assetErr != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lineNumber, _ := strconv.Atoi(lineNumberStr)
|
||||
|
||||
line := getLineFromAsset(templateName, lineNumber, "")
|
||||
|
||||
return "PANIC: Unable to compile templates!\n%s in template file %s at line %d:\n\n%s\nStacktrace:\n\n%s", []interface{}{message, filename, lineNumber, log.NewColoredValue(line, log.Reset), log.Stack(2)}
|
||||
}
|
||||
|
||||
func handleNotDefinedPanicError(err error) (string, []interface{}) {
|
||||
groups := notDefinedError.FindStringSubmatch(err.Error())
|
||||
if len(groups) != 4 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
templateName, lineNumberStr, functionName := groups[1], groups[2], groups[3]
|
||||
|
||||
functionName, _ = strconv.Unquote(`"` + functionName + `"`)
|
||||
|
||||
filename, assetErr := GetAssetFilename("templates/" + templateName + ".tmpl")
|
||||
if assetErr != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lineNumber, _ := strconv.Atoi(lineNumberStr)
|
||||
|
||||
line := getLineFromAsset(templateName, lineNumber, functionName)
|
||||
|
||||
return "PANIC: Unable to compile templates!\nUndefined function %q in template file %s at line %d:\n\n%s", []interface{}{functionName, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
|
||||
}
|
||||
|
||||
func handleUnexpected(err error) (string, []interface{}) {
|
||||
groups := unexpectedError.FindStringSubmatch(err.Error())
|
||||
if len(groups) != 4 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
templateName, lineNumberStr, unexpected := groups[1], groups[2], groups[3]
|
||||
unexpected, _ = strconv.Unquote(`"` + unexpected + `"`)
|
||||
|
||||
filename, assetErr := GetAssetFilename("templates/" + templateName + ".tmpl")
|
||||
if assetErr != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lineNumber, _ := strconv.Atoi(lineNumberStr)
|
||||
|
||||
line := getLineFromAsset(templateName, lineNumber, unexpected)
|
||||
|
||||
return "PANIC: Unable to compile templates!\nUnexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
|
||||
}
|
||||
|
||||
func handleExpectedEnd(err error) (string, []interface{}) {
|
||||
groups := expectedEndError.FindStringSubmatch(err.Error())
|
||||
if len(groups) != 4 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
templateName, lineNumberStr, unexpected := groups[1], groups[2], groups[3]
|
||||
|
||||
filename, assetErr := GetAssetFilename("templates/" + templateName + ".tmpl")
|
||||
if assetErr != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lineNumber, _ := strconv.Atoi(lineNumberStr)
|
||||
|
||||
line := getLineFromAsset(templateName, lineNumber, unexpected)
|
||||
|
||||
return "PANIC: Unable to compile templates!\nMissing end with unexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
|
||||
}
|
||||
|
||||
const dashSeparator = "----------------------------------------------------------------------\n"
|
||||
|
||||
func getLineFromAsset(templateName string, targetLineNum int, target string) string {
|
||||
bs, err := GetAsset("templates/" + templateName + ".tmpl")
|
||||
if err != nil {
|
||||
return fmt.Sprintf("(unable to read template file: %v)", err)
|
||||
}
|
||||
|
||||
sb := &strings.Builder{}
|
||||
|
||||
// Write the header
|
||||
sb.WriteString(dashSeparator)
|
||||
|
||||
var lineBs []byte
|
||||
|
||||
// Iterate through the lines from the asset file to find the target line
|
||||
for start, currentLineNum := 0, 1; currentLineNum <= targetLineNum && start < len(bs); currentLineNum++ {
|
||||
// Find the next new line
|
||||
end := bytes.IndexByte(bs[start:], '\n')
|
||||
|
||||
// adjust the end to be a direct pointer in to []byte
|
||||
if end < 0 {
|
||||
end = len(bs)
|
||||
} else {
|
||||
end += start
|
||||
}
|
||||
|
||||
// set lineBs to the current line []byte
|
||||
lineBs = bs[start:end]
|
||||
|
||||
// move start to after the current new line position
|
||||
start = end + 1
|
||||
|
||||
// Write 2 preceding lines + the target line
|
||||
if targetLineNum-currentLineNum < 3 {
|
||||
_, _ = sb.Write(lineBs)
|
||||
_ = sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a provided target to look for in the line add a pointer to it
|
||||
// e.g. ^^^^^^^
|
||||
if target != "" {
|
||||
idx := bytes.Index(lineBs, []byte(target))
|
||||
|
||||
if idx >= 0 {
|
||||
// take the current line and replace preceding text with whitespace (except for tab)
|
||||
for i := range lineBs[:idx] {
|
||||
if lineBs[i] != '\t' {
|
||||
lineBs[i] = ' '
|
||||
}
|
||||
}
|
||||
|
||||
// write the preceding "space"
|
||||
_, _ = sb.Write(lineBs[:idx])
|
||||
|
||||
// Now write the ^^ pointer
|
||||
_, _ = sb.WriteString(strings.Repeat("^", len(target)))
|
||||
_ = sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Finally write the footer
|
||||
sb.WriteString(dashSeparator)
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
|
|
@ -31,6 +31,18 @@ func GlobalModTime(filename string) time.Time {
|
|||
return timeutil.GetExecutableModTime()
|
||||
}
|
||||
|
||||
// GetAssetFilename returns the filename of the provided asset
|
||||
func GetAssetFilename(name string) (string, error) {
|
||||
filename := filepath.Join(setting.CustomPath, name)
|
||||
_, err := os.Stat(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return name, err
|
||||
} else if err == nil {
|
||||
return filename, nil
|
||||
}
|
||||
return "(builtin) " + name, nil
|
||||
}
|
||||
|
||||
// GetAsset get a special asset, only for chi
|
||||
func GetAsset(name string) ([]byte, error) {
|
||||
bs, err := os.ReadFile(filepath.Join(setting.CustomPath, name))
|
||||
|
|
|
@ -72,7 +72,7 @@ Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
|||
Tilmann Bach <tilmann AT outlook DOT com>
|
||||
Toni Villena Jiménez <tonivj5 AT gmail DOT com>
|
||||
Viktor Sperl <viktike32 AT gmail DOT com>
|
||||
Vladimir Jigulin mogaika AT yandex DOT ru
|
||||
Vladimir Jigulin <mogaika AT yandex DOT ru>
|
||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
|
||||
Yaşar Çiv <yasarciv67 AT gmail DOT com>
|
||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
|
||||
|
|
|
@ -227,7 +227,7 @@ default_keep_email_private_popup=E-Mail-Adressen von neuen Benutzern standardmä
|
|||
default_allow_create_organization=Erstellen von Organisationen standardmäßig erlauben
|
||||
default_allow_create_organization_popup=Neuen Nutzern das Erstellen von Organisationen standardmäßig erlauben.
|
||||
default_enable_timetracking=Zeiterfassung standardmäßig aktivieren
|
||||
default_enable_timetracking_popup=Zeiterfassung standardmäßig für neue Repositories aktivieren.
|
||||
default_enable_timetracking_popup=Zeiterfassung standardmäßig für neue Repositorys aktivieren.
|
||||
no_reply_address=Versteckte E-Mail-Domain
|
||||
no_reply_address_helper=Domain-Name für Benutzer mit einer versteckten Emailadresse. Zum Beispiel wird der Benutzername „Joe“ in Git als „joe@noreply.example.org“ protokolliert, wenn die versteckte E-Mail-Domain „noreply.example.org“ festgelegt ist.
|
||||
password_algorithm=Passwort Hashing Algorithmus
|
||||
|
@ -237,15 +237,15 @@ password_algorithm_helper=Lege den Passwort Hashing Algorithmus fest. Unterschie
|
|||
uname_holder=E-Mail-Adresse oder Benutzername
|
||||
password_holder=Passwort
|
||||
switch_dashboard_context=Kontext der Übersichtsseite wechseln
|
||||
my_repos=Repositories
|
||||
show_more_repos=Zeige mehr Repositories…
|
||||
collaborative_repos=Gemeinschaftliche Repositories
|
||||
my_repos=Repositorys
|
||||
show_more_repos=Zeige mehr Repositorys…
|
||||
collaborative_repos=Gemeinschaftliche Repositorys
|
||||
my_orgs=Meine Organisationen
|
||||
my_mirrors=Meine Mirrors
|
||||
view_home=%s ansehen
|
||||
search_repos=Finde ein Repository…
|
||||
filter=Andere Filter
|
||||
filter_by_team_repositories=Nach Team Repositories filtern
|
||||
filter_by_team_repositories=Nach Team-Repositorys filtern
|
||||
feed_of=Feed von "%s"
|
||||
|
||||
show_archived=Archiviert
|
||||
|
@ -258,10 +258,10 @@ show_both_private_public=Öffentliche und private anzeigen
|
|||
show_only_private=Nur private anzeigen
|
||||
show_only_public=Nur öffentliche anzeigen
|
||||
|
||||
issues.in_your_repos=Eigene Repositories
|
||||
issues.in_your_repos=Eigene Repositorys
|
||||
|
||||
[explore]
|
||||
repos=Repositories
|
||||
repos=Repositorys
|
||||
users=Benutzer
|
||||
organizations=Organisationen
|
||||
search=Suche
|
||||
|
@ -269,12 +269,14 @@ code=Code
|
|||
search.fuzzy=Ähnlich
|
||||
search.match=Genau
|
||||
code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator.
|
||||
repo_no_results=Keine passenden Repositories gefunden.
|
||||
repo_no_results=Keine passenden Repositorys gefunden.
|
||||
user_no_results=Keine passenden Benutzer gefunden.
|
||||
org_no_results=Keine passenden Organisationen gefunden.
|
||||
code_no_results=Es konnte kein passender Code für deinen Suchbegriff gefunden werden.
|
||||
code_search_results=Suchergebnisse für „%s“
|
||||
code_last_indexed_at=Zuletzt indexiert %s
|
||||
relevant_repositories_tooltip=Repositorys, die Forks sind oder die kein Thema, kein Symbol und keine Beschreibung haben, werden ausgeblendet.
|
||||
relevant_repositories=Es werden nur relevante Repositorys angezeigt, <a href="%s">zeigt ungefilterte Ergebnisse</a> an.
|
||||
|
||||
|
||||
[auth]
|
||||
|
@ -337,7 +339,7 @@ email_domain_blacklisted=Du kannst dich nicht mit deiner E-Mail-Adresse registri
|
|||
authorize_application=Anwendung autorisieren
|
||||
authorize_redirect_notice=Du wirst zu %s weitergeleitet, wenn du diese Anwendung autorisierst.
|
||||
authorize_application_created_by=Diese Anwendung wurde von %s erstellt.
|
||||
authorize_application_description=Wenn du diese Anwendung autorisierst, wird sie die Berechtigung erhalten, alle Informationen zu deinem Account zu bearbeiten oder zu lesen. Dies beinhaltet auch private Repositories und Organisationen.
|
||||
authorize_application_description=Wenn du diese Anwendung autorisierst, wird sie die Berechtigung erhalten, alle Informationen zu deinem Account zu bearbeiten oder zu lesen. Dies beinhaltet auch private Repositorys und Organisationen.
|
||||
authorize_title="%s" den Zugriff auf deinen Account gestatten?
|
||||
authorization_failed=Autorisierung fehlgeschlagen
|
||||
authorization_failed_desc=Die Autorisierung ist fehlgeschlagen, da wir eine ungültige Anfrage festgestellt haben. Bitte kontaktiere den Betreiber der Anwendung, die du gerade autorisieren wolltest.
|
||||
|
@ -453,7 +455,7 @@ lang_select_error=Wähle eine Sprache aus der Liste aus.
|
|||
username_been_taken=Der Benutzername ist bereits vergeben.
|
||||
username_change_not_local_user=Nicht-lokale Benutzer dürfen ihren Nutzernamen nicht ändern.
|
||||
repo_name_been_taken=Der Repository-Name wird schon verwendet.
|
||||
repository_force_private=Privat erzwingen ist aktiviert: Private Repositories können nicht veröffentlicht werden.
|
||||
repository_force_private=Privat erzwingen ist aktiviert: Private Repositorys können nicht veröffentlicht werden.
|
||||
repository_files_already_exist=Dateien für dieses Repository sind bereits vorhanden. Kontaktiere den Systemadministrator.
|
||||
repository_files_already_exist.adopt=Dateien für dieses Repository existieren bereits und können nur übernommen werden.
|
||||
repository_files_already_exist.delete=Dateien für dieses Repository sind bereits vorhanden. Du must sie löschen.
|
||||
|
@ -487,7 +489,7 @@ invalid_ssh_principal=Ungültige Identität: %s
|
|||
unable_verify_ssh_key=Dein SSH-Key kann nicht überprüft werden, probiere es erneut.
|
||||
auth_failed=Authentifizierung fehlgeschlagen: %v
|
||||
|
||||
still_own_repo=Dein Konto besitzt ein oder mehrere Repositories. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
still_own_repo=Dein Konto besitzt ein oder mehrere Repositorys. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
still_has_org=Dein Account ist Mitglied in mindestens einer Organisation. Bitte verlasse diese zuerst.
|
||||
still_own_packages=Ihr Konto besitzt ein oder mehrere Pakete; löschen Sie diese zuerst.
|
||||
org_still_own_repo=Diese Organisation besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese zuerst.
|
||||
|
@ -498,7 +500,7 @@ target_branch_not_exist=Der Ziel-Branch existiert nicht.
|
|||
[user]
|
||||
change_avatar=Profilbild ändern…
|
||||
join_on=Beigetreten am
|
||||
repositories=Repositories
|
||||
repositories=Repositorys
|
||||
activity=Öffentliche Aktivität
|
||||
followers=Follower
|
||||
starred=Favoriten
|
||||
|
@ -970,7 +972,7 @@ migrate.gogs.description=Daten von notabug.org oder anderen Gogs Instanzen migri
|
|||
migrate.onedev.description=Daten von code.onedev.io oder anderen OneDev Instanzen migrieren.
|
||||
migrate.codebase.description=Daten von codebasehq.com migrieren.
|
||||
migrate.gitbucket.description=Daten von GitBucket Instanzen migrieren.
|
||||
migrate.migrating_git=Git Daten werden migriert
|
||||
migrate.migrating_git=Git-Daten werden migriert
|
||||
migrate.migrating_topics=Themen werden migriert
|
||||
migrate.migrating_milestones=Meilensteine werden migriert
|
||||
migrate.migrating_labels=Labels werden migriert
|
||||
|
@ -2335,7 +2337,7 @@ settings.delete_prompt=Die Organisation wird dauerhaft gelöscht. Dies <strong>K
|
|||
settings.confirm_delete_account=Löschen bestätigen
|
||||
settings.delete_org_title=Organisation löschen
|
||||
settings.delete_org_desc=Diese Organisation wird dauerhaft gelöscht. Fortfahren?
|
||||
settings.hooks_desc=Webhooks hinzufügen, die für <strong>alle</strong> Repositories dieser Organisation ausgelöst werden.
|
||||
settings.hooks_desc=Webhooks hinzufügen, die für <strong>alle</strong> Repositorys dieser Organisation ausgelöst werden.
|
||||
|
||||
settings.labels_desc=Labels hinzufügen, die für <strong>alle Repositories</strong> dieser Organisation genutzt werden können.
|
||||
|
||||
|
@ -2439,7 +2441,7 @@ dashboard.cron.error=Fehler in Cron: %s: %[3]s
|
|||
dashboard.cron.finished=Cron: %[1]s ist beendet
|
||||
dashboard.delete_inactive_accounts=Alle nicht aktivierten Konten löschen
|
||||
dashboard.delete_inactive_accounts.started=Löschen aller nicht aktivierten Account-Aufgabe gestartet.
|
||||
dashboard.delete_repo_archives=Lösche alle Repository Archive (ZIP, TAR.GZ, …)
|
||||
dashboard.delete_repo_archives=Lösche alle Repository-Archive (ZIP, TAR.GZ, …)
|
||||
dashboard.delete_repo_archives.started=Löschen aller Repository-Archive gestartet.
|
||||
dashboard.delete_missing_repos=Alle Repository-Datensätze mit verloren gegangenen Git-Dateien löschen
|
||||
dashboard.delete_missing_repos.started=Alle Repositories löschen, die die Git-Dateien-Aufgabe nicht gestartet haben.
|
||||
|
@ -2528,8 +2530,9 @@ users.allow_create_organization=Darf Organisationen erstellen
|
|||
users.update_profile=Benutzerkonto aktualisieren
|
||||
users.delete_account=Benutzerkonto löschen
|
||||
users.cannot_delete_self=Du kannst dich nicht selbst löschen
|
||||
users.still_own_repo=Dieser Benutzer besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese zuerst.
|
||||
users.still_own_repo=Dieser Benutzer besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese(s) zuerst.
|
||||
users.still_has_org=Dieser Nutzer ist Mitglied einer Organisation. Du musst ihn zuerst aus allen Organisationen entfernen.
|
||||
users.purge_help=Erzwinge das Löschen des Benutzers inklusive aller seiner Repositorys, Organisationen, Pakete und Kommentare.
|
||||
users.still_own_packages=Dieser Benutzer besitzt noch ein oder mehrere Pakete. Lösche diese Pakete zuerst.
|
||||
users.deletion_success=Der Account wurde gelöscht.
|
||||
users.reset_2fa=2FA zurücksetzen
|
||||
|
|
|
@ -268,8 +268,11 @@ users = Users
|
|||
organizations = Organizations
|
||||
search = Search
|
||||
code = Code
|
||||
search.type.tooltip = Search type
|
||||
search.fuzzy = Fuzzy
|
||||
search.fuzzy.tooltip = Include results that also matches the search term closely
|
||||
search.match = Match
|
||||
search.match.tooltip = Include only results that matches the exact search term
|
||||
code_search_unavailable = Currently code search is not available. Please contact your site administrator.
|
||||
repo_no_results = No matching repositories found.
|
||||
user_no_results = No matching users found.
|
||||
|
@ -507,6 +510,7 @@ activity = Public Activity
|
|||
followers = Followers
|
||||
starred = Starred Repositories
|
||||
watched = Watched Repositories
|
||||
code = Code
|
||||
projects = Projects
|
||||
following = Following
|
||||
follow = Follow
|
||||
|
@ -1763,8 +1767,11 @@ activity.git_stats_deletion_n = %d deletions
|
|||
|
||||
search = Search
|
||||
search.search_repo = Search repository
|
||||
search.type.tooltip = Search type
|
||||
search.fuzzy = Fuzzy
|
||||
search.fuzzy.tooltip = Include results that also matches the search term closely
|
||||
search.match = Match
|
||||
search.match.tooltip = Include only results that matches the exact search term
|
||||
search.results = Search results for "%s" in <a href="%s">%s</a>
|
||||
search.code_no_results = No source code matching your search term found.
|
||||
search.code_search_unavailable = Currently code search is not available. Please contact your site administrator.
|
||||
|
@ -2310,6 +2317,7 @@ create_org = Create Organization
|
|||
repo_updated = Updated
|
||||
people = People
|
||||
teams = Teams
|
||||
code = Code
|
||||
lower_members = members
|
||||
lower_repositories = repositories
|
||||
create_new_team = New Team
|
||||
|
|
|
@ -268,8 +268,11 @@ users=Utilizadores
|
|||
organizations=Organizações
|
||||
search=Procurar
|
||||
code=Código
|
||||
search.type.tooltip=Tipo de pesquisa
|
||||
search.fuzzy=Aproximada
|
||||
search.fuzzy.tooltip=Incluir também os resultados que estejam próximos do termo de pesquisa
|
||||
search.match=Fiel
|
||||
search.match.tooltip=Incluir somente os resultados que correspondam rigorosamente ao termo de pesquisa
|
||||
code_search_unavailable=A pesquisa por código-fonte não está disponível, neste momento. Entre em contacto com o administrador.
|
||||
repo_no_results=Não foram encontrados quaisquer repositórios correspondentes.
|
||||
user_no_results=Não foram encontrados quaisquer utilizadores correspondentes.
|
||||
|
@ -507,6 +510,7 @@ activity=Trabalho público
|
|||
followers=Seguidores
|
||||
starred=Repositórios favoritos
|
||||
watched=Repositórios sob vigilância
|
||||
code=Código
|
||||
projects=Planeamentos
|
||||
following=Que segue
|
||||
follow=Seguir
|
||||
|
@ -1763,8 +1767,11 @@ activity.git_stats_deletion_n=%d eliminações
|
|||
|
||||
search=Procurar
|
||||
search.search_repo=Procurar repositório
|
||||
search.type.tooltip=Tipo de pesquisa
|
||||
search.fuzzy=Aproximada
|
||||
search.fuzzy.tooltip=Incluir também os resultados que estejam próximos do termo de pesquisa
|
||||
search.match=Fiel
|
||||
search.match.tooltip=Incluir somente os resultados que correspondam rigorosamente ao termo de pesquisa
|
||||
search.results=Resultados da procura de "%s" em <a href="%s">%s</a>
|
||||
search.code_no_results=Não foi encontrado qualquer código-fonte correspondente à sua pesquisa.
|
||||
search.code_search_unavailable=A pesquisa por código-fonte não está disponível, neste momento. Entre em contacto com o administrador.
|
||||
|
@ -2310,6 +2317,7 @@ create_org=Criar organização
|
|||
repo_updated=Modificado
|
||||
people=Pessoas
|
||||
teams=Equipas
|
||||
code=Código
|
||||
lower_members=membros
|
||||
lower_repositories=repositórios
|
||||
create_new_team=Nova equipa
|
||||
|
|
2886
package-lock.json
generated
2886
package-lock.json
generated
File diff suppressed because it is too large
Load diff
32
package.json
32
package.json
|
@ -8,13 +8,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@claviska/jquery-minicolors": "2.3.6",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-2",
|
||||
"@primer/octicons": "17.5.0",
|
||||
"@vue/compiler-sfc": "3.2.37",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "17.7.0",
|
||||
"@vue/compiler-sfc": "3.2.40",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"css-loader": "6.7.1",
|
||||
"dropzone": "6.0.0-beta.2",
|
||||
"easymde": "2.17.0",
|
||||
"easymde": "2.18.0",
|
||||
"esbuild-loader": "2.20.0",
|
||||
"escape-goat": "4.0.0",
|
||||
"fast-glob": "3.2.12",
|
||||
|
@ -23,19 +23,19 @@
|
|||
"jquery.are-you-sure": "1.9.0",
|
||||
"katex": "0.16.2",
|
||||
"less": "4.1.3",
|
||||
"less-loader": "11.0.0",
|
||||
"less-loader": "11.1.0",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"mermaid": "9.1.6",
|
||||
"mermaid": "9.1.7",
|
||||
"mini-css-extract-plugin": "2.6.1",
|
||||
"monaco-editor": "0.34.0",
|
||||
"monaco-editor-webpack-plugin": "7.0.1",
|
||||
"pretty-ms": "8.0.0",
|
||||
"sortablejs": "1.15.0",
|
||||
"swagger-ui-dist": "4.14.0",
|
||||
"swagger-ui-dist": "4.14.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vue": "3.2.37",
|
||||
"vue": "3.2.40",
|
||||
"vue-bar-graph": "2.0.0",
|
||||
"vue-loader": "17.0.0",
|
||||
"vue3-calendar-heatmap": "2.0.0",
|
||||
|
@ -47,23 +47,23 @@
|
|||
"wrap-ansi": "8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.25.2",
|
||||
"@playwright/test": "1.27.0",
|
||||
"@stoplight/spectral-cli": "6.5.1",
|
||||
"eslint": "8.23.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-jquery": "1.5.1",
|
||||
"eslint-plugin-sonarjs": "0.15.0",
|
||||
"eslint-plugin-unicorn": "43.0.2",
|
||||
"eslint-plugin-vue": "9.4.0",
|
||||
"jest": "29.0.3",
|
||||
"jest-environment-jsdom": "29.0.3",
|
||||
"eslint-plugin-unicorn": "44.0.2",
|
||||
"eslint-plugin-vue": "9.6.0",
|
||||
"jest": "29.1.2",
|
||||
"jest-environment-jsdom": "29.1.2",
|
||||
"jest-extended": "3.1.0",
|
||||
"markdownlint-cli": "0.32.2",
|
||||
"postcss-less": "6.0.0",
|
||||
"stylelint": "14.11.0",
|
||||
"stylelint": "14.13.0",
|
||||
"stylelint-config-standard": "28.0.0",
|
||||
"svgo": "2.8.0",
|
||||
"updates": "13.1.5"
|
||||
"updates": "13.1.8"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
|
|
|
@ -24,7 +24,7 @@ export default {
|
|||
},
|
||||
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
forbidOnly: Boolean(process.env.CI),
|
||||
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
|
@ -64,12 +64,13 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},
|
||||
// disabled because of https://github.com/go-gitea/gitea/issues/21355
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: {
|
||||
// ...devices['Desktop Firefox'],
|
||||
// },
|
||||
// },
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
|
|
1
public/img/svg/octicon-alert-fill.svg
Normal file
1
public/img/svg/octicon-alert-fill.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 16 16" class="svg octicon-alert-fill" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575L6.457 1.047zM8 5a.75.75 0 0 1 .75.75v2.5a.75.75 0 0 1-1.5 0v-2.5A.75.75 0 0 1 8 5zm1 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/></svg>
|
After Width: | Height: | Size: 368 B |
|
@ -196,7 +196,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
|||
r.Put("/symbolpackage", nuget.UploadSymbolPackage)
|
||||
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-f]{32}}FFFFFFFF/{filename2}", nuget.DownloadSymbolFile)
|
||||
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
})
|
||||
r.Group("/npm", func() {
|
||||
|
@ -316,8 +316,10 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
|
|||
r.Group("/blobs/uploads", func() {
|
||||
r.Post("", container.InitiateUploadBlob)
|
||||
r.Group("/{uuid}", func() {
|
||||
r.Get("", container.GetUploadBlob)
|
||||
r.Patch("", container.UploadBlob)
|
||||
r.Put("", container.EndUploadBlob)
|
||||
r.Delete("", container.CancelUploadBlob)
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Group("/blobs/{digest}", func() {
|
||||
|
@ -377,7 +379,7 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
|
|||
}
|
||||
|
||||
m := blobsUploadsPattern.FindStringSubmatch(path)
|
||||
if len(m) == 3 && (isPut || isPatch) {
|
||||
if len(m) == 3 && (isGet || isPut || isPatch || isDelete) {
|
||||
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
|
@ -391,10 +393,14 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
|
|||
|
||||
ctx.SetParams("uuid", m[2])
|
||||
|
||||
if isPatch {
|
||||
if isGet {
|
||||
container.GetUploadBlob(ctx)
|
||||
} else if isPatch {
|
||||
container.UploadBlob(ctx)
|
||||
} else {
|
||||
} else if isPut {
|
||||
container.EndUploadBlob(ctx)
|
||||
} else {
|
||||
container.CancelUploadBlob(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
conan_model "code.gitea.io/gitea/models/packages/conan"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -33,20 +34,18 @@ const (
|
|||
packageReferenceKey = "PackageReference"
|
||||
)
|
||||
|
||||
type stringSet map[string]struct{}
|
||||
|
||||
var (
|
||||
recipeFileList = stringSet{
|
||||
conanfileFile: struct{}{},
|
||||
"conanmanifest.txt": struct{}{},
|
||||
"conan_sources.tgz": struct{}{},
|
||||
"conan_export.tgz": struct{}{},
|
||||
}
|
||||
packageFileList = stringSet{
|
||||
conaninfoFile: struct{}{},
|
||||
"conanmanifest.txt": struct{}{},
|
||||
"conan_package.tgz": struct{}{},
|
||||
}
|
||||
recipeFileList = container.SetOf(
|
||||
conanfileFile,
|
||||
"conanmanifest.txt",
|
||||
"conan_sources.tgz",
|
||||
"conan_export.tgz",
|
||||
)
|
||||
packageFileList = container.SetOf(
|
||||
conaninfoFile,
|
||||
"conanmanifest.txt",
|
||||
"conan_package.tgz",
|
||||
)
|
||||
)
|
||||
|
||||
func jsonResponse(ctx *context.Context, status int, obj interface{}) {
|
||||
|
@ -268,7 +267,7 @@ func PackageUploadURLs(ctx *context.Context) {
|
|||
)
|
||||
}
|
||||
|
||||
func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL string) {
|
||||
func serveUploadURLs(ctx *context.Context, fileFilter container.Set[string], uploadURL string) {
|
||||
defer ctx.Req.Body.Close()
|
||||
|
||||
var files map[string]int64
|
||||
|
@ -279,7 +278,7 @@ func serveUploadURLs(ctx *context.Context, fileFilter stringSet, uploadURL strin
|
|||
|
||||
urls := make(map[string]string)
|
||||
for file := range files {
|
||||
if _, ok := fileFilter[file]; ok {
|
||||
if fileFilter.Contains(file) {
|
||||
urls[file] = fmt.Sprintf("%s/%s", uploadURL, file)
|
||||
}
|
||||
}
|
||||
|
@ -301,12 +300,12 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
uploadFile(ctx, packageFileList, pref.AsKey())
|
||||
}
|
||||
|
||||
func uploadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
||||
func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
|
||||
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
||||
pref := ctx.Data[packageReferenceKey].(*conan_module.PackageReference)
|
||||
|
||||
filename := ctx.Params("filename")
|
||||
if _, ok := fileFilter[filename]; !ok {
|
||||
if !fileFilter.Contains(filename) {
|
||||
apiError(ctx, http.StatusBadRequest, nil)
|
||||
return
|
||||
}
|
||||
|
@ -342,8 +341,7 @@ func uploadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
|||
Name: rref.Name,
|
||||
Version: rref.Version,
|
||||
},
|
||||
SemverCompatible: true,
|
||||
Creator: ctx.Doer,
|
||||
Creator: ctx.Doer,
|
||||
}
|
||||
pfci := &packages_service.PackageFileCreationInfo{
|
||||
PackageFileInfo: packages_service.PackageFileInfo{
|
||||
|
@ -443,11 +441,11 @@ func DownloadPackageFile(ctx *context.Context) {
|
|||
downloadFile(ctx, packageFileList, pref.AsKey())
|
||||
}
|
||||
|
||||
func downloadFile(ctx *context.Context, fileFilter stringSet, fileKey string) {
|
||||
func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKey string) {
|
||||
rref := ctx.Data[recipeReferenceKey].(*conan_module.RecipeReference)
|
||||
|
||||
filename := ctx.Params("filename")
|
||||
if _, ok := fileFilter[filename]; !ok {
|
||||
if !fileFilter.Contains(filename) {
|
||||
apiError(ctx, http.StatusBadRequest, nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -248,6 +248,27 @@ func InitiateUploadBlob(ctx *context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// https://docs.docker.com/registry/spec/api/#get-blob-upload
|
||||
func GetUploadBlob(ctx *context.Context) {
|
||||
uuid := ctx.Params("uuid")
|
||||
|
||||
upload, err := packages_model.GetBlobUploadByID(ctx, uuid)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageBlobUploadNotExist {
|
||||
apiErrorDefined(ctx, errBlobUploadUnknown)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||
Range: fmt.Sprintf("0-%d", upload.BytesReceived),
|
||||
UploadUUID: upload.ID,
|
||||
Status: http.StatusNoContent,
|
||||
})
|
||||
}
|
||||
|
||||
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
|
||||
func UploadBlob(ctx *context.Context) {
|
||||
image := ctx.Params("image")
|
||||
|
@ -354,6 +375,30 @@ func EndUploadBlob(ctx *context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// https://docs.docker.com/registry/spec/api/#delete-blob-upload
|
||||
func CancelUploadBlob(ctx *context.Context) {
|
||||
uuid := ctx.Params("uuid")
|
||||
|
||||
_, err := packages_model.GetBlobUploadByID(ctx, uuid)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageBlobUploadNotExist {
|
||||
apiErrorDefined(ctx, errBlobUploadUnknown)
|
||||
} else {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := container_service.RemoveBlobUploadByID(ctx, uuid); err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||
Status: http.StatusNoContent,
|
||||
})
|
||||
}
|
||||
|
||||
func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescriptor, error) {
|
||||
digest := ctx.Params("digest")
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
|
|||
PeerDependencies: metadata.PeerDependencies,
|
||||
OptionalDependencies: metadata.OptionalDependencies,
|
||||
Readme: metadata.Readme,
|
||||
Bin: metadata.Bin,
|
||||
Dist: npm_module.PackageDistribution{
|
||||
Shasum: pd.Files[0].Blob.HashSHA1,
|
||||
Integrity: "sha512-" + base64.StdEncoding.EncodeToString(hashBytes),
|
||||
|
|
|
@ -353,7 +353,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package
|
|||
// DownloadSymbolFile https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
|
||||
func DownloadSymbolFile(ctx *context.Context) {
|
||||
filename := ctx.Params("filename")
|
||||
guid := ctx.Params("guid")
|
||||
guid := ctx.Params("guid")[:32]
|
||||
filename2 := ctx.Params("filename2")
|
||||
|
||||
if filename != filename2 {
|
||||
|
|
|
@ -70,7 +70,7 @@ func getCommit(ctx *context.APIContext, identifier string) {
|
|||
return
|
||||
}
|
||||
|
||||
json, err := convert.ToCommit(ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil)
|
||||
json, err := convert.ToCommit(ctx.Repo.Repository, ctx.Repo.GitRepo, commit, nil, true)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "toCommit", err)
|
||||
return
|
||||
|
@ -104,6 +104,10 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||
// in: query
|
||||
// description: filepath of a file/dir
|
||||
// type: string
|
||||
// - name: stat
|
||||
// in: query
|
||||
// description: include diff stats for every commit (disable for speedup, default 'true')
|
||||
// type: boolean
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
|
@ -209,9 +213,12 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||
userCache := make(map[string]*user_model.User)
|
||||
|
||||
apiCommits := make([]*api.Commit, len(commits))
|
||||
|
||||
stat := ctx.FormString("stat") == "" || ctx.FormBool("stat")
|
||||
|
||||
for i, commit := range commits {
|
||||
// Create json struct
|
||||
apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache)
|
||||
apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, ctx.Repo.GitRepo, commit, userCache, stat)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "toCommit", err)
|
||||
return
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue