mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-03 07:30:18 +00:00
Compare commits
176 commits
Author | SHA1 | Date | |
---|---|---|---|
|
35c5192b25 | ||
|
e2905761c3 | ||
|
50ecae7357 | ||
|
dceadd2ebe | ||
|
3cecfa3df4 | ||
|
d9e6657879 | ||
|
151b786837 | ||
|
58b1ee5ebd | ||
|
1825c316a6 | ||
|
d1dd3fa49c | ||
|
eaeb4d1b96 | ||
|
688085c15e | ||
|
8d60c7d568 | ||
|
be302f3025 | ||
|
a3529d662f | ||
|
a4f977e00b | ||
|
b83482b4ba | ||
|
9ecaeda66e | ||
|
7fbcc58062 | ||
|
05f32114d1 | ||
|
5c79c2b431 | ||
|
3813b27862 | ||
|
042e9fcd81 | ||
|
e8e43a7ee4 | ||
|
a9d1e4311e | ||
|
ed0fc0ec46 | ||
|
fa307167f9 | ||
|
3f44844244 | ||
|
52925e9c7c | ||
|
188e515efc | ||
|
cdd057c7a3 | ||
|
6d0d4640f6 | ||
|
6ca70c5bf2 | ||
|
95dfd945bc | ||
|
568ff1015b | ||
|
4b6ef9265b | ||
|
b1ad8ccb73 | ||
|
758f84f33e | ||
|
3fcf865a4b | ||
|
c1c11aaf60 | ||
|
abc92df701 | ||
|
1dc8a66074 | ||
|
bbe98a3254 | ||
|
5ca4c6d066 | ||
|
75e0bdcec5 | ||
|
a918757105 | ||
|
c07416b3d0 | ||
|
875579cc65 | ||
|
83cf348e07 | ||
|
7cb67cfd7f | ||
|
1c1c2d36e8 | ||
|
082600a50e | ||
|
5136c879c2 | ||
|
ca414a7ccf | ||
|
331c32f9b6 | ||
|
298d05df3b | ||
|
85a8176708 | ||
|
0b5012c6fc | ||
|
0328f31fdc | ||
|
33fa93a952 | ||
|
68e405cf0b | ||
|
b6280f4d21 | ||
|
1987c86f3c | ||
|
c6176ee59f | ||
|
e8c776c793 | ||
|
cc64d4d2b2 | ||
|
f0d55e4819 | ||
|
fb14ca30eb | ||
|
a672f066f7 | ||
|
c6189cfcb9 | ||
|
42b2541cb5 | ||
|
9c93c6249c | ||
|
b615a59db8 | ||
|
e6bacf1fed | ||
|
d6ae2b3c4e | ||
|
447b3e2475 | ||
|
7ecb1d63bb | ||
|
b0981f6509 | ||
|
7f706bd171 | ||
|
803b0c9ab4 | ||
|
b903e2b753 | ||
|
0e70f73055 | ||
|
e0b7938d74 | ||
|
02b947a15f | ||
|
7b7318255e | ||
|
1171b24d52 | ||
|
7d56ee3c0f | ||
|
2e90b80d64 | ||
|
564fef1e20 | ||
|
14ed06d675 | ||
|
cc51f70d86 | ||
|
7d5ac68bc4 | ||
|
2648962ae0 | ||
|
e3390e2441 | ||
|
27a4c67992 | ||
|
55cb356b84 | ||
|
8a259e54c5 | ||
|
8663a351d1 | ||
|
3d3123d553 | ||
|
a798a1eb17 | ||
|
d17bfad940 | ||
|
8446caa813 | ||
|
ab33b7849f | ||
|
a58498cc43 | ||
|
8eac16de21 | ||
|
bd3787c774 | ||
|
f0e74da719 | ||
|
835411b978 | ||
|
3cdf9ed202 | ||
|
0c9dcda10d | ||
|
cb52eb639e | ||
|
5b7e54f72f | ||
|
042777abd7 | ||
|
8393ff78fc | ||
|
c1337e7316 | ||
|
70eaa99ac0 | ||
|
f806bbb815 | ||
|
ebe8e63dfd | ||
|
bc455883fc | ||
|
14dc00ae01 | ||
|
94c5a30c8b | ||
|
b99473f4ec | ||
|
2200c41ffd | ||
|
45475250bd | ||
|
df5513978a | ||
|
08d697ae70 | ||
|
5f1af1f37d | ||
|
2f91a461f7 | ||
|
084bec89ed | ||
|
271e8748a2 | ||
|
ec3f5f9992 | ||
|
d4c2db39bf | ||
|
d410e2acce | ||
|
216c8eada3 | ||
|
d5563be0ee | ||
|
ad5a8d043c | ||
|
cfe6779d4e | ||
|
b22d7fd8cd | ||
|
e2e326f154 | ||
|
2252a7bf84 | ||
|
bb7150c30d | ||
|
60fa2a5960 | ||
|
054602977a | ||
|
471b411873 | ||
|
a82e6301f7 | ||
|
1f9a9fab5f | ||
|
7ad2d039fd | ||
|
ab2ef1ae49 | ||
|
7db434bfa9 | ||
|
41f8ef8af5 | ||
|
6d83f5eddc | ||
|
665a06c41f | ||
|
f62f04c6bf | ||
|
253c97b922 | ||
|
c34a03d504 | ||
|
3f0dc69400 | ||
|
606b12caf0 | ||
|
41efddafde | ||
|
ee17289eeb | ||
|
d793f256c8 | ||
|
e1a79ae0bf | ||
|
ac91bb27ff | ||
|
97a7c04a8f | ||
|
99e89e57bc | ||
|
ab344a36e3 | ||
|
2bedd16c14 | ||
|
022eac4ac8 | ||
|
a75b0d2813 | ||
|
e8279fcefd | ||
|
dc9e795ce2 | ||
|
6ee3a8a039 | ||
|
6edee2eca4 | ||
|
6c86034e0f | ||
|
d07019f539 | ||
|
bb7d36d751 | ||
|
0fd8cba639 |
439 changed files with 6580 additions and 3308 deletions
|
@ -21,3 +21,6 @@ exclude_dir = [
|
||||||
]
|
]
|
||||||
exclude_regex = ["_test.go$", "_gen.go$"]
|
exclude_regex = ["_test.go$", "_gen.go$"]
|
||||||
stop_on_error = true
|
stop_on_error = true
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = true
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<!--
|
<!--
|
||||||
1. Please speak English, this is the language all maintainers can speak and write.
|
1. Please speak English, this is the language all maintainers can speak and write.
|
||||||
2. Please ask questions or configuration/deploy problems on our Discord
|
2. Please ask questions or configuration/deploy problems on our Discord
|
||||||
server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
|
server (https://discord.gg/gitea) or forum (https://forum.gitea.com).
|
||||||
3. Please take a moment to check that your issue doesn't already exist.
|
3. Please take a moment to check that your issue doesn't already exist.
|
||||||
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq)
|
||||||
5. Please give all relevant information below for bug reports, because
|
5. Please give all relevant information below for bug reports, because
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
- [ ] MySQL
|
- [ ] MySQL
|
||||||
- [ ] MSSQL
|
- [ ] MSSQL
|
||||||
- [ ] SQLite
|
- [ ] SQLite
|
||||||
- Can you reproduce the bug at https://try.gitea.io:
|
- Can you reproduce the bug at https://demo.gitea.com:
|
||||||
- [ ] Yes (provide example URL)
|
- [ ] Yes (provide example URL)
|
||||||
- [ ] No
|
- [ ] No
|
||||||
- Log gist:
|
- Log gist:
|
||||||
|
|
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
|
@ -37,7 +37,7 @@ body:
|
||||||
label: Can you reproduce the bug on the Gitea demo site?
|
label: Can you reproduce the bug on the Gitea demo site?
|
||||||
description: |
|
description: |
|
||||||
If so, please provide a URL in the Description field
|
If so, please provide a URL in the Description field
|
||||||
URL of Gitea demo: https://try.gitea.io
|
URL of Gitea demo: https://demo.gitea.com
|
||||||
options:
|
options:
|
||||||
- "Yes"
|
- "Yes"
|
||||||
- "No"
|
- "No"
|
||||||
|
@ -74,7 +74,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: How are you running Gitea?
|
label: How are you running Gitea?
|
||||||
description: |
|
description: |
|
||||||
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
|
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://demo.gitea.com or are using some other package
|
||||||
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
|
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
|
||||||
If you are using a package or systemd tell us what distribution you are using
|
If you are using a package or systemd tell us what distribution you are using
|
||||||
validations:
|
validations:
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
2
.github/ISSUE_TEMPLATE/ui.bug-report.yaml
vendored
|
@ -46,7 +46,7 @@ body:
|
||||||
label: Can you reproduce the bug on the Gitea demo site?
|
label: Can you reproduce the bug on the Gitea demo site?
|
||||||
description: |
|
description: |
|
||||||
If so, please provide a URL in the Description field
|
If so, please provide a URL in the Description field
|
||||||
URL of Gitea demo: https://try.gitea.io
|
URL of Gitea demo: https://demo.gitea.com
|
||||||
options:
|
options:
|
||||||
- "Yes"
|
- "Yes"
|
||||||
- "No"
|
- "No"
|
||||||
|
|
2
.github/workflows/release-nightly.yml
vendored
2
.github/workflows/release-nightly.yml
vendored
|
@ -49,7 +49,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||||
echo "Cleaned name is ${REF_NAME}"
|
echo "Cleaned name is ${REF_NAME}"
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
||||||
- name: configure aws
|
- name: configure aws
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
|
|
239
CHANGELOG.md
239
CHANGELOG.md
|
@ -4,6 +4,245 @@ 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.1](https://github.com/go-gitea/gitea/releases/tag/1.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
|
||||||
|
|
|
@ -77,7 +77,7 @@ If your issue has not been reported yet, [open an issue](https://github.com/go-g
|
||||||
and answer the questions so we can understand and reproduce the problematic behavior. \
|
and answer the questions so we can understand and reproduce the problematic behavior. \
|
||||||
Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \
|
Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \
|
||||||
The more detailed and specific you are, the faster we can fix the issue. \
|
The more detailed and specific you are, the faster we can fix the issue. \
|
||||||
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://try.gitea.io>, as perhaps your problem has already been fixed on a current version. \
|
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
|
||||||
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
||||||
|
|
||||||
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help.
|
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help.
|
||||||
|
@ -362,7 +362,7 @@ If you add a new feature or change an existing aspect of Gitea, the documentatio
|
||||||
|
|
||||||
## API v1
|
## API v1
|
||||||
|
|
||||||
The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
|
The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
|
||||||
|
|
||||||
### GitHub API compatibility
|
### GitHub API compatibility
|
||||||
|
|
||||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -1,12 +1,12 @@
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
|
FROM docker.io/library/golang:1.22-alpine3.20 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY ${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
ENV TAGS "bindata timetzdata $TAGS"
|
ENV TAGS="bindata timetzdata $TAGS"
|
||||||
ARG CGO_EXTRA_CFLAGS
|
ARG CGO_EXTRA_CFLAGS
|
||||||
|
|
||||||
# Build deps
|
# Build deps
|
||||||
|
@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.19
|
FROM docker.io/library/alpine:3.20
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 22 3000
|
EXPOSE 22 3000
|
||||||
|
@ -72,8 +72,8 @@ RUN addgroup \
|
||||||
git && \
|
git && \
|
||||||
echo "git:*" | chpasswd -e
|
echo "git:*" | chpasswd -e
|
||||||
|
|
||||||
ENV USER git
|
ENV USER=git
|
||||||
ENV GITEA_CUSTOM /data/gitea
|
ENV GITEA_CUSTOM=/data/gitea
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM docker.io/library/golang:1.22-alpine3.19 AS build-env
|
FROM docker.io/library/golang:1.22-alpine3.20 AS build-env
|
||||||
|
|
||||||
ARG GOPROXY
|
ARG GOPROXY
|
||||||
ENV GOPROXY ${GOPROXY:-direct}
|
ENV GOPROXY=${GOPROXY:-direct}
|
||||||
|
|
||||||
ARG GITEA_VERSION
|
ARG GITEA_VERSION
|
||||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||||
ENV TAGS "bindata timetzdata $TAGS"
|
ENV TAGS="bindata timetzdata $TAGS"
|
||||||
ARG CGO_EXTRA_CFLAGS
|
ARG CGO_EXTRA_CFLAGS
|
||||||
|
|
||||||
#Build deps
|
#Build deps
|
||||||
|
@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
|
||||||
/go/src/code.gitea.io/gitea/environment-to-ini
|
/go/src/code.gitea.io/gitea/environment-to-ini
|
||||||
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
|
||||||
|
|
||||||
FROM docker.io/library/alpine:3.19
|
FROM docker.io/library/alpine:3.20
|
||||||
LABEL maintainer="maintainers@gitea.io"
|
LABEL maintainer="maintainers@gitea.io"
|
||||||
|
|
||||||
EXPOSE 2222 3000
|
EXPOSE 2222 3000
|
||||||
|
@ -75,14 +75,14 @@ COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_au
|
||||||
|
|
||||||
# git:git
|
# git:git
|
||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
ENV GITEA_WORK_DIR /var/lib/gitea
|
ENV GITEA_WORK_DIR=/var/lib/gitea
|
||||||
ENV GITEA_CUSTOM /var/lib/gitea/custom
|
ENV GITEA_CUSTOM=/var/lib/gitea/custom
|
||||||
ENV GITEA_TEMP /tmp/gitea
|
ENV GITEA_TEMP=/tmp/gitea
|
||||||
ENV TMPDIR /tmp/gitea
|
ENV TMPDIR=/tmp/gitea
|
||||||
|
|
||||||
# TODO add to docs the ability to define the ini to load (useful to test and revert a config)
|
# TODO add to docs the ability to define the ini to load (useful to test and revert a config)
|
||||||
ENV GITEA_APP_INI /etc/gitea/app.ini
|
ENV GITEA_APP_INI=/etc/gitea/app.ini
|
||||||
ENV HOME "/var/lib/gitea/git"
|
ENV HOME="/var/lib/gitea/git"
|
||||||
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
VOLUME ["/var/lib/gitea", "/etc/gitea"]
|
||||||
WORKDIR /var/lib/gitea
|
WORKDIR /var/lib/gitea
|
||||||
|
|
||||||
|
|
6
Makefile
6
Makefile
|
@ -25,7 +25,7 @@ COMMA := ,
|
||||||
|
|
||||||
XGO_VERSION := go-1.22.x
|
XGO_VERSION := go-1.22.x
|
||||||
|
|
||||||
AIR_PACKAGE ?= github.com/cosmtrek/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.6.0
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
|
||||||
|
@ -88,7 +88,7 @@ ifneq ($(GITHUB_REF_TYPE),branch)
|
||||||
GITEA_VERSION ?= $(VERSION)
|
GITEA_VERSION ?= $(VERSION)
|
||||||
else
|
else
|
||||||
ifneq ($(GITHUB_REF_NAME),)
|
ifneq ($(GITHUB_REF_NAME),)
|
||||||
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
|
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))-nightly
|
||||||
else
|
else
|
||||||
VERSION ?= main
|
VERSION ?= main
|
||||||
endif
|
endif
|
||||||
|
@ -778,7 +778,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
|
||||||
.PHONY: generate-go
|
.PHONY: generate-go
|
||||||
generate-go: $(TAGS_PREREQ)
|
generate-go: $(TAGS_PREREQ)
|
||||||
@echo "Running go generate..."
|
@echo "Running go generate..."
|
||||||
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
|
@CC= GOOS= GOARCH= CGO_ENABLED=0 $(GO) generate -tags '$(TAGS)' ./...
|
||||||
|
|
||||||
.PHONY: security-check
|
.PHONY: security-check
|
||||||
security-check:
|
security-check:
|
||||||
|
|
|
@ -26,7 +26,7 @@ This project has been
|
||||||
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
[forked](https://blog.gitea.com/welcome-to-gitea/) from
|
||||||
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
[Gogs](https://gogs.io) since November of 2016, but a lot has changed.
|
||||||
|
|
||||||
For online demonstrations, you can visit [try.gitea.io](https://try.gitea.io).
|
For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com).
|
||||||
|
|
||||||
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ More info: https://docs.gitea.com/installation/install-from-source
|
||||||
./gitea web
|
./gitea web
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> If you're interested in using our APIs, we have experimental support with [documentation](https://try.gitea.io/api/swagger).
|
> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ https://docs.gitea.com/contributing/localization
|
||||||
## Further information
|
## Further information
|
||||||
|
|
||||||
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.com/).
|
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.com/).
|
||||||
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://discourse.gitea.io/).
|
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
||||||
|
|
||||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
|
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
|
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
|
||||||
|
|
||||||
如果你想试用在线演示,请访问 [try.gitea.io](https://try.gitea.io/)。
|
如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。
|
||||||
|
|
||||||
如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
|
如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
|
||||||
|
|
||||||
|
|
10
cmd/hook.go
10
cmd/hook.go
|
@ -220,10 +220,7 @@ Gitea or set your environment appropriately.`, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supportProcReceive := false
|
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
|
||||||
supportProcReceive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// TODO: support news feeds for wiki
|
// TODO: support news feeds for wiki
|
||||||
|
@ -341,6 +338,7 @@ Gitea or set your environment appropriately.`, "")
|
||||||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||||
|
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||||
|
|
||||||
hookOptions := private.HookOptions{
|
hookOptions := private.HookOptions{
|
||||||
|
@ -350,6 +348,8 @@ Gitea or set your environment appropriately.`, "")
|
||||||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
||||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||||
GitPushOptions: pushOptions(),
|
GitPushOptions: pushOptions(),
|
||||||
|
PullRequestID: prID,
|
||||||
|
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||||
}
|
}
|
||||||
oldCommitIDs := make([]string, hookBatchSize)
|
oldCommitIDs := make([]string, hookBatchSize)
|
||||||
newCommitIDs := make([]string, hookBatchSize)
|
newCommitIDs := make([]string, hookBatchSize)
|
||||||
|
@ -497,7 +497,7 @@ Gitea or set your environment appropriately.`, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if git.CheckGitVersionAtLeast("2.29") != nil {
|
if !git.DefaultFeatures().SupportProcReceive {
|
||||||
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
@ -34,7 +36,7 @@ var CmdMigrateStorage = &cli.Command{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
|
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "storage",
|
Name: "storage",
|
||||||
|
@ -160,6 +162,25 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||||
|
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
|
||||||
|
if artifact.Status == int64(actions_model.ArtifactStatusExpired) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := storage.Copy(dstStorage, artifact.StoragePath, storage.ActionsArtifacts, artifact.StoragePath)
|
||||||
|
if err != nil {
|
||||||
|
// ignore files that do not exist
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runMigrateStorage(ctx *cli.Context) error {
|
func runMigrateStorage(ctx *cli.Context) error {
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -230,6 +251,7 @@ func runMigrateStorage(ctx *cli.Context) error {
|
||||||
"repo-archivers": migrateRepoArchivers,
|
"repo-archivers": migrateRepoArchivers,
|
||||||
"packages": migratePackages,
|
"packages": migratePackages,
|
||||||
"actions-log": migrateActionsLog,
|
"actions-log": migrateActionsLog,
|
||||||
|
"actions-artifacts": migrateActionsArtifacts,
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := strings.ToLower(ctx.String("type"))
|
tp := strings.ToLower(ctx.String("type"))
|
||||||
|
|
|
@ -178,7 +178,7 @@ func runServ(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(words) < 2 {
|
||||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
// for AGit Flow
|
// for AGit Flow
|
||||||
if cmd == "ssh_info" {
|
if cmd == "ssh_info" {
|
||||||
fmt.Print(`{"type":"gitea","version":1}`)
|
fmt.Print(`{"type":"gitea","version":1}`)
|
||||||
|
|
|
@ -2035,6 +2035,17 @@ LEVEL = Info
|
||||||
;; or only create new users if UPDATE_EXISTING is set to false
|
;; or only create new users if UPDATE_EXISTING is set to false
|
||||||
;UPDATE_EXISTING = true
|
;UPDATE_EXISTING = true
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Cleanup expired actions assets
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;[cron.cleanup_actions]
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;ENABLED = true
|
||||||
|
;RUN_AT_START = true
|
||||||
|
;SCHEDULE = @midnight
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Clean-up deleted branches
|
;; Clean-up deleted branches
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
# Gitea: Docs
|
# Gitea: Docs
|
||||||
|
|
||||||
[](https://discord.gg/Gitea)
|
|
||||||
[](http://microbadger.com/images/gitea/docs "Get your own image badge on microbadger.com")
|
|
||||||
|
|
||||||
These docs are ingested by our [docs repo](https://gitea.com/gitea/gitea-docusaurus).
|
These docs are ingested by our [docs repo](https://gitea.com/gitea/gitea-docusaurus).
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
@ -18,5 +15,5 @@ for the full license text.
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright (c) 2016 The Gitea Authors <https://gitea.io>
|
Copyright (c) 2016 The Gitea Authors
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
# Gitea: 文档
|
# Gitea: 文档
|
||||||
|
|
||||||
[](http://drone.gitea.io/go-gitea/docs)
|
|
||||||
[](https://discord.gg/Gitea)
|
|
||||||
[](http://microbadger.com/images/gitea/docs "Get your own image badge on microbadger.com")
|
|
||||||
|
|
||||||
https://gitea.com/gitea/gitea-docusaurus
|
https://gitea.com/gitea/gitea-docusaurus
|
||||||
|
|
||||||
## 关于我们
|
## 关于我们
|
||||||
|
@ -18,5 +14,5 @@ https://gitea.com/gitea/gitea-docusaurus
|
||||||
## 版权声明
|
## 版权声明
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright (c) 2016 The Gitea Authors <https://gitea.io>
|
Copyright (c) 2016 The Gitea Authors
|
||||||
```
|
```
|
||||||
|
|
|
@ -214,9 +214,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||||
- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap.
|
- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap.
|
||||||
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
|
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
|
||||||
- `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment.
|
- `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment.
|
||||||
- `DEFAULT_THEME`: **gitea-auto**: Set the default theme for the Gitea installation, custom themes could be provided by "{CustomPath}/public/assets/css/theme-*.css".
|
- `DEFAULT_THEME`: **gitea-auto**: Set the default theme for the Gitea installation, custom themes could be provided by `{CustomPath}/public/assets/css/theme-*.css`.
|
||||||
- `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page.
|
- `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page.
|
||||||
- `THEMES`: **_empty_**: All available themes by "{CustomPath}/public/assets/css/theme-*.css". Allow users select personalized themes.
|
- `THEMES`: **_empty_**: All available themes by `{CustomPath}/public/assets/css/theme-*.css`. Allow users select personalized themes.
|
||||||
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
|
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
|
||||||
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
|
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
|
||||||
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
|
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
|
||||||
|
@ -974,12 +974,20 @@ Default templates for project boards:
|
||||||
- `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts.
|
- `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts.
|
||||||
- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false.
|
- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false.
|
||||||
|
|
||||||
## Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
|
#### Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
|
||||||
|
|
||||||
- `ENABLED`: **true**: Enable cleanup expired actions assets job.
|
- `ENABLED`: **true**: Enable cleanup expired actions assets job.
|
||||||
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
||||||
- `SCHEDULE`: **@midnight** : Cron syntax for the job.
|
- `SCHEDULE`: **@midnight** : Cron syntax for the job.
|
||||||
|
|
||||||
|
#### Cron - Cleanup Deleted Branches (`cron.deleted_branches_cleanup`)
|
||||||
|
|
||||||
|
- `ENABLED`: **true**: Enable deleted branches cleanup.
|
||||||
|
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
|
||||||
|
- `NOTICE_ON_SUCCESS`: **false**: Set to true to log a success message.
|
||||||
|
- `SCHEDULE`: **@midnight**: Cron syntax for scheduling deleted branches cleanup.
|
||||||
|
- `OLDER_THAN`: **24h**: Branches deleted OLDER_THAN ago will be cleaned up.
|
||||||
|
|
||||||
### Extended cron tasks (not enabled by default)
|
### Extended cron tasks (not enabled by default)
|
||||||
|
|
||||||
#### Cron - Garbage collect all repositories (`cron.git_gc_repos`)
|
#### Cron - Garbage collect all repositories (`cron.git_gc_repos`)
|
||||||
|
|
|
@ -212,9 +212,9 @@ menu:
|
||||||
- `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。
|
- `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。
|
||||||
- `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。
|
- `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。
|
||||||
- `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。
|
- `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。
|
||||||
- `DEFAULT_THEME`: **gitea-auto**: 在Gitea安装时候设置的默认主题,自定义的主题可以通过 "{CustomPath}/public/assets/css/theme-*.css" 提供。
|
- `DEFAULT_THEME`: **gitea-auto**: 在Gitea安装时候设置的默认主题,自定义的主题可以通过 `{CustomPath}/public/assets/css/theme-*.css` 提供。
|
||||||
- `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。
|
- `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。
|
||||||
- `THEMES`: **_empty_**: 所有可用的主题(由 "{CustomPath}/public/assets/css/theme-*.css" 提供)。允许用户选择个性化的主题,
|
- `THEMES`: **_empty_**: 所有可用的主题(由 `{CustomPath}/public/assets/css/theme-*.css` 提供)。允许用户选择个性化的主题,
|
||||||
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。
|
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。
|
||||||
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。
|
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。
|
||||||
这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。
|
这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。
|
||||||
|
|
|
@ -38,12 +38,10 @@ FROM gitea/gitea:@version@
|
||||||
COPY custom/app.ini /data/gitea/conf/app.ini
|
COPY custom/app.ini /data/gitea/conf/app.ini
|
||||||
[...]
|
[...]
|
||||||
|
|
||||||
RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng libffi-dev py-pip python3-dev py3-pip py3-pyzmq
|
RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng libffi-dev pandoc python3-dev py3-pyzmq pipx
|
||||||
# install any other package you need for your external renderers
|
# install any other package you need for your external renderers
|
||||||
|
|
||||||
RUN pip3 install --upgrade pip
|
RUN pipx install jupyter docutils --include-deps
|
||||||
RUN pip3 install -U setuptools
|
|
||||||
RUN pip3 install jupyter docutils
|
|
||||||
# add above any other python package you may need to install
|
# add above any other python package you may need to install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -37,12 +37,10 @@ FROM gitea/gitea:@version@
|
||||||
COPY custom/app.ini /data/gitea/conf/app.ini
|
COPY custom/app.ini /data/gitea/conf/app.ini
|
||||||
[...]
|
[...]
|
||||||
|
|
||||||
RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng libffi-dev py-pip python3-dev py3-pip py3-pyzmq
|
RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng libffi-dev pandoc python3-dev py3-pyzmq pipx
|
||||||
# 安装其他您需要的外部渲染器的软件包
|
# 安装其他您需要的外部渲染器的软件包
|
||||||
|
|
||||||
RUN pip3 install --upgrade pip
|
RUN pipx install jupyter docutils --include-deps
|
||||||
RUN pip3 install -U setuptools
|
|
||||||
RUN pip3 install jupyter docutils
|
|
||||||
# 在上面添加您需要安装的任何其他 Python 软件包
|
# 在上面添加您需要安装的任何其他 Python 软件包
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,35 @@ menu:
|
||||||
|
|
||||||
# Reverse Proxies
|
# Reverse Proxies
|
||||||
|
|
||||||
|
## General configuration
|
||||||
|
|
||||||
|
1. Set `[server] ROOT_URL = https://git.example.com/` in your `app.ini` file.
|
||||||
|
2. Make the reverse-proxy pass `https://git.example.com/foo` to `http://gitea:3000/foo`.
|
||||||
|
3. Make sure the reverse-proxy does not decode the URI. The request `https://git.example.com/a%2Fb` should be passed as `http://gitea:3000/a%2Fb`.
|
||||||
|
4. Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea to make Gitea see the real URL being visited.
|
||||||
|
|
||||||
|
### Use a sub-path
|
||||||
|
|
||||||
|
Usually it's **not recommended** to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases.
|
||||||
|
|
||||||
|
To make Gitea work with a sub-path (eg: `https://common.example.com/gitea/`),
|
||||||
|
there are some extra requirements besides the general configuration above:
|
||||||
|
|
||||||
|
1. Use `[server] ROOT_URL = https://common.example.com/gitea/` in your `app.ini` file.
|
||||||
|
2. Make the reverse-proxy pass `https://common.example.com/gitea/foo` to `http://gitea:3000/foo`.
|
||||||
|
3. The container registry requires a fixed sub-path `/v2` at the root level which must be configured:
|
||||||
|
- Make the reverse-proxy pass `https://common.example.com/v2` to `http://gitea:3000/v2`.
|
||||||
|
- Make sure the URI and headers are also correctly passed (see the general configuration above).
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
|
|
||||||
If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`:
|
If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`.
|
||||||
|
|
||||||
```
|
Make sure `client_max_body_size` is large enough, otherwise there would be "413 Request Entity Too Large" error when uploading large files.
|
||||||
|
|
||||||
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
...
|
||||||
server_name git.example.com;
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
client_max_body_size 512M;
|
client_max_body_size 512M;
|
||||||
proxy_pass http://localhost:3000;
|
proxy_pass http://localhost:3000;
|
||||||
|
@ -39,37 +59,35 @@ server {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Resolving Error: 413 Request Entity Too Large
|
|
||||||
|
|
||||||
This error indicates nginx is configured to restrict the file upload size,
|
|
||||||
it affects attachment uploading, form posting, package uploading and LFS pushing, etc.
|
|
||||||
You can fine tune the `client_max_body_size` option according to [nginx document](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size).
|
|
||||||
|
|
||||||
## Nginx with a sub-path
|
## Nginx with a sub-path
|
||||||
|
|
||||||
In case you already have a site, and you want Gitea to share the domain name, you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section inside the `http` section of `nginx.conf`:
|
In case you already have a site, and you want Gitea to share the domain name,
|
||||||
|
you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section
|
||||||
|
into the `http` section of `nginx.conf`:
|
||||||
|
|
||||||
```
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
...
|
||||||
server_name git.example.com;
|
location ~ ^/(gitea|v2)($|/) {
|
||||||
|
|
||||||
# Note: Trailing slash
|
|
||||||
location /gitea/ {
|
|
||||||
client_max_body_size 512M;
|
client_max_body_size 512M;
|
||||||
|
|
||||||
# make nginx use unescaped URI, keep "%2F" as is
|
# make nginx use unescaped URI, keep "%2F" as-is, remove the "/gitea" sub-path prefix, pass "/v2" as-is.
|
||||||
rewrite ^ $request_uri;
|
rewrite ^ $request_uri;
|
||||||
rewrite ^/gitea(/.*) $1 break;
|
rewrite ^(/gitea)?(/.*) $2 break;
|
||||||
proxy_pass http://127.0.0.1:3000$uri;
|
proxy_pass http://127.0.0.1:3000$uri;
|
||||||
|
|
||||||
# other common HTTP headers, see the "Nginx" config section above
|
# other common HTTP headers, see the "Nginx" config section above
|
||||||
proxy_set_header ...
|
proxy_set_header Connection $http_connection;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/git/` correctly in your configuration.
|
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/gitea/` correctly in your configuration.
|
||||||
|
|
||||||
## Nginx and serve static resources directly
|
## Nginx and serve static resources directly
|
||||||
|
|
||||||
|
@ -93,7 +111,7 @@ or use a cdn for the static files.
|
||||||
|
|
||||||
Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
|
Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
|
||||||
|
|
||||||
```apacheconf
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name git.example.com;
|
server_name git.example.com;
|
||||||
|
@ -112,7 +130,7 @@ server {
|
||||||
|
|
||||||
Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration.
|
Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration.
|
||||||
|
|
||||||
```apacheconf
|
```nginx
|
||||||
# application server running Gitea
|
# application server running Gitea
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
@ -124,7 +142,7 @@ server {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```apacheconf
|
```nginx
|
||||||
# static content delivery server
|
# static content delivery server
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
@ -151,6 +169,7 @@ If you want Apache HTTPD to serve your Gitea instance, you can add the following
|
||||||
ProxyRequests off
|
ProxyRequests off
|
||||||
AllowEncodedSlashes NoDecode
|
AllowEncodedSlashes NoDecode
|
||||||
ProxyPass / http://localhost:3000/ nocanon
|
ProxyPass / http://localhost:3000/ nocanon
|
||||||
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -172,6 +191,8 @@ In case you already have a site, and you want Gitea to share the domain name, yo
|
||||||
AllowEncodedSlashes NoDecode
|
AllowEncodedSlashes NoDecode
|
||||||
# Note: no trailing slash after either /git or port
|
# Note: no trailing slash after either /git or port
|
||||||
ProxyPass /git http://localhost:3000 nocanon
|
ProxyPass /git http://localhost:3000 nocanon
|
||||||
|
ProxyPreserveHost On
|
||||||
|
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -183,7 +204,7 @@ Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`.
|
||||||
|
|
||||||
If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile:
|
If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile:
|
||||||
|
|
||||||
```apacheconf
|
```
|
||||||
git.example.com {
|
git.example.com {
|
||||||
reverse_proxy localhost:3000
|
reverse_proxy localhost:3000
|
||||||
}
|
}
|
||||||
|
@ -193,7 +214,7 @@ git.example.com {
|
||||||
|
|
||||||
In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile:
|
In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile:
|
||||||
|
|
||||||
```apacheconf
|
```
|
||||||
git.example.com {
|
git.example.com {
|
||||||
route /git/* {
|
route /git/* {
|
||||||
uri strip_prefix /git
|
uri strip_prefix /git
|
||||||
|
@ -371,19 +392,3 @@ gitea:
|
||||||
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
|
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
|
||||||
|
|
||||||
Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.
|
Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.
|
||||||
|
|
||||||
## General sub-path configuration
|
|
||||||
|
|
||||||
Usually it's not recommended to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases.
|
|
||||||
|
|
||||||
If you really need to do so, to make Gitea works with sub-path (eg: `http://example.com/gitea/`), here are the requirements:
|
|
||||||
|
|
||||||
1. Set `[server] ROOT_URL = http://example.com/gitea/` in your `app.ini` file.
|
|
||||||
2. Make the reverse-proxy pass `http://example.com/gitea/foo` to `http://gitea-server:3000/foo`.
|
|
||||||
3. Make sure the reverse-proxy not decode the URI, the request `http://example.com/gitea/a%2Fb` should be passed as `http://gitea-server:3000/a%2Fb`.
|
|
||||||
|
|
||||||
## Docker / Container Registry
|
|
||||||
|
|
||||||
The container registry uses a fixed sub-path `/v2` which can't be changed.
|
|
||||||
Even if you deploy Gitea with a different sub-path, `/v2` will be used by the `docker` client.
|
|
||||||
Therefore you may need to add an additional route to your reverse proxy configuration.
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ curl -v "http://localhost/api/v1/repos/search?limit=1"
|
||||||
API Reference guide is auto-generated by swagger and available on:
|
API Reference guide is auto-generated by swagger and available on:
|
||||||
`https://gitea.your.host/api/swagger`
|
`https://gitea.your.host/api/swagger`
|
||||||
or on the
|
or on the
|
||||||
[Gitea demo instance](https://try.gitea.io/api/swagger)
|
[Gitea instance](https://gitea.com/api/swagger)
|
||||||
|
|
||||||
The OpenAPI document is at:
|
The OpenAPI document is at:
|
||||||
`https://gitea.your.host/swagger.v1.json`
|
`https://gitea.your.host/swagger.v1.json`
|
||||||
|
|
|
@ -45,7 +45,7 @@ To migrate from GitHub to Gitea, you can use Gitea's built-in migration form.
|
||||||
|
|
||||||
In order to migrate items such as issues, pull requests, etc. you will need to input at least your username.
|
In order to migrate items such as issues, pull requests, etc. you will need to input at least your username.
|
||||||
|
|
||||||
[Example (requires login)](https://try.gitea.io/repo/migrate)
|
[Example (requires login)](https://demo.gitea.com/repo/migrate)
|
||||||
|
|
||||||
To migrate from GitLab to Gitea, you can use this non-affiliated tool:
|
To migrate from GitLab to Gitea, you can use this non-affiliated tool:
|
||||||
|
|
||||||
|
@ -137,9 +137,9 @@ All Gitea instances have the built-in API and there is no way to disable it comp
|
||||||
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
|
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
|
||||||
For more information, refer to Gitea's [API docs](development/api-usage.md).
|
For more information, refer to Gitea's [API docs](development/api-usage.md).
|
||||||
|
|
||||||
You can see the latest API (for example) on https://try.gitea.io/api/swagger
|
You can see the latest API (for example) on https://gitea.com/api/swagger
|
||||||
|
|
||||||
You can also see an example of the `swagger.json` file at https://try.gitea.io/swagger.v1.json
|
You can also see an example of the `swagger.json` file at https://gitea.com/swagger.v1.json
|
||||||
|
|
||||||
## Adjusting your server for public/private use
|
## Adjusting your server for public/private use
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ menu:
|
||||||
|
|
||||||
为了迁移诸如问题、拉取请求等项目,您需要至少输入您的用户名。
|
为了迁移诸如问题、拉取请求等项目,您需要至少输入您的用户名。
|
||||||
|
|
||||||
[Example (requires login)](https://try.gitea.io/repo/migrate)
|
[Example (requires login)](https://demo.gitea.com/repo/migrate)
|
||||||
|
|
||||||
要从GitLab迁移到Gitea,您可以使用这个非关联的工具:
|
要从GitLab迁移到Gitea,您可以使用这个非关联的工具:
|
||||||
|
|
||||||
|
@ -141,9 +141,9 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供
|
||||||
但是,您可以在app.ini的api部分将ENABLE_SWAGGER设置为false,以禁用其文档显示。
|
但是,您可以在app.ini的api部分将ENABLE_SWAGGER设置为false,以禁用其文档显示。
|
||||||
有关更多信息,请参阅Gitea的[API文档](development/api-usage.md)。
|
有关更多信息,请参阅Gitea的[API文档](development/api-usage.md)。
|
||||||
|
|
||||||
您可以在上查看最新的API(例如)https://try.gitea.io/api/swagger
|
您可以在上查看最新的API(例如)https://gitea.com/api/swagger
|
||||||
|
|
||||||
您还可以在上查看`swagger.json`文件的示例 https://try.gitea.io/swagger.v1.json
|
您还可以在上查看`swagger.json`文件的示例 https://gitea.com/swagger.v1.json
|
||||||
|
|
||||||
## 调整服务器用于公共/私有使用
|
## 调整服务器用于公共/私有使用
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ menu:
|
||||||
|
|
||||||
- [Paid Commercial Support](https://about.gitea.com/)
|
- [Paid Commercial Support](https://about.gitea.com/)
|
||||||
- [Discord](https://discord.gg/Gitea)
|
- [Discord](https://discord.gg/Gitea)
|
||||||
- [Discourse Forum](https://discourse.gitea.io/)
|
- [Forum](https://forum.gitea.com/)
|
||||||
- [Matrix](https://matrix.to/#/#gitea-space:matrix.org)
|
- [Matrix](https://matrix.to/#/#gitea-space:matrix.org)
|
||||||
- NOTE: Most of the Matrix channels are bridged with their counterpart in Discord and may experience some degree of flakiness with the bridge process.
|
- NOTE: Most of the Matrix channels are bridged with their counterpart in Discord and may experience some degree of flakiness with the bridge process.
|
||||||
- Chinese Support
|
- Chinese Support
|
||||||
- [Discourse Chinese Category](https://discourse.gitea.io/c/5-category/5)
|
- [Discourse Chinese Category](https://forum.gitea.com/c/5-category/5)
|
||||||
- QQ Group 328432459
|
- QQ Group 328432459
|
||||||
|
|
||||||
# Bug Report
|
# Bug Report
|
||||||
|
@ -39,7 +39,7 @@ If you found a bug, please [create an issue on GitHub](https://github.com/go-git
|
||||||
- When using systemd, use `journalctl --lines 1000 --unit gitea` to collect logs.
|
- When using systemd, use `journalctl --lines 1000 --unit gitea` to collect logs.
|
||||||
- When using docker, use `docker logs --tail 1000 <gitea-container>` to collect logs.
|
- When using docker, use `docker logs --tail 1000 <gitea-container>` to collect logs.
|
||||||
4. Reproducible steps so that others could reproduce and understand the problem more quickly and easily.
|
4. Reproducible steps so that others could reproduce and understand the problem more quickly and easily.
|
||||||
- [try.gitea.io](https://try.gitea.io) could be used to reproduce the problem.
|
- [demo.gitea.com](https://demo.gitea.com) could be used to reproduce the problem.
|
||||||
5. If you encounter slow/hanging/deadlock problems, please report the stacktrace when the problem occurs.
|
5. If you encounter slow/hanging/deadlock problems, please report the stacktrace when the problem occurs.
|
||||||
Go to the "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report".
|
Go to the "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report".
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ menu:
|
||||||
|
|
||||||
- [付费商业支持](https://about.gitea.com/)
|
- [付费商业支持](https://about.gitea.com/)
|
||||||
- [Discord](https://discord.gg/Gitea)
|
- [Discord](https://discord.gg/Gitea)
|
||||||
- [Discourse 论坛](https://discourse.gitea.io/)
|
- [论坛](https://forum.gitea.com/)
|
||||||
- [Matrix](https://matrix.to/#/#gitea-space:matrix.org)
|
- [Matrix](https://matrix.to/#/#gitea-space:matrix.org)
|
||||||
- 注意:大多数 Matrix 频道都与 Discord 中的对应频道桥接,可能在桥接过程中会出现一定程度的不稳定性。
|
- 注意:大多数 Matrix 频道都与 Discord 中的对应频道桥接,可能在桥接过程中会出现一定程度的不稳定性。
|
||||||
- 中文支持
|
- 中文支持
|
||||||
- [Discourse 中文分类](https://discourse.gitea.io/c/5-category/5)
|
- [Discourse 中文分类](https://forum.gitea.com/c/5-category/5)
|
||||||
- QQ 群 328432459
|
- QQ 群 328432459
|
||||||
|
|
||||||
# Bug 报告
|
# Bug 报告
|
||||||
|
@ -39,7 +39,7 @@ menu:
|
||||||
- 在使用 systemd 时,使用 `journalctl --lines 1000 --unit gitea` 收集日志。
|
- 在使用 systemd 时,使用 `journalctl --lines 1000 --unit gitea` 收集日志。
|
||||||
- 在使用 Docker 时,使用 `docker logs --tail 1000 <gitea-container>` 收集日志。
|
- 在使用 Docker 时,使用 `docker logs --tail 1000 <gitea-container>` 收集日志。
|
||||||
4. 可重现的步骤,以便他人能够更快速、更容易地重现和理解问题。
|
4. 可重现的步骤,以便他人能够更快速、更容易地重现和理解问题。
|
||||||
- [try.gitea.io](https://try.gitea.io) 可用于重现问题。
|
- [demo.gitea.com](https://demo.gitea.com) 可用于重现问题。
|
||||||
5. 如果遇到慢速/挂起/死锁等问题,请在出现问题时报告堆栈跟踪。
|
5. 如果遇到慢速/挂起/死锁等问题,请在出现问题时报告堆栈跟踪。
|
||||||
转到 "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report"。
|
转到 "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report"。
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ up a self-hosted Git service.
|
||||||
With Go, this can be done platform-independently across
|
With Go, this can be done platform-independently across
|
||||||
**all platforms** which Go supports, including Linux, macOS, and Windows,
|
**all platforms** which Go supports, including Linux, macOS, and Windows,
|
||||||
on x86, amd64, ARM and PowerPC architectures.
|
on x86, amd64, ARM and PowerPC architectures.
|
||||||
You can try it out using [the online demo](https://try.gitea.io/).
|
You can try it out using [the online demo](https://demo.gitea.com).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,9 @@ slug: "badge"
|
||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
toc: false
|
toc: false
|
||||||
draft: false
|
draft: false
|
||||||
aliases:
|
|
||||||
- /en-us/badge
|
|
||||||
menu:
|
menu:
|
||||||
sidebar:
|
sidebar:
|
||||||
parent: "usage"
|
parent: "actions"
|
||||||
name: "Badge"
|
name: "Badge"
|
||||||
sidebar_position: 11
|
sidebar_position: 11
|
||||||
identifier: "Badge"
|
identifier: "Badge"
|
||||||
|
@ -27,7 +25,7 @@ It is designed to be compatible with [GitHub Actions workflow badge](https://doc
|
||||||
You can use the following URL to get the badge:
|
You can use the following URL to get the badge:
|
||||||
|
|
||||||
```
|
```
|
||||||
https://your-gitea-instance.com/{owner}/{repo}/actions/workflows/{workflow_file}?branch={branch}&event={event}
|
https://your-gitea-instance.com/{owner}/{repo}/actions/workflows/{workflow_file}/badge.svg?branch={branch}&event={event}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `{owner}`: The owner of the repository.
|
- `{owner}`: The owner of the repository.
|
|
@ -108,6 +108,10 @@ See [Creating an annotation for an error](https://docs.github.com/en/actions/usi
|
||||||
|
|
||||||
It's ignored by Gitea Actions now.
|
It's ignored by Gitea Actions now.
|
||||||
|
|
||||||
|
### Expressions
|
||||||
|
|
||||||
|
For [expressions](https://docs.github.com/en/actions/learn-github-actions/expressions), only [`always()`](https://docs.github.com/en/actions/learn-github-actions/expressions#always) is supported.
|
||||||
|
|
||||||
## Missing UI features
|
## Missing UI features
|
||||||
|
|
||||||
### Pre and Post steps
|
### Pre and Post steps
|
||||||
|
|
|
@ -108,6 +108,10 @@ Gitea Actions目前不支持此功能。
|
||||||
|
|
||||||
Gitea Actions目前不支持此功能。
|
Gitea Actions目前不支持此功能。
|
||||||
|
|
||||||
|
### 表达式
|
||||||
|
|
||||||
|
对于 [表达式](https://docs.github.com/en/actions/learn-github-actions/expressions), 当前仅 [`always()`](https://docs.github.com/en/actions/learn-github-actions/expressions#always) 被支持。
|
||||||
|
|
||||||
## 缺失的UI功能
|
## 缺失的UI功能
|
||||||
|
|
||||||
### 预处理和后处理步骤
|
### 预处理和后处理步骤
|
||||||
|
|
|
@ -5,11 +5,9 @@ slug: "secrets"
|
||||||
sidebar_position: 50
|
sidebar_position: 50
|
||||||
draft: false
|
draft: false
|
||||||
toc: false
|
toc: false
|
||||||
aliases:
|
|
||||||
- /en-us/secrets
|
|
||||||
menu:
|
menu:
|
||||||
sidebar:
|
sidebar:
|
||||||
parent: "usage"
|
parent: "actions"
|
||||||
name: "Secrets"
|
name: "Secrets"
|
||||||
sidebar_position: 50
|
sidebar_position: 50
|
||||||
identifier: "usage-secrets"
|
identifier: "usage-secrets"
|
|
@ -5,11 +5,9 @@ slug: "secrets"
|
||||||
sidebar_position: 50
|
sidebar_position: 50
|
||||||
draft: false
|
draft: false
|
||||||
toc: false
|
toc: false
|
||||||
aliases:
|
|
||||||
- /zh-cn/secrets
|
|
||||||
menu:
|
menu:
|
||||||
sidebar:
|
sidebar:
|
||||||
parent: "usage"
|
parent: "actions"
|
||||||
name: "密钥管理"
|
name: "密钥管理"
|
||||||
sidebar_position: 50
|
sidebar_position: 50
|
||||||
identifier: "usage-secrets"
|
identifier: "usage-secrets"
|
|
@ -236,7 +236,7 @@ configure this, set the fields below:
|
||||||
|
|
||||||
- Restrict what domains can log in if using a public SMTP host or SMTP host
|
- Restrict what domains can log in if using a public SMTP host or SMTP host
|
||||||
with multiple domains.
|
with multiple domains.
|
||||||
- Example: `gitea.io,mydomain.com,mydomain2.com`
|
- Example: `gitea.com,mydomain.com,mydomain2.com`
|
||||||
|
|
||||||
- Force SMTPS
|
- Force SMTPS
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ PAM提供了一种机制,通过对用户进行PAM认证来自动将其添加
|
||||||
|
|
||||||
- 如果使用公共 SMTP 主机或有多个域的 SMTP 主机,限制哪些域可以登录
|
- 如果使用公共 SMTP 主机或有多个域的 SMTP 主机,限制哪些域可以登录
|
||||||
限制哪些域可以登录。
|
限制哪些域可以登录。
|
||||||
- 示例: `gitea.io,mydomain.com,mydomain2.com`
|
- 示例: `gitea.com,mydomain.com,mydomain2.com`
|
||||||
|
|
||||||
- 强制使用 SMTPS
|
- 强制使用 SMTPS
|
||||||
- 默认情况下将使用SMTPS连接到端口465.如果您希望将smtp用于其他端口,自行设置
|
- 默认情况下将使用SMTPS连接到端口465.如果您希望将smtp用于其他端口,自行设置
|
||||||
|
|
|
@ -308,7 +308,7 @@ This is a example for a issue config file
|
||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Gitea
|
- name: Gitea
|
||||||
url: https://gitea.io
|
url: https://gitea.com
|
||||||
about: Visit the Gitea Website
|
about: Visit the Gitea Website
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ The following examples use the `npm` tool with the scope `@test`.
|
||||||
To register the package registry you need to configure a new package source.
|
To register the package registry you need to configure a new package source.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm config set {scope}:registry https://gitea.example.com/api/packages/{owner}/npm/
|
npm config set {scope}:registry=https://gitea.example.com/api/packages/{owner}/npm/
|
||||||
npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{token}"
|
npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{token}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{t
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm config set @test:registry https://gitea.example.com/api/packages/testuser/npm/
|
npm config set @test:registry=https://gitea.example.com/api/packages/testuser/npm/
|
||||||
npm config set -- '//gitea.example.com/api/packages/testuser/npm/:_authToken' "personal_access_token"
|
npm config set -- '//gitea.example.com/api/packages/testuser/npm/:_authToken' "personal_access_token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ menu:
|
||||||
要注册软件包注册表,您需要配置一个新的软件包源。
|
要注册软件包注册表,您需要配置一个新的软件包源。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm config set {scope}:registry https://gitea.example.com/api/packages/{owner}/npm/
|
npm config set {scope}:registry=https://gitea.example.com/api/packages/{owner}/npm/
|
||||||
npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{token}"
|
npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{token}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ npm config set -- '//gitea.example.com/api/packages/{owner}/npm/:_authToken' "{t
|
||||||
例如:
|
例如:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm config set @test:registry https://gitea.example.com/api/packages/testuser/npm/
|
npm config set @test:registry=https://gitea.example.com/api/packages/testuser/npm/
|
||||||
npm config set -- '//gitea.example.com/api/packages/testuser/npm/:_authToken' "personal_access_token"
|
npm config set -- '//gitea.example.com/api/packages/testuser/npm/:_authToken' "personal_access_token"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ The repository now gets mirrored periodically to the remote repository. You can
|
||||||
|
|
||||||
To set up a mirror from Gitea to GitHub, you need to follow these steps:
|
To set up a mirror from Gitea to GitHub, you need to follow these steps:
|
||||||
|
|
||||||
1. Create a [GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the *public_repo* box checked. Also check the **workflow** checkbox in case your repo using act for continuous integration.
|
1. Create a [GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the *public_repo* box checked. Also check the **workflow** checkbox in case your repo uses GitHub Actions for continuous integration.
|
||||||
2. Create a repository with that name on GitHub. Unlike Gitea, GitHub does not support creating repositories by pushing to the remote. You can also use an existing remote repo if it has the same commit history as your Gitea repo.
|
2. Create a repository with that name on GitHub. Unlike Gitea, GitHub does not support creating repositories by pushing to the remote. You can also use an existing remote repo if it has the same commit history as your Gitea repo.
|
||||||
3. In the settings of your Gitea repo, fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
|
3. In the settings of your Gitea repo, fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
|
||||||
4. Fill in the **Authorization** fields with your GitHub username and the personal access token as **Password**.
|
4. Fill in the **Authorization** fields with your GitHub username and the personal access token as **Password**.
|
||||||
|
@ -91,10 +91,10 @@ The repository pushes shortly thereafter. To force a push, select the **Synchron
|
||||||
|
|
||||||
### Mirror an existing ssh repository
|
### Mirror an existing ssh repository
|
||||||
|
|
||||||
Currently gitea supports no ssh push mirrors. You can work around this by adding a `post-receive` hook to your gitea repository that pushes manually.
|
Currently Gitea supports no ssh push mirrors. You can work around this by adding a `post-receive` hook to your Gitea repository that pushes manually.
|
||||||
|
|
||||||
1. Make sure the user running gitea has access to the git repo you are trying to mirror to from shell.
|
1. Make sure the user running Gitea has access to the git repo you are trying to mirror to from shell.
|
||||||
2. On the Webinterface at the repository settings > git hooks add a post-receive hook for the mirror. I.e.
|
2. On the web interface at the repository settings > git hooks add a post-receive hook for the mirror. I.e.
|
||||||
|
|
||||||
```
|
```
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1715534503,
|
||||||
|
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
37
flake.nix
Normal file
37
flake.nix
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
outputs =
|
||||||
|
{ nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
# generic
|
||||||
|
git
|
||||||
|
git-lfs
|
||||||
|
gnumake
|
||||||
|
gnused
|
||||||
|
gnutar
|
||||||
|
gzip
|
||||||
|
|
||||||
|
# frontend
|
||||||
|
nodejs_20
|
||||||
|
|
||||||
|
# linting
|
||||||
|
python312
|
||||||
|
poetry
|
||||||
|
|
||||||
|
# backend
|
||||||
|
go_1_22
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
25
go.mod
25
go.mod
|
@ -8,7 +8,7 @@ require (
|
||||||
code.gitea.io/sdk/gitea v0.17.1
|
code.gitea.io/sdk/gitea v0.17.1
|
||||||
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
|
||||||
connectrpc.com/connect v1.15.0
|
connectrpc.com/connect v1.15.0
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
|
||||||
gitea.com/go-chi/cache v0.2.0
|
gitea.com/go-chi/cache v0.2.0
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
|
||||||
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
|
||||||
|
@ -56,7 +56,7 @@ require (
|
||||||
github.com/google/go-github/v57 v57.0.0
|
github.com/google/go-github/v57 v57.0.0
|
||||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/feeds v1.1.2
|
github.com/gorilla/feeds v1.2.0
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
|
@ -104,13 +104,13 @@ require (
|
||||||
github.com/yuin/goldmark v1.7.0
|
github.com/yuin/goldmark v1.7.0
|
||||||
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.22.0
|
golang.org/x/crypto v0.23.0
|
||||||
golang.org/x/image v0.15.0
|
golang.org/x/image v0.18.0
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/net v0.25.0
|
||||||
golang.org/x/oauth2 v0.18.0
|
golang.org/x/oauth2 v0.18.0
|
||||||
golang.org/x/sys v0.19.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.16.0
|
||||||
golang.org/x/tools v0.19.0
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||||
google.golang.org/grpc v1.62.1
|
google.golang.org/grpc v1.62.1
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.33.0
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
|
@ -210,7 +210,7 @@ require (
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
@ -288,8 +288,8 @@ require (
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
|
||||||
golang.org/x/mod v0.16.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||||
|
@ -304,7 +304,8 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
||||||
|
|
||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
|
replace github.com/nektos/act => gitea.com/gitea/act v0.259.1
|
||||||
|
|
||||||
replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5
|
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
|
||||||
|
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
|
||||||
|
|
56
go.sum
56
go.sum
|
@ -20,8 +20,8 @@ git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4H
|
||||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||||
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw=
|
||||||
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028 h1:6/QAx4+s0dyRwdaTFPTnhGppuiuu0OqxIH9szyTpvKw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
|
||||||
gitea.com/go-chi/binding v0.0.0-20240316035258-17450c5f3028/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
|
||||||
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
|
||||||
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
|
||||||
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
|
||||||
|
@ -88,6 +88,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
|
github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58PaB6aA=
|
||||||
|
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
@ -418,6 +420,8 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||||
|
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
|
||||||
|
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
||||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
@ -432,11 +436,10 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
@ -554,8 +557,6 @@ github.com/meilisearch/meilisearch-go v0.26.2 h1:3gTlmiV1dHHumVUhYdJbvh3camiNiyq
|
||||||
github.com/meilisearch/meilisearch-go v0.26.2/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
github.com/meilisearch/meilisearch-go v0.26.2/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
||||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
|
||||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||||
github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
|
github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs=
|
||||||
|
@ -799,8 +800,6 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||||
github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 h1:3seWKGVhGoc66Ht5QlhQsr4xT2caDnFegsnh2NqvENU=
|
|
||||||
github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
|
||||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
||||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
|
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
@ -854,20 +853,20 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
@ -888,8 +887,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -900,8 +899,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
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/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=
|
||||||
|
@ -939,8 +938,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
|
@ -950,8 +949,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -963,8 +962,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.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=
|
||||||
|
@ -979,8 +979,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -524,7 +524,12 @@ func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.RequestedRepo != nil {
|
if opts.RequestedRepo != nil {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RequestedRepo.ID})
|
// repo's actions could have duplicate items, see the comment of NotifyWatchers
|
||||||
|
// so here we only filter the "original items", aka: user_id == act_user_id
|
||||||
|
cond = cond.And(
|
||||||
|
builder.Eq{"`action`.repo_id": opts.RequestedRepo.ID},
|
||||||
|
builder.Expr("`action`.user_id = `action`.act_user_id"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.RequestedTeam != nil {
|
if opts.RequestedTeam != nil {
|
||||||
|
@ -577,6 +582,10 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyWatchers creates batch of actions for every watcher.
|
// NotifyWatchers creates batch of actions for every watcher.
|
||||||
|
// It could insert duplicate actions for a repository action, like this:
|
||||||
|
// * Original action: UserID=1 (the real actor), ActUserID=1
|
||||||
|
// * Organization action: UserID=100 (the repo's org), ActUserID=1
|
||||||
|
// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1
|
||||||
func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
||||||
var watchers []*repo_model.Watch
|
var watchers []*repo_model.Watch
|
||||||
var repo *repo_model.Repository
|
var repo *repo_model.Repository
|
||||||
|
|
|
@ -318,3 +318,24 @@ func TestDeleteIssueActions(t *testing.T) {
|
||||||
assert.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index))
|
assert.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index))
|
||||||
unittest.AssertCount(t, &activities_model.Action{}, 0)
|
unittest.AssertCount(t, &activities_model.Action{}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepoActions(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
_ = db.TruncateBeans(db.DefaultContext, &activities_model.Action{})
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
_ = db.Insert(db.DefaultContext, &activities_model.Action{
|
||||||
|
UserID: 2 + int64(i),
|
||||||
|
ActUserID: 2,
|
||||||
|
RepoID: repo.ID,
|
||||||
|
OpType: activities_model.ActionCommentIssue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
count, _ := db.Count[activities_model.Action](db.DefaultContext, &db.ListOptions{})
|
||||||
|
assert.EqualValues(t, 3, count)
|
||||||
|
actions, _, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
|
||||||
|
RequestedRepo: repo,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, actions, 1)
|
||||||
|
}
|
||||||
|
|
|
@ -286,13 +286,14 @@ type UserIDCount struct {
|
||||||
Count int64
|
Count int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUIDsAndNotificationCounts between the two provided times
|
// GetUIDsAndNotificationCounts returns the unread counts for every user between the two provided times.
|
||||||
|
// It must return all user IDs which appear during the period, including count=0 for users who have read all.
|
||||||
func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.TimeStamp) ([]UserIDCount, error) {
|
func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.TimeStamp) ([]UserIDCount, error) {
|
||||||
sql := `SELECT user_id, count(*) AS count FROM notification ` +
|
sql := `SELECT user_id, sum(case when status= ? then 1 else 0 end) AS count FROM notification ` +
|
||||||
`WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` +
|
`WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` +
|
||||||
`updated_unix < ?) AND status = ? GROUP BY user_id`
|
`updated_unix < ?) GROUP BY user_id`
|
||||||
var res []UserIDCount
|
var res []UserIDCount
|
||||||
return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res)
|
return res, db.GetEngine(ctx).SQL(sql, NotificationStatusUnread, since, until).Find(&res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIssueReadBy sets issue to be read by given user.
|
// SetIssueReadBy sets issue to be read by given user.
|
||||||
|
|
|
@ -57,6 +57,7 @@ type Engine interface {
|
||||||
SumInt(bean any, columnName string) (res int64, err error)
|
SumInt(bean any, columnName string) (res int64, err error)
|
||||||
Sync(...any) error
|
Sync(...any) error
|
||||||
Select(string) *xorm.Session
|
Select(string) *xorm.Session
|
||||||
|
SetExpr(string, any) *xorm.Session
|
||||||
NotIn(string, ...any) *xorm.Session
|
NotIn(string, ...any) *xorm.Session
|
||||||
OrderBy(any, ...any) *xorm.Session
|
OrderBy(any, ...any) *xorm.Session
|
||||||
Exist(...any) (bool, error)
|
Exist(...any) (bool, error)
|
||||||
|
|
|
@ -45,3 +45,39 @@
|
||||||
is_deleted: false
|
is_deleted: false
|
||||||
deleted_by_id: 0
|
deleted_by_id: 0
|
||||||
deleted_unix: 0
|
deleted_unix: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 5
|
||||||
|
repo_id: 10
|
||||||
|
name: 'master'
|
||||||
|
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||||
|
commit_message: 'Initial commit'
|
||||||
|
commit_time: 1489927679
|
||||||
|
pusher_id: 12
|
||||||
|
is_deleted: false
|
||||||
|
deleted_by_id: 0
|
||||||
|
deleted_unix: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 6
|
||||||
|
repo_id: 10
|
||||||
|
name: 'outdated-new-branch'
|
||||||
|
commit_id: 'cb24c347e328d83c1e0c3c908a6b2c0a2fcb8a3d'
|
||||||
|
commit_message: 'add'
|
||||||
|
commit_time: 1489927679
|
||||||
|
pusher_id: 12
|
||||||
|
is_deleted: false
|
||||||
|
deleted_by_id: 0
|
||||||
|
deleted_unix: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 14
|
||||||
|
repo_id: 11
|
||||||
|
name: 'master'
|
||||||
|
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
|
||||||
|
commit_message: 'Initial commit'
|
||||||
|
commit_time: 1489927679
|
||||||
|
pusher_id: 13
|
||||||
|
is_deleted: false
|
||||||
|
deleted_by_id: 0
|
||||||
|
deleted_unix: 0
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
-
|
-
|
||||||
group_id: 1
|
group_id: 1
|
||||||
max_index: 5
|
max_index: 5
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 2
|
group_id: 2
|
||||||
max_index: 2
|
max_index: 2
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 3
|
group_id: 3
|
||||||
max_index: 2
|
max_index: 2
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 10
|
group_id: 10
|
||||||
max_index: 1
|
max_index: 1
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 32
|
group_id: 32
|
||||||
max_index: 2
|
max_index: 2
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 48
|
group_id: 48
|
||||||
max_index: 1
|
max_index: 1
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 42
|
group_id: 42
|
||||||
max_index: 1
|
max_index: 1
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 50
|
group_id: 50
|
||||||
max_index: 1
|
max_index: 1
|
||||||
|
|
||||||
-
|
-
|
||||||
group_id: 51
|
group_id: 51
|
||||||
max_index: 1
|
max_index: 1
|
||||||
|
|
|
@ -117,3 +117,15 @@
|
||||||
uid: 40
|
uid: 40
|
||||||
org_id: 41
|
org_id: 41
|
||||||
is_public: true
|
is_public: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 21
|
||||||
|
uid: 12
|
||||||
|
org_id: 25
|
||||||
|
is_public: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 22
|
||||||
|
uid: 2
|
||||||
|
org_id: 35
|
||||||
|
is_public: true
|
||||||
|
|
24
models/fixtures/protected_tag.yml
Normal file
24
models/fixtures/protected_tag.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
repo_id: 4
|
||||||
|
name_pattern: /v.+/
|
||||||
|
allowlist_user_i_ds: []
|
||||||
|
allowlist_team_i_ds: []
|
||||||
|
created_unix: 1715596037
|
||||||
|
updated_unix: 1715596037
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
repo_id: 1
|
||||||
|
name_pattern: v-*
|
||||||
|
allowlist_user_i_ds: []
|
||||||
|
allowlist_team_i_ds: []
|
||||||
|
created_unix: 1715596037
|
||||||
|
updated_unix: 1715596037
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
repo_id: 1
|
||||||
|
name_pattern: v-1.1
|
||||||
|
allowlist_user_i_ds: [2]
|
||||||
|
allowlist_team_i_ds: []
|
||||||
|
created_unix: 1715596037
|
||||||
|
updated_unix: 1715596037
|
|
@ -327,7 +327,7 @@
|
||||||
is_archived: false
|
is_archived: false
|
||||||
is_mirror: false
|
is_mirror: false
|
||||||
status: 0
|
status: 0
|
||||||
is_fork: false
|
is_fork: true
|
||||||
fork_id: 10
|
fork_id: 10
|
||||||
is_template: false
|
is_template: false
|
||||||
template_id: 0
|
template_id: 0
|
||||||
|
|
|
@ -239,3 +239,25 @@
|
||||||
num_members: 2
|
num_members: 2
|
||||||
includes_all_repositories: false
|
includes_all_repositories: false
|
||||||
can_create_org_repo: false
|
can_create_org_repo: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 23
|
||||||
|
org_id: 25
|
||||||
|
lower_name: owners
|
||||||
|
name: Owners
|
||||||
|
authorize: 4 # owner
|
||||||
|
num_repos: 0
|
||||||
|
num_members: 1
|
||||||
|
includes_all_repositories: false
|
||||||
|
can_create_org_repo: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 24
|
||||||
|
org_id: 35
|
||||||
|
lower_name: team24
|
||||||
|
name: team24
|
||||||
|
authorize: 2 # write
|
||||||
|
num_repos: 0
|
||||||
|
num_members: 1
|
||||||
|
includes_all_repositories: true
|
||||||
|
can_create_org_repo: false
|
||||||
|
|
|
@ -322,3 +322,21 @@
|
||||||
team_id: 22
|
team_id: 22
|
||||||
type: 3
|
type: 3
|
||||||
access_mode: 1
|
access_mode: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 55
|
||||||
|
team_id: 18
|
||||||
|
type: 1 # code
|
||||||
|
access_mode: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 56
|
||||||
|
team_id: 23
|
||||||
|
type: 1 # code
|
||||||
|
access_mode: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 57
|
||||||
|
team_id: 24
|
||||||
|
type: 1 # code
|
||||||
|
access_mode: 2
|
||||||
|
|
|
@ -147,3 +147,15 @@
|
||||||
org_id: 41
|
org_id: 41
|
||||||
team_id: 22
|
team_id: 22
|
||||||
uid: 39
|
uid: 39
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 26
|
||||||
|
org_id: 25
|
||||||
|
team_id: 23
|
||||||
|
uid: 12
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 27
|
||||||
|
org_id: 35
|
||||||
|
team_id: 24
|
||||||
|
uid: 2
|
||||||
|
|
|
@ -918,8 +918,8 @@
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_teams: 1
|
num_teams: 2
|
||||||
num_members: 1
|
num_members: 2
|
||||||
visibility: 0
|
visibility: 0
|
||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
|
@ -1289,8 +1289,8 @@
|
||||||
num_following: 0
|
num_following: 0
|
||||||
num_stars: 0
|
num_stars: 0
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_teams: 1
|
num_teams: 2
|
||||||
num_members: 1
|
num_members: 2
|
||||||
visibility: 2
|
visibility: 2
|
||||||
repo_admin_change_team_access: false
|
repo_admin_change_team_access: false
|
||||||
theme: ""
|
theme: ""
|
||||||
|
|
|
@ -10,9 +10,11 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"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/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/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -103,6 +105,7 @@ func (err ErrBranchesEqual) Unwrap() error {
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
ID int64
|
ID int64
|
||||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
Repo *repo_model.Repository `xorm:"-"`
|
||||||
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
|
||||||
CommitID string
|
CommitID string
|
||||||
CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
|
CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
|
||||||
|
@ -139,6 +142,14 @@ func (b *Branch) LoadPusher(ctx context.Context) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Branch) LoadRepo(ctx context.Context) (err error) {
|
||||||
|
if b.Repo != nil || b.RepoID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b.Repo, err = repo_model.GetRepositoryByID(ctx, b.RepoID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Branch))
|
db.RegisterModel(new(Branch))
|
||||||
db.RegisterModel(new(RenamedBranch))
|
db.RegisterModel(new(RenamedBranch))
|
||||||
|
@ -400,24 +411,111 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created
|
type FindRecentlyPushedNewBranchesOptions struct {
|
||||||
// except the indicate branch
|
Repo *repo_model.Repository
|
||||||
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
|
BaseRepo *repo_model.Repository
|
||||||
branches := make(BranchList, 0, 2)
|
CommitAfterUnix int64
|
||||||
subQuery := builder.Select("head_branch").From("pull_request").
|
MaxCount int
|
||||||
InnerJoin("issue", "issue.id = pull_request.issue_id").
|
}
|
||||||
Where(builder.Eq{
|
|
||||||
"pull_request.head_repo_id": repoID,
|
type RecentlyPushedNewBranch struct {
|
||||||
"issue.is_closed": false,
|
BranchDisplayName string
|
||||||
})
|
BranchLink string
|
||||||
err := db.GetEngine(ctx).
|
BranchCompareURL string
|
||||||
Where("pusher_id=? AND is_deleted=?", userID, false).
|
CommitTime timeutil.TimeStamp
|
||||||
And("name <> ?", excludeBranchName).
|
}
|
||||||
And("repo_id = ?", repoID).
|
|
||||||
And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()).
|
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 2 hours which has no opened PRs created
|
||||||
NotIn("name", subQuery).
|
// if opts.CommitAfterUnix is 0, we will find the branches that were committed to in the last 2 hours
|
||||||
OrderBy("branch.commit_time DESC").
|
// if opts.ListOptions is not set, we will only display top 2 latest branch
|
||||||
Limit(2).
|
func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, opts *FindRecentlyPushedNewBranchesOptions) ([]*RecentlyPushedNewBranch, error) {
|
||||||
Find(&branches)
|
if doer == nil {
|
||||||
return branches, err
|
return []*RecentlyPushedNewBranch{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all related repo ids
|
||||||
|
repoOpts := repo_model.SearchRepoOptions{
|
||||||
|
Actor: doer,
|
||||||
|
Private: true,
|
||||||
|
AllPublic: false, // Include also all public repositories of users and public organisations
|
||||||
|
AllLimited: false, // Include also all public repositories of limited organisations
|
||||||
|
Fork: optional.Some(true),
|
||||||
|
ForkFrom: opts.BaseRepo.ID,
|
||||||
|
Archived: optional.Some(false),
|
||||||
|
}
|
||||||
|
repoCond := repo_model.SearchRepositoryCondition(&repoOpts).And(repo_model.AccessibleRepositoryCondition(doer, unit.TypeCode))
|
||||||
|
if opts.Repo.ID == opts.BaseRepo.ID {
|
||||||
|
// should also include the base repo's branches
|
||||||
|
repoCond = repoCond.Or(builder.Eq{"id": opts.BaseRepo.ID})
|
||||||
|
} else {
|
||||||
|
// in fork repo, we only detect the fork repo's branch
|
||||||
|
repoCond = repoCond.And(builder.Eq{"id": opts.Repo.ID})
|
||||||
|
}
|
||||||
|
repoIDs := builder.Select("id").From("repository").Where(repoCond)
|
||||||
|
|
||||||
|
if opts.CommitAfterUnix == 0 {
|
||||||
|
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all related branches, these branches may already created PRs, we will check later
|
||||||
|
var branches []*Branch
|
||||||
|
if err := db.GetEngine(ctx).
|
||||||
|
Where(builder.And(
|
||||||
|
builder.Eq{
|
||||||
|
"pusher_id": doer.ID,
|
||||||
|
"is_deleted": false,
|
||||||
|
},
|
||||||
|
builder.Gte{"commit_time": opts.CommitAfterUnix},
|
||||||
|
builder.In("repo_id", repoIDs),
|
||||||
|
// newly created branch have no changes, so skip them
|
||||||
|
builder.Neq{"commit_id": baseBranch.CommitID},
|
||||||
|
)).
|
||||||
|
OrderBy(db.SearchOrderByRecentUpdated.String()).
|
||||||
|
Find(&branches); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
|
||||||
|
if opts.MaxCount == 0 {
|
||||||
|
// by default we display 2 recently pushed new branch
|
||||||
|
opts.MaxCount = 2
|
||||||
|
}
|
||||||
|
for _, branch := range branches {
|
||||||
|
// whether branch have already created PR
|
||||||
|
count, err := db.GetEngine(ctx).Table("pull_request").
|
||||||
|
// we should not only use branch name here, because if there are branches with same name in other repos,
|
||||||
|
// we can not detect them correctly
|
||||||
|
Where(builder.Eq{"head_repo_id": branch.RepoID, "head_branch": branch.Name}).Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no PR, we add to the result
|
||||||
|
if count == 0 {
|
||||||
|
if err := branch.LoadRepo(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
branchDisplayName := branch.Name
|
||||||
|
if branch.Repo.ID != opts.BaseRepo.ID && branch.Repo.ID != opts.Repo.ID {
|
||||||
|
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||||
|
}
|
||||||
|
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||||
|
BranchDisplayName: branchDisplayName,
|
||||||
|
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||||
|
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||||
|
CommitTime: branch.CommitTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(newBranches) == opts.MaxCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBranches, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
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/container"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
@ -59,6 +60,24 @@ func (branches BranchList) LoadPusher(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (branches BranchList) LoadRepo(ctx context.Context) error {
|
||||||
|
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||||
|
return branch.RepoID, branch.RepoID > 0 && branch.Repo == nil
|
||||||
|
})
|
||||||
|
|
||||||
|
reposMap := make(map[int64]*repo_model.Repository, len(ids))
|
||||||
|
if err := db.GetEngine(ctx).In("id", ids).Find(&reposMap); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, branch := range branches {
|
||||||
|
if branch.RepoID <= 0 || branch.Repo != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
branch.Repo = reposMap[branch.RepoID]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type FindBranchOptions struct {
|
type FindBranchOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
@ -88,17 +107,13 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
|
||||||
|
|
||||||
func (opts FindBranchOptions) ToOrders() string {
|
func (opts FindBranchOptions) ToOrders() string {
|
||||||
orderBy := opts.OrderBy
|
orderBy := opts.OrderBy
|
||||||
if opts.IsDeletedBranch.ValueOrDefault(true) { // if deleted branch included, put them at the end
|
|
||||||
if orderBy != "" {
|
|
||||||
orderBy += ", "
|
|
||||||
}
|
|
||||||
orderBy += "is_deleted ASC"
|
|
||||||
}
|
|
||||||
if orderBy == "" {
|
if orderBy == "" {
|
||||||
// the commit_time might be the same, so add the "name" to make sure the order is stable
|
// the commit_time might be the same, so add the "name" to make sure the order is stable
|
||||||
return "commit_time DESC, name ASC"
|
orderBy = "commit_time DESC, name ASC"
|
||||||
|
}
|
||||||
|
if opts.IsDeletedBranch.ValueOrDefault(true) { // if deleted branch included, put them at the beginning
|
||||||
|
orderBy = "is_deleted ASC, " + orderBy
|
||||||
}
|
}
|
||||||
|
|
||||||
return orderBy
|
return orderBy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -397,38 +397,18 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
|
||||||
|
|
||||||
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
|
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
|
||||||
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
|
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
|
||||||
type result struct {
|
|
||||||
Index int64
|
|
||||||
SHA string
|
|
||||||
}
|
|
||||||
getBase := func() *xorm.Session {
|
|
||||||
return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
|
|
||||||
}
|
|
||||||
|
|
||||||
start := timeutil.TimeStampNow().AddDuration(-before)
|
start := timeutil.TimeStampNow().AddDuration(-before)
|
||||||
results := make([]result, 0, 10)
|
|
||||||
|
|
||||||
sess := getBase().And("updated_unix >= ?", start).
|
var contexts []string
|
||||||
Select("max( `index` ) as `index`, sha").
|
if err := db.GetEngine(ctx).Table("commit_status").
|
||||||
GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
|
Where("repo_id = ?", repoID).And("updated_unix >= ?", start).
|
||||||
|
Cols("context").Distinct().Find(&contexts); err != nil {
|
||||||
err := sess.Find(&results)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts := make([]string, 0, len(results))
|
|
||||||
if len(results) == 0 {
|
|
||||||
return contexts, nil
|
return contexts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
conds := make([]builder.Cond, 0, len(results))
|
|
||||||
for _, result := range results {
|
|
||||||
conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
|
|
||||||
}
|
|
||||||
return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommitStatusOptions holds options for creating a CommitStatus
|
// NewCommitStatusOptions holds options for creating a CommitStatus
|
||||||
type NewCommitStatusOptions struct {
|
type NewCommitStatusOptions struct {
|
||||||
Repo *repo_model.Repository
|
Repo *repo_model.Repository
|
||||||
|
|
|
@ -5,11 +5,15 @@ package git_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
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"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -175,3 +179,55 @@ func Test_CalcCommitStatus(t *testing.T) {
|
||||||
assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
|
assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commit, err := gitRepo.GetBranchCommit(repo2.DefaultBranch)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_, err := db.DeleteByBean(db.DefaultContext, &git_model.CommitStatus{
|
||||||
|
RepoID: repo2.ID,
|
||||||
|
CreatorID: user2.ID,
|
||||||
|
SHA: commit.ID.String(),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
|
||||||
|
Repo: repo2,
|
||||||
|
Creator: user2,
|
||||||
|
SHA: commit.ID,
|
||||||
|
CommitStatus: &git_model.CommitStatus{
|
||||||
|
State: structs.CommitStatusFailure,
|
||||||
|
TargetURL: "https://example.com/tests/",
|
||||||
|
Context: "compliance/lint-backend",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
|
||||||
|
Repo: repo2,
|
||||||
|
Creator: user2,
|
||||||
|
SHA: commit.ID,
|
||||||
|
CommitStatus: &git_model.CommitStatus{
|
||||||
|
State: structs.CommitStatusSuccess,
|
||||||
|
TargetURL: "https://example.com/tests/",
|
||||||
|
Context: "compliance/lint-backend",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contexts, err := git_model.FindRepoRecentCommitStatusContexts(db.DefaultContext, repo2.ID, time.Hour)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, contexts, 1) {
|
||||||
|
assert.Equal(t, "compliance/lint-backend", contexts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadProject load the project the issue was assigned to
|
// LoadProject load the project the issue was assigned to
|
||||||
|
@ -90,22 +90,10 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
|
||||||
return issuesMap, nil
|
return issuesMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeProjectAssign changes the project associated with an issue
|
// IssueAssignOrRemoveProject changes the project associated with an issue
|
||||||
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
// If newProjectID is 0, the issue is removed from the project
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
|
||||||
if err != nil {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
|
||||||
oldProjectID := issue.projectID(ctx)
|
oldProjectID := issue.projectID(ctx)
|
||||||
|
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
if err := issue.LoadRepo(ctx); err != nil {
|
||||||
|
@ -118,8 +106,15 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
|
if !newProject.CanBeAccessedByOwnerRepo(issue.Repo.OwnerID, issue.Repo) {
|
||||||
return fmt.Errorf("issue's repository is not the same as project's repository")
|
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
|
||||||
|
}
|
||||||
|
if newColumnID == 0 {
|
||||||
|
newDefaultColumn, err := newProject.GetDefaultBoard(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newColumnID = newDefaultColumn.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +134,29 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if newProjectID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if newColumnID == 0 {
|
||||||
|
panic("newColumnID must not be zero") // shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
MaxSorting int64
|
||||||
|
IssueCount int64
|
||||||
|
}{}
|
||||||
|
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").Table("project_issue").
|
||||||
|
Where("project_id=?", newProjectID).
|
||||||
|
And("project_board_id=?", newColumnID).
|
||||||
|
Get(&res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
|
||||||
return db.Insert(ctx, &project_model.ProjectIssue{
|
return db.Insert(ctx, &project_model.ProjectIssue{
|
||||||
IssueID: issue.ID,
|
IssueID: issue.ID,
|
||||||
ProjectID: newProjectID,
|
ProjectID: newProjectID,
|
||||||
|
ProjectBoardID: newColumnID,
|
||||||
|
Sorting: newSorting,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIssueByAPI updates all allowed fields of given issue.
|
|
||||||
// If the issue status is changed a statusChangeComment is returned
|
|
||||||
// similarly if the title is changed the titleChanged bool is set to true
|
|
||||||
func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
|
||||||
return nil, false, fmt.Errorf("loadRepo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the issue
|
|
||||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
|
|
||||||
"name", "content", "milestone_id", "priority",
|
|
||||||
"deadline_unix", "updated_unix", "is_locked").
|
|
||||||
Update(issue); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
titleChanged = currentIssue.Title != issue.Title
|
|
||||||
if titleChanged {
|
|
||||||
opts := &CreateCommentOptions{
|
|
||||||
Type: CommentTypeChangeTitle,
|
|
||||||
Doer: doer,
|
|
||||||
Repo: issue.Repo,
|
|
||||||
Issue: issue,
|
|
||||||
OldTitle: currentIssue.Title,
|
|
||||||
NewTitle: issue.Title,
|
|
||||||
}
|
|
||||||
_, err := CreateComment(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("createComment: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentIssue.IsClosed != issue.IsClosed {
|
|
||||||
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
return statusChangeComment, titleChanged, committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
||||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||||
// if the deadline hasn't changed do nothing
|
// if the deadline hasn't changed do nothing
|
||||||
|
|
|
@ -430,6 +430,21 @@ func (pr *PullRequest) GetGitHeadBranchRefName() string {
|
||||||
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
|
return fmt.Sprintf("%s%s", git.BranchPrefix, pr.HeadBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReviewCommentsCount returns the number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||||
|
func (pr *PullRequest) GetReviewCommentsCount(ctx context.Context) int {
|
||||||
|
opts := FindCommentsOptions{
|
||||||
|
Type: CommentTypeReview,
|
||||||
|
IssueID: pr.IssueID,
|
||||||
|
}
|
||||||
|
conds := opts.ToConds()
|
||||||
|
|
||||||
|
count, err := db.GetEngine(ctx).Where(conds).Count(new(Comment))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
// IsChecking returns true if this pull request is still checking conflict.
|
// IsChecking returns true if this pull request is still checking conflict.
|
||||||
func (pr *PullRequest) IsChecking() bool {
|
func (pr *PullRequest) IsChecking() bool {
|
||||||
return pr.Status == PullRequestStatusChecking
|
return pr.Status == PullRequestStatusChecking
|
||||||
|
|
|
@ -155,14 +155,14 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
|
||||||
if r.CodeComments != nil {
|
if r.CodeComments != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = r.loadIssue(ctx); err != nil {
|
if err = r.LoadIssue(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Review) loadIssue(ctx context.Context) (err error) {
|
func (r *Review) LoadIssue(ctx context.Context) (err error) {
|
||||||
if r.Issue != nil {
|
if r.Issue != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ func (r *Review) LoadReviewerTeam(ctx context.Context) (err error) {
|
||||||
|
|
||||||
// LoadAttributes loads all attributes except CodeComments
|
// LoadAttributes loads all attributes except CodeComments
|
||||||
func (r *Review) LoadAttributes(ctx context.Context) (err error) {
|
func (r *Review) LoadAttributes(ctx context.Context) (err error) {
|
||||||
if err = r.loadIssue(ctx); err != nil {
|
if err = r.LoadIssue(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = r.LoadCodeComments(ctx); err != nil {
|
if err = r.LoadCodeComments(ctx); err != nil {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
-
|
-
|
||||||
id: 1
|
id: 1
|
||||||
|
user_id: 1
|
||||||
|
pull_id: 1
|
||||||
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
commit_sha: 19fe5caf872476db265596eaac1dc35ad1c6422d
|
||||||
|
|
|
@ -36,9 +36,9 @@ func expandHashReferencesToSha256(x *xorm.Engine) error {
|
||||||
if setting.Database.Type.IsMSSQL() {
|
if setting.Database.Type.IsMSSQL() {
|
||||||
// drop indexes that need to be re-created afterwards
|
// drop indexes that need to be re-created afterwards
|
||||||
droppedIndexes := []string{
|
droppedIndexes := []string{
|
||||||
"DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]",
|
"DROP INDEX [IDX_commit_status_context_hash] ON [commit_status]",
|
||||||
"DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]",
|
"DROP INDEX [UQE_review_state_pull_commit_user] ON [review_state]",
|
||||||
"DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]",
|
"DROP INDEX [UQE_repo_archiver_s] ON [repo_archiver]",
|
||||||
}
|
}
|
||||||
for _, s := range droppedIndexes {
|
for _, s := range droppedIndexes {
|
||||||
_, err := db.Exec(s)
|
_, err := db.Exec(s)
|
||||||
|
@ -92,7 +92,7 @@ func addObjectFormatNameToRepository(x *xorm.Engine) error {
|
||||||
|
|
||||||
// Here to catch weird edge-cases where column constraints above are
|
// Here to catch weird edge-cases where column constraints above are
|
||||||
// not applied by the DB backend
|
// not applied by the DB backend
|
||||||
_, err := x.Exec("UPDATE repository set object_format_name = 'sha1' WHERE object_format_name = '' or object_format_name IS NULL")
|
_, err := x.Exec("UPDATE `repository` set `object_format_name` = 'sha1' WHERE `object_format_name` = '' or `object_format_name` IS NULL")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,21 +19,21 @@ func PrepareOldRepository(t *testing.T) (*xorm.Engine, func()) {
|
||||||
|
|
||||||
type CommitStatus struct {
|
type CommitStatus struct {
|
||||||
ID int64
|
ID int64
|
||||||
ContextHash string
|
ContextHash string `xorm:"char(40) index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoArchiver struct {
|
type RepoArchiver struct {
|
||||||
ID int64
|
ID int64
|
||||||
RepoID int64
|
RepoID int64 `xorm:"index unique(s)"`
|
||||||
Type int
|
Type int `xorm:"unique(s)"`
|
||||||
CommitID string
|
CommitID string `xorm:"VARCHAR(40) unique(s)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReviewState struct {
|
type ReviewState struct {
|
||||||
ID int64
|
ID int64
|
||||||
CommitSHA string
|
UserID int64 `xorm:"NOT NULL UNIQUE(pull_commit_user)"`
|
||||||
UserID int64
|
PullID int64 `xorm:"NOT NULL INDEX UNIQUE(pull_commit_user) DEFAULT 0"`
|
||||||
PullID int64
|
CommitSHA string `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
|
|
|
@ -81,7 +81,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
||||||
{3, map[int64]bool{2: true, 4: false, 28: true}},
|
{3, map[int64]bool{2: true, 4: false, 28: true}},
|
||||||
{6, map[int64]bool{5: true, 28: true}},
|
{6, map[int64]bool{5: true, 28: true}},
|
||||||
{7, map[int64]bool{5: false}},
|
{7, map[int64]bool{5: false}},
|
||||||
{25, map[int64]bool{24: true}},
|
{25, map[int64]bool{12: true, 24: true}},
|
||||||
{22, map[int64]bool{}},
|
{22, map[int64]bool{}},
|
||||||
}
|
}
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
|
@ -108,7 +108,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
||||||
{3, map[int64]bool{2: true, 4: false, 28: false}},
|
{3, map[int64]bool{2: true, 4: false, 28: false}},
|
||||||
{6, map[int64]bool{5: true, 28: false}},
|
{6, map[int64]bool{5: true, 28: false}},
|
||||||
{7, map[int64]bool{5: true}},
|
{7, map[int64]bool{5: true}},
|
||||||
{25, map[int64]bool{24: false}}, // ErrTeamNotExist
|
{25, map[int64]bool{12: true, 24: false}}, // ErrTeamNotExist
|
||||||
{22, map[int64]bool{}}, // No member
|
{22, map[int64]bool{}}, // No member
|
||||||
}
|
}
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
|
|
|
@ -5,12 +5,14 @@ package project
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -82,6 +84,17 @@ func (b *Board) NumIssues(ctx context.Context) int {
|
||||||
return int(c)
|
return int(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Board) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
||||||
|
issues := make([]*ProjectIssue, 0, 5)
|
||||||
|
if err := db.GetEngine(ctx).Where("project_id=?", b.ProjectID).
|
||||||
|
And("project_board_id=?", b.ID).
|
||||||
|
OrderBy("sorting, id").
|
||||||
|
Find(&issues); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return issues, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Board))
|
db.RegisterModel(new(Board))
|
||||||
}
|
}
|
||||||
|
@ -150,12 +163,27 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
|
||||||
return db.Insert(ctx, boards)
|
return db.Insert(ctx, boards)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maxProjectColumns max columns allowed in a project, this should not bigger than 127
|
||||||
|
// because sorting is int8 in database
|
||||||
|
const maxProjectColumns = 20
|
||||||
|
|
||||||
// NewBoard adds a new project board to a given project
|
// NewBoard adds a new project board to a given project
|
||||||
func NewBoard(ctx context.Context, board *Board) error {
|
func NewBoard(ctx context.Context, board *Board) error {
|
||||||
if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
|
if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
|
||||||
return fmt.Errorf("bad color code: %s", board.Color)
|
return fmt.Errorf("bad color code: %s", board.Color)
|
||||||
}
|
}
|
||||||
|
res := struct {
|
||||||
|
MaxSorting int64
|
||||||
|
ColumnCount int64
|
||||||
|
}{}
|
||||||
|
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as column_count").Table("project_board").
|
||||||
|
Where("project_id=?", board.ProjectID).Get(&res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.ColumnCount >= maxProjectColumns {
|
||||||
|
return fmt.Errorf("NewBoard: maximum number of columns reached")
|
||||||
|
}
|
||||||
|
board.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0))
|
||||||
_, err := db.GetEngine(ctx).Insert(board)
|
_, err := db.GetEngine(ctx).Insert(board)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -189,7 +217,17 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
|
||||||
return fmt.Errorf("deleteBoardByID: cannot delete default board")
|
return fmt.Errorf("deleteBoardByID: cannot delete default board")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = board.removeIssues(ctx); err != nil {
|
// move all issues to the default column
|
||||||
|
project, err := GetProjectByID(ctx, board.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defaultColumn, err := project.GetDefaultBoard(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = board.moveIssuesToAnotherColumn(ctx, defaultColumn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,21 +280,15 @@ func UpdateBoard(ctx context.Context, board *Board) error {
|
||||||
// GetBoards fetches all boards related to a project
|
// GetBoards fetches all boards related to a project
|
||||||
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||||
boards := make([]*Board, 0, 5)
|
boards := make([]*Board, 0, 5)
|
||||||
|
if err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Find(&boards); err != nil {
|
||||||
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultB, err := p.getDefaultBoard(ctx)
|
return boards, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return append([]*Board{defaultB}, boards...), nil
|
// GetDefaultBoard return default board and ensure only one exists
|
||||||
}
|
func (p *Project) GetDefaultBoard(ctx context.Context) (*Board, error) {
|
||||||
|
|
||||||
// getDefaultBoard return default board and ensure only one exists
|
|
||||||
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
|
|
||||||
var board Board
|
var board Board
|
||||||
has, err := db.GetEngine(ctx).
|
has, err := db.GetEngine(ctx).
|
||||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||||
|
@ -316,3 +348,42 @@ func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (BoardList, error) {
|
||||||
|
columns := make([]*Board, 0, 5)
|
||||||
|
if err := db.GetEngine(ctx).
|
||||||
|
Where("project_id =?", projectID).
|
||||||
|
In("id", columnsIDs).
|
||||||
|
OrderBy("sorting").Find(&columns); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return columns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveColumnsOnProject sorts columns in a project
|
||||||
|
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error {
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
sess := db.GetEngine(ctx)
|
||||||
|
columnIDs := util.ValuesOfMap(sortedColumnIDs)
|
||||||
|
movedColumns, err := GetColumnsByIDs(ctx, project.ID, columnIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(movedColumns) != len(sortedColumnIDs) {
|
||||||
|
return errors.New("some columns do not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, column := range movedColumns {
|
||||||
|
if column.ProjectID != project.ID {
|
||||||
|
return fmt.Errorf("column[%d]'s projectID is not equal to project's ID [%d]", column.ProjectID, project.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for sorting, columnID := range sortedColumnIDs {
|
||||||
|
if _, err := sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
package project
|
package project
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -19,7 +21,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if default board was added
|
// check if default board was added
|
||||||
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
|
board, err := projectWithoutDefault.GetDefaultBoard(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(5), board.ProjectID)
|
assert.Equal(t, int64(5), board.ProjectID)
|
||||||
assert.Equal(t, "Uncategorized", board.Title)
|
assert.Equal(t, "Uncategorized", board.Title)
|
||||||
|
@ -28,7 +30,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// check if multiple defaults were removed
|
// check if multiple defaults were removed
|
||||||
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
|
board, err = projectWithMultipleDefaults.GetDefaultBoard(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(6), board.ProjectID)
|
assert.Equal(t, int64(6), board.ProjectID)
|
||||||
assert.Equal(t, int64(9), board.ID)
|
assert.Equal(t, int64(9), board.ID)
|
||||||
|
@ -42,3 +44,84 @@ func TestGetDefaultBoard(t *testing.T) {
|
||||||
assert.Equal(t, int64(6), board.ProjectID)
|
assert.Equal(t, int64(6), board.ProjectID)
|
||||||
assert.False(t, board.Default)
|
assert.False(t, board.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_moveIssuesToAnotherColumn(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
column1 := unittest.AssertExistsAndLoadBean(t, &Board{ID: 1, ProjectID: 1})
|
||||||
|
|
||||||
|
issues, err := column1.GetIssues(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issues, 1)
|
||||||
|
assert.EqualValues(t, 1, issues[0].ID)
|
||||||
|
|
||||||
|
column2 := unittest.AssertExistsAndLoadBean(t, &Board{ID: 2, ProjectID: 1})
|
||||||
|
issues, err = column2.GetIssues(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issues, 1)
|
||||||
|
assert.EqualValues(t, 3, issues[0].ID)
|
||||||
|
|
||||||
|
err = column1.moveIssuesToAnotherColumn(db.DefaultContext, column2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
issues, err = column1.GetIssues(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issues, 0)
|
||||||
|
|
||||||
|
issues, err = column2.GetIssues(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issues, 2)
|
||||||
|
assert.EqualValues(t, 3, issues[0].ID)
|
||||||
|
assert.EqualValues(t, 0, issues[0].Sorting)
|
||||||
|
assert.EqualValues(t, 1, issues[1].ID)
|
||||||
|
assert.EqualValues(t, 1, issues[1].Sorting)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MoveColumnsOnProject(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||||
|
columns, err := project1.GetBoards(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, columns, 3)
|
||||||
|
assert.EqualValues(t, 0, columns[0].Sorting) // even if there is no default sorting, the code should also work
|
||||||
|
assert.EqualValues(t, 0, columns[1].Sorting)
|
||||||
|
assert.EqualValues(t, 0, columns[2].Sorting)
|
||||||
|
|
||||||
|
err = MoveColumnsOnProject(db.DefaultContext, project1, map[int64]int64{
|
||||||
|
0: columns[1].ID,
|
||||||
|
1: columns[2].ID,
|
||||||
|
2: columns[0].ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
columnsAfter, err := project1.GetBoards(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, columnsAfter, 3)
|
||||||
|
assert.EqualValues(t, columns[1].ID, columnsAfter[0].ID)
|
||||||
|
assert.EqualValues(t, columns[2].ID, columnsAfter[1].ID)
|
||||||
|
assert.EqualValues(t, columns[0].ID, columnsAfter[2].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewBoard(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||||
|
columns, err := project1.GetBoards(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, columns, 3)
|
||||||
|
|
||||||
|
for i := 0; i < maxProjectColumns-3; i++ {
|
||||||
|
err := NewBoard(db.DefaultContext, &Board{
|
||||||
|
Title: fmt.Sprintf("board-%d", i+4),
|
||||||
|
ProjectID: project1.ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
err = NewBoard(db.DefaultContext, &Board{
|
||||||
|
Title: "board-21",
|
||||||
|
ProjectID: project1.ID,
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, strings.Contains(err.Error(), "maximum number of columns reached"))
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProjectIssue saves relation from issue to a project
|
// ProjectIssue saves relation from issue to a project
|
||||||
|
@ -17,7 +18,7 @@ type ProjectIssue struct { //revive:disable-line:exported
|
||||||
IssueID int64 `xorm:"INDEX"`
|
IssueID int64 `xorm:"INDEX"`
|
||||||
ProjectID int64 `xorm:"INDEX"`
|
ProjectID int64 `xorm:"INDEX"`
|
||||||
|
|
||||||
// If 0, then it has not been added to a specific board in the project
|
// ProjectBoardID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors.
|
||||||
ProjectBoardID int64 `xorm:"INDEX"`
|
ProjectBoardID int64 `xorm:"INDEX"`
|
||||||
|
|
||||||
// the sorting order on the board
|
// the sorting order on the board
|
||||||
|
@ -79,11 +80,8 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
|
||||||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
|
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
sess := db.GetEngine(ctx)
|
sess := db.GetEngine(ctx)
|
||||||
|
issueIDs := util.ValuesOfMap(sortedIssueIDs)
|
||||||
|
|
||||||
issueIDs := make([]int64, 0, len(sortedIssueIDs))
|
|
||||||
for _, issueID := range sortedIssueIDs {
|
|
||||||
issueIDs = append(issueIDs, issueID)
|
|
||||||
}
|
|
||||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -102,7 +100,44 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Board) removeIssues(ctx context.Context) error {
|
func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Board) error {
|
||||||
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
|
if b.ProjectID != newColumn.ProjectID {
|
||||||
|
return fmt.Errorf("columns have to be in the same project")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.ID == newColumn.ID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
MaxSorting int64
|
||||||
|
IssueCount int64
|
||||||
|
}{}
|
||||||
|
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").
|
||||||
|
Table("project_issue").
|
||||||
|
Where("project_id=?", newColumn.ProjectID).
|
||||||
|
And("project_board_id=?", newColumn.ID).
|
||||||
|
Get(&res); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
issues, err := b.GetIssues(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(issues) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
for i, issue := range issues {
|
||||||
|
issue.ProjectBoardID = newColumn.ID
|
||||||
|
issue.Sorting = nextSorting + int64(i)
|
||||||
|
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -161,6 +161,13 @@ func (p *Project) IsRepositoryProject() bool {
|
||||||
return p.Type == TypeRepository
|
return p.Type == TypeRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Project) CanBeAccessedByOwnerRepo(ownerID int64, repo *repo_model.Repository) bool {
|
||||||
|
if p.Type == TypeRepository {
|
||||||
|
return repo != nil && p.RepoID == repo.ID // if a project belongs to a repository, then its OwnerID is 0 and can be ignored
|
||||||
|
}
|
||||||
|
return p.OwnerID == ownerID && p.RepoID == 0
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Project))
|
db.RegisterModel(new(Project))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/avatar"
|
"code.gitea.io/gitea/modules/avatar"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
@ -84,13 +84,7 @@ func (repo *Repository) relAvatarLink(ctx context.Context) string {
|
||||||
return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar)
|
return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLink returns a link to the repository's avatar.
|
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||||
func (repo *Repository) AvatarLink(ctx context.Context) string {
|
func (repo *Repository) AvatarLink(ctx context.Context) string {
|
||||||
link := repo.relAvatarLink(ctx)
|
return httplib.MakeAbsoluteURL(ctx, repo.relAvatarLink(ctx))
|
||||||
// we only prepend our AppURL to our known (relative, internal) avatar link to get an absolute URL
|
|
||||||
if strings.HasPrefix(link, "/") && !strings.HasPrefix(link, "//") {
|
|
||||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
|
|
||||||
}
|
|
||||||
// otherwise, return the link as it is
|
|
||||||
return link
|
|
||||||
}
|
}
|
||||||
|
|
28
models/repo/avatar_test.go
Normal file
28
models/repo/avatar_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
repo := &Repository{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/repo-avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
|
||||||
|
}
|
|
@ -175,6 +175,8 @@ type SearchRepoOptions struct {
|
||||||
// True -> include just forks
|
// True -> include just forks
|
||||||
// False -> include just non-forks
|
// False -> include just non-forks
|
||||||
Fork optional.Option[bool]
|
Fork optional.Option[bool]
|
||||||
|
// If Fork option is True, you can use this option to limit the forks of a special repo by repo id.
|
||||||
|
ForkFrom int64
|
||||||
// None -> include templates AND non-templates
|
// None -> include templates AND non-templates
|
||||||
// True -> include just templates
|
// True -> include just templates
|
||||||
// False -> include just non-templates
|
// False -> include just non-templates
|
||||||
|
@ -514,6 +516,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||||
cond = cond.And(builder.Eq{"is_fork": false})
|
cond = cond.And(builder.Eq{"is_fork": false})
|
||||||
} else {
|
} else {
|
||||||
cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
|
cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
|
||||||
|
|
||||||
|
if opts.ForkFrom > 0 && opts.Fork.Value() {
|
||||||
|
cond = cond.And(builder.Eq{"fork_id": opts.ForkFrom})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,14 @@ import "code.gitea.io/gitea/models/db"
|
||||||
// SearchOrderByMap represents all possible search order
|
// SearchOrderByMap represents all possible search order
|
||||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
"asc": {
|
"asc": {
|
||||||
"alpha": db.SearchOrderByAlphabetically,
|
"alpha": "owner_name ASC, name ASC",
|
||||||
"created": db.SearchOrderByOldest,
|
"created": db.SearchOrderByOldest,
|
||||||
"updated": db.SearchOrderByLeastUpdated,
|
"updated": db.SearchOrderByLeastUpdated,
|
||||||
"size": db.SearchOrderBySize,
|
"size": db.SearchOrderBySize,
|
||||||
"id": db.SearchOrderByID,
|
"id": db.SearchOrderByID,
|
||||||
},
|
},
|
||||||
"desc": {
|
"desc": {
|
||||||
"alpha": db.SearchOrderByAlphabeticallyReverse,
|
"alpha": "owner_name DESC, name DESC",
|
||||||
"created": db.SearchOrderByNewest,
|
"created": db.SearchOrderByNewest,
|
||||||
"updated": db.SearchOrderByRecentUpdated,
|
"updated": db.SearchOrderByRecentUpdated,
|
||||||
"size": db.SearchOrderBySizeReverse,
|
"size": db.SearchOrderBySizeReverse,
|
||||||
|
|
|
@ -130,7 +130,10 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
||||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||||
if len(userIDs) > 0 {
|
if len(userIDs) > 0 {
|
||||||
if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
|
if err = e.In("id", uniqueUserIDs.Values()).
|
||||||
|
Where(builder.Eq{"`user`.is_active": true}).
|
||||||
|
OrderBy(user_model.GetOrderByName()).
|
||||||
|
Find(&users); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +155,8 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cond := builder.And(builder.Neq{"`user`.id": posterID})
|
cond := builder.And(builder.Neq{"`user`.id": posterID}).
|
||||||
|
And(builder.Eq{"`user`.is_active": true})
|
||||||
|
|
||||||
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
|
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
|
||||||
// This a private repository:
|
// This a private repository:
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
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"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -25,10 +26,19 @@ func TestRepoAssignees(t *testing.T) {
|
||||||
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)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, users, 4)
|
if assert.Len(t, users, 4) {
|
||||||
assert.ElementsMatch(t, []int64{10, 15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID})
|
assert.ElementsMatch(t, []int64{10, 15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not return deactivated users
|
||||||
|
assert.NoError(t, user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 15, IsActive: false}, "is_active"))
|
||||||
|
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, users, 3) {
|
||||||
|
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRepoGetReviewers(t *testing.T) {
|
func TestRepoGetReviewers(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
@ -38,17 +48,19 @@ func TestRepoGetReviewers(t *testing.T) {
|
||||||
ctx := db.DefaultContext
|
ctx := db.DefaultContext
|
||||||
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
|
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 4)
|
if assert.Len(t, reviewers, 3) {
|
||||||
|
assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
|
||||||
|
}
|
||||||
|
|
||||||
// should include doer if doer is not PR poster.
|
// should include doer if doer is not PR poster.
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
|
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 4)
|
assert.Len(t, reviewers, 3)
|
||||||
|
|
||||||
// should not include PR poster, if PR poster would be otherwise eligible
|
// should not include PR poster, if PR poster would be otherwise eligible
|
||||||
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
|
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, reviewers, 3)
|
assert.Len(t, reviewers, 2)
|
||||||
|
|
||||||
// test private user repo
|
// test private user repo
|
||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
@ -106,10 +107,23 @@ var (
|
||||||
TypeExternalTracker,
|
TypeExternalTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisabledRepoUnits contains the units that have been globally disabled
|
disabledRepoUnitsAtomic atomic.Pointer[[]Type] // the units that have been globally disabled
|
||||||
DisabledRepoUnits = []Type{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DisabledRepoUnitsGet returns the globally disabled units, it is a quick patch to fix data-race during testing.
|
||||||
|
// Because the queue worker might read when a test is mocking the value. FIXME: refactor to a clear solution later.
|
||||||
|
func DisabledRepoUnitsGet() []Type {
|
||||||
|
v := disabledRepoUnitsAtomic.Load()
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisabledRepoUnitsSet(v []Type) {
|
||||||
|
disabledRepoUnitsAtomic.Store(&v)
|
||||||
|
}
|
||||||
|
|
||||||
// Get valid set of default repository units from settings
|
// Get valid set of default repository units from settings
|
||||||
func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
|
func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
|
||||||
units := defaultUnits
|
units := defaultUnits
|
||||||
|
@ -127,7 +141,7 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove disabled units
|
// Remove disabled units
|
||||||
for _, disabledUnit := range DisabledRepoUnits {
|
for _, disabledUnit := range DisabledRepoUnitsGet() {
|
||||||
for i, unit := range units {
|
for i, unit := range units {
|
||||||
if unit == disabledUnit {
|
if unit == disabledUnit {
|
||||||
units = append(units[:i], units[i+1:]...)
|
units = append(units[:i], units[i+1:]...)
|
||||||
|
@ -140,11 +154,11 @@ func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
|
||||||
|
|
||||||
// LoadUnitConfig load units from settings
|
// LoadUnitConfig load units from settings
|
||||||
func LoadUnitConfig() error {
|
func LoadUnitConfig() error {
|
||||||
var invalidKeys []string
|
disabledRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DisabledRepoUnits...)
|
||||||
DisabledRepoUnits, invalidKeys = FindUnitTypes(setting.Repository.DisabledRepoUnits...)
|
|
||||||
if len(invalidKeys) > 0 {
|
if len(invalidKeys) > 0 {
|
||||||
log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
|
log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
|
||||||
}
|
}
|
||||||
|
DisabledRepoUnitsSet(disabledRepoUnits)
|
||||||
|
|
||||||
setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
|
setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
|
||||||
if len(invalidKeys) > 0 {
|
if len(invalidKeys) > 0 {
|
||||||
|
@ -167,7 +181,7 @@ func LoadUnitConfig() error {
|
||||||
|
|
||||||
// UnitGlobalDisabled checks if unit type is global disabled
|
// UnitGlobalDisabled checks if unit type is global disabled
|
||||||
func (u Type) UnitGlobalDisabled() bool {
|
func (u Type) UnitGlobalDisabled() bool {
|
||||||
for _, ud := range DisabledRepoUnits {
|
for _, ud := range DisabledRepoUnitsGet() {
|
||||||
if u == ud {
|
if u == ud {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,10 @@ import (
|
||||||
func TestLoadUnitConfig(t *testing.T) {
|
func TestLoadUnitConfig(t *testing.T) {
|
||||||
t.Run("regular", func(t *testing.T) {
|
t.Run("regular", func(t *testing.T) {
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
||||||
DisabledRepoUnits = disabledRepoUnits
|
DisabledRepoUnitsSet(disabledRepoUnits)
|
||||||
DefaultRepoUnits = defaultRepoUnits
|
DefaultRepoUnits = defaultRepoUnits
|
||||||
DefaultForkRepoUnits = defaultForkRepoUnits
|
DefaultForkRepoUnits = defaultForkRepoUnits
|
||||||
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
|
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
||||||
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
||||||
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
||||||
|
@ -28,16 +28,16 @@ func TestLoadUnitConfig(t *testing.T) {
|
||||||
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
|
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
|
||||||
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
|
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
|
||||||
assert.NoError(t, LoadUnitConfig())
|
assert.NoError(t, LoadUnitConfig())
|
||||||
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
|
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
|
||||||
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
||||||
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
||||||
})
|
})
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
||||||
DisabledRepoUnits = disabledRepoUnits
|
DisabledRepoUnitsSet(disabledRepoUnits)
|
||||||
DefaultRepoUnits = defaultRepoUnits
|
DefaultRepoUnits = defaultRepoUnits
|
||||||
DefaultForkRepoUnits = defaultForkRepoUnits
|
DefaultForkRepoUnits = defaultForkRepoUnits
|
||||||
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
|
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
||||||
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
||||||
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
||||||
|
@ -48,16 +48,16 @@ func TestLoadUnitConfig(t *testing.T) {
|
||||||
setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
|
setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
|
||||||
setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
|
setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
|
||||||
assert.NoError(t, LoadUnitConfig())
|
assert.NoError(t, LoadUnitConfig())
|
||||||
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
|
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
|
||||||
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
||||||
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
||||||
})
|
})
|
||||||
t.Run("duplicate", func(t *testing.T) {
|
t.Run("duplicate", func(t *testing.T) {
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
||||||
DisabledRepoUnits = disabledRepoUnits
|
DisabledRepoUnitsSet(disabledRepoUnits)
|
||||||
DefaultRepoUnits = defaultRepoUnits
|
DefaultRepoUnits = defaultRepoUnits
|
||||||
DefaultForkRepoUnits = defaultForkRepoUnits
|
DefaultForkRepoUnits = defaultForkRepoUnits
|
||||||
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
|
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
||||||
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
||||||
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
||||||
|
@ -68,16 +68,16 @@ func TestLoadUnitConfig(t *testing.T) {
|
||||||
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
|
setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
|
||||||
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
|
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
|
||||||
assert.NoError(t, LoadUnitConfig())
|
assert.NoError(t, LoadUnitConfig())
|
||||||
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
|
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
|
||||||
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
|
||||||
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
||||||
})
|
})
|
||||||
t.Run("empty_default", func(t *testing.T) {
|
t.Run("empty_default", func(t *testing.T) {
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
|
||||||
DisabledRepoUnits = disabledRepoUnits
|
DisabledRepoUnitsSet(disabledRepoUnits)
|
||||||
DefaultRepoUnits = defaultRepoUnits
|
DefaultRepoUnits = defaultRepoUnits
|
||||||
DefaultForkRepoUnits = defaultForkRepoUnits
|
DefaultForkRepoUnits = defaultForkRepoUnits
|
||||||
}(DisabledRepoUnits, DefaultRepoUnits, DefaultForkRepoUnits)
|
}(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
|
||||||
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
|
||||||
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
setting.Repository.DisabledRepoUnits = disabledRepoUnits
|
||||||
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
setting.Repository.DefaultRepoUnits = defaultRepoUnits
|
||||||
|
@ -88,7 +88,7 @@ func TestLoadUnitConfig(t *testing.T) {
|
||||||
setting.Repository.DefaultRepoUnits = []string{}
|
setting.Repository.DefaultRepoUnits = []string{}
|
||||||
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
|
setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
|
||||||
assert.NoError(t, LoadUnitConfig())
|
assert.NoError(t, LoadUnitConfig())
|
||||||
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnits)
|
assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
|
||||||
assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits)
|
assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits)
|
||||||
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/avatars"
|
"code.gitea.io/gitea/models/avatars"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/avatar"
|
"code.gitea.io/gitea/modules/avatar"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
@ -89,13 +89,11 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLink returns the full avatar link with http host
|
// AvatarLink returns the full avatar url with http host.
|
||||||
|
// TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||||
func (u *User) AvatarLink(ctx context.Context) string {
|
func (u *User) AvatarLink(ctx context.Context) string {
|
||||||
link := u.AvatarLinkWithSize(ctx, 0)
|
relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty
|
||||||
if !strings.HasPrefix(link, "//") && !strings.Contains(link, "://") {
|
return httplib.MakeAbsoluteURL(ctx, relLink)
|
||||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL+"/")
|
|
||||||
}
|
|
||||||
return link
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||||
|
|
28
models/user/avatar_test.go
Normal file
28
models/user/avatar_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
u := &User{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
@ -353,14 +354,12 @@ func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, ne
|
||||||
|
|
||||||
// VerifyActiveEmailCode verifies active email code when active account
|
// VerifyActiveEmailCode verifies active email code when active account
|
||||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
||||||
minutes := setting.Service.ActiveCodeLives
|
|
||||||
|
|
||||||
if user := GetVerifyUser(ctx, code); user != nil {
|
if user := GetVerifyUser(ctx, code); user != nil {
|
||||||
// time limit code
|
// time limit code
|
||||||
prefix := code[:base.TimeLimitCodeLength]
|
prefix := code[:base.TimeLimitCodeLength]
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
|
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
|
||||||
|
|
||||||
if base.VerifyTimeLimitCode(data, minutes, prefix) {
|
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
||||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
||||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
||||||
return emailAddress
|
return emailAddress
|
||||||
|
|
|
@ -304,7 +304,7 @@ func (u *User) OrganisationLink() string {
|
||||||
func (u *User) GenerateEmailActivateCode(email string) string {
|
func (u *User) GenerateEmailActivateCode(email string) string {
|
||||||
code := base.CreateTimeLimitCode(
|
code := base.CreateTimeLimitCode(
|
||||||
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
|
fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
|
||||||
setting.Service.ActiveCodeLives, nil)
|
setting.Service.ActiveCodeLives, time.Now(), nil)
|
||||||
|
|
||||||
// Add tail hex username
|
// Add tail hex username
|
||||||
code += hex.EncodeToString([]byte(u.LowerName))
|
code += hex.EncodeToString([]byte(u.LowerName))
|
||||||
|
@ -791,14 +791,11 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
|
||||||
|
|
||||||
// VerifyUserActiveCode verifies active code when active account
|
// VerifyUserActiveCode verifies active code when active account
|
||||||
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
|
func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
|
||||||
minutes := setting.Service.ActiveCodeLives
|
|
||||||
|
|
||||||
if user = GetVerifyUser(ctx, code); user != nil {
|
if user = GetVerifyUser(ctx, code); user != nil {
|
||||||
// time limit code
|
// time limit code
|
||||||
prefix := code[:base.TimeLimitCodeLength]
|
prefix := code[:base.TimeLimitCodeLength]
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
|
data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
|
||||||
|
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
||||||
if base.VerifyTimeLimitCode(data, minutes, prefix) {
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,67 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func naturalSortGetRune(str string, pos int) (r rune, size int, has bool) {
|
||||||
|
if pos >= len(str) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(str[pos:])
|
||||||
|
if r == utf8.RuneError {
|
||||||
|
r, size = rune(str[pos]), 1 // if invalid input, treat it as a single byte ascii
|
||||||
|
}
|
||||||
|
return r, size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||||
|
end = pos
|
||||||
|
for {
|
||||||
|
r, size, has := naturalSortGetRune(str, end)
|
||||||
|
if !has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
isCurRuneNum := '0' <= r && r <= '9'
|
||||||
|
if end == pos {
|
||||||
|
isNumber = isCurRuneNum
|
||||||
|
end += size
|
||||||
|
} else if isCurRuneNum == isNumber {
|
||||||
|
end += size
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end, isNumber
|
||||||
|
}
|
||||||
|
|
||||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||||
func NaturalSortLess(s1, s2 string) bool {
|
func NaturalSortLess(s1, s2 string) bool {
|
||||||
|
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||||
|
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||||
|
// So we need to handle the number parts by ourselves
|
||||||
c := collate.New(language.English, collate.Numeric)
|
c := collate.New(language.English, collate.Numeric)
|
||||||
return c.CompareString(s1, s2) < 0
|
pos1, pos2 := 0, 0
|
||||||
|
for pos1 < len(s1) && pos2 < len(s2) {
|
||||||
|
end1, isNum1 := naturalSortAdvance(s1, pos1)
|
||||||
|
end2, isNum2 := naturalSortAdvance(s2, pos2)
|
||||||
|
part1, part2 := s1[pos1:end1], s2[pos2:end2]
|
||||||
|
if isNum1 && isNum2 {
|
||||||
|
if part1 != part2 {
|
||||||
|
if len(part1) != len(part2) {
|
||||||
|
return len(part1) < len(part2)
|
||||||
|
}
|
||||||
|
return part1 < part2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||||
|
return cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos1, pos2 = end1, end2
|
||||||
|
}
|
||||||
|
return len(s1) < len(s2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,36 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNaturalSortLess(t *testing.T) {
|
func TestNaturalSortLess(t *testing.T) {
|
||||||
test := func(s1, s2 string, less bool) {
|
testLess := func(s1, s2 string) {
|
||||||
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
|
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
}
|
}
|
||||||
test("v1.20.0", "v1.2.0", false)
|
testEqual := func(s1, s2 string) {
|
||||||
test("v1.20.0", "v1.29.0", true)
|
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
test("v1.20.0", "v1.20.0", false)
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
test("abc", "bcd", true)
|
}
|
||||||
test("a-1-a", "a-1-b", true)
|
|
||||||
test("2", "12", true)
|
testEqual("", "")
|
||||||
test("a", "ab", true)
|
testLess("", "a")
|
||||||
|
testLess("", "1")
|
||||||
test("A", "b", true)
|
|
||||||
test("a", "B", true)
|
testLess("v1.2", "v1.2.0")
|
||||||
|
testLess("v1.2.0", "v1.10.0")
|
||||||
test("cafe", "café", true)
|
testLess("v1.20.0", "v1.29.0")
|
||||||
test("café", "cafe", false)
|
testEqual("v1.20.0", "v1.20.0")
|
||||||
test("caff", "café", false)
|
|
||||||
|
testLess("a", "A")
|
||||||
|
testLess("a", "B")
|
||||||
|
testLess("A", "b")
|
||||||
|
testLess("A", "ab")
|
||||||
|
|
||||||
|
testLess("abc", "bcd")
|
||||||
|
testLess("a-1-a", "a-1-b")
|
||||||
|
testLess("2", "12")
|
||||||
|
|
||||||
|
testLess("cafe", "café")
|
||||||
|
testLess("café", "caff")
|
||||||
|
|
||||||
|
testLess("A-2", "A-11")
|
||||||
|
testLess("0.txt", "1.txt")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,15 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -25,13 +28,6 @@ import (
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncodeSha1 string to sha1 hex value.
|
|
||||||
func EncodeSha1(str string) string {
|
|
||||||
h := sha1.New()
|
|
||||||
_, _ = h.Write([]byte(str))
|
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeSha256 string to sha256 hex value.
|
// EncodeSha256 string to sha256 hex value.
|
||||||
func EncodeSha256(str string) string {
|
func EncodeSha256(str string) string {
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
|
@ -62,63 +58,62 @@ func BasicAuthDecode(encoded string) (string, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyTimeLimitCode verify time limit code
|
// VerifyTimeLimitCode verify time limit code
|
||||||
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
|
func VerifyTimeLimitCode(now time.Time, data string, minutes int, code string) bool {
|
||||||
if len(code) <= 18 {
|
if len(code) <= 18 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// split code
|
startTimeStr := code[:12]
|
||||||
start := code[:12]
|
aliveTimeStr := code[12:18]
|
||||||
lives := code[12:18]
|
aliveTime, _ := strconv.Atoi(aliveTimeStr) // no need to check err, if anything wrong, the following code check will fail soon
|
||||||
if d, err := strconv.ParseInt(lives, 10, 0); err == nil {
|
|
||||||
minutes = int(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// right active code
|
|
||||||
retCode := CreateTimeLimitCode(data, minutes, start)
|
|
||||||
if retCode == code && minutes > 0 {
|
|
||||||
// check time is expired or not
|
|
||||||
before, _ := time.ParseInLocation("200601021504", start, time.Local)
|
|
||||||
now := time.Now()
|
|
||||||
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// check code
|
||||||
|
retCode := CreateTimeLimitCode(data, aliveTime, startTimeStr, nil)
|
||||||
|
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
||||||
|
retCode = CreateTimeLimitCode(data, aliveTime, startTimeStr, sha1.New()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
|
||||||
|
if subtle.ConstantTimeCompare([]byte(retCode), []byte(code)) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check time is expired or not: startTime <= now && now < startTime + minutes
|
||||||
|
startTime, _ := time.ParseInLocation("200601021504", startTimeStr, time.Local)
|
||||||
|
return (startTime.Before(now) || startTime.Equal(now)) && now.Before(startTime.Add(time.Minute*time.Duration(minutes)))
|
||||||
|
}
|
||||||
|
|
||||||
// TimeLimitCodeLength default value for time limit code
|
// TimeLimitCodeLength default value for time limit code
|
||||||
const TimeLimitCodeLength = 12 + 6 + 40
|
const TimeLimitCodeLength = 12 + 6 + 40
|
||||||
|
|
||||||
// CreateTimeLimitCode create a time limit code
|
// CreateTimeLimitCode create a time-limited code.
|
||||||
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
|
// Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
|
||||||
func CreateTimeLimitCode(data string, minutes int, startInf any) string {
|
// If h is nil, then use the default hmac hash.
|
||||||
format := "200601021504"
|
func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTimeGeneric T, h hash.Hash) string {
|
||||||
|
const format = "200601021504"
|
||||||
|
|
||||||
var start, end time.Time
|
var start time.Time
|
||||||
var startStr, endStr string
|
var startTimeAny any = startTimeGeneric
|
||||||
|
if t, ok := startTimeAny.(time.Time); ok {
|
||||||
if startInf == nil {
|
start = t
|
||||||
// Use now time create code
|
|
||||||
start = time.Now()
|
|
||||||
startStr = start.Format(format)
|
|
||||||
} else {
|
} else {
|
||||||
// use start string create code
|
var err error
|
||||||
startStr = startInf.(string)
|
start, err = time.ParseInLocation(format, startTimeAny.(string), time.Local)
|
||||||
start, _ = time.ParseInLocation(format, startStr, time.Local)
|
if err != nil {
|
||||||
startStr = start.Format(format)
|
return "" // return an invalid code because the "parse" failed
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
startStr := start.Format(format)
|
||||||
|
end := start.Add(time.Minute * time.Duration(minutes))
|
||||||
|
|
||||||
end = start.Add(time.Minute * time.Duration(minutes))
|
if h == nil {
|
||||||
endStr = end.Format(format)
|
h = hmac.New(sha1.New, setting.GetGeneralTokenSigningSecret())
|
||||||
|
}
|
||||||
// create sha1 encode string
|
_, _ = fmt.Fprintf(h, "%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, end.Format(format), minutes)
|
||||||
sh := sha1.New()
|
encoded := hex.EncodeToString(h.Sum(nil))
|
||||||
_, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes)))
|
|
||||||
encoded := hex.EncodeToString(sh.Sum(nil))
|
|
||||||
|
|
||||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
||||||
|
if len(code) != TimeLimitCodeLength {
|
||||||
|
panic("there is a hard requirement for the length of time-limited code") // it shouldn't happen
|
||||||
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,18 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodeSha1(t *testing.T) {
|
|
||||||
assert.Equal(t,
|
|
||||||
"8843d7f92416211de9ebb963ff4ce28125932878",
|
|
||||||
EncodeSha1("foobar"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeSha256(t *testing.T) {
|
func TestEncodeSha256(t *testing.T) {
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
|
"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
|
||||||
|
@ -46,43 +44,54 @@ func TestBasicAuthDecode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyTimeLimitCode(t *testing.T) {
|
func TestVerifyTimeLimitCode(t *testing.T) {
|
||||||
tc := []struct {
|
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||||
data string
|
initGeneralSecret := func(secret string) {
|
||||||
minutes int
|
setting.InstallLock = true
|
||||||
code string
|
setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
|
||||||
valid bool
|
[oauth2]
|
||||||
}{{
|
JWT_SECRET = %s
|
||||||
data: "data",
|
`, secret))
|
||||||
minutes: 2,
|
setting.LoadCommonSettings()
|
||||||
code: testCreateTimeLimitCode(t, "data", 2),
|
|
||||||
valid: true,
|
|
||||||
}, {
|
|
||||||
data: "abc123-ß",
|
|
||||||
minutes: 1,
|
|
||||||
code: testCreateTimeLimitCode(t, "abc123-ß", 1),
|
|
||||||
valid: true,
|
|
||||||
}, {
|
|
||||||
data: "data",
|
|
||||||
minutes: 2,
|
|
||||||
code: "2021012723240000005928251dac409d2c33a6eb82c63410aaad569bed",
|
|
||||||
valid: false,
|
|
||||||
}}
|
|
||||||
for _, test := range tc {
|
|
||||||
actualValid := VerifyTimeLimitCode(test.data, test.minutes, test.code)
|
|
||||||
assert.Equal(t, test.valid, actualValid, "data: '%s' code: '%s' should be valid: %t", test.data, test.code, test.valid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCreateTimeLimitCode(t *testing.T, data string, m int) string {
|
initGeneralSecret("KZb_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
||||||
result0 := CreateTimeLimitCode(data, m, nil)
|
now := time.Now()
|
||||||
result1 := CreateTimeLimitCode(data, m, time.Now().Format("200601021504"))
|
|
||||||
result2 := CreateTimeLimitCode(data, m, time.Unix(time.Now().Unix()+int64(time.Minute)*int64(m), 0).Format("200601021504"))
|
|
||||||
|
|
||||||
assert.Equal(t, result0, result1)
|
t.Run("TestGenericParameter", func(t *testing.T) {
|
||||||
assert.NotEqual(t, result0, result2)
|
time2000 := time.Date(2000, 1, 2, 3, 4, 5, 0, time.Local)
|
||||||
|
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, time2000, sha1.New()))
|
||||||
|
assert.Equal(t, "2000010203040000026fa5221b2731b7cf80b1b506f5e39e38c115fee5", CreateTimeLimitCode("test-sha1", 2, "200001020304", sha1.New()))
|
||||||
|
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, time2000, nil))
|
||||||
|
assert.Equal(t, "2000010203040000024842227a2f87041ff82025199c0187410a9297bf", CreateTimeLimitCode("test-hmac", 2, "200001020304", nil))
|
||||||
|
})
|
||||||
|
|
||||||
assert.True(t, len(result0) != 0)
|
t.Run("TestInvalidCode", func(t *testing.T) {
|
||||||
return result0
|
assert.False(t, VerifyTimeLimitCode(now, "data", 2, ""))
|
||||||
|
assert.False(t, VerifyTimeLimitCode(now, "data", 2, "invalid code"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TestCreateAndVerify", func(t *testing.T) {
|
||||||
|
code := CreateTimeLimitCode("data", 2, now, nil)
|
||||||
|
assert.False(t, VerifyTimeLimitCode(now.Add(-time.Minute), "data", 2, code)) // not started yet
|
||||||
|
assert.True(t, VerifyTimeLimitCode(now, "data", 2, code))
|
||||||
|
assert.True(t, VerifyTimeLimitCode(now.Add(time.Minute), "data", 2, code))
|
||||||
|
assert.False(t, VerifyTimeLimitCode(now.Add(time.Minute), "DATA", 2, code)) // invalid data
|
||||||
|
assert.False(t, VerifyTimeLimitCode(now.Add(2*time.Minute), "data", 2, code)) // expired
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TestDifferentSecret", func(t *testing.T) {
|
||||||
|
// use another secret to ensure the code is invalid for different secret
|
||||||
|
verifyDataCode := func(c string) bool {
|
||||||
|
return VerifyTimeLimitCode(now, "data", 2, c)
|
||||||
|
}
|
||||||
|
code1 := CreateTimeLimitCode("data", 2, now, sha1.New())
|
||||||
|
code2 := CreateTimeLimitCode("data", 2, now, nil)
|
||||||
|
assert.True(t, verifyDataCode(code1))
|
||||||
|
assert.True(t, verifyDataCode(code2))
|
||||||
|
initGeneralSecret("000_QLUd4fYVyxetjxC4eZkrBgWM2SndOOWDNtgUUko")
|
||||||
|
assert.False(t, verifyDataCode(code1))
|
||||||
|
assert.False(t, verifyDataCode(code2))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSize(t *testing.T) {
|
func TestFileSize(t *testing.T) {
|
||||||
|
|
2
modules/cache/cache.go
vendored
2
modules/cache/cache.go
vendored
|
@ -8,6 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
_ "gitea.com/go-chi/cache/memcache" //nolint:depguard // memcache plugin for cache, it is required for config "ADAPTER=memcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultCache StringCache
|
var defaultCache StringCache
|
||||||
|
|
|
@ -132,7 +132,7 @@ func (r *BlameReader) Close() error {
|
||||||
// CreateBlameReader creates reader for given repository, commit and file
|
// CreateBlameReader creates reader for given repository, commit and file
|
||||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||||
var ignoreRevsFile *string
|
var ignoreRevsFile *string
|
||||||
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
|
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
|
||||||
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -423,7 +423,7 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
||||||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||||
func (c *Commit) GetBranchName() (string, error) {
|
func (c *Commit) GetBranchName() (string, error) {
|
||||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||||
if CheckGitVersionAtLeast("2.13.0") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
|
||||||
cmd.AddArguments("--exclude", "refs/tags/*")
|
cmd.AddArguments("--exclude", "refs/tags/*")
|
||||||
}
|
}
|
||||||
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
||||||
|
|
|
@ -22,42 +22,63 @@ import (
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequiredVersion is the minimum Git version required
|
const RequiredVersion = "2.0.0" // the minimum Git version required
|
||||||
const RequiredVersion = "2.0.0"
|
|
||||||
|
|
||||||
var (
|
type Features struct {
|
||||||
// GitExecutable is the command name of git
|
gitVersion *version.Version
|
||||||
// Could be updated to an absolute path while initialization
|
|
||||||
GitExecutable = "git"
|
|
||||||
|
|
||||||
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
|
|
||||||
DefaultContext context.Context
|
|
||||||
|
|
||||||
DefaultFeatures struct {
|
|
||||||
GitVersion *version.Version
|
|
||||||
|
|
||||||
|
UsingGogit bool
|
||||||
SupportProcReceive bool // >= 2.29
|
SupportProcReceive bool // >= 2.29
|
||||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||||
|
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
|
||||||
|
DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
|
||||||
|
defaultFeatures *Features
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadGitVersion tries to get the current git version and stores it into a global variable
|
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
|
||||||
func loadGitVersion() error {
|
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
|
||||||
// doesn't need RWMutex because it's executed by Init()
|
|
||||||
if DefaultFeatures.GitVersion != nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VersionInfo returns git version information
|
||||||
|
func (f *Features) VersionInfo() string {
|
||||||
|
return f.gitVersion.Original()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultFeatures() *Features {
|
||||||
|
if defaultFeatures == nil {
|
||||||
|
if !setting.IsProd || setting.IsInTesting {
|
||||||
|
log.Warn("git.DefaultFeatures is called before git.InitXxx, initializing with default values")
|
||||||
|
}
|
||||||
|
if err := InitSimple(context.Background()); err != nil {
|
||||||
|
log.Fatal("git.InitSimple failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultFeatures
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGitVersionFeatures() (*Features, error) {
|
||||||
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return runErr
|
return nil, runErr
|
||||||
}
|
}
|
||||||
|
|
||||||
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
||||||
if err == nil {
|
if err != nil {
|
||||||
DefaultFeatures.GitVersion = ver
|
return nil, err
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
features := &Features{gitVersion: ver, UsingGogit: isGogit}
|
||||||
|
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
|
||||||
|
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
|
||||||
|
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
||||||
|
if features.SupportHashSha256 {
|
||||||
|
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
||||||
|
}
|
||||||
|
return features, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGitVersionLine(s string) (*version.Version, error) {
|
func parseGitVersionLine(s string) (*version.Version, error) {
|
||||||
|
@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
|
||||||
return fmt.Errorf("git not found: %w", err)
|
return fmt.Errorf("git not found: %w", err)
|
||||||
}
|
}
|
||||||
GitExecutable = absPath
|
GitExecutable = absPath
|
||||||
|
return nil
|
||||||
if err = loadGitVersion(); err != nil {
|
|
||||||
return fmt.Errorf("unable to load git version: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
versionRequired, err := version.NewVersion(RequiredVersion)
|
func ensureGitVersion() error {
|
||||||
if err != nil {
|
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultFeatures.GitVersion.LessThan(versionRequired) {
|
|
||||||
moreHint := "get git: https://git-scm.com/download/"
|
moreHint := "get git: https://git-scm.com/download/"
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
||||||
if _, err = os.Stat("/etc/redhat-release"); err == nil {
|
if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
||||||
// ius.io is the recommended official(git-scm.com) method to install git
|
// ius.io is the recommended official(git-scm.com) method to install git
|
||||||
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
|
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures.GitVersion.Original(), RequiredVersion, moreHint)
|
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkGitVersionCompatibility(DefaultFeatures.GitVersion); err != nil {
|
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
||||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures.GitVersion.String(), err)
|
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionInfo returns git version information
|
|
||||||
func VersionInfo() string {
|
|
||||||
if DefaultFeatures.GitVersion == nil {
|
|
||||||
return "(git not found)"
|
|
||||||
}
|
|
||||||
format := "%s"
|
|
||||||
args := []any{DefaultFeatures.GitVersion.Original()}
|
|
||||||
// Since git wire protocol has been released from git v2.18
|
|
||||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
|
||||||
format += ", Wire Protocol %s Enabled"
|
|
||||||
args = append(args, "Version 2") // for focus color
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkInit() error {
|
|
||||||
if setting.Git.HomePath == "" {
|
|
||||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
|
||||||
}
|
|
||||||
if DefaultContext != nil {
|
|
||||||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -154,8 +143,12 @@ func HomeDir() string {
|
||||||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||||
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
|
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
|
||||||
func InitSimple(ctx context.Context) error {
|
func InitSimple(ctx context.Context) error {
|
||||||
if err := checkInit(); err != nil {
|
if setting.Git.HomePath == "" {
|
||||||
return err
|
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||||
|
}
|
||||||
|
|
||||||
|
if DefaultContext != nil && (!setting.IsProd || setting.IsInTesting) {
|
||||||
|
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultContext = ctx
|
DefaultContext = ctx
|
||||||
|
@ -165,7 +158,24 @@ func InitSimple(ctx context.Context) error {
|
||||||
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
|
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetExecutablePath(setting.Git.Path)
|
if err := SetExecutablePath(setting.Git.Path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defaultFeatures, err = loadGitVersionFeatures()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ensureGitVersion(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||||
|
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||||
|
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitFull initializes git module with version check and change global variables, sync gitconfig.
|
// InitFull initializes git module with version check and change global variables, sync gitconfig.
|
||||||
|
@ -175,30 +185,18 @@ func InitFull(ctx context.Context) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
|
||||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
|
||||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since git wire protocol has been released from git v2.18
|
// Since git wire protocol has been released from git v2.18
|
||||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
if setting.Git.EnableAutoGitWireProtocol && DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||||
if CheckGitVersionAtLeast("2.9") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.9") {
|
||||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||||
}
|
}
|
||||||
DefaultFeatures.SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
|
|
||||||
DefaultFeatures.SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
|
|
||||||
if DefaultFeatures.SupportHashSha256 {
|
|
||||||
SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
|
|
||||||
} else {
|
|
||||||
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.LFS.StartServer {
|
if setting.LFS.StartServer {
|
||||||
if CheckGitVersionAtLeast("2.1.2") != nil {
|
if !DefaultFeatures().CheckVersionAtLeast("2.1.2") {
|
||||||
return errors.New("LFS server support requires Git >= 2.1.2")
|
return errors.New("LFS server support requires Git >= 2.1.2")
|
||||||
}
|
}
|
||||||
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
|
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
|
||||||
|
@ -238,13 +236,13 @@ func syncGitConfig() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.10") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CheckGitVersionAtLeast("2.18") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if DefaultFeatures.SupportProcReceive {
|
if DefaultFeatures().SupportProcReceive {
|
||||||
// set support for AGit flow
|
// set support for AGit flow
|
||||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default partial clones are disabled, enable them from git v2.22
|
// By default partial clones are disabled, enable them from git v2.22
|
||||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
||||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckGitVersionAtLeast check git version is at least the constraint version
|
|
||||||
func CheckGitVersionAtLeast(atLeast string) error {
|
|
||||||
if DefaultFeatures.GitVersion == nil {
|
|
||||||
panic("git module is not initialized") // it shouldn't happen
|
|
||||||
}
|
|
||||||
atLeastVersion, err := version.NewVersion(atLeast)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if DefaultFeatures.GitVersion.Compare(atLeastVersion) < 0 {
|
|
||||||
return fmt.Errorf("installed git binary version %s is not at least %s", DefaultFeatures.GitVersion.Original(), atLeast)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||||
badVersions := []struct {
|
badVersions := []struct {
|
||||||
Version *version.Version
|
Version *version.Version
|
||||||
|
|
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
||||||
ContextLineNumber int
|
ContextLineNumber int
|
||||||
IsFuzzy bool
|
IsFuzzy bool
|
||||||
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
||||||
|
PathspecList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
||||||
|
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||||
}
|
}
|
||||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||||
|
cmd.AddDashesAndList(opts.PathspecList...)
|
||||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||||
stderr := bytes.Buffer{}
|
stderr := bytes.Buffer{}
|
||||||
err = cmd.Run(&RunOpts{
|
err = cmd.Run(&RunOpts{
|
||||||
|
|
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, res)
|
}, res)
|
||||||
|
|
||||||
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []*GrepResult{
|
||||||
|
{
|
||||||
|
Filename: "java-hello/main.java",
|
||||||
|
LineNumbers: []int{3},
|
||||||
|
LineCodes: []string{" public static void main(String[] args)"},
|
||||||
|
},
|
||||||
|
}, res)
|
||||||
|
|
||||||
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []*GrepResult{
|
||||||
|
{
|
||||||
|
Filename: "main.vendor.java",
|
||||||
|
LineNumbers: []int{3},
|
||||||
|
LineCodes: []string{" public static void main(String[] args)"},
|
||||||
|
},
|
||||||
|
}, res)
|
||||||
|
|
||||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []*GrepResult{
|
assert.Equal(t, []*GrepResult{
|
||||||
|
|
|
@ -120,12 +120,8 @@ var (
|
||||||
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var SupportedObjectFormats = []ObjectFormat{
|
|
||||||
Sha1ObjectFormat,
|
|
||||||
}
|
|
||||||
|
|
||||||
func ObjectFormatFromName(name string) ObjectFormat {
|
func ObjectFormatFromName(name string) ObjectFormat {
|
||||||
for _, objectFormat := range SupportedObjectFormats {
|
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||||
if name == objectFormat.Name() {
|
if name == objectFormat.Name() {
|
||||||
return objectFormat
|
return objectFormat
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
||||||
|
|
||||||
func NewIDFromString(hexHash string) (ObjectID, error) {
|
func NewIDFromString(hexHash string) (ObjectID, error) {
|
||||||
var theObjectFormat ObjectFormat
|
var theObjectFormat ObjectFormat
|
||||||
for _, objectFormat := range SupportedObjectFormats {
|
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||||
if len(hexHash) == objectFormat.FullLength() {
|
if len(hexHash) == objectFormat.FullLength() {
|
||||||
theObjectFormat = objectFormat
|
theObjectFormat = objectFormat
|
||||||
break
|
break
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||||
var cmd *Command
|
var cmd *Command
|
||||||
if CheckGitVersionAtLeast("2.7") == nil {
|
if DefaultFeatures().CheckVersionAtLeast("2.7") {
|
||||||
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
||||||
} else {
|
} else {
|
||||||
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
||||||
|
|
|
@ -7,7 +7,6 @@ package git
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -63,32 +62,6 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectFormatOfRepo returns the hash type of repository at a given path
|
|
||||||
func GetObjectFormatOfRepo(ctx context.Context, repoPath string) (ObjectFormat, error) {
|
|
||||||
var stdout, stderr strings.Builder
|
|
||||||
|
|
||||||
err := NewCommand(ctx, "hash-object", "--stdin").Run(&RunOpts{
|
|
||||||
Dir: repoPath,
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: &stderr,
|
|
||||||
Stdin: &strings.Reader{},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stderr.Len() > 0 {
|
|
||||||
return nil, errors.New(stderr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
h, err := NewIDFromString(strings.TrimRight(stdout.String(), "\n"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Type(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitRepository initializes a new Git repository.
|
// InitRepository initializes a new Git repository.
|
||||||
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
||||||
err := os.MkdirAll(repoPath, os.ModePerm)
|
err := os.MkdirAll(repoPath, os.ModePerm)
|
||||||
|
@ -101,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
|
||||||
if !IsValidObjectFormat(objectFormatName) {
|
if !IsValidObjectFormat(objectFormatName) {
|
||||||
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
||||||
}
|
}
|
||||||
if DefaultFeatures.SupportHashSha256 {
|
if DefaultFeatures().SupportHashSha256 {
|
||||||
cmd.AddOptionValues("--object-format", objectFormatName)
|
cmd.AddOptionValues("--object-format", objectFormatName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
var isGogit bool
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue