1
0
Fork 0

Merge pull request '[gitea] week 13 cherry-pick' (#2769) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2769
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-03-26 18:45:57 +00:00
commit 8339b24d61
302 changed files with 2569 additions and 5061 deletions

View file

@ -8,6 +8,15 @@ delay = 1000
include_ext = ["go", "tmpl"] include_ext = ["go", "tmpl"]
include_file = ["main.go"] include_file = ["main.go"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"] exclude_dir = [
"models/fixtures",
"models/migrations/fixtures",
"modules/avatar/identicon/testdata",
"modules/avatar/testdata",
"modules/git/tests",
"modules/migration/file_format_testdata",
"routers/private/tests",
"services/gitdiff/testdata",
]
exclude_regex = ["_test.go$", "_gen.go$"] exclude_regex = ["_test.go$", "_gen.go$"]
stop_on_error = true stop_on_error = true

View file

@ -4,7 +4,7 @@
"features": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version":"20" "version": "20"
}, },
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {}, "ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
@ -24,7 +24,7 @@
"DavidAnson.vscode-markdownlint", "DavidAnson.vscode-markdownlint",
"Vue.volar", "Vue.volar",
"ms-azuretools.vscode-docker", "ms-azuretools.vscode-docker",
"zixuanchen.vitest-explorer", "vitest.explorer",
"qwtel.sqlite-viewer", "qwtel.sqlite-viewer",
"GitHub.vscode-pull-request-github" "GitHub.vscode-pull-request-github"
] ]

View file

@ -62,7 +62,6 @@ cpu.out
/data /data
/indexers /indexers
/log /log
/public/img/avatar
/tests/integration/gitea-integration-* /tests/integration/gitea-integration-*
/tests/integration/indexers-* /tests/integration/indexers-*
/tests/e2e/gitea-e2e-* /tests/e2e/gitea-e2e-*
@ -77,6 +76,7 @@ cpu.out
/public/assets/js /public/assets/js
/public/assets/css /public/assets/css
/public/assets/fonts /public/assets/fonts
/public/assets/img/avatar
/public/assets/img/webpack /public/assets/img/webpack
/vendor /vendor
/web_src/fomantic/node_modules /web_src/fomantic/node_modules

View file

@ -42,10 +42,6 @@ overrides:
worker: true worker: true
rules: rules:
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top]
- files: ["build/generate-images.js"]
rules:
i/no-unresolved: [0]
i/no-extraneous-dependencies: [0]
- files: ["*.config.*"] - files: ["*.config.*"]
rules: rules:
i/no-unused-modules: [0] i/no-unused-modules: [0]
@ -123,7 +119,7 @@ rules:
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}] "@stylistic/js/arrow-spacing": [2, {before: true, after: true}]
"@stylistic/js/block-spacing": [0] "@stylistic/js/block-spacing": [0]
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}] "@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}]
"@stylistic/js/comma-dangle": [2, only-multiline] "@stylistic/js/comma-dangle": [2, always-multiline]
"@stylistic/js/comma-spacing": [2, {before: false, after: true}] "@stylistic/js/comma-spacing": [2, {before: false, after: true}]
"@stylistic/js/comma-style": [2, last] "@stylistic/js/comma-style": [2, last]
"@stylistic/js/computed-property-spacing": [2, never] "@stylistic/js/computed-property-spacing": [2, never]
@ -290,7 +286,7 @@ rules:
jquery/no-class: [0] jquery/no-class: [0]
jquery/no-clone: [2] jquery/no-clone: [2]
jquery/no-closest: [0] jquery/no-closest: [0]
jquery/no-css: [0] jquery/no-css: [2]
jquery/no-data: [0] jquery/no-data: [0]
jquery/no-deferred: [2] jquery/no-deferred: [2]
jquery/no-delegate: [2] jquery/no-delegate: [2]
@ -413,7 +409,7 @@ rules:
no-jquery/no-constructor-attributes: [2] no-jquery/no-constructor-attributes: [2]
no-jquery/no-contains: [2] no-jquery/no-contains: [2]
no-jquery/no-context-prop: [2] no-jquery/no-context-prop: [2]
no-jquery/no-css: [0] no-jquery/no-css: [2]
no-jquery/no-data: [0] no-jquery/no-data: [0]
no-jquery/no-deferred: [2] no-jquery/no-deferred: [2]
no-jquery/no-delegate: [2] no-jquery/no-delegate: [2]

2
.gitignore vendored
View file

@ -64,7 +64,7 @@ cpu.out
/data /data
/indexers /indexers
/log /log
/public/img/avatar /public/assets/img/avatar
/tests/integration/gitea-integration-* /tests/integration/gitea-integration-*
/tests/integration/indexers-* /tests/integration/indexers-*
/tests/e2e/gitea-e2e-* /tests/e2e/gitea-e2e-*

View file

@ -42,7 +42,7 @@ vscode:
- DavidAnson.vscode-markdownlint - DavidAnson.vscode-markdownlint
- Vue.volar - Vue.volar
- ms-azuretools.vscode-docker - ms-azuretools.vscode-docker
- zixuanchen.vitest-explorer - vitest.explorer
- qwtel.sqlite-viewer - qwtel.sqlite-viewer
- GitHub.vscode-pull-request-github - GitHub.vscode-pull-request-github

View file

@ -30,7 +30,7 @@ rules:
"@stylistic/block-opening-brace-newline-after": null "@stylistic/block-opening-brace-newline-after": null
"@stylistic/block-opening-brace-newline-before": null "@stylistic/block-opening-brace-newline-before": null
"@stylistic/block-opening-brace-space-after": null "@stylistic/block-opening-brace-space-after": null
"@stylistic/block-opening-brace-space-before": null "@stylistic/block-opening-brace-space-before": always
"@stylistic/color-hex-case": lower "@stylistic/color-hex-case": lower
"@stylistic/declaration-bang-space-after": never "@stylistic/declaration-bang-space-after": never
"@stylistic/declaration-bang-space-before": null "@stylistic/declaration-bang-space-before": null
@ -140,7 +140,7 @@ rules:
function-disallowed-list: null function-disallowed-list: null
function-linear-gradient-no-nonstandard-direction: true function-linear-gradient-no-nonstandard-direction: true
function-name-case: lower function-name-case: lower
function-no-unknown: null function-no-unknown: true
function-url-no-scheme-relative: null function-url-no-scheme-relative: null
function-url-quotes: always function-url-quotes: always
function-url-scheme-allowed-list: null function-url-scheme-allowed-list: null
@ -168,7 +168,7 @@ rules:
no-duplicate-selectors: true no-duplicate-selectors: true
no-empty-source: true no-empty-source: true
no-invalid-double-slash-comments: true no-invalid-double-slash-comments: true
no-invalid-position-at-import-rule: null no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]]
no-irregular-whitespace: true no-irregular-whitespace: true
no-unknown-animations: null no-unknown-animations: null
no-unknown-custom-properties: null no-unknown-custom-properties: null
@ -181,6 +181,7 @@ rules:
rule-empty-line-before: null rule-empty-line-before: null
rule-selector-property-disallowed-list: null rule-selector-property-disallowed-list: null
scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}] scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}]
selector-anb-no-unmatchable: true
selector-attribute-name-disallowed-list: null selector-attribute-name-disallowed-list: null
selector-attribute-operator-allowed-list: null selector-attribute-operator-allowed-list: null
selector-attribute-operator-disallowed-list: null selector-attribute-operator-disallowed-list: null

View file

