Merge branch 'main' into lunny/automerge_support_delete_branch

This commit is contained in:
Lunny Xiao 2024-12-16 16:20:10 -08:00 committed by GitHub
commit 80110bec30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
424 changed files with 8537 additions and 5007 deletions

View File

@ -16,10 +16,10 @@ parserOptions:
parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
settings: settings:
import/extensions: [".js", ".ts"] import-x/extensions: [".js", ".ts"]
import/parsers: import-x/parsers:
"@typescript-eslint/parser": [".js", ".ts"] "@typescript-eslint/parser": [".js", ".ts"]
import/resolver: import-x/resolver:
typescript: true typescript: true
plugins: plugins:
@ -28,7 +28,7 @@ plugins:
- "@typescript-eslint/eslint-plugin" - "@typescript-eslint/eslint-plugin"
- eslint-plugin-array-func - eslint-plugin-array-func
- eslint-plugin-github - eslint-plugin-github
- eslint-plugin-i - eslint-plugin-import-x
- eslint-plugin-no-jquery - eslint-plugin-no-jquery
- eslint-plugin-no-use-extend-native - eslint-plugin-no-use-extend-native
- eslint-plugin-regexp - eslint-plugin-regexp
@ -58,15 +58,15 @@ overrides:
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top]
- files: ["*.config.*"] - files: ["*.config.*"]
rules: rules:
i/no-unused-modules: [0] import-x/no-unused-modules: [0]
- files: ["**/*.d.ts"] - files: ["**/*.d.ts"]
rules: rules:
i/no-unused-modules: [0] import-x/no-unused-modules: [0]
"@typescript-eslint/consistent-type-definitions": [0] "@typescript-eslint/consistent-type-definitions": [0]
"@typescript-eslint/consistent-type-imports": [0] "@typescript-eslint/consistent-type-imports": [0]
- files: ["web_src/js/types.ts"] - files: ["web_src/js/types.ts"]
rules: rules:
i/no-unused-modules: [0] import-x/no-unused-modules: [0]
- files: ["**/*.test.*", "web_src/js/test/setup.ts"] - files: ["**/*.test.*", "web_src/js/test/setup.ts"]
env: env:
vitest-globals/env: true vitest-globals/env: true
@ -394,49 +394,49 @@ rules:
id-blacklist: [0] id-blacklist: [0]
id-length: [0] id-length: [0]
id-match: [0] id-match: [0]
i/consistent-type-specifier-style: [0] import-x/consistent-type-specifier-style: [0]
i/default: [0] import-x/default: [0]
i/dynamic-import-chunkname: [0] import-x/dynamic-import-chunkname: [0]
i/export: [2] import-x/export: [2]
i/exports-last: [0] import-x/exports-last: [0]
i/extensions: [2, always, {ignorePackages: true}] import-x/extensions: [2, always, {ignorePackages: true}]
i/first: [2] import-x/first: [2]
i/group-exports: [0] import-x/group-exports: [0]
i/max-dependencies: [0] import-x/max-dependencies: [0]
i/named: [2] import-x/named: [2]
i/namespace: [0] import-x/namespace: [0]
i/newline-after-import: [0] import-x/newline-after-import: [0]
i/no-absolute-path: [0] import-x/no-absolute-path: [0]
i/no-amd: [2] import-x/no-amd: [2]
i/no-anonymous-default-export: [0] import-x/no-anonymous-default-export: [0]
i/no-commonjs: [2] import-x/no-commonjs: [2]
i/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}]
i/no-default-export: [0] import-x/no-default-export: [0]
i/no-deprecated: [0] import-x/no-deprecated: [0]
i/no-dynamic-require: [0] import-x/no-dynamic-require: [0]
i/no-empty-named-blocks: [2] import-x/no-empty-named-blocks: [2]
i/no-extraneous-dependencies: [2] import-x/no-extraneous-dependencies: [2]
i/no-import-module-exports: [0] import-x/no-import-module-exports: [0]
i/no-internal-modules: [0] import-x/no-internal-modules: [0]
i/no-mutable-exports: [0] import-x/no-mutable-exports: [0]
i/no-named-as-default-member: [0] import-x/no-named-as-default-member: [0]
i/no-named-as-default: [0] import-x/no-named-as-default: [0]
i/no-named-default: [0] import-x/no-named-default: [0]
i/no-named-export: [0] import-x/no-named-export: [0]
i/no-namespace: [0] import-x/no-namespace: [0]
i/no-nodejs-modules: [0] import-x/no-nodejs-modules: [0]
i/no-relative-packages: [0] import-x/no-relative-packages: [0]
i/no-relative-parent-imports: [0] import-x/no-relative-parent-imports: [0]
i/no-restricted-paths: [0] import-x/no-restricted-paths: [0]
i/no-self-import: [2] import-x/no-self-import: [2]
i/no-unassigned-import: [0] import-x/no-unassigned-import: [0]
i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
i/no-unused-modules: [2, {unusedExports: true}] import-x/no-unused-modules: [2, {unusedExports: true}]
i/no-useless-path-segments: [2, {commonjs: true}] import-x/no-useless-path-segments: [2, {commonjs: true}]
i/no-webpack-loader-syntax: [2] import-x/no-webpack-loader-syntax: [2]
i/order: [0] import-x/order: [0]
i/prefer-default-export: [0] import-x/prefer-default-export: [0]
i/unambiguous: [0] import-x/unambiguous: [0]
init-declarations: [0] init-declarations: [0]
line-comment-position: [0] line-comment-position: [0]
logical-assignment-operators: [0] logical-assignment-operators: [0]
@ -818,7 +818,7 @@ rules:
unicorn/consistent-destructuring: [2] unicorn/consistent-destructuring: [2]
unicorn/consistent-empty-array-spread: [2] unicorn/consistent-empty-array-spread: [2]
unicorn/consistent-existence-index-check: [0] unicorn/consistent-existence-index-check: [0]
unicorn/consistent-function-scoping: [2] unicorn/consistent-function-scoping: [0]
unicorn/custom-error-definition: [0] unicorn/custom-error-definition: [0]
unicorn/empty-brace-spaces: [2] unicorn/empty-brace-spaces: [2]
unicorn/error-message: [0] unicorn/error-message: [0]

View File

@ -3,3 +3,5 @@ self-hosted-runner:
- actuated-4cpu-8gb - actuated-4cpu-8gb
- actuated-4cpu-16gb - actuated-4cpu-16gb
- nscloud - nscloud
- namespace-profile-gitea-release-docker
- namespace-profile-gitea-release-binary

View File

@ -10,7 +10,7 @@ concurrency:
jobs: jobs:
nightly-binary: nightly-binary:
runs-on: nscloud runs-on: namespace-profile-gitea-release-binary
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -58,7 +58,7 @@ jobs:
run: | run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-docker-rootful: nightly-docker-rootful:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -95,7 +95,7 @@ jobs:
push: true push: true
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }} tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
nightly-docker-rootless: nightly-docker-rootless:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions

View File

@ -11,7 +11,7 @@ concurrency:
jobs: jobs:
binary: binary:
runs-on: nscloud runs-on: namespace-profile-gitea-release-binary
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -68,7 +68,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful: docker-rootful:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -99,7 +99,7 @@ jobs:
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
docker-rootless: docker-rootless:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions

View File

@ -13,7 +13,7 @@ concurrency:
jobs: jobs:
binary: binary:
runs-on: nscloud runs-on: namespace-profile-gitea-release-binary
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -70,7 +70,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful: docker-rootful:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
@ -105,7 +105,7 @@ jobs:
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
docker-rootless: docker-rootless:
runs-on: ubuntu-latest runs-on: namespace-profile-gitea-release-docker
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions

2
.gitignore vendored
View File

@ -28,7 +28,7 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
*.tsbuildInfo *.tsbuildinfo
*coverage.out *coverage.out
coverage.all coverage.all

View File

@ -19,6 +19,8 @@ linters:
- revive - revive
- staticcheck - staticcheck
- stylecheck - stylecheck
- tenv
- testifylint
- typecheck - typecheck
- unconvert - unconvert
- unused - unused
@ -34,6 +36,10 @@ output:
show-stats: true show-stats: true
linters-settings: linters-settings:
testifylint:
disable:
- go-require
- require-error
stylecheck: stylecheck:
checks: ["all", "-ST1005", "-ST1003"] checks: ["all", "-ST1005", "-ST1003"]
nakedret: nakedret:

View File

