mirror of
https://github.com/go-gitea/gitea
synced 2024-09-26 16:26:47 +02:00
Merge branch 'main' into bugfix/make-git-user-configurable
This commit is contained in:
commit
5dbe14afcf
@ -8,7 +8,9 @@
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {}
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.12"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
4
.github/workflows/cron-lock.yml
vendored
4
.github/workflows/cron-lock.yml
vendored
@ -20,8 +20,4 @@ jobs:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: 10
|
||||
issue-comment: |
|
||||
Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
|
||||
pr-inactive-days: 7
|
||||
pr-comment: |
|
||||
Automatically locked because of our [CONTRIBUTING guidelines](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#issue-locking)
|
||||
|
1
.github/workflows/files-changed.yml
vendored
1
.github/workflows/files-changed.yml
vendored
@ -48,6 +48,7 @@ jobs:
|
||||
- "Makefile"
|
||||
- ".golangci.yml"
|
||||
- ".editorconfig"
|
||||
- "options/locale/locale_en-US.ini"
|
||||
|
||||
frontend:
|
||||
- "**/*.js"
|
||||
|
8
.github/workflows/pull-compliance.yml
vendored
8
.github/workflows/pull-compliance.yml
vendored
@ -32,9 +32,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
- run: pip install poetry
|
||||
- run: make deps-py
|
||||
- run: make lint-templates
|
||||
@ -45,9 +45,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
- run: pip install poetry
|
||||
- run: make deps-py
|
||||
- run: make lint-yaml
|
||||
|
18
.github/workflows/pull-db-tests.yml
vendored
18
.github/workflows/pull-db-tests.yml
vendored
@ -49,7 +49,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: make test-pgsql-migration test-pgsql
|
||||
- name: run migration tests
|
||||
run: make test-pgsql-migration
|
||||
- name: run tests
|
||||
run: make test-pgsql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit
|
||||
@ -72,7 +75,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
- run: make test-sqlite-migration test-sqlite
|
||||
- name: run migration tests
|
||||
run: make test-sqlite-migration
|
||||
- name: run tests
|
||||
run: make test-sqlite
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||
@ -175,8 +181,10 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- name: run migration tests
|
||||
run: make test-mysql-migration
|
||||
- name: run tests
|
||||
run: make test-mysql-migration integration-test-coverage
|
||||
run: make integration-test-coverage
|
||||
env:
|
||||
TAGS: bindata
|
||||
RACE_ENABLED: true
|
||||
@ -208,7 +216,9 @@ jobs:
|
||||
- run: make backend
|
||||
env:
|
||||
TAGS: bindata
|
||||
- run: make test-mssql-migration test-mssql
|
||||
- run: make test-mssql-migration
|
||||
- name: run tests
|
||||
run: make test-mssql
|
||||
timeout-minutes: 50
|
||||
env:
|
||||
TAGS: bindata
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,7 +15,7 @@ _test
|
||||
|
||||
# MS VSCode
|
||||
.vscode
|
||||
__debug_bin
|
||||
__debug_bin*
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
|
@ -64,6 +64,7 @@ rules:
|
||||
"@stylistic/media-query-list-comma-newline-before": null
|
||||
"@stylistic/media-query-list-comma-space-after": null
|
||||
"@stylistic/media-query-list-comma-space-before": null
|
||||
"@stylistic/named-grid-areas-alignment": null
|
||||
"@stylistic/no-empty-first-line": null
|
||||
"@stylistic/no-eol-whitespace": true
|
||||
"@stylistic/no-extra-semicolons": true
|
||||
|
@ -464,7 +464,7 @@ We assume in good faith that the information you provide is legally binding.
|
||||
We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. \
|
||||
The overall goal is to make a major release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. \
|
||||
All the feature pull requests should be
|
||||
merged before feature freeze. And, during the frozen period, a corresponding
|
||||
merged before feature freeze. All feature pull requests haven't been merged before this feature freeze will be moved to next milestone, please notice our feature freeze announcement on discord. And, during the frozen period, a corresponding
|
||||
release branch is open for fixes backported from main branch. Release candidates
|
||||
are made during this period for user testing to
|
||||
obtain a final version that is maintained in this branch.
|
||||
|
22
Makefile
22
Makefile
@ -115,6 +115,7 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
|
||||
|
||||
FOMANTIC_WORK_DIR := web_src/fomantic
|
||||
|
||||
@ -147,6 +148,7 @@ GO_DIRS := build cmd models modules routers services tests
|
||||
WEB_DIRS := web_src/js web_src/css
|
||||
|
||||
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
|
||||
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
|
||||
|
||||
GO_SOURCES := $(wildcard *.go)
|
||||
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
|
||||
@ -426,7 +428,7 @@ lint-go-vet:
|
||||
|
||||
.PHONY: lint-editorconfig
|
||||
lint-editorconfig:
|
||||
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .github/workflows
|
||||
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
|
||||
|
||||
.PHONY: lint-actions
|
||||
lint-actions:
|
||||
@ -709,9 +711,7 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
|
||||
.PHONY: migrations.individual.mysql.test
|
||||
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
@ -719,20 +719,15 @@ migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test\#%
|
||||
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
|
||||
|
||||
|
||||
.PHONY: migrations.individual.mssql.test
|
||||
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg -test.failfast; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.mssql.test\#%
|
||||
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||
@ -740,9 +735,7 @@ migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
|
||||
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
|
||||
done
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES)
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test\#%
|
||||
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
|
||||
@ -908,6 +901,7 @@ fomantic:
|
||||
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
|
||||
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
|
||||
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
|
||||
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
|
||||
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
|
||||
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
|
||||
|
@ -4,8 +4,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -42,5 +42,5 @@ func runRegenerateKeys(_ *cli.Context) error {
|
||||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return asymkey_model.RewriteAllPublicKeys(ctx)
|
||||
return asymkey_service.RewriteAllPublicKeys(ctx)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
[Unit]
|
||||
Description=Gitea (Git with a cup of tea)
|
||||
After=syslog.target
|
||||
After=network.target
|
||||
###
|
||||
# Don't forget to add the database service dependencies
|
||||
|
@ -956,6 +956,12 @@ LEVEL = Info
|
||||
;GO_GET_CLONE_URL_PROTOCOL = https
|
||||
;;
|
||||
;; Close issues as long as a commit on any branch marks it as fixed
|
||||
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
|
||||
;;
|
||||
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
|
||||
;ENABLE_PUSH_CREATE_USER = false
|
||||
;ENABLE_PUSH_CREATE_ORG = false
|
||||
;;
|
||||
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions.
|
||||
;DISABLED_REPO_UNITS =
|
||||
;;
|
||||
@ -1474,8 +1480,10 @@ LEVEL = Info
|
||||
;;
|
||||
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
;DEFAULT_EMAIL_NOTIFICATIONS = enabled
|
||||
;; Disabled features for users, could be "deletion", more features can be disabled in future
|
||||
;; Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future
|
||||
;; - deletion: a user cannot delete their own account
|
||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||
;USER_DISABLED_FEATURES =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -518,8 +518,10 @@ And the following unique queues:
|
||||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
|
||||
- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion` and more features can be added in future.
|
||||
- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys` and more features can be added in future.
|
||||
- `deletion`: User cannot delete their own account.
|
||||
- `manage_ssh_keys`: User cannot configure ssh keys.
|
||||
- `manage_gpg_keys`: User cannot configure gpg keys.
|
||||
|
||||
## Security (`security`)
|
||||
|
||||
|
@ -497,8 +497,10 @@ Gitea 创建以下非唯一队列:
|
||||
|
||||
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**:用户电子邮件通知的默认配置(用户可配置)。选项:enabled、onmention、disabled
|
||||
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
|
||||
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`, 未来可以增加更多设置。
|
||||
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion`,`manage_ssh_keys`, `manage_gpg_keys` 未来可以增加更多设置。
|
||||
- `deletion`: 用户不能通过界面或者API删除他自己。
|
||||
- `manage_ssh_keys`: 用户不能通过界面或者API配置SSH Keys。
|
||||
- `manage_gpg_keys`: 用户不能配置 GPG 密钥。
|
||||
|
||||
## 安全性 (`security`)
|
||||
|
||||
|
@ -224,7 +224,7 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages
|
||||
{{if not (eq .Body "")}}
|
||||
<h3>Message content</h3>
|
||||
<hr>
|
||||
{{.Body | Str2html}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
</p>
|
||||
<hr>
|
||||
@ -259,20 +259,20 @@ This template produces something along these lines:
|
||||
The template system contains several functions that can be used to further process and format
|
||||
the messages. Here's a list of some of them:
|
||||
|
||||
| Name | Parameters | Available | Usage |
|
||||
| ---------------- | ----------- | --------- | --------------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | Any | Gitea's URL |
|
||||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
||||
| `AppDomain` | - | Any | Gitea's host name |
|
||||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
||||
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
|
||||
| `SafeHTML` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
|
||||
| Name | Parameters | Available | Usage |
|
||||
| ---------------- | ----------- | --------- | ------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | Any | Gitea's URL |
|
||||
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
|
||||
| `AppDomain` | - | Any | Gitea's host name |
|
||||
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
|
||||
| `SanitizeHTML` | string | Body only | Sanitizes text by removing any dangerous HTML tags from it |
|
||||
| `SafeHTML` | string | Body only | Takes the input as HTML, can be used for outputing raw HTML content |
|
||||
|
||||
These are _functions_, not metadata, so they have to be used:
|
||||
|
||||
```html
|
||||
Like this: {{Str2html "Escape<my>text"}}
|
||||
Or this: {{"Escape<my>text" | Str2html}}
|
||||
Like this: {{SanitizeHTML "Escape<my>text"}}
|
||||
Or this: {{"Escape<my>text" | SanitizeHTML}}
|
||||
Or this: {{AppUrl}}
|
||||
But not like this: {{.AppUrl}}
|
||||
```
|
||||
|
@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
|
||||
{{if not (eq .Body "")}}
|
||||
<h3>消息内容:</h3>
|
||||
<hr>
|
||||
{{.Body | Str2html}}
|
||||
{{.Body}}
|
||||
{{end}}
|
||||
</p>
|
||||
<hr>
|
||||
@ -242,20 +242,20 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
|
||||
|
||||
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
|
||||
|
||||
| 函数名 | 参数 | 可用于 | 用法 |
|
||||
|------------------| ----------- | ------------ | --------------------------------------------------------------------------------- |
|
||||
| `AppUrl` | - | 任何地方 | Gitea 的 URL |
|
||||
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
|
||||
| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
|
||||
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
|
||||
| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
|
||||
| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
|
||||
| 函数名 | 参数 | 可用于 | 用法 |
|
||||
|------------------| ----------- | ------------ | ------------------------------ |
|
||||
| `AppUrl` | - | 任何地方 | Gitea 的 URL |
|
||||
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
|
||||
| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
|
||||
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
|
||||
| `SanitizeHTML` | string | 仅正文部分 | 通过删除其中的危险 HTML 标签对文本进行清理 |
|
||||
| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于输出原始的 HTML 内容 |
|
||||
|
||||
这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
|
||||
|
||||
```html
|
||||
像这样使用: {{Str2html "Escape<my>text"}}
|
||||
或者这样使用: {{"Escape<my>text" | Str2html}}
|
||||
像这样使用: {{SanitizeHTML "Escape<my>text"}}
|
||||
或者这样使用: {{"Escape<my>text" | SanitizeHTML}}
|
||||
或者这样使用: {{AppUrl}}
|
||||
但不要像这样使用: {{.AppUrl}}
|
||||
```
|
||||
|
@ -47,7 +47,7 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
|
||||
9. Avoid unnecessary `!important` in CSS, add comments to explain why it's necessary if it can't be avoided.
|
||||
10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event.
|
||||
11. Custom event names are recommended to use `ce-` prefix.
|
||||
12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
|
||||
12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-df`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`).
|
||||
13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided.
|
||||
|
||||
### Accessibility / ARIA
|
||||
|
@ -47,7 +47,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
|
||||
9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。
|
||||
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
|
||||
11. 推荐使用自定义事件名称前缀`ce-`。
|
||||
12. Gitea 的 tailwind-style CSS 类使用`gt-`前缀(`gt-relative`),而 Gitea 自身的私有框架级 CSS 类使用`g-`前缀(`g-modal-confirm`)。
|
||||
12. 建议使用 Tailwind CSS,它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-df`),Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。
|
||||
13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。
|
||||
|
||||
### 可访问性 / ARIA
|
||||
|
@ -221,9 +221,11 @@ Our translations are currently crowd-sourced on our [Crowdin project](https://cr
|
||||
|
||||
Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration.
|
||||
|
||||
## Push Hook / Webhook aren't running
|
||||
## Push Hook / Webhook / Actions aren't running
|
||||
|
||||
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook, there are a few possibilities:
|
||||
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook and Actions workflows, it's likely that the git hooks are not working.
|
||||
|
||||
There are a few possibilities:
|
||||
|
||||
1. The git hooks are out of sync: run "Resynchronize pre-receive, update and post-receive hooks of all repositories" on the site admin panel
|
||||
2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script`
|
||||
|
@ -225,9 +225,11 @@ Gitea还提供了自己的SSH服务器,用于在SSHD不可用时使用。
|
||||
|
||||
无论您想要更改翻译还是添加新的翻译,都需要在Crowdin集成中进行,因为所有翻译都会被CI覆盖。
|
||||
|
||||
## 推送钩子/ Webhook未运行
|
||||
## 推送钩子/ Webhook / Actions 未运行
|
||||
|
||||
如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发Webhook,有几种可能性:
|
||||
如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发 Webhook 和 Actions,可能是 git 钩子不工作而导致的。
|
||||
|
||||
这可能是由于以下原因:
|
||||
|
||||
1. Git钩子不同步:在站点管理面板上运行“重新同步所有仓库的pre-receive、update和post-receive钩子”
|
||||
2. Git仓库(和钩子)存储在一些不支持脚本执行的文件系统上(例如由NAS挂载),请确保文件系统支持`chmod a+x any-script`
|
||||
|
56
docs/content/usage/blocking-users.en-us.md
Normal file
56
docs/content/usage/blocking-users.en-us.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
date: "2024-01-31T00:00:00+00:00"
|
||||
title: "Blocking a user"
|
||||
slug: "blocking-user"
|
||||
sidebar_position: 25
|
||||
toc: false
|
||||
draft: false
|
||||
aliases:
|
||||
- /en-us/webhooks
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
name: "Blocking a user"
|
||||
sidebar_position: 30
|
||||
identifier: "blocking-user"
|
||||
---
|
||||
|
||||
# Blocking a user
|
||||
|
||||
Gitea supports blocking of users to restrict how they can interact with you and your content.
|
||||
|
||||
You can block a user in your account settings, from the user's profile or from comments created by the user.
|
||||
The user is not directly notified about the block, but they can notice they are blocked when they attempt to interact with you.
|
||||
Organization owners can block anyone who is not a member of the organization too.
|
||||
If a blocked user has admin permissions, they can still perform all actions even if blocked.
|
||||
|
||||
### When you block a user
|
||||
|
||||
- the user stops following you
|
||||
- you stop following the user
|
||||
- the user's stars are removed from your repositories
|
||||
- your stars are removed from their repositories
|
||||
- the user stops watching your repositories
|
||||
- you stop watching their repositories
|
||||
- the user's issue assignments are removed from your repositories
|
||||
- your issue assignments are removed from their repositories
|
||||
- the user is removed as a collaborator on your repositories
|
||||
- you are removed as a collaborator on their repositories
|
||||
- any pending repository transfers to or from the blocked user are canceled
|
||||
|
||||
### When you block a user, the user cannot
|
||||
|
||||
- follow you
|
||||
- watch your repositories
|
||||
- star your repositories
|
||||
- fork your repositories
|
||||
- transfer repositories to you
|
||||
- open issues or pull requests on your repositories
|
||||
- comment on issues or pull requests you've created
|
||||
- comment on issues or pull requests on your repositories
|
||||
- react to your comments on issues or pull requests
|
||||
- react to comments on issues or pull requests on your repositories
|
||||
- assign you to issues or pull requests
|
||||
- add you as a collaborator on their repositories
|
||||
- send you notifications by @mentioning your username
|
||||
- be added as team member (if blocked by an organization)
|
@ -136,6 +136,12 @@ body:
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
# some markdown that will only be visible once the issue has been created
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This issue was created by an issue **template** :)
|
||||
visible: [content]
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
@ -187,11 +193,16 @@ body:
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
- label: I have also read the CONTRIBUTION.MD
|
||||
required: true
|
||||
visible: [form]
|
||||
- label: This is a TODO only visible after issue creation
|
||||
visible: [content]
|
||||
```
|
||||
|
||||
### Markdown
|
||||
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted.
|
||||
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.
|
||||
|
||||
Attributes:
|
||||
|
||||
@ -199,6 +210,8 @@ Attributes:
|
||||
|-------|--------------------------------------------------------------|----------|--------|---------|--------------|
|
||||
| value | The text that is rendered. Markdown formatting is supported. | Required | String | - | - |
|
||||
|
||||
visible: Default is **[form]**
|
||||
|
||||
### Textarea
|
||||
|
||||
You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields.
|
||||
@ -219,6 +232,8 @@ Validations:
|
||||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Input
|
||||
|
||||
You can use an `input` element to add a single-line text field to your form.
|
||||
@ -240,6 +255,8 @@ Validations:
|
||||
| is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - |
|
||||
| regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Dropdown
|
||||
|
||||
You can use a `dropdown` element to add a dropdown menu in your form.
|
||||
@ -259,6 +276,8 @@ Validations:
|
||||
|----------|------------------------------------------------------|----------|---------|---------|--------------|
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
### Checkboxes
|
||||
|
||||
You can use the `checkboxes` element to add a set of checkboxes to your form.
|
||||
@ -266,17 +285,20 @@ You can use the `checkboxes` element to add a set of checkboxes to your form.
|
||||
Attributes:
|
||||
|
||||
| Key | Description | Required | Type | Default | Valid values |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------|----------|--------|--------------|--------------|
|
||||
| ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
|
||||
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
|
||||
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - |
|
||||
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
|
||||
|
||||
For each value in the options array, you can set the following keys.
|
||||
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|----------|------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| Key | Description | Required | Type | Default | Options |
|
||||
|--------------|------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------|---------|---------|
|
||||
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
|
||||
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
|
||||
| visible | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content". | Optional | String array | false | - |
|
||||
|
||||
visible: Default is **[form, content]**
|
||||
|
||||
## Syntax for issue config
|
||||
|
||||
@ -292,15 +314,15 @@ contact_links:
|
||||
|
||||
### Possible Options
|
||||
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|--------------------|----------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
| Key | Description | Type | Default |
|
||||
|----------------------|-------------------------------------------------------|--------------------|-------------|
|
||||
| blank_issues_enabled | If set to false, the User is forced to use a Template | Boolean | true |
|
||||
| contact_links | Custom Links to show in the Choose Box | Contact Link Array | Empty Array |
|
||||
|
||||
### Contact Link
|
||||
|
||||
| Key | Description | Type | Required |
|
||||
|----------------------|-------------------------------------------------------------------------------------------------------|---------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
| Key | Description | Type | Required |
|
||||
|-------|----------------------------------|--------|----------|
|
||||
| name | the name of your link | String | true |
|
||||
| url | The URL of your Link | String | true |
|
||||
| about | A short description of your Link | String | true |
|
||||
|
14
go.mod
14
go.mod
@ -49,7 +49,7 @@ require (
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-swagger/go-swagger v0.30.5
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0
|
||||
github.com/go-webauthn/webauthn v0.10.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
|
||||
@ -57,7 +57,7 @@ require (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/google/go-github/v57 v57.0.0
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
@ -73,7 +73,7 @@ require (
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/markbates/goth v1.78.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/meilisearch/meilisearch-go v0.26.1
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
@ -113,7 +113,7 @@ require (
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.17.0
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -129,7 +129,7 @@ require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ClickHouse/ch-go v0.61.1 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
@ -241,7 +241,7 @@ require (
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/paulmach/orb v0.11.0 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
@ -302,7 +302,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||
|
||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.2.51
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
|
||||
|
||||
replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5
|
||||
|
||||
|
28
go.sum
28
go.sum
@ -48,8 +48,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||
gitea.com/gitea/act v0.2.51 h1:gXc/B4OlTciTTzAx9cmNyw04n2SDO7exPjAsR5Idu+c=
|
||||
gitea.com/gitea/act v0.2.51/go.mod h1:CoaX2053jqBlD6JMgu4d4UgFL/rp2I14Kt5mMqcs0Z0=
|
||||
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo=
|
||||
gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
|
||||
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
|
||||
@ -78,8 +78,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ClickHouse/ch-go v0.61.1 h1:j5rx3qnvcnYjhnP1IdXE/vdIRQiqgwAzyqOaasA6QCw=
|
||||
github.com/ClickHouse/ch-go v0.61.1/go.mod h1:myxt/JZgy2BYHFGQqzmaIpbfr5CMbs3YHVULaWQj5YU=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
@ -384,8 +384,8 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.m
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo=
|
||||
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
|
||||
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
|
||||
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
||||
@ -488,8 +488,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@ -643,8 +643,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
@ -718,8 +718,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/paulmach/orb v0.11.0 h1:JfVXJUBeH9ifc/OrhBY0lL16QsmPgpCHMlqSSYhcgAA=
|
||||
github.com/paulmach/orb v0.11.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
@ -1308,8 +1308,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/shared/types"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -159,7 +160,7 @@ type FindRunnerOptions struct {
|
||||
OwnerID int64
|
||||
Sort string
|
||||
Filter string
|
||||
IsOnline util.OptionalBool
|
||||
IsOnline optional.Option[bool]
|
||||
WithAvailable bool // not only runners belong to, but also runners can be used
|
||||
}
|
||||
|
||||
@ -186,10 +187,12 @@ func (opts FindRunnerOptions) ToConds() builder.Cond {
|
||||
cond = cond.And(builder.Like{"name", opts.Filter})
|
||||
}
|
||||
|
||||
if opts.IsOnline.IsTrue() {
|
||||
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
} else if opts.IsOnline.IsFalse() {
|
||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
if opts.IsOnline.Has() {
|
||||
if opts.IsOnline.Value() {
|
||||
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
} else {
|
||||
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
|
||||
}
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -82,3 +83,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error)
|
||||
})
|
||||
return count != 0, err
|
||||
}
|
||||
|
||||
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) {
|
||||
variables := map[string]string{}
|
||||
|
||||
// Global
|
||||
globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{})
|
||||
if err != nil {
|
||||
log.Error("find global variables: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Org / User level
|
||||
ownerVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID})
|
||||
if err != nil {
|
||||
log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Repo level
|
||||
repoVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{RepoID: run.RepoID})
|
||||
if err != nil {
|
||||
log.Error("find variables of repo: %d, error: %v", run.RepoID, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Level precedence: Repo > Org / User > Global
|
||||
for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
|
||||
variables[v.Name] = v.Data
|
||||
}
|
||||
|
||||
return variables, nil
|
||||
}
|
||||
|
@ -225,8 +225,8 @@ func (a *Action) ShortActUserName(ctx context.Context) string {
|
||||
return base.EllipsisString(a.GetActUserName(ctx), 20)
|
||||
}
|
||||
|
||||
// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
|
||||
func (a *Action) GetDisplayName(ctx context.Context) string {
|
||||
// GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
|
||||
func (a *Action) GetActDisplayName(ctx context.Context) string {
|
||||
if setting.UI.DefaultShowFullName {
|
||||
trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx))
|
||||
if len(trimmedFullName) > 0 {
|
||||
@ -236,8 +236,8 @@ func (a *Action) GetDisplayName(ctx context.Context) string {
|
||||
return a.ShortActUserName(ctx)
|
||||
}
|
||||
|
||||
// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
|
||||
func (a *Action) GetDisplayNameTitle(ctx context.Context) string {
|
||||
// GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
|
||||
func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
||||
if setting.UI.DefaultShowFullName {
|
||||
return a.ShortActUserName(ctx)
|
||||
}
|
||||
@ -393,10 +393,14 @@ func (a *Action) GetCreate() time.Time {
|
||||
return a.CreatedUnix.AsTime()
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of issues associated with
|
||||
// the action.
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 3)
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
ret := strings.SplitN(a.Content, "|", 3)
|
||||
for len(ret) < 3 {
|
||||
ret = append(ret, "")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetIssueTitle returns the title of first issue associated with the action.
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@ -44,6 +43,12 @@ const (
|
||||
|
||||
var sshOpLocker sync.Mutex
|
||||
|
||||
func WithSSHOpLocker(f func() error) error {
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
return f()
|
||||
}
|
||||
|
||||
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
|
||||
func AuthorizedStringForKey(key *PublicKey) string {
|
||||
sb := &strings.Builder{}
|
||||
@ -114,65 +119,6 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
|
||||
// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
|
||||
// outside any session scope independently.
|
||||
func RewriteAllPublicKeys(ctx context.Context) error {
|
||||
// Don't rewrite key if internal server
|
||||
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
if setting.SSH.RootPath != "" {
|
||||
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
|
||||
// This of course doesn't guarantee that this is the right directory for authorized_keys
|
||||
// but at least if it's supposed to be this directory and it doesn't exist and we're the
|
||||
// right user it will at least be created properly.
|
||||
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
|
||||
if err != nil {
|
||||
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
|
||||
tmpPath := fPath + ".tmp"
|
||||
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
t.Close()
|
||||
if err := util.Remove(tmpPath); err != nil {
|
||||
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if setting.SSH.AuthorizedKeysBackup {
|
||||
isExist, err := util.IsExist(fPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
|
||||
return err
|
||||
}
|
||||
if isExist {
|
||||
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
|
||||
if err = util.CopyFile(fPath, bakPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := RegeneratePublicKeys(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Close()
|
||||
return util.Rename(tmpPath, fPath)
|
||||
}
|
||||
|
||||
// RegeneratePublicKeys regenerates the authorized_keys file
|
||||
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
|
||||
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
|
||||
|
@ -9,51 +9,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// AddPrincipalKey adds new principal to database and authorized_principals file.
|
||||
func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) {
|
||||
dbCtx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
// Principals cannot be duplicated.
|
||||
has, err := db.GetEngine(dbCtx).
|
||||
Where("content = ? AND type = ?", content, KeyTypePrincipal).
|
||||
Get(new(PublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if has {
|
||||
return nil, ErrKeyAlreadyExist{0, "", content}
|
||||
}
|
||||
|
||||
key := &PublicKey{
|
||||
OwnerID: ownerID,
|
||||
Name: content,
|
||||
Content: content,
|
||||
Mode: perm.AccessModeWrite,
|
||||
Type: KeyTypePrincipal,
|
||||
LoginSourceID: authSourceID,
|
||||
}
|
||||
if err = db.Insert(dbCtx, key); err != nil {
|
||||
return nil, fmt.Errorf("addKey: %w", err)
|
||||
}
|
||||
|
||||
if err = committer.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
committer.Close()
|
||||
|
||||
return key, RewriteAllPrincipalKeys(ctx)
|
||||
}
|
||||
|
||||
// CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines
|
||||
func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content string) (_ string, err error) {
|
||||
if setting.SSH.Disabled {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -243,14 +244,14 @@ func CreateSource(ctx context.Context, source *Source) error {
|
||||
|
||||
type FindSourcesOptions struct {
|
||||
db.ListOptions
|
||||
IsActive util.OptionalBool
|
||||
IsActive optional.Option[bool]
|
||||
LoginType Type
|
||||
}
|
||||
|
||||
func (opts FindSourcesOptions) ToConds() builder.Cond {
|
||||
conds := builder.NewCond()
|
||||
if !opts.IsActive.IsNone() {
|
||||
conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
|
||||
if opts.IsActive.Has() {
|
||||
conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
|
||||
}
|
||||
if opts.LoginType != NoType {
|
||||
conds = conds.And(builder.Eq{"`type`": opts.LoginType})
|
||||
@ -262,7 +263,7 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
|
||||
// source of type LoginSSPI
|
||||
func IsSSPIEnabled(ctx context.Context) bool {
|
||||
exist, err := db.Exist[Source](ctx, FindSourcesOptions{
|
||||
IsActive: util.OptionalBoolTrue,
|
||||
IsActive: optional.Some(true),
|
||||
LoginType: SSPI,
|
||||
}.ToConds())
|
||||
if err != nil {
|
||||
|
@ -166,8 +166,7 @@ func preprocessDatabaseCollation(x *xorm.Engine) {
|
||||
|
||||
// try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed)
|
||||
// at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation
|
||||
// and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'.
|
||||
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL {
|
||||
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 {
|
||||
if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil {
|
||||
log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err)
|
||||
} else {
|
||||
|
@ -42,120 +42,132 @@
|
||||
|
||||
-
|
||||
id: 8
|
||||
user_id: 15
|
||||
user_id: 10
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 9
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
user_id: 10
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 10
|
||||
user_id: 15
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 11
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 12
|
||||
user_id: 15
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 11
|
||||
id: 13
|
||||
user_id: 15
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 12
|
||||
id: 14
|
||||
user_id: 15
|
||||
repo_id: 32
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 13
|
||||
id: 15
|
||||
user_id: 18
|
||||
repo_id: 21
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 14
|
||||
id: 16
|
||||
user_id: 18
|
||||
repo_id: 22
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 15
|
||||
id: 17
|
||||
user_id: 18
|
||||
repo_id: 23
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 16
|
||||
id: 18
|
||||
user_id: 18
|
||||
repo_id: 24
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 17
|
||||
id: 19
|
||||
user_id: 20
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 18
|
||||
id: 20
|
||||
user_id: 20
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 19
|
||||
id: 21
|
||||
user_id: 20
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 20
|
||||
id: 22
|
||||
user_id: 29
|
||||
repo_id: 4
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 21
|
||||
id: 23
|
||||
user_id: 29
|
||||
repo_id: 24
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 22
|
||||
id: 24
|
||||
user_id: 31
|
||||
repo_id: 27
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 23
|
||||
id: 25
|
||||
user_id: 31
|
||||
repo_id: 28
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 24
|
||||
id: 26
|
||||
user_id: 38
|
||||
repo_id: 60
|
||||
mode: 2
|
||||
|
||||
-
|
||||
id: 25
|
||||
id: 27
|
||||
user_id: 38
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 26
|
||||
id: 28
|
||||
user_id: 39
|
||||
repo_id: 61
|
||||
mode: 1
|
||||
|
||||
-
|
||||
id: 27
|
||||
id: 29
|
||||
user_id: 40
|
||||
repo_id: 61
|
||||
mode: 4
|
||||
|
@ -17,3 +17,22 @@
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
-
|
||||
id: 792
|
||||
title: "update actions"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "artifact.yaml"
|
||||
index: 188
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
@ -12,3 +12,17 @@
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 193
|
||||
run_id: 792
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job_2
|
||||
attempt: 1
|
||||
job_id: job_2
|
||||
task_id: 48
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
@ -18,3 +18,23 @@
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 48
|
||||
job_id: 193
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 6 # 6 is the status code for "running", running task can upload artifacts
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: ffffcfffffffbffffffffffffffffefffffffafffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffff
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
|
@ -51,3 +51,15 @@
|
||||
repo_id: 60
|
||||
user_id: 38
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 10
|
||||
repo_id: 21
|
||||
user_id: 10
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 11
|
||||
repo_id: 32
|
||||
user_id: 10
|
||||
mode: 2 # write
|
||||
|
@ -3,3 +3,35 @@
|
||||
hook_id: 1
|
||||
uuid: uuid1
|
||||
is_delivered: true
|
||||
is_succeed: false
|
||||
request_content: >
|
||||
{
|
||||
"url": "/matrix-delivered",
|
||||
"http_method":"PUT",
|
||||
"headers": {
|
||||
"X-Head": "42"
|
||||
},
|
||||
"body": "{}"
|
||||
}
|
||||
|
||||
-
|
||||
id: 2
|
||||
hook_id: 1
|
||||
uuid: uuid2
|
||||
is_delivered: false
|
||||
|
||||
-
|
||||
id: 3
|
||||
hook_id: 1
|
||||
uuid: uuid3
|
||||
is_delivered: true
|
||||
is_succeed: true
|
||||
payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content)
|
||||
request_content: >
|
||||
{
|
||||
"url": "/matrix-success",
|
||||
"http_method":"PUT",
|
||||
"headers": {
|
||||
"X-Head": "42"
|
||||
}
|
||||
}
|
||||
|
@ -14,3 +14,7 @@
|
||||
id: 4
|
||||
assignee_id: 2
|
||||
issue_id: 17
|
||||
-
|
||||
id: 5
|
||||
assignee_id: 10
|
||||
issue_id: 6
|
||||
|
@ -5,3 +5,19 @@
|
||||
repo_id: 3
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
||||
-
|
||||
id: 2
|
||||
doer_id: 16
|
||||
recipient_id: 10
|
||||
repo_id: 21
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
||||
-
|
||||
id: 3
|
||||
doer_id: 3
|
||||
recipient_id: 10
|
||||
repo_id: 32
|
||||
created_unix: 1553610671
|
||||
updated_unix: 1553610671
|
||||
|
@ -650,12 +650,6 @@
|
||||
type: 2
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 98
|
||||
repo_id: 1
|
||||
type: 8
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 99
|
||||
repo_id: 1
|
||||
|
@ -614,8 +614,8 @@
|
||||
owner_name: user16
|
||||
lower_name: big_test_public_3
|
||||
name: big_test_public_3
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_watches: 1
|
||||
num_stars: 1
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
@ -945,8 +945,8 @@
|
||||
owner_name: org3
|
||||
lower_name: repo21
|
||||
name: repo21
|
||||
num_watches: 0
|
||||
num_stars: 0
|
||||
num_watches: 1
|
||||
num_stars: 1
|
||||
num_forks: 0
|
||||
num_issues: 2
|
||||
num_closed_issues: 0
|
||||
|
@ -7,3 +7,13 @@
|
||||
id: 2
|
||||
uid: 2
|
||||
repo_id: 4
|
||||
|
||||
-
|
||||
id: 3
|
||||
uid: 10
|
||||
repo_id: 21
|
||||
|
||||
-
|
||||
id: 4
|
||||
uid: 10
|
||||
repo_id: 32
|
||||
|
@ -361,7 +361,7 @@
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_stars: 2
|
||||
num_repos: 3
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
|
19
models/fixtures/user_blocking.yml
Normal file
19
models/fixtures/user_blocking.yml
Normal file
@ -0,0 +1,19 @@
|
||||
-
|
||||
id: 1
|
||||
blocker_id: 2
|
||||
blockee_id: 29
|
||||
|
||||
-
|
||||
id: 2
|
||||
blocker_id: 17
|
||||
blockee_id: 28
|
||||
|
||||
-
|
||||
id: 3
|
||||
blocker_id: 2
|
||||
blockee_id: 34
|
||||
|
||||
-
|
||||
id: 4
|
||||
blocker_id: 50
|
||||
blockee_id: 34
|
@ -27,3 +27,15 @@
|
||||
user_id: 11
|
||||
repo_id: 1
|
||||
mode: 3 # auto
|
||||
|
||||
-
|
||||
id: 6
|
||||
user_id: 10
|
||||
repo_id: 21
|
||||
mode: 1 # normal
|
||||
|
||||
-
|
||||
id: 7
|
||||
user_id: 10
|
||||
repo_id: 32
|
||||
mode: 1 # normal
|
||||
|
@ -158,6 +158,11 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
||||
return &branch, nil
|
||||
}
|
||||
|
||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) {
|
||||
branches := make([]*Branch, 0, len(branchNames))
|
||||
return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches)
|
||||
}
|
||||
|
||||
func AddBranches(ctx context.Context, branches []*Branch) error {
|
||||
for _, branch := range branches {
|
||||
if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
|
||||
|
@ -64,6 +64,27 @@ func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.U
|
||||
return db.Exist[IssueAssignees](ctx, builder.Eq{"assignee_id": user.ID, "issue_id": issue.ID})
|
||||
}
|
||||
|
||||
type AssignedIssuesOptions struct {
|
||||
db.ListOptions
|
||||
AssigneeID int64
|
||||
RepoOwnerID int64
|
||||
}
|
||||
|
||||
func (opts *AssignedIssuesOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.AssigneeID != 0 {
|
||||
cond = cond.And(builder.In("issue.id", builder.Select("issue_id").From("issue_assignees").Where(builder.Eq{"assignee_id": opts.AssigneeID})))
|
||||
}
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.In("issue.repo_id", builder.Select("id").From("repository").Where(builder.Eq{"owner_id": opts.RepoOwnerID})))
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func GetAssignedIssues(ctx context.Context, opts *AssignedIssuesOptions) ([]*Issue, int64, error) {
|
||||
return db.FindAndCount[Issue](ctx, opts)
|
||||
}
|
||||
|
||||
// ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
|
||||
func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
|
@ -8,6 +8,7 @@ package issues
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
@ -21,6 +22,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
@ -259,8 +261,8 @@ type Comment struct {
|
||||
CommitID int64
|
||||
Line int64 // - previous line / + proposed line
|
||||
TreePath string
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
RenderedContent template.HTML `xorm:"-"`
|
||||
|
||||
// Path represents the 4 lines of code cemented by this comment
|
||||
Patch string `xorm:"-"`
|
||||
@ -1035,8 +1037,8 @@ type FindCommentsOptions struct {
|
||||
TreePath string
|
||||
Type CommentType
|
||||
IssueIDs []int64
|
||||
Invalidated util.OptionalBool
|
||||
IsPull util.OptionalBool
|
||||
Invalidated optional.Option[bool]
|
||||
IsPull optional.Option[bool]
|
||||
}
|
||||
|
||||
// ToConds implements FindOptions interface
|
||||
@ -1068,11 +1070,11 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
|
||||
if len(opts.TreePath) > 0 {
|
||||
cond = cond.And(builder.Eq{"comment.tree_path": opts.TreePath})
|
||||
}
|
||||
if !opts.Invalidated.IsNone() {
|
||||
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
|
||||
if opts.Invalidated.Has() {
|
||||
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.Value()})
|
||||
}
|
||||
if opts.IsPull != util.OptionalBoolNone {
|
||||
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
|
||||
if opts.IsPull.Has() {
|
||||
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.Value()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
@ -1081,7 +1083,7 @@ func (opts FindCommentsOptions) ToConds() builder.Cond {
|
||||
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
|
||||
comments := make([]*Comment, 0, 10)
|
||||
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
||||
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
|
||||
if opts.RepoID > 0 || opts.IsPull.Has() {
|
||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||
}
|
||||
|
||||
|
@ -172,13 +172,9 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
|
||||
|
||||
// HasIssueContentHistory check if a ContentHistory entry exists
|
||||
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
|
||||
exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{
|
||||
IssueID: issueID,
|
||||
CommentID: commentID,
|
||||
})
|
||||
exists, err := db.GetEngine(dbCtx).Where(builder.Eq{"issue_id": issueID, "comment_id": commentID}).Exist(&ContentHistory{})
|
||||
if err != nil {
|
||||
log.Error("can not fetch issue content history. err=%v", err)
|
||||
return false, err
|
||||
return false, fmt.Errorf("can not check issue content history. err: %w", err)
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
@ -78,3 +78,22 @@ func TestContentHistory(t *testing.T) {
|
||||
assert.EqualValues(t, 7, list2[1].HistoryID)
|
||||
assert.EqualValues(t, 4, list2[2].HistoryID)
|
||||
}
|
||||
|
||||
func TestHasIssueContentHistoryForCommentOnly(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
_ = db.TruncateBeans(db.DefaultContext, &issues_model.ContentHistory{})
|
||||
|
||||
hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
|
||||
assert.False(t, hasHistory1)
|
||||
hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
|
||||
assert.False(t, hasHistory2)
|
||||
|
||||
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow(), "c-a", true)
|
||||
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
|
||||
|
||||
hasHistory1, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
|
||||
assert.False(t, hasHistory1)
|
||||
hasHistory2, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
|
||||
assert.True(t, hasHistory2)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package issues
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
@ -105,7 +106,7 @@ type Issue struct {
|
||||
OriginalAuthorID int64 `xorm:"index"`
|
||||
Title string `xorm:"name"`
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
RenderedContent template.HTML `xorm:"-"`
|
||||
Labels []*Label `xorm:"-"`
|
||||
MilestoneID int64 `xorm:"INDEX"`
|
||||
Milestone *Milestone `xorm:"-"`
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
@ -34,8 +34,8 @@ type IssuesOptions struct { //nolint
|
||||
MilestoneIDs []int64
|
||||
ProjectID int64
|
||||
ProjectBoardID int64
|
||||
IsClosed util.OptionalBool
|
||||
IsPull util.OptionalBool
|
||||
IsClosed optional.Option[bool]
|
||||
IsPull optional.Option[bool]
|
||||
LabelIDs []int64
|
||||
IncludedLabelNames []string
|
||||
ExcludedLabelNames []string
|
||||
@ -46,7 +46,7 @@ type IssuesOptions struct { //nolint
|
||||
UpdatedBeforeUnix int64
|
||||
// prioritize issues from this repo
|
||||
PriorityRepoID int64
|
||||
IsArchived util.OptionalBool
|
||||
IsArchived optional.Option[bool]
|
||||
Org *organization.Organization // issues permission scope
|
||||
Team *organization.Team // issues permission scope
|
||||
User *user_model.User // issues permission scope
|
||||
@ -217,8 +217,8 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||
|
||||
applyRepoConditions(sess, opts)
|
||||
|
||||
if !opts.IsClosed.IsNone() {
|
||||
sess.And("issue.is_closed=?", opts.IsClosed.IsTrue())
|
||||
if opts.IsClosed.Has() {
|
||||
sess.And("issue.is_closed=?", opts.IsClosed.Value())
|
||||
}
|
||||
|
||||
if opts.AssigneeID > 0 {
|
||||
@ -260,21 +260,18 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||
|
||||
applyProjectBoardCondition(sess, opts)
|
||||
|
||||
switch opts.IsPull {
|
||||
case util.OptionalBoolTrue:
|
||||
sess.And("issue.is_pull=?", true)
|
||||
case util.OptionalBoolFalse:
|
||||
sess.And("issue.is_pull=?", false)
|
||||
if opts.IsPull.Has() {
|
||||
sess.And("issue.is_pull=?", opts.IsPull.Value())
|
||||
}
|
||||
|
||||
if opts.IsArchived != util.OptionalBoolNone {
|
||||
sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.IsTrue()})
|
||||
if opts.IsArchived.Has() {
|
||||
sess.And(builder.Eq{"repository.is_archived": opts.IsArchived.Value()})
|
||||
}
|
||||
|
||||
applyLabelsCondition(sess, opts)
|
||||
|
||||
if opts.User != nil {
|
||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.IsTrue()))
|
||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
||||
}
|
||||
|
||||
return sess
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
@ -170,11 +169,8 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
|
||||
applyReviewedCondition(sess, opts.ReviewedID)
|
||||
}
|
||||
|
||||
switch opts.IsPull {
|
||||
case util.OptionalBoolTrue:
|
||||
sess.And("issue.is_pull=?", true)
|
||||
case util.OptionalBoolFalse:
|
||||
sess.And("issue.is_pull=?", false)
|
||||
if opts.IsPull.Has() {
|
||||
sess.And("issue.is_pull=?", opts.IsPull.Value())
|
||||
}
|
||||
|
||||
return sess
|
||||
|
@ -517,6 +517,15 @@ func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_mo
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
|
||||
}
|
||||
|
||||
notBlocked := make([]*user_model.User, 0, len(mentions))
|
||||
for _, user := range mentions {
|
||||
if !user_model.IsUserBlockedBy(ctx, doer, user.ID) {
|
||||
notBlocked = append(notBlocked, user)
|
||||
}
|
||||
}
|
||||
mentions = notBlocked
|
||||
|
||||
if err = UpdateIssueMentions(ctx, issue.ID, mentions); err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
|
||||
}
|
||||
|
@ -214,6 +214,10 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
|
||||
if !perm.CanReadIssuesOrPulls(refIssue.IsPull) {
|
||||
return nil, references.XRefActionNone, nil
|
||||
}
|
||||
if user_model.IsUserBlockedBy(stdCtx, ctx.Doer, refIssue.PosterID, refIssue.Repo.OwnerID) {
|
||||
return nil, references.XRefActionNone, nil
|
||||
}
|
||||
|
||||
// Accept close/reopening actions only if the poster is able to close the
|
||||
// referenced issue manually at this moment. The only exception is
|
||||
// the poster of a new PR referencing an issue on the same repo: then the merger
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/label"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -126,7 +127,7 @@ func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
|
||||
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
|
||||
RepoIDs: []int64{repoID},
|
||||
LabelIDs: []int64{labelID},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
|
||||
for _, count := range counts {
|
||||
|
@ -6,10 +6,12 @@ package issues
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -47,8 +49,8 @@ type Milestone struct {
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
Repo *repo_model.Repository `xorm:"-"`
|
||||
Name string
|
||||
Content string `xorm:"TEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
Content string `xorm:"TEXT"`
|
||||
RenderedContent template.HTML `xorm:"-"`
|
||||
IsClosed bool
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
@ -301,7 +303,7 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
|
||||
}
|
||||
numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -28,7 +28,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
||||
type FindMilestoneOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
IsClosed util.OptionalBool
|
||||
IsClosed optional.Option[bool]
|
||||
Name string
|
||||
SortType string
|
||||
RepoCond builder.Cond
|
||||
@ -40,8 +40,8 @@ func (opts FindMilestoneOptions) ToConds() builder.Cond {
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.IsClosed != util.OptionalBoolNone {
|
||||
cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.IsTrue()})
|
||||
if opts.IsClosed.Has() {
|
||||
cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
|
||||
}
|
||||
if opts.RepoCond != nil && opts.RepoCond.IsValid() {
|
||||
cond = cond.And(builder.In("repo_id", builder.Select("id").From("repository").Where(opts.RepoCond)))
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -39,10 +39,10 @@ func TestGetMilestoneByRepoID(t *testing.T) {
|
||||
func TestGetMilestonesByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
test := func(repoID int64, state api.StateType) {
|
||||
var isClosed util.OptionalBool
|
||||
var isClosed optional.Option[bool]
|
||||
switch state {
|
||||
case api.StateClosed, api.StateOpen:
|
||||
isClosed = util.OptionalBoolOf(state == api.StateClosed)
|
||||
isClosed = optional.Some(state == api.StateClosed)
|
||||
}
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
@ -84,7 +84,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
|
||||
|
||||
milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, milestones, 0)
|
||||
@ -101,7 +101,7 @@ func TestGetMilestones(t *testing.T) {
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
IsClosed: optional.Some(false),
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@ -118,7 +118,7 @@ func TestGetMilestones(t *testing.T) {
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoID: repo.ID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
Name: "",
|
||||
SortType: sortType,
|
||||
})
|
||||
@ -178,7 +178,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: repoID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo.NumClosedMilestones, count)
|
||||
@ -189,7 +189,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
|
||||
|
||||
count, err := db.Count[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: unittest.NonexistentID,
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, count)
|
||||
@ -206,7 +206,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
|
||||
|
||||
openCounts, err := issues_model.CountMilestonesMap(db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoIDs: []int64{1, 2},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo1OpenCount, openCounts[1])
|
||||
@ -215,7 +215,7 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
|
||||
closedCounts, err := issues_model.CountMilestonesMap(db.DefaultContext,
|
||||
issues_model.FindMilestoneOptions{
|
||||
RepoIDs: []int64{1, 2},
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, repo1ClosedCount, closedCounts[1])
|
||||
@ -234,7 +234,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoIDs: []int64{repo1.ID, repo2.ID},
|
||||
IsClosed: util.OptionalBoolFalse,
|
||||
IsClosed: optional.Some(false),
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@ -252,7 +252,7 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoIDs: []int64{repo1.ID, repo2.ID},
|
||||
IsClosed: util.OptionalBoolTrue,
|
||||
IsClosed: optional.Some(true),
|
||||
SortType: sortType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -901,12 +901,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullReque
|
||||
}
|
||||
defer repo.Close()
|
||||
|
||||
branch, err := repo.GetDefaultBranch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commit, err := repo.GetBranchCommit(branch)
|
||||
commit, err := repo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -240,25 +240,6 @@ func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
|
||||
return reaction, nil
|
||||
}
|
||||
|
||||
// CreateIssueReaction creates a reaction on issue.
|
||||
func CreateIssueReaction(ctx context.Context, doerID, issueID int64, content string) (*Reaction, error) {
|
||||
return CreateReaction(ctx, &ReactionOptions{
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateCommentReaction creates a reaction on comment.
|
||||
func CreateCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) (*Reaction, error) {
|
||||
return CreateReaction(ctx, &ReactionOptions{
|
||||
Type: content,
|
||||
DoerID: doerID,
|
||||
IssueID: issueID,
|
||||
CommentID: commentID,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteReaction deletes reaction for issue or comment.
|
||||
func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
|
||||
reaction := &Reaction{
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@ -68,7 +68,7 @@ type FindReviewOptions struct {
|
||||
IssueID int64
|
||||
ReviewerID int64
|
||||
OfficialOnly bool
|
||||
Dismissed util.OptionalBool
|
||||
Dismissed optional.Option[bool]
|
||||
}
|
||||
|
||||
func (opts *FindReviewOptions) toCond() builder.Cond {
|
||||
@ -85,8 +85,8 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
|
||||
if opts.OfficialOnly {
|
||||
cond = cond.And(builder.Eq{"official": true})
|
||||
}
|
||||
if !opts.Dismissed.IsNone() {
|
||||
cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.IsTrue()})
|
||||
if opts.Dismissed.Has() {
|
||||
cond = cond.And(builder.Eq{"dismissed": opts.Dismissed.Value()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -340,7 +341,7 @@ func GetTrackedTimeByID(ctx context.Context, id int64) (*TrackedTime, error) {
|
||||
}
|
||||
|
||||
// GetIssueTotalTrackedTime returns the total tracked time for issues by given conditions.
|
||||
func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool) (int64, error) {
|
||||
func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool]) (int64, error) {
|
||||
if len(opts.IssueIDs) <= MaxQueryParameters {
|
||||
return getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs)
|
||||
}
|
||||
@ -363,7 +364,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
|
||||
return accum, nil
|
||||
}
|
||||
|
||||
func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed util.OptionalBool, issueIDs []int64) (int64, error) {
|
||||
func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isClosed optional.Option[bool], issueIDs []int64) (int64, error) {
|
||||
sumSession := func(opts *IssuesOptions, issueIDs []int64) *xorm.Session {
|
||||
sess := db.GetEngine(ctx).
|
||||
Table("tracked_time").
|
||||
@ -378,8 +379,8 @@ func getIssueTotalTrackedTimeChunk(ctx context.Context, opts *IssuesOptions, isC
|
||||
}
|
||||
|
||||
session := sumSession(opts, issueIDs)
|
||||
if !isClosed.IsNone() {
|
||||
session = session.And("issue.is_closed = ?", isClosed.IsTrue())
|
||||
if isClosed.Has() {
|
||||
session = session.And("issue.is_closed = ?", isClosed.Value())
|
||||
}
|
||||
return session.SumInt(new(trackedTime), "tracked_time.time")
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -120,15 +120,15 @@ func TestTotalTimesForEachUser(t *testing.T) {
|
||||
func TestGetIssueTotalTrackedTime(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolFalse)
|
||||
ttt, err := issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(false))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3682, ttt)
|
||||
|
||||
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolTrue)
|
||||
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.Some(true))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, ttt)
|
||||
|
||||
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, util.OptionalBoolNone)
|
||||
ttt, err = issues_model.GetIssueTotalTrackedTime(db.DefaultContext, &issues_model.IssuesOptions{MilestoneIDs: []int64{1}}, optional.None[bool]())
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3682, ttt)
|
||||
}
|
||||
|
@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) {
|
||||
"updated_unix",
|
||||
}
|
||||
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
|
||||
for i := range columns {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
if err := sess.Begin(); err != nil {
|
||||
sess.Close()
|
||||
@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) {
|
||||
return
|
||||
}
|
||||
for j := range columns[i+1:] {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
|
@ -0,0 +1,4 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
index: 1
|
@ -0,0 +1,11 @@
|
||||
-
|
||||
id: 1
|
||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
|
||||
issue_id: 1
|
||||
release_id: 0
|
||||
|
||||
-
|
||||
id: 2
|
||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
|
||||
issue_id: 0
|
||||
release_id: 1
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
context_hash: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,5 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
merge_base: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||
merged_commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
sha1: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_id: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,3 @@
|
||||
-
|
||||
id: 1
|
||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
@ -0,0 +1,4 @@
|
||||
-
|
||||
id: 1
|
||||
description: the badge
|
||||
image_url: https://gitea.com/myimage.png
|
@ -558,6 +558,14 @@ var migrations = []Migration{
|
||||
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
|
||||
// v286 -> v287
|
||||
NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
|
||||
// v287 -> v288
|
||||
NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
|
||||
// v288 -> v289
|
||||
NewMigration("Add user_blocking table", v1_22.AddUserBlockingTable),
|
||||
// v289 -> v290
|
||||
NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
|
||||
// v290 -> v291
|
||||
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var issueAttachments []*Attachment
|
||||
err := x.Where("issue_id > 0").Find(&issueAttachments)
|
||||
type NewAttachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
}
|
||||
|
||||
var issueAttachments []*NewAttachment
|
||||
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range issueAttachments {
|
||||
assert.Greater(t, attach.RepoID, 0)
|
||||
assert.Greater(t, attach.IssueID, 0)
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.IssueID, int64(0))
|
||||
var issue Issue
|
||||
has, err := x.ID(attach.IssueID).Get(&issue)
|
||||
assert.NoError(t, err)
|
||||
@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
assert.EqualValues(t, attach.RepoID, issue.RepoID)
|
||||
}
|
||||
|
||||
var releaseAttachments []*Attachment
|
||||
err = x.Where("release_id > 0").Find(&releaseAttachments)
|
||||
var releaseAttachments []*NewAttachment
|
||||
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range releaseAttachments {
|
||||
assert.Greater(t, attach.RepoID, 0)
|
||||
assert.Greater(t, attach.IssueID, 0)
|
||||
assert.Greater(t, attach.RepoID, int64(0))
|
||||
assert.Greater(t, attach.ReleaseID, int64(0))
|
||||
var release Release
|
||||
has, err := x.ID(attach.ReleaseID).Get(&release)
|
||||
assert.NoError(t, err)
|
||||
|
@ -4,7 +4,10 @@
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func AddCombinedIndexToIssueUser(x *xorm.Engine) error {
|
||||
@ -20,8 +23,18 @@ func AddCombinedIndexToIssueUser(x *xorm.Engine) error {
|
||||
return err
|
||||
}
|
||||
for _, issueUser := range duplicatedIssueUsers {
|
||||
if _, err := x.Exec("delete from issue_user where id in (SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?)", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1); err != nil {
|
||||
return err
|
||||
if x.Dialect().URI().DBType == schemas.MSSQL {
|
||||
if _, err := x.Exec(fmt.Sprintf("delete from issue_user where id in (SELECT top %d id FROM issue_user WHERE issue_id = ? and uid = ?)", issueUser.Cnt-1), issueUser.IssueID, issueUser.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var ids []int64
|
||||
if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,9 +36,9 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
|
||||
if setting.Database.Type.IsMSSQL() {
|
||||
// drop indexes that need to be re-created afterwards
|
||||
droppedIndexes := []string{
|
||||
"DROP INDEX commit_status.IDX_commit_status_context_hash",
|
||||
"DROP INDEX review_state.UQE_review_state_pull_commit_user",
|
||||
"DROP INDEX repo_archiver.UQE_repo_archiver_s",
|
||||
"DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]",
|
||||
"DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]",
|
||||
"DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]",
|
||||
}
|
||||
for _, s := range droppedIndexes {
|
||||
_, err := db.Exec(s)
|
||||
@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
|
||||
} else if setting.Database.Type.IsMSSQL() {
|
||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` VARCHAR(64)", alts[0], alts[1]))
|
||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1]))
|
||||
} else {
|
||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1]))
|
||||
}
|
||||
|
@ -17,14 +17,72 @@ func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
}
|
||||
|
||||
type CommitStatus struct {
|
||||
ID int64
|
||||
ContextHash string
|
||||
}
|
||||
|
||||
type RepoArchiver struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Type int
|
||||
CommitID string
|
||||
}
|
||||
|
||||
type ReviewState struct {
|
||||
ID int64
|
||||
CommitSHA string
|
||||
UserID int64
|
||||
PullID int64
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int64
|
||||
CommitSHA string
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
ID int64
|
||||
CommitSHA string
|
||||
MergeBase string
|
||||
MergedCommitID string
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
ID int64
|
||||
Sha1 string
|
||||
}
|
||||
|
||||
type RepoIndexerStatus struct {
|
||||
ID int64
|
||||
CommitSHA string
|
||||
}
|
||||
|
||||
type Review struct {
|
||||
ID int64
|
||||
CommitID string
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
return base.PrepareTestEnv(t, 0, new(Repository))
|
||||
return base.PrepareTestEnv(t, 0,
|
||||
new(Repository),
|
||||
new(CommitStatus),
|
||||
new(RepoArchiver),
|
||||
new(ReviewState),
|
||||
new(Review),
|
||||
new(Comment),
|
||||
new(PullRequest),
|
||||
new(Release),
|
||||
new(RepoIndexerStatus),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_RepositoryFormat(t *testing.T) {
|
||||
x, deferable := PrepareOldRepository(t)
|
||||
defer deferable()
|
||||
|
||||
assert.NoError(t, AdjustDBForSha256(x))
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
ObjectFormatName string `xorg:"not null default('sha1')"`
|
||||
@ -37,12 +95,10 @@ func Test_RepositoryFormat(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, count)
|
||||
|
||||
assert.NoError(t, AdjustDBForSha256(x))
|
||||
|
||||
repo.ID = 20
|
||||
repo.ObjectFormatName = "sha256"
|
||||
_, err = x.Insert(repo)
|
||||
assert.NoError(t, err)
|
||||
id := repo.ID
|
||||
|
||||
count, err = x.Count(new(Repository))
|
||||
assert.NoError(t, err)
|
||||
@ -55,7 +111,7 @@ func Test_RepositoryFormat(t *testing.T) {
|
||||
assert.EqualValues(t, "sha1", repo.ObjectFormatName)
|
||||
|
||||
repo = new(Repository)
|
||||
ok, err = x.ID(20).Get(repo)
|
||||
ok, err = x.ID(id).Get(repo)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, true, ok)
|
||||
assert.EqualValues(t, "sha256", repo.ObjectFormatName)
|
||||
|
46
models/migrations/v1_22/v287.go
Normal file
46
models/migrations/v1_22/v287.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type BadgeUnique struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Slug string `xorm:"UNIQUE"`
|
||||
}
|
||||
|
||||
func (BadgeUnique) TableName() string {
|
||||
return "badge"
|
||||
}
|
||||
|
||||
func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error {
|
||||
type Badge struct {
|
||||
Slug string
|
||||
}
|
||||
|
||||
err := x.Sync(new(Badge))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sess.Sync(new(BadgeUnique))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
57
models/migrations/v1_22/v287_test.go
Normal file
57
models/migrations/v1_22/v287_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_UpdateBadgeColName(t *testing.T) {
|
||||
type Badge struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Description string
|
||||
ImageURL string
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := base.PrepareTestEnv(t, 0, new(Badge))
|
||||
defer deferable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
oldBadges := []*Badge{
|
||||
{Description: "Test Badge 1", ImageURL: "https://example.com/badge1.png"},
|
||||
{Description: "Test Badge 2", ImageURL: "https://example.com/badge2.png"},
|
||||
{Description: "Test Badge 3", ImageURL: "https://example.com/badge3.png"},
|
||||
}
|
||||
|
||||
for _, badge := range oldBadges {
|
||||
_, err := x.Insert(badge)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if err := UseSlugInsteadOfIDForBadges(x); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
got := []BadgeUnique{}
|
||||
if err := x.Table("badge").Asc("id").Find(&got); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
for i, e := range oldBadges {
|
||||
got := got[i+1] // 1 is in the badge.yml
|
||||
assert.Equal(t, e.ID, got.ID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", e.ID), got.Slug)
|
||||
}
|
||||
|
||||
// TODO: check if badges have been updated
|
||||
}
|
26
models/migrations/v1_22/v288.go
Normal file
26
models/migrations/v1_22/v288.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type Blocking struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
BlockerID int64 `xorm:"UNIQUE(block)"`
|
||||
BlockeeID int64 `xorm:"UNIQUE(block)"`
|
||||
Note string
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
}
|
||||
|
||||
func (*Blocking) TableName() string {
|
||||
return "user_blocking"
|
||||
}
|
||||
|
||||
func AddUserBlockingTable(x *xorm.Engine) error {
|
||||
return x.Sync(&Blocking{})
|
||||
}
|
18
models/migrations/v1_22/v289.go
Normal file
18
models/migrations/v1_22/v289.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddDefaultWikiBranch(x *xorm.Engine) error {
|
||||
type Repository struct {
|
||||
ID int64
|
||||
DefaultWikiBranch string
|
||||
}
|
||||
if err := x.Sync(&Repository{}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := x.Exec("UPDATE `repository` SET default_wiki_branch = 'master' WHERE (default_wiki_branch IS NULL) OR (default_wiki_branch = '')")
|
||||
return err
|
||||
}
|
17
models/migrations/v1_22/v290.go
Normal file
17
models/migrations/v1_22/v290.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type HookTask struct {
|
||||
PayloadVersion int `xorm:"DEFAULT 1"`
|
||||
}
|
||||
|
||||
func AddPayloadVersionToHookTaskTable(x *xorm.Engine) error {
|
||||
// create missing column
|
||||
return x.Sync(new(HookTask))
|
||||
}
|
@ -12,15 +12,16 @@ import (
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
)
|
||||
|
||||
// RemoveOrgUser removes user from given organization.
|
||||
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *user_model.User) error {
|
||||
ou := new(organization.OrgUser)
|
||||
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("uid=?", userID).
|
||||
And("org_id=?", orgID).
|
||||
Where("uid=?", user.ID).
|
||||
And("org_id=?", org.ID).
|
||||
Get(ou)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get org-user: %w", err)
|
||||
@ -28,13 +29,8 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserByID [%d]: %w", orgID, err)
|
||||
}
|
||||
|
||||
// Check if the user to delete is the last member in owner team.
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, orgID, userID); err != nil {
|
||||
if isOwner, err := organization.IsOrganizationOwner(ctx, org.ID, user.ID); err != nil {
|
||||
return err
|
||||
} else if isOwner {
|
||||
t, err := organization.GetOwnerTeam(ctx, org.ID)
|
||||
@ -45,8 +41,8 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if t.Members[0].ID == userID {
|
||||
return organization.ErrLastOrgOwner{UID: userID}
|
||||
if t.Members[0].ID == user.ID {
|
||||
return organization.ErrLastOrgOwner{UID: user.ID}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,28 +55,32 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
|
||||
if _, err := db.DeleteByID[organization.OrgUser](ctx, ou.ID); err != nil {
|
||||
return err
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", org.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all repository accesses and unwatch them.
|
||||
env, err := organization.AccessibleReposEnv(ctx, org, userID)
|
||||
env, err := organization.AccessibleReposEnv(ctx, org, user.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AccessibleReposEnv: %w", err)
|
||||
}
|
||||
repoIDs, err := env.RepoIDs(1, org.NumRepos)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserRepositories [%d]: %w", userID, err)
|
||||
return fmt.Errorf("GetUserRepositories [%d]: %w", user.ID, err)
|
||||
}
|
||||
for _, repoID := range repoIDs {
|
||||
if err = repo_model.WatchRepo(ctx, userID, repoID, false); err != nil {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
if _, err = db.GetEngine(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Where("user_id = ?", user.ID).
|
||||
In("repo_id", repoIDs).
|
||||
Delete(new(access_model.Access)); err != nil {
|
||||
return err
|
||||
@ -88,12 +88,12 @@ func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
|
||||
}
|
||||
|
||||
// Delete member in their teams.
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, userID)
|
||||
teams, err := organization.GetUserOrgTeams(ctx, org.ID, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range teams {
|
||||
if err = removeTeamMember(ctx, t, userID); err != nil {
|
||||
if err = removeTeamMember(ctx, t, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func AddRepository(ctx context.Context, t *organization.Team, repo *repo_model.R
|
||||
return fmt.Errorf("getMembers: %w", err)
|
||||
}
|
||||
for _, u := range t.Members {
|
||||
if err = repo_model.WatchRepo(ctx, u.ID, repo.ID, true); err != nil {
|
||||
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil {
|
||||
return fmt.Errorf("watchRepo: %w", err)
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
|
||||
continue
|
||||
}
|
||||
|
||||
if err = repo_model.WatchRepo(ctx, user.ID, repo.ID, false); err != nil {
|
||||
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -341,7 +341,7 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
}
|
||||
|
||||
for _, tm := range t.Members {
|
||||
if err := removeInvalidOrgUser(ctx, tm.ID, t.OrgID); err != nil {
|
||||
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -356,19 +356,23 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
|
||||
// AddTeamMember adds new membership of given team to given organization,
|
||||
// the user will have membership to given organization automatically when needed.
|
||||
func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
func AddTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
if user_model.IsUserBlockedBy(ctx, user, team.OrgID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
|
||||
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || isAlreadyMember {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := organization.AddOrgUser(ctx, team.OrgID, userID); err != nil {
|
||||
if err := organization.AddOrgUser(ctx, team.OrgID, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// check in transaction
|
||||
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || isAlreadyMember {
|
||||
return err
|
||||
}
|
||||
@ -376,7 +380,7 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if err := db.Insert(ctx, &organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: team.OrgID,
|
||||
TeamID: team.ID,
|
||||
}); err != nil {
|
||||
@ -392,7 +396,7 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
subQuery := builder.Select("repo_id").From("team_repo").
|
||||
Where(builder.Eq{"team_id": team.ID})
|
||||
|
||||
if _, err := sess.Where("user_id=?", userID).
|
||||
if _, err := sess.Where("user_id=?", user.ID).
|
||||
In("repo_id", subQuery).
|
||||
And("mode < ?", team.AccessMode).
|
||||
SetExpr("mode", team.AccessMode).
|
||||
@ -402,14 +406,14 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
|
||||
// for not exist access
|
||||
var repoIDs []int64
|
||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
|
||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID})
|
||||
if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
|
||||
return fmt.Errorf("select id accesses: %w", err)
|
||||
}
|
||||
|
||||
accesses := make([]*access_model.Access, 0, 100)
|
||||
for i, repoID := range repoIDs {
|
||||
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
|
||||
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode})
|
||||
if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
|
||||
if err = db.Insert(ctx, accesses); err != nil {
|
||||
return fmt.Errorf("insert new user accesses: %w", err)
|
||||
@ -430,10 +434,11 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
if err := team.LoadRepositories(ctx); err != nil {
|
||||
log.Error("team.LoadRepositories failed: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment
|
||||
go func(repos []*repo_model.Repository) {
|
||||
for _, repo := range repos {
|
||||
if err = repo_model.WatchRepo(db.DefaultContext, userID, repo.ID, true); err != nil {
|
||||
if err = repo_model.WatchRepo(db.DefaultContext, user, repo, true); err != nil {
|
||||
log.Error("watch repo failed: %v", err)
|
||||
}
|
||||
}
|
||||
@ -443,16 +448,16 @@ func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
func removeTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
e := db.GetEngine(ctx)
|
||||
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
|
||||
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||
if err != nil || !isMember {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the user to delete is the last member in owner team.
|
||||
if team.IsOwnerTeam() && team.NumMembers == 1 {
|
||||
return organization.ErrLastOrgOwner{UID: userID}
|
||||
return organization.ErrLastOrgOwner{UID: user.ID}
|
||||
}
|
||||
|
||||
team.NumMembers--
|
||||
@ -462,7 +467,7 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
|
||||
}
|
||||
|
||||
if _, err := e.Delete(&organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: team.OrgID,
|
||||
TeamID: team.ID,
|
||||
}); err != nil {
|
||||
@ -476,76 +481,76 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
|
||||
|
||||
// Delete access to team repositories.
|
||||
for _, repo := range team.Repos {
|
||||
if err := access_model.RecalculateUserAccess(ctx, repo, userID); err != nil {
|
||||
if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove watches from now unaccessible
|
||||
if err := ReconsiderWatches(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderWatches(ctx, repo, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove issue assignments from now unaccessible
|
||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
|
||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return removeInvalidOrgUser(ctx, userID, team.OrgID)
|
||||
return removeInvalidOrgUser(ctx, team.OrgID, user)
|
||||
}
|
||||
|
||||
func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error {
|
||||
func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.User) error {
|
||||
// Check if the user is a member of any team in the organization.
|
||||
if count, err := db.GetEngine(ctx).Count(&organization.TeamUser{
|
||||
UID: userID,
|
||||
UID: user.ID,
|
||||
OrgID: orgID,
|
||||
}); err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return RemoveOrgUser(ctx, orgID, userID)
|
||||
org, err := organization.GetOrgByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RemoveOrgUser(ctx, org, user)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTeamMember removes member from given team of given organization.
|
||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
|
||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
if err := removeTeamMember(ctx, team, userID); err != nil {
|
||||
if err := removeTeamMember(ctx, team, user); err != nil {
|
||||
return err
|
||||
}
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
user, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": user.ID}).
|
||||
In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
|
||||
Delete(&issues_model.IssueAssignees{}); err != nil {
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
|
||||
return fmt.Errorf("Could not delete assignee[%d] %w", user.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
|
||||
if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
|
||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||
if has, err := access_model.HasAccess(ctx, user.ID, repo); err != nil || has {
|
||||
return err
|
||||
}
|
||||
if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
|
||||
if err := repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove all IssueWatches a user has subscribed to in the repository
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
|
||||
return issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID)
|
||||
}
|
||||
|
@ -21,33 +21,42 @@ import (
|
||||
func TestTeam_AddMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
|
||||
test := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
||||
}
|
||||
test(1, 2)
|
||||
test(1, 4)
|
||||
test(3, 2)
|
||||
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
test(team1, user2)
|
||||
test(team1, user4)
|
||||
test(team3, user2)
|
||||
}
|
||||
|
||||
func TestTeam_RemoveMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testSuccess := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
|
||||
testSuccess := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
||||
}
|
||||
testSuccess(1, 4)
|
||||
testSuccess(2, 2)
|
||||
testSuccess(3, 2)
|
||||
testSuccess(3, unittest.NonexistentID)
|
||||
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
err := RemoveTeamMember(db.DefaultContext, team, 2)
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
testSuccess(team1, user4)
|
||||
testSuccess(team2, user2)
|
||||
testSuccess(team3, user2)
|
||||
|
||||
err := RemoveTeamMember(db.DefaultContext, team1, user2)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
}
|
||||
|
||||
@ -120,33 +129,42 @@ func TestDeleteTeam(t *testing.T) {
|
||||
func TestAddTeamMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
test := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
|
||||
test := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
||||
}
|
||||
test(1, 2)
|
||||
test(1, 4)
|
||||
test(3, 2)
|
||||
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
test(team1, user2)
|
||||
test(team1, user4)
|
||||
test(team3, user2)
|
||||
}
|
||||
|
||||
func TestRemoveTeamMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
testSuccess := func(teamID, userID int64) {
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
|
||||
testSuccess := func(team *organization.Team, user *user_model.User) {
|
||||
assert.NoError(t, RemoveTeamMember(db.DefaultContext, team, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
||||
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
||||
}
|
||||
testSuccess(1, 4)
|
||||
testSuccess(2, 2)
|
||||
testSuccess(3, 2)
|
||||
testSuccess(3, unittest.NonexistentID)
|
||||
|
||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
err := RemoveTeamMember(db.DefaultContext, team, 2)
|
||||
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
||||
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
||||
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
testSuccess(team1, user4)
|
||||
testSuccess(team2, user2)
|
||||
testSuccess(team3, user2)
|
||||
|
||||
err := RemoveTeamMember(db.DefaultContext, team1, user2)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
}
|
||||
|
||||
@ -155,15 +173,15 @@ func TestRepository_RecalculateAccesses3(t *testing.T) {
|
||||
team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5})
|
||||
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
|
||||
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
// adding user29 to team5 should add an explicit access row for repo 23
|
||||
// even though repo 23 is public
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29.ID))
|
||||
assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29))
|
||||
|
||||
has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
|
||||
has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
@ -16,22 +16,27 @@ import (
|
||||
|
||||
func TestUser_RemoveMember(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
|
||||
// remove a user that is a member
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: user4.ID, OrgID: org.ID})
|
||||
prevNumMembers := org.NumMembers
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 4))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user4))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user4.ID, OrgID: org.ID})
|
||||
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.Equal(t, prevNumMembers-1, org.NumMembers)
|
||||
|
||||
// remove a user that is not a member
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3})
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user5.ID, OrgID: org.ID})
|
||||
prevNumMembers = org.NumMembers
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org.ID, 5))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user5))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: user5.ID, OrgID: org.ID})
|
||||
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.Equal(t, prevNumMembers, org.NumMembers)
|
||||
|
||||
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
|
||||
@ -39,23 +44,31 @@ func TestUser_RemoveMember(t *testing.T) {
|
||||
|
||||
func TestRemoveOrgUser(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
testSuccess := func(orgID, userID int64) {
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
|
||||
|
||||
testSuccess := func(org *organization.Organization, user *user_model.User) {
|
||||
expectedNumMembers := org.NumMembers
|
||||
if unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) {
|
||||
if unittest.BeanExists(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) {
|
||||
expectedNumMembers--
|
||||
}
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, orgID, userID))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: orgID, UID: userID})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
|
||||
assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user))
|
||||
unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID})
|
||||
org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID})
|
||||
assert.EqualValues(t, expectedNumMembers, org.NumMembers)
|
||||
}
|
||||
testSuccess(3, 4)
|
||||
testSuccess(3, 4)
|
||||
|
||||
err := RemoveOrgUser(db.DefaultContext, 7, 5)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
org7 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 7})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
|
||||
testSuccess(org3, user4)
|
||||
|
||||
org3 = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
testSuccess(org3, user4)
|
||||
|
||||
err := RemoveOrgUser(db.DefaultContext, org7, user5)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: 7, UID: 5})
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: org7.ID, UID: user5.ID})
|
||||
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
|
||||
}
|
||||
|
@ -400,6 +400,7 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
|
||||
&TeamUnit{OrgID: org.ID},
|
||||
&TeamInvite{OrgID: org.ID},
|
||||
&secret_model.Secret{OwnerID: org.ID},
|
||||
&user_model.Blocking{BlockerID: org.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("DeleteBeans: %w", err)
|
||||
}
|
||||
|
@ -30,14 +30,6 @@ func IsTeamMember(ctx context.Context, orgID, teamID, userID int64) (bool, error
|
||||
Exist()
|
||||
}
|
||||
|
||||
// GetTeamUsersByTeamID returns team users for a team
|
||||
func GetTeamUsersByTeamID(ctx context.Context, teamID int64) ([]*TeamUser, error) {
|
||||
teamUsers := make([]*TeamUser, 0, 10)
|
||||
return teamUsers, db.GetEngine(ctx).
|
||||
Where("team_id=?", teamID).
|
||||
Find(&teamUsers)
|
||||
}
|
||||
|
||||
// SearchMembersOptions holds the search options
|
||||
type SearchMembersOptions struct {
|
||||
db.ListOptions
|
||||
|
@ -70,16 +70,26 @@ type PackageFileDescriptor struct {
|
||||
Properties PackagePropertyList
|
||||
}
|
||||
|
||||
// PackageWebLink returns the package web link
|
||||
// PackageWebLink returns the relative package web link
|
||||
func (pd *PackageDescriptor) PackageWebLink() string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// FullWebLink returns the package version web link
|
||||
func (pd *PackageDescriptor) FullWebLink() string {
|
||||
// VersionWebLink returns the relative package version web link
|
||||
func (pd *PackageDescriptor) VersionWebLink() string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// PackageHTMLURL returns the absolute package HTML URL
|
||||
func (pd *PackageDescriptor) PackageHTMLURL() string {
|
||||
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
||||
}
|
||||
|
||||
// VersionHTMLURL returns the absolute package version HTML URL
|
||||
func (pd *PackageDescriptor) VersionHTMLURL() string {
|
||||
return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion))
|
||||
}
|
||||
|
||||
// CalculateBlobSize returns the total blobs size in bytes
|
||||
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
|
||||
size := int64(0)
|
||||
|
@ -55,7 +55,7 @@ func CountPackages(ctx context.Context, opts *packages_model.PackageSearchOption
|
||||
|
||||
func toConds(opts *packages_model.PackageSearchOptions) builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"package.is_internal": opts.IsInternal.IsTrue(),
|
||||
"package.is_internal": opts.IsInternal.Value(),
|
||||
"package.owner_id": opts.OwnerID,
|
||||
"package.type": packages_model.TypeNuGet,
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -105,7 +106,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
|
||||
ExactMatch: true,
|
||||
Value: version,
|
||||
},
|
||||
IsInternal: util.OptionalBoolOf(isInternal),
|
||||
IsInternal: optional.Some(isInternal),
|
||||
Paginator: db.NewAbsoluteListOptions(0, 1),
|
||||
})
|
||||
if err != nil {
|
||||
@ -122,7 +123,7 @@ func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Ty
|
||||
pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
Type: packageType,
|
||||
IsInternal: util.OptionalBoolFalse,
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
return pvs, err
|
||||
}
|
||||
@ -136,7 +137,7 @@ func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Ty
|
||||
ExactMatch: true,
|
||||
Value: name,
|
||||
},
|
||||
IsInternal: util.OptionalBoolFalse,
|
||||
IsInternal: optional.Some(false),
|
||||
})
|
||||
return pvs, err
|
||||
}
|
||||
@ -182,18 +183,18 @@ type PackageSearchOptions struct {
|
||||
Name SearchValue // only results with the specific name are found
|
||||
Version SearchValue // only results with the specific version are found
|
||||
Properties map[string]string // only results are found which contain all listed version properties with the specific value
|
||||
IsInternal util.OptionalBool
|
||||
HasFileWithName string // only results are found which are associated with a file with the specific name
|
||||
HasFiles util.OptionalBool // only results are found which have associated files
|
||||
IsInternal optional.Option[bool]
|
||||
HasFileWithName string // only results are found which are associated with a file with the specific name
|
||||
HasFiles optional.Option[bool] // only results are found which have associated files
|
||||
Sort VersionSort
|
||||
db.Paginator
|
||||
}
|
||||
|
||||
func (opts *PackageSearchOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if !opts.IsInternal.IsNone() {
|
||||
if opts.IsInternal.Has() {
|
||||
cond = builder.Eq{
|
||||
"package_version.is_internal": opts.IsInternal.IsTrue(),
|
||||
"package_version.is_internal": opts.IsInternal.Value(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,10 +251,10 @@ func (opts *PackageSearchOptions) ToConds() builder.Cond {
|
||||
cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
|
||||
}
|
||||
|
||||
if !opts.HasFiles.IsNone() {
|
||||
if opts.HasFiles.Has() {
|
||||
filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
|
||||
|
||||
if opts.HasFiles.IsFalse() {
|
||||
if !opts.HasFiles.Value() {
|
||||
filesCond = builder.Not{filesCond}
|
||||
}
|
||||
|
||||
@ -307,8 +308,8 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
|
||||
And(builder.Expr("pv2.id IS NULL"))
|
||||
|
||||
joinCond := builder.Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))")
|
||||
if !opts.IsInternal.IsNone() {
|
||||
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.IsTrue()})
|
||||
if opts.IsInternal.Has() {
|
||||
joinCond = joinCond.And(builder.Eq{"pv2.is_internal": opts.IsInternal.Value()})
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).
|
||||
|
@ -128,9 +128,9 @@ func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap
|
||||
|
||||
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
|
||||
func refreshCollaboratorAccesses(ctx context.Context, repoID int64, accessMap map[int64]*userAccess) error {
|
||||
collaborators, err := repo_model.GetCollaborators(ctx, repoID, db.ListOptions{})
|
||||
collaborators, _, err := repo_model.GetCollaborators(ctx, &repo_model.FindCollaborationOptions{RepoID: repoID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getCollaborations: %w", err)
|
||||
return fmt.Errorf("GetCollaborators: %w", err)
|
||||
}
|
||||
for _, c := range collaborators {
|
||||
if c.User.IsGhost() {
|
||||
|
@ -232,7 +232,7 @@ func UpdateBoard(ctx context.Context, board *Board) error {
|
||||
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||
boards := make([]*Board, 0, 5)
|
||||
|
||||
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("Sorting").Find(&boards); err != nil {
|
||||
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,13 @@ package project
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -100,7 +102,7 @@ type Project struct {
|
||||
CardType CardType
|
||||
Type Type
|
||||
|
||||
RenderedContent string `xorm:"-"`
|
||||
RenderedContent template.HTML `xorm:"-"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
@ -195,7 +197,7 @@ type SearchOptions struct {
|
||||
db.ListOptions
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
IsClosed util.OptionalBool
|
||||
IsClosed optional.Option[bool]
|
||||
OrderBy db.SearchOrderBy
|
||||
Type Type
|
||||
Title string
|
||||
@ -206,11 +208,8 @@ func (opts SearchOptions) ToConds() builder.Cond {
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
switch opts.IsClosed {
|
||||
case util.OptionalBoolTrue:
|
||||
cond = cond.And(builder.Eq{"is_closed": true})
|
||||
case util.OptionalBoolFalse:
|
||||
cond = cond.And(builder.Eq{"is_closed": false})
|
||||
if opts.IsClosed.Has() {
|
||||
cond = cond.And(builder.Eq{"is_closed": opts.IsClosed.Value()})
|
||||
}
|
||||
|
||||
if opts.Type > 0 {
|
||||
|
@ -36,14 +36,44 @@ type Collaborator struct {
|
||||
Collaboration *Collaboration
|
||||
}
|
||||
|
||||
type FindCollaborationOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
RepoOwnerID int64
|
||||
CollaboratorID int64
|
||||
}
|
||||
|
||||
func (opts *FindCollaborationOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"collaboration.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.RepoOwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"repository.owner_id": opts.RepoOwnerID})
|
||||
}
|
||||
if opts.CollaboratorID != 0 {
|
||||
cond = cond.And(builder.Eq{"collaboration.user_id": opts.CollaboratorID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (opts *FindCollaborationOptions) ToJoins() []db.JoinFunc {
|
||||
if opts.RepoOwnerID != 0 {
|
||||
return []db.JoinFunc{
|
||||
func(e db.Engine) error {
|
||||
e.Join("INNER", "repository", "repository.id = collaboration.repo_id")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCollaborators returns the collaborators for a repository
|
||||
func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
|
||||
collaborations, err := db.Find[Collaboration](ctx, FindCollaborationOptions{
|
||||
ListOptions: listOptions,
|
||||
RepoID: repoID,
|
||||
})
|
||||
func GetCollaborators(ctx context.Context, opts *FindCollaborationOptions) ([]*Collaborator, int64, error) {
|
||||
collaborations, total, err := db.FindAndCount[Collaboration](ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db.Find[Collaboration]: %w", err)
|
||||
return nil, 0, fmt.Errorf("db.FindAndCount[Collaboration]: %w", err)
|
||||
}
|
||||
|
||||
collaborators := make([]*Collaborator, 0, len(collaborations))
|
||||
@ -54,7 +84,7 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti
|
||||
|
||||
usersMap := make(map[int64]*user_model.User)
|
||||
if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil {
|
||||
return nil, fmt.Errorf("Find users map by user ids: %w", err)
|
||||
return nil, 0, fmt.Errorf("Find users map by user ids: %w", err)
|
||||
}
|
||||
|
||||
for _, c := range collaborations {
|
||||
@ -67,7 +97,7 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti
|
||||
Collaboration: c,
|
||||
})
|
||||
}
|
||||
return collaborators, nil
|
||||
return collaborators, total, nil
|
||||
}
|
||||
|
||||
// GetCollaboration get collaboration for a repository id with a user id
|
||||
@ -88,15 +118,6 @@ func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
|
||||
}
|
||||
|
||||
type FindCollaborationOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func (opts FindCollaborationOptions) ToConds() builder.Cond {
|
||||
return builder.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
|
||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
|
||||
func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid int64, mode perm.AccessMode) error {
|
||||
// Discard invalid input
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user