@ -44,9 +44,6 @@ DOCKER_TAG ?= latest
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
GOPATH ?= $(shell $(GO) env GOPATH)
export PATH := $(GOPATH)/bin:$(PATH)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766 CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
endif endif
@ -148,6 +145,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.config.js tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
@ -396,19 +395,19 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules lint-js: node_modules
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js tests/e2e npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES)
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js tests/e2e --fix npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) --fix
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules lint-css: node_modules
npx stylelint --color --max-warnings=0 web_src/css web_src/js/components/*.vue npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix .PHONY: lint-css-fix
lint-css-fix: node_modules lint-css-fix: node_modules
npx stylelint --color --max-warnings=0 web_src/css web_src/js/components/*.vue --fix npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger .PHONY: lint-swagger
lint-swagger: node_modules lint-swagger: node_modules
@ -468,7 +467,7 @@ lint-yaml: .venv
.PHONY: watch .PHONY: watch
watch: watch:
@bash build/watch.sh @bash tools/watch.sh
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules watch-frontend: node-check node_modules
@ -962,7 +961,7 @@ $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
.PHONY: svg .PHONY: svg
svg: node-check | node_modules svg: node-check | node_modules
rm -rf $(SVG_DEST_DIR) rm -rf $(SVG_DEST_DIR)
node build/generate-svg.js node tools/generate-svg.js
.PHONY: svg-check .PHONY: svg-check
svg-check: svg svg-check: svg
@ -997,7 +996,7 @@ generate-gitignore:
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules
npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7 npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS) node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: generate-manpage:

View file

@ -37,7 +37,7 @@ gitea embedded list [--include-vendored] [patterns...]
- 列出所有模板文件,无论在哪个虚拟目录下:`**.tmpl` - 列出所有模板文件,无论在哪个虚拟目录下:`**.tmpl`
- 列出所有邮件模板文件:`templates/mail/**.tmpl` - 列出所有邮件模板文件:`templates/mail/**.tmpl`
- 列出 `public/img` 目录下的所有文件:`public/img/**` 列出 `public/assets/img` 目录下的所有文件:`public/assets/img/**`
不要忘记为模式使用引号,因为空格、`*` 和其他字符可能对命令行解释器有特殊含义。 不要忘记为模式使用引号,因为空格、`*` 和其他字符可能对命令行解释器有特殊含义。
@ -49,8 +49,8 @@ gitea embedded list [--include-vendored] [patterns...]
```sh ```sh
$ gitea embedded list '**openid**' $ gitea embedded list '**openid**'
public/img/auth/openid_connect.svg public/assets/img/auth/openid_connect.svg
public/img/openid-16x16.png public/assets/img/openid-16x16.png
templates/user/auth/finalize_openid.tmpl templates/user/auth/finalize_openid.tmpl
templates/user/auth/signin_openid.tmpl templates/user/auth/signin_openid.tmpl
templates/user/auth/signup_openid_connect.tmpl templates/user/auth/signup_openid_connect.tmpl

View file

@ -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. 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. 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. 11. Custom event names are recommended to use `ce-` prefix.
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`). 12. Prefer using Tailwind CSS which is available via `tw-` prefix, e.g. `tw-relative`. Gitea's helper CSS classes use `gt-` prefix (`gt-mono`), 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. 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 ### Accessibility / ARIA

View file

@ -47,7 +47,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。 9. 避免在 CSS 中使用不必要的`!important`,如果无法避免,添加注释解释为什么需要它。
10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。
11. 推荐使用自定义事件名称前缀`ce-`。 11. 推荐使用自定义事件名称前缀`ce-`。
12. 建议使用 Tailwind CSS它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-df`Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。 12. 建议使用 Tailwind CSS它可以通过 `tw-` 前缀获得,例如 `tw-relative`. Gitea 自身的助手类 CSS 使用 `gt-` 前缀(`gt-mono`Gitea 自身的私有框架级 CSS 类使用 `g-` 前缀(`g-modal-confirm`)。
13. 尽量避免内联脚本和样式建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免请解释无法避免的原因。 13. 尽量避免内联脚本和样式建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免请解释无法避免的原因。
### 可访问性 / ARIA ### 可访问性 / ARIA

View file

@ -214,7 +214,7 @@ REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
### Building and adding SVGs ### Building and adding SVGs
SVG icons are built using the `make svg` target which compiles the icon sources defined in `build/generate-svg.js` into the output directory `public/assets/img/svg`. Custom icons can be added in the `web_src/svg` directory. SVG icons are built using the `make svg` target which compiles the icon sources into the output directory `public/assets/img/svg`. Custom icons can be added in the `web_src/svg` directory.
### Building the Logo ### Building the Logo

View file

@ -201,7 +201,7 @@ REPO_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200
### 构建和添加 SVGs ### 构建和添加 SVGs
SVG 图标是使用 `make svg` 目标构建的,该目标将 `build/generate-svg.js` 中定义的图标源编译到输出目录 `public/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。 SVG 图标是使用 `make svg` 命令构建的,该命令将图标资源编译到输出目录 `public/assets/img/svg` 中。可以在 `web_src/svg` 目录中添加自定义图标。
### 构建 Logo ### 构建 Logo

View file

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
@ -841,3 +842,31 @@ func UpdateNotificationStatuses(ctx context.Context, user *user_model.User, curr
Update(n) Update(n)
return err return err
} }
// LoadIssuePullRequests loads all issues' pull requests if possible
func (nl NotificationList) LoadIssuePullRequests(ctx context.Context) error {
issues := make(map[int64]*issues_model.Issue, len(nl))
for _, notification := range nl {
if notification.Issue != nil && notification.Issue.IsPull && notification.Issue.PullRequest == nil {
issues[notification.Issue.ID] = notification.Issue
}
}
if len(issues) == 0 {
return nil
}
pulls, err := issues_model.GetPullRequestByIssueIDs(ctx, util.KeysOfMap(issues))
if err != nil {
return err
}
for _, pull := range pulls {
if issue := issues[pull.IssueID]; issue != nil {
issue.PullRequest = pull
issue.PullRequest.Issue = issue
}
}
return nil
}

View file

@ -198,6 +198,8 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
@ -207,11 +209,12 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
} }
_, err = t.WriteString(line + "\n") _, err = t.WriteString(line + "\n")
if err != nil { if err != nil {
f.Close()
return err return err
} }
} }
f.Close() if err = scanner.Err(); err != nil {
return fmt.Errorf("RegeneratePublicKeys scan: %w", err)
}
} }
return nil return nil
} }

View file

@ -120,6 +120,8 @@ func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
if err != nil { if err != nil {
return err return err
} }
defer f.Close()
scanner := bufio.NewScanner(f) scanner := bufio.NewScanner(f)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
@ -129,11 +131,12 @@ func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
} }
_, err = t.WriteString(line + "\n") _, err = t.WriteString(line + "\n")
if err != nil { if err != nil {
f.Close()
return err return err
} }
} }
f.Close() if err = scanner.Err(); err != nil {
return fmt.Errorf("regeneratePrincipalKeys scan: %w", err)
}
} }
return nil return nil
} }

View file

@ -24,7 +24,7 @@ import (
const ( const (
// DefaultAvatarClass is the default class of a rendered avatar // DefaultAvatarClass is the default class of a rendered avatar
DefaultAvatarClass = "ui avatar gt-vm" DefaultAvatarClass = "ui avatar tw-align-middle"
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar // DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
DefaultAvatarPixelSize = 28 DefaultAvatarPixelSize = 28
) )

View file

@ -194,20 +194,6 @@ func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool {
return issue.Repo.IsTimetrackerEnabled(ctx) return issue.Repo.IsTimetrackerEnabled(ctx)
} }
// GetPullRequest returns the issue pull request
func (issue *Issue) GetPullRequest(ctx context.Context) (pr *PullRequest, err error) {
if !issue.IsPull {
return nil, fmt.Errorf("Issue is not a pull request")
}
pr, err = GetPullRequestByIssueID(ctx, issue.ID)
if err != nil {
return nil, err
}
pr.Issue = issue
return pr, err
}
// LoadPoster loads poster // LoadPoster loads poster
func (issue *Issue) LoadPoster(ctx context.Context) (err error) { func (issue *Issue) LoadPoster(ctx context.Context) (err error) {
if issue.Poster == nil && issue.PosterID != 0 { if issue.Poster == nil && issue.PosterID != 0 {

View file

@ -370,6 +370,9 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
for _, issue := range issues { for _, issue := range issues {
issue.PullRequest = pullRequestMaps[issue.ID] issue.PullRequest = pullRequestMaps[issue.ID]
if issue.PullRequest != nil {
issue.PullRequest.Issue = issue
}
} }
return nil return nil
} }

View file

@ -11,7 +11,6 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -23,7 +22,7 @@ type PullRequestsOptions struct {
db.ListOptions db.ListOptions
State string State string
SortType string SortType string
Labels []string Labels []int64
MilestoneID int64 MilestoneID int64
} }
@ -36,11 +35,9 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
sess.And("issue.is_closed=?", opts.State == "closed") sess.And("issue.is_closed=?", opts.State == "closed")
} }
if labelIDs, err := base.StringsToInt64s(opts.Labels); err != nil { if len(opts.Labels) > 0 {
return nil, err
} else if len(labelIDs) > 0 {
sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id"). sess.Join("INNER", "issue_label", "issue.id = issue_label.issue_id").
In("issue_label.label_id", labelIDs) In("issue_label.label_id", opts.Labels)
} }
if opts.MilestoneID > 0 { if opts.MilestoneID > 0 {
@ -220,3 +217,12 @@ func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bo
Limit(1). Limit(1).
Get(new(Issue)) Get(new(Issue))
} }
// GetPullRequestByIssueIDs returns all pull requests by issue ids
func GetPullRequestByIssueIDs(ctx context.Context, issueIDs []int64) (PullRequestList, error) {
prs := make([]*PullRequest, 0, len(issueIDs))
return prs, db.GetEngine(ctx).
Where("issue_id > 0").
In("issue_id", issueIDs).
Find(&prs)
}

View file

@ -67,7 +67,6 @@ func TestPullRequestsNewest(t *testing.T) {
}, },
State: "open", State: "open",
SortType: "newest", SortType: "newest",
Labels: []string{},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3, count) assert.EqualValues(t, 3, count)
@ -114,7 +113,6 @@ func TestPullRequestsOldest(t *testing.T) {
}, },
State: "open", State: "open",
SortType: "oldest", SortType: "oldest",
Labels: []string{},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3, count) assert.EqualValues(t, 3, count)

View file

@ -239,11 +239,11 @@ type CreateReviewOptions struct {
// IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals) // IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals)
func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.User) (bool, error) { func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.User) (bool, error) {
pr, err := GetPullRequestByIssueID(ctx, issue.ID) if err := issue.LoadPullRequest(ctx); err != nil {
if err != nil {
return false, err return false, err
} }
pr := issue.PullRequest
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
if err != nil { if err != nil {
return false, err return false, err
@ -271,11 +271,10 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.
// IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals) // IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals)
func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organization.Team) (bool, error) { func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organization.Team) (bool, error) {
pr, err := GetPullRequestByIssueID(ctx, issue.ID) if err := issue.LoadPullRequest(ctx); err != nil {
if err != nil {
return false, err return false, err
} }
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, issue.PullRequest.BaseRepoID, issue.PullRequest.BaseBranch)
if err != nil { if err != nil {
return false, err return false, err
} }

View file

@ -319,8 +319,9 @@ func CreateOrganization(ctx context.Context, org *Organization, owner *user_mode
// Add initial creator to organization and owner team. // Add initial creator to organization and owner team.
if err = db.Insert(ctx, &OrgUser{ if err = db.Insert(ctx, &OrgUser{
UID: owner.ID, UID: owner.ID,
OrgID: org.ID, OrgID: org.ID,
IsPublic: setting.Service.DefaultOrgMemberVisible,
}); err != nil { }); err != nil {
return fmt.Errorf("insert org-user relation: %w", err) return fmt.Errorf("insert org-user relation: %w", err)
} }

View file

@ -443,7 +443,7 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail
cond = cond.And(builder.Eq{"email_address.is_activated": opts.IsActivated.Value()}) cond = cond.And(builder.Eq{"email_address.is_activated": opts.IsActivated.Value()})
} }
count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid"). count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.id = email_address.uid").
Where(cond).Count(new(EmailAddress)) Where(cond).Count(new(EmailAddress))
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err) return nil, 0, fmt.Errorf("Count: %w", err)
@ -459,7 +459,7 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail
emails := make([]*SearchEmailResult, 0, opts.PageSize) emails := make([]*SearchEmailResult, 0, opts.PageSize)
err = db.GetEngine(ctx).Table("email_address"). err = db.GetEngine(ctx).Table("email_address").
Select("email_address.*, `user`.name, `user`.full_name"). Select("email_address.*, `user`.name, `user`.full_name").
Join("INNER", "`user`", "`user`.ID = email_address.uid"). Join("INNER", "`user`", "`user`.id = email_address.uid").
Where(cond). Where(cond).
OrderBy(orderby). OrderBy(orderby).
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).

View file

@ -100,7 +100,7 @@ func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limi
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scan: %w", err) return nil, fmt.Errorf("ReadLogs scan: %w", err)
} }
return rows, nil return rows, nil

View file

@ -41,6 +41,12 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
} }
logIndex += preStep.LogLength logIndex += preStep.LogLength
// lastHasRunStep is the last step that has run.
// For example,
// 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1.
// 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3.
// 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2.
// So its Stopped is the Started of postStep when there are no more steps to run.
var lastHasRunStep *actions_model.ActionTaskStep var lastHasRunStep *actions_model.ActionTaskStep
for _, step := range task.Steps { for _, step := range task.Steps {
if step.Status.HasRun() { if step.Status.HasRun() {
@ -56,11 +62,15 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
Name: postStepName, Name: postStepName,
Status: actions_model.StatusWaiting, Status: actions_model.StatusWaiting,
} }
if task.Status.IsDone() { // If the lastHasRunStep is the last step, or it has failed, postStep has started.
if lastHasRunStep.Status.IsFailure() || lastHasRunStep == task.Steps[len(task.Steps)-1] {
postStep.LogIndex = logIndex postStep.LogIndex = logIndex
postStep.LogLength = task.LogLength - postStep.LogIndex postStep.LogLength = task.LogLength - postStep.LogIndex
postStep.Status = task.Status
postStep.Started = lastHasRunStep.Stopped postStep.Started = lastHasRunStep.Stopped
postStep.Status = actions_model.StatusRunning
}
if task.Status.IsDone() {
postStep.Status = task.Status
postStep.Stopped = task.Stopped postStep.Stopped = task.Stopped
} }
ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2) ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2)

View file

@ -103,6 +103,40 @@ func TestFullSteps(t *testing.T) {
{Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100}, {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100},
}, },
}, },
{
name: "all steps finished but task is running",
task: &actions_model.ActionTask{
Steps: []*actions_model.ActionTaskStep{
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
},
Status: actions_model.StatusRunning,
Started: 10000,
Stopped: 0,
LogLength: 100,
},
want: []*actions_model.ActionTaskStep{
{Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010},
{Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090},
{Name: postStepName, Status: actions_model.StatusRunning, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 0},
},
},
{
name: "skipped task",
task: &actions_model.ActionTask{
Steps: []*actions_model.ActionTaskStep{
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
},
Status: actions_model.StatusSkipped,
Started: 0,
Stopped: 0,
LogLength: 0,
},
want: []*actions_model.ActionTaskStep{
{Name: preStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
{Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
{Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -150,13 +150,16 @@ func TruncateString(str string, limit int) string {
// StringsToInt64s converts a slice of string to a slice of int64. // StringsToInt64s converts a slice of string to a slice of int64.
func StringsToInt64s(strs []string) ([]int64, error) { func StringsToInt64s(strs []string) ([]int64, error) {
ints := make([]int64, len(strs)) if strs == nil {
for i := range strs { return nil, nil
n, err := strconv.ParseInt(strs[i], 10, 64) }
ints := make([]int64, 0, len(strs))
for _, s := range strs {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
return ints, err return nil, err
} }
ints[i] = n ints = append(ints, n)
} }
return ints, nil return ints, nil
} }

View file

@ -138,12 +138,13 @@ func TestStringsToInt64s(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, result) assert.Equal(t, expected, result)
} }
testSuccess(nil, nil)
testSuccess([]string{}, []int64{}) testSuccess([]string{}, []int64{})
testSuccess([]string{"-1234"}, []int64{-1234}) testSuccess([]string{"-1234"}, []int64{-1234})
testSuccess([]string{"1", "4", "16", "64", "256"}, testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
[]int64{1, 4, 16, 64, 256})
_, err := StringsToInt64s([]string{"-1", "a", "$"}) ints, err := StringsToInt64s([]string{"-1", "a"})
assert.Len(t, ints, 0)
assert.Error(t, err) assert.Error(t, err)
} }

View file

@ -9,6 +9,7 @@ import (
"bytes" "bytes"
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"os/exec" "os/exec"
"strconv" "strconv"
@ -390,6 +391,9 @@ func (c *Commit) GetSubModules() (*ObjectCache, error) {
} }
} }
} }
if err = scanner.Err(); err != nil {
return nil, fmt.Errorf("GetSubModules scan: %w", err)
}
return c.submoduleCache, nil return c.submoduleCache, nil
} }

View file

@ -283,7 +283,7 @@ type DivergeObject struct {
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch // GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) { func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
cmd := NewCommand(ctx, "rev-list", "--count", "--left-right"). cmd := NewCommand(ctx, "rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch) AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
if err != nil { if err != nil {
return do, err return do, err

View file

@ -124,6 +124,10 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
} }
} }
} }
if err = scanner.Err(); err != nil {
_ = stdoutReader.Close()
return fmt.Errorf("GetCodeActivityStats scan: %w", err)
}
a := make([]*CodeActivityAuthor, 0, len(authors)) a := make([]*CodeActivityAuthor, 0, len(authors))
for _, v := range authors { for _, v := range authors {
a = append(a, v) a = append(a, v)

View file

@ -59,8 +59,8 @@ func (g *Manager) start() {
go func() { go func() {
defer func() { defer func() {
close(startupDone) close(startupDone)
// Close the unused listeners and ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function // Close the unused listeners
_ = CloseProvidedListeners() closeProvidedListeners()
}() }()
// Wait for all servers to be created // Wait for all servers to be created
g.createServerCond.L.Lock() g.createServerCond.L.Lock()

View file

@ -129,25 +129,17 @@ func getProvidedFDs() (savedErr error) {
return savedErr return savedErr
} }
// CloseProvidedListeners closes all unused provided listeners. // closeProvidedListeners closes all unused provided listeners.
func CloseProvidedListeners() error { func closeProvidedListeners() {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
var returnableError error
for _, l := range providedListeners { for _, l := range providedListeners {
err := l.Close() err := l.Close()
if err != nil { if err != nil {
log.Error("Error in closing unused provided listener: %v", err) log.Error("Error in closing unused provided listener: %v", err)
if returnableError != nil {
returnableError = fmt.Errorf("%v & %w", returnableError, err)
} else {
returnableError = err
}
} }
} }
providedListeners = []net.Listener{} providedListeners = []net.Listener{}
return returnableError
} }
// DefaultGetListener obtains a listener for the stream-oriented local network address: // DefaultGetListener obtains a listener for the stream-oriented local network address:

View file

@ -39,6 +39,8 @@ import (
const ( const (
unicodeNormalizeName = "unicodeNormalize" unicodeNormalizeName = "unicodeNormalize"
maxBatchSize = 16 maxBatchSize = 16
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
fuzzyDenominator = 4
) )
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error { func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
@ -239,15 +241,12 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
keywordQuery query.Query keywordQuery query.Query
) )
phraseQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
phraseQuery.FieldVal = "Content"
phraseQuery.Analyzer = repoIndexerAnalyzer
keywordQuery = phraseQuery
if opts.IsKeywordFuzzy { if opts.IsKeywordFuzzy {
phraseQuery := bleve.NewMatchPhraseQuery(opts.Keyword) phraseQuery.Fuzziness = len(opts.Keyword) / fuzzyDenominator
phraseQuery.FieldVal = "Content"
phraseQuery.Analyzer = repoIndexerAnalyzer
keywordQuery = phraseQuery
} else {
prefixQuery := bleve.NewPrefixQuery(opts.Keyword)
prefixQuery.FieldVal = "Content"
keywordQuery = prefixQuery
} }
if len(opts.RepoIDs) > 0 { if len(opts.RepoIDs) > 0 {

View file

@ -20,17 +20,11 @@ func NumericEqualityQuery(value int64, field string) *query.NumericRangeQuery {
} }
// MatchPhraseQuery generates a match phrase query for the given phrase, field and analyzer // MatchPhraseQuery generates a match phrase query for the given phrase, field and analyzer
func MatchPhraseQuery(matchPhrase, field, analyzer string) *query.MatchPhraseQuery { func MatchPhraseQuery(matchPhrase, field, analyzer string, fuzziness int) *query.MatchPhraseQuery {
q := bleve.NewMatchPhraseQuery(matchPhrase) q := bleve.NewMatchPhraseQuery(matchPhrase)
q.FieldVal = field q.FieldVal = field
q.Analyzer = analyzer q.Analyzer = analyzer
return q q.Fuzziness = fuzziness
}
// PrefixQuery generates a match prefix query for the given prefix and field
func PrefixQuery(matchPrefix, field string) *query.PrefixQuery {
q := bleve.NewPrefixQuery(matchPrefix)
q.FieldVal = field
return q return q
} }

View file

@ -35,7 +35,11 @@ func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
}) })
} }
const maxBatchSize = 16 const (
maxBatchSize = 16
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
fuzzyDenominator = 4
)
// IndexerData an update to the issue indexer // IndexerData an update to the issue indexer
type IndexerData internal.IndexerData type IndexerData internal.IndexerData
@ -156,19 +160,16 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
var queries []query.Query var queries []query.Query
if options.Keyword != "" { if options.Keyword != "" {
fuzziness := 0
if options.IsFuzzyKeyword { if options.IsFuzzyKeyword {
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{ fuzziness = len(options.Keyword) / fuzzyDenominator
inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer),
inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer),
inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer),
}...))
} else {
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
inner_bleve.PrefixQuery(options.Keyword, "title"),
inner_bleve.PrefixQuery(options.Keyword, "content"),
inner_bleve.PrefixQuery(options.Keyword, "comments"),
}...))
} }
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
inner_bleve.MatchPhraseQuery(options.Keyword, "title", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(options.Keyword, "content", issueIndexerAnalyzer, fuzziness),
inner_bleve.MatchPhraseQuery(options.Keyword, "comments", issueIndexerAnalyzer, fuzziness),
}...))
} }
if len(options.RepoIDs) > 0 || options.AllPublic { if len(options.RepoIDs) > 0 || options.AllPublic {

View file

@ -515,10 +515,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByCreatedDesc", Name: "SortByCreatedDesc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByCreatedDesc,
},
SortBy: internal.SortByCreatedDesc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -533,10 +531,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByUpdatedDesc", Name: "SortByUpdatedDesc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByUpdatedDesc,
},
SortBy: internal.SortByUpdatedDesc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -551,10 +547,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByCommentsDesc", Name: "SortByCommentsDesc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByCommentsDesc,
},
SortBy: internal.SortByCommentsDesc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -569,10 +563,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByDeadlineDesc", Name: "SortByDeadlineDesc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByDeadlineDesc,
},
SortBy: internal.SortByDeadlineDesc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -587,10 +579,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByCreatedAsc", Name: "SortByCreatedAsc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByCreatedAsc,
},
SortBy: internal.SortByCreatedAsc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -605,10 +595,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByUpdatedAsc", Name: "SortByUpdatedAsc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByUpdatedAsc,
},
SortBy: internal.SortByUpdatedAsc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -623,10 +611,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByCommentsAsc", Name: "SortByCommentsAsc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByCommentsAsc,
},
SortBy: internal.SortByCommentsAsc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))
@ -641,10 +627,8 @@ var cases = []*testIndexerCase{
{ {
Name: "SortByDeadlineAsc", Name: "SortByDeadlineAsc",
SearchOptions: &internal.SearchOptions{ SearchOptions: &internal.SearchOptions{
Paginator: &db.ListOptions{ Paginator: &db.ListOptionsAll,
ListAll: true, SortBy: internal.SortByDeadlineAsc,
},
SortBy: internal.SortByDeadlineAsc,
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, len(data), len(result.Hits)) assert.Equal(t, len(data), len(result.Hits))

View file

@ -61,9 +61,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
) )
{ {
reviews, err := issue_model.FindReviews(ctx, issue_model.FindReviewOptions{ reviews, err := issue_model.FindReviews(ctx, issue_model.FindReviewOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
IssueID: issueID, IssueID: issueID,
OfficialOnly: false, OfficialOnly: false,
}) })

View file

@ -6,6 +6,7 @@ package markup
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt"
"html" "html"
"io" "io"
"regexp" "regexp"
@ -123,6 +124,9 @@ func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error {
return err return err
} }
} }
if err = scan.Err(); err != nil {
return fmt.Errorf("fallbackRender scan: %w", err)
}
_, err = tmpBlock.WriteString("</pre>") _, err = tmpBlock.WriteString("</pre>")
if err != nil { if err != nil {

View file

@ -54,7 +54,7 @@ func Sorted[S ~[]E, E cmp.Ordered](values S) S {
return values return values
} }
// TODO: Replace with "maps.Values" once available // TODO: Replace with "maps.Values" once available, current it only in golang.org/x/exp/maps but not in standard library
func ValuesOfMap[K comparable, V any](m map[K]V) []V { func ValuesOfMap[K comparable, V any](m map[K]V) []V {
values := make([]V, 0, len(m)) values := make([]V, 0, len(m))
for _, v := range m { for _, v := range m {
@ -62,3 +62,12 @@ func ValuesOfMap[K comparable, V any](m map[K]V) []V {
} }
return values return values
} }
// TODO: Replace with "maps.Keys" once available, current it only in golang.org/x/exp/maps but not in standard library
func KeysOfMap[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}

View file

@ -0,0 +1,3 @@
This file may be distributed, modified, and used in other works with just
one restriction: modified versions must clearly indicate the modification
(a name change, or a displayed message, or ?).

View file

@ -115,6 +115,7 @@ loading = Loading…
error = Error error = Error
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it. error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.
go_back = Go Back go_back = Go Back
invalid_data = Invalid data: %v
never = Never never = Never
unknown = Unknown unknown = Unknown
@ -1333,6 +1334,8 @@ editor.file_editing_no_longer_exists = The file being edited, "%s", no longer ex
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository. editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them. editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_already_exists = A file named "%s" already exists in this repository. editor.file_already_exists = A file named "%s" already exists in this repository.
editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge.
editor.push_out_of_date = The push appears to be out of date.
editor.commit_empty_file_header = Commit an empty file editor.commit_empty_file_header = Commit an empty file
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
editor.no_changes_to_show = There are no changes to show. editor.no_changes_to_show = There are no changes to show.
@ -3133,7 +3136,7 @@ auths.tip.nextcloud = Register a new OAuth consumer on your instance using the f
auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps
auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login" auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login"
auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new
auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications auths.tip.gitlab_new = Register a new application on https://gitlab.com/-/profile/applications
auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/ auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/
auths.tip.openid_connect = Use the OpenID Connect Discovery URL (<server>/.well-known/openid-configuration) to specify the endpoints auths.tip.openid_connect = Use the OpenID Connect Discovery URL (<server>/.well-known/openid-configuration) to specify the endpoints
auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled
@ -3671,6 +3674,7 @@ runs.pushed_by = pushed by
runs.workflow = Workflow runs.workflow = Workflow
runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s
runs.no_matching_online_runner_helper = No matching online runner with label: %s runs.no_matching_online_runner_helper = No matching online runner with label: %s
runs.no_job_without_needs = The workflow must contain at least one job without dependencies.
runs.actor = Actor runs.actor = Actor
runs.status = Status runs.status = Status
runs.actors_no_select = All actors runs.actors_no_select = All actors

1383
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -84,7 +84,7 @@
"eslint-plugin-vue": "9.23.0", "eslint-plugin-vue": "9.23.0",
"eslint-plugin-vue-scoped-css": "2.7.2", "eslint-plugin-vue-scoped-css": "2.7.2",
"eslint-plugin-wc": "2.0.4", "eslint-plugin-wc": "2.0.4",
"jsdom": "24.0.0", "happy-dom": "14.2.0",
"markdownlint-cli": "0.39.0", "markdownlint-cli": "0.39.0",
"postcss-html": "1.6.0", "postcss-html": "1.6.0",
"stylelint": "16.2.1", "stylelint": "16.2.1",

View file

@ -27,7 +27,7 @@ export default {
* Maximum time expect() should wait for the condition to be met. * Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();` * For example in `await expect(locator).toHaveText();`
*/ */
timeout: 2000 timeout: 2000,
}, },
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */

View file

@ -874,10 +874,11 @@ func EditIssue(ctx *context.APIContext) {
} }
if form.State != nil { if form.State != nil {
if issue.IsPull { if issue.IsPull {
if pr, err := issue.GetPullRequest(ctx); err != nil { if err := issue.LoadPullRequest(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "GetPullRequest", err) ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
return return
} else if pr.HasMerged { }
if issue.PullRequest.HasMerged {
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged") ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
return return
} }

View file

@ -240,18 +240,12 @@ func ListPinnedPullRequests(ctx *context.APIContext) {
} }
apiPrs := make([]*api.PullRequest, len(issues)) apiPrs := make([]*api.PullRequest, len(issues))
if err := issues.LoadPullRequests(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadPullRequests", err)
return
}
for i, currentIssue := range issues { for i, currentIssue := range issues {
pr, err := currentIssue.GetPullRequest(ctx) pr := currentIssue.PullRequest
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetPullRequest", err)
return
}
if err = pr.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if err = pr.LoadAttributes(ctx); err != nil { if err = pr.LoadAttributes(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return

View file

@ -21,6 +21,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -96,13 +97,17 @@ func ListPullRequests(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
return
}
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{ prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
ListOptions: listOptions, ListOptions: listOptions,
State: ctx.FormTrim("state"), State: ctx.FormTrim("state"),
SortType: ctx.FormTrim("sort"), SortType: ctx.FormTrim("sort"),
Labels: ctx.FormStrings("labels"), Labels: labelIDs,
MilestoneID: ctx.FormInt64("milestone"), MilestoneID: ctx.FormInt64("milestone"),
}) })
if err != nil { if err != nil {

View file

@ -75,6 +75,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
updates = append(updates, option) updates = append(updates, option)
if repo.IsEmpty && (refFullName.BranchName() == "master" || refFullName.BranchName() == "main") { if repo.IsEmpty && (refFullName.BranchName() == "master" || refFullName.BranchName() == "main") {
// put the master/main branch first // put the master/main branch first
// FIXME: It doesn't always work, since the master/main branch may not be the first batch of updates.
// If the user pushes many branches at once, the Git hook will call the internal API in batches, rather than all at once.
// See https://github.com/go-gitea/gitea/blob/cb52b17f92e2d2293f7c003649743464492bca48/cmd/hook.go#L27
// If the user executes `git push origin --all` and pushes more than 30 branches, the master/main may not be the default branch.
copy(updates[1:], updates) copy(updates[1:], updates)
updates[0] = option updates[0] = option
} }

View file

@ -275,9 +275,7 @@ func ViewUser(ctx *context.Context) {
} }
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
OwnerID: u.ID, OwnerID: u.ID,
OrderBy: db.SearchOrderByAlphabetically, OrderBy: db.SearchOrderByAlphabetically,
Private: true, Private: true,
@ -300,9 +298,7 @@ func ViewUser(ctx *context.Context) {
ctx.Data["EmailsTotal"] = len(emails) ctx.Data["EmailsTotal"] = len(emails)
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
UserID: u.ID, UserID: u.ID,
IncludePrivate: true, IncludePrivate: true,
}) })

View file

@ -104,8 +104,13 @@ func List(ctx *context.Context) {
workflows = append(workflows, workflow) workflows = append(workflows, workflow)
continue continue
} }
// Check whether have matching runner // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
hasJobWithoutNeeds := false
// Check whether have matching runner and a job without "needs"
for _, j := range wf.Jobs { for _, j := range wf.Jobs {
if !hasJobWithoutNeeds && len(j.Needs()) == 0 {
hasJobWithoutNeeds = true
}
runsOnList := j.RunsOn() runsOnList := j.RunsOn()
for _, ro := range runsOnList { for _, ro := range runsOnList {
if strings.Contains(ro, "${{") { if strings.Contains(ro, "${{") {
@ -123,6 +128,9 @@ func List(ctx *context.Context) {
break break
} }
} }
if !hasJobWithoutNeeds {
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs")
}
workflows = append(workflows, workflow) workflows = append(workflows, workflow)
} }
} }

View file

@ -353,12 +353,25 @@ func Rerun(ctx *context_module.Context) {
return return
} }
if jobIndexStr != "" { if jobIndexStr == "" { // rerun all jobs
jobs = []*actions_model.ActionRunJob{job} for _, j := range jobs {
// if the job has needs, it should be set to "blocked" status to wait for other jobs
shouldBlock := len(j.Needs) > 0
if err := rerunJob(ctx, j, shouldBlock); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
}
ctx.JSON(http.StatusOK, struct{}{})
return
} }
for _, j := range jobs { rerunJobs := actions_service.GetAllRerunJobs(job, jobs)
if err := rerunJob(ctx, j); err != nil {
for _, j := range rerunJobs {
// jobs other than the specified one should be set to "blocked" status
shouldBlock := j.JobID != job.JobID
if err := rerunJob(ctx, j, shouldBlock); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
return return
} }
@ -367,7 +380,7 @@ func Rerun(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, struct{}{}) ctx.JSON(http.StatusOK, struct{}{})
} }
func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) error { func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
status := job.Status status := job.Status
if !status.IsDone() { if !status.IsDone() {
return nil return nil
@ -375,6 +388,9 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) erro
job.TaskID = 0 job.TaskID = 0
job.Status = actions_model.StatusWaiting job.Status = actions_model.StatusWaiting
if shouldBlock {
job.Status = actions_model.StatusBlocked
}
job.Started = 0 job.Started = 0
job.Stopped = 0 job.Stopped = 0

View file

@ -367,7 +367,7 @@ func Diff(ctx *context.Context) {
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{ListAll: true}) statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View file

@ -697,10 +697,8 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
defer gitRepo.Close() defer gitRepo.Close()
branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID, RepoID: repo.ID,
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
IsDeletedBranch: optional.Some(false), IsDeletedBranch: optional.Some(false),
}) })
if err != nil { if err != nil {
@ -754,10 +752,8 @@ func CompareDiff(ctx *context.Context) {
} }
headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: ci.HeadRepo.ID, RepoID: ci.HeadRepo.ID,
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
IsDeletedBranch: optional.Some(false), IsDeletedBranch: optional.Some(false),
}) })
if err != nil { if err != nil {
@ -980,5 +976,8 @@ func getExcerptLines(commit *git.Commit, filePath string, idxLeft, idxRight, chu
} }
diffLines = append(diffLines, diffLine) diffLines = append(diffLines, diffLine)
} }
if err = scanner.Err(); err != nil {
return nil, fmt.Errorf("getExcerptLines scan: %w", err)
}
return diffLines, nil return diffLines, nil
} }

