Merge branch 'main' into main
This commit is contained in:
commit
d87a16b86f
553 changed files with 4787 additions and 64021 deletions
|
@ -804,11 +804,12 @@ steps:
|
||||||
depends_on: [gpg-sign]
|
depends_on: [gpg-sign]
|
||||||
|
|
||||||
- name: github
|
- name: github
|
||||||
image: plugins/github-release:1
|
image: plugins/github-release:latest
|
||||||
pull: always
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
files:
|
files:
|
||||||
- "dist/release/*"
|
- "dist/release/*"
|
||||||
|
file_exists: overwrite
|
||||||
environment:
|
environment:
|
||||||
GITHUB_TOKEN:
|
GITHUB_TOKEN:
|
||||||
from_secret: github_token
|
from_secret: github_token
|
||||||
|
|
|
@ -442,6 +442,7 @@ rules:
|
||||||
unicorn/require-post-message-target-origin: [0]
|
unicorn/require-post-message-target-origin: [0]
|
||||||
unicorn/string-content: [0]
|
unicorn/string-content: [0]
|
||||||
unicorn/template-indent: [2]
|
unicorn/template-indent: [2]
|
||||||
|
unicorn/text-encoding-identifier-case: [0]
|
||||||
unicorn/throw-new-error: [2]
|
unicorn/throw-new-error: [2]
|
||||||
use-isnan: [2]
|
use-isnan: [2]
|
||||||
valid-typeof: [2, {requireStringLiterals: true}]
|
valid-typeof: [2, {requireStringLiterals: true}]
|
||||||
|
|
|
@ -14,6 +14,7 @@ rules:
|
||||||
declaration-block-no-redundant-longhand-properties: null
|
declaration-block-no-redundant-longhand-properties: null
|
||||||
declaration-block-single-line-max-declarations: null
|
declaration-block-single-line-max-declarations: null
|
||||||
declaration-empty-line-before: null
|
declaration-empty-line-before: null
|
||||||
|
function-no-unknown: null
|
||||||
hue-degree-notation: null
|
hue-degree-notation: null
|
||||||
indentation: 2
|
indentation: 2
|
||||||
max-line-length: null
|
max-line-length: null
|
||||||
|
|
|
@ -423,6 +423,10 @@ be reviewed by two maintainers and must pass the automatic tests.
|
||||||
* And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
* And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
||||||
* If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
* If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||||
* Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
* Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||||
|
* Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
|
||||||
|
* bump the version of https://dl.gitea.io/gitea/version.json
|
||||||
|
* merge the blog post PR
|
||||||
|
* announce the release in discord `#announcements`
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
|
|
26
Makefile
26
Makefile
|
@ -235,7 +235,7 @@ clean:
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
@hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) install mvdan.cc/gofumpt@latest; \
|
$(GO) install mvdan.cc/gofumpt@v0.3.0; \
|
||||||
fi
|
fi
|
||||||
@echo "Running gitea-fmt (with gofumpt)..."
|
@echo "Running gitea-fmt (with gofumpt)..."
|
||||||
@$(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
@$(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
|
||||||
|
@ -287,7 +287,7 @@ errcheck:
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) install mvdan.cc/gofumpt@latest; \
|
$(GO) install mvdan.cc/gofumpt@0.3.0; \
|
||||||
fi
|
fi
|
||||||
# get all go files and run gitea-fmt (with gofmt) on them
|
# get all go files and run gitea-fmt (with gofmt) on them
|
||||||
@diff=$$($(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \
|
@diff=$$($(GO) run build/code-batch-process.go gitea-fmt -l '{file-list}'); \
|
||||||
|
@ -313,10 +313,9 @@ lint: lint-frontend lint-backend
|
||||||
lint-frontend: node_modules
|
lint-frontend: node_modules
|
||||||
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js docs/assets/js
|
npx eslint --color --max-warnings=0 web_src/js build templates *.config.js docs/assets/js
|
||||||
npx stylelint --color --max-warnings=0 web_src/less
|
npx stylelint --color --max-warnings=0 web_src/less
|
||||||
npx editorconfig-checker templates
|
|
||||||
|
|
||||||
.PHONY: lint-backend
|
.PHONY: lint-backend
|
||||||
lint-backend: golangci-lint vet
|
lint-backend: golangci-lint vet editorconfig-checker
|
||||||
|
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
watch:
|
watch:
|
||||||
|
@ -405,6 +404,11 @@ test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
|
||||||
|
|
||||||
|
.PHONY: test-sqlite-migration\#%
|
||||||
|
test-sqlite-migration\#%: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
|
||||||
|
|
||||||
|
|
||||||
generate-ini-mysql:
|
generate-ini-mysql:
|
||||||
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
||||||
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
|
||||||
|
@ -510,6 +514,10 @@ bench-pgsql: integrations.pgsql.test generate-ini-pgsql
|
||||||
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
integration-test-coverage: integrations.cover.test generate-ini-mysql
|
||||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
|
.PHONY: integration-test-coverage-sqlite
|
||||||
|
integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite
|
||||||
|
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out
|
||||||
|
|
||||||
integrations.mysql.test: git-check $(GO_SOURCES)
|
integrations.mysql.test: git-check $(GO_SOURCES)
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.mysql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -o integrations.mysql.test
|
||||||
|
|
||||||
|
@ -528,6 +536,9 @@ integrations.sqlite.test: git-check $(GO_SOURCES)
|
||||||
integrations.cover.test: git-check $(GO_SOURCES)
|
integrations.cover.test: git-check $(GO_SOURCES)
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.test
|
||||||
|
|
||||||
|
integrations.cover.sqlite.test: git-check $(GO_SOURCES)
|
||||||
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(GO_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)'
|
||||||
|
|
||||||
.PHONY: migrations.mysql.test
|
.PHONY: migrations.mysql.test
|
||||||
migrations.mysql.test: $(GO_SOURCES)
|
migrations.mysql.test: $(GO_SOURCES)
|
||||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql.test
|
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql.test
|
||||||
|
@ -784,6 +795,13 @@ golangci-lint-check:
|
||||||
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
|
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
.PHONY: editorconfig-checker
|
||||||
|
editorconfig-checker:
|
||||||
|
@hash editorconfig-checker > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) install github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@50adf46752da119dfef66e57be3ce2693ea4aa9c; \
|
||||||
|
fi
|
||||||
|
editorconfig-checker templates
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||||
|
|
57
cmd/admin.go
57
cmd/admin.go
|
@ -56,6 +56,7 @@ var (
|
||||||
microcmdUserList,
|
microcmdUserList,
|
||||||
microcmdUserChangePassword,
|
microcmdUserChangePassword,
|
||||||
microcmdUserDelete,
|
microcmdUserDelete,
|
||||||
|
microcmdUserGenerateAccessToken,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +155,27 @@ var (
|
||||||
Action: runDeleteUser,
|
Action: runDeleteUser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
microcmdUserGenerateAccessToken = cli.Command{
|
||||||
|
Name: "generate-access-token",
|
||||||
|
Usage: "Generate a access token for a specific user",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username,u",
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "token-name,t",
|
||||||
|
Usage: "Token name",
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "raw",
|
||||||
|
Usage: "Display only the token value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runGenerateAccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
subcmdRepoSyncReleases = cli.Command{
|
subcmdRepoSyncReleases = cli.Command{
|
||||||
Name: "repo-sync-releases",
|
Name: "repo-sync-releases",
|
||||||
Usage: "Synchronize repository releases with tags",
|
Usage: "Synchronize repository releases with tags",
|
||||||
|
@ -641,6 +663,41 @@ func runDeleteUser(c *cli.Context) error {
|
||||||
return user_service.DeleteUser(user)
|
return user_service.DeleteUser(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runGenerateAccessToken(c *cli.Context) error {
|
||||||
|
if !c.IsSet("username") {
|
||||||
|
return fmt.Errorf("You must provide the username to generate a token for them")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := user_model.GetUserByName(c.String("username"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &models.AccessToken{
|
||||||
|
Name: c.String("token-name"),
|
||||||
|
UID: user.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.NewAccessToken(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool("raw") {
|
||||||
|
fmt.Printf("%s\n", t.Token)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Access token was successfully created: %s\n", t.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func runRepoSyncReleases(_ *cli.Context) error {
|
func runRepoSyncReleases(_ *cli.Context) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -4,5 +4,5 @@ grep 'git' go.mod | grep '\.com' | grep -v indirect | grep -v replace | cut -f 2
|
||||||
go get -u "$line"
|
go get -u "$line"
|
||||||
make vendor
|
make vendor
|
||||||
git add .
|
git add .
|
||||||
git commit -S -m "update $line"
|
git commit -m "update $line"
|
||||||
done
|
done
|
||||||
|
|
|
@ -890,7 +890,7 @@ PATH =
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Path for local repository copy. Defaults to `tmp/local-repo`
|
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart)
|
||||||
;LOCAL_COPY_PATH = tmp/local-repo
|
;LOCAL_COPY_PATH = tmp/local-repo
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -902,7 +902,7 @@ PATH =
|
||||||
;; Whether repository file uploads are enabled. Defaults to `true`
|
;; Whether repository file uploads are enabled. Defaults to `true`
|
||||||
;ENABLED = true
|
;ENABLED = true
|
||||||
;;
|
;;
|
||||||
;; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart)
|
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart)
|
||||||
;TEMP_PATH = data/tmp/uploads
|
;TEMP_PATH = data/tmp/uploads
|
||||||
;;
|
;;
|
||||||
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
|
@ -1115,7 +1115,7 @@ PATH =
|
||||||
;SEARCH_REPO_DESCRIPTION = true
|
;SEARCH_REPO_DESCRIPTION = true
|
||||||
;;
|
;;
|
||||||
;; Whether to enable a Service Worker to cache frontend assets
|
;; Whether to enable a Service Worker to cache frontend assets
|
||||||
;USE_SERVICE_WORKER = true
|
;USE_SERVICE_WORKER = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -2125,6 +2125,8 @@ PATH =
|
||||||
;RENDER_COMMAND = "asciidoc --out-file=- -"
|
;RENDER_COMMAND = "asciidoc --out-file=- -"
|
||||||
;; Don't pass the file on STDIN, pass the filename as argument instead.
|
;; Don't pass the file on STDIN, pass the filename as argument instead.
|
||||||
;IS_INPUT_FILE = false
|
;IS_INPUT_FILE = false
|
||||||
|
; Don't filter html tags and attributes if true
|
||||||
|
;DISABLE_SANITIZER = false
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -18,7 +18,7 @@ params:
|
||||||
description: Git with a cup of tea
|
description: Git with a cup of tea
|
||||||
author: The Gitea Authors
|
author: The Gitea Authors
|
||||||
website: https://docs.gitea.io
|
website: https://docs.gitea.io
|
||||||
version: 1.16.0
|
version: 1.16.3
|
||||||
minGoVersion: 1.16
|
minGoVersion: 1.16
|
||||||
goVersion: 1.17
|
goVersion: 1.17
|
||||||
minNodeVersion: 12.17
|
minNodeVersion: 12.17
|
||||||
|
|
|
@ -107,7 +107,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
### Repository - Upload (`repository.upload`)
|
### Repository - Upload (`repository.upload`)
|
||||||
|
|
||||||
- `ENABLED`: **true**: Whether repository file uploads are enabled
|
- `ENABLED`: **true**: Whether repository file uploads are enabled
|
||||||
- `TEMP_PATH`: **data/tmp/uploads**: Path for uploads (tmp gets deleted on Gitea restart)
|
- `TEMP_PATH`: **data/tmp/uploads**: Path for uploads (content gets deleted on Gitea restart)
|
||||||
- `ALLOWED_TYPES`: **\<empty\>**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
- `ALLOWED_TYPES`: **\<empty\>**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
- `FILE_MAX_SIZE`: **3**: Max size of each file in megabytes.
|
- `FILE_MAX_SIZE`: **3**: Max size of each file in megabytes.
|
||||||
- `MAX_FILES`: **5**: Max number of files per upload
|
- `MAX_FILES`: **5**: Max number of files per upload
|
||||||
|
@ -144,7 +144,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
|
|
||||||
## Repository - Local (`repository.local`)
|
## Repository - Local (`repository.local`)
|
||||||
|
|
||||||
- `LOCAL_COPY_PATH`: **tmp/local-repo**: Path for temporary local repository copies. Defaults to `tmp/local-repo`
|
- `LOCAL_COPY_PATH`: **tmp/local-repo**: Path for temporary local repository copies. Defaults to `tmp/local-repo` (content gets deleted on Gitea restart)
|
||||||
|
|
||||||
## Repository - MIME type mapping (`repository.mimetype_mapping`)
|
## Repository - MIME type mapping (`repository.mimetype_mapping`)
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
add it to this config.
|
add it to this config.
|
||||||
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
|
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
|
||||||
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
|
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
|
||||||
- `USE_SERVICE_WORKER`: **true**: Whether to enable a Service Worker to cache frontend assets.
|
- `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets.
|
||||||
|
|
||||||
### UI - Admin (`ui.admin`)
|
### UI - Admin (`ui.admin`)
|
||||||
|
|
||||||
|
@ -1003,13 +1003,13 @@ IS_INPUT_FILE = false
|
||||||
command. Multiple extensions needs a comma as splitter.
|
command. Multiple extensions needs a comma as splitter.
|
||||||
- RENDER\_COMMAND: External command to render all matching extensions.
|
- RENDER\_COMMAND: External command to render all matching extensions.
|
||||||
- IS\_INPUT\_FILE: **false** Input is not a standard input but a file param followed `RENDER_COMMAND`.
|
- IS\_INPUT\_FILE: **false** Input is not a standard input but a file param followed `RENDER_COMMAND`.
|
||||||
|
- DISABLE_SANITIZER: **false** Don't filter html tags and attributes if true. Don't change this to true except you know what that means.
|
||||||
|
|
||||||
Two special environment variables are passed to the render command:
|
Two special environment variables are passed to the render command:
|
||||||
- `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links.
|
- `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links.
|
||||||
- `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths.
|
- `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths.
|
||||||
|
|
||||||
|
If `DISABLE_SANITIZER` is false, Gitea supports customizing the sanitization policy for rendered HTML. The example below will support KaTeX output from pandoc.
|
||||||
Gitea supports customizing the sanitization policy for rendered HTML. The example below will support KaTeX output from pandoc.
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[markup.sanitizer.TeX]
|
[markup.sanitizer.TeX]
|
||||||
|
|
|
@ -318,6 +318,33 @@ IS_INPUT_FILE = false
|
||||||
- FILE_EXTENSIONS: 关联的文档的扩展名,多个扩展名用都好分隔。
|
- FILE_EXTENSIONS: 关联的文档的扩展名,多个扩展名用都好分隔。
|
||||||
- RENDER_COMMAND: 工具的命令行命令及参数。
|
- RENDER_COMMAND: 工具的命令行命令及参数。
|
||||||
- IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。
|
- IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。
|
||||||
|
- DISABLE_SANITIZER: **false** 如果为 true 则不过滤 HTML 标签和属性。除非你知道这意味着什么,否则不要设置为 true。
|
||||||
|
|
||||||
|
以下两个环境变量将会被传递给渲染命令:
|
||||||
|
|
||||||
|
- `GITEA_PREFIX_SRC`:包含当前的`src`路径的URL前缀,可以被用于链接的前缀。
|
||||||
|
- `GITEA_PREFIX_RAW`:包含当前的`raw`路径的URL前缀,可以被用于图片的前缀。
|
||||||
|
|
||||||
|
如果 `DISABLE_SANITIZER` 为 false,则 Gitea 支持自定义渲染 HTML 的净化策略。以下例子将用 pandoc 支持 KaTeX 输出。
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[markup.sanitizer.TeX]
|
||||||
|
; Pandoc renders TeX segments as <span>s with the "math" class, optionally
|
||||||
|
; with "inline" or "display" classes depending on context.
|
||||||
|
ELEMENT = span
|
||||||
|
ALLOW_ATTR = class
|
||||||
|
REGEXP = ^\s*((math(\s+|$)|inline(\s+|$)|display(\s+|$)))+
|
||||||
|
ALLOW_DATA_URI_IMAGES = true
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ELEMENT`: 将要被应用到该策略的 HTML 元素,不能为空。
|
||||||
|
- `ALLOW_ATTR`: 将要被应用到该策略的属性,不能为空。
|
||||||
|
- `REGEXP`: 正则表达式,用来匹配属性的内容。如果为空,则跟属性内容无关。
|
||||||
|
- `ALLOW_DATA_URI_IMAGES`: **false** 允许 data uri 图片 (`<img src="data:image/png;base64,..."/>`)。
|
||||||
|
|
||||||
|
多个净化规则可以被同时定义,只要section名称最后一位不重复即可。如: `[markup.sanitizer.TeX-2]`。
|
||||||
|
为了针对一种渲染类型进行一个特殊的净化策略,必须使用形如 `[markup.sanitizer.asciidoc.rule-1]` 的方式来命名 seciton。
|
||||||
|
如果此规则没有匹配到任何渲染类型,它将会被应用到所有的渲染类型。
|
||||||
|
|
||||||
## Time (`time`)
|
## Time (`time`)
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,27 @@ Once your configuration changes have been made, restart Gitea to have changes ta
|
||||||
**Note**: Prior to Gitea 1.12 there was a single `markup.sanitiser` section with keys that were redefined for multiple rules, however,
|
**Note**: Prior to Gitea 1.12 there was a single `markup.sanitiser` section with keys that were redefined for multiple rules, however,
|
||||||
there were significant problems with this method of configuration necessitating configuration through multiple sections.
|
there were significant problems with this method of configuration necessitating configuration through multiple sections.
|
||||||
|
|
||||||
|
### Example: HTML
|
||||||
|
|
||||||
|
Render HTML files directly:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[markup.html]
|
||||||
|
ENABLED = true
|
||||||
|
FILE_EXTENSIONS = .html,.htm
|
||||||
|
RENDER_COMMAND = cat
|
||||||
|
; Input is not a standard input but a file
|
||||||
|
IS_INPUT_FILE = true
|
||||||
|
|
||||||
|
[markup.sanitizer.html.1]
|
||||||
|
ELEMENT = div
|
||||||
|
ALLOW_ATTR = class
|
||||||
|
|
||||||
|
[markup.sanitizer.html.2]
|
||||||
|
ELEMENT = a
|
||||||
|
ALLOW_ATTR = class
|
||||||
|
```
|
||||||
|
|
||||||
### Example: Office DOCX
|
### Example: Office DOCX
|
||||||
|
|
||||||
Display Office DOCX files with [`pandoc`](https://pandoc.org/):
|
Display Office DOCX files with [`pandoc`](https://pandoc.org/):
|
||||||
|
|
|
@ -185,8 +185,6 @@ Before committing, make sure the linters pass:
|
||||||
make lint-frontend
|
make lint-frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: When working on frontend code, set `USE_SERVICE_WORKER` to `false` in `app.ini` to prevent undesirable caching of frontend assets.
|
|
||||||
|
|
||||||
### Configuring local ElasticSearch instance
|
### Configuring local ElasticSearch instance
|
||||||
|
|
||||||
Start local ElasticSearch instance using docker:
|
Start local ElasticSearch instance using docker:
|
||||||
|
|
|
@ -48,3 +48,9 @@ To deploy Gitea to DigitalOcean, have a look at the [DigitalOcean Marketplace](h
|
||||||
[Linode](https://www.linode.com/) has Gitea as an app in their marketplace.
|
[Linode](https://www.linode.com/) has Gitea as an app in their marketplace.
|
||||||
|
|
||||||
To deploy Gitea to Linode, have a look at the [Linode Marketplace](https://www.linode.com/marketplace/apps/linode/gitea/).
|
To deploy Gitea to Linode, have a look at the [Linode Marketplace](https://www.linode.com/marketplace/apps/linode/gitea/).
|
||||||
|
|
||||||
|
## alwaysdata
|
||||||
|
|
||||||
|
[alwaysdata](https://www.alwaysdata.com/) has Gitea as an app in their marketplace.
|
||||||
|
|
||||||
|
To deploy Gitea to alwaysdata, have a look at the [alwaysdata Marketplace](https://www.alwaysdata.com/en/marketplace/gitea/).
|
||||||
|
|
|
@ -95,3 +95,59 @@ Repository Git Hooks should be regenerated if installation method is changed (eg
|
||||||
With Gitea running, and from the directory Gitea's binary is located, execute: `./gitea admin regenerate hooks`
|
With Gitea running, and from the directory Gitea's binary is located, execute: `./gitea admin regenerate hooks`
|
||||||
|
|
||||||
This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail.
|
This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail.
|
||||||
|
|
||||||
|
### Using Docker (`restore`)
|
||||||
|
|
||||||
|
There is also no support for a recovery command in a Docker-based gitea instance. The restore process contains the same steps as described in the previous section but with different paths.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# open bash session in container
|
||||||
|
docker exec --user git -it 2a83b293548e bash
|
||||||
|
# unzip your backup file within the container
|
||||||
|
unzip gitea-dump-1610949662.zip
|
||||||
|
cd gitea-dump-1610949662
|
||||||
|
# restore the gitea data
|
||||||
|
mv data/* /data/gitea
|
||||||
|
# restore the repositories itself
|
||||||
|
mv repos/* /data/git/repositories/
|
||||||
|
# adjust file permissions
|
||||||
|
chown -R git:git /data
|
||||||
|
# Regenerate Git Hooks
|
||||||
|
/usr/local/bin/gitea -c '/data/gitea/conf/app.ini' admin regenerate hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
The default user in the gitea container is `git` (1000:1000). Please replace `2a83b293548e` with your gitea container id or name.
|
||||||
|
|
||||||
|
These are the default paths used in the container:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DEFAULT CONFIGURATION:
|
||||||
|
CustomPath: /data/gitea (GITEA_CUSTOM)
|
||||||
|
CustomConf: /data/gitea/conf/app.ini
|
||||||
|
AppPath: /usr/local/bin/gitea
|
||||||
|
AppWorkPath: /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Docker-rootless (`restore`)
|
||||||
|
|
||||||
|
The restore workflow in Docker-rootless containers differs only in the directories to be used:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# open bash session in container
|
||||||
|
docker exec --user git -it 2a83b293548e bash
|
||||||
|
# unzip your backup file within the container
|
||||||
|
unzip gitea-dump-1610949662.zip
|
||||||
|
cd gitea-dump-1610949662
|
||||||
|
# restore the app.ini
|
||||||
|
mv data/conf/app.ini /etc/gitea/app.ini
|
||||||
|
# restore the gitea data
|
||||||
|
mv data/* /var/lib/gitea
|
||||||
|
# restore the repositories itself
|
||||||
|
mv repos/* /var/lib/gitea/git/repositories
|
||||||
|
# adjust file permissions
|
||||||
|
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
|
||||||
|
# Regenerate Git Hooks
|
||||||
|
/usr/local/bin/gitea -c '/etc/gitea/app.ini' admin regenerate hooks
|
||||||
|
```
|
||||||
|
|
|
@ -30,6 +30,10 @@ server {
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:3000;
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -47,6 +51,10 @@ server {
|
||||||
location /git/ {
|
location /git/ {
|
||||||
# Note: Trailing slash
|
# Note: Trailing slash
|
||||||
proxy_pass http://localhost:3000/;
|
proxy_pass http://localhost:3000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
118
go.mod
118
go.mod
|
@ -3,100 +3,100 @@ module code.gitea.io/gitea
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.78.0 // indirect
|
|
||||||
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
|
code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
|
||||||
code.gitea.io/sdk/gitea v0.15.1
|
code.gitea.io/sdk/gitea v0.15.1
|
||||||
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
|
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
|
||||||
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1
|
gitea.com/go-chi/cache v0.0.0-20211201020628-dcb774c4ffea
|
||||||
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
|
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
|
||||||
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
|
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
|
||||||
gitea.com/lunny/levelqueue v0.4.1
|
gitea.com/lunny/levelqueue v0.4.1
|
||||||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210705153151-cc34b1f6908b // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.7.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/alecthomas/chroma v0.10.0
|
github.com/alecthomas/chroma v0.10.0
|
||||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
github.com/bits-and-blooms/bitset v1.2.1 // indirect
|
||||||
github.com/blevesearch/bleve/v2 v2.3.0
|
github.com/blevesearch/bleve/v2 v2.3.1
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||||
github.com/caddyserver/certmagic v0.15.2
|
github.com/caddyserver/certmagic v0.15.4
|
||||||
github.com/chi-middleware/proxy v1.1.1
|
github.com/chi-middleware/proxy v1.1.1
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
|
github.com/couchbase/go-couchbase v0.0.0-20210224140812-5740cd35f448 // indirect
|
||||||
github.com/couchbase/gomemcached v0.1.2 // indirect
|
github.com/couchbase/gomemcached v0.1.2 // indirect
|
||||||
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
|
github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 // indirect
|
||||||
github.com/denisenkom/go-mssqldb v0.10.0
|
github.com/denisenkom/go-mssqldb v0.12.0
|
||||||
github.com/djherbis/buffer v1.2.0
|
github.com/djherbis/buffer v1.2.0
|
||||||
github.com/djherbis/nio/v3 v3.0.1
|
github.com/djherbis/nio/v3 v3.0.1
|
||||||
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
|
github.com/duo-labs/webauthn v0.0.0-20220223184316-4d1cf2d34051
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
|
github.com/editorconfig/editorconfig-core-go/v2 v2.4.3
|
||||||
github.com/emirpasic/gods v1.12.0
|
github.com/emirpasic/gods v1.12.0
|
||||||
github.com/ethantkoenig/rupture v1.0.0
|
github.com/ethantkoenig/rupture v1.0.1
|
||||||
github.com/gliderlabs/ssh v0.3.3
|
github.com/gliderlabs/ssh v0.3.3
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.0.4
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/go-chi/cors v1.2.0
|
github.com/go-chi/cors v1.2.0
|
||||||
github.com/go-enry/go-enry/v2 v2.7.1
|
github.com/go-enry/go-enry/v2 v2.8.0
|
||||||
github.com/go-git/go-billy/v5 v5.3.1
|
github.com/go-git/go-billy/v5 v5.3.1
|
||||||
github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4
|
github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.4.2
|
||||||
github.com/go-redis/redis/v8 v8.11.0
|
github.com/go-redis/redis/v8 v8.11.4
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/go-swagger/go-swagger v0.27.0
|
github.com/go-swagger/go-swagger v0.29.0
|
||||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
|
github.com/goccy/go-json v0.9.5 // indirect
|
||||||
|
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
|
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-github/v39 v39.2.0
|
github.com/google/go-github/v39 v39.2.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/feeds v1.1.1
|
github.com/gorilla/feeds v1.1.1
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.3.1
|
github.com/hashicorp/go-version v1.4.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
github.com/huandu/xstrings v1.3.2
|
github.com/huandu/xstrings v1.3.2
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||||
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
|
||||||
github.com/klauspost/compress v1.13.1
|
github.com/klauspost/compress v1.15.0
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9
|
github.com/klauspost/cpuid/v2 v2.0.11
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/lib/pq v1.10.4
|
||||||
github.com/lib/pq v1.10.2
|
|
||||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
github.com/markbates/goth v1.68.0
|
github.com/markbates/goth v1.69.0
|
||||||
github.com/mattn/go-isatty v0.0.13
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.8
|
github.com/mattn/go-sqlite3 v1.14.12
|
||||||
github.com/mholt/archiver/v3 v3.5.0
|
github.com/mholt/acmez v1.0.2 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.16
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.18
|
||||||
|
github.com/miekg/dns v1.1.46 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/minio-go/v7 v7.0.12
|
github.com/minio/minio-go/v7 v7.0.23
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||||
github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
|
github.com/msteinert/pam v1.0.0
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/niklasfasching/go-org v1.5.0
|
github.com/niklasfasching/go-org v1.6.2
|
||||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||||
github.com/oliamb/cutter v0.2.2
|
github.com/oliamb/cutter v0.2.2
|
||||||
github.com/olivere/elastic/v7 v7.0.25
|
github.com/olivere/elastic/v7 v7.0.31
|
||||||
github.com/pelletier/go-toml v1.9.0 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pquerna/otp v1.3.0
|
github.com/pquerna/otp v1.3.0
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/quasoft/websspi v1.0.0
|
github.com/quasoft/websspi v1.1.2
|
||||||
github.com/rs/xid v1.3.0 // indirect
|
github.com/rs/xid v1.3.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
|
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
|
||||||
github.com/sergi/go-diff v1.2.0
|
github.com/sergi/go-diff v1.2.0
|
||||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||||
|
@ -107,34 +107,34 @@ require (
|
||||||
github.com/tstranex/u2f v1.0.0
|
github.com/tstranex/u2f v1.0.0
|
||||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
github.com/unknwon/com v1.0.1
|
github.com/unknwon/com v1.0.1
|
||||||
github.com/unknwon/i18n v0.0.0-20210321134014-0ebbf2df1c44
|
github.com/unknwon/i18n v0.0.0-20210904045753-ff3a8617e361
|
||||||
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
|
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
|
||||||
github.com/unrolled/render v1.4.0
|
github.com/unrolled/render v1.4.1
|
||||||
github.com/urfave/cli v1.22.5
|
github.com/urfave/cli v1.22.5
|
||||||
github.com/xanzy/go-gitlab v0.50.1
|
github.com/xanzy/go-gitlab v0.58.0
|
||||||
|
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||||
github.com/yohcop/openid-go v1.0.0
|
github.com/yohcop/openid-go v1.0.0
|
||||||
github.com/yuin/goldmark v1.4.4
|
github.com/yuin/goldmark v1.4.8
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
|
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||||
github.com/yuin/goldmark-meta v1.0.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.jolheiser.com/hcaptcha v0.0.4
|
go.jolheiser.com/hcaptcha v0.0.4
|
||||||
go.jolheiser.com/pwn v0.0.3
|
go.jolheiser.com/pwn v0.0.3
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.7.0 // indirect
|
go.uber.org/multierr v1.8.0 // indirect
|
||||||
go.uber.org/zap v1.19.0 // indirect
|
go.uber.org/zap v1.21.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||||
golang.org/x/tools v0.1.0
|
golang.org/x/tools v0.1.9
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
gopkg.in/ini.v1 v1.62.0
|
gopkg.in/ini.v1 v1.66.4
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
mvdan.cc/xurls/v2 v2.2.0
|
mvdan.cc/xurls/v2 v2.4.0
|
||||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||||
xorm.io/builder v0.3.9
|
xorm.io/builder v0.3.9
|
||||||
xorm.io/xorm v1.2.5
|
xorm.io/xorm v1.2.5
|
||||||
|
|
|
@ -468,7 +468,6 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
}{
|
}{
|
||||||
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
|
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
|
||||||
|
|
||||||
// Transfer to a user with teams in another org should fail
|
// Transfer to a user with teams in another org should fail
|
||||||
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
|
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
|
||||||
// Transfer to a user with non-existent team IDs should fail
|
// Transfer to a user with non-existent team IDs should fail
|
||||||
|
|
|
@ -178,7 +178,9 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository)
|
||||||
assert.GreaterOrEqual(c.t, len(issues), 1)
|
assert.GreaterOrEqual(c.t, len(issues), 1)
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number))
|
filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number))
|
||||||
comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment)
|
comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{
|
||||||
|
"Index": {ignore: true},
|
||||||
|
}).([]*base.Comment)
|
||||||
assert.True(c.t, ok)
|
assert.True(c.t, ok)
|
||||||
for _, comment := range comments {
|
for _, comment := range comments {
|
||||||
assert.EqualValues(c.t, issue.Number, comment.IssueIndex)
|
assert.EqualValues(c.t, issue.Number, comment.IssueIndex)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ref: refs/heads/main
|
|
@ -0,0 +1,6 @@
|
||||||
|
[core]
|
||||||
|
bare = true
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = false
|
||||||
|
symlinks = false
|
||||||
|
ignorecase = true
|
|
@ -51,8 +51,6 @@ func TestSignin(t *testing.T) {
|
||||||
{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
|
{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||||
{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||||
{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
|
||||||
// test for duplicate email
|
|
||||||
{username: "user2@example.com", password: "password", message: i18n.Tr("en", "form.email_been_used")},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range samples {
|
for _, s := range samples {
|
||||||
|
|
|
@ -181,6 +181,11 @@ func (log *TestLogger) Init(config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *TestLogger) Content() (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Flush when log should be flushed
|
// Flush when log should be flushed
|
||||||
func (log *TestLogger) Flush() {
|
func (log *TestLogger) Flush() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ func TestExportUserGPGKeys(t *testing.T) {
|
||||||
defer prepareTestEnv(t)()
|
defer prepareTestEnv(t)()
|
||||||
// Export empty key list
|
// Export empty key list
|
||||||
testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Note: This user hasn't uploaded any GPG keys.
|
||||||
|
|
||||||
|
|
||||||
=twTO
|
=twTO
|
||||||
|
|
|
@ -43,7 +43,7 @@ type WebAuthnCredential struct {
|
||||||
Name string
|
Name string
|
||||||
LowerName string `xorm:"unique(s)"`
|
LowerName string `xorm:"unique(s)"`
|
||||||
UserID int64 `xorm:"INDEX unique(s)"`
|
UserID int64 `xorm:"INDEX unique(s)"`
|
||||||
CredentialID string `xorm:"INDEX"`
|
CredentialID string `xorm:"INDEX VARCHAR(410)"`
|
||||||
PublicKey []byte
|
PublicKey []byte
|
||||||
AttestationType string
|
AttestationType string
|
||||||
AAGUID []byte
|
AAGUID []byte
|
||||||
|
|
|
@ -148,6 +148,17 @@ func DeleteByBean(ctx context.Context, bean interface{}) (int64, error) {
|
||||||
return GetEngine(ctx).Delete(bean)
|
return GetEngine(ctx).Delete(bean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteBeans deletes all given beans, beans should contain delete conditions.
|
||||||
|
func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) {
|
||||||
|
e := GetEngine(ctx)
|
||||||
|
for i := range beans {
|
||||||
|
if _, err = e.Delete(beans[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
|
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
|
||||||
func CountByBean(ctx context.Context, bean interface{}) (int64, error) {
|
func CountByBean(ctx context.Context, bean interface{}) (int64, error) {
|
||||||
return GetEngine(ctx).Count(bean)
|
return GetEngine(ctx).Count(bean)
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: repo2
|
lower_name: repo2
|
||||||
name: repo2
|
name: repo2
|
||||||
|
is_empty: false
|
||||||
is_archived: false
|
is_archived: false
|
||||||
is_private: true
|
is_private: true
|
||||||
num_issues: 2
|
num_issues: 2
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
owner_name: user3
|
owner_name: user3
|
||||||
lower_name: repo3
|
lower_name: repo3
|
||||||
name: repo3
|
name: repo3
|
||||||
|
is_empty: false
|
||||||
is_private: true
|
is_private: true
|
||||||
num_issues: 1
|
num_issues: 1
|
||||||
num_closed_issues: 0
|
num_closed_issues: 0
|
||||||
|
@ -56,6 +58,7 @@
|
||||||
owner_name: user5
|
owner_name: user5
|
||||||
lower_name: repo4
|
lower_name: repo4
|
||||||
name: repo4
|
name: repo4
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
num_closed_issues: 0
|
num_closed_issues: 0
|
||||||
|
@ -143,6 +146,7 @@
|
||||||
owner_name: user12
|
owner_name: user12
|
||||||
lower_name: repo10
|
lower_name: repo10
|
||||||
name: repo10
|
name: repo10
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
num_closed_issues: 0
|
num_closed_issues: 0
|
||||||
|
@ -160,6 +164,7 @@
|
||||||
owner_name: user13
|
owner_name: user13
|
||||||
lower_name: repo11
|
lower_name: repo11
|
||||||
name: repo11
|
name: repo11
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
num_closed_issues: 0
|
num_closed_issues: 0
|
||||||
|
@ -217,7 +222,8 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: repo15
|
lower_name: repo15
|
||||||
name: repo15
|
name: repo15
|
||||||
is_empty: true
|
is_empty: false
|
||||||
|
is_private: true
|
||||||
status: 0
|
status: 0
|
||||||
|
|
||||||
-
|
-
|
||||||
|
@ -226,6 +232,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: repo16
|
lower_name: repo16
|
||||||
name: repo16
|
name: repo16
|
||||||
|
is_empty: false
|
||||||
is_private: true
|
is_private: true
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
num_closed_issues: 0
|
num_closed_issues: 0
|
||||||
|
@ -459,6 +466,8 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: repo20
|
lower_name: repo20
|
||||||
name: repo20
|
name: repo20
|
||||||
|
is_empty: false
|
||||||
|
is_private: true
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
|
@ -484,6 +493,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: utf8
|
lower_name: utf8
|
||||||
name: utf8
|
name: utf8
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
status: 0
|
status: 0
|
||||||
|
|
||||||
|
@ -519,6 +529,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: commits_search_test
|
lower_name: commits_search_test
|
||||||
name: commits_search_test
|
name: commits_search_test
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -532,6 +543,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: git_hooks_test
|
lower_name: git_hooks_test
|
||||||
name: git_hooks_test
|
name: git_hooks_test
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -545,6 +557,7 @@
|
||||||
owner_name: limited_org
|
owner_name: limited_org
|
||||||
lower_name: public_repo_on_limited_org
|
lower_name: public_repo_on_limited_org
|
||||||
name: public_repo_on_limited_org
|
name: public_repo_on_limited_org
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -558,6 +571,7 @@
|
||||||
owner_name: limited_org
|
owner_name: limited_org
|
||||||
lower_name: private_repo_on_limited_org
|
lower_name: private_repo_on_limited_org
|
||||||
name: private_repo_on_limited_org
|
name: private_repo_on_limited_org
|
||||||
|
is_empty: false
|
||||||
is_private: true
|
is_private: true
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -571,6 +585,7 @@
|
||||||
owner_name: privated_org
|
owner_name: privated_org
|
||||||
lower_name: public_repo_on_private_org
|
lower_name: public_repo_on_private_org
|
||||||
name: public_repo_on_private_org
|
name: public_repo_on_private_org
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -584,6 +599,7 @@
|
||||||
owner_name: privated_org
|
owner_name: privated_org
|
||||||
lower_name: private_repo_on_private_org
|
lower_name: private_repo_on_private_org
|
||||||
name: private_repo_on_private_org
|
name: private_repo_on_private_org
|
||||||
|
is_empty: false
|
||||||
is_private: true
|
is_private: true
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -596,6 +612,7 @@
|
||||||
owner_name: user2
|
owner_name: user2
|
||||||
lower_name: glob
|
lower_name: glob
|
||||||
name: glob
|
name: glob
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -622,6 +639,7 @@
|
||||||
owner_name: user27
|
owner_name: user27
|
||||||
lower_name: template1
|
lower_name: template1
|
||||||
name: template1
|
name: template1
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
is_template: true
|
is_template: true
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
|
@ -650,6 +668,7 @@
|
||||||
owner_name: org26
|
owner_name: org26
|
||||||
lower_name: repo_external_tracker
|
lower_name: repo_external_tracker
|
||||||
name: repo_external_tracker
|
name: repo_external_tracker
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -663,6 +682,7 @@
|
||||||
owner_name: org26
|
owner_name: org26
|
||||||
lower_name: repo_external_tracker_numeric
|
lower_name: repo_external_tracker_numeric
|
||||||
name: repo_external_tracker_numeric
|
name: repo_external_tracker_numeric
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -676,6 +696,7 @@
|
||||||
owner_name: org26
|
owner_name: org26
|
||||||
lower_name: repo_external_tracker_alpha
|
lower_name: repo_external_tracker_alpha
|
||||||
name: repo_external_tracker_alpha
|
name: repo_external_tracker_alpha
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -690,6 +711,7 @@
|
||||||
owner_name: user27
|
owner_name: user27
|
||||||
lower_name: repo49
|
lower_name: repo49
|
||||||
name: repo49
|
name: repo49
|
||||||
|
is_empty: false
|
||||||
is_private: false
|
is_private: false
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
|
@ -736,3 +758,13 @@
|
||||||
num_projects: 0
|
num_projects: 0
|
||||||
num_closed_projects: 0
|
num_closed_projects: 0
|
||||||
status: 0
|
status: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 52
|
||||||
|
owner_id: 30
|
||||||
|
owner_name: user30
|
||||||
|
lower_name: empty
|
||||||
|
name: empty
|
||||||
|
is_empty: true
|
||||||
|
is_private: true
|
||||||
|
status: 0
|
||||||
|
|
|
@ -522,7 +522,7 @@
|
||||||
is_restricted: true
|
is_restricted: true
|
||||||
avatar: avatar29
|
avatar: avatar29
|
||||||
avatar_email: user30@example.com
|
avatar_email: user30@example.com
|
||||||
num_repos: 2
|
num_repos: 3
|
||||||
is_active: true
|
is_active: true
|
||||||
prohibit_login: true
|
prohibit_login: true
|
||||||
|
|
||||||
|
|
114
models/issue.go
114
models/issue.go
|
@ -13,6 +13,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
admin_model "code.gitea.io/gitea/models/admin"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/issues"
|
"code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
@ -24,6 +25,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/storage"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
@ -1990,6 +1992,118 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteIssue deletes the issue
|
||||||
|
func DeleteIssue(issue *Issue) error {
|
||||||
|
ctx, committer, err := db.TxContext()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err := deleteIssue(ctx, issue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteInIssue(e db.Engine, issueID int64, beans ...interface{}) error {
|
||||||
|
for _, bean := range beans {
|
||||||
|
if _, err := e.In("issue_id", issueID).Delete(bean); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteIssue(ctx context.Context, issue *Issue) error {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
if _, err := e.ID(issue.ID).NoAutoCondition().Delete(issue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if issue.IsPull {
|
||||||
|
if _, err := e.ID(issue.RepoID).Decr("num_pulls").Update(new(repo_model.Repository)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if issue.IsClosed {
|
||||||
|
if _, err := e.ID(issue.RepoID).Decr("num_closed_pulls").Update(new(repo_model.Repository)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := e.ID(issue.RepoID).Decr("num_issues").Update(new(repo_model.Repository)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if issue.IsClosed {
|
||||||
|
if _, err := e.ID(issue.RepoID).Decr("num_closed_issues").Update(new(repo_model.Repository)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete actions assigned to this issue
|
||||||
|
subQuery := builder.Select("`id`").
|
||||||
|
From("`comment`").
|
||||||
|
Where(builder.Eq{"`issue_id`": issue.ID})
|
||||||
|
if _, err := e.In("comment_id", subQuery).Delete(&Action{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := e.Table("action").Where("repo_id = ?", issue.RepoID).
|
||||||
|
In("op_type", ActionCreateIssue, ActionCreatePullRequest).
|
||||||
|
Where("content LIKE ?", strconv.FormatInt(issue.ID, 10)+"|%").
|
||||||
|
Delete(&Action{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find attachments related to this issue and remove them
|
||||||
|
var attachments []*repo_model.Attachment
|
||||||
|
if err := e.In("issue_id", issue.ID).Find(&attachments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range attachments {
|
||||||
|
admin_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", attachments[i].RelativePath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete all database data still assigned to this issue
|
||||||
|
if err := deleteInIssue(e, issue.ID,
|
||||||
|
&issues.ContentHistory{},
|
||||||
|
&Comment{},
|
||||||
|
&IssueLabel{},
|
||||||
|
&IssueDependency{},
|
||||||
|
&IssueAssignees{},
|
||||||
|
&IssueUser{},
|
||||||
|
&Reaction{},
|
||||||
|
&IssueWatch{},
|
||||||
|
&Stopwatch{},
|
||||||
|
&TrackedTime{},
|
||||||
|
&ProjectIssue{},
|
||||||
|
&repo_model.Attachment{},
|
||||||
|
&PullRequest{},
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// References to this issue in other issues
|
||||||
|
if _, err := e.In("ref_issue_id", issue.ID).Delete(&Comment{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete dependencies for issues in other repositories
|
||||||
|
if _, err := e.In("dependency_id", issue.ID).Delete(&IssueDependency{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete from dependent issues
|
||||||
|
if _, err := e.In("dependent_issue_id", issue.ID).Delete(&Comment{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DependencyInfo represents high level information about an issue which is a dependency of another issue.
|
// DependencyInfo represents high level information about an issue which is a dependency of another issue.
|
||||||
type DependencyInfo struct {
|
type DependencyInfo struct {
|
||||||
Issue `xorm:"extends"`
|
Issue `xorm:"extends"`
|
||||||
|
|
|
@ -1152,9 +1152,7 @@ func DeleteComment(comment *Comment) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteComment(e db.Engine, comment *Comment) error {
|
func deleteComment(e db.Engine, comment *Comment) error {
|
||||||
if _, err := e.Delete(&Comment{
|
if _, err := e.ID(comment.ID).NoAutoCondition().Delete(comment); err != nil {
|
||||||
ID: comment.ID,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
|
// ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
|
||||||
|
@ -53,7 +54,7 @@ func (s Stopwatch) Seconds() int64 {
|
||||||
|
|
||||||
// Duration returns a human-readable duration string based on local server time
|
// Duration returns a human-readable duration string based on local server time
|
||||||
func (s Stopwatch) Duration() string {
|
func (s Stopwatch) Duration() string {
|
||||||
return SecToTime(s.Seconds())
|
return util.SecToTime(s.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
||||||
|
@ -164,7 +165,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
|
||||||
Doer: user,
|
Doer: user,
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Content: SecToTime(timediff),
|
Content: util.SecToTime(timediff),
|
||||||
Type: CommentTypeStopTracking,
|
Type: CommentTypeStopTracking,
|
||||||
TimeID: tt.ID,
|
TimeID: tt.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -263,32 +264,3 @@ func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) e
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
|
|
||||||
func SecToTime(duration int64) string {
|
|
||||||
seconds := duration % 60
|
|
||||||
minutes := (duration / (60)) % 60
|
|
||||||
hours := duration / (60 * 60)
|
|
||||||
|
|
||||||
var hrs string
|
|
||||||
|
|
||||||
if hours > 0 {
|
|
||||||
hrs = fmt.Sprintf("%dh", hours)
|
|
||||||
}
|
|
||||||
if minutes > 0 {
|
|
||||||
if hours == 0 {
|
|
||||||
hrs = fmt.Sprintf("%dmin", minutes)
|
|
||||||
} else {
|
|
||||||
hrs = fmt.Sprintf("%s %dmin", hrs, minutes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if seconds > 0 {
|
|
||||||
if hours == 0 && minutes == 0 {
|
|
||||||
hrs = fmt.Sprintf("%ds", seconds)
|
|
||||||
} else {
|
|
||||||
hrs = fmt.Sprintf("%s %ds", hrs, seconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hrs
|
|
||||||
}
|
|
||||||
|
|
|
@ -397,6 +397,58 @@ func TestIssue_InsertIssue(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue_DeleteIssue(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issueIDs, err := GetIssueIDsByRepoID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 5, len(issueIDs))
|
||||||
|
|
||||||
|
issue := &Issue{
|
||||||
|
RepoID: 1,
|
||||||
|
ID: issueIDs[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DeleteIssue(issue)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issueIDs, err = GetIssueIDsByRepoID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 4, len(issueIDs))
|
||||||
|
|
||||||
|
// check attachment removal
|
||||||
|
attachments, err := repo_model.GetAttachmentsByIssueID(4)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issue, err = GetIssueByID(4)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = DeleteIssue(issue)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 2, len(attachments))
|
||||||
|
for i := range attachments {
|
||||||
|
attachment, err := repo_model.GetAttachmentByUUID(attachments[i].UUID)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, repo_model.IsErrAttachmentNotExist(err))
|
||||||
|
assert.Nil(t, attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check issue dependencies
|
||||||
|
user, err := user_model.GetUserByID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issue1, err := GetIssueByID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issue2, err := GetIssueByID(2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = CreateIssueDependency(user, issue1, issue2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
left, err := IssueNoDependenciesLeft(issue1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, left)
|
||||||
|
err = DeleteIssue(&Issue{ID: 2})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
left, err = IssueNoDependenciesLeft(issue1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, left)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue_ResolveMentions(t *testing.T) {
|
func TestIssue_ResolveMentions(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -177,7 +178,7 @@ func AddTime(user *user_model.User, issue *Issue, amount int64, created time.Tim
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Doer: user,
|
Doer: user,
|
||||||
Content: SecToTime(amount),
|
Content: util.SecToTime(amount),
|
||||||
Type: CommentTypeAddTimeManual,
|
Type: CommentTypeAddTimeManual,
|
||||||
TimeID: t.ID,
|
TimeID: t.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -226,7 +227,7 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]string,
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
totalTimes[user] = SecToTime(total)
|
totalTimes[user] = util.SecToTime(total)
|
||||||
}
|
}
|
||||||
return totalTimes, nil
|
return totalTimes, nil
|
||||||
}
|
}
|
||||||
|
@ -260,7 +261,7 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
|
||||||
Issue: issue,
|
Issue: issue,
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Doer: user,
|
Doer: user,
|
||||||
Content: "- " + SecToTime(removedTime),
|
Content: "- " + util.SecToTime(removedTime),
|
||||||
Type: CommentTypeDeleteTimeManual,
|
Type: CommentTypeDeleteTimeManual,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -289,7 +290,7 @@ func DeleteTime(t *TrackedTime) error {
|
||||||
Issue: t.Issue,
|
Issue: t.Issue,
|
||||||
Repo: t.Issue.Repo,
|
Repo: t.Issue.Repo,
|
||||||
Doer: t.User,
|
Doer: t.User,
|
||||||
Content: "- " + SecToTime(t.Time),
|
Content: "- " + util.SecToTime(t.Time),
|
||||||
Type: CommentTypeDeleteTimeManual,
|
Type: CommentTypeDeleteTimeManual,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -34,7 +34,7 @@ func TestAddTime(t *testing.T) {
|
||||||
assert.Equal(t, int64(3661), tt.Time)
|
assert.Equal(t, int64(3661), tt.Time)
|
||||||
|
|
||||||
comment := unittest.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*Comment)
|
comment := unittest.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*Comment)
|
||||||
assert.Equal(t, comment.Content, "1h 1min 1s")
|
assert.Equal(t, comment.Content, "1 hour 1 minute")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTrackedTimes(t *testing.T) {
|
func TestGetTrackedTimes(t *testing.T) {
|
||||||
|
@ -86,7 +86,7 @@ func TestTotalTimes(t *testing.T) {
|
||||||
assert.Len(t, total, 1)
|
assert.Len(t, total, 1)
|
||||||
for user, time := range total {
|
for user, time := range total {
|
||||||
assert.Equal(t, int64(1), user.ID)
|
assert.Equal(t, int64(1), user.ID)
|
||||||
assert.Equal(t, "6min 40s", time)
|
assert.Equal(t, "6 minutes 40 seconds", time)
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 2})
|
total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 2})
|
||||||
|
@ -94,9 +94,9 @@ func TestTotalTimes(t *testing.T) {
|
||||||
assert.Len(t, total, 2)
|
assert.Len(t, total, 2)
|
||||||
for user, time := range total {
|
for user, time := range total {
|
||||||
if user.ID == 2 {
|
if user.ID == 2 {
|
||||||
assert.Equal(t, "1h 1min 2s", time)
|
assert.Equal(t, "1 hour 1 minute", time)
|
||||||
} else if user.ID == 1 {
|
} else if user.ID == 1 {
|
||||||
assert.Equal(t, "20s", time)
|
assert.Equal(t, "20 seconds", time)
|
||||||
} else {
|
} else {
|
||||||
assert.Error(t, assert.AnError)
|
assert.Error(t, assert.AnError)
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ func TestTotalTimes(t *testing.T) {
|
||||||
assert.Len(t, total, 1)
|
assert.Len(t, total, 1)
|
||||||
for user, time := range total {
|
for user, time := range total {
|
||||||
assert.Equal(t, int64(2), user.ID)
|
assert.Equal(t, int64(2), user.ID)
|
||||||
assert.Equal(t, "1s", time)
|
assert.Equal(t, "1 second", time)
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 4})
|
total, err = TotalTimes(&FindTrackedTimesOptions{IssueID: 4})
|
||||||
|
|
|
@ -195,7 +195,8 @@ func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *cross
|
||||||
|
|
||||||
// verifyReferencedIssue will check if the referenced issue exists, and whether the doer has permission to do what
|
// verifyReferencedIssue will check if the referenced issue exists, and whether the doer has permission to do what
|
||||||
func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossReferencesContext, repo *repo_model.Repository,
|
func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossReferencesContext, repo *repo_model.Repository,
|
||||||
ref references.IssueReference) (*Issue, references.XRefAction, error) {
|
ref references.IssueReference,
|
||||||
|
) (*Issue, references.XRefAction, error) {
|
||||||
refIssue := &Issue{RepoID: repo.ID, Index: ref.Index}
|
refIssue := &Issue{RepoID: repo.ID, Index: ref.Index}
|
||||||
refAction := ref.Action
|
refAction := ref.Action
|
||||||
e := db.GetEngine(stdCtx)
|
e := db.GetEngine(stdCtx)
|
||||||
|
|
|
@ -137,6 +137,7 @@ func QueryIssueContentHistoryEditedCountMap(dbCtx context.Context, issueID int64
|
||||||
type IssueContentListItem struct {
|
type IssueContentListItem struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
UserName string
|
UserName string
|
||||||
|
UserFullName string
|
||||||
UserAvatarLink string
|
UserAvatarLink string
|
||||||
|
|
||||||
HistoryID int64
|
HistoryID int64
|
||||||
|
@ -148,7 +149,7 @@ type IssueContentListItem struct {
|
||||||
// FetchIssueContentHistoryList fetch list
|
// FetchIssueContentHistoryList fetch list
|
||||||
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
|
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
|
||||||
res := make([]*IssueContentListItem, 0)
|
res := make([]*IssueContentListItem, 0)
|
||||||
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name,"+
|
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name, u.full_name as user_full_name,"+
|
||||||
"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
|
"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
|
||||||
Table([]string{"issue_content_history", "h"}).
|
Table([]string{"issue_content_history", "h"}).
|
||||||
Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
|
Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").
|
||||||
|
|
|
@ -43,8 +43,9 @@ func TestContentHistory(t *testing.T) {
|
||||||
when the refactor of models are done, this test will be possible to be run then with a real `User` model.
|
when the refactor of models are done, this test will be possible to be run then with a real `User` model.
|
||||||
*/
|
*/
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
|
FullName string
|
||||||
}
|
}
|
||||||
_ = dbEngine.Sync2(&User{})
|
_ = dbEngine.Sync2(&User{})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||||
|
-
|
||||||
|
id: 4
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
|
@ -0,0 +1,21 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
name: "u2fkey-correctly-migrated"
|
||||||
|
user_id: 1
|
||||||
|
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||||
|
counter: 0
|
||||||
|
- id: 2
|
||||||
|
name: "u2fkey-incorrectly-migrated"
|
||||||
|
user_id: 1
|
||||||
|
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||||
|
counter: 0
|
||||||
|
- id: 3
|
||||||
|
name: "u2fkey-deleted"
|
||||||
|
user_id: 1
|
||||||
|
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||||
|
counter: 0
|
||||||
|
- id: 4
|
||||||
|
name: "u2fkey-wrong-user-id"
|
||||||
|
user_id: 2
|
||||||
|
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
|
||||||
|
counter: 0
|
|
@ -0,0 +1,30 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
lower_name: "u2fkey-correctly-migrated"
|
||||||
|
name: "u2fkey-correctly-migrated"
|
||||||
|
user_id: 1
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
|
||||||
|
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
|
||||||
|
attestation_type: 'fido-u2f'
|
||||||
|
sign_count: 1
|
||||||
|
clone_warning: false
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
lower_name: "u2fkey-incorrectly-migrated"
|
||||||
|
name: "u2fkey-incorrectly-migrated"
|
||||||
|
user_id: 1
|
||||||
|
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
|
||||||
|
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
|
||||||
|
attestation_type: 'fido-u2f'
|
||||||
|
sign_count: 1
|
||||||
|
clone_warning: false
|
||||||
|
-
|
||||||
|
id: 4
|
||||||
|
lower_name: "u2fkey-wrong-user-id"
|
||||||
|
name: "u2fkey-wrong-user-id"
|
||||||
|
user_id: 1
|
||||||
|
credential_id: "THIS SHOULD CHANGE"
|
||||||
|
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
|
||||||
|
attestation_type: 'fido-u2f'
|
||||||
|
sign_count: 1
|
||||||
|
clone_warning: false
|
|
@ -61,7 +61,6 @@ type Version struct {
|
||||||
// update minDBVersion accordingly
|
// update minDBVersion accordingly
|
||||||
var migrations = []Migration{
|
var migrations = []Migration{
|
||||||
// Gitea 1.5.0 ends at v69
|
// Gitea 1.5.0 ends at v69
|
||||||
|
|
||||||
// v70 -> v71
|
// v70 -> v71
|
||||||
NewMigration("add issue_dependencies", addIssueDependencies),
|
NewMigration("add issue_dependencies", addIssueDependencies),
|
||||||
// v71 -> v72
|
// v71 -> v72
|
||||||
|
@ -367,9 +366,13 @@ var migrations = []Migration{
|
||||||
// v206 -> v207
|
// v206 -> v207
|
||||||
NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
|
NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
|
||||||
// v207 -> v208
|
// v207 -> v208
|
||||||
NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
|
NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", addWebAuthnCred),
|
||||||
// v208 -> v209
|
// v208 -> v209
|
||||||
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
|
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", useBase32HexForCredIDInWebAuthnCredential),
|
||||||
|
// v209 -> v210
|
||||||
|
NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410),
|
||||||
|
// v210 -> v211
|
||||||
|
NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
|
@ -166,6 +166,11 @@ func (log *TestLogger) Init(config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *TestLogger) Content() (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Flush when log should be flushed
|
// Flush when log should be flushed
|
||||||
func (log *TestLogger) Flush() {
|
func (log *TestLogger) Flush() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,86 +5,11 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
|
||||||
"encoding/base64"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
|
|
||||||
"github.com/tstranex/u2f"
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addWebAuthnCred(x *xorm.Engine) error {
|
func addWebAuthnCred(x *xorm.Engine) error {
|
||||||
// Create webauthnCredential table
|
// NO-OP Don't migrate here - let v210 do this.
|
||||||
type webauthnCredential struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
Name string
|
|
||||||
LowerName string `xorm:"unique(s)"`
|
|
||||||
UserID int64 `xorm:"INDEX unique(s)"`
|
|
||||||
CredentialID string `xorm:"INDEX"`
|
|
||||||
PublicKey []byte
|
|
||||||
AttestationType string
|
|
||||||
AAGUID []byte
|
|
||||||
SignCount uint32 `xorm:"BIGINT"`
|
|
||||||
CloneWarning bool
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
|
||||||
}
|
|
||||||
if err := x.Sync2(&webauthnCredential{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now migrate the old u2f registrations to the new format
|
|
||||||
type u2fRegistration struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
Name string
|
|
||||||
UserID int64 `xorm:"INDEX"`
|
|
||||||
Raw []byte
|
|
||||||
Counter uint32 `xorm:"BIGINT"`
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var start int
|
|
||||||
regs := make([]*u2fRegistration, 0, 50)
|
|
||||||
for {
|
|
||||||
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, reg := range regs {
|
|
||||||
parsed := new(u2f.Registration)
|
|
||||||
err = parsed.UnmarshalBinary(reg.Raw)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &webauthnCredential{
|
|
||||||
ID: reg.ID,
|
|
||||||
Name: reg.Name,
|
|
||||||
LowerName: strings.ToLower(reg.Name),
|
|
||||||
UserID: reg.UserID,
|
|
||||||
CredentialID: base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
|
|
||||||
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
|
|
||||||
AttestationType: "fido-u2f",
|
|
||||||
AAGUID: []byte{},
|
|
||||||
SignCount: reg.Counter,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := x.Insert(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(regs) < 50 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
start += 50
|
|
||||||
regs = regs[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,46 +5,10 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base32"
|
|
||||||
"encoding/base64"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
|
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
|
||||||
// Create webauthnCredential table
|
// noop
|
||||||
type webauthnCredential struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
CredentialID string `xorm:"INDEX"`
|
|
||||||
}
|
|
||||||
if err := x.Sync2(&webauthnCredential{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var start int
|
|
||||||
regs := make([]*webauthnCredential, 0, 50)
|
|
||||||
for {
|
|
||||||
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, reg := range regs {
|
|
||||||
credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
|
|
||||||
reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
|
|
||||||
|
|
||||||
_, err := x.Update(reg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(regs) < 50 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
start += 50
|
|
||||||
regs = regs[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
17
models/migrations/v209.go
Normal file
17
models/migrations/v209.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// 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/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func increaseCredentialIDTo410(x *xorm.Engine) error {
|
||||||
|
// no-op
|
||||||
|
// v208 was completely wrong
|
||||||
|
// So now we have to no-op again.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
178
models/migrations/v210.go
Normal file
178
models/migrations/v210.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
// 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 (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"encoding/base32"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/tstranex/u2f"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
// v208 migration was completely broken
|
||||||
|
func remigrateU2FCredentials(x *xorm.Engine) error {
|
||||||
|
// Create webauthnCredential table
|
||||||
|
type webauthnCredential struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string
|
||||||
|
LowerName string `xorm:"unique(s)"`
|
||||||
|
UserID int64 `xorm:"INDEX unique(s)"`
|
||||||
|
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||||
|
PublicKey []byte
|
||||||
|
AttestationType string
|
||||||
|
AAGUID []byte
|
||||||
|
SignCount uint32 `xorm:"BIGINT"`
|
||||||
|
CloneWarning bool
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
if err := x.Sync2(&webauthnCredential{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x.Dialect().URI().DBType {
|
||||||
|
case schemas.MYSQL:
|
||||||
|
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case schemas.ORACLE:
|
||||||
|
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case schemas.MSSQL:
|
||||||
|
// This column has an index on it. I could write all of the code to attempt to change the index OR
|
||||||
|
// I could just use recreate table.
|
||||||
|
sess := x.NewSession()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
_ = sess.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := recreateTable(sess, new(webauthnCredential)); err != nil {
|
||||||
|
_ = sess.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
_ = sess.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case schemas.POSTGRES:
|
||||||
|
_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
|
||||||
|
// nor is there any need to re-migrate
|
||||||
|
}
|
||||||
|
|
||||||
|
exist, err := x.IsTableExist("u2f_registration")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now migrate the old u2f registrations to the new format
|
||||||
|
type u2fRegistration struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string
|
||||||
|
UserID int64 `xorm:"INDEX"`
|
||||||
|
Raw []byte
|
||||||
|
Counter uint32 `xorm:"BIGINT"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var start int
|
||||||
|
regs := make([]*u2fRegistration, 0, 50)
|
||||||
|
for {
|
||||||
|
err := x.OrderBy("id").Limit(50, start).Find(®s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = func() error {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return fmt.Errorf("unable to allow start session. Error: %w", err)
|
||||||
|
}
|
||||||
|
if x.Dialect().URI().DBType == schemas.MSSQL {
|
||||||
|
if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil {
|
||||||
|
return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, reg := range regs {
|
||||||
|
parsed := new(u2f.Registration)
|
||||||
|
err = parsed.UnmarshalBinary(reg.Raw)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remigrated := &webauthnCredential{
|
||||||
|
ID: reg.ID,
|
||||||
|
Name: reg.Name,
|
||||||
|
LowerName: strings.ToLower(reg.Name),
|
||||||
|
UserID: reg.UserID,
|
||||||
|
CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle),
|
||||||
|
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
|
||||||
|
AttestationType: "fido-u2f",
|
||||||
|
AAGUID: []byte{},
|
||||||
|
SignCount: reg.Counter,
|
||||||
|
UpdatedUnix: reg.UpdatedUnix,
|
||||||
|
CreatedUnix: reg.CreatedUnix,
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
_, err = sess.Insert(remigrated)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(regs) < 50 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start += 50
|
||||||
|
regs = regs[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
75
models/migrations/v210_test.go
Normal file
75
models/migrations/v210_test.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2021 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_remigrateU2FCredentials(t *testing.T) {
|
||||||
|
// Create webauthnCredential table
|
||||||
|
type WebauthnCredential struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string
|
||||||
|
LowerName string `xorm:"unique(s)"`
|
||||||
|
UserID int64 `xorm:"INDEX unique(s)"`
|
||||||
|
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||||
|
PublicKey []byte
|
||||||
|
AttestationType string
|
||||||
|
SignCount uint32 `xorm:"BIGINT"`
|
||||||
|
CloneWarning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now migrate the old u2f registrations to the new format
|
||||||
|
type U2fRegistration struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Name string
|
||||||
|
UserID int64 `xorm:"INDEX"`
|
||||||
|
Raw []byte
|
||||||
|
Counter uint32 `xorm:"BIGINT"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpectedWebauthnCredential struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
|
||||||
|
if x == nil || t.Failed() {
|
||||||
|
defer deferable()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
if x.Dialect().URI().DBType == schemas.SQLITE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the migration
|
||||||
|
if err := remigrateU2FCredentials(x); err != nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []ExpectedWebauthnCredential{}
|
||||||
|
if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []ExpectedWebauthnCredential{}
|
||||||
|
if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, expected, got)
|
||||||
|
}
|
|
@ -498,14 +498,15 @@ func (n *Notification) APIURL() string {
|
||||||
type NotificationList []*Notification
|
type NotificationList []*Notification
|
||||||
|
|
||||||
// LoadAttributes load Repo Issue User and Comment if not loaded
|
// LoadAttributes load Repo Issue User and Comment if not loaded
|
||||||
func (nl NotificationList) LoadAttributes() (err error) {
|
func (nl NotificationList) LoadAttributes() error {
|
||||||
|
var err error
|
||||||
for i := 0; i < len(nl); i++ {
|
for i := 0; i < len(nl); i++ {
|
||||||
err = nl[i].LoadAttributes()
|
err = nl[i].LoadAttributes()
|
||||||
if err != nil && !IsErrCommentNotExist(err) {
|
if err != nil && !IsErrCommentNotExist(err) {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
func (nl NotificationList) getPendingRepoIDs() []int64 {
|
||||||
|
|
|
@ -331,9 +331,7 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
|
||||||
return fmt.Errorf("%s is a user not an organization", org.Name)
|
return fmt.Errorf("%s is a user not an organization", org.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
if err := db.DeleteBeans(ctx,
|
||||||
|
|
||||||
if err := deleteBeans(e,
|
|
||||||
&Team{OrgID: org.ID},
|
&Team{OrgID: org.ID},
|
||||||
&OrgUser{OrgID: org.ID},
|
&OrgUser{OrgID: org.ID},
|
||||||
&TeamUser{OrgID: org.ID},
|
&TeamUser{OrgID: org.ID},
|
||||||
|
@ -342,7 +340,7 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
|
||||||
return fmt.Errorf("deleteBeans: %v", err)
|
return fmt.Errorf("deleteBeans: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := e.ID(org.ID).Delete(new(user_model.User)); err != nil {
|
if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil {
|
||||||
return fmt.Errorf("Delete: %v", err)
|
return fmt.Errorf("Delete: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -123,7 +122,8 @@ func NewRepoContext() {
|
||||||
loadRepoConfig()
|
loadRepoConfig()
|
||||||
unit.LoadUnitConfig()
|
unit.LoadUnitConfig()
|
||||||
|
|
||||||
admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
|
admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
|
||||||
|
admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", LocalCopyPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRepoUnitUser check whether user could visit the unit of this repository
|
// CheckRepoUnitUser check whether user could visit the unit of this repository
|
||||||
|
@ -150,27 +150,56 @@ func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*us
|
||||||
}
|
}
|
||||||
|
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
accesses := make([]*Access, 0, 10)
|
userIDs := make([]int64, 0, 10)
|
||||||
if err = e.
|
if err = e.Table("access").
|
||||||
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
|
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
|
||||||
Find(&accesses); err != nil {
|
Select("user_id").
|
||||||
|
Find(&userIDs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additionalUserIDs := make([]int64, 0, 10)
|
||||||
|
if err = e.Table("team_user").
|
||||||
|
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||||
|
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||||
|
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
|
||||||
|
Distinct("`team_user`.uid").
|
||||||
|
Select("`team_user`.uid").
|
||||||
|
Find(&additionalUserIDs); err != nil {
|
||||||
|
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]
|
||||||
|
|
||||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
// 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.
|
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||||
users := make([]*user_model.User, 0, len(accesses)+1)
|
users := make([]*user_model.User, 0, len(userIDs)+1)
|
||||||
if len(accesses) > 0 {
|
if len(userIDs) > 0 {
|
||||||
userIDs := make([]int64, len(accesses))
|
|
||||||
for i := 0; i < len(accesses); i++ {
|
|
||||||
userIDs[i] = accesses[i].UserID
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = e.In("id", userIDs).Find(&users); err != nil {
|
if err = e.In("id", userIDs).Find(&users); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !repo.Owner.IsOrganization() {
|
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
|
||||||
users = append(users, repo.Owner)
|
users = append(users, repo.Owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +521,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, repo_model.RepoUnit{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
Type: tp,
|
Type: tp,
|
||||||
Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyleMerge},
|
Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyleMerge, AllowRebaseUpdate: true},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, repo_model.RepoUnit{
|
||||||
|
@ -765,7 +794,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deleteBeans(sess,
|
if err := db.DeleteBeans(ctx,
|
||||||
&Access{RepoID: repo.ID},
|
&Access{RepoID: repo.ID},
|
||||||
&Action{RepoID: repo.ID},
|
&Action{RepoID: repo.ID},
|
||||||
&Collaboration{RepoID: repoID},
|
&Collaboration{RepoID: repoID},
|
||||||
|
@ -927,28 +956,28 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove archives
|
// Remove archives
|
||||||
for i := range archivePaths {
|
for _, archive := range archivePaths {
|
||||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archivePaths[i])
|
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove lfs objects
|
// Remove lfs objects
|
||||||
for i := range lfsPaths {
|
for _, lfsObj := range lfsPaths {
|
||||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsPaths[i])
|
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove issue attachment files.
|
// Remove issue attachment files.
|
||||||
for i := range attachmentPaths {
|
for _, attachment := range attachmentPaths {
|
||||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
|
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove release attachment files.
|
// Remove release attachment files.
|
||||||
for i := range releaseAttachments {
|
for _, releaseAttachment := range releaseAttachments {
|
||||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachments[i])
|
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove attachment with no issue_id and release_id.
|
// Remove attachment with no issue_id and release_id.
|
||||||
for i := range newAttachmentPaths {
|
for _, newAttachment := range newAttachmentPaths {
|
||||||
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
|
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repo.Avatar) > 0 {
|
if len(repo.Avatar) > 0 {
|
||||||
|
|
|
@ -120,11 +120,12 @@ func DeleteMirrorByRepoID(repoID int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MirrorsIterate iterates all mirror repositories.
|
// MirrorsIterate iterates all mirror repositories.
|
||||||
func MirrorsIterate(f func(idx int, bean interface{}) error) error {
|
func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
|
||||||
return db.GetEngine(db.DefaultContext).
|
return db.GetEngine(db.DefaultContext).
|
||||||
Where("next_update_unix<=?", time.Now().Unix()).
|
Where("next_update_unix<=?", time.Now().Unix()).
|
||||||
And("next_update_unix!=0").
|
And("next_update_unix!=0").
|
||||||
OrderBy("updated_unix ASC").
|
OrderBy("updated_unix ASC").
|
||||||
|
Limit(limit).
|
||||||
Iterate(new(Mirror), f)
|
Iterate(new(Mirror), f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,10 +101,11 @@ func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushMirrorsIterate iterates all push-mirror repositories.
|
// PushMirrorsIterate iterates all push-mirror repositories.
|
||||||
func PushMirrorsIterate(f func(idx int, bean interface{}) error) error {
|
func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
|
||||||
return db.GetEngine(db.DefaultContext).
|
return db.GetEngine(db.DefaultContext).
|
||||||
Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
|
Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
|
||||||
And("`interval` != 0").
|
And("`interval` != 0").
|
||||||
OrderBy("last_update ASC").
|
OrderBy("last_update ASC").
|
||||||
|
Limit(limit).
|
||||||
Iterate(new(PushMirror), f)
|
Iterate(new(PushMirror), f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func TestPushMirrorsIterate(t *testing.T) {
|
||||||
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
|
||||||
PushMirrorsIterate(func(idx int, bean interface{}) error {
|
PushMirrorsIterate(1, func(idx int, bean interface{}) error {
|
||||||
m, ok := bean.(*PushMirror)
|
m, ok := bean.(*PushMirror)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, "test-1", m.RemoteName)
|
assert.Equal(t, "test-1", m.RemoteName)
|
||||||
|
|
|
@ -290,7 +290,14 @@ func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.Units, err = getUnitsByRepoID(db.GetEngine(ctx), repo.ID)
|
repo.Units, err = getUnitsByRepoID(db.GetEngine(ctx), repo.ID)
|
||||||
log.Trace("repo.Units: %-+v", repo.Units)
|
if log.IsTrace() {
|
||||||
|
unitTypeStrings := make([]string, len(repo.Units))
|
||||||
|
for i, unit := range repo.Units {
|
||||||
|
unitTypeStrings[i] = unit.Type.String()
|
||||||
|
}
|
||||||
|
log.Trace("repo.Units, ID=%d, Types: [%s]", repo.ID, strings.Join(unitTypeStrings, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +520,7 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||||
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
|
desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
URLPrefix: repo.HTMLURL(),
|
URLPrefix: repo.HTMLURL(),
|
||||||
Metas: repo.ComposeMetas(),
|
// Don't use Metas to speedup requests
|
||||||
}, repo.Description)
|
}, repo.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
||||||
|
|
|
@ -115,12 +115,15 @@ type PullRequestsConfig struct {
|
||||||
AllowSquash bool
|
AllowSquash bool
|
||||||
AllowManualMerge bool
|
AllowManualMerge bool
|
||||||
AutodetectManualMerge bool
|
AutodetectManualMerge bool
|
||||||
|
AllowRebaseUpdate bool
|
||||||
DefaultDeleteBranchAfterMerge bool
|
DefaultDeleteBranchAfterMerge bool
|
||||||
DefaultMergeStyle MergeStyle
|
DefaultMergeStyle MergeStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up a PullRequestsConfig from serialized format.
|
// FromDB fills up a PullRequestsConfig from serialized format.
|
||||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
|
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
|
||||||
|
// AllowRebaseUpdate = true as default for existing PullRequestConfig in DB
|
||||||
|
cfg.AllowRebaseUpdate = true
|
||||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,3 +167,21 @@ func TestLinkedRepository(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepoAssignees(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
|
||||||
|
users, err := GetRepoAssignees(repo2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 1)
|
||||||
|
assert.Equal(t, users[0].ID, int64(2))
|
||||||
|
|
||||||
|
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository)
|
||||||
|
users, err = GetRepoAssignees(repo21)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 3)
|
||||||
|
assert.Equal(t, users[0].ID, int64(15))
|
||||||
|
assert.Equal(t, users[1].ID, int64(18))
|
||||||
|
assert.Equal(t, users[2].ID, int64(16))
|
||||||
|
}
|
||||||
|
|
|
@ -29,16 +29,6 @@ func GetOrganizationCount(ctx context.Context, u *user_model.User) (int64, error
|
||||||
Count(new(OrgUser))
|
Count(new(OrgUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteBeans deletes all given beans, beans should contain delete conditions.
|
|
||||||
func deleteBeans(e db.Engine, beans ...interface{}) (err error) {
|
|
||||||
for i := range beans {
|
|
||||||
if _, err = e.Delete(beans[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser deletes models associated to an user.
|
// DeleteUser deletes models associated to an user.
|
||||||
func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
@ -82,7 +72,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
|
||||||
}
|
}
|
||||||
// ***** END: Follow *****
|
// ***** END: Follow *****
|
||||||
|
|
||||||
if err = deleteBeans(e,
|
if err = db.DeleteBeans(ctx,
|
||||||
&AccessToken{UID: u.ID},
|
&AccessToken{UID: u.ID},
|
||||||
&Collaboration{UserID: u.ID},
|
&Collaboration{UserID: u.ID},
|
||||||
&Access{UserID: u.ID},
|
&Access{UserID: u.ID},
|
||||||
|
|
|
@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
results[user.ID] = false // Set default to false
|
results[user.ID] = false // Set default to false
|
||||||
}
|
}
|
||||||
tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
|
|
||||||
if err == nil {
|
if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
|
||||||
for _, token := range tokenMaps {
|
for _, token := range tokenMaps {
|
||||||
results[token.UID] = true
|
results[token.UID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
|
||||||
|
for _, id := range ids {
|
||||||
|
results[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
|
||||||
|
|
||||||
userIDs := users.GetUserIDs()
|
userIDs := users.GetUserIDs()
|
||||||
tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
|
tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
|
||||||
err := e.
|
if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
|
||||||
In("uid", userIDs).
|
|
||||||
Find(&tokenMaps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("find two factor: %v", err)
|
return nil, fmt.Errorf("find two factor: %v", err)
|
||||||
}
|
}
|
||||||
return tokenMaps, nil
|
return tokenMaps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
|
||||||
|
if len(users) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ids := make([]int64, 0, len(users))
|
||||||
|
if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
|
||||||
|
return nil, fmt.Errorf("find two factor: %v", err)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUsersByIDs returns all resolved users from a list of Ids.
|
// GetUsersByIDs returns all resolved users from a list of Ids.
|
||||||
func GetUsersByIDs(ids []int64) (UserList, error) {
|
func GetUsersByIDs(ids []int64) (UserList, error) {
|
||||||
ous := make([]*User, 0, len(ids))
|
ous := make([]*User, 0, len(ids))
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
// SearchUserOptions contains the options for searching
|
// SearchUserOptions contains the options for searching
|
||||||
type SearchUserOptions struct {
|
type SearchUserOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
|
||||||
Keyword string
|
Keyword string
|
||||||
Type UserType
|
Type UserType
|
||||||
UID int64
|
UID int64
|
||||||
|
@ -33,6 +34,8 @@ type SearchUserOptions struct {
|
||||||
IsRestricted util.OptionalBool
|
IsRestricted util.OptionalBool
|
||||||
IsTwoFactorEnabled util.OptionalBool
|
IsTwoFactorEnabled util.OptionalBool
|
||||||
IsProhibitLogin util.OptionalBool
|
IsProhibitLogin util.OptionalBool
|
||||||
|
|
||||||
|
ExtraParamStrings map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
|
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
|
||||||
|
|
|
@ -827,8 +827,9 @@ func validateUser(u *User) error {
|
||||||
return ValidateEmail(u.Email)
|
return ValidateEmail(u.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
|
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
|
||||||
if err := validateUser(u); err != nil {
|
err := validateUser(u)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -860,15 +861,35 @@ func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if !u.IsOrganization() { // check if primary email in email_address table
|
||||||
|
primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !primaryEmailExist {
|
||||||
|
if _, err := e.Insert(&EmailAddress{
|
||||||
|
Email: u.Email,
|
||||||
|
UID: u.ID,
|
||||||
|
IsActivated: true,
|
||||||
|
IsPrimary: true,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := e.ID(u.ID).AllCols().Update(u)
|
if len(cols) == 0 {
|
||||||
|
_, err = e.ID(u.ID).AllCols().Update(u)
|
||||||
|
} else {
|
||||||
|
_, err = e.ID(u.ID).Cols(cols...).Update(u)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser updates user's information.
|
// UpdateUser updates user's information.
|
||||||
func UpdateUser(u *User, emailChanged bool) error {
|
func UpdateUser(u *User, emailChanged bool, cols ...string) error {
|
||||||
return updateUser(db.DefaultContext, u, emailChanged)
|
return updateUser(db.DefaultContext, u, emailChanged, cols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserCols update user according special columns
|
// UpdateUserCols update user according special columns
|
||||||
|
@ -1117,19 +1138,9 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
email = strings.ToLower(email)
|
email = strings.ToLower(email)
|
||||||
// First try to find the user by primary email
|
|
||||||
user := &User{Email: email}
|
|
||||||
has, err := db.GetEngine(ctx).Get(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, check in alternative list for activated email addresses
|
// Otherwise, check in alternative list for activated email addresses
|
||||||
emailAddress := &EmailAddress{Email: email, IsActivated: true}
|
emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
|
||||||
has, err = db.GetEngine(ctx).Get(emailAddress)
|
has, err := db.GetEngine(ctx).Get(emailAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,20 @@ func TestCreateUserInvalidEmail(t *testing.T) {
|
||||||
assert.True(t, IsErrEmailInvalid(err))
|
assert.True(t, IsErrEmailInvalid(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
|
|
||||||
|
// add new user with user2's email
|
||||||
|
user.Name = "testuser"
|
||||||
|
user.LowerName = strings.ToLower(user.Name)
|
||||||
|
user.ID = 0
|
||||||
|
err := CreateUser(user)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, IsErrEmailAlreadyUsed(err))
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetUserIDsByNames(t *testing.T) {
|
func TestGetUserIDsByNames(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ func EscapeControlBytes(text []byte) (EscapeStatus, []byte) {
|
||||||
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
|
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
readStart := 0
|
readStart := 0
|
||||||
|
runeCount := 0
|
||||||
var n int
|
var n int
|
||||||
var writePos int
|
var writePos int
|
||||||
|
|
||||||
|
@ -74,10 +75,13 @@ readingloop:
|
||||||
for err == nil {
|
for err == nil {
|
||||||
n, err = text.Read(buf[readStart:])
|
n, err = text.Read(buf[readStart:])
|
||||||
bs := buf[:n+readStart]
|
bs := buf[:n+readStart]
|
||||||
|
n = len(bs)
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for i < len(bs) {
|
for i < len(bs) {
|
||||||
r, size := utf8.DecodeRune(bs[i:])
|
r, size := utf8.DecodeRune(bs[i:])
|
||||||
|
runeCount++
|
||||||
|
|
||||||
// Now handle the codepoints
|
// Now handle the codepoints
|
||||||
switch {
|
switch {
|
||||||
case r == utf8.RuneError:
|
case r == utf8.RuneError:
|
||||||
|
@ -112,6 +116,8 @@ readingloop:
|
||||||
lineHasRTLScript = false
|
lineHasRTLScript = false
|
||||||
lineHasLTRScript = false
|
lineHasLTRScript = false
|
||||||
|
|
||||||
|
case runeCount == 1 && r == 0xFEFF: // UTF BOM
|
||||||
|
// the first BOM is safe
|
||||||
case r == '\r' || r == '\t' || r == ' ':
|
case r == '\r' || r == '\t' || r == ' ':
|
||||||
// These are acceptable control characters and space characters
|
// These are acceptable control characters and space characters
|
||||||
case unicode.IsSpace(r):
|
case unicode.IsSpace(r):
|
||||||
|
|
|
@ -129,6 +129,14 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
|
||||||
"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
|
"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
|
||||||
status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
|
status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// UTF-8/16/32 all use the same codepoint for BOM
|
||||||
|
// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
|
||||||
|
name: "UTF BOM",
|
||||||
|
text: "\xef\xbb\xbftest",
|
||||||
|
result: "\xef\xbb\xbftest",
|
||||||
|
status: EscapeStatus{HasLTRScript: true},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEscapeControlString(t *testing.T) {
|
func TestEscapeControlString(t *testing.T) {
|
||||||
|
@ -163,10 +171,18 @@ func TestEscapeControlReader(t *testing.T) {
|
||||||
// lets add some control characters to the tests
|
// lets add some control characters to the tests
|
||||||
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
|
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
|
||||||
copy(tests, escapeControlTests)
|
copy(tests, escapeControlTests)
|
||||||
|
|
||||||
|
// if there is a BOM, we should keep the BOM
|
||||||
|
addPrefix := func(prefix, s string) string {
|
||||||
|
if strings.HasPrefix(s, "\xef\xbb\xbf") {
|
||||||
|
return s[:3] + prefix + s[3:]
|
||||||
|
}
|
||||||
|
return prefix + s
|
||||||
|
}
|
||||||
for _, test := range escapeControlTests {
|
for _, test := range escapeControlTests {
|
||||||
test.name += " (+Control)"
|
test.name += " (+Control)"
|
||||||
test.text = "\u001E" + test.text
|
test.text = addPrefix("\u001E", test.text)
|
||||||
test.result = `<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">` + "\u001e" + `</span></span>` + test.result
|
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
|
||||||
test.status.Escaped = true
|
test.status.Escaped = true
|
||||||
test.status.HasControls = true
|
test.status.HasControls = true
|
||||||
tests = append(tests, test)
|
tests = append(tests, test)
|
||||||
|
@ -174,8 +190,8 @@ func TestEscapeControlReader(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range escapeControlTests {
|
for _, test := range escapeControlTests {
|
||||||
test.name += " (+Mark)"
|
test.name += " (+Mark)"
|
||||||
test.text = "\u0300" + test.text
|
test.text = addPrefix("\u0300", test.text)
|
||||||
test.result = `<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">` + "\u0300" + `</span></span>` + test.result
|
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
|
||||||
test.status.Escaped = true
|
test.status.Escaped = true
|
||||||
test.status.HasMarks = true
|
test.status.HasMarks = true
|
||||||
tests = append(tests, test)
|
tests = append(tests, test)
|
||||||
|
@ -200,3 +216,12 @@ func TestEscapeControlReader(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEscapeControlReader_panic(t *testing.T) {
|
||||||
|
bs := make([]byte, 0, 20479)
|
||||||
|
bs = append(bs, 'A')
|
||||||
|
for i := 0; i < 6826; i++ {
|
||||||
|
bs = append(bs, []byte("—")...)
|
||||||
|
}
|
||||||
|
_, _ = EscapeControlBytes(bs)
|
||||||
|
}
|
||||||
|
|
|
@ -129,7 +129,23 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||||
|
|
||||||
// Team.
|
// Team.
|
||||||
if ctx.Org.IsMember {
|
if ctx.Org.IsMember {
|
||||||
|
shouldSeeAllTeams := false
|
||||||
if ctx.Org.IsOwner {
|
if ctx.Org.IsOwner {
|
||||||
|
shouldSeeAllTeams = true
|
||||||
|
} else {
|
||||||
|
teams, err := org.GetUserTeams(ctx.User.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUserTeams", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, team := range teams {
|
||||||
|
if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
|
||||||
|
shouldSeeAllTeams = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldSeeAllTeams {
|
||||||
ctx.Org.Teams, err = org.LoadTeams()
|
ctx.Org.Teams, err = org.LoadTeams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadTeams", err)
|
ctx.ServerError("LoadTeams", err)
|
||||||
|
|
|
@ -914,7 +914,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
||||||
|
|
||||||
if refType == RepoRefLegacy {
|
if refType == RepoRefLegacy {
|
||||||
// redirect from old URL scheme to new URL scheme
|
// redirect from old URL scheme to new URL scheme
|
||||||
prefix := strings.TrimPrefix(setting.AppSubURL+strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Repo.RepoLink)
|
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
||||||
|
|
||||||
ctx.Redirect(path.Join(
|
ctx.Redirect(path.Join(
|
||||||
ctx.Repo.RepoLink,
|
ctx.Repo.RepoLink,
|
||||||
|
|
|
@ -158,6 +158,8 @@ func (c *Command) RunWithContext(rc *RunContext) error {
|
||||||
fmt.Sprintf("LC_ALL=%s", DefaultLocale),
|
fmt.Sprintf("LC_ALL=%s", DefaultLocale),
|
||||||
// avoid prompting for credentials interactively, supported since git v2.3
|
// avoid prompting for credentials interactively, supported since git v2.3
|
||||||
"GIT_TERMINAL_PROMPT=0",
|
"GIT_TERMINAL_PROMPT=0",
|
||||||
|
// ignore replace references (https://git-scm.com/docs/git-replace)
|
||||||
|
"GIT_NO_REPLACE_OBJECTS=1",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Dir = rc.Dir
|
cmd.Dir = rc.Dir
|
||||||
|
|
|
@ -80,21 +80,20 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error {
|
||||||
// IsEmpty Check if repository is empty.
|
// IsEmpty Check if repository is empty.
|
||||||
func (repo *Repository) IsEmpty() (bool, error) {
|
func (repo *Repository) IsEmpty() (bool, error) {
|
||||||
var errbuf, output strings.Builder
|
var errbuf, output strings.Builder
|
||||||
if err := NewCommand(repo.Ctx, "rev-list", "--all", "--count", "--max-count=1").
|
if err := NewCommand(repo.Ctx, "show-ref", "--head", "^HEAD$").
|
||||||
RunWithContext(&RunContext{
|
RunWithContext(&RunContext{
|
||||||
Timeout: -1,
|
Timeout: -1,
|
||||||
Dir: repo.Path,
|
Dir: repo.Path,
|
||||||
Stdout: &output,
|
Stdout: &output,
|
||||||
Stderr: &errbuf,
|
Stderr: &errbuf,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
if err.Error() == "exit status 1" && errbuf.String() == "" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
|
return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := strconv.Atoi(strings.TrimSpace(output.String()))
|
return strings.TrimSpace(output.String()) == "", nil
|
||||||
if err != nil {
|
|
||||||
return true, fmt.Errorf("check empty: convert %s to count failed: %v", output.String(), err)
|
|
||||||
}
|
|
||||||
return c == 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloneRepoOptions options when clone a repository
|
// CloneRepoOptions options when clone a repository
|
||||||
|
|
|
@ -185,7 +185,8 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
|
||||||
// Run run cmd
|
// Run run cmd
|
||||||
func (c *CheckAttributeReader) Run() error {
|
func (c *CheckAttributeReader) Run() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = c.Close()
|
_ = c.stdinReader.Close()
|
||||||
|
_ = c.stdOut.Close()
|
||||||
}()
|
}()
|
||||||
stdErr := new(bytes.Buffer)
|
stdErr := new(bytes.Buffer)
|
||||||
err := c.cmd.RunWithContext(&RunContext{
|
err := c.cmd.RunWithContext(&RunContext{
|
||||||
|
@ -196,14 +197,19 @@ func (c *CheckAttributeReader) Run() error {
|
||||||
Stdout: c.stdOut,
|
Stdout: c.stdOut,
|
||||||
Stderr: stdErr,
|
Stderr: stdErr,
|
||||||
PipelineFunc: func(_ context.Context, _ context.CancelFunc) error {
|
PipelineFunc: func(_ context.Context, _ context.CancelFunc) error {
|
||||||
close(c.running)
|
select {
|
||||||
|
case <-c.running:
|
||||||
|
default:
|
||||||
|
close(c.running)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" {
|
if err != nil && // If there is an error we need to return but:
|
||||||
|
c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
|
||||||
|
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
|
||||||
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
|
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,10 +249,8 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
|
||||||
|
|
||||||
// Close close pip after use
|
// Close close pip after use
|
||||||
func (c *CheckAttributeReader) Close() error {
|
func (c *CheckAttributeReader) Close() error {
|
||||||
err := c.stdinWriter.Close()
|
|
||||||
_ = c.stdinReader.Close()
|
|
||||||
_ = c.stdOut.Close()
|
|
||||||
c.cancel()
|
c.cancel()
|
||||||
|
err := c.stdinWriter.Close()
|
||||||
select {
|
select {
|
||||||
case <-c.running:
|
case <-c.running:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -88,7 +88,10 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer func() {
|
||||||
|
_ = checker.Close()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,6 +193,7 @@ func (g *Manager) RunAtHammer(hammer func()) {
|
||||||
|
|
||||||
func (g *Manager) doShutdown() {
|
func (g *Manager) doShutdown() {
|
||||||
if !g.setStateTransition(stateRunning, stateShuttingDown) {
|
if !g.setStateTransition(stateRunning, stateShuttingDown) {
|
||||||
|
g.DoImmediateHammer()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
|
|
|
@ -168,8 +168,12 @@ func (g *Manager) DoGracefulRestart() {
|
||||||
if setting.GracefulRestartable {
|
if setting.GracefulRestartable {
|
||||||
log.Info("PID: %d. Forking...", os.Getpid())
|
log.Info("PID: %d. Forking...", os.Getpid())
|
||||||
err := g.doFork()
|
err := g.doFork()
|
||||||
if err != nil && err.Error() != "another process already forked. Ignoring this one" {
|
if err != nil {
|
||||||
log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
|
if err.Error() == "another process already forked. Ignoring this one" {
|
||||||
|
g.DoImmediateHammer()
|
||||||
|
} else {
|
||||||
|
log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
|
log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
|
||||||
|
|
|
@ -182,7 +182,8 @@ func NewBleveIndexer(indexDir string) (*BleveIndexer, bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BleveIndexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserError, batchReader *bufio.Reader, commitSha string,
|
func (b *BleveIndexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserError, batchReader *bufio.Reader, commitSha string,
|
||||||
update fileUpdate, repo *repo_model.Repository, batch *gitea_bleve.FlushingBatch) error {
|
update fileUpdate, repo *repo_model.Repository, batch *gitea_bleve.FlushingBatch,
|
||||||
|
) error {
|
||||||
// Ignore vendored files in code search
|
// Ignore vendored files in code search
|
||||||
if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) {
|
if setting.Indexer.ExcludeVendored && analyze.IsVendor(update.Filename) {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -37,6 +37,9 @@ func (db *DBIndexer) Index(id int64) error {
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepositoryCtx(ctx, repo.RepoPath())
|
gitRepo, err := git.OpenRepositoryCtx(ctx, repo.RepoPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err.Error() == "no such file or directory" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
72
modules/log/buffer.go
Normal file
72
modules/log/buffer.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// 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 log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bufferWriteCloser struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
buffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bufferWriteCloser) Write(p []byte) (int, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
return b.buffer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bufferWriteCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bufferWriteCloser) String() string {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
return b.buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferLogger implements LoggerProvider and writes messages in a buffer.
|
||||||
|
type BufferLogger struct {
|
||||||
|
WriterLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferLogger create BufferLogger returning as LoggerProvider.
|
||||||
|
func NewBufferLogger() LoggerProvider {
|
||||||
|
log := &BufferLogger{}
|
||||||
|
log.NewWriterLogger(&bufferWriteCloser{})
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init inits connection writer
|
||||||
|
func (log *BufferLogger) Init(string) error {
|
||||||
|
log.NewWriterLogger(log.out)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *BufferLogger) Content() (string, error) {
|
||||||
|
return log.out.(*bufferWriteCloser).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush when log should be flushed
|
||||||
|
func (log *BufferLogger) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseReopen does nothing
|
||||||
|
func (log *BufferLogger) ReleaseReopen() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the default name for this implementation
|
||||||
|
func (log *BufferLogger) GetName() string {
|
||||||
|
return "buffer"
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("buffer", NewBufferLogger)
|
||||||
|
}
|
64
modules/log/buffer_test.go
Normal file
64
modules/log/buffer_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// 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 log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBufferLogger(t *testing.T) {
|
||||||
|
logger := NewBufferLogger()
|
||||||
|
bufferLogger := logger.(*BufferLogger)
|
||||||
|
assert.NotNil(t, bufferLogger)
|
||||||
|
|
||||||
|
err := logger.Init("")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
location, _ := time.LoadLocation("EST")
|
||||||
|
date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location)
|
||||||
|
|
||||||
|
msg := "TEST MSG"
|
||||||
|
event := Event{
|
||||||
|
level: INFO,
|
||||||
|
msg: msg,
|
||||||
|
caller: "CALLER",
|
||||||
|
filename: "FULL/FILENAME",
|
||||||
|
line: 1,
|
||||||
|
time: date,
|
||||||
|
}
|
||||||
|
logger.LogEvent(&event)
|
||||||
|
content, err := bufferLogger.Content()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, content, msg)
|
||||||
|
logger.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufferLoggerContent(t *testing.T) {
|
||||||
|
level := INFO
|
||||||
|
logger := NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String()))
|
||||||
|
|
||||||
|
logger.SetLogger("buffer", "buffer", "{}")
|
||||||
|
defer logger.DelLogger("buffer")
|
||||||
|
|
||||||
|
msg := "A UNIQUE MESSAGE"
|
||||||
|
Error(msg)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i := 0; i < 30000; i++ {
|
||||||
|
content, err := logger.GetLoggerProviderContent("buffer")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if strings.Contains(content, msg) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
assert.True(t, found)
|
||||||
|
}
|
|
@ -119,6 +119,11 @@ func (log *ConnLogger) Init(jsonconfig string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *ConnLogger) Content() (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Flush does nothing for this implementation
|
// Flush does nothing for this implementation
|
||||||
func (log *ConnLogger) Flush() {
|
func (log *ConnLogger) Flush() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,11 @@ func (log *ConsoleLogger) Init(config string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *ConsoleLogger) Content() (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Flush when log should be flushed
|
// Flush when log should be flushed
|
||||||
func (log *ConsoleLogger) Flush() {
|
func (log *ConsoleLogger) Flush() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,6 +216,12 @@ func (m *MultiChannelledLog) GetEventLogger(name string) EventLogger {
|
||||||
return m.loggers[name]
|
return m.loggers[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEventProvider returns a sub logger provider content from this MultiChannelledLog
|
||||||
|
func (m *MultiChannelledLog) GetLoggerProviderContent(name string) (string, error) {
|
||||||
|
channelledLogger := m.GetEventLogger(name).(*ChannelledLog)
|
||||||
|
return channelledLogger.loggerProvider.Content()
|
||||||
|
}
|
||||||
|
|
||||||
// GetEventLoggerNames returns a list of names
|
// GetEventLoggerNames returns a list of names
|
||||||
func (m *MultiChannelledLog) GetEventLoggerNames() []string {
|
func (m *MultiChannelledLog) GetEventLoggerNames() []string {
|
||||||
m.rwmutex.RLock()
|
m.rwmutex.RLock()
|
||||||
|
|
|
@ -243,6 +243,15 @@ func (log *FileLogger) deleteOldLog() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *FileLogger) Content() (string, error) {
|
||||||
|
b, err := os.ReadFile(log.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Flush flush file logger.
|
// Flush flush file logger.
|
||||||
// there are no buffering messages in file logger in memory.
|
// there are no buffering messages in file logger in memory.
|
||||||
// flush file means sync file from disk.
|
// flush file means sync file from disk.
|
||||||
|
|
|
@ -7,6 +7,7 @@ package log
|
||||||
// LoggerProvider represents behaviors of a logger provider.
|
// LoggerProvider represents behaviors of a logger provider.
|
||||||
type LoggerProvider interface {
|
type LoggerProvider interface {
|
||||||
Init(config string) error
|
Init(config string) error
|
||||||
|
Content() (string, error)
|
||||||
EventLogger
|
EventLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,11 @@ func (log *SMTPLogger) sendMail(p []byte) (int, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content returns the content accumulated in the content provider
|
||||||
|
func (log *SMTPLogger) Content() (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Flush when log should be flushed
|
// Flush when log should be flushed
|
||||||
func (log *SMTPLogger) Flush() {
|
func (log *SMTPLogger) Flush() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizerDisabled disabled sanitize if return true
|
||||||
|
func (Renderer) SanitizerDisabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func writeField(w io.Writer, element, class, field string) error {
|
func writeField(w io.Writer, element, class, field string) error {
|
||||||
if _, err := io.WriteString(w, "<"); err != nil {
|
if _, err := io.WriteString(w, "<"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
5
modules/markup/external/external.go
vendored
5
modules/markup/external/external.go
vendored
|
@ -54,6 +54,11 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return p.MarkupSanitizerRules
|
return p.MarkupSanitizerRules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizerDisabled disabled sanitize if return true
|
||||||
|
func (p *Renderer) SanitizerDisabled() bool {
|
||||||
|
return p.DisableSanitizer
|
||||||
|
}
|
||||||
|
|
||||||
func envMark(envName string) string {
|
func envMark(envName string) string {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return "%" + envName + "%"
|
return "%" + envName + "%"
|
||||||
|
|
|
@ -99,7 +99,7 @@ var issueFullPatternOnce sync.Once
|
||||||
func getIssueFullPattern() *regexp.Regexp {
|
func getIssueFullPattern() *regexp.Regexp {
|
||||||
issueFullPatternOnce.Do(func() {
|
issueFullPatternOnce.Do(func() {
|
||||||
issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
|
issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
|
||||||
`\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
|
`[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
|
||||||
})
|
})
|
||||||
return issueFullPattern
|
return issueFullPattern
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,15 @@ func TestRender_CrossReferences(t *testing.T) {
|
||||||
test(
|
test(
|
||||||
"/home/gitea/go-gitea/gitea#12345",
|
"/home/gitea/go-gitea/gitea#12345",
|
||||||
`<p>/home/gitea/go-gitea/gitea#12345</p>`)
|
`<p>/home/gitea/go-gitea/gitea#12345</p>`)
|
||||||
|
test(
|
||||||
|
util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345"),
|
||||||
|
`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p>`)
|
||||||
|
test(
|
||||||
|
util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345"),
|
||||||
|
`<p><a href="`+util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
|
||||||
|
test(
|
||||||
|
util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
|
||||||
|
`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMisc_IsSameDomain(t *testing.T) {
|
func TestMisc_IsSameDomain(t *testing.T) {
|
||||||
|
|
|
@ -221,6 +221,11 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{}
|
return []setting.MarkupSanitizerRule{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizerDisabled disabled sanitize if return true
|
||||||
|
func (Renderer) SanitizerDisabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Render implements markup.Renderer
|
// Render implements markup.Renderer
|
||||||
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
return render(ctx, input, output)
|
return render(ctx, input, output)
|
||||||
|
|
|
@ -47,6 +47,11 @@ func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{}
|
return []setting.MarkupSanitizerRule{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizerDisabled disabled sanitize if return true
|
||||||
|
func (Renderer) SanitizerDisabled() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Render renders orgmode rawbytes to HTML
|
// Render renders orgmode rawbytes to HTML
|
||||||
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
htmlWriter := org.NewHTMLWriter()
|
htmlWriter := org.NewHTMLWriter()
|
||||||
|
|
|
@ -81,6 +81,7 @@ type Renderer interface {
|
||||||
Extensions() []string
|
Extensions() []string
|
||||||
NeedPostProcess() bool
|
NeedPostProcess() bool
|
||||||
SanitizerRules() []setting.MarkupSanitizerRule
|
SanitizerRules() []setting.MarkupSanitizerRule
|
||||||
|
SanitizerDisabled() bool
|
||||||
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
|
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +128,12 @@ func RenderString(ctx *RenderContext, content string) (string, error) {
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nopCloser struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
|
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
var err error
|
var err error
|
||||||
|
@ -136,18 +143,25 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
|
||||||
_ = pw.Close()
|
_ = pw.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
pr2, pw2 := io.Pipe()
|
var pr2 io.ReadCloser
|
||||||
defer func() {
|
var pw2 io.WriteCloser
|
||||||
_ = pr2.Close()
|
|
||||||
_ = pw2.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Add(1)
|
if !renderer.SanitizerDisabled() {
|
||||||
go func() {
|
pr2, pw2 = io.Pipe()
|
||||||
err = SanitizeReader(pr2, renderer.Name(), output)
|
defer func() {
|
||||||
_ = pr2.Close()
|
_ = pr2.Close()
|
||||||
wg.Done()
|
_ = pw2.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err = SanitizeReader(pr2, renderer.Name(), output)
|
||||||
|
_ = pr2.Close()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
pw2 = nopCloser{output}
|
||||||
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -9,7 +9,8 @@ import "time"
|
||||||
|
|
||||||
// Comment is a standard comment information
|
// Comment is a standard comment information
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
IssueIndex int64 `yaml:"issue_index"`
|
IssueIndex int64 `yaml:"issue_index"`
|
||||||
|
Index int64
|
||||||
PosterID int64 `yaml:"poster_id"`
|
PosterID int64 `yaml:"poster_id"`
|
||||||
PosterName string `yaml:"poster_name"`
|
PosterName string `yaml:"poster_name"`
|
||||||
PosterEmail string `yaml:"poster_email"`
|
PosterEmail string `yaml:"poster_email"`
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
package nosql
|
package nosql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
|
@ -20,8 +23,16 @@ func (m *Manager) CloseLevelDB(connection string) error {
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
db, ok := m.LevelDBConnections[connection]
|
db, ok := m.LevelDBConnections[connection]
|
||||||
if !ok {
|
if !ok {
|
||||||
connection = ToLevelDBURI(connection).String()
|
// Try the full URI
|
||||||
db, ok = m.LevelDBConnections[connection]
|
uri := ToLevelDBURI(connection)
|
||||||
|
db, ok = m.LevelDBConnections[uri.String()]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// Try the datadir directly
|
||||||
|
dataDir := path.Join(uri.Host, uri.Path)
|
||||||
|
|
||||||
|
db, ok = m.LevelDBConnections[dataDir]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -40,6 +51,12 @@ func (m *Manager) CloseLevelDB(connection string) error {
|
||||||
|
|
||||||
// GetLevelDB gets a levelDB for a particular connection
|
// GetLevelDB gets a levelDB for a particular connection
|
||||||
func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
|
func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
|
||||||
|
// Convert the provided connection description to the common format
|
||||||
|
uri := ToLevelDBURI(connection)
|
||||||
|
|
||||||
|
// Get the datadir
|
||||||
|
dataDir := path.Join(uri.Host, uri.Path)
|
||||||
|
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
db, ok := m.LevelDBConnections[connection]
|
db, ok := m.LevelDBConnections[connection]
|
||||||
|
@ -48,12 +65,28 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
|
||||||
|
|
||||||
return db.db, nil
|
return db.db, nil
|
||||||
}
|
}
|
||||||
uri := ToLevelDBURI(connection)
|
|
||||||
db = &levelDBHolder{
|
db, ok = m.LevelDBConnections[uri.String()]
|
||||||
name: []string{connection, uri.String()},
|
if ok {
|
||||||
|
db.count++
|
||||||
|
|
||||||
|
return db.db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is already a connection to this leveldb reuse that
|
||||||
|
// NOTE: if there differing options then only the first leveldb connection will be used
|
||||||
|
db, ok = m.LevelDBConnections[dataDir]
|
||||||
|
if ok {
|
||||||
|
db.count++
|
||||||
|
log.Warn("Duplicate connnection to level db: %s with different connection strings. Initial connection: %s. This connection: %s", dataDir, db.name[0], connection)
|
||||||
|
db.name = append(db.name, connection)
|
||||||
|
m.LevelDBConnections[connection] = db
|
||||||
|
return db.db, nil
|
||||||
|
}
|
||||||
|
db = &levelDBHolder{
|
||||||
|
name: []string{connection, uri.String(), dataDir},
|
||||||
}
|
}
|
||||||
|
|
||||||
dataDir := path.Join(uri.Host, uri.Path)
|
|
||||||
opts := &opt.Options{}
|
opts := &opt.Options{}
|
||||||
for k, v := range uri.Query() {
|
for k, v := range uri.Query() {
|
||||||
switch replacer.Replace(strings.ToLower(k)) {
|
switch replacer.Replace(strings.ToLower(k)) {
|
||||||
|
@ -134,7 +167,11 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
|
||||||
db.db, err = leveldb.OpenFile(dataDir, opts)
|
db.db, err = leveldb.OpenFile(dataDir, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.IsCorrupted(err) {
|
if !errors.IsCorrupted(err) {
|
||||||
return nil, err
|
if strings.Contains(err.Error(), "resource temporarily unavailable") {
|
||||||
|
return nil, fmt.Errorf("unable to lock level db at %s: %w", dataDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unable to open level db at %s: %w", dataDir, err)
|
||||||
}
|
}
|
||||||
db.db, err = leveldb.RecoverFile(dataDir, opts)
|
db.db, err = leveldb.RecoverFile(dataDir, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -91,7 +91,8 @@ func (a *actionNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *m
|
||||||
|
|
||||||
// NotifyCreateIssueComment notifies comment on an issue to notifiers
|
// NotifyCreateIssueComment notifies comment on an issue to notifiers
|
||||||
func (a *actionNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func (a *actionNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
act := &models.Action{
|
act := &models.Action{
|
||||||
ActUserID: doer.ID,
|
ActUserID: doer.ID,
|
||||||
ActUser: doer,
|
ActUser: doer,
|
||||||
|
|
|
@ -22,6 +22,7 @@ type Notifier interface {
|
||||||
NotifyTransferRepository(doer *user_model.User, repo *repo_model.Repository, oldOwnerName string)
|
NotifyTransferRepository(doer *user_model.User, repo *repo_model.Repository, oldOwnerName string)
|
||||||
NotifyNewIssue(issue *models.Issue, mentions []*user_model.User)
|
NotifyNewIssue(issue *models.Issue, mentions []*user_model.User)
|
||||||
NotifyIssueChangeStatus(*user_model.User, *models.Issue, *models.Comment, bool)
|
NotifyIssueChangeStatus(*user_model.User, *models.Issue, *models.Comment, bool)
|
||||||
|
NotifyDeleteIssue(*user_model.User, *models.Issue)
|
||||||
NotifyIssueChangeMilestone(doer *user_model.User, issue *models.Issue, oldMilestoneID int64)
|
NotifyIssueChangeMilestone(doer *user_model.User, issue *models.Issue, oldMilestoneID int64)
|
||||||
NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment)
|
NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment)
|
||||||
NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment)
|
NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment)
|
||||||
|
|
|
@ -33,6 +33,10 @@ func (*NullNotifier) NotifyNewIssue(issue *models.Issue, mentions []*user_model.
|
||||||
func (*NullNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) {
|
func (*NullNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyDeleteIssue notify when some issue deleted
|
||||||
|
func (*NullNotifier) NotifyDeleteIssue(doer *user_model.User, issue *models.Issue) {
|
||||||
|
}
|
||||||
|
|
||||||
// NotifyNewPullRequest places a place holder function
|
// NotifyNewPullRequest places a place holder function
|
||||||
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*user_model.User) {
|
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*user_model.User) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ func NewNotifier() base.Notifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *indexerNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func (r *indexerNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
if comment.Type == models.CommentTypeComment {
|
if comment.Type == models.CommentTypeComment {
|
||||||
if issue.Comments == nil {
|
if issue.Comments == nil {
|
||||||
if err := issue.LoadDiscussComments(); err != nil {
|
if err := issue.LoadDiscussComments(); err != nil {
|
||||||
|
|
|
@ -29,7 +29,8 @@ func NewNotifier() base.Notifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mailNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func (m *mailNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("mailNotifier.NotifyCreateIssueComment Issue[%d] #%d in [%d]", issue.ID, issue.Index, issue.RepoID))
|
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("mailNotifier.NotifyCreateIssueComment Issue[%d] #%d in [%d]", issue.ID, issue.Index, issue.RepoID))
|
||||||
defer finished()
|
defer finished()
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ func NewContext() {
|
||||||
|
|
||||||
// NotifyCreateIssueComment notifies issue comment related message to notifiers
|
// NotifyCreateIssueComment notifies issue comment related message to notifiers
|
||||||
func NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
for _, notifier := range notifiers {
|
for _, notifier := range notifiers {
|
||||||
notifier.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
|
notifier.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +60,13 @@ func NotifyIssueChangeStatus(doer *user_model.User, issue *models.Issue, actionC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyDeleteIssue notify when some issue deleted
|
||||||
|
func NotifyDeleteIssue(doer *user_model.User, issue *models.Issue) {
|
||||||
|
for _, notifier := range notifiers {
|
||||||
|
notifier.NotifyDeleteIssue(doer, issue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NotifyMergePullRequest notifies merge pull request to notifiers
|
// NotifyMergePullRequest notifies merge pull request to notifiers
|
||||||
func NotifyMergePullRequest(pr *models.PullRequest, doer *user_model.User) {
|
func NotifyMergePullRequest(pr *models.PullRequest, doer *user_model.User) {
|
||||||
for _, notifier := range notifiers {
|
for _, notifier := range notifiers {
|
||||||
|
@ -201,7 +209,8 @@ func NotifyIssueChangeRef(doer *user_model.User, issue *models.Issue, oldRef str
|
||||||
|
|
||||||
// NotifyIssueChangeLabels notifies change labels to notifiers
|
// NotifyIssueChangeLabels notifies change labels to notifiers
|
||||||
func NotifyIssueChangeLabels(doer *user_model.User, issue *models.Issue,
|
func NotifyIssueChangeLabels(doer *user_model.User, issue *models.Issue,
|
||||||
addedLabels, removedLabels []*models.Label) {
|
addedLabels, removedLabels []*models.Label,
|
||||||
|
) {
|
||||||
for _, notifier := range notifiers {
|
for _, notifier := range notifiers {
|
||||||
notifier.NotifyIssueChangeLabels(doer, issue, addedLabels, removedLabels)
|
notifier.NotifyIssueChangeLabels(doer, issue, addedLabels, removedLabels)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@ func (ns *notificationService) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *notificationService) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func (ns *notificationService) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
opts := issueNotificationOpts{
|
opts := issueNotificationOpts{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
NotificationAuthorID: doer.ID,
|
NotificationAuthorID: doer.ID,
|
||||||
|
@ -203,7 +204,7 @@ func (ns *notificationService) NotifyPullRevieweDismiss(doer *user_model.User, r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
|
func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
|
||||||
if !removed {
|
if !removed && doer.ID != assignee.ID {
|
||||||
opts := issueNotificationOpts{
|
opts := issueNotificationOpts{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
NotificationAuthorID: doer.ID,
|
NotificationAuthorID: doer.ID,
|
||||||
|
|
|
@ -424,7 +424,8 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *user_model.User, c *models.C
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *webhookNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
func (m *webhookNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
|
||||||
issue *models.Issue, comment *models.Comment, mentions []*user_model.User) {
|
issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
|
||||||
|
) {
|
||||||
mode, _ := models.AccessLevel(doer, repo)
|
mode, _ := models.AccessLevel(doer, repo)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -498,7 +499,8 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *user_model.User, comment *mo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *webhookNotifier) NotifyIssueChangeLabels(doer *user_model.User, issue *models.Issue,
|
func (m *webhookNotifier) NotifyIssueChangeLabels(doer *user_model.User, issue *models.Issue,
|
||||||
addedLabels, removedLabels []*models.Label) {
|
addedLabels, removedLabels []*models.Label,
|
||||||
|
) {
|
||||||
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyIssueChangeLabels User: %s[%d] Issue[%d] #%d in [%d]", doer.Name, doer.ID, issue.ID, issue.Index, issue.RepoID))
|
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyIssueChangeLabels User: %s[%d] Issue[%d] #%d in [%d]", doer.Name, doer.ID, issue.ID, issue.Index, issue.RepoID))
|
||||||
defer finished()
|
defer finished()
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ type MarkupRenderer struct {
|
||||||
IsInputFile bool
|
IsInputFile bool
|
||||||
NeedPostProcess bool
|
NeedPostProcess bool
|
||||||
MarkupSanitizerRules []MarkupSanitizerRule
|
MarkupSanitizerRules []MarkupSanitizerRule
|
||||||
|
DisableSanitizer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkupSanitizerRule defines the policy for whitelisting attributes on
|
// MarkupSanitizerRule defines the policy for whitelisting attributes on
|
||||||
|
@ -144,11 +145,12 @@ func newMarkupRenderer(name string, sec *ini.Section) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
|
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
|
||||||
Enabled: sec.Key("ENABLED").MustBool(false),
|
Enabled: sec.Key("ENABLED").MustBool(false),
|
||||||
MarkupName: name,
|
MarkupName: name,
|
||||||
FileExtensions: exts,
|
FileExtensions: exts,
|
||||||
Command: command,
|
Command: command,
|
||||||
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
|
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
|
||||||
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
|
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
|
||||||
|
DisableSanitizer: sec.Key("DISABLE_SANITIZER").MustBool(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue