ソースを参照

Merge branch 'main' into pacman-packages

pull/25396/head
Danila Fominykh 6ヶ月前
コミット
a4af98e35f
コミッターのメールアドレスに関連付けられたアカウントが存在しません
100個のファイルの変更1396行の追加1175行の削除
  1. 67
    59
      .eslintrc.yaml
  2. 0
    2
      .golangci.yml
  3. 2
    2
      Makefile
  4. 1
    1
      cmd/admin_auth.go
  5. 16
    12
      contrib/environment-to-ini/environment-to-ini.go
  6. 7
    0
      custom/conf/app.example.ini
  7. 3
    1
      docs/content/administration/config-cheat-sheet.en-us.md
  8. 0
    31
      docs/content/usage/actions/comparison.en-us.md
  9. 0
    31
      docs/content/usage/actions/comparison.zh-cn.md
  10. 2
    1
      docs/content/usage/profile-readme.en-us.md
  11. 1
    1
      go.mod
  12. 2
    6
      go.sum
  13. 24
    0
      models/actions/runner.go
  14. 1
    1
      models/activities/statistic.go
  15. 0
    9
      models/auth/oauth2.go
  16. 22
    29
      models/auth/source.go
  17. 2
    10
      models/issues/issue.go
  18. 10
    0
      models/issues/review.go
  19. 30
    0
      models/issues/review_test.go
  20. 11
    19
      models/org.go
  21. 1
    1
      models/org_team.go
  22. 0
    27
      modules/context/api.go
  23. 1
    1
      modules/context/repo.go
  24. 7
    0
      modules/doctor/dbconsistency.go
  25. 1
    1
      modules/markup/html.go
  26. 12
    0
      modules/markup/html_test.go
  27. 4
    4
      modules/markup/orgmode/orgmode.go
  28. 11
    7
      modules/markup/orgmode/orgmode_test.go
  29. 1
    0
      modules/setting/ui.go
  30. 2
    7
      modules/ssh/ssh.go
  31. 2
    0
      options/locale/locale_en-US.ini
  32. 4
    0
      options/locale/locale_sk-SK.ini
  33. 646
    525
      package-lock.json
  34. 20
    19
      package.json
  35. 1
    1
      poetry.lock
  36. 0
    36
      routers/api/v1/api.go
  37. 7
    7
      routers/web/admin/auths.go
  38. 1
    1
      routers/web/admin/orgs.go
  39. 11
    4
      routers/web/admin/users.go
  40. 4
    8
      routers/web/auth/auth.go
  41. 1
    1
      routers/web/explore/org.go
  42. 7
    2
      routers/web/explore/repo.go
  43. 8
    9
      routers/web/explore/user.go
  44. 43
    0
      routers/web/githttp.go
  45. 26
    0
      routers/web/org/home.go
  46. 0
    0
      routers/web/repo/githttp.go
  47. 0
    0
      routers/web/repo/githttp_test.go
  48. 2
    5
      routers/web/repo/view.go
  49. 25
    3
      routers/web/user/setting/security/security.go
  50. 4
    14
      routers/web/web.go
  51. 22
    2
      services/auth/basic.go
  52. 4
    1
      services/auth/signin.go
  53. 8
    1
      services/auth/source/oauth2/init.go
  54. 27
    20
      services/auth/source/oauth2/providers.go
  55. 4
    1
      services/auth/sspi.go
  56. 1
    1
      services/auth/sync.go
  57. 6
    4
      services/migrations/gitea_downloader.go
  58. 3
    2
      services/migrations/gitlab.go
  59. 2
    0
      services/user/delete.go
  60. 3
    0
      templates/org/home.tmpl
  61. 1
    1
      templates/repo/commit_page.tmpl
  62. 2
    10
      templates/repo/commit_statuses.tmpl
  63. 1
    1
      templates/repo/commits_list.tmpl
  64. 1
    1
      templates/repo/commits_list_small.tmpl
  65. 3
    3
      templates/repo/diff/box.tmpl
  66. 4
    4
      templates/repo/home.tmpl
  67. 7
    0
      templates/repo/issue/card.tmpl
  68. 5
    5
      templates/repo/issue/view_content.tmpl
  69. 23
    24
      templates/repo/issue/view_content/comments.tmpl
  70. 8
    1
      templates/repo/issue/view_content/pull.tmpl
  71. 42
    25
      templates/repo/pulls/status.tmpl
  72. 1
    1
      templates/repo/release/list.tmpl
  73. 1
    1
      templates/repo/view_list.tmpl
  74. 1
    1
      templates/shared/issuelist.tmpl
  75. 3
    4
      templates/user/auth/signin_inner.tmpl
  76. 3
    4
      templates/user/auth/signup_inner.tmpl
  77. 3
    3
      templates/user/dashboard/issues.tmpl
  78. 3
    3
      templates/user/dashboard/milestones.tmpl
  79. 55
    0
      tests/integration/api_twofa_test.go
  80. 2
    2
      web_src/css/modules/divider.css
  81. 36
    22
      web_src/css/modules/tippy.css
  82. 32
    18
      web_src/css/repo.css
  83. 1
    1
      web_src/css/shared/flex-list.css
  84. 0
    1
      web_src/js/components/DashboardRepoList.vue
  85. 1
    1
      web_src/js/components/DiffCommitSelector.vue
  86. 4
    1
      web_src/js/features/common-global.js
  87. 0
    1
      web_src/js/features/comp/ImagePaste.js
  88. 0
    1
      web_src/js/features/file-fold.js
  89. 0
    1
      web_src/js/features/org-team.js
  90. 0
    3
      web_src/js/features/pull-view-file.js
  91. 1
    0
      web_src/js/features/repo-commit.js
  92. 0
    1
      web_src/js/features/repo-editor.js
  93. 0
    1
      web_src/js/features/repo-findfile.js
  94. 1
    1
      web_src/js/features/repo-home.js
  95. 10
    0
      web_src/js/features/repo-issue-pr-status.js
  96. 0
    2
      web_src/js/features/repo-issue.js
  97. 2
    2
      web_src/js/features/repo-legacy.js
  98. 0
    1
      web_src/js/features/repo-settings.js
  99. 6
    93
      web_src/js/modules/fomantic.js
  100. 0
    0
      web_src/js/modules/fomantic/api.js

+ 67
- 59
.eslintrc.yaml ファイルの表示

@@ -10,6 +10,7 @@ parserOptions:

plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js"
- eslint-plugin-array-func
- eslint-plugin-i
- eslint-plugin-jquery
@@ -114,11 +115,74 @@ rules:
"@eslint-community/eslint-comments/no-unused-enable": [2]
"@eslint-community/eslint-comments/no-use": [0]
"@eslint-community/eslint-comments/require-description": [0]
"@stylistic/js/array-bracket-newline": [0]
"@stylistic/js/array-bracket-spacing": [2, never]
"@stylistic/js/array-element-newline": [0]
"@stylistic/js/arrow-parens": [2, always]
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}]
"@stylistic/js/block-spacing": [0]
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}]
"@stylistic/js/comma-dangle": [2, only-multiline]
"@stylistic/js/comma-spacing": [2, {before: false, after: true}]
"@stylistic/js/comma-style": [2, last]
"@stylistic/js/computed-property-spacing": [2, never]
"@stylistic/js/dot-location": [2, property]
"@stylistic/js/eol-last": [2]
"@stylistic/js/func-call-spacing": [2, never]
"@stylistic/js/function-call-argument-newline": [0]
"@stylistic/js/function-paren-newline": [0]
"@stylistic/js/generator-star-spacing": [0]
"@stylistic/js/implicit-arrow-linebreak": [0]
"@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}]
"@stylistic/js/key-spacing": [2]
"@stylistic/js/keyword-spacing": [2]
"@stylistic/js/linebreak-style": [2, unix]
"@stylistic/js/lines-around-comment": [0]
"@stylistic/js/lines-between-class-members": [0]
"@stylistic/js/max-len": [0]
"@stylistic/js/max-statements-per-line": [0]
"@stylistic/js/multiline-ternary": [0]
"@stylistic/js/new-parens": [2]
"@stylistic/js/newline-per-chained-call": [0]
"@stylistic/js/no-confusing-arrow": [0]
"@stylistic/js/no-extra-parens": [0]
"@stylistic/js/no-extra-semi": [2]
"@stylistic/js/no-floating-decimal": [0]
"@stylistic/js/no-mixed-operators": [0]
"@stylistic/js/no-mixed-spaces-and-tabs": [2]
"@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
"@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}]
"@stylistic/js/no-tabs": [2]
"@stylistic/js/no-trailing-spaces": [2]
"@stylistic/js/no-whitespace-before-property": [2]
"@stylistic/js/nonblock-statement-body-position": [2]
"@stylistic/js/object-curly-newline": [0]
"@stylistic/js/object-curly-spacing": [2, never]
"@stylistic/js/object-property-newline": [0]
"@stylistic/js/one-var-declaration-per-line": [0]
"@stylistic/js/operator-linebreak": [2, after]
"@stylistic/js/padded-blocks": [2, never]
"@stylistic/js/padding-line-between-statements": [0]
"@stylistic/js/quote-props": [0]
"@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
"@stylistic/js/rest-spread-spacing": [2, never]
"@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}]
"@stylistic/js/semi-spacing": [2, {before: false, after: true}]
"@stylistic/js/semi-style": [2, last]
"@stylistic/js/space-before-blocks": [2, always]
"@stylistic/js/space-before-function-paren": [0]
"@stylistic/js/space-in-parens": [2, never]
"@stylistic/js/space-infix-ops": [2]
"@stylistic/js/space-unary-ops": [2]
"@stylistic/js/spaced-comment": [2, always]
"@stylistic/js/switch-colon-spacing": [2]
"@stylistic/js/template-curly-spacing": [2, never]
"@stylistic/js/template-tag-spacing": [2, never]
"@stylistic/js/wrap-iife": [2, inside]
"@stylistic/js/wrap-regex": [0]
"@stylistic/js/yield-star-spacing": [2, after]
accessor-pairs: [2]
array-bracket-newline: [0]
array-bracket-spacing: [2, never]
array-callback-return: [2, {checkForEach: true}]
array-element-newline: [0]
array-func/avoid-reverse: [2]
array-func/from-map: [2]
array-func/no-unnecessary-this-arg: [2]
@@ -126,18 +190,11 @@ rules:
array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map
array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat
arrow-body-style: [0]
arrow-parens: [2, always]
arrow-spacing: [2, {before: true, after: true}]
block-scoped-var: [2]
brace-style: [2, 1tbs, {allowSingleLine: true}]
camelcase: [0]
capitalized-comments: [0]
class-methods-use-this: [0]
comma-dangle: [2, only-multiline]
comma-spacing: [2, {before: false, after: true}]
comma-style: [2, last]
complexity: [0]
computed-property-spacing: [2, never]
consistent-return: [0]
consistent-this: [0]
constructor-super: [2]
@@ -145,25 +202,18 @@ rules:
default-case-last: [2]
default-case: [0]
default-param-last: [0]
dot-location: [2, property]
dot-notation: [0]
eol-last: [2]
eqeqeq: [2]
for-direction: [2]
func-call-spacing: [2, never]
func-name-matching: [2]
func-names: [0]
func-style: [0]
function-call-argument-newline: [0]
function-paren-newline: [0]
generator-star-spacing: [0]
getter-return: [2]
grouped-accessor-pairs: [2]
guard-for-in: [0]
id-blacklist: [0]
id-length: [0]
id-match: [0]
implicit-arrow-linebreak: [0]
i/consistent-type-specifier-style: [0]
i/default: [0]
i/dynamic-import-chunkname: [0]
@@ -207,7 +257,6 @@ rules:
i/order: [0]
i/prefer-default-export: [0]
i/unambiguous: [0]
indent: [2, 2, {SwitchCase: 1}]
init-declarations: [0]
jquery/no-ajax-events: [2]
jquery/no-ajax: [0]
@@ -258,27 +307,17 @@ rules:
jquery/no-val: [0]
jquery/no-when: [2]
jquery/no-wrap: [2]
key-spacing: [2]
keyword-spacing: [2]
line-comment-position: [0]
linebreak-style: [2, unix]
lines-around-comment: [0]
lines-between-class-members: [0]
logical-assignment-operators: [0]
max-classes-per-file: [0]
max-depth: [0]
max-len: [0]
max-lines-per-function: [0]
max-lines: [0]
max-nested-callbacks: [0]
max-params: [0]
max-statements-per-line: [0]
max-statements: [0]
multiline-comment-style: [2, separate-lines]
multiline-ternary: [0]
new-cap: [0]
new-parens: [2]
newline-per-chained-call: [0]
no-alert: [0]
no-array-constructor: [2]
no-async-promise-executor: [0]
@@ -290,7 +329,6 @@ rules:
no-class-assign: [2]
no-compare-neg-zero: [2]
no-cond-assign: [2, except-parens]
no-confusing-arrow: [0]
no-console: [1, {allow: [debug, info, warn, error]}]
no-const-assign: [2]
no-constant-binary-expression: [2]
@@ -320,10 +358,7 @@ rules:
no-extra-bind: [2]
no-extra-boolean-cast: [2]
no-extra-label: [0]
no-extra-parens: [0]
no-extra-semi: [2]
no-fallthrough: [2]
no-floating-decimal: [0]
no-func-assign: [2]
no-global-assign: [2]
no-implicit-coercion: [2]
@@ -437,10 +472,7 @@ rules:
no-loss-of-precision: [2]
no-magic-numbers: [0]
no-misleading-character-class: [2]
no-mixed-operators: [0]
no-mixed-spaces-and-tabs: [2]
no-multi-assign: [0]
no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
no-multi-str: [2]
no-negated-condition: [0]
no-nested-ternary: [0]
@@ -474,12 +506,10 @@ rules:
no-shadow-restricted-names: [2]
no-shadow: [0]
no-sparse-arrays: [2]
no-tabs: [2]
no-template-curly-in-string: [2]
no-ternary: [0]
no-this-before-super: [2]
no-throw-literal: [2]
no-trailing-spaces: [2]
no-undef-init: [2]
no-undef: [2, {typeof: true}]
no-undefined: [0]
@@ -509,18 +539,12 @@ rules:
no-var: [2]
no-void: [2]
no-warning-comments: [0]
no-whitespace-before-property: [2]
no-with: [0] # handled by no-restricted-syntax
nonblock-statement-body-position: [2]
object-curly-newline: [0]
object-curly-spacing: [2, never]
object-shorthand: [2, always]
one-var-declaration-per-line: [0]
one-var: [0]
operator-assignment: [2, always]
operator-linebreak: [2, after]
padded-blocks: [2, never]
padding-line-between-statements: [0]
prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}]
prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}]
prefer-destructuring: [0]
@@ -534,7 +558,6 @@ rules:
prefer-rest-params: [2]
prefer-spread: [2]
prefer-template: [2]
quote-props: [0]
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
radix: [2, as-needed]
regexp/confusing-quantifier: [2]
@@ -620,10 +643,6 @@ rules:
require-await: [0]
require-unicode-regexp: [0]
require-yield: [2]
rest-spread-spacing: [2, never]
semi-spacing: [2, {before: false, after: true}]
semi-style: [2, last]
semi: [2, always, {omitLastInOneLineBlock: true}]
sonarjs/cognitive-complexity: [0]
sonarjs/elseif-without-else: [0]
sonarjs/max-switch-cases: [0]
@@ -659,16 +678,8 @@ rules:
sort-imports: [0]
sort-keys: [0]
sort-vars: [0]
space-before-blocks: [2, always]
space-in-parens: [2, never]
space-infix-ops: [2]
space-unary-ops: [2]
spaced-comment: [2, always]
strict: [0]
switch-colon-spacing: [2]
symbol-description: [2]
template-curly-spacing: [2, never]
template-tag-spacing: [2, never]
unicode-bom: [2, never]
unicorn/better-regex: [0]
unicorn/catch-error-name: [0]
@@ -807,7 +818,4 @@ rules:
wc/no-typos: [2]
wc/require-listener-teardown: [2]
wc/tag-name-matches-class: [2]
wrap-iife: [2, inside]
wrap-regex: [0]
yield-star-spacing: [2, after]
yoda: [2, never]