View file

@ -374,9 +374,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
} }
} else if models.IsErrCommitIDDoesNotMatch(err) { } else if models.IsErrCommitIDDoesNotMatch(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplEditFile, &form) ctx.RenderWithErr(ctx.Tr("repo.editor.commit_id_not_matching"), tplEditFile, &form)
} else if git.IsErrPushOutOfDate(err) { } else if git.IsErrPushOutOfDate(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplEditFile, &form) ctx.RenderWithErr(ctx.Tr("repo.editor.push_out_of_date"), tplEditFile, &form)
} else if git.IsErrPushRejected(err) { } else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected) errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 { if len(errPushRej.Message) == 0 {

View file

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
) )
@ -17,7 +18,7 @@ const (
// FindFiles render the page to find repository files // FindFiles render the page to find repository files
func FindFiles(ctx *context.Context) { func FindFiles(ctx *context.Context) {
path := ctx.Params("*") path := ctx.Params("*")
ctx.Data["TreeLink"] = ctx.Repo.RepoLink + "/src/" + path ctx.Data["TreeLink"] = ctx.Repo.RepoLink + "/src/" + util.PathEscapeSegments(path)
ctx.Data["DataLink"] = ctx.Repo.RepoLink + "/tree-list/" + path ctx.Data["DataLink"] = ctx.Repo.RepoLink + "/tree-list/" + util.PathEscapeSegments(path)
ctx.HTML(http.StatusOK, tplFindFiles) ctx.HTML(http.StatusOK, tplFindFiles)
} }

View file

@ -192,8 +192,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
if len(selectLabels) > 0 { if len(selectLabels) > 0 {
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
if err != nil { if err != nil {
ctx.ServerError("StringsToInt64s", err) ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
return
} }
} }
@ -451,13 +450,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t" linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link, ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link, ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, ctx.Link, ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels), url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived) milestoneID, projectID, assigneeID, posterID, archived)
ctx.Data["SelLabelIDs"] = labelIDs ctx.Data["SelLabelIDs"] = labelIDs
ctx.Data["SelectLabels"] = selectLabels ctx.Data["SelectLabels"] = selectLabels
ctx.Data["ViewType"] = viewType ctx.Data["ViewType"] = viewType

View file

@ -505,7 +505,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
if len(compareInfo.Commits) != 0 { if len(compareInfo.Commits) != 0 {
sha := compareInfo.Commits[0].ID.String() sha := compareInfo.Commits[0].ID.String()
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{ListAll: true}) commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil
@ -567,7 +567,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err) ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil return nil
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}) commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil
@ -659,7 +659,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
return nil return nil
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}) commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
ctx.ServerError("GetLatestCommitStatus", err) ctx.ServerError("GetLatestCommitStatus", err)
return nil return nil

View file

@ -136,7 +136,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions)
} }
if canReadActions { if canReadActions {
statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptions{ListAll: true}) statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -618,26 +618,31 @@ func SearchRepo(ctx *context.Context) {
} }
} }
var err error // To improve performance when only the count is requested
if ctx.FormBool("count_only") {
if count, err := repo_model.CountRepository(ctx, opts); err != nil {
log.Error("CountRepository: %v", err)
ctx.JSON(http.StatusInternalServerError, nil) // frontend JS doesn't handle error response (same as below)
} else {
ctx.SetTotalCountHeader(count)
ctx.JSONOK()
}
return
}
repos, count, err := repo_model.SearchRepository(ctx, opts) repos, count, err := repo_model.SearchRepository(ctx, opts)
if err != nil { if err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{ log.Error("SearchRepository: %v", err)
OK: false, ctx.JSON(http.StatusInternalServerError, nil)
Error: err.Error(),
})
return return
} }
ctx.SetTotalCountHeader(count) ctx.SetTotalCountHeader(count)
// To improve performance when only the count is requested
if ctx.FormBool("count_only") {
return
}
latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos) latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos)
if err != nil { if err != nil {
log.Error("FindReposLastestCommitStatuses: %v", err) log.Error("FindReposLastestCommitStatuses: %v", err)
ctx.JSON(http.StatusInternalServerError, nil)
return return
} }
@ -679,9 +684,7 @@ func GetBranchesList(ctx *context.Context) {
branchOpts := git_model.FindBranchOptions{ branchOpts := git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: optional.Some(false), IsDeletedBranch: optional.Some(false),
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
} }
branches, err := git_model.FindBranchNames(ctx, branchOpts) branches, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil { if err != nil {
@ -714,9 +717,7 @@ func PrepareBranchList(ctx *context.Context) {
branchOpts := git_model.FindBranchOptions{ branchOpts := git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: optional.Some(false), IsDeletedBranch: optional.Some(false),
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
} }
brs, err := git_model.FindBranchNames(ctx, branchOpts) brs, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil { if err != nil {

View file

@ -364,7 +364,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
ctx.Data["LatestCommitVerification"] = verification ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit) ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{ListAll: true}) statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll)
if err != nil { if err != nil {
log.Error("GetLatestCommitStatus: %v", err) log.Error("GetLatestCommitStatus: %v", err)
} }

View file

@ -16,6 +16,8 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
@ -35,6 +37,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
prepareContextForCommonProfile(ctx) prepareContextForCommonProfile(ctx)
ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
if setting.Service.UserLocationMapURL != "" { if setting.Service.UserLocationMapURL != "" {
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location) ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
@ -46,6 +49,17 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
return return
} }
ctx.Data["OpenIDs"] = openIDs ctx.Data["OpenIDs"] = openIDs
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["RenderedDescription"] = content
}
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{

View file

@ -529,17 +529,14 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// Get IDs for labels (a filter option for issues/pulls). // Get IDs for labels (a filter option for issues/pulls).
// Required for IssuesOptions. // Required for IssuesOptions.
var labelIDs []int64
selectedLabels := ctx.FormString("labels") selectedLabels := ctx.FormString("labels")
if len(selectedLabels) > 0 && selectedLabels != "0" { if len(selectedLabels) > 0 && selectedLabels != "0" {
var err error var err error
labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ",")) opts.LabelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ","))
if err != nil { if err != nil {
ctx.ServerError("StringsToInt64s", err) ctx.Flash.Error(ctx.Tr("invalid_data", selectedLabels), true)
return
} }
} }
opts.LabelIDs = labelIDs
// ------------------------------ // ------------------------------
// Get issues as defined by opts. // Get issues as defined by opts.

View file

@ -144,6 +144,12 @@ func getNotifications(ctx *context.Context) {
ctx.ServerError("LoadIssues", err) ctx.ServerError("LoadIssues", err)
return return
} }
if err = notifications.LoadIssuePullRequests(ctx); err != nil {
ctx.ServerError("LoadIssuePullRequests", err)
return
}
notifications = notifications.Without(failures) notifications = notifications.Without(failures)
failCount += len(failures) failCount += len(failures)
@ -262,8 +268,7 @@ func NotificationSubscriptions(ctx *context.Context) {
var err error var err error
labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ",")) labelIDs, err = base.StringsToInt64s(strings.Split(selectedLabels, ","))
if err != nil { if err != nil {
ctx.ServerError("StringsToInt64s", err) ctx.Flash.Error(ctx.Tr("invalid_data", selectedLabels), true)
return
} }
} }

View file