@ -4,6 +4,399 @@ This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
* SECURITY
* Fix basic auth with webauthn (#32531) (#32536)
* Refactor internal routers (partial backport, auth token const time comparing) (#32473) (#32479)
* PERFORMANCE
* Remove transaction for archive download (#32186) (#32520)
* BUGFIXES
* Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32365) (#32397)
* Fix get reviewers fails when selecting user without pull request permissions unit (#32415) (#32616)
* Fix adding index files to tmp directory (#32360) (#32593)
* Fix PR creation on forked repositories via API (#31863) (#32591)
* Fix missing menu tabs in organization project view page (#32313) (#32592)
* Support HTTP POST requests to `/userinfo`, aligning to OpenID Core specification (#32578) (#32594)
* Fix debian package clean up cron job (#32351) (#32590)
* Fix GetInactiveUsers (#32540) (#32588)
* Allow the actions user to login via the jwt token (#32527) (#32580)
* Fix submodule parsing (#32571) (#32577)
* Refactor find forks and fix possible bugs that weaken permissions check (#32528) (#32547)
* Fix some places that don't respect org full name setting (#32243) (#32550)
* Refactor push mirror find and add check for updating push mirror (#32539) (#32549)
* Fix basic auth with webauthn (#32531) (#32536)
* Fix artifact v4 upload above 8MB (#31664) (#32523)
* Fix oauth2 error handle not return immediately (#32514) (#32516)
* Fix action not triggered when commit message is too long (#32498) (#32507)
* Fix `GetRepoLink` nil pointer dereference on dashboard feed page when repo is deleted with actions enabled (#32501) (#32502)
* Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32397) (#32397)
* Fix the permission check for user search API and limit the number of returned users for `/user/search` (#32310)
* Fix SearchIssues swagger docs (#32208) (#32298)
* Fix dropdown content overflow (#31610) (#32250)
* Disable Oauth check if oauth disabled (#32368) (#32480)
* Respect renamed dependencies of Cargo registry (#32430) (#32478)
* Fix mermaid diagram height when initially hidden (#32457) (#32464)
* Fix broken releases when re-pushing tags (#32435) (#32449)
* Only provide the commit summary for Discord webhook push events (#32432) (#32447)
* Only query team tables if repository is under org when getting assignees (#32414) (#32426)
* Fix created_unix for mirroring (#32342) (#32406)
* Respect UI.ExploreDefaultSort setting again (#32357) (#32385)
* Fix broken image when editing comment with non-image attachments (#32319) (#32345)
* Fix disable 2fa bug (#32320) (#32330)
* Always update expiration time when creating an artifact (#32281) (#32285)
* Fix null errors on conversation holder (#32258) (#32266) (#32282)
* Only rename a user when they should receive a different name (#32247) (#32249)
* Fix checkbox bug on private/archive filter (#32236) (#32240)
* Add a doctor check to disable the "Actions" unit for mirrors (#32424) (#32497)
* Quick fix milestone deadline 9999 (#32423)
* Make `show stats` work when only one file changed (#32244) (#32268)
* Make `owner/repo/pulls` handlers use "PR reader" permission (#32254) (#32265)
* Update scheduled tasks even if changes are pushed by "ActionsUser" (#32246) (#32252)
* MISC
* Remove unnecessary code: `GetPushMirrorsByRepoID` called on all repo pages (#32560) (#32567)
* Improve some sanitizer rules (#32534)
* Update nix development environment vor v1.22.x (#32495)
* Add warn log when deleting inactive users (#32318) (#32321)
* Update github.com/go-enry/go-enry to v2.9.1 (#32295) (#32296)
* Warn users when they try to use a non-root-url to sign in/up (#32272) (#32273)
## [1.22.3](https://github.com/go-gitea/gitea/releases/tag/v1.22.3) - 2024-10-08
* SECURITY
* Fix bug when a token is given public only (#32204) (#32218)
* PERFORMANCE
* Increase `cacheContextLifetime` to reduce false reports (#32011) (#32023)
* Don't join repository when loading action table data (#32127) (#32143)
* BUGFIXES
* Fix javascript error when an anonymous user visits migration page (#32144) (#32179)
* Don't init signing keys if oauth2 provider is disabled (#32177)
* Fix wrong status of `Set up Job` when first step is skipped (#32120) (#32125)
* Fix bug when deleting a migrated branch (#32075) (#32123)
* Truncate commit message during Discord webhook push events (#31970) (#32121)
* Allow to set branch protection in an empty repository (#32095) (#32119)
* Fix panic when cloning with wrong ssh format. (#32076) (#32118)
* Fix rename branch permission bug (#32066) (#32108)
* Fix: database not update release when using `git push --tags --force` (#32040) (#32074)
* Add missing comment reply handling (#32050) (#32065)
* Do not escape relative path in RPM primary index (#32038) (#32054)
* Fix `/repos/{owner}/{repo}/pulls/{index}/files` endpoint not populating `previous_filename` (#32017) (#32028)
* Support allowed hosts for migrations to work with proxy (#32025) (#32026)
* Fix the logic of finding the latest pull review commit ID (#32139) (#32165)
* Fix bug in getting merged pull request by commit (#32079) (#32117)
* Fix wrong last modify time (#32102) (#32104)
* Fix incorrect `/tokens` api (#32085) (#32092)
* Handle invalid target when creating releases using API (#31841) (#32043)
* Check if the `due_date` is nil when editing issues (#32035) (#32042)
* Fix container parallel upload bugs (#32022)
* Fixed race condition when deleting documents by repoId in ElasticSearch (#32185) (#32188)
* Refactor CSRF protector (#32057) (#32069)
* Fix Bug in Issue/pulls list (#32081) (#32115)
* Include collaboration repositories on dashboard source/forks/mirrors list (#31946) (#32122)
* Add null check for responseData.invalidTopics (#32212) (#32217)
* TESTING
* Fix mssql ci with a new mssql version on ci (#32094)
* MISC
* Upgrade some dependencies include minio-go (#32166)
* Add bin to Composer Metadata (#32099) (#32106)
* Lazy load avatar images (#32051) (#32063)
* Upgrade cache to v0.2.1 (#32003) (#32009)
## [1.22.2](https://github.com/go-gitea/gitea/releases/tag/v1.22.2) - 2024-08-28
* Security
* Replace v-html with v-text in search inputbox (#31966) (#31973)
* Fix nuget/conan/container packages upload bugs (#31967) (#31982)
* PERFORMANCE
* Refactor the usage of batch catfile (#31754) (#31889)
* BUGFIXES
* Fix overflowing content in action run log (#31842) (#31853)
* Scroll images in project issues separately from the remaining issue (#31683) (#31823)
* Add `:focus-visible` style to buttons (#31799) (#31819)
* Fix the display of project type for deleted projects (#31732) (#31734)
* Fix API owner ID should be zero when created repo secret (#31715) (#31811)
* Set owner id to zero when GetRegistrationToken for repo (#31725) (#31729)
* Fix API endpoint for registration-token (#31722) (#31728)
* Add permission check when creating PR (#31033) (#31720)
* Don't return 500 if mirror url contains special chars (#31859) (#31895)
* Fix agit automerge (#31207) (#31881)
* Add CfTurnstileSitekey context data to all captcha templates (#31874) (#31876)
* Avoid returning without written ctx when posting PR (#31843) (#31848)
* Fix raw wiki links (#31825) (#31845)
* Fix panic of ssh public key page after deletion of auth source (#31829) (#31836)
* Fixes for unreachable project issues when transfer repository from organization (#31770) (#31828)
* Show lock owner instead of repo owner on LFS setting page (#31788) (#31817)
* Fix `IsObjectExist` with gogit (#31790) (#31806)
* Fix protected branch files detection on pre_receive hook (#31778) (#31796)
* Add `TAGS` to `TEST_TAGS` and fix bugs found with gogit (#31791) (#31795)
* Rename head branch of pull requests when renaming a branch (#31759) (#31774)
* Fix wiki revision pagination (#31760) (#31772)
* Bump vue-bar-graph (#31705) (#31753)
* Distinguish LFS object errors to ignore missing objects during migration (#31702) (#31745)
* Make GetRepositoryByName more safer (#31712) (#31718)
* Fix a branch divergence cache bug (#31659) (#31661)
* Allow org team names of length 255 in create team form (#31564) (#31603)
* Use old behavior for telegram webhook (#31588)
* Bug fix for translation in ru (#31892)
* Fix actions notify bug (#31866) (#31875)
* Fix the component of access token list not mounted (#31824) (#31868)
* Add missing repository type filter parameters to pager (#31832) (#31837)
* Fix dates displaying in a wrong manner when we're close to the end of… (#31750)
* Fix "Filter by commit" Dropdown (#31695) (#31696)
* Properly filter issue list given no assignees filter (#31522) (#31685)
* Prevent update pull refs manually and will not affect other refs update (#31931)(#31955)
* Fix sort order for organization home and user profile page (#31921) (#31922)
* Fix search team (#31923) (#31942)
* Fix 500 error when state params is set when editing issue/PR by API (#31880) (#31952)
* Fix index too many file names bug (#31903) (#31953)
* Add lock for parallel maven upload (#31851) (#31954)
* MISC
* Remove "dsa-1024" testcases from Test_SSHParsePublicKey and Test_calcFingerprint (#31905) (#31914)
* Upgrade bleve to 2.4.2 (#31894)
* Remove unneccessary uses of `word-break: break-all` (#31637) (#31652)
* Return an empty string when a repo has no avatar in the repo API (#31187) (#31567)
* Upgrade micromatch to 4.0.8 (#31944)
* Update webpack to 5.94.0 (#31941)
## [1.22.1](https://github.com/go-gitea/gitea/releases/tag/v1.22.1) - 2024-07-04
* SECURITY
* Add replacement module for `mholt/archiver` (#31267) (#31270)
* API
* Fix missing images in editor preview due to wrong links (#31299) (#31393)
* Fix duplicate sub-path for avatars (#31365) (#31368)
* Reduce memory usage for chunked artifact uploads to MinIO (#31325) (#31338)
* Remove sub-path from container registry realm (#31293) (#31300)
* Fix NuGet Package API for $filter with Id equality (#31188) (#31242)
* Add an immutable tarball link to archive download headers for Nix (#31139) (#31145)
* Add missed return after `ctx.ServerError` (#31130) (#31133)
* BUGFIXES
* Fix avatar radius problem on the new issue page (#31506) (#31508)
* Fix overflow menu flickering on mobile (#31484) (#31488)
* Fix poor table column width due to breaking words (#31473) (#31477)
* Support relative paths to videos from Wiki pages (#31061) (#31453)
* Fix new issue/pr avatar (#31419) (#31424)
* Increase max length of org team names from 30 to 255 characters (#31410) (#31421)
* Fix line number width in code preview (#31307) (#31316)
* Optimize runner-tags layout to enhance visual experience (#31258) (#31263)
* Fix overflow on push notification (#31179) (#31238)
* Fix overflow on notifications (#31178) (#31237)
* Fix overflow in issue card (#31203) (#31225)
* Split sanitizer functions and fine-tune some tests (#31192) (#31200)
* use correct l10n string (#31487) (#31490)
* Fix dropzone JS error when attachment is disabled (#31486)
* Fix web notification icon not updated once you read all notifications (#31447) (#31466)
* Switch to "Write" tab when edit comment again (#31445) (#31461)
* Fix the link for .git-blame-ignore-revs bypass (#31432) (#31442)
* Fix the wrong line number in the diff view page when expanded twice. (#31431) (#31440)
* Fix labels and projects menu overflow on issue page (#31435) (#31439)
* Fix Account Linking UpdateMigrationsByType (#31428) (#31434)
* Fix markdown math brackets render problem (#31420) (#31430)
* Fix rendered wiki page link (#31398) (#31407)
* Fix natural sort (#31384) (#31394)
* Allow downloading attachments of draft releases (#31369) (#31380)
* Fix repo graph JS (#31377)
* Fix incorrect localization `explorer.go` (#31348) (#31350)
* Fix hash render end with colon (#31319) (#31346)
* Fix line number widths (#31341) (#31343)
* Fix navbar `+` menu flashing on page load (#31281) (#31342)
* Fix adopt repository has empty object name in database (#31333) (#31335)
* Delete legacy cookie before setting new cookie (#31306) (#31317)
* Fix some URLs whose sub-path is missing (#31289) (#31292)
* Fix admin oauth2 custom URL settings (#31246) (#31247)
* Make pasted "img" tag has the same behavior as markdown image (#31235) (#31243)
* Fix agit checkout command line hint & fix ShowMergeInstructions checking (#31219) (#31222)
* Fix the possible migration failure on 286 with postgres 16 (#31209) (#31218)
* Fix branch order (#31174) (#31193)
* Fix markup preview (#31158) (#31166)
* Fix push multiple branches error with tests (#31151) (#31153)
* Fix API repository object format missed (#31118) (#31132)
* Fix missing memcache import (#31105) (#31109)
* Upgrade `github.com/hashicorp/go-retryablehttp` (#31499)
* Fix double border in system status table (#31363) (#31401)
* Fix bug filtering issues which have no project (#31337) (#31367)
* Fix #31185 try fix lfs download from bitbucket failed (#31201) (#31329)
* Add nix flake for dev shell (#30967) (#31310)
* Fix and clean up `ConfirmModal` (#31283) (#31291)
* Optimize repo-list layout to enhance visual experience (#31272) (#31276)
* fixed the dropdown menu for the top New button to expand to the left (#31273) (#31275)
* Fix Activity Page Contributors dropdown (#31264) (#31269)
* fix: allow actions artifacts storage migration to complete succesfully (#31251) (#31257)
* Make blockquote attention recognize more syntaxes (#31240) (#31250)
* Remove .segment from .project-column (#31204) (#31239)
* Ignore FindRecentlyPushedNewBranches err (#31164) (#31171)
* Use vertical layout for multiple code expander buttons (#31122) (#31152)
* Remove duplicate `ProxyPreserveHost` in Apache httpd doc (#31143) (#31147)
* Improve mobile review ui (#31091) (#31136)
* Fix DashboardRepoList margin (#31121) (#31128)
* Update pip related commands for docker (#31106) (#31111)
## [1.22.0](https://github.com/go-gitea/gitea/releases/tag/v1.22.0) - 2024-05-27
This release stands as a monumental milestone in our development journey with a record-breaking incorporation of [1528](https://github.com/go-gitea/gitea/pulls?q=is%3Apr+milestone%3A1.22.0+is%3Amerged) pull requests. It marks the most extensive update in Gitea's history, showcasing a plethora of new features and infrastructure improvements.
Noteworthy advancements in this release include the introduction of `HTMX` and `Tailwind`, signaling a strategic shift as we gradually phase out `jquery` and `Fomantic UI`. These changes reflect our commitment to embracing modern technologies and enhancing the user experience.
Key highlights of this release encompass significant changes categorized under `BREAKING`, `FEATURES`, `ENHANCEMENTS`, and `PERFORMANCE`, each contributing to a more robust and efficient Gitea platform.
* BREAKING
* Improve reverse proxy documents and clarify the AppURL guessing behavior (#31003) (#31020)
* Remember log in for a month by default (#30150)
* Breaking summary for template refactoring (#29395)
* All custom templates need to follow these changes
* Recommend/convert to use case-sensitive collation for MySQL/MSSQL (#28662)
* Make offline mode as default to not connect external avatar service by default (#28548)
* Include public repos in the doer's dashboard for issue search (#28304)
* Use restricted sanitizer for repository description (#28141)
* Support storage base path as prefix (#27827)
* Enhanced auth token / remember me (#27606)
* Rename the default themes to `gitea-light`, `gitea-dark`, `gitea-auto` (#27419)
* If you didn't see the new themes, please remove the `[ui].THEMES` config option from `app.ini`
* Require MySQL 8.0, PostgreSQL 12, MSSQL 2012 (#27337)
* FEATURES
* Allow everyone to read or write a wiki by a repo unit setting (#30495)
* Use raw Wiki links for non-renderable Wiki files (#30273)
* Render embedded code preview by permalink in markdown (#30234) (#30249)
* Support repo code search without setting up an indexer (#29998)
* Support pasting URLs over markdown text (#29566)
* Allow to change primary email before account activation (#29412)
* Customizable "Open with" applications for repository clone (#29320)
* Allow options to disable user deletion from the interface on app.ini (#29275)
* Extend issue template YAML engine (#29274)
* Add support for `linguist-detectable` and `linguist-documentation` (#29267)
* Implement code frequency graph (#29191)
* Show commit status for releases (#29149)
* Add user blocking (#29028)
* Actions Artifacts v4 backend (#28965)
* Add merge style `fast-forward-only` (#28954)
* Retarget depending pulls when the parent branch is deleted (#28686)
* Add global setting on how timestamps should be rendered (#28657)
* Implement actions badge SVGs (#28102)
* Add skip ci functionality (#28075)
* Show latest commit for file (#28067)
* Allow to sync tags from the admin dashboard (#28045)
* Add Profile Readme for Organisations (#27955)
* Implement contributors graph (#27882)
* Artifact deletion in actions ui (#27172)
* Add API routes to get runner registration token (#27144)
* Add support for forking single branch (#25821)
* Add support for sha256 repositories (#23894)
* Add admin API route for managing user's badges (#23106)
* ENHANCEMENTS
* Make gitea webhooks openproject compatible (#28435) (#31081)
* Support using label names when changing issue labels (#30943) (#30958)
* Fix various problems around project board view (#30696) (#30902)
* Improve context popup rendering (#30824) (#30829)
* Allow to save empty comment (#30706)
* Prevent allow/reject reviews on merged/closed PRs (#30686)
* Initial support for colorblindness-friendly themes (#30625)
* Some NuGet package enhancements (#30280) (#30324)
* Markup color and font size fixes (#30282) (#30310)
* Show 12 lines in markup code preview (#30255) (#30257)
* Add `[other].SHOW_FOOTER_POWERED_BY` setting to hide `Powered by` (#30253)
* Pulse page improvements (#30149)
* Render code tags in commit messages (#30146)
* Prevent re-review and dismiss review actions on closed and merged PRs (#30065)
* Cancel previous runs of the same PR automatically (#29961)
* Drag-and-drop improvements for projects and issue pins (#29875)
* Add default board to new projects, remove uncategorized pseudo-board (#29874)
* Prevent layout shift in `<overflow-menu>` items (#29831)
* Add skip ci support for pull request title (#29774)
* Add more stats tables (#29730)
* Update API to return 'source_id' for users (#29718)
* Determine fuzziness of bleve indexer by keyword length (#29706)
* Expose fuzzy search for issues/pulls (#29701)
* Put an edit file button on pull request files to allow a quick operation (#29697)
* Fix action runner offline label padding (#29691)
* Update allowed attachment types (#29688)
* Completely style the webkit autofill (#29683)
* Highlight archived labels (#29680)
* Add a warning for disallowed email domains (#29658)
* Set user's 24h preference from their current OS locale (#29651)
* Add setting to disable user features when user login type is not plain (#29615)
* Improve natural sort (#29611)
* Make wiki default branch name changeable (#29603)
* Unify search boxes (#29530)
* Add support for API blob upload of release attachments (#29507)
* Detect broken git hooks (#29494)
* Sync branches to DB immediately when handling git hook calling (#29493)
* Allow options to disable user GPG key configuration from the interface on app.ini (#29486)
* Allow options to disable user SSH key configuration from the interface on app.ini (#29447)
* Use relative links for commits, mentions, and issues in markdown (#29427)
* Add `<overflow-menu>`, rename webcomponents (#29400)
* Include resource state events in Gitlab downloads (#29382)
* Properly migrate target branch change GitLab comment (#29340)
* Recolor dark theme to blue shade (#29283)
* Partially enable MSSQL case-sensitive collation support (#29238)
* Auto-update the system status in the admin dashboard (#29163)
* Integrate alpine `noarch` packages into other architectures index (#29137)
* Document how the TOC election process works (#29135)
* Tweak repo header (#29134)
* Make blockquote border size less aggressive (#29124)
* Downscale pasted PNG images based on metadata (#29123)
* Show `View at this point in history` for every commit (#29122)
* Add support for action artifact serve direct (#29120)
* Change webhook-type in create-view (#29114)
* Drop "@" from the email sender to avoid spam filters (#29109)
* Allow non-admin users to delete review requests (#29057)
* Improve user search display name (#29002)
* Include username in email headers (#28981)
* Show whether a PR is WIP inside popups (#28975)
* Also match weakly validated ETags (#28957)
* Support nuspec manifest download for Nuget packages (#28921)
* Fix hardcoded GitHub icon used as migrated release avatar (#28910)
* Propagate install_if and provider_priority to APKINDEX (#28899)
* Add artifacts v4 JWT to job message and accept it (#28885)
* Enable/disable owner and repo projects independently (#28805)
* Add non-JS fallback for reaction tooltips (#28785)
* Add the ability to see open and closed issues at the same time (#28757)
* Move sign-in labels to be above inputs (#28753)
* Display the latest sync time for pull mirrors on the repo page (#28712)
* Show in Web UI if the file is vendored and generated (#28620)
* Add orphaned topic consistency check (#28507)
* Add branch protection setting for ignoring stale approvals (#28498)
* Add option to set language in admin user view (#28449)
* Fix incorrect run order of action jobs (#28367)
* Add missing exclusive in advanced label options (#28322)
* Added instance-level variables (#28115)
* Add edit option for README.md (#28071)
* Fix link to `Code` tab on wiki commits (#28041)
* Allow to set explore page default sort (#27951)
* Improve PR diff view on mobile (#27883)
* Properly migrate automatic merge GitLab comments (#27873)
* Display issue task list on project cards (#27865)
* Add Index to pull_auto_merge.doer_id (#27811)
* Fix display member unit in the menu bar if there are no hidden members in public org (#27795)
* List all Debian package versions in `Packages` (#27786)
* Allow pull requests Manually Merged option to be used by non-admins (#27780)
* Only show diff file tree when more than one file changed (#27775)
* Show placeholder email in privacy popup (#27770)
* Revamp repo header (#27760)
* Add `must-change-password` command line parameter (#27626)
* Unify password changing and invalidate auth tokens (#27625)
* Add border to file tree 'sub-items' and add padding to 'item-file' (#27593)
* Add slow SQL query warning (#27545)
* Pre-register OAuth application for tea (#27509)
* Differentiate between `push` and `pull` `mirror sync in progress` (#27390)
* Link to file from its history (#27354)
* Add a shortcut to user's profile page to admin user details (#27299)
* Doctor: delete action entries without existing user (#27292)
* Show total TrackedTime on issue/pull/milestone lists (#26672)
* Don't show the new pull request button when the page is not compare pull (#26431)
* Add `Hide/Show all checks` button to commit status check (#26284)
* Improvements of releases list and tags list (#25859)
* PERFORMANCE
* Fix package list performance (#30520) (#30616)
* Add commit status summary table to reduce query from commit status table (#30223)
* Refactor markup/csv: don't read all to memory (#29760)
* Lazy load object format with command line and don't do it in OpenRepository (#29712)
* Add cache for branch divergence on branch list page (#29577)
* Do some performance optimization for issues list and view issue/pull (#29515)
* Cache repository default branch commit status to reduce query on commit status table (#29444)
* Use `crypto/sha256` (#29386)
* Some performance optimization on the dashboard and issues page (#29010)
* Add combined index for issue_user.uid and issue_id (#28080)
## [1.21.11](https://github.com/go-gitea/gitea/releases/tag/v1.21.11) - 2024-04-07 ## [1.21.11](https://github.com/go-gitea/gitea/releases/tag/v1.21.11) - 2024-04-07
* SECURITY * SECURITY

View File

@ -46,7 +46,6 @@ Wim <wim@42.be> (@42wim)
Jason Song <i@wolfogre.com> (@wolfogre) Jason Song <i@wolfogre.com> (@wolfogre)
Yarden Shoham <git@yardenshoham.com> (@yardenshoham) Yarden Shoham <git@yardenshoham.com> (@yardenshoham)
Yu Tian <zettat123@gmail.com> (@Zettat123) Yu Tian <zettat123@gmail.com> (@Zettat123)
Eddie Yang <576951401@qq.com> (@yp05327)
Dong Ge <gedong_1994@163.com> (@sillyguodong) Dong Ge <gedong_1994@163.com> (@sillyguodong)
Xinyi Gong <hestergong@gmail.com> (@HesterG) Xinyi Gong <hestergong@gmail.com> (@HesterG)
wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang) wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang)

View File

@ -28,7 +28,7 @@ XGO_VERSION := go-1.23.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules lint-js: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
# npx vue-tsc npx vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
# npx vue-tsc npx vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules lint-css: node_modules
@ -451,10 +451,6 @@ lint-templates: .venv node_modules
lint-yaml: .venv lint-yaml: .venv
@poetry run yamllint . @poetry run yamllint .
.PHONY: tsc
tsc:
npx vue-tsc
.PHONY: watch .PHONY: watch
watch: watch:
@bash tools/watch.sh @bash tools/watch.sh

View File

@ -1040,9 +1040,13 @@ LEVEL = Info
;; Don't allow download source archive files from UI ;; Don't allow download source archive files from UI
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
;; Allow fork repositories without maximum number limit ;; Allow to fork repositories without maximum number limit
;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true ;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true
;; Allow to fork repositories into the same owner (user or organization)
;; This feature is experimental, not fully tested, and may be changed in the future
;ALLOW_FORK_INTO_SAME_OWNER = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository.editor] ;[repository.editor]

10
go.mod
View File

@ -48,7 +48,7 @@ require (
github.com/ethantkoenig/rupture v1.0.1 github.com/ethantkoenig/rupture v1.0.1
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.7 github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0
@ -121,13 +121,13 @@ require (
github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.31.0
golang.org/x/image v0.21.0 golang.org/x/image v0.21.0
golang.org/x/net v0.30.0 golang.org/x/net v0.30.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.8.0 golang.org/x/sync v0.10.0
golang.org/x/sys v0.26.0 golang.org/x/sys v0.28.0
golang.org/x/text v0.19.0 golang.org/x/text v0.21.0
golang.org/x/tools v0.26.0 golang.org/x/tools v0.26.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1 google.golang.org/protobuf v1.35.1

19
go.sum
View File

@ -293,8 +293,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 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/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks= github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks=
@ -893,8 +893,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
@ -946,8 +947,9 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -982,8 +984,9 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -996,8 +999,9 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -1009,8 +1013,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -37,6 +37,7 @@ type ActionRun struct {
TriggerUser *user_model.User `xorm:"-"` TriggerUser *user_model.User `xorm:"-"`
ScheduleID int64 ScheduleID int64
Ref string `xorm:"index"` // the commit/tag/… that caused the run Ref string `xorm:"index"` // the commit/tag/… that caused the run
IsRefDeleted bool `xorm:"-"`
CommitSHA string CommitSHA string
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow. IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
NeedApproval bool // may need approval if it's a fork pull request NeedApproval bool // may need approval if it's a fork pull request

View File

@ -137,7 +137,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
if err != nil { if err != nil {
return 0, err return 0, err
} }
run.Status = aggregateJobStatus(jobs) run.Status = AggregateJobStatus(jobs)
if run.Started.IsZero() && run.Status.IsRunning() { if run.Started.IsZero() && run.Status.IsRunning() {
run.Started = timeutil.TimeStampNow() run.Started = timeutil.TimeStampNow()
} }
@ -152,29 +152,35 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
return affected, nil return affected, nil
} }
func aggregateJobStatus(jobs []*ActionRunJob) Status { func AggregateJobStatus(jobs []*ActionRunJob) Status {
allDone := true allSuccessOrSkipped := len(jobs) != 0
allWaiting := true allSkipped := len(jobs) != 0
hasFailure := false var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool
for _, job := range jobs { for _, job := range jobs {
if !job.Status.IsDone() { allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped)
allDone = false allSkipped = allSkipped && job.Status == StatusSkipped
} hasFailure = hasFailure || job.Status == StatusFailure
if job.Status != StatusWaiting && !job.Status.IsDone() { hasCancelled = hasCancelled || job.Status == StatusCancelled
allWaiting = false hasWaiting = hasWaiting || job.Status == StatusWaiting
} hasRunning = hasRunning || job.Status == StatusRunning
if job.Status == StatusFailure || job.Status == StatusCancelled { hasBlocked = hasBlocked || job.Status == StatusBlocked
hasFailure = true
}
} }
if allDone { switch {
if hasFailure { case allSkipped:
return StatusFailure return StatusSkipped
} case allSuccessOrSkipped:
return StatusSuccess return StatusSuccess
} case hasCancelled:
if allWaiting { return StatusCancelled
case hasFailure:
return StatusFailure
case hasRunning:
return StatusRunning
case hasWaiting:
return StatusWaiting return StatusWaiting
case hasBlocked:
return StatusBlocked
default:
return StatusUnknown // it shouldn't happen
} }
return StatusRunning
} }

View File

@ -0,0 +1,85 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAggregateJobStatus(t *testing.T) {
testStatuses := func(expected Status, statuses []Status) {
t.Helper()
var jobs []*ActionRunJob
for _, v := range statuses {
jobs = append(jobs, &ActionRunJob{Status: v})
}
actual := AggregateJobStatus(jobs)
if !assert.Equal(t, expected, actual) {
var statusStrings []string
for _, s := range statuses {
statusStrings = append(statusStrings, s.String())
}
t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected])
}
}
cases := []struct {
statuses []Status
expected Status
}{
// unknown cases, maybe it shouldn't happen in real world
{[]Status{}, StatusUnknown},
{[]Status{StatusUnknown, StatusSuccess}, StatusUnknown},
{[]Status{StatusUnknown, StatusSkipped}, StatusUnknown},
{[]Status{StatusUnknown, StatusFailure}, StatusFailure},
{[]Status{StatusUnknown, StatusCancelled}, StatusCancelled},
{[]Status{StatusUnknown, StatusWaiting}, StatusWaiting},
{[]Status{StatusUnknown, StatusRunning}, StatusRunning},
{[]Status{StatusUnknown, StatusBlocked}, StatusBlocked},
// success with other status
{[]Status{StatusSuccess}, StatusSuccess},
{[]Status{StatusSuccess, StatusSkipped}, StatusSuccess}, // skipped doesn't affect success
{[]Status{StatusSuccess, StatusFailure}, StatusFailure},
{[]Status{StatusSuccess, StatusCancelled}, StatusCancelled},
{[]Status{StatusSuccess, StatusWaiting}, StatusWaiting},
{[]Status{StatusSuccess, StatusRunning}, StatusRunning},
{[]Status{StatusSuccess, StatusBlocked}, StatusBlocked},
// any cancelled, then cancelled
{[]Status{StatusCancelled}, StatusCancelled},
{[]Status{StatusCancelled, StatusSuccess}, StatusCancelled},
{[]Status{StatusCancelled, StatusSkipped}, StatusCancelled},
{[]Status{StatusCancelled, StatusFailure}, StatusCancelled},
{[]Status{StatusCancelled, StatusWaiting}, StatusCancelled},
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
// failure with other status, fail fast
// Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
{[]Status{StatusFailure}, StatusFailure},
{[]Status{StatusFailure, StatusSuccess}, StatusFailure},
{[]Status{StatusFailure, StatusSkipped}, StatusFailure},
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
{[]Status{StatusFailure, StatusWaiting}, StatusFailure},
{[]Status{StatusFailure, StatusRunning}, StatusFailure},
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status
// TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
{[]Status{StatusSkipped}, StatusSkipped},
{[]Status{StatusSkipped, StatusSuccess}, StatusSuccess},
{[]Status{StatusSkipped, StatusFailure}, StatusFailure},
{[]Status{StatusSkipped, StatusCancelled}, StatusCancelled},
{[]Status{StatusSkipped, StatusWaiting}, StatusWaiting},
{[]Status{StatusSkipped, StatusRunning}, StatusRunning},
{[]Status{StatusSkipped, StatusBlocked}, StatusBlocked},
}
for _, c := range cases {
testStatuses(c.expected, c.statuses)
}
}

View File

@ -17,7 +17,7 @@ func TestGetLatestRunnerToken(t *testing.T) {
token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3})
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }
func TestNewRunnerToken(t *testing.T) { func TestNewRunnerToken(t *testing.T) {
@ -26,7 +26,7 @@ func TestNewRunnerToken(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }
func TestUpdateRunnerToken(t *testing.T) { func TestUpdateRunnerToken(t *testing.T) {
@ -36,5 +36,5 @@ func TestUpdateRunnerToken(t *testing.T) {
assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token)) assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token))
expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, token, expectedToken) assert.EqualValues(t, expectedToken, token)
} }

View File

@ -4,7 +4,6 @@
package activities_test package activities_test
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -91,11 +90,11 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?") assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
assert.Equal(t, count, int64(contributions)) assert.Equal(t, count, int64(contributions))
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc)) assert.Equal(t, tc.CountResult, contributions, "testcase '%s'", tc.desc)
// Test JSON rendering // Test JSON rendering
jsonData, err := json.Marshal(heatmap) jsonData, err := json.Marshal(heatmap)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tc.JSONResult, string(jsonData)) assert.JSONEq(t, tc.JSONResult, string(jsonData))
} }
} }

View File

@ -18,7 +18,7 @@ func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
secret, err := app.GenerateClientSecret(db.DefaultContext) secret, err := app.GenerateClientSecret(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(secret) > 0) assert.NotEmpty(t, secret)
unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
} }
@ -165,7 +165,7 @@ func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, code) assert.NotNil(t, code)
assert.True(t, len(code.Code) > 32) // secret length > 32 assert.Greater(t, len(code.Code), 32) // secret length > 32
} }
func TestOAuth2Grant_TableName(t *testing.T) { func TestOAuth2Grant_TableName(t *testing.T) {

View File

@ -4,7 +4,7 @@
package db // it's not db_test, because this file is for testing the private type halfCommitter package db // it's not db_test, because this file is for testing the private type halfCommitter
import ( import (
"fmt" "errors"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -80,7 +80,7 @@ func Test_halfCommitter(t *testing.T) {
testWithCommitter(mockCommitter, func(committer Committer) error { testWithCommitter(mockCommitter, func(committer Committer) error {
defer committer.Close() defer committer.Close()
if true { if true {
return fmt.Errorf("error") return errors.New("error")
} }
return committer.Commit() return committer.Commit()
}) })
@ -94,7 +94,7 @@ func Test_halfCommitter(t *testing.T) {
testWithCommitter(mockCommitter, func(committer Committer) error { testWithCommitter(mockCommitter, func(committer Committer) error {
committer.Close() committer.Close()
committer.Commit() committer.Commit()
return fmt.Errorf("error") return errors.New("error")
}) })
mockCommitter.Assert(t) mockCommitter.Assert(t)

View File

@ -38,8 +38,6 @@ func TestIterate(t *testing.T) {
if !has { if !has {
return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID} return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID}
} }
assert.EqualValues(t, repoUnit.RepoID, repoUnit.RepoID)
assert.EqualValues(t, repoUnit.CreatedUnix, repoUnit.CreatedUnix)
return nil return nil
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -26,8 +26,10 @@ const (
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
) )
const ( // NoConditionID means a condition to filter the records which don't match any id.
// Which means a condition to filter the records which don't match any id. // eg: "milestone_id=-1" means "find the items without any milestone.
// It's different from zero which means the condition could be ignored. const NoConditionID int64 = -1
NoConditionID = -1
) // NonExistingID means a condition to match no result (eg: a non-existing user)
// It doesn't use -1 or -2 because they are used as builtin users.
const NonExistingID int64 = -1000000

View File

@ -36,3 +36,41 @@
updated: 1683636626 updated: 1683636626
need_approval: 0 need_approval: 0
approved_by: 0 approved_by: 0
-
id: 793
title: "job output"
repo_id: 4
owner_id: 1
workflow_id: "test.yaml"
index: 189
trigger_user_id: 1
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 794
title: "job output"
repo_id: 4
owner_id: 1
workflow_id: "test.yaml"
index: 190
trigger_user_id: 1
ref: "refs/heads/test"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View File

@ -26,3 +26,46 @@
status: 1 status: 1
started: 1683636528 started: 1683636528
stopped: 1683636626 stopped: 1683636626
-
id: 194
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job1 (1)
attempt: 1
job_id: job1
task_id: 49
status: 1
started: 1683636528
stopped: 1683636626
-
id: 195
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job1 (2)
attempt: 1
job_id: job1
task_id: 50
status: 1
started: 1683636528
stopped: 1683636626
-
id: 196
run_id: 793
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job2
attempt: 1
job_id: job2
needs: [job1]
task_id: 51
status: 5
started: 1683636528
stopped: 1683636626

View File

@ -57,3 +57,63 @@
log_length: 707 log_length: 707
log_size: 90179 log_size: 90179
log_expired: 0 log_expired: 0
-
id: 49
job_id: 194
attempt: 1
runner_id: 1
status: 1 # success
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784220
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0
-
id: 50
job_id: 195
attempt: 1
runner_id: 1
status: 1 # success
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784221
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0
-
id: 51
job_id: 196
attempt: 1
runner_id: 1
status: 6 # running
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
token_salt: ffffffffff
token_last_eight: ffffffff
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0

View File

@ -0,0 +1,20 @@
-
id: 1
task_id: 49
output_key: output_a
output_value: abc
-
id: 2
task_id: 49
output_key: output_b
output_value: ''
-
id: 3
task_id: 50
output_key: output_a
output_value: ''
-
id: 4
task_id: 50
output_key: output_b
output_value: bbb

View File

@ -81,3 +81,15 @@
is_deleted: false is_deleted: false
deleted_by_id: 0 deleted_by_id: 0
deleted_unix: 0 deleted_unix: 0
-
id: 15
repo_id: 4
name: 'master'
commit_id: 'c7cd3cd144e6d23c9d6f3d07e52b2c1a956e0338'
commit_message: 'add Readme'
commit_time: 1588147171
pusher_id: 13
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

View File

@ -4,6 +4,7 @@
reviewer_id: 1 reviewer_id: 1
issue_id: 2 issue_id: 2
content: "Demo Review" content: "Demo Review"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
- -
@ -12,6 +13,7 @@
reviewer_id: 534543 reviewer_id: 534543
issue_id: 534543 issue_id: 534543
content: "Invalid Review #1" content: "Invalid Review #1"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
- -
@ -20,6 +22,7 @@
reviewer_id: 1 reviewer_id: 1
issue_id: 343545 issue_id: 343545
content: "Invalid Review #2" content: "Invalid Review #2"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
- -
@ -28,6 +31,7 @@
reviewer_id: 1 reviewer_id: 1
issue_id: 2 issue_id: 2
content: "Pending Review" content: "Pending Review"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
- -
@ -36,6 +40,7 @@
reviewer_id: 1 reviewer_id: 1
issue_id: 3 issue_id: 3
content: "New review 1" content: "New review 1"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
- -
@ -61,8 +66,8 @@
type: 1 type: 1
reviewer_id: 4 reviewer_id: 4
issue_id: 3 issue_id: 3
original_author_id: 0
content: "New review 5" content: "New review 5"
original_author_id: 0
commit_id: 8091a55037cd59e47293aca02981b5a67076b364 commit_id: 8091a55037cd59e47293aca02981b5a67076b364
stale: true stale: true
updated_unix: 946684813 updated_unix: 946684813
@ -73,9 +78,9 @@
reviewer_id: 2 reviewer_id: 2
issue_id: 3 issue_id: 3
content: "New review 3 rejected" content: "New review 3 rejected"
original_author_id: 0
updated_unix: 946684814 updated_unix: 946684814
created_unix: 946684814 created_unix: 946684814
original_author_id: 0
- -
id: 10 id: 10
@ -83,6 +88,7 @@
reviewer_id: 100 reviewer_id: 100
issue_id: 3 issue_id: 3
content: "a deleted user's review" content: "a deleted user's review"
original_author_id: 0
official: true official: true
updated_unix: 946684815 updated_unix: 946684815
created_unix: 946684815 created_unix: 946684815
@ -112,6 +118,7 @@
reviewer_id: 5 reviewer_id: 5
issue_id: 11 issue_id: 11
content: "old review from user5" content: "old review from user5"
original_author_id: 0
updated_unix: 946684820 updated_unix: 946684820
created_unix: 946684820 created_unix: 946684820
@ -121,6 +128,7 @@
reviewer_id: 5 reviewer_id: 5
issue_id: 11 issue_id: 11
content: "duplicate review from user5 (latest)" content: "duplicate review from user5 (latest)"
original_author_id: 0
updated_unix: 946684830 updated_unix: 946684830
created_unix: 946684830 created_unix: 946684830
@ -130,6 +138,7 @@
reviewer_id: 6 reviewer_id: 6
issue_id: 11 issue_id: 11
content: "singular review from org6 and final review for this pr" content: "singular review from org6 and final review for this pr"
original_author_id: 0
updated_unix: 946684831 updated_unix: 946684831
created_unix: 946684831 created_unix: 946684831
@ -139,6 +148,7 @@
reviewer_id: 20 reviewer_id: 20
issue_id: 20 issue_id: 20
content: "review request for user20" content: "review request for user20"
original_author_id: 0
updated_unix: 946684832 updated_unix: 946684832
created_unix: 946684832 created_unix: 946684832
@ -148,6 +158,7 @@
reviewer_id: 20 reviewer_id: 20
issue_id: 20 issue_id: 20
content: "review approved by user20" content: "review approved by user20"
original_author_id: 0
updated_unix: 946684833 updated_unix: 946684833
created_unix: 946684833 created_unix: 946684833
@ -158,6 +169,7 @@
reviewer_team_id: 5 reviewer_team_id: 5
issue_id: 20 issue_id: 20
content: "review request for team5" content: "review request for team5"
original_author_id: 0
updated_unix: 946684834 updated_unix: 946684834
created_unix: 946684834 created_unix: 946684834
@ -168,6 +180,7 @@
reviewer_team_id: 0 reviewer_team_id: 0
issue_id: 20 issue_id: 20
content: "review request for user15" content: "review request for user15"
original_author_id: 0
updated_unix: 946684835 updated_unix: 946684835
created_unix: 946684835 created_unix: 946684835
@ -177,6 +190,7 @@
reviewer_id: 1 reviewer_id: 1
issue_id: 2 issue_id: 2
content: "Review Comment" content: "Review Comment"
original_author_id: 0
updated_unix: 946684810 updated_unix: 946684810
created_unix: 946684810 created_unix: 946684810
@ -186,6 +200,7 @@
reviewer_id: 5 reviewer_id: 5
issue_id: 3 issue_id: 3
content: "reviewed by user5" content: "reviewed by user5"
original_author_id: 0
commit_id: 4a357436d925b5c974181ff12a994538ddc5a269 commit_id: 4a357436d925b5c974181ff12a994538ddc5a269
updated_unix: 946684816 updated_unix: 946684816
created_unix: 946684816 created_unix: 946684816
@ -196,5 +211,6 @@
reviewer_id: 5 reviewer_id: 5
issue_id: 3 issue_id: 3
content: "review request for user5" content: "review request for user5"
original_author_id: 0
updated_unix: 946684817 updated_unix: 946684817
created_unix: 946684817 created_unix: 946684817

View File

@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
@ -169,9 +170,22 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
return &branch, nil return &branch, nil
} }
func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) { func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
branches := make([]*Branch, 0, len(branchNames)) branches := make([]*Branch, 0, len(branchNames))
return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches)
sess := db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames)
if !includeDeleted {
sess.And("is_deleted=?", false)
}
return branches, sess.Find(&branches)
}
func BranchesToNamesSet(branches []*Branch) container.Set[string] {
names := make(container.Set[string], len(branches))
for _, branch := range branches {
names.Add(branch.Name)
}
return names
} }
func AddBranches(ctx context.Context, branches []*Branch) error { func AddBranches(ctx context.Context, branches []*Branch) error {

View File

@ -34,7 +34,7 @@ func TestGetCommitStatuses(t *testing.T) {
SHA: sha1, SHA: sha1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int(maxResults), 5) assert.Equal(t, 5, int(maxResults))
assert.Len(t, statuses, 5) assert.Len(t, statuses, 5)
assert.Equal(t, "ci/awesomeness", statuses[0].Context) assert.Equal(t, "ci/awesomeness", statuses[0].Context)
@ -63,7 +63,7 @@ func TestGetCommitStatuses(t *testing.T) {
SHA: sha1, SHA: sha1,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int(maxResults), 5) assert.Equal(t, 5, int(maxResults))
assert.Empty(t, statuses) assert.Empty(t, statuses)
} }

View File

@ -4,7 +4,6 @@
package git package git
import ( import (
"fmt"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -76,7 +75,7 @@ func TestBranchRuleMatch(t *testing.T) {
infact = " not" infact = " not"
} }
assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName), assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName),
fmt.Sprintf("%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact), "%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact,
) )
} }
} }

View File

@ -64,7 +64,7 @@ func TestFetchCodeComments(t *testing.T) {
} }
func TestAsCommentType(t *testing.T) { func TestAsCommentType(t *testing.T) {
assert.Equal(t, issues_model.CommentType(0), issues_model.CommentTypeComment) assert.Equal(t, issues_model.CommentTypeComment, issues_model.CommentType(0))
assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("")) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType(""))
assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense")) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense"))
assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment")) assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment"))

View File

@ -125,8 +125,11 @@ type Issue struct {
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"` PullRequest *PullRequest `xorm:"-"`
NumComments int NumComments int
Ref string
PinOrder int `xorm:"DEFAULT 0"` // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
Ref string
PinOrder int `xorm:"DEFAULT 0"`
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`

View File

@ -18,12 +18,12 @@ func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
} }
defer committer.Close() defer committer.Close()
var max int64 var maxIndex int64
if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil { if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil {
return err return err
} }
if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, max); err != nil { if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex); err != nil {
return err return err
} }

View File

@ -27,8 +27,8 @@ type IssuesOptions struct { //nolint
RepoIDs []int64 // overwrites RepoCond if the length is not 0 RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories AllPublic bool // include also all public repositories
RepoCond builder.Cond RepoCond builder.Cond
AssigneeID int64 AssigneeID optional.Option[int64]
PosterID int64 PosterID optional.Option[int64]
MentionedID int64 MentionedID int64
ReviewRequestedID int64 ReviewRequestedID int64
ReviewedID int64 ReviewedID int64
@ -231,15 +231,8 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
sess.And("issue.is_closed=?", opts.IsClosed.Value()) sess.And("issue.is_closed=?", opts.IsClosed.Value())
} }
if opts.AssigneeID > 0 { applyAssigneeCondition(sess, opts.AssigneeID)
applyAssigneeCondition(sess, opts.AssigneeID) applyPosterCondition(sess, opts.PosterID)
} else if opts.AssigneeID == db.NoConditionID {
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
}
if opts.PosterID > 0 {
applyPosterCondition(sess, opts.PosterID)
}
if opts.MentionedID > 0 { if opts.MentionedID > 0 {
applyMentionedCondition(sess, opts.MentionedID) applyMentionedCondition(sess, opts.MentionedID)
@ -359,13 +352,27 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati
return cond return cond
} }
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) { func applyAssigneeCondition(sess *xorm.Session, assigneeID optional.Option[int64]) {
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). // old logic: 0 is also treated as "not filtering assignee", because the "assignee" was read as FormInt64
And("issue_assignees.assignee_id = ?", assigneeID) if !assigneeID.Has() || assigneeID.Value() == 0 {
return
}
if assigneeID.Value() == db.NoConditionID {
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
} else {
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", assigneeID.Value())
}
} }
func applyPosterCondition(sess *xorm.Session, posterID int64) { func applyPosterCondition(sess *xorm.Session, posterID optional.Option[int64]) {
sess.And("issue.poster_id=?", posterID) if !posterID.Has() {
return
}
// poster doesn't need to support db.NoConditionID(-1), so just use the value as-is
if posterID.Has() {
sess.And("issue.poster_id=?", posterID.Value())
}
} }
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) { func applyMentionedCondition(sess *xorm.Session, mentionedID int64) {

View File

@ -151,15 +151,9 @@ func applyIssuesOptions(sess *xorm.Session, opts *IssuesOptions, issueIDs []int6
applyProjectCondition(sess, opts) applyProjectCondition(sess, opts)
if opts.AssigneeID > 0 { applyAssigneeCondition(sess, opts.AssigneeID)
applyAssigneeCondition(sess, opts.AssigneeID)
} else if opts.AssigneeID == db.NoConditionID {
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
}
if opts.PosterID > 0 { applyPosterCondition(sess, opts.PosterID)
applyPosterCondition(sess, opts.PosterID)
}
if opts.MentionedID > 0 { if opts.MentionedID > 0 {
applyMentionedCondition(sess, opts.MentionedID) applyMentionedCondition(sess, opts.MentionedID)

View File

@ -16,6 +16,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -155,7 +156,7 @@ func TestIssues(t *testing.T) {
}{ }{
{ {
issues_model.IssuesOptions{ issues_model.IssuesOptions{
AssigneeID: 1, AssigneeID: optional.Some(int64(1)),
SortType: "oldest", SortType: "oldest",
}, },
[]int64{1, 6}, []int64{1, 6},
@ -433,7 +434,7 @@ func assertCreateIssues(t *testing.T, isPull bool) {
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
assert.EqualValues(t, milestone.ID, 1) assert.EqualValues(t, 1, milestone.ID)
reaction := &issues_model.Reaction{ reaction := &issues_model.Reaction{
Type: "heart", Type: "heart",
UserID: owner.ID, UserID: owner.ID,

View File

@ -48,17 +48,17 @@ func TestGetIssueWatchers(t *testing.T) {
iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{}) iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Watcher is inactive, thus 0 // Watcher is inactive, thus 0
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Watcher is explicit not watching // Watcher is explicit not watching
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)
// Issue has no Watchers // Issue has no Watchers
assert.Len(t, iws, 0) assert.Empty(t, iws)
iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{}) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{})
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -31,12 +31,12 @@ func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) {
// First test : with negative and scope // First test : with negative and scope
label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"}) label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"})
assert.Equal(t, "1", label.QueryString) assert.Equal(t, "1", label.QueryString)
assert.Equal(t, true, label.IsSelected) assert.True(t, label.IsSelected)
// Second test : with duplicates // Second test : with duplicates
label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"}) label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"})
assert.Equal(t, "1,8", label.QueryString) assert.Equal(t, "1,8", label.QueryString)
assert.Equal(t, false, label.IsSelected) assert.False(t, label.IsSelected)
// Third test : empty set // Third test : empty set
label.LoadSelectedLabelsAfterClick([]int64{}, []string{}) label.LoadSelectedLabelsAfterClick([]int64{}, []string{})
@ -248,7 +248,7 @@ func TestGetLabelsByIssueID(t *testing.T) {
labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID) labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, labels, 0) assert.Empty(t, labels)
} }
func TestUpdateLabel(t *testing.T) { func TestUpdateLabel(t *testing.T) {
@ -271,7 +271,7 @@ func TestUpdateLabel(t *testing.T) {
assert.EqualValues(t, label.Color, newLabel.Color) assert.EqualValues(t, label.Color, newLabel.Color)
assert.EqualValues(t, label.Name, newLabel.Name) assert.EqualValues(t, label.Name, newLabel.Name)
assert.EqualValues(t, label.Description, newLabel.Description) assert.EqualValues(t, label.Description, newLabel.Description)
assert.EqualValues(t, newLabel.ArchivedUnix, 0) assert.EqualValues(t, 0, newLabel.ArchivedUnix)
unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
} }

View File

@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
IsClosed: optional.Some(false), IsClosed: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, milestones, 0) assert.Empty(t, milestones)
} }
func TestGetMilestones(t *testing.T) { func TestGetMilestones(t *testing.T) {

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -257,6 +258,64 @@ func (prs PullRequestList) GetIssueIDs() []int64 {
}) })
} }
func (prs PullRequestList) LoadReviewCommentsCounts(ctx context.Context) (map[int64]int, error) {
issueIDs := prs.GetIssueIDs()
countsMap := make(map[int64]int, len(issueIDs))
counts := make([]struct {
IssueID int64
Count int
}, 0, len(issueIDs))
if err := db.GetEngine(ctx).Select("issue_id, count(*) as count").
Table("comment").In("issue_id", issueIDs).And("type = ?", CommentTypeReview).
GroupBy("issue_id").Find(&counts); err != nil {
return nil, err
}
for _, c := range counts {
countsMap[c.IssueID] = c.Count
}
return countsMap, nil
}
func (prs PullRequestList) LoadReviews(ctx context.Context) (ReviewList, error) {
issueIDs := prs.GetIssueIDs()
reviews := make([]*Review, 0, len(issueIDs))
subQuery := builder.Select("max(id) as id").
From("review").
Where(builder.In("issue_id", issueIDs)).
And(builder.In("`type`", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest)).
And(builder.Eq{
"dismissed": false,
"original_author_id": 0,
"reviewer_team_id": 0,
}).
GroupBy("issue_id, reviewer_id")
// Get latest review of each reviewer, sorted in order they were made
if err := db.GetEngine(ctx).In("id", subQuery).OrderBy("review.updated_unix ASC").Find(&reviews); err != nil {
return nil, err
}
teamReviewRequests := make([]*Review, 0, 5)
subQueryTeam := builder.Select("max(id) as id").
From("review").
Where(builder.In("issue_id", issueIDs)).
And(builder.Eq{
"original_author_id": 0,
}).And(builder.Neq{
"reviewer_team_id": 0,
}).
GroupBy("issue_id, reviewer_team_id")
if err := db.GetEngine(ctx).In("id", subQueryTeam).OrderBy("review.updated_unix ASC").Find(&teamReviewRequests); err != nil {
return nil, err
}
if len(teamReviewRequests) > 0 {
reviews = append(reviews, teamReviewRequests...)
}
return reviews, nil
}
// HasMergedPullRequestInRepo returns whether the user(poster) has merged pull-request in the repo // HasMergedPullRequestInRepo returns whether the user(poster) has merged pull-request in the repo
func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bool, error) { func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bool, error) {
return db.GetEngine(ctx). return db.GetEngine(ctx).

View File

@ -0,0 +1,64 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package issues_test
import (
"testing"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestPullRequestList_LoadAttributes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
}
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext))
for _, pr := range prs {
assert.NotNil(t, pr.Issue)
assert.Equal(t, pr.IssueID, pr.Issue.ID)
}
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes(db.DefaultContext))
}
func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
}
reviewComments, err := issues_model.PullRequestList(prs).LoadReviewCommentsCounts(db.DefaultContext)
assert.NoError(t, err)
assert.Len(t, reviewComments, 2)
for _, pr := range prs {
assert.EqualValues(t, 1, reviewComments[pr.IssueID])
}
}
func TestPullRequestList_LoadReviews(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
}
reviewList, err := issues_model.PullRequestList(prs).LoadReviews(db.DefaultContext)
assert.NoError(t, err)
// 1, 7, 8, 9, 10, 22
assert.Len(t, reviewList, 6)
assert.EqualValues(t, 1, reviewList[0].ID)
assert.EqualValues(t, 7, reviewList[1].ID)
assert.EqualValues(t, 8, reviewList[2].ID)
assert.EqualValues(t, 9, reviewList[3].ID)
assert.EqualValues(t, 10, reviewList[4].ID)
assert.EqualValues(t, 22, reviewList[5].ID)
}

View File

@ -79,11 +79,11 @@ func TestPullRequestsNewest(t *testing.T) {
func TestLoadRequestedReviewers(t *testing.T) { func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
assert.NoError(t, pull.LoadIssue(db.DefaultContext)) assert.NoError(t, pull.LoadIssue(db.DefaultContext))
issue := pull.Issue issue := pull.Issue
assert.NoError(t, issue.LoadRepo(db.DefaultContext)) assert.NoError(t, issue.LoadRepo(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 0) assert.Empty(t, pull.RequestedReviewers)
user1, err := user_model.GetUserByID(db.DefaultContext, 1) user1, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err) assert.NoError(t, err)
@ -93,7 +93,7 @@ func TestLoadRequestedReviewers(t *testing.T) {
assert.NotNil(t, comment) assert.NotNil(t, comment)
assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext)) assert.NoError(t, pull.LoadRequestedReviewers(db.DefaultContext))
assert.Len(t, pull.RequestedReviewers, 1) assert.Len(t, pull.RequestedReviewers, 6)
comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{}) comment, err = issues_model.RemoveReviewRequest(db.DefaultContext, issue, user1, &user_model.User{})
assert.NoError(t, err) assert.NoError(t, err)
@ -234,22 +234,6 @@ func TestPullRequest_UpdateCols(t *testing.T) {
unittest.CheckConsistencyFor(t, pr) unittest.CheckConsistencyFor(t, pr)
} }
func TestPullRequestList_LoadAttributes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
prs := []*issues_model.PullRequest{
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
}
assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes(db.DefaultContext))
for _, pr := range prs {
assert.NotNil(t, pr.Issue)
assert.Equal(t, pr.IssueID, pr.Issue.ID)
}
assert.NoError(t, issues_model.PullRequestList([]*issues_model.PullRequest{}).LoadAttributes(db.DefaultContext))
}
// TODO TestAddTestPullRequestTask // TODO TestAddTestPullRequestTask
func TestPullRequest_IsWorkInProgress(t *testing.T) { func TestPullRequest_IsWorkInProgress(t *testing.T) {

View File

@ -47,14 +47,9 @@ func (reviews ReviewList) LoadReviewersTeams(ctx context.Context) error {
} }
} }
teamsMap := make(map[int64]*organization_model.Team, 0) teamsMap, err := organization_model.GetTeamsByIDs(ctx, reviewersTeamsIDs)
for _, teamID := range reviewersTeamsIDs { if err != nil {
team, err := organization_model.GetTeamByID(ctx, teamID) return err
if err != nil {
return err
}
teamsMap[teamID] = team
} }
for _, review := range reviews { for _, review := range reviews {

View File

@ -126,42 +126,48 @@ func TestGetReviewersByIssueID(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
expectedReviews := []*issues_model.Review{} expectedReviews := []*issues_model.Review{}
expectedReviews = append(expectedReviews, expectedReviews = append(expectedReviews,
&issues_model.Review{ &issues_model.Review{
ID: 7,
Reviewer: org3, Reviewer: org3,
Type: issues_model.ReviewTypeReject, Type: issues_model.ReviewTypeReject,
UpdatedUnix: 946684812, UpdatedUnix: 946684812,
}, },
&issues_model.Review{ &issues_model.Review{
ID: 8,
Reviewer: user4, Reviewer: user4,
Type: issues_model.ReviewTypeApprove, Type: issues_model.ReviewTypeApprove,
UpdatedUnix: 946684813, UpdatedUnix: 946684813,
}, },
&issues_model.Review{ &issues_model.Review{
ID: 9,
Reviewer: user2, Reviewer: user2,
Type: issues_model.ReviewTypeReject, Type: issues_model.ReviewTypeReject,
UpdatedUnix: 946684814, UpdatedUnix: 946684814,
}) },
&issues_model.Review{
ID: 10,
Reviewer: user_model.NewGhostUser(),
Type: issues_model.ReviewTypeReject,
UpdatedUnix: 946684815,
},
&issues_model.Review{
ID: 22,
Reviewer: user5,
Type: issues_model.ReviewTypeRequest,
UpdatedUnix: 946684817,
},
)
allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
assert.NoError(t, err) assert.NoError(t, err)
for _, review := range allReviews { for _, review := range allReviews {
assert.NoError(t, review.LoadReviewer(db.DefaultContext)) assert.NoError(t, review.LoadReviewer(db.DefaultContext))
} }
if assert.Len(t, allReviews, 3) { if assert.Len(t, allReviews, 5) {
for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
assert.Equal(t, expectedReviews[i].Type, review.Type)
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
}
}
allReviews, err = issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID)
assert.NoError(t, err)
assert.NoError(t, allReviews.LoadReviewers(db.DefaultContext))
if assert.Len(t, allReviews, 3) {
for i, review := range allReviews { for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer) assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
assert.Equal(t, expectedReviews[i].Type, review.Type) assert.Equal(t, expectedReviews[i].Type, review.Type)

View File

@ -32,7 +32,7 @@ func TestCancelStopwatch(t *testing.T) {
_ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
assert.Nil(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2)) assert.NoError(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2))
} }
func TestStopwatchExists(t *testing.T) { func TestStopwatchExists(t *testing.T) {

View File

@ -50,7 +50,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
// by User // by User
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 1})
@ -60,7 +60,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 3}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 3})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
// by Repo // by Repo
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 2}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 2})
@ -69,7 +69,7 @@ func TestGetTrackedTimes(t *testing.T) {
assert.Equal(t, int64(1), times[0].Time) assert.Equal(t, int64(1), times[0].Time)
issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID) issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, issue.RepoID, int64(2)) assert.Equal(t, int64(2), issue.RepoID)
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 1}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 1})
assert.NoError(t, err) assert.NoError(t, err)
@ -77,7 +77,7 @@ func TestGetTrackedTimes(t *testing.T) {
times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 10}) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 10})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, times, 0) assert.Empty(t, times)
} }
func TestTotalTimesForEachUser(t *testing.T) { func TestTotalTimesForEachUser(t *testing.T) {

View File

@ -56,8 +56,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments) err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range issueAttachments { for _, attach := range issueAttachments {
assert.Greater(t, attach.RepoID, int64(0)) assert.Positive(t, attach.RepoID)
assert.Greater(t, attach.IssueID, int64(0)) assert.Positive(t, attach.IssueID)
var issue Issue var issue Issue
has, err := x.ID(attach.IssueID).Get(&issue) has, err := x.ID(attach.IssueID).Get(&issue)
assert.NoError(t, err) assert.NoError(t, err)
@ -69,8 +69,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) {
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments) err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
assert.NoError(t, err) assert.NoError(t, err)
for _, attach := range releaseAttachments { for _, attach := range releaseAttachments {
assert.Greater(t, attach.RepoID, int64(0)) assert.Positive(t, attach.RepoID)
assert.Greater(t, attach.ReleaseID, int64(0)) assert.Positive(t, attach.ReleaseID)
var release Release var release Release
has, err := x.ID(attach.ReleaseID).Get(&release) has, err := x.ID(attach.ReleaseID).Get(&release)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -107,12 +107,12 @@ func Test_RepositoryFormat(t *testing.T) {
repo = new(Repository) repo = new(Repository)
ok, err := x.ID(2).Get(repo) ok, err := x.ID(2).Get(repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, true, ok) assert.True(t, ok)
assert.EqualValues(t, "sha1", repo.ObjectFormatName) assert.EqualValues(t, "sha1", repo.ObjectFormatName)
repo = new(Repository) repo = new(Repository)
ok, err = x.ID(id).Get(repo) ok, err = x.ID(id).Get(repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, true, ok) assert.True(t, ok)
assert.EqualValues(t, "sha256", repo.ObjectFormatName) assert.EqualValues(t, "sha256", repo.ObjectFormatName)
} }

View File

@ -39,7 +39,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) {
tables, err := x.DBMetas() tables, err := x.DBMetas()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, len(tables)) assert.Len(t, tables, 1)
found := false found := false
for _, index := range tables[0].Indexes { for _, index := range tables[0].Indexes {
if index.Type == schemas.UniqueType { if index.Type == schemas.UniqueType {

View File

@ -40,7 +40,7 @@ func TestFindOrgs(t *testing.T) {
IncludePrivate: false, IncludePrivate: false,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, orgs, 0) assert.Empty(t, orgs)
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4, UserID: 4,

View File

@ -283,7 +283,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) {
OrgID: unittest.NonexistentID, OrgID: unittest.NonexistentID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, orgUsers, 0) assert.Empty(t, orgUsers)
} }
func TestChangeOrgUserStatus(t *testing.T) { func TestChangeOrgUserStatus(t *testing.T) {

View File

@ -131,3 +131,8 @@ func GetTeamsByOrgIDs(ctx context.Context, orgIDs []int64) (TeamList, error) {
teams := make([]*Team, 0, 10) teams := make([]*Team, 0, 10)
return teams, db.GetEngine(ctx).Where(builder.In("org_id", orgIDs)).Find(&teams) return teams, db.GetEngine(ctx).Where(builder.In("org_id", orgIDs)).Find(&teams)
} }
func GetTeamsByIDs(ctx context.Context, teamIDs []int64) (map[int64]*Team, error) {
teams := make(map[int64]*Team, len(teamIDs))
return teams, db.GetEngine(ctx).Where(builder.In("`id`", teamIDs)).Find(&teams)
}

View File

@ -0,0 +1,25 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package organization_test
import (
"testing"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func Test_GetTeamsByIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// 1 owner team, 2 normal team
teams, err := org_model.GetTeamsByIDs(db.DefaultContext, []int64{1, 2})
assert.NoError(t, err)
assert.Len(t, teams, 2)
assert.Equal(t, "Owners", teams[1].Name)
assert.Equal(t, "team1", teams[2].Name)
}

View File

@ -15,7 +15,7 @@ func TestAccessMode(t *testing.T) {
m := ParseAccessMode(name) m := ParseAccessMode(name)
assert.Equal(t, AccessMode(i), m) assert.Equal(t, AccessMode(i), m)
} }
assert.Equal(t, AccessMode(4), AccessModeOwner) assert.Equal(t, AccessModeOwner, AccessMode(4))
assert.Equal(t, "owner", AccessModeOwner.ToString()) assert.Equal(t, "owner", AccessModeOwner.ToString())
assert.Equal(t, AccessModeNone, ParseAccessMode("owner")) assert.Equal(t, AccessModeNone, ParseAccessMode("owner"))
assert.Equal(t, AccessModeNone, ParseAccessMode("invalid")) assert.Equal(t, AccessModeNone, ParseAccessMode("invalid"))

View File

@ -5,7 +5,6 @@ package project
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -66,7 +65,7 @@ func Test_moveIssuesToAnotherColumn(t *testing.T) {
issues, err = column1.GetIssues(db.DefaultContext) issues, err = column1.GetIssues(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, issues, 0) assert.Empty(t, issues)
issues, err = column2.GetIssues(db.DefaultContext) issues, err = column2.GetIssues(db.DefaultContext)
assert.NoError(t, err) assert.NoError(t, err)
@ -123,5 +122,5 @@ func Test_NewColumn(t *testing.T) {
ProjectID: project1.ID, ProjectID: project1.ID,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "maximum number of columns reached")) assert.Contains(t, err.Error(), "maximum number of columns reached")
} }

View File

@ -144,8 +144,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "https://try.gitea.io/user2/repo2") test(t, "https://try.gitea.io/user2/repo2")
@ -159,8 +159,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2") test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2")
@ -177,8 +177,8 @@ func TestGetRepositoryByURL(t *testing.T) {
assert.NotNil(t, repo) assert.NotNil(t, repo)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, repo.ID, int64(2)) assert.Equal(t, int64(2), repo.ID)
assert.Equal(t, repo.OwnerID, int64(2)) assert.Equal(t, int64(2), repo.OwnerID)
} }
test(t, "sshuser@try.gitea.io:user2/repo2") test(t, "sshuser@try.gitea.io:user2/repo2")

View File

@ -52,7 +52,7 @@ func TestRepository_GetStargazers2(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, gazers, 0) assert.Empty(t, gazers)
} }
func TestClearRepoStars(t *testing.T) { func TestClearRepoStars(t *testing.T) {
@ -71,5 +71,5 @@ func TestClearRepoStars(t *testing.T) {
gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, gazers, 0) assert.Empty(t, gazers)
} }

View File

@ -21,7 +21,7 @@ func TestRepoAssignees(t *testing.T) {
users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2) users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 1) assert.Len(t, users, 1)
assert.Equal(t, users[0].ID, int64(2)) assert.Equal(t, int64(2), users[0].ID)
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)

View File

@ -41,7 +41,7 @@ func TestGetWatchers(t *testing.T) {
watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watches, 0) assert.Empty(t, watches)
} }
func TestRepository_GetWatchers(t *testing.T) { func TestRepository_GetWatchers(t *testing.T) {
@ -58,7 +58,7 @@ func TestRepository_GetWatchers(t *testing.T) {
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9})
watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, watchers, 0) assert.Empty(t, watchers)
} }
func TestWatchIfAuto(t *testing.T) { func TestWatchIfAuto(t *testing.T) {

View File

@ -79,7 +79,7 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any)
e := db.GetEngine(db.DefaultContext).Table(table) e := db.GetEngine(db.DefaultContext).Table(table)
res, err := whereOrderConditions(e, conditions).Query() res, err := whereOrderConditions(e, conditions).Query()
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(res) == 1, assert.Len(t, res, 1,
"Expected to find one row in %s (with conditions %+v), but found %d", "Expected to find one row in %s (with conditions %+v), but found %d",
table, conditions, len(res), table, conditions, len(res),
) )

View File

@ -97,8 +97,7 @@ func TestListEmails(t *testing.T) {
} }
emails, count, err := user_model.SearchEmails(db.DefaultContext, opts) emails, count, err := user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEqual(t, int64(0), count) assert.Greater(t, count, int64(5))
assert.True(t, count > 5)
contains := func(match func(s *user_model.SearchEmailResult) bool) bool { contains := func(match func(s *user_model.SearchEmailResult) bool) bool {
for _, v := range emails { for _, v := range emails {

View File

@ -56,5 +56,5 @@ func TestSettings(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, settings, 0) assert.Empty(t, settings)
} }

View File

@ -201,7 +201,7 @@ func TestNewGitSig(t *testing.T) {
assert.NotContains(t, sig.Name, "<") assert.NotContains(t, sig.Name, "<")
assert.NotContains(t, sig.Name, ">") assert.NotContains(t, sig.Name, ">")
assert.NotContains(t, sig.Name, "\n") assert.NotContains(t, sig.Name, "\n")
assert.NotEqual(t, len(strings.TrimSpace(sig.Name)), 0) assert.NotEmpty(t, strings.TrimSpace(sig.Name))
} }
} }
@ -216,7 +216,7 @@ func TestDisplayName(t *testing.T) {
if len(strings.TrimSpace(user.FullName)) == 0 { if len(strings.TrimSpace(user.FullName)) == 0 {
assert.Equal(t, user.Name, displayName) assert.Equal(t, user.Name, displayName)
} }
assert.NotEqual(t, len(strings.TrimSpace(displayName)), 0) assert.NotEmpty(t, strings.TrimSpace(displayName))
} }
} }
@ -322,15 +322,15 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, results, 1) assert.Len(t, results, 1)
if len(results) > 1 { if len(results) > 1 {
assert.Equal(t, results[0].ID, 1) assert.Equal(t, 1, results[0].ID)
} }
results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true) results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, results, 2) assert.Len(t, results, 2)
if len(results) > 2 { if len(results) > 2 {
assert.Equal(t, results[0].ID, 1) assert.Equal(t, 1, results[0].ID)
assert.Equal(t, results[1].ID, 4) assert.Equal(t, 4, results[1].ID)
} }
} }
@ -499,7 +499,7 @@ func Test_ValidateUser(t *testing.T) {
{ID: 2, Visibility: structs.VisibleTypePrivate}: true, {ID: 2, Visibility: structs.VisibleTypePrivate}: true,
} }
for kase, expected := range kases { for kase, expected := range kases {
assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), fmt.Sprintf("case: %+v", kase)) assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), "case: %+v", kase)
} }
} }
@ -570,11 +570,11 @@ func TestDisabledUserFeatures(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0) assert.Empty(t, setting.Admin.UserDisabledFeatures.Values())
// no features should be disabled with a plain login type // no features should be disabled with a plain login type
assert.LessOrEqual(t, user.LoginType, auth.Plain) assert.LessOrEqual(t, user.LoginType, auth.Plain)
assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0) assert.Empty(t, user_model.DisabledFeaturesWithLoginType(user).Values())
for _, f := range testValues.Values() { for _, f := range testValues.Values() {
assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f))
} }
@ -600,5 +600,5 @@ func TestGetInactiveUsers(t *testing.T) {
interval := time.Now().Unix() - 1730468968 + 3600*24 interval := time.Now().Unix() - 1730468968 + 3600*24
users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second))) users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second)))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 0) assert.Empty(t, users)
} }

View File

@ -43,7 +43,7 @@ func TestWebhook_History(t *testing.T) {
webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
tasks, err = webhook.History(db.DefaultContext, 0) tasks, err = webhook.History(db.DefaultContext, 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, tasks, 0) assert.Empty(t, tasks)
} }
func TestWebhook_UpdateEvent(t *testing.T) { func TestWebhook_UpdateEvent(t *testing.T) {
@ -206,7 +206,7 @@ func TestHookTasks(t *testing.T) {
hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, hookTasks, 0) assert.Empty(t, hookTasks)
} }
func TestCreateHookTask(t *testing.T) { func TestCreateHookTask(t *testing.T) {

View File

@ -8,7 +8,6 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"regexp"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -28,9 +27,9 @@ func TestActivityPubSignedPost(t *testing.T) {
expected := "BODY" expected := "BODY"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Regexp(t, regexp.MustCompile("^"+setting.Federation.DigestAlgorithm), r.Header.Get("Digest")) assert.Regexp(t, "^"+setting.Federation.DigestAlgorithm, r.Header.Get("Digest"))
assert.Contains(t, r.Header.Get("Signature"), pubID) assert.Contains(t, r.Header.Get("Signature"), pubID)
assert.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType) assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type"))
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, string(body)) assert.Equal(t, expected, string(body))

View File

@ -58,7 +58,7 @@ func TestLayered(t *testing.T) {
assertRead := func(expected string, expectedErr error, elems ...string) { assertRead := func(expected string, expectedErr error, elems ...string) {
bs, err := assets.ReadFile(elems...) bs, err := assets.ReadFile(elems...)
if err != nil { if err != nil {
assert.ErrorAs(t, err, &expectedErr) assert.ErrorIs(t, err, expectedErr)
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, string(bs)) assert.Equal(t, expected, string(bs))

View File

@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
result, err := Auth("gitea", "user1", "false-pwd") result, err := Auth("gitea", "user1", "false-pwd")
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, "Authentication failure") assert.EqualError(t, err, "Authentication failure")
assert.Len(t, result, 0) assert.Len(t, result)
} }

View File

@ -18,7 +18,7 @@ func TestDummyHasher(t *testing.T) {
password, salt := "password", "ZogKvWdyEx" password, salt := "password", "ZogKvWdyEx"
hash, err := dummy.Hash(password, salt) hash, err := dummy.Hash(password, salt)
assert.Nil(t, err) assert.NoError(t, err)
assert.Equal(t, hash, salt+":"+password) assert.Equal(t, hash, salt+":"+password)
assert.True(t, dummy.VerifyPassword(password, hash, salt)) assert.True(t, dummy.VerifyPassword(password, hash, salt))

View File

@ -99,10 +99,10 @@ func IsComplexEnough(pwd string) bool {
func Generate(n int) (string, error) { func Generate(n int) (string, error) {
NewComplexity() NewComplexity()
buffer := make([]byte, n) buffer := make([]byte, n)
max := big.NewInt(int64(len(validChars))) maxInt := big.NewInt(int64(len(validChars)))
for { for {
for j := 0; j < n; j++ { for j := 0; j < n; j++ {
rnd, err := rand.Int(rand.Reader, max) rnd, err := rand.Int(rand.Reader, maxInt)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -6,7 +6,6 @@ package base
import ( import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"os"
"testing" "testing"
"time" "time"
@ -157,7 +156,7 @@ func TestStringsToInt64s(t *testing.T) {
testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256}) testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256})
ints, err := StringsToInt64s([]string{"-1", "a"}) ints, err := StringsToInt64s([]string{"-1", "a"})
assert.Len(t, ints, 0) assert.Empty(t, ints)
assert.Error(t, err) assert.Error(t, err)
} }
@ -172,9 +171,9 @@ func TestInt64sToStrings(t *testing.T) {
// TODO: Test EntryIcon // TODO: Test EntryIcon
func TestSetupGiteaRoot(t *testing.T) { func TestSetupGiteaRoot(t *testing.T) {
_ = os.Setenv("GITEA_ROOT", "test") t.Setenv("GITEA_ROOT", "test")
assert.Equal(t, "test", SetupGiteaRoot()) assert.Equal(t, "test", SetupGiteaRoot())
_ = os.Setenv("GITEA_ROOT", "") t.Setenv("GITEA_ROOT", "")
assert.NotEqual(t, "test", SetupGiteaRoot()) assert.NotEqual(t, "test", SetupGiteaRoot())
} }

View File

@ -25,7 +25,7 @@ func TestPrepareFileNameAndType(t *testing.T) {
assert.Equal(t, assert.Equal(t,
fmt.Sprintf("outFile=%s, outType=%s", expFile, expType), fmt.Sprintf("outFile=%s, outType=%s", expFile, expType),
fmt.Sprintf("outFile=%s, outType=%s", outFile, outType), fmt.Sprintf("outFile=%s, outType=%s", outFile, outType),
fmt.Sprintf("argFile=%s, argType=%s", argFile, argType), "argFile=%s, argType=%s", argFile, argType,
) )
} }

View File

@ -474,3 +474,17 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting
} }
return c.repo.GetDefaultPublicGPGKey(forceUpdate) return c.repo.GetDefaultPublicGPGKey(forceUpdate)
} }
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
minLen := util.OptionalArg(minLength, objFmt.FullLength())
if len(s) < minLen || len(s) > objFmt.FullLength() {
return false
}
for _, c := range s {
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
if !isHex {
return false
}
}
return true
}

View File

@ -146,7 +146,7 @@ func TestHasPreviousCommitSha256(t *testing.T) {
parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c") parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c")
notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236") notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236")
assert.Equal(t, objectFormat, parentSHA.Type()) assert.Equal(t, objectFormat, parentSHA.Type())
assert.Equal(t, objectFormat.Name(), "sha256") assert.Equal(t, "sha256", objectFormat.Name())
haz, err := commit.HasPreviousCommit(parentSHA) haz, err := commit.HasPreviousCommit(parentSHA)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -343,9 +343,9 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
}, },
} }
assert.Equal(t, commitFileStatus.Added, expected.Added) assert.Equal(t, expected.Added, commitFileStatus.Added)
assert.Equal(t, commitFileStatus.Removed, expected.Removed) assert.Equal(t, expected.Removed, commitFileStatus.Removed)
assert.Equal(t, commitFileStatus.Modified, expected.Modified) assert.Equal(t, expected.Modified, commitFileStatus.Modified)
} }
func Test_GetCommitBranchStart(t *testing.T) { func Test_GetCommitBranchStart(t *testing.T) {

View File

@ -73,9 +73,9 @@ func TestGrepSearch(t *testing.T) {
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{}) res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, res, 0) assert.Empty(t, res)
res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{}) res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{})
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, res, 0) assert.Empty(t, res)
} }

View File

@ -100,5 +100,5 @@ func TestParseTreeEntriesInvalid(t *testing.T) {
// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315 // there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af")) entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
assert.Error(t, err) assert.Error(t, err)
assert.Len(t, entries, 0) assert.Empty(t, entries)
} }

View File

@ -142,7 +142,6 @@ func (ref RefName) RemoteName() string {
// ShortName returns the short name of the reference name // ShortName returns the short name of the reference name
func (ref RefName) ShortName() string { func (ref RefName) ShortName() string {
refName := string(ref)
if ref.IsBranch() { if ref.IsBranch() {
return ref.BranchName() return ref.BranchName()
} }
@ -158,8 +157,7 @@ func (ref RefName) ShortName() string {
if ref.IsFor() { if ref.IsFor() {
return ref.ForBranchName() return ref.ForBranchName()
} }
return string(ref) // usually it is a commit ID
return refName
} }
// RefGroup returns the group type of the reference // RefGroup returns the group type of the reference

View File

@ -223,7 +223,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
if err != nil { if err != nil {
if strings.Contains(stderr, "non-fast-forward") { if strings.Contains(stderr, "non-fast-forward") {
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err} return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
} else if strings.Contains(stderr, "! [remote rejected]") { } else if strings.Contains(stderr, "! [remote rejected]") || strings.Contains(stderr, "! [rejected]") {
err := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err} err := &ErrPushRejected{StdOut: stdout, StdErr: stderr, Err: err}
err.GenerateMessage() err.GenerateMessage()
return err return err

View File

@ -34,7 +34,7 @@ func TestRepository_GetBranches(t *testing.T) {
branches, countAll, err = bareRepo1.GetBranchNames(5, 1) branches, countAll, err = bareRepo1.GetBranchNames(5, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 0) assert.Empty(t, branches)
assert.EqualValues(t, 3, countAll) assert.EqualValues(t, 3, countAll)
assert.ElementsMatch(t, []string{}, branches) assert.ElementsMatch(t, []string{}, branches)
} }
@ -66,7 +66,7 @@ func TestGetRefsBySha(t *testing.T) {
// do not exist // do not exist
branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, branches, 0) assert.Empty(t, branches)
// refs/pull/1/head // refs/pull/1/head
branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix) branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix)

View File

@ -465,15 +465,15 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
refs := strings.Split(stdout, "\n") refs := strings.Split(stdout, "\n")
var max int var maxNum int
if len(refs) > limit { if len(refs) > limit {
max = limit maxNum = limit
} else { } else {
max = len(refs) - 1 maxNum = len(refs) - 1
} }
branches := make([]string, max) branches := make([]string, maxNum)
for i, ref := range refs[:max] { for i, ref := range refs[:maxNum] {
parts := strings.Fields(ref) parts := strings.Fields(ref)
branches[i] = parts[len(parts)-1] branches[i] = parts[len(parts)-1]

View File

@ -246,18 +246,40 @@ func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, bi
// GetDiff generates and returns patch data between given revisions, optimized for human readability // GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(base, head string, w io.Writer) error { func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head).Run(&RunOpts{ stderr := new(bytes.Buffer)
Dir: repo.Path, err := NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base + "..." + head).
Stdout: w, Run(&RunOpts{
}) Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return err
} }
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs. // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base, head).Run(&RunOpts{ stderr := new(bytes.Buffer)
Dir: repo.Path, err := NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base + "..." + head).
Stdout: w, Run(&RunOpts{
}) Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base, head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return err
} }
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`

View File

@ -72,7 +72,7 @@ func TestReadPatch(t *testing.T) {
assert.Empty(t, noFile) assert.Empty(t, noFile)
assert.Empty(t, noCommit) assert.Empty(t, noCommit)
assert.Len(t, oldCommit, 40) assert.Len(t, oldCommit, 40)
assert.True(t, oldCommit == "6e8e2a6f9efd71dbe6917816343ed8415ad696c3") assert.Equal(t, "6e8e2a6f9efd71dbe6917816343ed8415ad696c3", oldCommit)
} }
func TestReadWritePullHead(t *testing.T) { func TestReadWritePullHead(t *testing.T) {
@ -113,7 +113,7 @@ func TestReadWritePullHead(t *testing.T) {
} }
assert.Len(t, headContents, 40) assert.Len(t, headContents, 40)
assert.True(t, headContents == newCommit) assert.Equal(t, headContents, newCommit)
// Remove file after the test // Remove file after the test
err = repo.RemoveReference(PullPrefix + "1/head") err = repo.RemoveReference(PullPrefix + "1/head")

View File

@ -61,3 +61,31 @@ func parseTags(refs []string) []string {
} }
return results return results
} }
// UnstableGuessRefByShortName does the best guess to see whether a "short name" provided by user is a branch, tag or commit.
// It could guess wrongly if the input is already ambiguous. For example:
// * "refs/heads/the-name" vs "refs/heads/refs/heads/the-name"
// * "refs/tags/1234567890" vs commit "1234567890"
// In most cases, it SHOULD AVOID using this function, unless there is an irresistible reason (eg: make API friendly to end users)
// If the function is used, the caller SHOULD CHECK the ref type carefully.
func (repo *Repository) UnstableGuessRefByShortName(shortName string) RefName {
if repo.IsBranchExist(shortName) {
return RefNameFromBranch(shortName)
}
if repo.IsTagExist(shortName) {
return RefNameFromTag(shortName)
}
if strings.HasPrefix(shortName, "refs/") {
if repo.IsReferenceExist(shortName) {
return RefName(shortName)
}
}
commit, err := repo.GetCommit(shortName)
if err == nil {
commitIDString := commit.ID.String()
if strings.HasPrefix(commitIDString, shortName) {
return RefName(commitIDString)
}
}
return ""
}

View File

@ -64,7 +64,7 @@ func TestLockAndDo(t *testing.T) {
} }
func testLockAndDo(t *testing.T) { func testLockAndDo(t *testing.T) {
const concurrency = 1000 const concurrency = 50
ctx := context.Background() ctx := context.Background()
count := 0 count := 0

View File

@ -218,13 +218,13 @@ func (g *Manager) ServerDone() {
g.runningServerWaitGroup.Done() g.runningServerWaitGroup.Done()
} }
func (g *Manager) setStateTransition(old, new state) bool { func (g *Manager) setStateTransition(oldState, newState state) bool {
g.lock.Lock() g.lock.Lock()
if g.state != old { if g.state != oldState {
g.lock.Unlock() g.lock.Unlock()
return false return false
} }
g.state = new g.state = newState
g.lock.Unlock() g.lock.Unlock()
return true return true
} }

View File

@ -35,18 +35,18 @@ func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery {
return q return q
} }
func NumericRangeInclusiveQuery(min, max optional.Option[int64], field string) *query.NumericRangeQuery { func NumericRangeInclusiveQuery(minOption, maxOption optional.Option[int64], field string) *query.NumericRangeQuery {
var minF, maxF *float64 var minF, maxF *float64
var minI, maxI *bool var minI, maxI *bool
if min.Has() { if minOption.Has() {
minF = new(float64) minF = new(float64)
*minF = float64(min.Value()) *minF = float64(minOption.Value())
minI = new(bool) minI = new(bool)
*minI = true *minI = true
} }
if max.Has() { if maxOption.Has() {
maxF = new(float64) maxF = new(float64)
*maxF = float64(max.Value()) *maxF = float64(maxOption.Value())
maxI = new(bool) maxI = new(bool)
*maxI = true *maxI = true
} }

View File

@ -10,12 +10,12 @@ import (
) )
// ParsePaginator parses a db.Paginator into a skip and limit // ParsePaginator parses a db.Paginator into a skip and limit
func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) { func ParsePaginator(paginator *db.ListOptions, maxNums ...int) (int, int) {
// Use a very large number to indicate no limit // Use a very large number to indicate no limit
unlimited := math.MaxInt32 unlimited := math.MaxInt32
if len(max) > 0 { if len(maxNums) > 0 {
// Some indexer engines have a limit on the page size, respect that // Some indexer engines have a limit on the page size, respect that
unlimited = max[0] unlimited = maxNums[0]
} }
if paginator == nil || paginator.IsListAll() { if paginator == nil || paginator.IsListAll() {

View File

@ -23,7 +23,7 @@ import (
const ( const (
issueIndexerAnalyzer = "issueIndexer" issueIndexerAnalyzer = "issueIndexer"
issueIndexerDocType = "issueIndexerDocType" issueIndexerDocType = "issueIndexerDocType"
issueIndexerLatestVersion = 4 issueIndexerLatestVersion = 5
) )
const unicodeNormalizeName = "unicodeNormalize" const unicodeNormalizeName = "unicodeNormalize"
@ -75,6 +75,7 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) {
docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping) docMapping.AddFieldMappingsAt("is_pull", boolFieldMapping)
docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping) docMapping.AddFieldMappingsAt("is_closed", boolFieldMapping)
docMapping.AddFieldMappingsAt("is_archived", boolFieldMapping)
docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping)
docMapping.AddFieldMappingsAt("no_label", boolFieldMapping) docMapping.AddFieldMappingsAt("no_label", boolFieldMapping)
docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping) docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping)
@ -185,6 +186,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed")) queries = append(queries, inner_bleve.BoolFieldQuery(options.IsClosed.Value(), "is_closed"))
} }
if options.IsArchived.Has() {
queries = append(queries, inner_bleve.BoolFieldQuery(options.IsArchived.Value(), "is_archived"))
}
if options.NoLabelOnly { if options.NoLabelOnly {
queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label")) queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_label"))

View File

@ -54,8 +54,8 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
RepoIDs: options.RepoIDs, RepoIDs: options.RepoIDs,
AllPublic: options.AllPublic, AllPublic: options.AllPublic,
RepoCond: nil, RepoCond: nil,
AssigneeID: convertID(options.AssigneeID), AssigneeID: optional.Some(convertID(options.AssigneeID)),
PosterID: convertID(options.PosterID), PosterID: options.PosterID,
MentionedID: convertID(options.MentionID), MentionedID: convertID(options.MentionID),
ReviewRequestedID: convertID(options.ReviewRequestedID), ReviewRequestedID: convertID(options.ReviewRequestedID),
ReviewedID: convertID(options.ReviewedID), ReviewedID: convertID(options.ReviewedID),
@ -72,7 +72,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
UpdatedAfterUnix: options.UpdatedAfterUnix.Value(), UpdatedAfterUnix: options.UpdatedAfterUnix.Value(),
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
PriorityRepoID: 0, PriorityRepoID: 0,
IsArchived: optional.None[bool](), IsArchived: options.IsArchived,
Org: nil, Org: nil,
Team: nil, Team: nil,
User: nil, User: nil,

View File

@ -11,11 +11,12 @@ import (
func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions { func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions {
searchOpt := &SearchOptions{ searchOpt := &SearchOptions{
Keyword: keyword, Keyword: keyword,
RepoIDs: opts.RepoIDs, RepoIDs: opts.RepoIDs,
AllPublic: opts.AllPublic, AllPublic: opts.AllPublic,
IsPull: opts.IsPull, IsPull: opts.IsPull,
IsClosed: opts.IsClosed, IsClosed: opts.IsClosed,
IsArchived: opts.IsArchived,
} }
if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 { if len(opts.LabelIDs) == 1 && opts.LabelIDs[0] == 0 {
@ -40,14 +41,14 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
if opts.ProjectID > 0 { if opts.ProjectID > 0 {
searchOpt.ProjectID = optional.Some(opts.ProjectID) searchOpt.ProjectID = optional.Some(opts.ProjectID)
} else if opts.ProjectID == -1 { // FIXME: this is inconsistent from other places } else if opts.ProjectID == db.NoConditionID { // FIXME: this is inconsistent from other places
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0) searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
} }
if opts.AssigneeID > 0 { if opts.AssigneeID.Value() == db.NoConditionID {
searchOpt.AssigneeID = optional.Some(opts.AssigneeID) searchOpt.AssigneeID = optional.Some[int64](0) // FIXME: this is inconsistent from other places, 0 means "no assignee"
} else if opts.AssigneeID == -1 { // FIXME: this is inconsistent from other places } else if opts.AssigneeID.Value() != 0 {
searchOpt.AssigneeID = optional.Some[int64](0) searchOpt.AssigneeID = opts.AssigneeID
} }
// See the comment of issues_model.SearchOptions for the reason why we need to convert // See the comment of issues_model.SearchOptions for the reason why we need to convert
@ -62,7 +63,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
} }
searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID) searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
searchOpt.PosterID = convertID(opts.PosterID) searchOpt.PosterID = opts.PosterID
searchOpt.MentionID = convertID(opts.MentionedID) searchOpt.MentionID = convertID(opts.MentionedID)
searchOpt.ReviewedID = convertID(opts.ReviewedID) searchOpt.ReviewedID = convertID(opts.ReviewedID)
searchOpt.ReviewRequestedID = convertID(opts.ReviewRequestedID) searchOpt.ReviewRequestedID = convertID(opts.ReviewRequestedID)

View File

@ -18,7 +18,7 @@ import (
) )
const ( const (
issueIndexerLatestVersion = 1 issueIndexerLatestVersion = 2
// multi-match-types, currently only 2 types are used // multi-match-types, currently only 2 types are used
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
esMultiMatchTypeBestFields = "best_fields" esMultiMatchTypeBestFields = "best_fields"
@ -58,6 +58,7 @@ const (
"is_pull": { "type": "boolean", "index": true }, "is_pull": { "type": "boolean", "index": true },
"is_closed": { "type": "boolean", "index": true }, "is_closed": { "type": "boolean", "index": true },
"is_archived": { "type": "boolean", "index": true },
"label_ids": { "type": "integer", "index": true }, "label_ids": { "type": "integer", "index": true },
"no_label": { "type": "boolean", "index": true }, "no_label": { "type": "boolean", "index": true },
"milestone_id": { "type": "integer", "index": true }, "milestone_id": { "type": "integer", "index": true },
@ -168,6 +169,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value())) query.Must(elastic.NewTermQuery("is_closed", options.IsClosed.Value()))
} }
if options.IsArchived.Has() {
query.Must(elastic.NewTermQuery("is_archived", options.IsArchived.Value()))
}
if options.NoLabelOnly { if options.NoLabelOnly {
query.Must(elastic.NewTermQuery("no_label", true)) query.Must(elastic.NewTermQuery("no_label", true))

View File

@ -37,6 +37,7 @@ func TestDBSearchIssues(t *testing.T) {
t.Run("search issues by ID", searchIssueByID) t.Run("search issues by ID", searchIssueByID)
t.Run("search issues is pr", searchIssueIsPull) t.Run("search issues is pr", searchIssueIsPull)
t.Run("search issues is closed", searchIssueIsClosed) t.Run("search issues is closed", searchIssueIsClosed)
t.Run("search issues is archived", searchIssueIsArchived)
t.Run("search issues by milestone", searchIssueByMilestoneID) t.Run("search issues by milestone", searchIssueByMilestoneID)
t.Run("search issues by label", searchIssueByLabelID) t.Run("search issues by label", searchIssueByLabelID)
t.Run("search issues by time", searchIssueByTime) t.Run("search issues by time", searchIssueByTime)
@ -191,7 +192,7 @@ func searchIssueByID(t *testing.T) {
}, },
{ {
// NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it will set AssigneeID to 0 when it is passed as -1. // NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it will set AssigneeID to 0 when it is passed as -1.
opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: -1}), opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: optional.Some(db.NoConditionID)}),
expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2}, expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2},
}, },
{ {
@ -298,6 +299,33 @@ func searchIssueIsClosed(t *testing.T) {
} }
} }
func searchIssueIsArchived(t *testing.T) {
tests := []struct {
opts SearchOptions
expectedIDs []int64
}{
{
SearchOptions{
IsArchived: optional.Some(false),
},
[]int64{22, 21, 17, 16, 15, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
},
{
SearchOptions{
IsArchived: optional.Some(true),
},
[]int64{14},
},
}
for _, test := range tests {
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, test.expectedIDs, issueIDs)
}
}
func searchIssueByMilestoneID(t *testing.T) { func searchIssueByMilestoneID(t *testing.T) {
tests := []struct { tests := []struct {
opts SearchOptions opts SearchOptions

View File

@ -25,6 +25,7 @@ type IndexerData struct {
// Fields used for filtering // Fields used for filtering
IsPull bool `json:"is_pull"` IsPull bool `json:"is_pull"`
IsClosed bool `json:"is_closed"` IsClosed bool `json:"is_closed"`
IsArchived bool `json:"is_archived"`
LabelIDs []int64 `json:"label_ids"` LabelIDs []int64 `json:"label_ids"`
NoLabel bool `json:"no_label"` // True if LabelIDs is empty NoLabel bool `json:"no_label"` // True if LabelIDs is empty
MilestoneID int64 `json:"milestone_id"` MilestoneID int64 `json:"milestone_id"`
@ -81,8 +82,9 @@ type SearchOptions struct {
RepoIDs []int64 // repository IDs which the issues belong to RepoIDs []int64 // repository IDs which the issues belong to
AllPublic bool // if include all public repositories AllPublic bool // if include all public repositories
IsPull optional.Option[bool] // if the issues is a pull request IsPull optional.Option[bool] // if the issues is a pull request
IsClosed optional.Option[bool] // if the issues is closed IsClosed optional.Option[bool] // if the issues is closed
IsArchived optional.Option[bool] // if the repo is archived
IncludedLabelIDs []int64 // labels the issues have IncludedLabelIDs []int64 // labels the issues have
ExcludedLabelIDs []int64 // labels the issues don't have ExcludedLabelIDs []int64 // labels the issues don't have

View File

@ -113,7 +113,7 @@ var cases = []*testIndexerCase{
}, },
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
assert.Equal(t, len(data), int(result.Total)) assert.Equal(t, len(data), int(result.Total))
}, },
}, },
@ -176,7 +176,7 @@ var cases = []*testIndexerCase{
IsPull: optional.Some(false), IsPull: optional.Some(false),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.False(t, data[v.ID].IsPull) assert.False(t, data[v.ID].IsPull)
} }
@ -192,7 +192,7 @@ var cases = []*testIndexerCase{
IsPull: optional.Some(true), IsPull: optional.Some(true),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.True(t, data[v.ID].IsPull) assert.True(t, data[v.ID].IsPull)
} }
@ -208,7 +208,7 @@ var cases = []*testIndexerCase{
IsClosed: optional.Some(false), IsClosed: optional.Some(false),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.False(t, data[v.ID].IsClosed) assert.False(t, data[v.ID].IsClosed)
} }
@ -224,7 +224,7 @@ var cases = []*testIndexerCase{
IsClosed: optional.Some(true), IsClosed: optional.Some(true),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.True(t, data[v.ID].IsClosed) assert.True(t, data[v.ID].IsClosed)
} }
@ -274,7 +274,7 @@ var cases = []*testIndexerCase{
MilestoneIDs: []int64{1, 2, 6}, MilestoneIDs: []int64{1, 2, 6},
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID) assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID)
} }
@ -292,7 +292,7 @@ var cases = []*testIndexerCase{
MilestoneIDs: []int64{0}, MilestoneIDs: []int64{0},
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].MilestoneID) assert.Equal(t, int64(0), data[v.ID].MilestoneID)
} }
@ -310,7 +310,7 @@ var cases = []*testIndexerCase{
ProjectID: optional.Some(int64(1)), ProjectID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].ProjectID) assert.Equal(t, int64(1), data[v.ID].ProjectID)
} }
@ -328,7 +328,7 @@ var cases = []*testIndexerCase{
ProjectID: optional.Some(int64(0)), ProjectID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].ProjectID) assert.Equal(t, int64(0), data[v.ID].ProjectID)
} }
@ -346,7 +346,7 @@ var cases = []*testIndexerCase{
ProjectColumnID: optional.Some(int64(1)), ProjectColumnID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].ProjectColumnID) assert.Equal(t, int64(1), data[v.ID].ProjectColumnID)
} }
@ -364,7 +364,7 @@ var cases = []*testIndexerCase{
ProjectColumnID: optional.Some(int64(0)), ProjectColumnID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].ProjectColumnID) assert.Equal(t, int64(0), data[v.ID].ProjectColumnID)
} }
@ -382,7 +382,7 @@ var cases = []*testIndexerCase{
PosterID: optional.Some(int64(1)), PosterID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].PosterID) assert.Equal(t, int64(1), data[v.ID].PosterID)
} }
@ -400,7 +400,7 @@ var cases = []*testIndexerCase{
AssigneeID: optional.Some(int64(1)), AssigneeID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].AssigneeID) assert.Equal(t, int64(1), data[v.ID].AssigneeID)
} }
@ -418,7 +418,7 @@ var cases = []*testIndexerCase{
AssigneeID: optional.Some(int64(0)), AssigneeID: optional.Some(int64(0)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].AssigneeID) assert.Equal(t, int64(0), data[v.ID].AssigneeID)
} }
@ -436,7 +436,7 @@ var cases = []*testIndexerCase{
MentionID: optional.Some(int64(1)), MentionID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].MentionIDs, int64(1)) assert.Contains(t, data[v.ID].MentionIDs, int64(1))
} }
@ -454,7 +454,7 @@ var cases = []*testIndexerCase{
ReviewedID: optional.Some(int64(1)), ReviewedID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].ReviewedIDs, int64(1)) assert.Contains(t, data[v.ID].ReviewedIDs, int64(1))
} }
@ -472,7 +472,7 @@ var cases = []*testIndexerCase{
ReviewRequestedID: optional.Some(int64(1)), ReviewRequestedID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1)) assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1))
} }
@ -490,7 +490,7 @@ var cases = []*testIndexerCase{
SubscriberID: optional.Some(int64(1)), SubscriberID: optional.Some(int64(1)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.Contains(t, data[v.ID].SubscriberIDs, int64(1)) assert.Contains(t, data[v.ID].SubscriberIDs, int64(1))
} }
@ -509,7 +509,7 @@ var cases = []*testIndexerCase{
UpdatedBeforeUnix: optional.Some(int64(30)), UpdatedBeforeUnix: optional.Some(int64(30)),
}, },
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits)) assert.Len(t, result.Hits, 5)
for _, v := range result.Hits { for _, v := range result.Hits {
assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20)) assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20))
assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30)) assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30))

View File

@ -18,7 +18,7 @@ import (
) )
const ( const (
issueIndexerLatestVersion = 3 issueIndexerLatestVersion = 4
// TODO: make this configurable if necessary // TODO: make this configurable if necessary
maxTotalHits = 10000 maxTotalHits = 10000
@ -61,6 +61,7 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer {
"is_public", "is_public",
"is_pull", "is_pull",
"is_closed", "is_closed",
"is_archived",
"label_ids", "label_ids",
"no_label", "no_label",
"milestone_id", "milestone_id",
@ -145,6 +146,9 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
if options.IsClosed.Has() { if options.IsClosed.Has() {
query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value())) query.And(inner_meilisearch.NewFilterEq("is_closed", options.IsClosed.Value()))
} }
if options.IsArchived.Has() {
query.And(inner_meilisearch.NewFilterEq("is_archived", options.IsArchived.Value()))
}
if options.NoLabelOnly { if options.NoLabelOnly {
query.And(inner_meilisearch.NewFilterEq("no_label", true)) query.And(inner_meilisearch.NewFilterEq("no_label", true))

View File

@ -101,6 +101,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
Comments: comments, Comments: comments,
IsPull: issue.IsPull, IsPull: issue.IsPull,
IsClosed: issue.IsClosed, IsClosed: issue.IsClosed,
IsArchived: issue.Repo.IsArchived,
LabelIDs: labels, LabelIDs: labels,
NoLabel: len(labels) == 0, NoLabel: len(labels) == 0,
MilestoneID: issue.MilestoneID, MilestoneID: issue.MilestoneID,

View File

@ -72,7 +72,10 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
url := fmt.Sprintf("%s/objects/batch", c.endpoint) url := fmt.Sprintf("%s/objects/batch", c.endpoint)
request := &BatchRequest{operation, c.transferNames(), nil, objects} // `ref` is an "optional object describing the server ref that the objects belong to"
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
payload := new(bytes.Buffer) payload := new(bytes.Buffer)
err := json.NewEncoder(payload).Encode(request) err := json.NewEncoder(payload).Encode(request)
if err != nil { if err != nil {
@ -236,6 +239,7 @@ func createRequest(ctx context.Context, method, url string, headers map[string]s
req.Header.Set(key, value) req.Header.Set(key, value)
} }
req.Header.Set("Accept", AcceptHeader) req.Header.Set("Accept", AcceptHeader)
req.Header.Set("User-Agent", UserAgentHeader)
return req, nil return req, nil
} }

View File

@ -14,8 +14,12 @@ import (
const ( const (
// MediaType contains the media type for LFS server requests // MediaType contains the media type for LFS server requests
MediaType = "application/vnd.git-lfs+json" MediaType = "application/vnd.git-lfs+json"
// Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served // AcceptHeader Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
// UserAgentHeader Add User-Agent for gitea's self-implemented lfs client,
// and the version is consistent with the latest version of git lfs can be avoided incompatibilities.
// Some lfs servers will check this
UserAgentHeader = "git-lfs/3.6.0 (Gitea)"
) )
// BatchRequest contains multiple requests processed in one batch operation. // BatchRequest contains multiple requests processed in one batch operation.

View File

@ -96,7 +96,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
_, err := a.Download(context.Background(), c.link) _, err := a.Download(context.Background(), c.link)
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }
@ -129,7 +129,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy")) err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy"))
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }
@ -162,7 +162,7 @@ func TestBasicTransferAdapter(t *testing.T) {
for n, c := range cases { for n, c := range cases {
err := a.Verify(context.Background(), c.link, p) err := a.Verify(context.Background(), c.link, p)
if len(c.expectederror) > 0 { if len(c.expectederror) > 0 {
assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror)
} else { } else {
assert.NoError(t, err, "case %d", n) assert.NoError(t, err, "case %d", n)
} }

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