+ 0
- 2
.golangci.yml ファイルの表示

@@ -29,7 +29,6 @@ linters:
fast: false

run:
go: "1.21"
timeout: 10m
skip-dirs:
- node_modules
@@ -75,7 +74,6 @@ linters-settings:
- name: modifies-value-receiver
gofumpt:
extra-rules: true
lang-version: "1.21"
depguard:
rules:
main:

+ 2
- 2
Makefile ファイルの表示

@@ -875,7 +875,7 @@ node_modules: package-lock.json
@touch node_modules

.venv: poetry.lock
poetry install
poetry install --no-root
@touch .venv

.PHONY: update
@@ -892,7 +892,7 @@ update-js: node-check | node_modules
update-py: node-check | node_modules
npx updates -u -f pyproject.toml
rm -rf .venv poetry.lock
poetry install
poetry install --no-root
@touch .venv

.PHONY: fomantic

+ 1
- 1
cmd/admin_auth.go ファイルの表示

@@ -62,7 +62,7 @@ func runListAuth(c *cli.Context) error {
return err
}

authSources, err := auth_model.Sources(ctx)
authSources, err := auth_model.FindSources(ctx, auth_model.FindSourcesOptions{})
if err != nil {
return err
}

+ 16
- 12
contrib/environment-to-ini/environment-to-ini.go ファイルの表示

@@ -47,24 +47,28 @@ func main() {
on the configuration cheat sheet.`
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "custom-path, C",
Value: setting.CustomPath,
Usage: "Custom path file path",
Name: "custom-path",
Aliases: []string{"C"},
Value: setting.CustomPath,
Usage: "Custom path file path",
},
&cli.StringFlag{
Name: "config, c",
Value: setting.CustomConf,
Usage: "Custom configuration file path",
Name: "config",
Aliases: []string{"c"},
Value: setting.CustomConf,
Usage: "Custom configuration file path",
},
&cli.StringFlag{
Name: "work-path, w",
Value: setting.AppWorkPath,
Usage: "Set the gitea working path",
Name: "work-path",
Aliases: []string{"w"},
Value: setting.AppWorkPath,
Usage: "Set the gitea working path",
},
&cli.StringFlag{
Name: "out, o",
Value: "",
Usage: "Destination file to write to",
Name: "out",
Aliases: []string{"o"},
Value: "",
Usage: "Destination file to write to",
},
}
app.Action = runEnvironmentToIni

+ 7
- 0
custom/conf/app.example.ini ファイルの表示

@@ -1221,6 +1221,9 @@ LEVEL = Info
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes
;;
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
;REACTION_MAX_USER_NUM = 10
;;
;; Additional Emojis not defined in the utf8 standard
;; By default we support gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and add it to this config.
;; Dont mistake it for Reactions.
@@ -1235,6 +1238,10 @@ LEVEL = Info
;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
;ONLY_SHOW_RELEVANT_REPOS = false
;;
;; Change the sort type of the explore pages.
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest".
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

+ 3
- 1
docs/content/administration/config-cheat-sheet.en-us.md ファイルの表示

@@ -223,13 +223,15 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
- `REACTION_MAX_USER_NUM`: **10**: Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
- `CUSTOM_EMOJIS`: **gitea, codeberg, gitlab, git, github, gogs**: Additional Emojis not defined in the utf8 standard.
By default, we support Gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and
add it to this config.
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
- `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
- `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
- `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest"

### UI - Admin (`ui.admin`)


+ 0
- 31
docs/content/usage/actions/comparison.en-us.md ファイルの表示

@@ -130,34 +130,3 @@ More details about the `[actions].DEFAULT_ACTIONS_URL` configuration can be foun

Context availability is not checked, so you can use the env context on more places.
See [Context availability](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability).

## Known issues

### `docker/build-push-action@v4`

See [act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294).

`ACTIONS_RUNTIME_TOKEN` is a random string in Gitea Actions, not a JWT.
But the `docker/build-push-action@v4` tries to parse the token as JWT and doesn't handle the error, so the job fails.

There are two workarounds:

Set the `ACTIONS_RUNTIME_TOKEN` to empty manually, like:

``` yml
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: ''
with:
...
```

The bug has been fixed in a newer [commit](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad), but it has not been released. So you could use the latest version by specifying the branch name, like:

``` yml
- name: Build and push
uses: docker/build-push-action@master
with:
...
```

+ 0
- 31
docs/content/usage/actions/comparison.zh-cn.md ファイルの表示

@@ -132,34 +132,3 @@ Gitea Actions目前不支持此功能。

不检查上下文可用性,因此您可以在更多地方使用env上下文。
请参阅[上下文可用性](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability)。

## 已知问题

### `docker/build-push-action@v4`

请参阅[act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294)。

`ACTIONS_RUNTIME_TOKEN`在Gitea Actions中是一个随机字符串,而不是JWT。
但是`DOCKER/BUILD-PUSH-ACTION@V4尝试将令牌解析为JWT,并且不处理错误,因此Job失败。

有两种解决方法:

手动将`ACTIONS_RUNTIME_TOKEN`设置为空字符串,例如:

``` yml
- name: Build and push
uses: docker/build-push-action@v4
env:
ACTIONS_RUNTIME_TOKEN: ''
with:
...
```

该问题已在较新的[提交](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad)中修复,但尚未发布。因此,您可以通过指定分支名称来使用最新版本,例如:

``` yml
- name: Build and push
uses: docker/build-push-action@master
with:
...
```

+ 2
- 1
docs/content/usage/profile-readme.en-us.md ファイルの表示

@@ -15,6 +15,7 @@ menu:

# Profile READMEs

To display a Markdown file in your Gitea profile page, simply create a repository named `.profile` and add a new file called `README.md`. Gitea will automatically display the contents of the file on your profile, above your repositories.
To display a Markdown file in your Gitea user or organization profile page, create a repository named `.profile` and add a new file named `README.md` to it.
Gitea will automatically display the contents of the file on your profile, in a new "Overview" above your repositories.

Making the `.profile` repository private will hide the Profile README.

+ 1
- 1
go.mod ファイルの表示

@@ -36,7 +36,7 @@ require (
github.com/ethantkoenig/rupture v1.0.1
github.com/felixge/fgprof v0.9.3
github.com/fsnotify/fsnotify v1.6.0
github.com/gliderlabs/ssh v0.3.5
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.0.10

+ 2
- 6
go.sum ファイルの表示

@@ -329,8 +329,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94=
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46/go.mod h1:i/TCLcdiX9Up/vs+Rp8c3yMbqp2Y4Y7Nh9uzGFCa5pM=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo=
@@ -1237,7 +1237,6 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -1337,9 +1336,7 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1353,7 +1350,6 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=

+ 24
- 0
models/actions/runner.go ファイルの表示

@@ -266,3 +266,27 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
_, err := db.GetEngine(ctx).Insert(t)
return err
}

func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
// Only affect action runners were a owner ID is set, as actions runners
// could also be created on a repository.
return db.GetEngine(ctx).Table("action_runner").
Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
Where("`action_runner`.owner_id != ?", 0).
And(builder.IsNull{"`user`.id"}).
Count(new(ActionRunner))
}

func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
subQuery := builder.Select("`action_runner`.id").
From("`action_runner`").
Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
Where(builder.Neq{"`action_runner`.owner_id": 0}).
And(builder.IsNull{"`user`.id"})
b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
res, err := db.GetEngine(ctx).Exec(b)
if err != nil {
return 0, err
}
return res.RowsAffected()
}

+ 1
- 1
models/activities/statistic.go ファイルの表示

@@ -102,7 +102,7 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
stats.Counter.Follow, _ = e.Count(new(user_model.Follow))
stats.Counter.Mirror, _ = e.Count(new(repo_model.Mirror))
stats.Counter.Release, _ = e.Count(new(repo_model.Release))
stats.Counter.AuthSource = auth.CountSources(ctx)
stats.Counter.AuthSource = auth.CountSources(ctx, auth.FindSourcesOptions{})
stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook))
stats.Counter.Milestone, _ = e.Count(new(issues_model.Milestone))
stats.Counter.Label, _ = e.Count(new(issues_model.Label))

+ 0
- 9
models/auth/oauth2.go ファイルの表示

@@ -631,15 +631,6 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error {
return util.ErrNotExist
}

// GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderSources(ctx context.Context) ([]*Source, error) {
sources := make([]*Source, 0, 1)
if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil {
return nil, err
}
return sources, nil
}

// GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
func GetActiveOAuth2SourceByName(ctx context.Context, name string) (*Source, error) {
authSource := new(Source)

+ 22
- 29
models/auth/source.go ファイルの表示

@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/convert"
)
@@ -233,44 +234,33 @@ func CreateSource(ctx context.Context, source *Source) error {
err = registerableSource.RegisterSource()
if err != nil {
// remove the AuthSource in case of errors while registering configuration
if _, err := db.GetEngine(ctx).Delete(source); err != nil {
if _, err := db.GetEngine(ctx).ID(source.ID).Delete(new(Source)); err != nil {
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
}
}
return err
}

// Sources returns a slice of all login sources found in DB.
func Sources(ctx context.Context) ([]*Source, error) {
auths := make([]*Source, 0, 6)
return auths, db.GetEngine(ctx).Find(&auths)
type FindSourcesOptions struct {
IsActive util.OptionalBool
LoginType Type
}

// SourcesByType returns all sources of the specified type
func SourcesByType(ctx context.Context, loginType Type) ([]*Source, error) {
sources := make([]*Source, 0, 1)
if err := db.GetEngine(ctx).Where("type = ?", loginType).Find(&sources); err != nil {
return nil, err
func (opts FindSourcesOptions) ToConds() builder.Cond {
conds := builder.NewCond()
if !opts.IsActive.IsNone() {
conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
}
return sources, nil
}

// AllActiveSources returns all active sources
func AllActiveSources(ctx context.Context) ([]*Source, error) {
sources := make([]*Source, 0, 5)
if err := db.GetEngine(ctx).Where("is_active = ?", true).Find(&sources); err != nil {
return nil, err
if opts.LoginType != NoType {
conds = conds.And(builder.Eq{"`type`": opts.LoginType})
}
return sources, nil
return conds
}

// ActiveSources returns all active sources of the specified type
func ActiveSources(ctx context.Context, tp Type) ([]*Source, error) {
sources := make([]*Source, 0, 1)
if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, tp).Find(&sources); err != nil {
return nil, err
}
return sources, nil
// FindSources returns a slice of login sources found in DB according to given conditions.
func FindSources(ctx context.Context, opts FindSourcesOptions) ([]*Source, error) {
auths := make([]*Source, 0, 6)
return auths, db.GetEngine(ctx).Where(opts.ToConds()).Find(&auths)
}

// IsSSPIEnabled returns true if there is at least one activated login
@@ -279,7 +269,10 @@ func IsSSPIEnabled(ctx context.Context) bool {
if !db.HasEngine {
return false
}
sources, err := ActiveSources(ctx, SSPI)
sources, err := FindSources(ctx, FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
LoginType: SSPI,
})
if err != nil {
log.Error("ActiveSources: %v", err)
return false
@@ -354,8 +347,8 @@ func UpdateSource(ctx context.Context, source *Source) error {
}

// CountSources returns number of login sources.
func CountSources(ctx context.Context) int64 {
count, _ := db.GetEngine(ctx).Count(new(Source))
func CountSources(ctx context.Context, opts FindSourcesOptions) int64 {
count, _ := db.GetEngine(ctx).Where(opts.ToConds()).Count(new(Source))
return count
}


+ 2
- 10
models/issues/issue.go ファイルの表示

@@ -142,22 +142,14 @@ type Issue struct {
}

var (
issueTasksPat *regexp.Regexp
issueTasksDonePat *regexp.Regexp
)

const (
issueTasksRegexpStr = `(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`
issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`
issueTasksPat = regexp.MustCompile(`(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`)
issueTasksDonePat = regexp.MustCompile(`(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`)
)

// IssueIndex represents the issue index table
type IssueIndex db.ResourceIndex

func init() {
issueTasksPat = regexp.MustCompile(issueTasksRegexpStr)
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr)

db.RegisterModel(new(Issue))
db.RegisterModel(new(IssueIndex))
}