@ -79,7 +79,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
} }
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event) ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status) state := toCommitStatus(job.Status)
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}); err == nil { if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil {
for _, v := range statuses { for _, v := range statuses {
if v.Context == ctxname { if v.Context == ctxname {
if v.State == state { if v.State == state {

View file

@ -515,6 +515,12 @@ func (*actionsNotifier) MergePullRequest(ctx context.Context, doer *user_model.U
} }
func (n *actionsNotifier) PushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) { func (n *actionsNotifier) PushCommits(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, opts *repository.PushUpdateOptions, commits *repository.PushCommits) {
commitID, _ := git.NewIDFromString(opts.NewCommitID)
if commitID.IsZero() {
log.Trace("new commitID is empty")
return
}
ctx = withMethod(ctx, "PushCommits") ctx = withMethod(ctx, "PushCommits")
apiPusher := convert.ToUser(ctx, pusher, nil) apiPusher := convert.ToUser(ctx, pusher, nil)
@ -547,9 +553,9 @@ func (n *actionsNotifier) CreateRef(ctx context.Context, pusher *user_model.User
apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeNone}) apiRepo := convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeNone})
newNotifyInput(repo, pusher, webhook_module.HookEventCreate). newNotifyInput(repo, pusher, webhook_module.HookEventCreate).
WithRef(refFullName.ShortName()). // FIXME: should we use a full ref name WithRef(refFullName.String()).
WithPayload(&api.CreatePayload{ WithPayload(&api.CreatePayload{
Ref: refFullName.ShortName(), Ref: refFullName.String(),
Sha: refID, Sha: refID,
RefType: refFullName.RefType(), RefType: refFullName.RefType(),
Repo: apiRepo, Repo: apiRepo,
@ -566,7 +572,7 @@ func (n *actionsNotifier) DeleteRef(ctx context.Context, pusher *user_model.User
newNotifyInput(repo, pusher, webhook_module.HookEventDelete). newNotifyInput(repo, pusher, webhook_module.HookEventDelete).
WithPayload(&api.DeletePayload{ WithPayload(&api.DeletePayload{
Ref: refFullName.ShortName(), Ref: refFullName.String(),
RefType: refFullName.RefType(), RefType: refFullName.RefType(),
PusherType: api.PusherTypeUser, PusherType: api.PusherTypeUser,
Repo: apiRepo, Repo: apiRepo,
@ -623,6 +629,10 @@ func (n *actionsNotifier) UpdateRelease(ctx context.Context, doer *user_model.Us
} }
func (n *actionsNotifier) DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) { func (n *actionsNotifier) DeleteRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release) {
if rel.IsTag {
// has sent same action in `PushCommits`, so skip it.
return
}
ctx = withMethod(ctx, "DeleteRelease") ctx = withMethod(ctx, "DeleteRelease")
notifyRelease(ctx, doer, rel, api.HookReleaseDeleted) notifyRelease(ctx, doer, rel, api.HookReleaseDeleted)
} }

38
services/actions/rerun.go Normal file
View file

@ -0,0 +1,38 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/container"
)
// GetAllRerunJobs get all jobs that need to be rerun when job should be rerun
func GetAllRerunJobs(job *actions_model.ActionRunJob, allJobs []*actions_model.ActionRunJob) []*actions_model.ActionRunJob {
rerunJobs := []*actions_model.ActionRunJob{job}
rerunJobsIDSet := make(container.Set[string])
rerunJobsIDSet.Add(job.JobID)
for {
found := false
for _, j := range allJobs {
if rerunJobsIDSet.Contains(j.JobID) {
continue
}
for _, need := range j.Needs {
if rerunJobsIDSet.Contains(need) {
found = true
rerunJobs = append(rerunJobs, j)
rerunJobsIDSet.Add(j.JobID)
break
}
}
}
if !found {
break
}
}
return rerunJobs
}

View file

@ -0,0 +1,48 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"github.com/stretchr/testify/assert"
)
func TestGetAllRerunJobs(t *testing.T) {
job1 := &actions_model.ActionRunJob{JobID: "job1"}
job2 := &actions_model.ActionRunJob{JobID: "job2", Needs: []string{"job1"}}
job3 := &actions_model.ActionRunJob{JobID: "job3", Needs: []string{"job2"}}
job4 := &actions_model.ActionRunJob{JobID: "job4", Needs: []string{"job2", "job3"}}
jobs := []*actions_model.ActionRunJob{job1, job2, job3, job4}
testCases := []struct {
job *actions_model.ActionRunJob
rerunJobs []*actions_model.ActionRunJob
}{
{
job1,
[]*actions_model.ActionRunJob{job1, job2, job3, job4},
},
{
job2,
[]*actions_model.ActionRunJob{job2, job3, job4},
},
{
job3,
[]*actions_model.ActionRunJob{job3, job4},
},
{
job4,
[]*actions_model.ActionRunJob{job4},
},
}
for _, tc := range testCases {
rerunJobs := GetAllRerunJobs(tc.job, jobs)
assert.ElementsMatch(t, tc.rerunJobs, rerunJobs)
}
}

View file

@ -4,7 +4,6 @@
package auth package auth
import ( import (
"context"
"net/http" "net/http"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -29,40 +28,33 @@ func (s *Session) Name() string {
// object for that uid. // object for that uid.
// Returns nil if there is no user uid stored in the session. // Returns nil if there is no user uid stored in the session.
func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
user := SessionUser(req.Context(), sess)
if user != nil {
return user, nil
}
return nil, nil
}
// SessionUser returns the user object corresponding to the "uid" session variable.
func SessionUser(ctx context.Context, sess SessionStore) *user_model.User {
if sess == nil { if sess == nil {
return nil return nil, nil
} }
// Get user ID // Get user ID
uid := sess.Get("uid") uid := sess.Get("uid")
if uid == nil { if uid == nil {
return nil return nil, nil
} }
log.Trace("Session Authorization: Found user[%d]", uid) log.Trace("Session Authorization: Found user[%d]", uid)
id, ok := uid.(int64) id, ok := uid.(int64)
if !ok { if !ok {
return nil return nil, nil
} }
// Get user object // Get user object
user, err := user_model.GetUserByID(ctx, id) user, err := user_model.GetUserByID(req.Context(), id)
if err != nil { if err != nil {
if !user_model.IsErrUserNotExist(err) { if !user_model.IsErrUserNotExist(err) {
log.Error("GetUserById: %v", err) log.Error("GetUserByID: %v", err)
// Return the err as-is to keep current signed-in session, in case the err is something like context.Canceled. Otherwise non-existing user (nil, nil) will make the caller clear the signed-in session.
return nil, err
} }
return nil return nil, nil
} }
log.Trace("Session Authorization: Logged in user %-v", user) log.Trace("Session Authorization: Logged in user %-v", user)
return user return user, nil
} }

View file

@ -61,8 +61,9 @@ func ToNotificationThread(ctx context.Context, n *activities_model.Notification)
result.Subject.LatestCommentHTMLURL = comment.HTMLURL(ctx) result.Subject.LatestCommentHTMLURL = comment.HTMLURL(ctx)
} }
pr, _ := n.Issue.GetPullRequest(ctx) if err := n.Issue.LoadPullRequest(ctx); err == nil &&
if pr != nil && pr.HasMerged { n.Issue.PullRequest != nil &&
n.Issue.PullRequest.HasMerged {
result.Subject.State = "merged" result.Subject.State = "merged"
} }
} }

View file

@ -50,7 +50,11 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
} }
linesInAuthorizedKeys.Add(line) linesInAuthorizedKeys.Add(line)
} }
f.Close() if err = scanner.Err(); err != nil {
return fmt.Errorf("scan: %w", err)
}
// although there is a "defer close" above, here close explicitly before the generating, because it needs to open the file for writing again
_ = f.Close()
// now we regenerate and check if there are any lines missing // now we regenerate and check if there are any lines missing
regenerated := &bytes.Buffer{} regenerated := &bytes.Buffer{}

View file

@ -153,7 +153,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", fmt.Errorf("LoadBaseRepo: %w", err) return "", fmt.Errorf("LoadBaseRepo: %w", err)
} }
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true}) commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
if err != nil { if err != nil {
return "", fmt.Errorf("GetLatestCommitStatus: %w", err) return "", fmt.Errorf("GetLatestCommitStatus: %w", err)
} }

View file

@ -893,7 +893,7 @@ func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues
return nil, nil, shaErr return nil, nil, shaErr
} }
statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true}) statuses, _, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
lastStatus = git_model.CalcCommitStatus(statuses) lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err return statuses, lastStatus, err
} }

View file

@ -52,9 +52,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
issueIDs := prs.GetIssueIDs() issueIDs := prs.GetIssueIDs()
codeComments, err := db.Find[issues_model.Comment](ctx, issues_model.FindCommentsOptions{ codeComments, err := db.Find[issues_model.Comment](ctx, issues_model.FindCommentsOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
Type: issues_model.CommentTypeCode, Type: issues_model.CommentTypeCode,
Invalidated: optional.Some(false), Invalidated: optional.Some(false),
IssueIDs: issueIDs, IssueIDs: issueIDs,
@ -268,11 +266,11 @@ func CreateCodeCommentKnownReviewID(ctx context.Context, doer *user_model.User,
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, reviewType issues_model.ReviewType, content, commitID string, attachmentUUIDs []string) (*issues_model.Review, *issues_model.Comment, error) { func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, reviewType issues_model.ReviewType, content, commitID string, attachmentUUIDs []string) (*issues_model.Review, *issues_model.Comment, error) {
pr, err := issue.GetPullRequest(ctx) if err := issue.LoadPullRequest(ctx); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
pr := issue.PullRequest
var stale bool var stale bool
if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject { if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject {
stale = false stale = false
@ -322,12 +320,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
// DismissApprovalReviews dismiss all approval reviews because of new commits // DismissApprovalReviews dismiss all approval reviews because of new commits
func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest) error { func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest) error {
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{ reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true, IssueID: pull.IssueID,
}, Type: issues_model.ReviewTypeApprove,
IssueID: pull.IssueID, Dismissed: optional.Some(false),
Type: issues_model.ReviewTypeApprove,
Dismissed: optional.Some(false),
}) })
if err != nil { if err != nil {
return err return err

View file

@ -144,10 +144,8 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
} }
branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID, RepoID: repo.ID,
ListOptions: db.ListOptions{ ListOptions: db.ListOptionsAll,
ListAll: true,
},
IsDeletedBranch: optional.Some(false), IsDeletedBranch: optional.Some(false),
}) })

View file

@ -127,10 +127,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
p := protectedBranches.GetFirstMatched(branchName) p := protectedBranches.GetFirstMatched(branchName)
isProtected := p != nil isProtected := p != nil
divergence := &git.DivergeObject{ var divergence *git.DivergeObject
Ahead: -1,
Behind: -1,
}
// it's not default branch // it's not default branch
if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
@ -141,6 +138,11 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g
} }
} }
if divergence == nil {
// tolerate the error that we cannot get divergence
divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
}
pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName) pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)

View file

