Merge remote-tracking branch 'origin/main' into richmahn-11835-api-for-git-refs

This commit is contained in:
Richard Mahn 2022-10-12 15:17:22 -06:00
commit 49ab440cef
187 changed files with 4140 additions and 2783 deletions

View file

@ -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=''

View file

@ -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
View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,12 @@ menu:
{{< toc >}}
## Quickstart
To get a quick working development environment you could use Gitpod.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](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:

View file

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

View file

@ -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
View file

@ -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
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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'",
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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"))
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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',

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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