+ 10
- 0
models/issues/review.go ファイルの表示

@@ -897,6 +897,16 @@ func DeleteReview(ctx context.Context, r *Review) error {
return err
}

opts = FindCommentsOptions{
Type: CommentTypeDismissReview,
IssueID: r.IssueID,
ReviewID: r.ID,
}

if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil {
return err
}

if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil {
return err
}

+ 30
- 0
models/issues/review_test.go ファイルの表示

@@ -8,6 +8,7 @@ import (

"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"

@@ -258,3 +259,32 @@ func TestDeleteReview(t *testing.T) {
assert.NoError(t, err)
assert.True(t, review1.Official)
}

func TestDeleteDismissedReview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
Content: "reject",
Type: issues_model.ReviewTypeReject,
Official: false,
Issue: issue,
Reviewer: user,
})
assert.NoError(t, err)
assert.NoError(t, issues_model.DismissReview(db.DefaultContext, review, true))
comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeDismissReview,
Doer: user,
Repo: repo,
Issue: issue,
ReviewID: review.ID,
Content: "dismiss",
})
assert.NoError(t, err)
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID})
assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review))
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID})
}

+ 11
- 19
models/org.go ファイルの表示

@@ -14,12 +14,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
)

func removeOrgUser(ctx context.Context, orgID, userID int64) error {
// RemoveOrgUser removes user from given organization.
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
ou := new(organization.OrgUser)

sess := db.GetEngine(ctx)

has, err := sess.
has, err := db.GetEngine(ctx).
Where("uid=?", userID).
And("org_id=?", orgID).
Get(ou)
@@ -52,7 +51,13 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
}
}

if _, err := sess.ID(ou.ID).Delete(ou); err != nil {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()

if _, err := db.GetEngine(ctx).ID(ou.ID).Delete(ou); err != nil {
return err
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
return err
@@ -74,7 +79,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
}

if len(repoIDs) > 0 {
if _, err = sess.
if _, err = db.GetEngine(ctx).
Where("user_id = ?", userID).
In("repo_id", repoIDs).
Delete(new(access_model.Access)); err != nil {
@@ -93,18 +98,5 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
}
}

return nil
}

// RemoveOrgUser removes user from given organization.
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
if err := removeOrgUser(ctx, orgID, userID); err != nil {
return err
}
return committer.Commit()
}

+ 1
- 1
models/org_team.go ファイルの表示

@@ -502,7 +502,7 @@ func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error {
}); err != nil {
return err
} else if count == 0 {
return removeOrgUser(ctx, orgID, userID)
return RemoveOrgUser(ctx, orgID, userID)
}
return nil
}

+ 0
- 27
modules/context/api.go ファイルの表示

@@ -11,7 +11,6 @@ import (
"net/url"
"strings"

"code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -211,32 +210,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
}
}

// CheckForOTP validates OTP
func (ctx *APIContext) CheckForOTP() {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
return // Skip 2FA
}

otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
twofa, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if err != nil {
if auth.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err)
return
}
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err)
return
}
if !ok {
ctx.Error(http.StatusUnauthorized, "", nil)
return
}
}

// APIContexter returns apicontext as middleware
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {

+ 1
- 1
modules/context/repo.go ファイルの表示

@@ -850,7 +850,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return getRefNameFromPath(ctx, repo, path, func(s string) bool {
b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s)
if err != nil {
log.Error("FindRenamedBranch", err)
log.Error("FindRenamedBranch: %v", err)
return false
}


+ 7
- 0
modules/doctor/dbconsistency.go ファイルの表示

@@ -6,6 +6,7 @@ package doctor
import (
"context"

actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
@@ -151,6 +152,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
Fixer: activities_model.FixActionCreatedUnixString,
FixedMessage: "Set to zero",
},
{
Name: "Action Runners without existing owner",
Counter: actions_model.CountRunnersWithoutBelongingOwner,
Fixer: actions_model.FixRunnersWithoutBelongingOwner,
FixedMessage: "Removed",
},
}

// TODO: function to recalc all counters

+ 1
- 1
modules/markup/html.go ファイルの表示

@@ -66,7 +66,7 @@ var (
// well as the HTML5 spec:
// http://spec.commonmark.org/0.28/#email-address
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|\\.(\\s|$))")
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")

// blackfriday extensions create IDs like fn:user-content-footnote
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)

+ 12
- 0
modules/markup/html_test.go ファイルの表示

@@ -264,6 +264,18 @@ func TestRender_email(t *testing.T) {
"send email to info@gitea.co.uk.",
`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`)

test(
`j.doe@example.com,
j.doe@example.com.
j.doe@example.com;
j.doe@example.com?
j.doe@example.com!`,
`<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,<br/>
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.<br/>
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;<br/>
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?<br/>
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)

// Test that should *not* be turned into email links
test(
"\"info@gitea.com\"",

+ 4
- 4
modules/markup/orgmode/orgmode.go ファイルの表示

@@ -158,7 +158,7 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) {
case "image":
if l.Description == nil {
imageSrc := getMediaURL(link)
fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, link, link)
fmt.Fprintf(r, `<img src="%s" alt="%s" />`, imageSrc, link)
} else {
description := strings.TrimPrefix(org.String(l.Description...), "file:")
imageSrc := getMediaURL([]byte(description))
@@ -167,18 +167,18 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) {
case "video":
if l.Description == nil {
imageSrc := getMediaURL(link)
fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, imageSrc, link, link)
fmt.Fprintf(r, `<video src="%s">%s</video>`, imageSrc, link)
} else {
description := strings.TrimPrefix(org.String(l.Description...), "file:")
videoSrc := getMediaURL([]byte(description))
fmt.Fprintf(r, `<a href="%s"><video src="%s" title="%s"></video></a>`, link, videoSrc, videoSrc)
fmt.Fprintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc)
}
default:
description := string(link)
if l.Description != nil {
description = r.WriteNodesAsString(l.Description...)
}
fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description)
fmt.Fprintf(r, `<a href="%s">%s</a>`, link, description)
}
}


+ 11
- 7
modules/markup/orgmode/orgmode_test.go ファイルの表示

@@ -34,12 +34,12 @@ func TestRender_StandardLinks(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
}

googleRendered := "<p><a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a></p>"
test("[[https://google.com/]]", googleRendered)
test("[[https://google.com/]]",
`<p><a href="https://google.com/">https://google.com/</a></p>`)

lnk := util.URLJoin(AppSubURL, "WikiPage")
test("[[WikiPage][WikiPage]]",
"<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>")
`<p><a href="`+lnk+`">WikiPage</a></p>`)
}

func TestRender_Media(t *testing.T) {
@@ -59,19 +59,23 @@ func TestRender_Media(t *testing.T) {
result := util.URLJoin(AppSubURL, url)

test("[[file:"+url+"]]",
"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
`<p><img src="`+result+`" alt="`+result+`" /></p>`)

// With description.
test("[[https://example.com][https://example.com/example.svg]]",
`<p><a href="https://example.com"><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></a></p>`)
test("[[https://example.com][pre https://example.com/example.svg post]]",
`<p><a href="https://example.com">pre <img src="https://example.com/example.svg" alt="https://example.com/example.svg" /> post</a></p>`)
test("[[https://example.com][https://example.com/example.mp4]]",
`<p><a href="https://example.com"><video src="https://example.com/example.mp4" title="https://example.com/example.mp4"></video></a></p>`)
`<p><a href="https://example.com"><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></a></p>`)
test("[[https://example.com][pre https://example.com/example.mp4 post]]",
`<p><a href="https://example.com">pre <video src="https://example.com/example.mp4">https://example.com/example.mp4</video> post</a></p>`)

// Without description.
test("[[https://example.com/example.svg]]",
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" title="https://example.com/example.svg" /></p>`)
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></p>`)
test("[[https://example.com/example.mp4]]",
`<p><video src="https://example.com/example.mp4" title="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
`<p><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`)
}

func TestRender_Source(t *testing.T) {

+ 1
- 0
modules/setting/ui.go ファイルの表示

@@ -33,6 +33,7 @@ var UI = struct {
CustomEmojisMap map[string]string `ini:"-"`
SearchRepoDescription bool
OnlyShowRelevantRepos bool
ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"`

Notification struct {
MinTimeout time.Duration

+ 2
- 7
modules/ssh/ssh.go ファイルの表示

@@ -17,7 +17,6 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
@@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) {
}

func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context"
// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211
parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context)

if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
}
@@ -200,7 +195,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// look for the exact principal
principalLoop:
for _, principal := range cert.ValidPrincipals {
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal)
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal)
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal)
@@ -257,7 +252,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
}

pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())

+ 2
- 0
options/locale/locale_en-US.ini ファイルの表示

@@ -1786,6 +1786,8 @@ pulls.status_checks_failure = Some checks failed
pulls.status_checks_error = Some checks reported errors
pulls.status_checks_requested = Required
pulls.status_checks_details = Details
pulls.status_checks_hide_all = Hide all checks
pulls.status_checks_show_all = Show all checks
pulls.update_branch = Update branch by merge
pulls.update_branch_rebase = Update branch by rebase
pulls.update_branch_success = Branch update was successful

+ 4
- 0
options/locale/locale_sk-SK.ini ファイルの表示

@@ -77,20 +77,24 @@ milestones=Míľniky

ok=OK
cancel=Zrušiť
retry=Opakovať
save=Uložiť
add=Pridať
add_all=Pridať všetko
remove=Odstrániť
remove_all=Odstrániť všetko
remove_label_str=Odstrániť položku „%s“
edit=Upraviť

enabled=Povolené

copy=Kopírovať
copy_url=Kopírovať URL
copy_content=Kopírovať obsah
copy_branch=Kopírovať meno vetvy
copy_success=Skopírované!
copy_error=Kopírovanie zlyhalo
copy_type_unsupported=Tento typ súboru nie je možné skopírovať

write=Zapísať
preview=Náhľad

+ 646
- 525
package-lock.json
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 20
- 19
package.json ファイルの表示

@@ -5,8 +5,8 @@
},
"dependencies": {
"@citation-js/core": "0.7.1",
"@citation-js/plugin-bibtex": "0.7.1",
"@citation-js/plugin-csl": "0.7.1",
"@citation-js/plugin-bibtex": "0.7.2",
"@citation-js/plugin-csl": "0.7.2",
"@citation-js/plugin-software-formats": "0.6.1",
"@claviska/jquery-minicolors": "2.3.6",
"@github/markdown-toolbar-element": "2.2.1",
@@ -17,7 +17,7 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "6.0.2",
"asciinema-player": "3.6.2",
"asciinema-player": "3.6.3",
"clippie": "4.0.6",
"css-loader": "6.8.1",
"dropzone": "6.0.0-beta.2",
@@ -29,7 +29,7 @@
"katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
"mermaid": "10.5.0",
"mermaid": "10.6.0",
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3",
"monaco-editor": "0.44.0",
@@ -37,49 +37,50 @@
"pdfobject": "2.2.12",
"pretty-ms": "8.0.0",
"sortablejs": "1.15.0",
"swagger-ui-dist": "5.9.0",
"swagger-ui-dist": "5.9.1",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "3.3.4",
"vue": "3.3.7",
"vue-bar-graph": "2.0.0",
"vue-loader": "17.3.0",
"vue-loader": "17.3.1",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.88.2",
"webpack": "5.89.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "8.1.0"
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
"@playwright/test": "1.38.1",
"@playwright/test": "1.39.0",
"@stoplight/spectral-cli": "6.11.0",
"@stylistic/eslint-plugin-js": "1.0.0",
"@vitejs/plugin-vue": "4.4.0",
"eslint": "8.51.0",
"eslint": "8.53.0",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-i": "2.28.1",
"eslint-plugin-i": "2.29.0",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-regexp": "2.0.0",
"eslint-plugin-sonarjs": "0.21.0",
"eslint-plugin-unicorn": "48.0.1",
"eslint-plugin-vitest": "0.3.2",
"eslint-plugin-regexp": "2.1.1",
"eslint-plugin-sonarjs": "0.23.0",
"eslint-plugin-unicorn": "49.0.0",
"eslint-plugin-vitest": "0.3.9",
"eslint-plugin-vitest-globals": "1.4.0",
"eslint-plugin-vue": "9.17.0",
"eslint-plugin-vue": "9.18.1",
"eslint-plugin-vue-scoped-css": "2.5.1",
"eslint-plugin-wc": "2.0.4",
"jsdom": "22.1.0",
"markdownlint-cli": "0.37.0",
"postcss-html": "1.5.0",
"stylelint": "15.10.3",
"stylelint": "15.11.0",
"stylelint-declaration-block-no-ignored-properties": "2.7.0",
"stylelint-declaration-strict-value": "1.9.2",
"stylelint-stylistic": "0.4.3",
"svgo": "3.0.2",
"updates": "15.0.2",
"updates": "15.0.4",
"vite-string-plugin": "1.1.2",
"vitest": "0.34.6"
},

+ 1
- 1
poetry.lock ファイルの表示

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.

[[package]]
name = "click"

+ 0
- 36
routers/api/v1/api.go ファイルの表示

@@ -316,10 +316,6 @@ func reqToken() func(ctx *context.APIContext) {
return
}

if ctx.IsBasicAuth {
ctx.CheckForOTP()
return
}
if ctx.IsSigned {
return
}
@@ -344,7 +340,6 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
return
}
ctx.CheckForOTP()
}
}

@@ -701,12 +696,6 @@ func bind[T any](_ T) any {
}
}

// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
// in the session (if there is a user id stored in session other plugins might return the user
// object for that id).
//
// The Session plugin is expected to be executed second, in order to skip authentication
// for users that have already signed in.
func buildAuthGroup() *auth.Group {
group := auth.NewGroup(
&auth.OAuth2{},
@@ -786,31 +775,6 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
})
return
}
if ctx.IsSigned && ctx.IsBasicAuth {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
return // Skip 2FA
}
twofa, err := auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if err != nil {
if auth_model.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.InternalServerError(err)
return
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.InternalServerError(err)
return
}
if !ok {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
}
}

if options.AdminRequired {

+ 7
- 7
routers/web/admin/auths.go ファイルの表示

@@ -48,13 +48,13 @@ func Authentications(ctx *context.Context) {
ctx.Data["PageIsAdminAuthentications"] = true

var err error
ctx.Data["Sources"], err = auth.Sources(ctx)
ctx.Data["Sources"], err = auth.FindSources(ctx, auth.FindSourcesOptions{})
if err != nil {
ctx.ServerError("auth.Sources", err)
return
}

ctx.Data["Total"] = auth.CountSources(ctx)
ctx.Data["Total"] = auth.CountSources(ctx, auth.FindSourcesOptions{})
ctx.HTML(http.StatusOK, tplAuths)
}

@@ -99,7 +99,7 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetOAuth2Providers()
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers

ctx.Data["SSPIAutoCreateUsers"] = true
@@ -242,7 +242,7 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetOAuth2Providers()
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers

ctx.Data["SSPIAutoCreateUsers"] = true
@@ -284,7 +284,7 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplAuthNew, form)
return
}
existing, err := auth.SourcesByType(ctx, auth.SSPI)
existing, err := auth.FindSources(ctx, auth.FindSourcesOptions{LoginType: auth.SSPI})
if err != nil || len(existing) > 0 {
ctx.Data["Err_Type"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form)
@@ -334,7 +334,7 @@ func EditAuthSource(ctx *context.Context) {

ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetOAuth2Providers()
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers

source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
@@ -368,7 +368,7 @@ func EditAuthSourcePost(ctx *context.Context) {
ctx.Data["PageIsAdminAuthentications"] = true

ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetOAuth2Providers()
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers

source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))

+ 1
- 1
routers/web/admin/orgs.go ファイルの表示

@@ -24,7 +24,7 @@ func Organizations(ctx *context.Context) {
ctx.Data["PageIsAdminOrganizations"] = true

if ctx.FormString("sort") == "" {
ctx.SetFormString("sort", explore.UserSearchDefaultAdminSort)
ctx.SetFormString("sort", UserSearchDefaultAdminSort)
}

explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{

+ 11
- 4
routers/web/admin/users.go ファイルの表示

@@ -37,6 +37,9 @@ const (
tplUserEdit base.TplName = "admin/user/edit"
)

// UserSearchDefaultAdminSort is the default sort type for admin view
const UserSearchDefaultAdminSort = "alphabetically"

// Users show all the users
func Users(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.users")
@@ -56,7 +59,7 @@ func Users(ctx *context.Context) {

sortType := ctx.FormString("sort")
if sortType == "" {
sortType = explore.UserSearchDefaultAdminSort
sortType = UserSearchDefaultAdminSort
ctx.SetFormString("sort", sortType)
}
ctx.PageData["adminUserListSearchForm"] = map[string]any{
@@ -90,7 +93,9 @@ func NewUser(ctx *context.Context) {

ctx.Data["login_type"] = "0-0"

sources, err := auth.Sources(ctx)
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
})
if err != nil {
ctx.ServerError("auth.Sources", err)
return
@@ -109,7 +114,9 @@ func NewUserPost(ctx *context.Context) {
ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()

sources, err := auth.Sources(ctx)
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
})
if err != nil {
ctx.ServerError("auth.Sources", err)
return
@@ -230,7 +237,7 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
ctx.Data["LoginSource"] = &auth.Source{}
}

sources, err := auth.Sources(ctx)
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{})
if err != nil {
ctx.ServerError("auth.Sources", err)
return nil

+ 4
- 8
routers/web/auth/auth.go ファイルの表示

@@ -160,12 +160,11 @@ func SignIn(ctx *context.Context) {
return
}

orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx)
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
ctx.Data["OAuth2Providers"] = oauth2Providers
ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
@@ -184,12 +183,11 @@ func SignIn(ctx *context.Context) {
func SignInPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in")

orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx)
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
ctx.Data["OAuth2Providers"] = oauth2Providers
ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
@@ -408,13 +406,12 @@ func SignUp(ctx *context.Context) {

ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"

orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx)
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignUp", err)
return
}

ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
ctx.Data["OAuth2Providers"] = oauth2Providers
context.SetCaptchaData(ctx)

@@ -438,13 +435,12 @@ func SignUpPost(ctx *context.Context) {

ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"

orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx)
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignUp", err)
return
}

ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
ctx.Data["OAuth2Providers"] = oauth2Providers
context.SetCaptchaData(ctx)


+ 1
- 1
routers/web/explore/org.go ファイルの表示

@@ -25,7 +25,7 @@ func Organizations(ctx *context.Context) {
}

if ctx.FormString("sort") == "" {
ctx.SetFormString("sort", UserSearchDefaultSortType)
ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
}

RenderUserSearch(ctx, &user_model.SearchUserOptions{

+ 7
- 2
routers/web/explore/repo.go ファイルの表示

@@ -57,8 +57,13 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
orderBy db.SearchOrderBy
)

ctx.Data["SortType"] = ctx.FormString("sort")
switch ctx.FormString("sort") {
sortOrder := ctx.FormString("sort")
if sortOrder == "" {
sortOrder = setting.UI.ExploreDefaultSort
}
ctx.Data["SortType"] = sortOrder

switch sortOrder {
case "newest":
orderBy = db.SearchOrderByNewest
case "oldest":

+ 8
- 9
routers/web/explore/user.go ファイルの表示

@@ -23,12 +23,6 @@ const (
tplExploreUsers base.TplName = "explore/users"
)

// UserSearchDefaultSortType is the default sort type for user search
const (
UserSearchDefaultSortType = "recentupdate"
UserSearchDefaultAdminSort = "alphabetically"
)

var nullByte = []byte{0x00}

func isKeywordValid(keyword string) bool {
@@ -60,8 +54,13 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,

// we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns

ctx.Data["SortType"] = ctx.FormString("sort")
switch ctx.FormString("sort") {
sortOrder := ctx.FormString("sort")
if sortOrder == "" {
sortOrder = setting.UI.ExploreDefaultSort
}
ctx.Data["SortType"] = sortOrder

switch sortOrder {
case "newest":
orderBy = "`user`.id DESC"
case "oldest":
@@ -134,7 +133,7 @@ func Users(ctx *context.Context) {
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

if ctx.FormString("sort") == "" {
ctx.SetFormString("sort", UserSearchDefaultSortType)
ctx.SetFormString("sort", setting.UI.ExploreDefaultSort)
}

RenderUserSearch(ctx, &user_model.SearchUserOptions{

+ 43
- 0
routers/web/githttp.go ファイルの表示

@@ -0,0 +1,43 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package web

import (
"net/http"

"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
context_service "code.gitea.io/gitea/services/context"
)

func requireSignIn(ctx *context.Context) {
if !setting.Service.RequireSignInView {
return
}

// rely on the results of Contexter
if !ctx.IsSigned {
// TODO: support digit auth - which would be Authorization header with digit
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
ctx.Error(http.StatusUnauthorized)
}
}

func gitHTTPRouters(m *web.Route) {
m.Group("", func() {
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
m.GetOptions("/info/refs", repo.GetInfoRefs)
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
}

+ 26
- 0
routers/web/org/home.go ファイルの表示

@@ -13,6 +13,8 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
@@ -155,5 +157,29 @@ func Home(ctx *context.Context) {

ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0

profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx)
defer profileClose()
prepareOrgProfileReadme(ctx, profileGitRepo, profileReadmeBlob)

ctx.HTML(http.StatusOK, tplOrgHome)
}

func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileReadme *git.Blob) {
if profileGitRepo == nil || profileReadme == nil {
return
}

if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
ctx.Data["ProfileReadme"] = profileContent
}
}
}

routers/web/repo/http.go → routers/web/repo/githttp.go ファイルの表示


routers/web/repo/http_test.go → routers/web/repo/githttp_test.go ファイルの表示


+ 2
- 5
routers/web/repo/view.go ファイルの表示

@@ -716,14 +716,11 @@ func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {
return
}
defer dataRc.Close()
buf := make([]byte, 1024)
n, err := util.ReadAtMost(dataRc, buf)
ctx.PageData["citationFileContent"], err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
if err != nil {
ctx.ServerError("ReadAtMost", err)
ctx.ServerError("GetBlobContent", err)
return
}
buf = buf[:n]
ctx.PageData["citationFileContent"] = string(buf)
break
}
}

+ 25
- 3
routers/web/user/setting/security/security.go ファイルの表示

@@ -6,12 +6,14 @@ package security

import (
"net/http"
"sort"

auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
)

@@ -82,7 +84,7 @@ func loadSecurityData(ctx *context.Context) {
// map the provider display name with the AuthSource
sources := make(map[*auth_model.Source]string)
for _, externalAccount := range accountLinks {
if authSource, err := auth_model.GetSourceByID(ctx, externalAccount.LoginSourceID); err == nil && authSource.IsActive {
if authSource, err := auth_model.GetSourceByID(ctx, externalAccount.LoginSourceID); err == nil {
var providerDisplayName string

type DisplayNamed interface {
@@ -105,11 +107,31 @@ func loadSecurityData(ctx *context.Context) {
}
ctx.Data["AccountLinks"] = sources

orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx)
authSources, err := auth_model.FindSources(ctx, auth_model.FindSourcesOptions{
IsActive: util.OptionalBoolNone,
LoginType: auth_model.OAuth2,
})
if err != nil {
ctx.ServerError("GetActiveOAuth2Providers", err)
ctx.ServerError("FindSources", err)
return
}

var orderedOAuth2Names []string
oauth2Providers := make(map[string]oauth2.Provider)
for _, source := range authSources {
provider, err := oauth2.CreateProviderFromSource(source)
if err != nil {
ctx.ServerError("CreateProviderFromSource", err)
return
}
oauth2Providers[source.Name] = provider
if source.IsActive {
orderedOAuth2Names = append(orderedOAuth2Names, source.Name)
}
}

sort.Strings(orderedOAuth2Names)

ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
ctx.Data["OAuth2Providers"] = oauth2Providers


+ 4
- 14
routers/web/web.go ファイルの表示

@@ -276,6 +276,8 @@ func Routes() *web.Route {
return routes
}

var ignSignInAndCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})

// registerRoutes register routes
func registerRoutes(m *web.Route) {
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
@@ -283,7 +285,7 @@ func registerRoutes(m *web.Route) {
// TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView)
ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
ignSignInAndCsrf := verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
validation.AddBindingRules()

linkAccountEnabled := func(ctx *context.Context) {
@@ -1512,19 +1514,7 @@ func registerRoutes(m *web.Route) {
})
}, ignSignInAndCsrf, lfsServerEnabled)

m.Group("", func() {
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
m.GetOptions("/info/refs", repo.GetInfoRefs)
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
gitHTTPRouters(m)
})
})
// ***** END: Repository *****

+ 22
- 2
services/auth/basic.go ファイルの表示

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
)

@@ -131,11 +132,30 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
return nil, err
}

if skipper, ok := source.Cfg.(LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
store.GetData()["SkipLocalTwoFA"] = true
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
if err := validateTOTP(req, u); err != nil {
return nil, err
}
}

log.Trace("Basic Authorization: Logged in user %-v", u)

return u, nil
}

func validateTOTP(req *http.Request, u *user_model.User) error {
twofa, err := auth_model.GetTwoFactorByUID(req.Context(), u.ID)
if err != nil {
if auth_model.IsErrTwoFactorNotEnrolled(err) {
// No 2FA enrollment for this user
return nil
}
return err
}
if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil {
return err
} else if !ok {
return util.NewInvalidArgumentErrorf("invalid provided OTP")
}
return nil
}

+ 4
- 1
services/auth/signin.go ファイルの表示

@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"

@@ -85,7 +86,9 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use
}
}

sources, err := auth.AllActiveSources(ctx)
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
})
if err != nil {
return nil, nil, err
}

+ 8
- 1
services/auth/source/oauth2/init.go ファイルの表示

@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"github.com/google/uuid"
"github.com/gorilla/sessions"
@@ -63,7 +64,13 @@ func ResetOAuth2(ctx context.Context) error {

// initOAuth2Sources is used to load and register all active OAuth2 providers
func initOAuth2Sources(ctx context.Context) error {
authSources, _ := auth.GetActiveOAuth2ProviderSources(ctx)
authSources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
LoginType: auth.OAuth2,
})
if err != nil {
return err
}
for _, source := range authSources {
oauth2Source, ok := source.Cfg.(*Source)
if !ok {

+ 27
- 20
services/auth/source/oauth2/providers.go ファイルの表示

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"github.com/markbates/goth"
)
@@ -80,10 +81,10 @@ func RegisterGothProvider(provider GothProvider) {
gothProviders[provider.Name()] = provider
}

// GetOAuth2Providers returns the map of unconfigured OAuth2 providers
// GetSupportedOAuth2Providers returns the map of unconfigured OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetOAuth2Providers() []Provider {
func GetSupportedOAuth2Providers() []Provider {
providers := make([]Provider, 0, len(gothProviders))

for _, provider := range gothProviders {
@@ -95,33 +96,39 @@ func GetOAuth2Providers() []Provider {
return providers
}

// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetActiveOAuth2Providers(ctx context.Context) ([]string, map[string]Provider, error) {
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
func CreateProviderFromSource(source *auth.Source) (Provider, error) {
oauth2Cfg, ok := source.Cfg.(*Source)
if !ok {
return nil, fmt.Errorf("invalid OAuth2 source config: %v", oauth2Cfg)
}
gothProv := gothProviders[oauth2Cfg.Provider]
return &AuthSourceProvider{GothProvider: gothProv, sourceName: source.Name, iconURL: oauth2Cfg.IconURL}, nil
}

authSources, err := auth.GetActiveOAuth2ProviderSources(ctx)
// GetOAuth2Providers returns the list of configured OAuth2 providers
func GetOAuth2Providers(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) {
authSources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: isActive,
LoginType: auth.OAuth2,
})
if err != nil {
return nil, nil, err
return nil, err
}

var orderedKeys []string
providers := make(map[string]Provider)
providers := make([]Provider, 0, len(authSources))
for _, source := range authSources {
oauth2Cfg, ok := source.Cfg.(*Source)
if !ok {
log.Error("Invalid OAuth2 source config: %v", oauth2Cfg)
continue
provider, err := CreateProviderFromSource(source)
if err != nil {
return nil, err
}
gothProv := gothProviders[oauth2Cfg.Provider]
providers[source.Name] = &AuthSourceProvider{GothProvider: gothProv, sourceName: source.Name, iconURL: oauth2Cfg.IconURL}
orderedKeys = append(orderedKeys, source.Name)
providers = append(providers, provider)
}

sort.Strings(orderedKeys)
sort.Slice(providers, func(i, j int) bool {
return providers[i].Name() < providers[j].Name()
})

return orderedKeys, providers, nil
return providers, nil
}

// RegisterProviderWithGothic register a OAuth2 provider in goth lib

+ 4
- 1
services/auth/sspi.go ファイルの表示

@@ -130,7 +130,10 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,

// getConfig retrieves the SSPI configuration from login sources
func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) {
sources, err := auth.ActiveSources(ctx, auth.SSPI)
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{
IsActive: util.OptionalBoolTrue,
LoginType: auth.SSPI,
})
if err != nil {
return nil, err
}

+ 1
- 1
services/auth/sync.go ファイルの表示

@@ -15,7 +15,7 @@ import (
func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
log.Trace("Doing: SyncExternalUsers")

ls, err := auth.Sources(ctx)
ls, err := auth.FindSources(ctx, auth.FindSourcesOptions{})
if err != nil {
log.Error("SyncExternalUsers: %v", err)
return err

+ 6
- 4
services/migrations/gitea_downloader.go ファイルの表示

@@ -282,6 +282,8 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
httpClient := NewMigrationHTTPClient()

for _, asset := range rel.Attachments {
assetID := asset.ID // Don't optimize this, for closure we need a local variable
assetDownloadURL := asset.DownloadURL
size := int(asset.Size)
dlCount := int(asset.DownloadCount)
r.Assets = append(r.Assets, &base.ReleaseAsset{
@@ -292,18 +294,18 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
Created: asset.Created,
DownloadURL: &asset.DownloadURL,
DownloadFunc: func() (io.ReadCloser, error) {
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID)
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, assetID)
if err != nil {
return nil, err
}

if !hasBaseURL(asset.DownloadURL, g.baseURL) {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL)
if !hasBaseURL(assetDownloadURL, g.baseURL) {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, assetDownloadURL)
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
}

// FIXME: for a private download?
req, err := http.NewRequest("GET", asset.DownloadURL, nil)
req, err := http.NewRequest("GET", assetDownloadURL, nil)
if err != nil {
return nil, err
}

+ 3
- 2
services/migrations/gitlab.go ファイルの表示

@@ -310,6 +310,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
httpClient := NewMigrationHTTPClient()

for k, asset := range rel.Assets.Links {
assetID := asset.ID // Don't optimize this, for closure we need a local variable
r.Assets = append(r.Assets, &base.ReleaseAsset{
ID: int64(asset.ID),
Name: asset.Name,
@@ -317,13 +318,13 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
Size: &zero,
DownloadCount: &zero,
DownloadFunc: func() (io.ReadCloser, error) {
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, asset.ID, gitlab.WithContext(g.ctx))
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(g.ctx))
if err != nil {
return nil, err
}

if !hasBaseURL(link.URL, g.baseURL) {
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, link.URL)
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL)
return io.NopCloser(strings.NewReader(link.URL)), nil
}


+ 2
- 0
services/user/delete.go ファイルの表示

@@ -10,6 +10,7 @@ import (

_ "image/jpeg" // Needed for jpeg support

actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
asymkey_model "code.gitea.io/gitea/models/asymkey"
auth_model "code.gitea.io/gitea/models/auth"
@@ -90,6 +91,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
&pull_model.AutoMerge{DoerID: u.ID},
&pull_model.ReviewState{UserID: u.ID},
&user_model.Redirect{RedirectUserID: u.ID},
&actions_model.ActionRunner{OwnerID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %w", err)
}

+ 3
- 0
templates/org/home.tmpl ファイルの表示

@@ -38,6 +38,9 @@
<div class="ui container">
<div class="ui mobile reversed stackable grid">
<div class="ui {{if .ShowMemberAndTeamTab}}eleven wide{{end}} column">
{{if .ProfileReadme}}
<div id="readme_profile" class="markup">{{.ProfileReadme | Str2html}}</div>
{{end}}
{{template "explore/repo_search" .}}
{{template "explore/repo_list" .}}
{{template "base/paginate" .}}

+ 1
- 1
templates/repo/commit_page.tmpl ファイルの表示

@@ -19,7 +19,7 @@
{{end}}
<div class="ui top attached header clearing segment gt-relative commit-header {{$class}}">
<div class="gt-df gt-mb-4 gt-fw">
<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses "root" $}}</h3>
<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}}
<div>
<a class="ui primary tiny button" href="{{.SourcePath}}">

+ 2
- 10
templates/repo/commit_statuses.tmpl ファイルの表示

@@ -8,15 +8,7 @@
{{template "repo/commit_status" .Status}}
</span>
{{end}}
<div class="tippy-target ui relaxed list divided">
{{range .Statuses}}
<div class="ui item singular-status gt-df">
{{template "repo/commit_status" .}}
<span class="ui gt-ml-3 gt-f1">{{.Context}} <span class="text grey">{{.Description}}</span></span>
{{if .TargetURL}}
<a class="gt-ml-3" href="{{.TargetURL}}" target="_blank" rel="noopener noreferrer">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>
{{end}}
</div>
{{end}}
<div class="tippy-target">
{{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}}
</div>
{{end}}

+ 1
- 1
templates/repo/commits_list.tmpl ファイルの表示

@@ -66,7 +66,7 @@
{{if IsMultilineCommitMessage .Message}}
<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
{{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{if IsMultilineCommitMessage .Message}}
<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .Message $commitRepoLink ($.Repository.ComposeMetas ctx)}}</pre>
{{end}}

+ 1
- 1
templates/repo/commits_list_small.tmpl ファイルの表示

@@ -14,7 +14,7 @@
{{$commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String)}}

<span class="shabox gt-df gt-ac gt-float-right">
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $.root}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{$class := "ui sha label"}}
{{if .Signature}}
{{$class = (print $class " isSigned")}}

+ 3
- 3
templates/repo/diff/box.tmpl ファイルの表示

@@ -53,7 +53,7 @@
{{if not .DiffNotAvailable}}
{{if and .IsShowingOnlySingleCommit .PageIsPullFiles}}
<div class="ui info message">
<div>{{ctx.Locale.Tr "repo.pulls.showing_only_single_commit" (ShortSha .BeforeCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div>
<div>{{ctx.Locale.Tr "repo.pulls.showing_only_single_commit" (ShortSha .AfterCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div>
</div>
{{else if and (not .IsShowingAllCommits) .PageIsPullFiles}}
<div class="ui info message">
@@ -238,8 +238,8 @@
"DropzoneParentContainer" ".ui.form"
)}}
<div class="text right edit buttons">
<button class="ui cancel button" tabindex="3">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui primary save button" tabindex="2">{{ctx.Locale.Tr "repo.issues.save"}}</button>
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
</div>
</div>
</template>

+ 4
- 4
templates/repo/home.tmpl ファイルの表示

@@ -5,7 +5,7 @@
{{template "base/alert" .}}
{{template "repo/code/recently_pushed_new_branches" .}}
{{if and (not .HideRepoInfo) (not .IsBlame)}}
<div class="ui repo-description">
<div class="ui repo-description gt-word-break">
<div id="repo-desc" class="gt-font-16">
{{$description := .Repository.DescriptionHTML $.Context}}
{{if $description}}<span class="description">{{$description | RenderCodeBlock}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{ctx.Locale.Tr "repo.no_desc"}}</span>{{end}}
@@ -35,9 +35,9 @@
</div>
{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<div class="ui form gt-hidden gt-df gt-mt-4" id="topic_edit">
<div class="field gt-f1 gt-mr-3">
<div class="ui fluid multiple search selection dropdown" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}">
<div class="ui form gt-hidden gt-df gt-fc gt-mt-4" id="topic_edit">
<div class="field gt-f1 gt-mb-2">
<div class="ui fluid multiple search selection dropdown gt-fw" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}">
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
{{/* keey the same layout as Fomantic UI generated labels */}}

+ 7
- 0
templates/repo/issue/card.tmpl ファイルの表示

@@ -49,6 +49,13 @@
</div>
{{end}}
{{end}}
{{$tasks := .GetTasks}}
{{if gt $tasks 0}}
<div class="meta gt-my-2">
{{svg "octicon-checklist" 16 "gt-mr-2 gt-vm"}}
<span class="gt-vm">{{.GetTasksDone}} / {{$tasks}}</span>
</div>
{{end}}
</div>

{{if or .Labels .Assignees}}

+ 5
- 5
templates/repo/issue/view_content.tmpl ファイルの表示

@@ -92,7 +92,7 @@
<div class="text right">
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .DisableStatusChange)}}
{{if .Issue.IsClosed}}
<button id="status-button" class="ui primary basic button" tabindex="6" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen">
<button id="status-button" class="ui primary basic button" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen">
{{ctx.Locale.Tr "repo.issues.reopen_issue"}}
</button>
{{else}}
@@ -100,12 +100,12 @@
{{if .Issue.IsPull}}
{{$closeTranslationKey = "repo.pulls.close"}}
{{end}}
<button id="status-button" class="ui red basic button" tabindex="6" data-status="{{ctx.Locale.Tr $closeTranslationKey}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.close_comment_issue"}}" name="status" value="close">
<button id="status-button" class="ui red basic button" data-status="{{ctx.Locale.Tr $closeTranslationKey}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.close_comment_issue"}}" name="status" value="close">
{{ctx.Locale.Tr $closeTranslationKey}}
</button>
{{end}}
{{end}}
<button class="ui primary button" tabindex="5">
<button class="ui primary button">
{{ctx.Locale.Tr "repo.issues.create_comment"}}
</button>
</div>
@@ -162,8 +162,8 @@

<div class="field">
<div class="text right edit">
<button class="ui basic cancel button" tabindex="3">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui primary save button" tabindex="2">{{ctx.Locale.Tr "repo.issues.save"}}</button>
<button class="ui basic cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
</div>
</div>
</div>

+ 23
- 24
templates/repo/issue/view_content/comments.tmpl ファイルの表示

@@ -4,7 +4,8 @@
{{$createdStr:= TimeSinceUnix .CreatedUnix ctx.Locale}}

<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF,
5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING,
5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 8 = MILESTONE_CHANGE,
9 = ASSIGNEES_CHANGE, 10 = TITLE_CHANGE, 11 = DELETE_BRANCH, 12 = START_TRACKING,
13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE,
18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE,
22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED, 25 = TARGET_BRANCH_CHANGED,
@@ -184,31 +185,29 @@
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{ctx.Locale.Tr "repo.issues.change_milestone_at" (.OldMilestone.Name|Escape) (.Milestone.Name|Escape) $createdStr | Safe}}{{else}}{{ctx.Locale.Tr "repo.issues.remove_milestone_at" (.OldMilestone.Name|Escape) $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{ctx.Locale.Tr "repo.issues.add_milestone_at" (.Milestone.Name|Escape) $createdStr | Safe}}{{end}}
</span>
</div>
{{else if eq .Type 9}}
{{else if and (eq .Type 9) (gt .AssigneeID 0)}}
<div class="timeline-item event" id="{{.HashTag}}">
<span class="badge">{{svg "octicon-person"}}</span>
{{if gt .AssigneeID 0}}
{{if .RemovedAssignee}}
{{template "shared/user/avatarlink" dict "user" .Assignee}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Assignee}}
{{if eq .Poster.ID .Assignee.ID}}
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}}
{{else}}
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
{{end}}
</span>
{{else}}
{{template "shared/user/avatarlink" dict "user" .Assignee}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Assignee}}
{{if eq .Poster.ID .AssigneeID}}
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr | Safe}}
{{else}}
{{ctx.Locale.Tr "repo.issues.add_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
{{end}}
</span>
{{end}}
{{if .RemovedAssignee}}
{{template "shared/user/avatarlink" dict "user" .Assignee}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Assignee}}
{{if eq .Poster.ID .Assignee.ID}}
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}}
{{else}}
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
{{end}}
</span>
{{else}}
{{template "shared/user/avatarlink" dict "user" .Assignee}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Assignee}}
{{if eq .Poster.ID .AssigneeID}}
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr | Safe}}
{{else}}
{{ctx.Locale.Tr "repo.issues.add_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}}
{{end}}
</span>
{{end}}
</div>
{{else if eq .Type 10}}

+ 8
- 1
templates/repo/issue/view_content/pull.tmpl ファイルの表示

@@ -20,7 +20,14 @@
{{- else if .Issue.PullRequest.CanAutoMerge}}green
{{- else}}red{{end}}">{{svg "octicon-git-merge" 40}}</div>
<div class="content">
{{template "repo/pulls/status" .}}
<div class="ui attached segment fitted">
{{template "repo/pulls/status" (dict
"CommitStatus" .LatestCommitStatus
"CommitStatuses" .LatestCommitStatuses
"ShowHideChecks" true
"is_context_required" .is_context_required
)}}
</div>
{{$showGeneralMergeForm := false}}
<div class="ui attached merge-section segment {{if not $.LatestCommitStatus}}no-header{{end}} flex-items-block">
{{if .Issue.PullRequest.HasMerged}}

+ 42
- 25
templates/repo/pulls/status.tmpl ファイルの表示

@@ -1,27 +1,43 @@
{{if $.LatestCommitStatus}}
{{if not $.Issue.PullRequest.HasMerged}}
<div class="ui top attached header">
{{if eq .LatestCommitStatus.State "pending"}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{else if eq .LatestCommitStatus.State "success"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}}
{{else if eq .LatestCommitStatus.State "warning"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_warning"}}
{{else if eq .LatestCommitStatus.State "failure"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_failure"}}
{{else if eq .LatestCommitStatus.State "error"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_error"}}
{{else}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{end}}
</div>
{{end}}
{{/*
Template Attributes:
* CommitStatus: summary of all commit status state
* CommitStatuses: all commit status elements
* ShowHideChecks: whether use a button to show/hide the checks
* is_context_required: Used in pull request commit status check table
*/}}

{{if .CommitStatus}}
<div class="commit-status-panel">
<div class="ui top attached header commit-status-header">
{{if eq .CommitStatus.State "pending"}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{else if eq .CommitStatus.State "success"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}}
{{else if eq .CommitStatus.State "warning"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_warning"}}
{{else if eq .CommitStatus.State "failure"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_failure"}}
{{else if eq .CommitStatus.State "error"}}
{{ctx.Locale.Tr "repo.pulls.status_checks_error"}}
{{else}}
{{ctx.Locale.Tr "repo.pulls.status_checking"}}
{{end}}

{{range $.LatestCommitStatuses}}
<div class="ui attached segment pr-status">
{{template "repo/commit_status" .}}
<div class="status-context">
<span>{{.Context}} <span class="text grey">{{.Description}}</span></span>
{{if .ShowHideChecks}}
<div class="ui right">
<button class="commit-status-hide-checks btn interact-fg"
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}"
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}">
{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button>
</div>
{{end}}
</div>

<div class="commit-status-list">
{{range .CommitStatuses}}
<div class="commit-status-item">
{{template "repo/commit_status" .}}
<div class="status-context gt-ellipsis">{{.Context}} <span class="text light-2">{{.Description}}</span></div>
<div class="ui status-details">
{{if $.is_context_required}}
{{if (call $.is_context_required .Context)}}<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
@@ -29,6 +45,7 @@
<span>{{if .TargetURL}}<a href="{{.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
</div>
</div>
</div>
{{end}}
{{end}}
</div>
</div>
{{end}}

+ 1
- 1
templates/repo/release/list.tmpl ファイルの表示

@@ -16,7 +16,7 @@
</div>
<div class="ui twelve wide column detail">
<div class="gt-df gt-ac gt-sb gt-fw gt-mb-3">
<h4 class="release-list-title">
<h4 class="release-list-title gt-word-break">
<a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a>
{{if .IsDraft}}
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span>

+ 1
- 1
templates/repo/view_list.tmpl ファイルの表示

@@ -24,7 +24,7 @@
{{template "repo/shabox_badge" dict "root" $ "verification" .LatestCommitVerification}}
{{end}}
</a>
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses "root" $}}
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}}
{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}}
<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $.RepoLink $commitLink ($.Repository.ComposeMetas ctx)}}</span>
{{if IsMultilineCommitMessage .LatestCommit.Message}}

+ 1
- 1
templates/shared/issuelist.tmpl ファイルの表示

@@ -16,7 +16,7 @@
<a class="gt-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a>
{{if .IsPull}}
{{if (index $.CommitStatuses .PullRequest.ID)}}
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID) "root" $}}
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
{{end}}
{{end}}
<span class="labels-list gt-ml-2">

+ 3
- 4
templates/user/auth/signin_inner.tmpl ファイルの表示

@@ -52,16 +52,15 @@
</div>
{{end}}

{{if and .OrderedOAuth2Names .OAuth2Providers}}
{{if .OAuth2Providers}}
<div class="divider divider-text">
{{ctx.Locale.Tr "sign_in_or"}}
</div>
<div id="oauth2-login-navigator" class="gt-py-2">
<div class="gt-df gt-fc gt-jc">
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $key := .OrderedOAuth2Names}}
{{$provider := index $.OAuth2Providers $key}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$key}}">
{{range $provider := .OAuth2Providers}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>

+ 3
- 4
templates/user/auth/signup_inner.tmpl ファイルの表示

@@ -56,16 +56,15 @@
{{end}}
{{end}}

{{if and .OrderedOAuth2Names .OAuth2Providers}}
{{if .OAuth2Providers}}
<div class="divider divider-text">
{{ctx.Locale.Tr "sign_in_or"}}
</div>
<div id="oauth2-login-navigator" class="gt-py-2">
<div class="gt-df gt-fc gt-jc">
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $key := .OrderedOAuth2Names}}
{{$provider := index $.OAuth2Providers $key}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$key}}">
{{range $provider := .OAuth2Providers}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a>

+ 3
- 3
templates/user/dashboard/issues.tmpl ファイルの表示

@@ -2,8 +2,8 @@
<div role="main" aria-label="{{.Title}}" class="page-content dashboard issues">
{{template "user/dashboard/navbar" .}}
<div class="ui container">
<div class="ui stackable grid">
<div class="four wide column">
<div class="flex-container">
<div class="flex-container-nav">
<div class="ui secondary vertical filter menu gt-bg-transparent">
<a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="{{.Link}}?type=your_repositories&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}">
{{ctx.Locale.Tr "home.issues.in_your_repos"}}
@@ -59,7 +59,7 @@
{{end}}
</div>
</div>
<div class="twelve wide column content">
<div class="flex-container-main content">
<div class="list-header">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">

+ 3
- 3
templates/user/dashboard/milestones.tmpl ファイルの表示

@@ -2,8 +2,8 @@
<div role="main" aria-label="{{.Title}}" class="page-content dashboard issues repository milestones">
{{template "user/dashboard/navbar" .}}
<div class="ui container">
<div class="ui stackable grid">
<div class="four wide column">
<div class="flex-container">
<div class="flex-container-nav">
<div class="ui secondary vertical filter menu gt-bg-transparent">
<div class="item">
{{ctx.Locale.Tr "home.issues.in_your_repos"}}
@@ -33,7 +33,7 @@
{{end}}
</div>
</div>
<div class="twelve wide column content">
<div class="flex-container-main content">
<div class="list-header">
<div class="small-menu-items ui compact tiny menu list-header-toggle">
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">

+ 55
- 0
tests/integration/api_twofa_test.go ファイルの表示

@@ -0,0 +1,55 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package integration

import (
"net/http"
"testing"
"time"

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/tests"

"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
)

func TestAPITwoFactor(t *testing.T) {
defer tests.PrepareTestEnv(t)()

user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16})

req := NewRequestf(t, "GET", "/api/v1/user")
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusOK)

otpKey, err := totp.Generate(totp.GenerateOpts{
SecretSize: 40,
Issuer: "gitea-test",
AccountName: user.Name,
})
assert.NoError(t, err)

tfa := &auth_model.TwoFactor{
UID: user.ID,
}
assert.NoError(t, tfa.SetSecret(otpKey.Secret()))

assert.NoError(t, auth_model.NewTwoFactor(db.DefaultContext, tfa))

req = NewRequestf(t, "GET", "/api/v1/user")
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusUnauthorized)

passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now())
assert.NoError(t, err)

req = NewRequestf(t, "GET", "/api/v1/user")
req = AddBasicAuthHeader(req, user.Name)
req.Header.Set("X-Gitea-OTP", passcode)
MakeRequest(t, req, http.StatusOK)
}

+ 2
- 2
web_src/css/modules/divider.css ファイルの表示

@@ -1,5 +1,5 @@
.divider {
margin: 1rem 0;
margin: 10px 0;
height: 0;
font-weight: var(--font-weight-medium);
text-transform: uppercase;
@@ -15,7 +15,7 @@
.divider.divider-text {
display: flex;
align-items: center;
padding: 7px 0;
padding: 5px 0;
}

.divider.divider-text::before,

+ 36
- 22
web_src/css/modules/tippy.css ファイルの表示

@@ -6,7 +6,7 @@
}

[data-tippy-root] {
max-width: calc(100vw - 10px);
max-width: calc(100vw - 32px);
}

.tippy-box {
@@ -18,37 +18,60 @@
font-size: 1rem;
}

.tippy-content {
position: relative;
padding: 1rem; /* if you need different padding, use different data-theme */
z-index: 1;
}

/* tooltip theme for text tooltips */

.tippy-box[data-theme="tooltip"] {
background-color: var(--color-tooltip-bg);
color: var(--color-tooltip-text);
border: none;
}

.tippy-box[data-theme="tooltip"] .tippy-content {
padding: 0.5rem 1rem;
}

.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner,
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer {
fill: var(--color-tooltip-bg);
}

/* menu theme for .ui.menu */

.tippy-box[data-theme="menu"] {
background-color: var(--color-menu);
color: var(--color-text);
}

.tippy-box[data-theme="form-fetch-error"] {
border-color: var(--color-error-border);
background-color: var(--color-error-bg);
color: var(--color-error-text);
.tippy-box[data-theme="menu"] .tippy-content {
padding: 0;
}

.tippy-content {
position: relative;
padding: 1rem;
z-index: 1;
.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner {
fill: var(--color-menu);
}

.tippy-box[data-theme="tooltip"] .tippy-content {
padding: 0.5rem 1rem;
}
/* box-with-header theme to look like .ui.attached.segment. can contain .ui.attached.header */

.tippy-box[data-theme="menu"] .tippy-content {
.tippy-box[data-theme="box-with-header"] .tippy-content {
background: var(--color-box-body);
border-radius: var(--border-radius);
padding: 0;
}

.tippy-box[data-theme="box-with-header"][data-placement^="top"] .tippy-svg-arrow-inner {
fill: var(--color-box-body);
}

.tippy-box[data-theme="box-with-header"][data-placement^="bottom"] .tippy-svg-arrow-inner {
fill: var(--color-box-header);
}

.tippy-box[data-placement^="top"] > .tippy-svg-arrow {
bottom: 0;
}
@@ -107,12 +130,3 @@
.tippy-svg-arrow-inner {
fill: var(--color-body);
}

.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner,
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer {
fill: var(--color-tooltip-bg);
}

.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner {
fill: var(--color-menu);
}

+ 32
- 18
web_src/css/repo.css ファイルの表示

@@ -211,14 +211,14 @@
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
margin-bottom: 5px;
}

@media (max-width: 767.68px) {
@media (max-width: 767.98px) {
.repository.file.list .repo-description {
flex-direction: column;
gap: 8px;
align-items: normal;
align-items: stretch;
}
}

@@ -3074,43 +3074,57 @@ tbody.commit-list {
}
}

.pr-status {
padding: 0 !important; /* To clear fomantic's padding on .ui.segment elements */
.commit-status-header {
/* reset the default ".ui.attached.header" styles, to use the outer border */
border: none !important;
/* add a bottom border to make sure the there is always a divider between the header and list when the list is scrolling */
border-bottom: 1px solid var(--color-secondary) !important;
/* use negative margin to avoid the newly added border conflict with the list's top border */
margin: 0 0 -1px !important;
}

.commit-status-list {
max-height: 240px; /* fit exactly 6 items, commit-status-item.height * 6 */
overflow-x: hidden;
transition: max-height .2s;
}

.commit-status-item {
height: 40px;
padding: 0 10px;
display: flex;
gap: 8px;
align-items: center;
}

.pr-status .commit-status {
margin: 1em;
flex-shrink: 0;
.commit-status-item + .commit-status-item {
border-top: 1px solid var(--color-secondary);
}

.pr-status .status-context {
display: flex;
justify-content: space-between;
width: 100%;
.commit-status-item .commit-status {
flex-shrink: 0;
}

.pr-status .status-context > span {
padding: 1em 0;
.commit-status-item .status-context {
color: var(--color-text);
flex: 1;
}

.pr-status .status-details {
.commit-status-item .status-details {
display: flex;
padding-right: 0.5em;
align-items: center;
justify-content: flex-end;
}

@media (max-width: 767.98px) {
.pr-status .status-details {
.commit-status-item .status-details {
flex-direction: column;
align-items: flex-end;
justify-content: center;
}
}

.pr-status .status-details > span {
.commit-status-item .status-details > span {
padding-right: 0.5em; /* To match the alignment with the "required" label */
}


+ 1
- 1
web_src/css/shared/flex-list.css ファイルの表示

@@ -6,7 +6,7 @@
display: flex;
gap: 8px;
align-items: flex-start;
padding: 1em 0;
padding: 10px 0;
}

.flex-item .flex-item-leading {

+ 0
- 1
web_src/js/components/DashboardRepoList.vue ファイルの表示

@@ -210,7 +210,6 @@ const sfc = {
this.searchRepos();
},


changePage(page) {
this.page = page;
if (this.page > this.finalPage) {

+ 1
- 1
web_src/js/components/DiffCommitSelector.vue ファイルの表示

@@ -94,7 +94,7 @@ export default {
focusElem(elem, prevElem) {
if (elem) {
elem.tabIndex = 0;
prevElem.tabIndex = -1;
if (prevElem) prevElem.tabIndex = -1;
elem.focus();
}
},

+ 4
- 1
web_src/js/features/common-global.js ファイルの表示

@@ -46,7 +46,6 @@ export function initFootLanguageMenu() {
$('.language-menu a[lang]').on('click', linkLanguageAction);
}


export function initGlobalEnterQuickSubmit() {
$(document).on('keydown', '.js-quick-submit', (e) => {
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.key === 'Enter')) {
@@ -248,6 +247,10 @@ export function initGlobalDropzone() {
});
}
});
this.on('error', function (file, message) {
showErrorToast(message);
this.removeFile(file);
});
},
});
}

+ 0
- 1
web_src/js/features/comp/ImagePaste.js ファイルの表示

@@ -90,7 +90,6 @@ class CodeMirrorEditor {
}
}


const uploadClipboardImage = async (editor, dropzone, e) => {
const $dropzone = $(dropzone);
const uploadUrl = $dropzone.attr('data-upload-url');

+ 0
- 1
web_src/js/features/file-fold.js ファイルの表示

@@ -17,4 +17,3 @@ export function setFileFolding(fileContentBox, foldArrow, newFold) {
export function invertFileFolding(fileContentBox, foldArrow) {
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true');
}


+ 0
- 1
web_src/js/features/org-team.js ファイルの表示

@@ -15,7 +15,6 @@ export function initOrgTeamSettings() {
});
}


export function initOrgTeamSearchRepoBox() {
const $searchRepoBox = $('#search-repo-box');
$searchRepoBox.search({

+ 0
- 3
web_src/js/features/pull-view-file.js ファイルの表示

@@ -9,7 +9,6 @@ const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all
const expandFilesBtnSelector = '#expand-files-btn';
const collapseFilesBtnSelector = '#collapse-files-btn';


// Refreshes the summary of viewed files if present
// The data used will be window.config.pageData.prReview.numberOf{Viewed}Files
function refreshViewedFilesSummary() {
@@ -93,5 +92,3 @@ export function initExpandAndCollapseFilesButton() {
}
});
}



+ 1
- 0
web_src/js/features/repo-commit.js ファイルの表示

@@ -66,6 +66,7 @@ export function initCommitStatuses() {
placement: top ? 'top-start' : 'bottom-start',
interactive: true,
role: 'dialog',
theme: 'box-with-header',
});
});
}

+ 0
- 1
web_src/js/features/repo-editor.js ファイルの表示

@@ -60,7 +60,6 @@ function initEditorForm() {
initEditDiffTab($('.repository .edit.form'));
}


function getCursorPosition($e) {
const el = $e.get(0);
let pos = 0;

+ 0
- 1
web_src/js/features/repo-findfile.js ファイルの表示

@@ -9,7 +9,6 @@ const threshold = 50;
let files = [];
let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult;


// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...]
// res[even] is unmatched, res[odd] is matched, see unit tests for examples
// argument subLower must be a lower-cased string.

+ 1
- 1
web_src/js/features/repo-home.js ファイルの表示

@@ -43,7 +43,7 @@ export function initRepoTopicBar() {
const topicArray = topics.split(',');
topicArray.sort();
for (let i = 0; i < topicArray.length; i++) {
const link = $('<a class="ui repo-topic large label topic"></a>');
const link = $('<a class="ui repo-topic large label topic gt-m-0"></a>');
link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
link.text(topicArray[i]);
link.insertBefore(mgrBtn); // insert all new topics before manage button

+ 10
- 0
web_src/js/features/repo-issue-pr-status.js ファイルの表示

@@ -0,0 +1,10 @@
export function initRepoPullRequestCommitStatus() {
for (const btn of document.querySelectorAll('.commit-status-hide-checks')) {
const panel = btn.closest('.commit-status-panel');
const list = panel.querySelector('.commit-status-list');
btn.addEventListener('click', () => {
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');
});
}
}

+ 0
- 2
web_src/js/features/repo-issue.js ファイルの表示

@@ -308,7 +308,6 @@ export function initRepoIssueReferenceRepositorySearch() {
});
}


export function initRepoIssueWipTitle() {
$('.title_wip_desc > a').on('click', (e) => {
e.preventDefault();
@@ -552,7 +551,6 @@ export function initRepoIssueWipToggle() {
});
}


export function initRepoIssueTitleEdit() {
// Edit issue title
const $issueTitle = $('#issue-title');

+ 2
- 2
web_src/js/features/repo-legacy.js ファイルの表示

@@ -20,6 +20,7 @@ import {initCommentContent, initMarkupContent} from '../markup/content.js';
import {initCompReactionSelector} from './comp/ReactionSelector.js';
import {initRepoSettingBranches} from './repo-settings.js';
import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js';
import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.js';
import {hideElem, showElem} from '../utils/dom.js';
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
import {attachRefIssueContextPopup} from './contextpopup.js';
@@ -300,7 +301,6 @@ export function initRepoCommentForm() {
selectItem('.select-assignee', '#assignee_id');
}


async function onEditContent(event) {
event.preventDefault();

@@ -538,7 +538,6 @@ export function initRepository() {
initRepoDiffConversationNav();
initRepoIssueReferenceIssue();


initRepoIssueCommentDelete();
initRepoIssueDependencyDelete();
initRepoIssueCodeCommentCancel();
@@ -546,6 +545,7 @@ export function initRepository() {
initCompReactionSelector($(document));

initRepoPullRequestMergeForm();
initRepoPullRequestCommitStatus();
}

// Pull request

+ 0
- 1
web_src/js/features/repo-settings.js ファイルの表示

@@ -66,7 +66,6 @@ export function initRepoSettingSearchTeamBox() {
});
}


export function initRepoSettingGitHook() {
if ($('.edit.githook').length === 0) return;
const filename = document.querySelector('.hook-filename').textContent;

+ 6
- 93
web_src/js/modules/fomantic.js ファイルの表示

@@ -1,7 +1,9 @@
import $ from 'jquery';
import {initAriaCheckboxPatch} from './aria/checkbox.js';
import {initAriaDropdownPatch} from './aria/dropdown.js';
import {initAriaModalPatch} from './aria/modal.js';
import {initFomanticApiPatch} from './fomantic/api.js';
import {initAriaCheckboxPatch} from './fomantic/checkbox.js';
import {initAriaDropdownPatch} from './fomantic/dropdown.js';
import {initAriaModalPatch} from './fomantic/modal.js';
import {initFomanticTransition} from './fomantic/transition.js';
import {svg} from '../svg.js';

export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)');
@@ -22,57 +24,7 @@ export function initGiteaFomantic() {
return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`);
};

const transitionNopBehaviors = new Set([
'clear queue', 'stop', 'stop all', 'destroy',
'force repaint', 'repaint', 'reset',
'looping', 'remove looping', 'disable', 'enable',
'set duration', 'save conditions', 'restore conditions',
]);
// stand-in for removed transition module
$.fn.transition = function (arg0, arg1, arg2) {
if (arg0 === 'is supported') return true;
if (arg0 === 'is animating') return false;
if (arg0 === 'is inward') return false;
if (arg0 === 'is outward') return false;

let argObj;
if (typeof arg0 === 'string') {
// many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage
if (transitionNopBehaviors.has(arg0)) return this;
// now, the arg0 is an animation name, the syntax: (animation, duration, complete)
argObj = {animation: arg0, ...(arg1 && {duration: arg1}), ...(arg2 && {onComplete: arg2})};
} else if (typeof arg0 === 'object') {
argObj = arg0;
} else {
throw new Error(`invalid argument: ${arg0}`);
}

const isAnimationIn = argObj.animation?.startsWith('show') || argObj.animation?.endsWith(' in');
const isAnimationOut = argObj.animation?.startsWith('hide') || argObj.animation?.endsWith(' out');
this.each((_, el) => {
let toShow = isAnimationIn;
if (!isAnimationIn && !isAnimationOut) {
// If the animation is not in/out, then it must be a toggle animation.
// Fomantic uses computed styles to check "visibility", but to avoid unnecessary arguments, here it only checks the class.
toShow = this.hasClass('hidden'); // maybe it could also check "!this.hasClass('visible')", leave it to the future until there is a real problem.
}
argObj.onStart?.call(el);
if (toShow) {
el.classList.remove('hidden');
el.classList.add('visible', 'transition');
if (argObj.displayType) el.style.setProperty('display', argObj.displayType, 'important');
argObj.onShow?.call(el);
} else {
el.classList.add('hidden');
el.classList.remove('visible'); // don't remove the transition class because the Fomantic animation style is `.hidden.transition`.
el.style.removeProperty('display');
argObj.onHidden?.call(el);
}
argObj.onComplete?.call(el);
});
return this;
};

initFomanticTransition();
initFomanticApiPatch();

// Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future.
@@ -80,42 +32,3 @@ export function initGiteaFomantic() {
initAriaDropdownPatch();
initAriaModalPatch();
}

function initFomanticApiPatch() {
//
// Fomantic API module has some very buggy behaviors:
//
// If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter.
// However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again.
//
// There are 2 problems:
// 1. It may guess wrong, and skip encoding a parameter which looks like encoded.
// 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail.
//
// This patch only fixes the second error behavior at the moment.
//
const patchKey = '_giteaFomanticApiPatch';
const oldApi = $.api;
$.api = $.fn.api = function(...args) {
const apiCall = oldApi.bind(this);
const ret = oldApi.apply(this, args);

if (typeof args[0] !== 'string') {
const internalGet = apiCall('internal', 'get');
if (!internalGet.urlEncodedValue[patchKey]) {
const oldUrlEncodedValue = internalGet.urlEncodedValue;
internalGet.urlEncodedValue = function (value) {
try {
return oldUrlEncodedValue(value);
} catch {
// if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves.
return encodeURIComponent(value);
}
};
internalGet.urlEncodedValue[patchKey] = true;
}
}
return ret;
};
$.api.settings = oldApi.settings;
}

+ 0
- 0
web_src/js/modules/fomantic/api.js ファイルの表示


変更されたファイルが多すぎるため、一部のファイルは表示されません

読み込み中…
キャンセル
保存