@ -181,7 +181,9 @@ func (t telegramConvertor) Package(p *api.PackagePayload) (TelegramPayload, erro
func createTelegramPayload(message string) TelegramPayload { func createTelegramPayload(message string) TelegramPayload {
return TelegramPayload{ return TelegramPayload{
Message: strings.TrimSpace(message), Message: strings.TrimSpace(message),
ParseMode: "HTML",
DisableWebPreview: true,
} }
} }

View file

@ -18,6 +18,15 @@ import (
func TestTelegramPayload(t *testing.T) { func TestTelegramPayload(t *testing.T) {
tc := telegramConvertor{} tc := telegramConvertor{}
t.Run("Correct webhook params", func(t *testing.T) {
p := createTelegramPayload("testMsg ")
assert.Equal(t, "HTML", p.ParseMode)
assert.Equal(t, true, p.DisableWebPreview)
assert.Equal(t, "testMsg", p.Message)
})
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
p := createTestPayload() p := createTestPayload()

View file

@ -55,5 +55,41 @@ export default {
current: 'currentcolor', current: 'currentcolor',
transparent: 'transparent', transparent: 'transparent',
}, },
borderRadius: {
'none': '0',
'sm': '2px',
'DEFAULT': 'var(--border-radius)', // 4px
'md': 'var(--border-radius-medium)', // 6px
'lg': '8px',
'xl': '12px',
'2xl': '16px',
'3xl': '24px',
'full': 'var(--border-radius-circle)', // 50%
},
fontWeight: {
light: 'var(--font-weight-light)',
normal: 'var(--font-weight-normal)',
medium: 'var(--font-weight-medium)',
semibold: 'var(--font-weight-semibold)',
bold: 'var(--font-weight-bold)',
},
fontSize: { // not using `rem` units because our root is currently 14px
'xs': '12px',
'sm': '14px',
'base': '16px',
'lg': '18px',
'xl': '20px',
'2xl': '24px',
'3xl': '30px',
'4xl': '36px',
'5xl': '48px',
'6xl': '60px',
'7xl': '72px',
'8xl': '96px',
'9xl': '128px',
...Object.fromEntries(Array.from({length: 100}, (_, i) => {
return [`${i}`, `${i === 0 ? '0' : `${i}px`}`];
})),
},
}, },
}; };

View file

@ -99,7 +99,7 @@
<li>GitHub</li> <li>GitHub</li>
<span>{{ctx.Locale.Tr "admin.auths.tip.github"}}</span> <span>{{ctx.Locale.Tr "admin.auths.tip.github"}}</span>
<li>GitLab</li> <li>GitLab</li>
<span>{{ctx.Locale.Tr "admin.auths.tip.gitlab"}}</span> <span>{{ctx.Locale.Tr "admin.auths.tip.gitlab_new"}}</span>
<li>Google</li> <li>Google</li>
<span>{{ctx.Locale.Tr "admin.auths.tip.google_plus"}}</span> <span>{{ctx.Locale.Tr "admin.auths.tip.google_plus"}}</span>
<li>OpenID Connect</li> <li>OpenID Connect</li>

View file

@ -4,8 +4,8 @@
{{ctx.Locale.Tr "admin.emails.email_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.emails.email_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui secondary filter menu gt-ac gt-mx-0"> <div class="ui secondary filter menu tw-items-center gt-mx-0">
<form class="ui form ignore-dirty gt-f1"> <form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword}} {{template "shared/search/combo" dict "Value" .Keyword}}
</form> </form>
<!-- Sort --> <!-- Sort -->
@ -15,10 +15,10 @@
</span> </span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
<a class="{{if or (eq .SortType "email") (not .SortType)}}active {{end}}item" href="{{$.Link}}?sort=email&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.email"}}</a> <a class="{{if or (eq .SortType "email") (not .SortType)}}active {{end}}item" href="?sort=email&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.email"}}</a>
<a class="{{if eq .SortType "reverseemail"}}active {{end}}item" href="{{$.Link}}?sort=reverseemail&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.email_reverse"}}</a> <a class="{{if eq .SortType "reverseemail"}}active {{end}}item" href="?sort=reverseemail&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.email_reverse"}}</a>
<a class="{{if eq .SortType "username"}}active {{end}}item" href="{{$.Link}}?sort=username&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.name"}}</a> <a class="{{if eq .SortType "username"}}active {{end}}item" href="?sort=username&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.name"}}</a>
<a class="{{if eq .SortType "reverseusername"}}active {{end}}item" href="{{$.Link}}?sort=reverseusername&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.name_reverse"}}</a> <a class="{{if eq .SortType "reverseusername"}}active {{end}}item" href="?sort=reverseusername&q={{$.Keyword}}">{{ctx.Locale.Tr "admin.emails.filter_sort.name_reverse"}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -17,10 +17,10 @@
<tbody> <tbody>
{{range .Notices}} {{range .Notices}}
<tr> <tr>
<td><div class="ui checkbox gt-df" data-id="{{.ID}}"><input type="checkbox"></div></td> <td><div class="ui checkbox tw-flex" data-id="{{.ID}}"><input type="checkbox"></div></td>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td>{{ctx.Locale.Tr .TrStr}}</td> <td>{{ctx.Locale.Tr .TrStr}}</td>
<td class="view-detail auto-ellipsis" style="width: 80%;"><span class="notice-description">{{.Description}}</span></td> <td class="view-detail auto-ellipsis tw-w-4/5"><span class="notice-description">{{.Description}}</span></td>
<td nowrap>{{DateTime "short" .CreatedUnix}}</td> <td nowrap>{{DateTime "short" .CreatedUnix}}</td>
<td class="view-detail"><a href="#">{{svg "octicon-note" 16}}</a></td> <td class="view-detail"><a href="#">{{svg "octicon-note" 16}}</a></td>
</tr> </tr>
@ -49,8 +49,8 @@
</div> </div>
</div> </div>
</div> </div>
<button class="ui small teal button" id="delete-selection" data-link="{{.Link}}/delete" data-redirect="{{.Link}}?page={{.Page.Paginater.Current}}"> <button class="ui small teal button" id="delete-selection" data-link="{{.Link}}/delete" data-redirect="?page={{.Page.Paginater.Current}}">
{{ctx.Locale.Tr "admin.notices.delete_selected"}} <span class="text">{{ctx.Locale.Tr "admin.notices.delete_selected"}}</span>
</button> </button>
</th> </th>
</tr> </tr>

View file

@ -7,8 +7,8 @@
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui secondary filter menu gt-ac gt-mx-0"> <div class="ui secondary filter menu tw-items-center gt-mx-0">
<form class="ui form ignore-dirty gt-f1"> <form class="ui form ignore-dirty tw-flex-1">
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.org_kind")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.org_kind")}}
</form> </form>
<!-- Sort --> <!-- Sort -->
@ -18,12 +18,12 @@
</span> </span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
<a class="{{if or (eq .SortType "oldest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if or (eq .SortType "oldest") (not .SortType)}}active {{end}}item" href="?sort=oldest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a> <a class="{{if eq .SortType "newest"}}active {{end}}item" href="?sort=newest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> <a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="?sort=alphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> <a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a> <a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="?sort=recentupdate&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a> <a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="?sort=leastupdate&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -30,7 +30,7 @@
- -
{{else}} {{else}}
{{$sum}} {{$sum}}
<form action="{{$.Link}}/remove-all-items" method="post" class="gt-dib gt-ml-4"> <form action="{{$.Link}}/remove-all-items" method="post" class="tw-inline-block gt-ml-4">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button class="ui tiny basic red button">{{ctx.Locale.Tr "admin.monitor.queue.settings.remove_all_items"}}</button> <button class="ui tiny basic red button">{{ctx.Locale.Tr "admin.monitor.queue.settings.remove_all_items"}}</button>
</form> </form>

View file

@ -20,8 +20,8 @@
{{if .Dirs}} {{if .Dirs}}
<div class="ui aligned divided list"> <div class="ui aligned divided list">
{{range $dirI, $dir := .Dirs}} {{range $dirI, $dir := .Dirs}}
<div class="item gt-df gt-ac"> <div class="item tw-flex tw-items-center">
<span class="gt-f1"> {{svg "octicon-file-directory-fill"}} {{$dir}}</span> <span class="tw-flex-1"> {{svg "octicon-file-directory-fill"}} {{$dir}}</span>
<div> <div>
<button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button> <button class="ui button primary show-modal gt-p-3" data-modal="#adopt-unadopted-modal-{{$dirI}}">{{svg "octicon-plus"}} {{ctx.Locale.Tr "repo.adopt_preexisting_label"}}</button>
<div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}"> <div class="ui g-modal-confirm modal" id="adopt-unadopted-modal-{{$dirI}}">

View file

@ -1,5 +1,5 @@
<div class="item"> <div class="item">
<div class="gt-df gt-ac"> <div class="tw-flex tw-items-center">
<div class="icon gt-ml-3 gt-mr-3"> <div class="icon gt-ml-3 gt-mr-3">
{{if eq .Process.Type "request"}} {{if eq .Process.Type "request"}}
{{svg "octicon-globe" 16}} {{svg "octicon-globe" 16}}
@ -11,7 +11,7 @@
{{svg "octicon-code" 16}} {{svg "octicon-code" 16}}
{{end}} {{end}}
</div> </div>
<div class="content gt-f1"> <div class="content tw-flex-1">
<div class="header">{{.Process.Description}}</div> <div class="header">{{.Process.Description}}</div>
<div class="description">{{if ne .Process.Type "none"}}{{TimeSince .Process.Start ctx.Locale}}{{end}}</div> <div class="description">{{if ne .Process.Type "none"}}{{TimeSince .Process.Start ctx.Locale}}{{end}}</div>
</div> </div>
@ -40,9 +40,9 @@
</summary> </summary>
<div class="list"> <div class="list">
{{range .Entry}} {{range .Entry}}
<div class="item gt-df gt-ac"> <div class="item tw-flex tw-items-center">
<span class="icon gt-mr-4">{{svg "octicon-dot-fill" 16}}</span> <span class="icon gt-mr-4">{{svg "octicon-dot-fill" 16}}</span>
<div class="content gt-f1"> <div class="content tw-flex-1">
<div class="header"><code>{{.Function}}</code></div> <div class="header"><code>{{.Function}}</code></div>
<div class="description"><code>{{.File}}:{{.Line}}</code></div> <div class="description"><code>{{.File}}:{{.Line}}</code></div>
</div> </div>

View file

@ -1,11 +1,11 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin monitor")}} {{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin monitor")}}
<div class="admin-setting-content"> <div class="admin-setting-content">
<div class="gt-df gt-ac"> <div class="tw-flex tw-items-center">
<div class="gt-f1"> <div class="tw-flex-1">
<div class="ui compact small menu"> <div class="ui compact small menu">
<a class="{{if eq .ShowGoroutineList "process"}}active {{end}}item" href="{{.Link}}?show=process">{{ctx.Locale.Tr "admin.monitor.process"}}</a> <a class="{{if eq .ShowGoroutineList "process"}}active {{end}}item" href="?show=process">{{ctx.Locale.Tr "admin.monitor.process"}}</a>
<a class="{{if eq .ShowGoroutineList "stacktrace"}}active {{end}}item" href="{{.Link}}?show=stacktrace">{{ctx.Locale.Tr "admin.monitor.stacktrace"}}</a> <a class="{{if eq .ShowGoroutineList "stacktrace"}}active {{end}}item" href="?show=stacktrace">{{ctx.Locale.Tr "admin.monitor.stacktrace"}}</a>
</div> </div>
</div> </div>
<form target="_blank" action="{{AppSubUrl}}/admin/monitor/diagnosis" class="ui form"> <form target="_blank" action="{{AppSubUrl}}/admin/monitor/diagnosis" class="ui form">

View file

@ -103,7 +103,7 @@
<td><span>{{ctx.Locale.Tr "admin.users.never_login"}}</span></td> <td><span>{{ctx.Locale.Tr "admin.users.never_login"}}</span></td>
{{end}} {{end}}
<td> <td>
<div class="gt-df gt-gap-3"> <div class="tw-flex tw-gap-2">
<a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">{{svg "octicon-person"}}</a> <a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">{{svg "octicon-person"}}</a>
<a href="{{$.Link}}/{{.ID}}/edit" data-tooltip-content="{{ctx.Locale.Tr "edit"}}">{{svg "octicon-pencil"}}</a> <a href="{{$.Link}}/{{.ID}}/edit" data-tooltip-content="{{ctx.Locale.Tr "edit"}}">{{svg "octicon-pencil"}}</a>
</div> </div>

View file

@ -2,7 +2,7 @@
<div class="admin-setting-content"> <div class="admin-setting-content">
<div class="admin-responsive-columns"> <div class="admin-responsive-columns">
<div class="gt-f1"> <div class="tw-flex-1">
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.Title}} {{.Title}}
<div class="ui right"> <div class="ui right">
@ -13,7 +13,7 @@
{{template "admin/user/view_details" .}} {{template "admin/user/view_details" .}}
</div> </div>
</div> </div>
<div class="gt-f1"> <div class="tw-flex-1">
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.emails"}} {{ctx.Locale.Tr "admin.emails"}}
<div class="ui right"> <div class="ui right">

View file

@ -56,7 +56,7 @@
<div class="navbar-right ui secondary menu"> <div class="navbar-right ui secondary menu">
{{if and .IsSigned .MustChangePassword}} {{if and .IsSigned .MustChangePassword}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> <div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text gt-df gt-ac"> <span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}} {{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}}
<span class="mobile-only gt-ml-3">{{.SignedUser.Name}}</span> <span class="mobile-only gt-ml-3">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span> <span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
@ -83,8 +83,8 @@
<span class="mobile-only gt-ml-3">{{ctx.Locale.Tr "active_stopwatch"}}</span> <span class="mobile-only gt-ml-3">{{ctx.Locale.Tr "active_stopwatch"}}</span>
</a> </a>
<div class="active-stopwatch-popup item tippy-target gt-p-3"> <div class="active-stopwatch-popup item tippy-target gt-p-3">
<div class="gt-df gt-ac"> <div class="tw-flex tw-items-center">
<a class="stopwatch-link gt-df gt-ac" href="{{.ActiveStopwatch.IssueLink}}"> <a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}">
{{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span> <span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
<span class="ui primary label stopwatch-time gt-my-0 gt-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}"> <span class="ui primary label stopwatch-time gt-my-0 gt-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
@ -142,7 +142,7 @@
</div><!-- end dropdown menu create new --> </div><!-- end dropdown menu create new -->
<div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}"> <div class="ui dropdown jump item gt-mx-0 gt-pr-3" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text gt-df gt-ac"> <span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}} {{ctx.AvatarUtils.Avatar .SignedUser 24 "gt-mr-2"}}
<span class="mobile-only gt-ml-3">{{.SignedUser.Name}}</span> <span class="mobile-only gt-ml-3">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span> <span class="not-mobile">{{svg "octicon-triangle-down"}}</span>

View file

@ -17,7 +17,7 @@
{{if eq .Num -1}} {{if eq .Num -1}}
<a class="disabled item">...</a> <a class="disabled item">...</a>
{{else}} {{else}}
<a class="{{if .IsCurrent}}active {{end}}item tw-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a> <a class="{{if .IsCurrent}}active {{end}}item tw-items-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
{{end}} {{end}}
{{end}} {{end}}
<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}> <a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>

View file

@ -73,7 +73,7 @@
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
<a class="muted" href="{{$.Link}}"> <a class="muted" href="{{$.Link}}">
<span class="flex-text-inline"><i class="color-icon gt-mr-3" style="background-color: aqua"></i>Go</span> <span class="flex-text-inline"><i class="color-icon gt-mr-3 tw-bg-blue"></i>Go</span>
</a> </a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-star" 16}}45000</a> <a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-star" 16}}45000</a>
<a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-git-branch" 16}}1234</a> <a class="text grey flex-text-inline" href="{{$.Link}}">{{svg "octicon-git-branch" 16}}1234</a>
@ -104,7 +104,7 @@
</div> </div>
<h1>If parent provides the padding/margin space:</h1> <h1>If parent provides the padding/margin space:</h1>
<div class="gt-border-secondary gt-py-4"> <div class="tw-border tw-border-secondary gt-py-4">
<div class="flex-list flex-space-fitted"> <div class="flex-list flex-space-fitted">
<div class="flex-item">item 1 (no padding top)</div> <div class="flex-item">item 1 (no padding top)</div>
<div class="flex-item">item 2 (no padding bottom)</div> <div class="flex-item">item 2 (no padding bottom)</div>

View file

@ -73,7 +73,7 @@
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" "I know and must do this is dangerous operation")}} {{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" "I know and must do this is dangerous operation")}}
</div> </div>
<div class="modal-buttons flex-text-block gt-fw"></div> <div class="modal-buttons flex-text-block tw-flex-wrap"></div>
<script type="module"> <script type="module">
for (const el of $('.ui.modal')) { for (const el of $('.ui.modal')) {
const $btn = $('<button>').text(`${el.id}`).on('click', () => { const $btn = $('<button>').text(`${el.id}`).on('click', () => {

View file

@ -95,8 +95,8 @@
<div> <div>
<h1>Loading</h1> <h1>Loading</h1>
<div class="is-loading small-loading-icon gt-border-secondary gt-py-2"><span>loading ...</span></div> <div class="is-loading small-loading-icon tw-border tw-border-secondary gt-py-2"><span>loading ...</span></div>
<div class="is-loading gt-border-secondary gt-py-4"> <div class="is-loading tw-border tw-border-secondary gt-py-4">
<p>loading ...</p> <p>loading ...</p>
<p>loading ...</p> <p>loading ...</p>
<p>loading ...</p> <p>loading ...</p>

View file

@ -1,10 +1,10 @@
{{template "base/head" .}} {{template "base/head" .}}
<div class="page-content devtest"> <div class="page-content devtest">
<div class="gt-df"> <div class="tw-flex">
<div style="width: 80%; "> <div class="tw-w-4/5">
hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello
</div> </div>
<div style="width: 20%;"> <div class="tw-w-1/5">
{{template "devtest/tmplerr-sub" .}} {{template "devtest/tmplerr-sub" .}}
</div> </div>
</div> </div>

View file

@ -32,7 +32,7 @@
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
{{if .PrimaryLanguage}} {{if .PrimaryLanguage}}
<a class="muted" href="{{$.Link}}?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}{{if $.TabName}}&tab={{$.TabName}}{{end}}"> <a class="muted" href="?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}{{if $.TabName}}&tab={{$.TabName}}{{end}}">
<span class="flex-text-inline"><i class="color-icon gt-mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{.PrimaryLanguage.Language}}</span> <span class="flex-text-inline"><i class="color-icon gt-mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{.PrimaryLanguage.Language}}</span>
</a> </a>
{{end}} {{end}}

View file

@ -1,5 +1,5 @@
<div class="ui small secondary filter menu gt-ac gt-mx-0"> <div class="ui small secondary filter menu tw-items-center gt-mx-0">
<form class="ui form ignore-dirty gt-f1"> <form class="ui form ignore-dirty tw-flex-1">
{{if .PageIsExploreUsers}} {{if .PageIsExploreUsers}}
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.user_kind")}}
{{else}} {{else}}
@ -13,10 +13,10 @@
</span> </span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu"> <div class="menu">
<a class="{{if eq .SortType "newest"}}active {{end}}item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a> <a class="{{if eq .SortType "newest"}}active {{end}}item" href="?sort=newest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if eq .SortType "oldest"}}active {{end}}item" href="?sort=oldest&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> <a class="{{if eq .SortType "alphabetically"}}active {{end}}item" href="?sort=alphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> <a class="{{if eq .SortType "reversealphabetically"}}active {{end}}item" href="?sort=reversealphabetically&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="flex-list"> <div class="flex-list">
{{range .Users}} {{range .Users}}
<div class="flex-item gt-ac"> <div class="flex-item tw-items-center">
<div class="flex-item-leading"> <div class="flex-item-leading">
{{ctx.AvatarUtils.Avatar . 48}} {{ctx.AvatarUtils.Avatar . 48}}
</div> </div>

View file

@ -1,13 +1,13 @@
<div class="ui container gt-df"> <div class="ui container tw-flex">
{{ctx.AvatarUtils.Avatar .Org 100 "org-avatar"}} {{ctx.AvatarUtils.Avatar .Org 100 "org-avatar"}}
<div id="org-info" class="gt-df gt-fc"> <div id="org-info" class="tw-flex tw-flex-col">
<div class="ui header"> <div class="ui header">
{{.Org.DisplayName}} {{.Org.DisplayName}}
<span class="org-visibility"> <span class="org-visibility">
{{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}} {{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}}
{{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}} {{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}}
</span> </span>
<span class="gt-df gt-ac gt-gap-2 gt-ml-auto gt-font-16 tw-whitespace-nowrap"> <span class="tw-flex tw-items-center tw-gap-1 tw-ml-auto tw-text-16 tw-whitespace-nowrap">
{{if .EnableFeed}} {{if .EnableFeed}}
<a class="ui basic label button gt-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}"> <a class="ui basic label button gt-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss" 24}} {{svg "octicon-rss" 24}}

Some files were not shown because too many files have changed in this diff Show more