mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-13 18:32:54 +00:00
Compare commits
57 Commits
v1.25.0
...
432e128074
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
432e128074 | ||
|
|
8d6442a43e | ||
|
|
3d66e75a47 | ||
|
|
e98d9bb93e | ||
|
|
a601c09826 | ||
|
|
b5f50ff63b | ||
|
|
b1b35e934e | ||
|
|
544450a212 | ||
|
|
0ab447005d | ||
|
|
52902d4ece | ||
|
|
0e91c8a068 | ||
|
|
45cdc5d8fd | ||
|
|
b276849cd8 | ||
|
|
46d1d154e8 | ||
|
|
f164e38e04 | ||
|
|
d4d338f1c1 | ||
|
|
f6895f632e | ||
|
|
eaa916a786 | ||
|
|
91901c2a60 | ||
|
|
20cf4b7849 | ||
|
|
5e7207d428 | ||
|
|
e3bfee80dd | ||
|
|
f93e2cf301 | ||
|
|
1b01d6de82 | ||
|
|
d67cd622d0 | ||
|
|
15f3e9d5a5 | ||
|
|
01fa8b2b7e | ||
|
|
1d9ae7ac23 | ||
|
|
01873a99c1 | ||
|
|
ce70863793 | ||
|
|
327f2207dc | ||
|
|
db876d8f17 | ||
|
|
2b71bf283b | ||
|
|
1ca4fef611 | ||
|
|
70ee6b9029 | ||
|
|
e5b404ec53 | ||
|
|
5842cd23a6 | ||
|
|
289bd9694b | ||
|
|
154d7521a5 | ||
|
|
24189dcced | ||
|
|
f84bf259ad | ||
|
|
470b21056a | ||
|
|
61011f1648 | ||
|
|
7ea9722c1d | ||
|
|
297f63af42 | ||
|
|
6a55749359 | ||
|
|
8116742e2d | ||
|
|
0a9cbf3228 | ||
|
|
74dfadb543 | ||
|
|
8ffc1fbfbf | ||
|
|
e95378329b | ||
|
|
fddf6cd63f | ||
|
|
d253e2055b | ||
|
|
e194d89c74 | ||
|
|
04b6f90889 | ||
|
|
65a37572f3 | ||
|
|
f85cd7aeb5 |
@@ -25,6 +25,10 @@ insert_final_newline = false
|
||||
[templates/user/auth/oidc_wellknown.tmpl]
|
||||
indent_style = space
|
||||
|
||||
[templates/shared/actions/runner_badge_*.tmpl]
|
||||
# editconfig lint requires these XML-like files to have charset defined, but the files don't have.
|
||||
charset = unset
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
|
||||
2
.github/workflows/cron-licenses.yml
vendored
2
.github/workflows/cron-licenses.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
if: github.repository == 'go-gitea/gitea'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
14
.github/workflows/pull-compliance.yml
vendored
14
.github/workflows/pull-compliance.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
10
.github/workflows/pull-db-tests.yml
vendored
10
.github/workflows/pull-db-tests.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
- "9000:9000"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
- "993:993"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -218,7 +218,7 @@ jobs:
|
||||
- 10000:10000
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
2
.github/workflows/pull-e2e-tests.yml
vendored
2
.github/workflows/pull-e2e-tests.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
6
.github/workflows/release-nightly.yml
vendored
6
.github/workflows/release-nightly.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
2
.github/workflows/release-tag-rc.yml
vendored
2
.github/workflows/release-tag-rc.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
2
.github/workflows/release-tag-version.yml
vendored
2
.github/workflows/release-tag-version.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
178
CHANGELOG.md
178
CHANGELOG.md
@@ -4,7 +4,55 @@ 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
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/1.25.0) - 2025-10-30
|
||||
## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/1.25.2) - 2025-11-23
|
||||
|
||||
* SECURITY
|
||||
* Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988)
|
||||
* Fix various permission & login related bugs (#36002) (#36004)
|
||||
* ENHANCEMENTS
|
||||
* Display source code downloads last for release attachments (#35897) (#35903)
|
||||
* Change project default column icon to 'star' (#35967) (#35979)
|
||||
* BUGFIXES
|
||||
* Allow empty commit when merging pull request with squash style (#35989) (#36003)
|
||||
* Fix container push tag overwriting (#35936) (#35954)
|
||||
* Fix corrupted external render content (#35946) and upgrade golang.org/x packages (#35950)
|
||||
* Limit reading bytes instead of ReadAll (#35928) (#35934)
|
||||
* Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
|
||||
* Fix team member access check (#35899) (#35905)
|
||||
* Fix conda null depend issue (#35900) (#35902)
|
||||
* Set the dates to now when not specified by the caller (#35861) (#35874)
|
||||
* Fix gogit ListEntriesRecursiveWithSize (#35862)
|
||||
* Misc CSS fixes (#35888) (#35981)
|
||||
* Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
|
||||
* Load jQuery as early as possible to support custom scripts (#35926) (#35929)
|
||||
* Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
|
||||
* Make OAuth2 issuer configurable (#35915) (#35916)
|
||||
* Fix #35763: Add proper page title for project pages (#35773) (#35909)
|
||||
* Fix avatar upload error handling (#35887) (#35890)
|
||||
* Contribution heatmap improvements (#35876) (#35880)
|
||||
* Remove padding override on `.ui .sha.label` (#35864) (#35873)
|
||||
* Fix pull description code label background (#35865) (#35870)
|
||||
|
||||
## [1.25.1](https://github.com/go-gitea/gitea/releases/tag/v1.25.1) - 2025-11-03
|
||||
|
||||
* BUGFIXES
|
||||
* Make ACME email optional (#35849) #35857
|
||||
* Add a doctor command to fix inconsistent run status (#35840) (#35845)
|
||||
* Remove wrong code (#35846)
|
||||
* Fix viewed files number is not right if not all files loaded (#35821) (#35844)
|
||||
* Fix incorrect pull request counter (#35819) (#35841)
|
||||
* Upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
|
||||
* Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
|
||||
* Fix clone mixed bug (#35810) (#35822)
|
||||
* Fix cli "Before" handling (#35797) (#35808)
|
||||
* Improve and fix markup code preview rendering (#35777) (#35787)
|
||||
* Fix actions rerun bug (#35783) (#35784)
|
||||
* Fix actions schedule update issue (#35767) (#35774)
|
||||
* Fix circular spin animation direction (#35785) (#35823)
|
||||
* Fix file extension on gogs.png (#35793) (#35799)
|
||||
* Add pnpm to Snapcraft (#35778)
|
||||
|
||||
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/v1.25.0) - 2025-10-30
|
||||
|
||||
* BREAKING
|
||||
* Return 201 Created for CreateVariable API responses (#34517)
|
||||
@@ -231,7 +279,119 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Docs/fix typo and grammar in CONTRIBUTING.md (#35024)
|
||||
* Improve english grammar and readability in locale_en-US.ini (#35017)
|
||||
|
||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
|
||||
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/v1.24.7) - 2025-10-24
|
||||
|
||||
* SECURITY
|
||||
* Refactor legacy code (#35708) (#35713)
|
||||
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
|
||||
* Fix a bug missed return (#35655) (#35671)
|
||||
* BUGFIXES
|
||||
* Fix inputing review comment will remove reviewer (#35591) (#35664)
|
||||
* TESTING
|
||||
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
|
||||
* Fix build (#35669)
|
||||
|
||||
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/v1.24.6) - 2025-09-10
|
||||
|
||||
* SECURITY
|
||||
* Upgrade xz to v0.5.15 (#35385)
|
||||
* BUGFIXES
|
||||
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
|
||||
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
|
||||
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
|
||||
* Remove usernames when empty in discord webhook (#35412) (#35417)
|
||||
* Allow foreachref parser to grow its buffer (#35365) (#35376)
|
||||
* Allow deleting comment with content via API like web did (#35346) (#35354)
|
||||
* Fix atom/rss mixed error (#35345) (#35347)
|
||||
* Fix review request webhook bug (#35339)
|
||||
* Remove duplicate html IDs (#35210) (#35325)
|
||||
* Fix LFS range size header response (#35277) (#35293)
|
||||
* Fix GitHub release assets URL validation (#35287) (#35290)
|
||||
* Fix token lifetime, closes #35230 (#35271) (#35281)
|
||||
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
|
||||
|
||||
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
|
||||
|
||||
* BUGFIXES
|
||||
* Fix a bug where lfs gc never worked. (#35198) (#35255)
|
||||
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
|
||||
* Fix bug when review pull request commits (#35192) (#35246)
|
||||
* MISC
|
||||
* Vertically center "Show Resolved" (#35211) (#35218)
|
||||
|
||||
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
|
||||
|
||||
* BUGFIXES
|
||||
* Fix various bugs (1.24) (#35186)
|
||||
* Fix migrate input box bug (#35166) (#35171)
|
||||
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
|
||||
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
|
||||
* Fix submodule nil check (#35096) (#35098)
|
||||
* MISC
|
||||
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
|
||||
* Increase gap on latest commit (#35104) (#35113)
|
||||
|
||||
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
|
||||
|
||||
* BUGFIXES
|
||||
* Fix form property assignment edge case (#35073) (#35078)
|
||||
* Improve submodule relative path handling (#35056) (#35075)
|
||||
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
|
||||
* Fix updating user visibility (#35036) (#35044)
|
||||
* Support base64-encoded agit push options (#35037) (#35041)
|
||||
* Make submodule link work with relative path (#35034) (#35038)
|
||||
* Fix bug when displaying git user avatar in commits list (#35006)
|
||||
* Fix API response for swagger spec (#35029)
|
||||
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
|
||||
* Fix the response format for actions/workflows (#35009) (#35016)
|
||||
* Fix repo settings and protocol log problems (#35012) (#35013)
|
||||
* Fix project images scroll (#34971) (#34972)
|
||||
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
|
||||
* Fix git graph page (#34948) (#34949)
|
||||
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
|
||||
* Fix some log and UI problems (#34863) (#34868)
|
||||
* Fix archive API (#34853) (#34857)
|
||||
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
|
||||
* Fix SSH LFS timeout (#34838) (#34842)
|
||||
* Fix team permissions (#34827) (#34836)
|
||||
* Fix job status aggregation logic (#34823) (#34835)
|
||||
* Fix issue filter (#34914) (#34915)
|
||||
* Fix typo in pull request merge warning message text (#34899) (#34903)
|
||||
* Support the open-icon of folder (#34168) (#34896)
|
||||
* Optimize flex layout of release attachment area (#34885) (#34886)
|
||||
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
|
||||
* Skip updating timestamp when sync branch (#34875)
|
||||
* Fix required contexts and commit status matching bug (#34815) (#34829)
|
||||
|
||||
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
|
||||
|
||||
* BUGFIXES
|
||||
* Fix container range bug (#34795) (#34796)
|
||||
* Upgrade chi to v5.2.2 (#34798) (#34799)
|
||||
* BUILD
|
||||
* Bump poetry feature to new url for dev container (#34787) (#34790)
|
||||
|
||||
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
|
||||
|
||||
* ENHANCEMENTS
|
||||
* Improve alignment of commit status icon on commit page (#34750) (#34757)
|
||||
* Support title and body query parameters for new PRs (#34537) (#34752)
|
||||
|
||||
* BUGFIXES
|
||||
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
|
||||
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
|
||||
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
|
||||
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
|
||||
* Fix markdown wrap (#34697) (#34702)
|
||||
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
|
||||
* Fix commit message rendering and some UI problems (#34680) (#34683)
|
||||
* Fix container range bug (#34725) (#34732)
|
||||
* Fix incorrect cli default values (#34765) (#34766)
|
||||
* Fix dropdown filter (#34708) (#34711)
|
||||
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
|
||||
* Fix tag target (#34781) #34783
|
||||
|
||||
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
|
||||
|
||||
* BREAKING
|
||||
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
|
||||
@@ -601,7 +761,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Bump x/net (#32896) (#32900)
|
||||
* Only activity tab needs heatmap data loading (#34652)
|
||||
|
||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
|
||||
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
|
||||
|
||||
* SECURITY
|
||||
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
|
||||
@@ -628,7 +788,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* Bump go version in go.mod (#34160)
|
||||
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
|
||||
|
||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
|
||||
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
|
||||
|
||||
* Enhancements
|
||||
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
|
||||
@@ -726,7 +886,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
* BUGFIXES
|
||||
* Fix a bug caused by status webhook template #33512
|
||||
|
||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
|
||||
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04
|
||||
|
||||
* BREAKING
|
||||
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
|
||||
@@ -3256,7 +3416,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Improve decryption failure message (#24573) (#24575)
|
||||
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
|
||||
|
||||
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
|
||||
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03
|
||||
|
||||
* SECURITY
|
||||
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
|
||||
@@ -3269,7 +3429,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Fix incorrect CurrentUser check for docker rootless (#24435)
|
||||
* Getting the tag list does not require being signed in (#24413) (#24416)
|
||||
|
||||
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
|
||||
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26
|
||||
|
||||
* SECURITY
|
||||
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
|
||||
@@ -3768,7 +3928,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Display attachments of review comment when comment content is blank (#23035) (#23046)
|
||||
* Return empty url for submodule tree entries (#23043) (#23048)
|
||||
|
||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
|
||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20
|
||||
|
||||
* SECURITY
|
||||
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
|
||||
@@ -4195,7 +4355,7 @@ Key highlights of this release encompass significant changes categorized under `
|
||||
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
|
||||
* Fix UI mis-align for PR commit history (#20845) (#20859)
|
||||
|
||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
|
||||
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17
|
||||
|
||||
* SECURITY
|
||||
* Correctly escape within tribute.js (#20831) (#20832)
|
||||
|
||||
2
Makefile
2
Makefile
@@ -35,7 +35,7 @@ SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf0
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
|
||||
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.8
|
||||
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0
|
||||
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0
|
||||
|
||||
|
||||
@@ -121,6 +121,12 @@ func globalBool(c *cli.Command, name string) bool {
|
||||
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
|
||||
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) {
|
||||
return func(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
if setting.InstallLock {
|
||||
// During config loading, there might also be logs (for example: deprecation warnings).
|
||||
// It must make sure that console logger is set up before config is loaded.
|
||||
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
|
||||
return nil, errors.New("console logger must be setup before config is loaded")
|
||||
}
|
||||
level := defaultLevel
|
||||
if globalBool(c, "quiet") {
|
||||
level = log.FATAL
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
var CmdKeys = &cli.Command{
|
||||
Name: "keys",
|
||||
Usage: "(internal) Should only be called by SSH server",
|
||||
Hidden: true, // internal commands shouldn't not be visible
|
||||
Hidden: true, // internal commands shouldn't be visible
|
||||
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
|
||||
Before: PrepareConsoleLoggerLevel(log.FATAL),
|
||||
Action: runKeys,
|
||||
|
||||
10
cmd/main.go
10
cmd/main.go
@@ -50,11 +50,15 @@ DEFAULT CONFIGURATION:
|
||||
|
||||
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
|
||||
originBefore := originCmd.Before
|
||||
originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
|
||||
prepareWorkPathAndCustomConf(cmd)
|
||||
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) {
|
||||
ctx = ctxOrig
|
||||
if originBefore != nil {
|
||||
return originBefore(ctx, cmd)
|
||||
ctx, err = originBefore(ctx, cmd)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
prepareWorkPathAndCustomConf(cmd)
|
||||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v3"
|
||||
@@ -28,11 +29,11 @@ func makePathOutput(workPath, customPath, customConf string) string {
|
||||
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
|
||||
}
|
||||
|
||||
func newTestApp(testCmdAction cli.ActionFunc) *cli.Command {
|
||||
func newTestApp(testCmd cli.Command) *cli.Command {
|
||||
app := NewMainApp(AppVersion{})
|
||||
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
|
||||
prepareSubcommandWithGlobalFlags(testCmd)
|
||||
app.Commands = append(app.Commands, testCmd)
|
||||
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd")
|
||||
prepareSubcommandWithGlobalFlags(&testCmd)
|
||||
app.Commands = append(app.Commands, &testCmd)
|
||||
app.DefaultCommand = testCmd.Name
|
||||
return app
|
||||
}
|
||||
@@ -156,9 +157,11 @@ func TestCliCmd(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.cmd, func(t *testing.T) {
|
||||
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error {
|
||||
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||
return nil
|
||||
app := newTestApp(cli.Command{
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
for k, v := range c.env {
|
||||
t.Setenv(k, v)
|
||||
@@ -173,31 +176,54 @@ func TestCliCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCliCmdError(t *testing.T) {
|
||||
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") })
|
||||
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }})
|
||||
r, err := runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, r.ExitCode)
|
||||
assert.Empty(t, r.Stdout)
|
||||
assert.Equal(t, "Command error: normal error\n", r.Stderr)
|
||||
|
||||
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) })
|
||||
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }})
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 2, r.ExitCode)
|
||||
assert.Empty(t, r.Stdout)
|
||||
assert.Equal(t, "exit error\n", r.Stderr)
|
||||
|
||||
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
|
||||
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 1, r.ExitCode)
|
||||
assert.Empty(t, r.Stdout)
|
||||
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
|
||||
|
||||
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
|
||||
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
|
||||
r, err = runTestApp(app, "./gitea", "test-cmd")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
|
||||
assert.Empty(t, r.Stdout)
|
||||
assert.Empty(t, r.Stderr)
|
||||
}
|
||||
|
||||
func TestCliCmdBefore(t *testing.T) {
|
||||
ctxNew := context.WithValue(context.Background(), any("key"), "value")
|
||||
configValues := map[string]string{}
|
||||
setting.CustomConf = "/tmp/any.ini"
|
||||
var actionCtx context.Context
|
||||
app := newTestApp(cli.Command{
|
||||
Before: func(context.Context, *cli.Command) (context.Context, error) {
|
||||
configValues["before"] = setting.CustomConf
|
||||
return ctxNew, nil
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
configValues["action"] = setting.CustomConf
|
||||
actionCtx = ctx
|
||||
return nil
|
||||
},
|
||||
})
|
||||
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ctxNew, actionCtx)
|
||||
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
|
||||
assert.Equal(t, "/dev/null", configValues["action"])
|
||||
}
|
||||
|
||||
14
cmd/serv.go
14
cmd/serv.go
@@ -18,7 +18,7 @@ import (
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
@@ -207,7 +207,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
||||
username := repoPathFields[0]
|
||||
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||
|
||||
if !repo.IsValidSSHAccessRepoName(reponame) {
|
||||
if !repo_model.IsValidSSHAccessRepoName(reponame) {
|
||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||
}
|
||||
|
||||
@@ -253,10 +253,12 @@ func runServ(ctx context.Context, c *cli.Command) error {
|
||||
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
|
||||
}
|
||||
|
||||
// LowerCase and trim the repoPath as that's how they are stored.
|
||||
// This should be done after splitting the repoPath into username and reponame
|
||||
// so that username and reponame are not affected.
|
||||
repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git")
|
||||
// because the original repoPath maybe redirected, we need to use the returned actual repository information
|
||||
if results.IsWiki {
|
||||
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
|
||||
} else {
|
||||
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
|
||||
}
|
||||
|
||||
// LFS SSH protocol
|
||||
if verb == git.CmdVerbLfsTransfer {
|
||||
|
||||
@@ -567,6 +567,11 @@ ENABLED = true
|
||||
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
|
||||
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
|
||||
;;
|
||||
;; The "issuer" claim identifies the principal that issued the JWT.
|
||||
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
|
||||
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
|
||||
;JWT_CLAIM_ISSUER =
|
||||
;;
|
||||
;; Lifetime of an OAuth2 access token in seconds
|
||||
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
|
||||
;;
|
||||
|
||||
18
go.mod
18
go.mod
@@ -1,6 +1,8 @@
|
||||
module code.gitea.io/gitea
|
||||
|
||||
go 1.25.1
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.5
|
||||
|
||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||
// But some CAs use negative serial number, just relax the check. related:
|
||||
@@ -116,13 +118,13 @@ require (
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
gitlab.com/gitlab-org/api/client-go v0.142.4
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/sys v0.37.0
|
||||
golang.org/x/text v0.30.0
|
||||
golang.org/x/sync v0.18.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/text v0.31.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.8
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
@@ -279,9 +281,9 @@ require (
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@@ -840,8 +840,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -878,8 +878,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -908,8 +908,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -932,8 +932,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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=
|
||||
@@ -975,8 +975,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -987,8 +987,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1002,8 +1002,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
@@ -1039,8 +1039,8 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -13,6 +13,8 @@ func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
FixtureFiles: []string{
|
||||
"action_runner_token.yml",
|
||||
"action_run.yml",
|
||||
"repository.yml",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,6 +184,7 @@ func (run *ActionRun) IsSchedule() bool {
|
||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||
NoAutoTime().
|
||||
Cols("num_action_runs", "num_closed_action_runs").
|
||||
SetExpr("num_action_runs",
|
||||
builder.Select("count(*)").From("action_run").
|
||||
Where(builder.Eq{"repo_id": repo.ID}),
|
||||
|
||||
35
models/actions/run_test.go
Normal file
35
models/actions/run_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUpdateRepoRunsNumbers(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// update the number to a wrong one, the original is 3
|
||||
_, err := db.GetEngine(t.Context()).ID(4).Cols("num_closed_action_runs").Update(&repo_model.Repository{
|
||||
NumClosedActionRuns: 2,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
assert.Equal(t, 4, repo.NumActionRuns)
|
||||
assert.Equal(t, 2, repo.NumClosedActionRuns)
|
||||
|
||||
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
|
||||
err = updateRepoRunsNumbers(t.Context(), repo)
|
||||
assert.NoError(t, err)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
assert.Equal(t, 5, repo.NumActionRuns)
|
||||
assert.Equal(t, 3, repo.NumClosedActionRuns)
|
||||
}
|
||||
@@ -386,7 +386,7 @@ func SetNotificationStatus(ctx context.Context, notificationID int64, user *user
|
||||
|
||||
notification.Status = status
|
||||
|
||||
_, err = db.GetEngine(ctx).ID(notificationID).Update(notification)
|
||||
_, err = db.GetEngine(ctx).ID(notificationID).Cols("status").Update(notification)
|
||||
return notification, err
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
|
||||
}
|
||||
|
||||
key.Verified = true
|
||||
if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil {
|
||||
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -139,3 +139,24 @@
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
||||
-
|
||||
id: 796
|
||||
title: "update actions"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "artifact.yaml"
|
||||
index: 191
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
trigger_event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 5
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
|
||||
@@ -129,3 +129,18 @@
|
||||
status: 5
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
||||
-
|
||||
id: 205
|
||||
run_id: 796
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job_2
|
||||
attempt: 1
|
||||
job_id: job_2
|
||||
task_id: 55
|
||||
status: 3
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
|
||||
@@ -177,3 +177,24 @@
|
||||
log_length: 0
|
||||
log_size: 0
|
||||
log_expired: 0
|
||||
|
||||
-
|
||||
id: 55
|
||||
job_id: 205
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 3 # 3 is the status code for "cancelled"
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab
|
||||
token_salt: eeeeeeee
|
||||
token_last_eight: eeeeeeee
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
|
||||
@@ -733,3 +733,10 @@
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 111
|
||||
repo_id: 4
|
||||
type: 10
|
||||
config: "{}"
|
||||
created_unix: 946684810
|
||||
|
||||
@@ -110,6 +110,8 @@
|
||||
num_closed_milestones: 0
|
||||
num_projects: 0
|
||||
num_closed_projects: 1
|
||||
num_action_runs: 4
|
||||
num_closed_action_runs: 3
|
||||
is_private: false
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
|
||||
@@ -368,7 +368,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
||||
}
|
||||
|
||||
// 1. update branch in database
|
||||
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
|
||||
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Cols("name").Update(&Branch{
|
||||
Name: to,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
||||
@@ -466,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
|
||||
return currentWhitelist, nil
|
||||
}
|
||||
|
||||
prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
whitelist = make([]int64, 0, len(newWhitelist))
|
||||
for _, userID := range newWhitelist {
|
||||
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
||||
return nil, err
|
||||
} else if !reader {
|
||||
if !prUserIDs.Contains(userID) {
|
||||
continue
|
||||
}
|
||||
whitelist = append(whitelist, userID)
|
||||
|
||||
@@ -862,10 +862,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
||||
if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
case CommentTypeReopen, CommentTypeClose:
|
||||
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// comment type reopen and close event have their own logic to update numbers but not here
|
||||
}
|
||||
// update the issue's updated_unix column
|
||||
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
|
||||
|
||||
@@ -146,8 +146,19 @@ func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User
|
||||
}
|
||||
|
||||
// update repository's issue closed number
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||
return nil, err
|
||||
switch cmtType {
|
||||
case CommentTypeClose, CommentTypeMergePull:
|
||||
// only increase closed count
|
||||
if err := IncrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case CommentTypeReopen:
|
||||
// only decrease closed count
|
||||
if err := DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid comment type: %d", cmtType)
|
||||
}
|
||||
|
||||
return CreateComment(ctx, &CreateCommentOptions{
|
||||
@@ -318,7 +329,6 @@ type NewIssueOptions struct {
|
||||
Issue *Issue
|
||||
LabelIDs []int64
|
||||
Attachments []string // In UUID format.
|
||||
IsPull bool
|
||||
}
|
||||
|
||||
// NewIssueWithIndex creates issue with given index
|
||||
@@ -369,7 +379,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
||||
}
|
||||
}
|
||||
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
|
||||
// Update repository issue total count
|
||||
if err := IncrRepoIssueNumbers(ctx, opts.Repo.ID, opts.Issue.IsPull, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -439,6 +450,42 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
|
||||
})
|
||||
}
|
||||
|
||||
// IncrRepoIssueNumbers increments repository issue numbers.
|
||||
func IncrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, totalOrClosed bool) error {
|
||||
dbSession := db.GetEngine(ctx)
|
||||
var colName string
|
||||
if totalOrClosed {
|
||||
colName = util.Iif(isPull, "num_pulls", "num_issues")
|
||||
} else {
|
||||
colName = util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
|
||||
}
|
||||
_, err := dbSession.Incr(colName).ID(repoID).
|
||||
NoAutoCondition().NoAutoTime().
|
||||
Update(new(repo_model.Repository))
|
||||
return err
|
||||
}
|
||||
|
||||
// DecrRepoIssueNumbers decrements repository issue numbers.
|
||||
func DecrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, includeTotal, includeClosed bool) error {
|
||||
if !includeTotal && !includeClosed {
|
||||
return fmt.Errorf("no numbers to decrease for repo id %d", repoID)
|
||||
}
|
||||
|
||||
dbSession := db.GetEngine(ctx)
|
||||
if includeTotal {
|
||||
colName := util.Iif(isPull, "num_pulls", "num_issues")
|
||||
dbSession = dbSession.Decr(colName)
|
||||
}
|
||||
if includeClosed {
|
||||
closedColName := util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
|
||||
dbSession = dbSession.Decr(closedColName)
|
||||
}
|
||||
_, err := dbSession.ID(repoID).
|
||||
NoAutoCondition().NoAutoTime().
|
||||
Update(new(repo_model.Repository))
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueMentions updates issue-user relations for mentioned users.
|
||||
func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error {
|
||||
if len(mentions) == 0 {
|
||||
|
||||
@@ -181,6 +181,7 @@ func updateMilestone(ctx context.Context, m *Milestone) error {
|
||||
func UpdateMilestoneCounters(ctx context.Context, id int64) error {
|
||||
e := db.GetEngine(ctx)
|
||||
_, err := e.ID(id).
|
||||
Cols("num_issues", "num_closed_issues").
|
||||
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
|
||||
builder.Eq{"milestone_id": id},
|
||||
)).
|
||||
|
||||
@@ -471,13 +471,13 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
|
||||
|
||||
issue.Index = idx
|
||||
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
|
||||
issue.IsPull = true
|
||||
|
||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
LabelIDs: labelIDs,
|
||||
Attachments: uuids,
|
||||
IsPull: true,
|
||||
}); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
|
||||
return err
|
||||
|
||||
@@ -21,6 +21,7 @@ func UpdateOpenMilestoneCounts(x *xorm.Engine) error {
|
||||
|
||||
for _, id := range openMilestoneIDs {
|
||||
_, err := x.ID(id).
|
||||
Cols("num_issues", "num_closed_issues").
|
||||
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
|
||||
builder.Eq{"milestone_id": id},
|
||||
)).
|
||||
|
||||
@@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
|
||||
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
|
||||
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
|
||||
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
|
||||
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
|
||||
teams := make([]*Team, 0, 5)
|
||||
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) {
|
||||
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(teamIDs) == 0 {
|
||||
return teams, nil
|
||||
}
|
||||
err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams)
|
||||
return teams, err
|
||||
}
|
||||
|
||||
func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) {
|
||||
sub := builder.Select("team_id").From("team_unit").
|
||||
Where(builder.Expr("team_unit.team_id = team.id")).
|
||||
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
|
||||
And(builder.Expr("team_unit.access_mode >= ?", mode))
|
||||
|
||||
err := db.GetEngine(ctx).
|
||||
err = db.GetEngine(ctx).
|
||||
Select("team.id").
|
||||
Table("team").
|
||||
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||
And("team_repo.org_id = ?", orgID).
|
||||
And("team_repo.repo_id = ?", repoID).
|
||||
And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
|
||||
And(builder.Or(
|
||||
builder.Expr("team.authorize >= ?", mode),
|
||||
builder.In("team.id", sub),
|
||||
)).
|
||||
OrderBy("name").
|
||||
Find(&teams)
|
||||
|
||||
return teams, err
|
||||
Find(&teamIDs)
|
||||
return teamIDs, err
|
||||
}
|
||||
|
||||
func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) {
|
||||
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(teamIDs) == 0 {
|
||||
return userIDs, nil
|
||||
}
|
||||
err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs)
|
||||
return userIDs, err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -458,54 +459,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
|
||||
return perm.HasAnyUnitAccess(), nil
|
||||
}
|
||||
|
||||
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
|
||||
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
|
||||
if err = repo.LoadOwner(ctx); err != nil {
|
||||
func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) {
|
||||
userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
|
||||
if len(userIDs) == 0 {
|
||||
return users, nil
|
||||
}
|
||||
if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(accesses)+1)
|
||||
if len(accesses) > 0 {
|
||||
userIDs := make([]int64, len(accesses))
|
||||
for i := 0; i < len(accesses); i++ {
|
||||
userIDs[i] = accesses[i].UserID
|
||||
}
|
||||
|
||||
if err = e.In("id", userIDs).Find(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !repo.Owner.IsOrganization() {
|
||||
users = append(users, repo.Owner)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetRepoReaders returns all users that have explicit read access or higher to the repository.
|
||||
func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
|
||||
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead)
|
||||
}
|
||||
|
||||
// GetRepoWriters returns all users that have write access to the repository.
|
||||
func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
|
||||
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
|
||||
}
|
||||
|
||||
// IsRepoReader returns true if user has explicit read access or higher to the repository.
|
||||
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
|
||||
if repo.OwnerID == userID {
|
||||
return true, nil
|
||||
func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) {
|
||||
userIDs := container.Set[int64]{}
|
||||
e := db.GetEngine(ctx)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
|
||||
for _, a := range accesses {
|
||||
userIDs.Add(a.UserID)
|
||||
}
|
||||
|
||||
if err := repo.LoadOwner(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !repo.Owner.IsOrganization() {
|
||||
userIDs.Add(repo.Owner.ID)
|
||||
} else {
|
||||
teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs.AddMultiple(teamUserIDs...)
|
||||
}
|
||||
return userIDs, nil
|
||||
}
|
||||
|
||||
// CheckRepoUnitUser check whether user could visit the unit of this repository
|
||||
|
||||
@@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
|
||||
require.NoError(t, db.Insert(ctx, team))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
||||
|
||||
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
|
||||
perm, err := GetUserRepoPermission(ctx, repo32, user)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
|
||||
@@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) {
|
||||
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
|
||||
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
|
||||
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
|
||||
|
||||
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, user.ID, users[0].ID)
|
||||
|
||||
users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, users)
|
||||
})
|
||||
|
||||
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
|
||||
@@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) {
|
||||
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
|
||||
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
|
||||
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
|
||||
|
||||
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, user.ID, users[0].ID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,6 +49,19 @@ func init() {
|
||||
db.RegisterModel(new(ReviewState))
|
||||
}
|
||||
|
||||
func (rs *ReviewState) GetViewedFileCount() int {
|
||||
if len(rs.UpdatedFiles) == 0 {
|
||||
return 0
|
||||
}
|
||||
var numViewedFiles int
|
||||
for _, state := range rs.UpdatedFiles {
|
||||
if state == Viewed {
|
||||
numViewedFiles++
|
||||
}
|
||||
}
|
||||
return numViewedFiles
|
||||
}
|
||||
|
||||
// GetReviewState returns the ReviewState with all given values prefilled, whether or not it exists in the database.
|
||||
// If the review didn't exist before in the database, it won't afterwards either.
|
||||
// The returned boolean shows whether the review exists in the database
|
||||
@@ -60,18 +73,18 @@ func GetReviewState(ctx context.Context, userID, pullID int64, commitSHA string)
|
||||
|
||||
// UpdateReviewState updates the given review inside the database, regardless of whether it existed before or not
|
||||
// The given map of files with their viewed state will be merged with the previous review, if present
|
||||
func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) error {
|
||||
func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) (*ReviewState, error) {
|
||||
log.Trace("Updating review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, updatedFiles)
|
||||
|
||||
review, exists, err := GetReviewState(ctx, userID, pullID, commitSHA)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exists {
|
||||
review.UpdatedFiles = mergeFiles(review.UpdatedFiles, updatedFiles)
|
||||
} else if previousReview, err := getNewestReviewStateApartFrom(ctx, userID, pullID, commitSHA); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
|
||||
// Overwrite the viewed files of the previous review if present
|
||||
} else if previousReview != nil {
|
||||
@@ -85,11 +98,11 @@ func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA stri
|
||||
if !exists {
|
||||
log.Trace("Inserting new review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, review.UpdatedFiles)
|
||||
_, err := engine.Insert(review)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("Updating already existing review with ID %d (user %d, repo %d, commit %s) with the updated files %v.", review.ID, userID, pullID, commitSHA, review.UpdatedFiles)
|
||||
_, err = engine.ID(review.ID).Update(&ReviewState{UpdatedFiles: review.UpdatedFiles})
|
||||
return err
|
||||
_, err = engine.ID(review.ID).Cols("updated_files").Update(review)
|
||||
return review, err
|
||||
}
|
||||
|
||||
// mergeFiles merges the given maps of files with their viewing state into one map.
|
||||
|
||||
@@ -159,7 +159,7 @@ func RemoveTopicsFromRepo(ctx context.Context, repoID int64) error {
|
||||
builder.In("id",
|
||||
builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}),
|
||||
),
|
||||
).Cols("repo_count").SetExpr("repo_count", "repo_count-1").Update(&Topic{})
|
||||
).Decr("repo_count").Update(&Topic{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/glob"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
@@ -77,7 +77,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := io.ReadAll(f)
|
||||
content, err := util.ReadWithLimit(f, 1024*1024)
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -76,7 +76,7 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
|
||||
if p.IconSVGs[svgID] == "" {
|
||||
p.IconSVGs[svgID] = svgHTML
|
||||
}
|
||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use href="#` + svgID + `"></use></svg>`)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||
|
||||
@@ -25,7 +25,7 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
|
||||
return ""
|
||||
}
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString(`<div class=tw-hidden>`)
|
||||
sb.WriteString(`<div class="svg-icon-container">`)
|
||||
for _, icon := range p.IconSVGs {
|
||||
sb.WriteString(string(icon))
|
||||
}
|
||||
|
||||
@@ -19,12 +19,17 @@ type TreeEntry struct {
|
||||
gogitTreeEntry *object.TreeEntry
|
||||
ptree *Tree
|
||||
|
||||
fullName string
|
||||
|
||||
size int64
|
||||
sized bool
|
||||
}
|
||||
|
||||
// Name returns the name of the entry
|
||||
func (te *TreeEntry) Name() string {
|
||||
if te.fullName != "" {
|
||||
return te.fullName
|
||||
}
|
||||
return te.gogitTreeEntry.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
seen := map[plumbing.Hash]bool{}
|
||||
walker := object.NewTreeWalker(t.gogitTree, true, seen)
|
||||
for {
|
||||
_, entry, err := walker.Next()
|
||||
fullName, entry, err := walker.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
@@ -84,6 +84,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
|
||||
ID: ParseGogitHash(entry.Hash),
|
||||
gogitTreeEntry: &entry,
|
||||
ptree: t,
|
||||
fullName: fullName,
|
||||
}
|
||||
entries = append(entries, convertedEntry)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
@@ -76,7 +75,7 @@ func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTempla
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
content, err := io.ReadAll(r)
|
||||
content, err := util.ReadWithLimit(r, 1024*1024)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read all: %w", err)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ func TestMathRender(t *testing.T) {
|
||||
"$ a $",
|
||||
`<p><code class="language-math">a</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$$b$",
|
||||
`<p><code class="language-math">a</code><code class="language-math">b</code></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$a$ $b$",
|
||||
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
|
||||
@@ -59,7 +63,7 @@ func TestMathRender(t *testing.T) {
|
||||
`<p>a$b $a a$b b$</p>` + nl,
|
||||
},
|
||||
{
|
||||
"a$x$",
|
||||
"a$x$", // Pattern: "word$other$" The real world example is: "Price is between US$1 and US$2.", so don't parse this.
|
||||
`<p>a$x$</p>` + nl,
|
||||
},
|
||||
{
|
||||
@@ -70,6 +74,10 @@ func TestMathRender(t *testing.T) {
|
||||
"$a$ ($b$) [$c$] {$d$}",
|
||||
`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
|
||||
},
|
||||
{
|
||||
"[$a$](link)",
|
||||
`<p><a href="/link" rel="nofollow"><code class="language-math">a</code></a></p>` + nl,
|
||||
},
|
||||
{
|
||||
"$$a$$",
|
||||
`<p><code class="language-math">a</code></p>` + nl,
|
||||
|
||||
@@ -54,6 +54,10 @@ func isAlphanumeric(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
||||
}
|
||||
|
||||
func isInMarkdownLinkText(block text.Reader, lineAfter []byte) bool {
|
||||
return block.PrecendingCharacter() == '[' && bytes.HasPrefix(lineAfter, []byte("]("))
|
||||
}
|
||||
|
||||
// Parse parses the current line and returns a result of parsing.
|
||||
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
@@ -115,7 +119,9 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
|
||||
}
|
||||
// check valid ending character
|
||||
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
|
||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
|
||||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 ||
|
||||
succeedingCharacter == '$' ||
|
||||
isInMarkdownLinkText(block, line[i+len(stopMark):])
|
||||
if checkSurrounding && !isValidEndingChar {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
||||
// Chroma always uses 1-2 letters for style names, we could tolerate it at the moment
|
||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^\w{0,2}$`)).OnElements("span")
|
||||
|
||||
// Line numbers on codepreview
|
||||
policy.AllowAttrs("data-line-number").OnElements("span")
|
||||
|
||||
// Custom URL-Schemes
|
||||
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
||||
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
|
||||
@@ -216,7 +216,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
|
||||
if p.Metadata.Readme != "" {
|
||||
f, err := archive.Open(p.Metadata.Readme)
|
||||
if err == nil {
|
||||
buf, _ := io.ReadAll(f)
|
||||
buf, _ := util.ReadWithLimit(f, 1024*1024)
|
||||
m.Readme = string(buf)
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
} else if strings.EqualFold(hd.Name, "readme.md") {
|
||||
data, err := io.ReadAll(tr)
|
||||
data, err := util.ReadWithLimit(tr, 1024*1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -327,14 +327,14 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
|
||||
|
||||
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
|
||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
|
||||
}
|
||||
}
|
||||
|
||||
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
||||
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
|
||||
if rootCfg.Section(oldSection).HasKey(oldKey) {
|
||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
||||
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ var OAuth2 = struct {
|
||||
InvalidateRefreshTokens bool
|
||||
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
|
||||
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
|
||||
JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"`
|
||||
MaxTokenLength int
|
||||
DefaultApplications []string
|
||||
}{
|
||||
|
||||
@@ -235,9 +235,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
||||
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
|
||||
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
|
||||
}
|
||||
if AcmeEmail == "" {
|
||||
log.Fatal("ACME Email is not set (ACME_EMAIL).")
|
||||
}
|
||||
} else {
|
||||
CertFile = sec.Key("CERT_FILE").String()
|
||||
KeyFile = sec.Key("KEY_FILE").String()
|
||||
|
||||
@@ -250,6 +250,7 @@ func (a *AzureBlobStorage) Delete(path string) error {
|
||||
func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
|
||||
blobClient := a.getBlobClient(path)
|
||||
|
||||
// TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage
|
||||
startTime := time.Now()
|
||||
u, err := blobClient.GetSASURL(sas.BlobPermissions{
|
||||
Read: true,
|
||||
|
||||
@@ -279,20 +279,44 @@ func (m *MinioStorage) Delete(path string) error {
|
||||
}
|
||||
|
||||
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
||||
func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
|
||||
func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
|
||||
// copy serveDirectReqParams
|
||||
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
|
||||
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"")
|
||||
|
||||
// Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head.
|
||||
// So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI.
|
||||
// Detect content type by extension name, only support the well-known safe types for inline rendering.
|
||||
// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
|
||||
ext := path.Ext(name)
|
||||
inlineExtMimeTypes := map[string]string{
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".avif": "image/avif",
|
||||
// ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy
|
||||
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline
|
||||
".pdf": "application/pdf",
|
||||
|
||||
// TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType"
|
||||
}
|
||||
if mimeType, ok := inlineExtMimeTypes[ext]; ok {
|
||||
reqParams.Set("response-content-type", mimeType)
|
||||
reqParams.Set("response-content-disposition", "inline")
|
||||
} else {
|
||||
reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name)))
|
||||
}
|
||||
|
||||
expires := 5 * time.Minute
|
||||
if method == http.MethodHead {
|
||||
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
|
||||
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
|
||||
return u, convertMinioErr(err)
|
||||
}
|
||||
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
|
||||
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
|
||||
return u, convertMinioErr(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) {
|
||||
// ReadWithLimit reads at most "limit" bytes from r into buf.
|
||||
// If EOF or ErrUnexpectedEOF occurs while reading, err will be nil.
|
||||
func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) {
|
||||
return readWithLimit(r, 1024, n)
|
||||
return readWithLimit(r, 4*1024, n)
|
||||
}
|
||||
|
||||
func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) {
|
||||
|
||||
@@ -46,11 +46,15 @@ func RouterMockPoint(pointName string) func(next http.Handler) http.Handler {
|
||||
//
|
||||
// Then the mock function will be executed as a middleware at the mock point.
|
||||
// It only takes effect in testing mode (setting.IsInTesting == true).
|
||||
func RouteMock(pointName string, h any) {
|
||||
func RouteMock(pointName string, h any) func() {
|
||||
if _, ok := routeMockPoints[pointName]; !ok {
|
||||
panic("route mock point not found: " + pointName)
|
||||
}
|
||||
old := routeMockPoints[pointName]
|
||||
routeMockPoints[pointName] = toHandlerProvider(h)
|
||||
return func() {
|
||||
routeMockPoints[pointName] = old
|
||||
}
|
||||
}
|
||||
|
||||
// RouteMockReset resets all mock points (no mock anymore)
|
||||
|
||||
@@ -55,7 +55,7 @@ func NewRouter() *Router {
|
||||
// Use supports two middlewares
|
||||
func (r *Router) Use(middlewares ...any) {
|
||||
for _, m := range middlewares {
|
||||
if m != nil {
|
||||
if !isNilOrFuncNil(m) {
|
||||
r.chiRouter.Use(toHandlerProvider(m))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ more = More
|
||||
buttons.heading.tooltip = Add heading
|
||||
buttons.bold.tooltip = Add bold text
|
||||
buttons.italic.tooltip = Add italic text
|
||||
buttons.strikethrough.tooltip = Add strikethrough text
|
||||
buttons.quote.tooltip = Quote text
|
||||
buttons.code.tooltip = Add code
|
||||
buttons.link.tooltip = Add a link
|
||||
@@ -1481,6 +1482,7 @@ projects.column.new_submit = "Create Column"
|
||||
projects.column.new = "New Column"
|
||||
projects.column.set_default = "Set Default"
|
||||
projects.column.set_default_desc = "Set this column as default for uncategorized issues and pulls"
|
||||
projects.column.default_column_hint = "New issues added to this project will be added to this column"
|
||||
projects.column.delete = "Delete Column"
|
||||
projects.column.deletion_desc = "Deleting a project column moves all related issues to the default column. Continue?"
|
||||
projects.column.color = "Color"
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -148,7 +148,7 @@ func EnumeratePackages(ctx *context.Context) {
|
||||
Timestamp: fileMetadata.Timestamp,
|
||||
Build: fileMetadata.Build,
|
||||
BuildNumber: fileMetadata.BuildNumber,
|
||||
Dependencies: fileMetadata.Dependencies,
|
||||
Dependencies: util.SliceNilAsEmpty(fileMetadata.Dependencies),
|
||||
License: versionMetadata.License,
|
||||
LicenseFamily: versionMetadata.LicenseFamily,
|
||||
HashMD5: pfd.Blob.HashMD5,
|
||||
|
||||
@@ -290,8 +290,8 @@ func PostBlobsUploads(ctx *context.Context) {
|
||||
Creator: ctx.Doer,
|
||||
},
|
||||
); err != nil {
|
||||
switch err {
|
||||
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
|
||||
switch {
|
||||
case errors.Is(err, packages_service.ErrQuotaTotalCount), errors.Is(err, packages_service.ErrQuotaTypeSize), errors.Is(err, packages_service.ErrQuotaTotalSize):
|
||||
apiError(ctx, http.StatusForbidden, err)
|
||||
default:
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
@@ -439,8 +439,8 @@ func PutBlobsUpload(ctx *context.Context) {
|
||||
Creator: ctx.Doer,
|
||||
},
|
||||
); err != nil {
|
||||
switch err {
|
||||
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
|
||||
switch {
|
||||
case errors.Is(err, packages_service.ErrQuotaTotalCount), errors.Is(err, packages_service.ErrQuotaTypeSize), errors.Is(err, packages_service.ErrQuotaTotalSize):
|
||||
apiError(ctx, http.StatusForbidden, err)
|
||||
default:
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
@@ -592,13 +592,10 @@ func PutManifest(ctx *context.Context) {
|
||||
apiErrorDefined(ctx, namedError)
|
||||
} else if errors.Is(err, container_model.ErrContainerBlobNotExist) {
|
||||
apiErrorDefined(ctx, errBlobUnknown)
|
||||
} else if errors.Is(err, packages_service.ErrQuotaTotalCount) || errors.Is(err, packages_service.ErrQuotaTypeSize) || errors.Is(err, packages_service.ErrQuotaTotalSize) {
|
||||
apiError(ctx, http.StatusForbidden, err)
|
||||
} else {
|
||||
switch err {
|
||||
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
|
||||
apiError(ctx, http.StatusForbidden, err)
|
||||
default:
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
@@ -83,9 +82,11 @@ type processManifestTxRet struct {
|
||||
}
|
||||
|
||||
func handleCreateManifestResult(ctx context.Context, err error, mci *manifestCreationInfo, contentStore *packages_module.ContentStore, txRet *processManifestTxRet) (string, error) {
|
||||
if err != nil && txRet.created && txRet.pb != nil {
|
||||
if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil {
|
||||
log.Error("Error deleting package blob from content store: %v", err)
|
||||
if err != nil {
|
||||
if txRet.created && txRet.pb != nil {
|
||||
if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil {
|
||||
log.Error("Error deleting package blob from content store: %v", err)
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
@@ -199,14 +200,14 @@ func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *p
|
||||
if errors.Is(err, container_model.ErrContainerBlobNotExist) {
|
||||
return errManifestBlobUnknown
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("GetContainerBlob: %w", err)
|
||||
}
|
||||
|
||||
size, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{
|
||||
VersionID: pfd.File.VersionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("CalculateFileSize: %w", err)
|
||||
}
|
||||
|
||||
metadata.Manifests = append(metadata.Manifests, &container_module.Manifest{
|
||||
@@ -218,7 +219,7 @@ func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *p
|
||||
|
||||
pv, err := createPackageAndVersion(ctx, mci, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("createPackageAndVersion: %w", err)
|
||||
}
|
||||
|
||||
txRet.pv = pv
|
||||
@@ -241,7 +242,7 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||
if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
|
||||
if !errors.Is(err, packages_model.ErrDuplicatePackage) {
|
||||
log.Error("Error inserting package: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("TryInsertPackage: %w", err)
|
||||
}
|
||||
created = false
|
||||
}
|
||||
@@ -249,7 +250,7 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||
if created {
|
||||
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil {
|
||||
log.Error("Error setting package property: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("InsertProperty(PropertyRepository): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,9 +258,16 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||
|
||||
metadataJSON, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("json.Marshal(metadata): %w", err)
|
||||
}
|
||||
|
||||
// "docker buildx imagetools create" multi-arch operations:
|
||||
// {"type":"oci","is_tagged":false,"platform":"unknown/unknown"}
|
||||
// {"type":"oci","is_tagged":false,"platform":"linux/amd64","layer_creation":["ADD file:9233f6f2237d79659a9521f7e390df217cec49f1a8aa3a12147bbca1956acdb9 in /","CMD [\"/bin/sh\"]"]}
|
||||
// {"type":"oci","is_tagged":false,"platform":"unknown/unknown"}
|
||||
// {"type":"oci","is_tagged":false,"platform":"linux/arm64","layer_creation":["ADD file:df53811312284306901fdaaff0a357a4bf40d631e662fe9ce6d342442e494b6c in /","CMD [\"/bin/sh\"]"]}
|
||||
// {"type":"oci","is_tagged":true,"manifests":[{"platform":"linux/amd64","digest":"sha256:72bb73e706c0dec424d00a1febb21deaf1175a70ead009ad8b159729cfcf5769","size":2819478},{"platform":"linux/arm64","digest":"sha256:9e1426dd084a3221663b85ca1ee99d140c50b153917a5c5604c1f9b78229fd24","size":2716499},{"platform":"unknown/unknown","digest":"sha256:b93f03d0ae11b988243e1b2cd8d29accf5b9670547b7bd8c7d96abecc7283e6e","size":1798},{"platform":"unknown/unknown","digest":"sha256:f034b182ba66366c63a5d195c6dfcd3333c027409c0ac98e55ade36aaa3b2963","size":1798}]}
|
||||
|
||||
_pv := &packages_model.PackageVersion{
|
||||
PackageID: p.ID,
|
||||
CreatorID: mci.Creator.ID,
|
||||
@@ -270,52 +278,43 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
||||
pv, err := packages_model.GetOrInsertVersion(ctx, _pv)
|
||||
if err != nil {
|
||||
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
|
||||
log.Error("Error inserting package: %v", err)
|
||||
return nil, err
|
||||
log.Error("Error GetOrInsertVersion (first try) package: %v", err)
|
||||
return nil, fmt.Errorf("GetOrInsertVersion: first try: %w", err)
|
||||
}
|
||||
|
||||
if container_module.IsMediaTypeImageIndex(mci.MediaType) {
|
||||
if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
|
||||
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// keep download count on overwriting
|
||||
_pv.DownloadCount = pv.DownloadCount
|
||||
if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil {
|
||||
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
|
||||
log.Error("Error inserting package: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
|
||||
return nil, fmt.Errorf("DeletePackageVersionAndReferences: %w", err)
|
||||
}
|
||||
// keep download count on overwriting
|
||||
_pv.DownloadCount = pv.DownloadCount
|
||||
pv, err = packages_model.GetOrInsertVersion(ctx, _pv)
|
||||
if err != nil {
|
||||
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
|
||||
log.Error("Error GetOrInsertVersion (second try) package: %v", err)
|
||||
return nil, fmt.Errorf("GetOrInsertVersion: second try: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("CheckCountQuotaExceeded: %w", err)
|
||||
}
|
||||
|
||||
if mci.IsTagged {
|
||||
if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("InsertOrUpdateProperty(ManifestTagged): %w", err)
|
||||
}
|
||||
} else {
|
||||
if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("DeletePropertiesByName(ManifestTagged): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("DeletePropertiesByName(ManifestReference): %w", err)
|
||||
}
|
||||
for _, manifest := range metadata.Manifests {
|
||||
if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("InsertProperty(ManifestReference): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,9 +216,12 @@ func EditUser(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if form.Email != nil {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
||||
if !user_model.IsEmailDomainAllowed(*form.Email) {
|
||||
err = fmt.Errorf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email)
|
||||
}
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
case user_model.IsErrEmailAlreadyUsed(err):
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
@@ -227,10 +230,6 @@ func EditUser(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !user_model.IsEmailDomainAllowed(*form.Email) {
|
||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
|
||||
}
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
|
||||
@@ -82,6 +82,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/activitypub"
|
||||
"code.gitea.io/gitea/routers/api/v1/admin"
|
||||
@@ -791,7 +792,9 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
ar, err := common.AuthShared(ctx.Base, nil, authMethod)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusUnauthorized, err)
|
||||
msg, ok := auth.ErrAsUserAuthMessage(err)
|
||||
msg = util.Iif(ok, msg, "invalid username, password or token")
|
||||
ctx.APIError(http.StatusUnauthorized, msg)
|
||||
return
|
||||
}
|
||||
ctx.Doer = ar.Doer
|
||||
|
||||
@@ -897,7 +897,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
whitelistUsers = protectBranch.WhitelistUserIDs
|
||||
}
|
||||
if form.ForcePushAllowlistDeployKeys != nil {
|
||||
if form.ForcePushAllowlistUsernames != nil {
|
||||
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
|
||||
@@ -369,11 +369,11 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
|
||||
},
|
||||
Signoff: commonOpts.Signoff,
|
||||
}
|
||||
if commonOpts.Dates.Author.IsZero() {
|
||||
commonOpts.Dates.Author = time.Now()
|
||||
if changeFileOpts.Dates.Author.IsZero() {
|
||||
changeFileOpts.Dates.Author = time.Now()
|
||||
}
|
||||
if commonOpts.Dates.Committer.IsZero() {
|
||||
commonOpts.Dates.Committer = time.Now()
|
||||
if changeFileOpts.Dates.Committer.IsZero() {
|
||||
changeFileOpts.Dates.Committer = time.Now()
|
||||
}
|
||||
ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ func CreateIssueDependency(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
dependencyPerm := getPermissionForRepo(ctx, target.Repo)
|
||||
dependencyPerm := getPermissionForRepo(ctx, dependency.Repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func RemoveIssueDependency(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
dependencyPerm := getPermissionForRepo(ctx, target.Repo)
|
||||
dependencyPerm := getPermissionForRepo(ctx, dependency.Repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
@@ -58,6 +59,13 @@ func GetReleaseByTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if release.IsDraft { // only the users with write access can see draft releases
|
||||
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit_model.TypeReleases) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = release.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -71,8 +71,11 @@ func RequestContextHandler() func(h http.Handler) http.Handler {
|
||||
req = req.WithContext(cache.WithCacheContext(ctx))
|
||||
ds.SetContextValue(httplib.RequestContextKey, req)
|
||||
ds.AddCleanUp(func() {
|
||||
if req.MultipartForm != nil {
|
||||
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
// The req in context might have changed due to the new req.WithContext calls
|
||||
// For example: in NewBaseContext, a new "req" with context is created, and the multipart-form is parsed there.
|
||||
ctxReq := ds.GetContextValue(httplib.RequestContextKey).(*http.Request)
|
||||
if ctxReq.MultipartForm != nil {
|
||||
_ = ctxReq.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
}
|
||||
})
|
||||
next.ServeHTTP(respWriter, req)
|
||||
|
||||
@@ -108,21 +108,19 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
results.RepoName = repoName[:len(repoName)-5]
|
||||
}
|
||||
|
||||
// Check if there is a user redirect for the requested owner
|
||||
redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName)
|
||||
if err == nil {
|
||||
owner, err := user_model.GetUserByID(ctx, redirectedUserID)
|
||||
if err == nil {
|
||||
log.Info("User %s has been redirected to %s", results.OwnerName, owner.Name)
|
||||
results.OwnerName = owner.Name
|
||||
} else {
|
||||
log.Warn("User %s has a redirect to user with ID %d, but no user with this ID could be found. Trying without redirect...", results.OwnerName, redirectedUserID)
|
||||
}
|
||||
}
|
||||
|
||||
owner, err := user_model.GetUserByName(ctx, results.OwnerName)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there is a user redirect for the requested owner
|
||||
redirectedUserID, err := user_model.LookupUserRedirect(ctx, results.OwnerName)
|
||||
if err != nil {
|
||||
// User is fetching/cloning a non-existent repository
|
||||
log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr())
|
||||
ctx.JSON(http.StatusNotFound, private.Response{
|
||||
@@ -130,11 +128,20 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||
})
|
||||
return
|
||||
|
||||
redirectUser, err := user_model.GetUserByID(ctx, redirectedUserID)
|
||||
if err != nil {
|
||||
// User is fetching/cloning a non-existent repository
|
||||
log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr())
|
||||
ctx.JSON(http.StatusNotFound, private.Response{
|
||||
UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("User %s has been redirected to %s", results.OwnerName, redirectUser.Name)
|
||||
results.OwnerName = redirectUser.Name
|
||||
owner = redirectUser
|
||||
}
|
||||
if !owner.IsOrganization() && !owner.IsActive {
|
||||
ctx.JSON(http.StatusForbidden, private.Response{
|
||||
@@ -143,24 +150,33 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
return
|
||||
}
|
||||
|
||||
redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName)
|
||||
if err == nil {
|
||||
redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID)
|
||||
if err == nil {
|
||||
log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name)
|
||||
results.RepoName = redirectedRepo.Name
|
||||
results.OwnerName = redirectedRepo.OwnerName
|
||||
owner.ID = redirectedRepo.OwnerID
|
||||
} else {
|
||||
log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID)
|
||||
}
|
||||
}
|
||||
|
||||
// Now get the Repository and set the results section
|
||||
repoExist := true
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, results.RepoName)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
redirectedRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, results.RepoName)
|
||||
if err == nil {
|
||||
redirectedRepo, err := repo_model.GetRepositoryByID(ctx, redirectedRepoID)
|
||||
if err == nil {
|
||||
log.Info("Repository %s/%s has been redirected to %s/%s", results.OwnerName, results.RepoName, redirectedRepo.OwnerName, redirectedRepo.Name)
|
||||
results.RepoName = redirectedRepo.Name
|
||||
results.OwnerName = redirectedRepo.OwnerName
|
||||
repo = redirectedRepo
|
||||
owner.ID = redirectedRepo.OwnerID
|
||||
} else {
|
||||
log.Warn("Repo %s/%s has a redirect to repo with ID %d, but no repo with this ID could be found. Trying without redirect...", results.OwnerName, results.RepoName, redirectedRepoID)
|
||||
}
|
||||
}
|
||||
|
||||
if repo == nil {
|
||||
repoExist = false
|
||||
if mode == perm.AccessModeRead {
|
||||
// User is fetching/cloning a non-existent repository
|
||||
@@ -170,13 +186,6 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
})
|
||||
return
|
||||
}
|
||||
// else fallthrough (push-to-create may kick in below)
|
||||
} else {
|
||||
log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ func EditUserPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if form.Email != "" {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, u, form.Email); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
|
||||
@@ -277,8 +277,11 @@ type LinkAccountData struct {
|
||||
GothUser goth.User
|
||||
}
|
||||
|
||||
func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
|
||||
func init() {
|
||||
gob.Register(LinkAccountData{})
|
||||
}
|
||||
|
||||
func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
|
||||
v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -287,7 +290,6 @@ func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
|
||||
}
|
||||
|
||||
func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error {
|
||||
gob.Register(LinkAccountData{})
|
||||
return updateSession(ctx, nil, map[string]any{
|
||||
"linkAccountData": linkAccountData,
|
||||
})
|
||||
|
||||
@@ -436,6 +436,7 @@ func ViewProject(ctx *context.Context) {
|
||||
ctx.Data["Project"] = project
|
||||
ctx.Data["IssuesMap"] = issuesMap
|
||||
ctx.Data["Columns"] = columns
|
||||
ctx.Data["Title"] = fmt.Sprintf("%s - %s", project.Title, ctx.ContextUser.DisplayName())
|
||||
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
ctx.ServerError("RenderUserOrgHeader", err)
|
||||
|
||||
@@ -412,6 +412,12 @@ func Rerun(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// rerun is not allowed if the run is not done
|
||||
if !run.Status.IsDone() {
|
||||
ctx.JSONError(ctx.Locale.Tr("actions.runs.not_done"))
|
||||
return
|
||||
}
|
||||
|
||||
// can not rerun job when workflow is disabled
|
||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
||||
cfg := cfgUnit.ActionsConfig()
|
||||
@@ -420,24 +426,22 @@ func Rerun(ctx *context_module.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// reset run's start and stop time when it is done
|
||||
if run.Status.IsDone() {
|
||||
run.PreviousDuration = run.Duration()
|
||||
run.Started = 0
|
||||
run.Stopped = 0
|
||||
run.Status = actions_model.StatusWaiting
|
||||
if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "status", "previous_duration"); err != nil {
|
||||
ctx.ServerError("UpdateRun", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := run.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("run.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
|
||||
// reset run's start and stop time
|
||||
run.PreviousDuration = run.Duration()
|
||||
run.Started = 0
|
||||
run.Stopped = 0
|
||||
run.Status = actions_model.StatusWaiting
|
||||
if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "status", "previous_duration"); err != nil {
|
||||
ctx.ServerError("UpdateRun", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := run.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("run.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
|
||||
|
||||
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
|
||||
if ctx.Written() {
|
||||
return
|
||||
@@ -472,7 +476,7 @@ func Rerun(ctx *context_module.Context) {
|
||||
|
||||
func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error {
|
||||
status := job.Status
|
||||
if !status.IsDone() || !job.Run.Status.IsDone() {
|
||||
if !status.IsDone() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,13 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||
// rely on the results of Contexter
|
||||
if !ctx.IsSigned {
|
||||
// TODO: support digit auth - which would be Authorization header with digit
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
||||
if setting.OAuth2.Enabled {
|
||||
// `Basic realm="Gitea"` tells the GCM to use builtin OAuth2 application: https://github.com/git-ecosystem/git-credential-manager/pull/1442
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
||||
} else {
|
||||
// If OAuth2 is disabled, then use another realm to avoid GCM OAuth2 attempt
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea (Basic Auth)"`)
|
||||
}
|
||||
ctx.HTTPError(http.StatusUnauthorized)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -206,12 +206,11 @@ func SoftDeleteContentHistory(ctx *context.Context) {
|
||||
ctx.NotFound(issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
if history.CommentID != commentID {
|
||||
ctx.NotFound(issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
if commentID != 0 {
|
||||
if history.CommentID != commentID {
|
||||
ctx.NotFound(issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
|
||||
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
|
||||
return
|
||||
|
||||
@@ -753,12 +753,16 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||
// as the viewed information is designed to be loaded only on latest PR
|
||||
// diff and if you're signed in.
|
||||
var reviewState *pull_model.ReviewState
|
||||
var numViewedFiles int
|
||||
if ctx.IsSigned && isShowAllCommits {
|
||||
reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
|
||||
if err != nil {
|
||||
ctx.ServerError("SyncUserSpecificDiff", err)
|
||||
return
|
||||
}
|
||||
if reviewState != nil {
|
||||
numViewedFiles = reviewState.GetViewedFileCount()
|
||||
}
|
||||
}
|
||||
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, beforeCommitID, afterCommitID)
|
||||
@@ -767,10 +771,11 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||
return
|
||||
}
|
||||
ctx.Data["DiffShortStat"] = diffShortStat
|
||||
ctx.Data["NumViewedFiles"] = numViewedFiles
|
||||
|
||||
ctx.PageData["prReview"] = map[string]any{
|
||||
"numberOfFiles": diffShortStat.NumFiles,
|
||||
"numberOfViewedFiles": diff.NumViewedFiles,
|
||||
"numberOfViewedFiles": numViewedFiles,
|
||||
}
|
||||
|
||||
if err = diff.LoadComments(ctx, issue, ctx.Doer, ctx.Data["ShowOutdatedComments"].(bool)); err != nil {
|
||||
@@ -1203,7 +1208,11 @@ func MergePullRequest(ctx *context.Context) {
|
||||
func deleteBranchAfterMergeAndFlashMessage(ctx *context.Context, prID int64) {
|
||||
var fullBranchName string
|
||||
err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, prID, &fullBranchName)
|
||||
if errTr := util.ErrorAsTranslatable(err); errTr != nil {
|
||||
if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) {
|
||||
// no need to show error to end users if no permission or branch not exist
|
||||
log.Debug("DeleteBranchAfterMerge (ignore unnecessary error): %v", err)
|
||||
return
|
||||
} else if errTr := util.ErrorAsTranslatable(err); errTr != nil {
|
||||
ctx.Flash.Error(errTr.Translate(ctx.Locale))
|
||||
return
|
||||
} else if err == nil {
|
||||
|
||||
@@ -331,7 +331,7 @@ func UpdateViewedFiles(ctx *context.Context) {
|
||||
updatedFiles[file] = state
|
||||
}
|
||||
|
||||
if err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil {
|
||||
if _, err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil {
|
||||
ctx.ServerError("UpdateReview", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,9 @@ func SettingsProtectedBranch(c *context.Context) {
|
||||
|
||||
c.Data["PageIsSettingsBranches"] = true
|
||||
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
|
||||
|
||||
users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
|
||||
users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.Repository.GetReaders", err)
|
||||
c.ServerError("GetUsersWithUnitAccess", err)
|
||||
return
|
||||
}
|
||||
c.Data["Users"] = users
|
||||
|
||||
@@ -149,9 +149,9 @@ func setTagsContext(ctx *context.Context) error {
|
||||
}
|
||||
ctx.Data["ProtectedTags"] = protectedTags
|
||||
|
||||
users, err := access_model.GetRepoReaders(ctx, ctx.Repo.Repository)
|
||||
users, err := access_model.GetUsersWithUnitAccess(ctx, ctx.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
ctx.ServerError("Repo.Repository.GetReaders", err)
|
||||
ctx.ServerError("GetUsersWithUnitAccess", err)
|
||||
return err
|
||||
}
|
||||
ctx.Data["Users"] = users
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
|
||||
})
|
||||
|
||||
mockIconForFile := func(id string) template.HTML {
|
||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use xlink:href="#` + id + `"></use></svg>`)
|
||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
||||
}
|
||||
assert.Equal(t, WebDiffFileTree{
|
||||
TreeRoot: WebDiffFileItem{
|
||||
|
||||
@@ -95,6 +95,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []b
|
||||
|
||||
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
|
||||
if err != nil { // fallback to a plain file
|
||||
fi.lfsMeta = &pointer
|
||||
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
|
||||
return buf, dataRc, fi, nil
|
||||
}
|
||||
|
||||
@@ -92,8 +92,6 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
|
||||
ctx.ServerError("Render", err)
|
||||
return true
|
||||
}
|
||||
// to prevent iframe from loading third-party url
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -241,14 +239,17 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
|
||||
// * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d)
|
||||
// * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered
|
||||
|
||||
utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
contentReader := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||
if fInfo.st.IsRepresentableAsText() {
|
||||
contentReader = charset.ToUTF8WithFallbackReader(contentReader, charset.ConvertOpts{})
|
||||
}
|
||||
switch {
|
||||
case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize:
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader):
|
||||
case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, contentReader):
|
||||
// it also sets ctx.Data["FileContent"] and more
|
||||
ctx.Data["IsMarkup"] = true
|
||||
case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader):
|
||||
case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, contentReader):
|
||||
// it also sets ctx.Data["FileContent"] and more
|
||||
ctx.Data["IsDisplayingSource"] = true
|
||||
case handleFileViewRenderImage(ctx, fInfo, buf):
|
||||
|
||||
@@ -133,7 +133,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
|
||||
return nil
|
||||
}
|
||||
defer reader.Close()
|
||||
content, err := io.ReadAll(reader)
|
||||
content, err := util.ReadWithLimit(reader, 5*1024*1024) // 5MB should be enough for a wiki page
|
||||
if err != nil {
|
||||
ctx.ServerError("ReadAll", err)
|
||||
return nil
|
||||
|
||||
@@ -227,6 +227,8 @@ func ctxDataSet(args ...any) func(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
const RouterMockPointBeforeWebRoutes = "before-web-routes"
|
||||
|
||||
// Routes returns all web routes
|
||||
func Routes() *web.Router {
|
||||
routes := web.NewRouter()
|
||||
@@ -285,7 +287,7 @@ func Routes() *web.Router {
|
||||
|
||||
webRoutes := web.NewRouter()
|
||||
webRoutes.Use(mid...)
|
||||
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS())
|
||||
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS(), web.RouterMockPoint(RouterMockPointBeforeWebRoutes))
|
||||
routes.Mount("", webRoutes)
|
||||
return routes
|
||||
}
|
||||
|
||||
@@ -236,12 +236,12 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
}
|
||||
|
||||
if shouldDetectSchedules {
|
||||
if err := handleSchedules(ctx, schedules, commit, input, ref.String()); err != nil {
|
||||
if err := handleSchedules(ctx, schedules, commit, input, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
|
||||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
|
||||
}
|
||||
|
||||
func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
|
||||
@@ -293,7 +293,7 @@ func handleWorkflows(
|
||||
detectedWorkflows []*actions_module.DetectedWorkflow,
|
||||
commit *git.Commit,
|
||||
input *notifyInput,
|
||||
ref string,
|
||||
ref git.RefName,
|
||||
) error {
|
||||
if len(detectedWorkflows) == 0 {
|
||||
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
|
||||
@@ -329,7 +329,7 @@ func handleWorkflows(
|
||||
WorkflowID: dwf.EntryName,
|
||||
TriggerUserID: input.Doer.ID,
|
||||
TriggerUser: input.Doer,
|
||||
Ref: ref,
|
||||
Ref: ref.String(),
|
||||
CommitSHA: commit.ID.String(),
|
||||
IsForkPullRequest: isForkPullRequest,
|
||||
Event: input.Event,
|
||||
@@ -500,13 +500,9 @@ func handleSchedules(
|
||||
detectedWorkflows []*actions_module.DetectedWorkflow,
|
||||
commit *git.Commit,
|
||||
input *notifyInput,
|
||||
ref string,
|
||||
ref git.RefName,
|
||||
) error {
|
||||
branch, err := commit.GetBranchName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if branch != input.Repo.DefaultBranch {
|
||||
if ref.BranchName() != input.Repo.DefaultBranch {
|
||||
log.Trace("commit branch is not default branch in repo")
|
||||
return nil
|
||||
}
|
||||
@@ -552,7 +548,7 @@ func handleSchedules(
|
||||
WorkflowID: dwf.EntryName,
|
||||
TriggerUserID: user_model.ActionsUserID,
|
||||
TriggerUser: user_model.NewActionsUser(),
|
||||
Ref: ref,
|
||||
Ref: ref.String(),
|
||||
CommitSHA: commit.ID.String(),
|
||||
Event: input.Event,
|
||||
EventPayload: string(p),
|
||||
@@ -614,5 +610,5 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
|
||||
// so we use action user as the Doer of the notifyInput
|
||||
notifyInput := newNotifyInputForSchedules(repo)
|
||||
|
||||
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
|
||||
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, git.RefNameFromBranch(repo.DefaultBranch))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -40,6 +41,20 @@ var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
}
|
||||
})
|
||||
|
||||
type ErrUserAuthMessage string
|
||||
|
||||
func (e ErrUserAuthMessage) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func ErrAsUserAuthMessage(err error) (string, bool) {
|
||||
var msg ErrUserAuthMessage
|
||||
if errors.As(err, &msg) {
|
||||
return msg.Error(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Init should be called exactly once when the application starts to allow plugins
|
||||
// to allocate necessary resources
|
||||
func Init() {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
@@ -146,7 +145,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||
return nil, err
|
||||
}
|
||||
if hasWebAuthn {
|
||||
return nil, errors.New("basic authorization is not allowed while WebAuthn enrolled")
|
||||
return nil, ErrUserAuthMessage("basic authorization is not allowed while WebAuthn enrolled")
|
||||
}
|
||||
|
||||
if err := validateTOTP(req, u); err != nil {
|
||||
|
||||
@@ -105,9 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||
}
|
||||
}
|
||||
if source.AttributeAvatar != "" {
|
||||
if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil {
|
||||
return user, err
|
||||
}
|
||||
_ = user_service.UploadAvatar(ctx, user, sr.Avatar)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,10 @@ type Base struct {
|
||||
Locale translation.Locale
|
||||
}
|
||||
|
||||
var ParseMultipartFormMaxMemory = int64(32 << 20)
|
||||
|
||||
func (b *Base) ParseMultipartForm() bool {
|
||||
err := b.Req.ParseMultipartForm(32 << 20)
|
||||
err := b.Req.ParseMultipartForm(ParseMultipartFormMaxMemory)
|
||||
if err != nil {
|
||||
// TODO: all errors caused by client side should be ignored (connection closed).
|
||||
if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
|
||||
@@ -118,7 +118,7 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
||||
if uidChanged {
|
||||
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
||||
} else if cookieToken != "" {
|
||||
// If cookie token presents, re-use existing unexpired token, else generate a new one.
|
||||
// If cookie token present, re-use existing unexpired token, else generate a new one.
|
||||
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
||||
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
||||
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
||||
|
||||
@@ -139,7 +139,7 @@ func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T,
|
||||
|
||||
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
|
||||
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
|
||||
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
log.Error("GetRepoReaders: %v", err)
|
||||
}
|
||||
@@ -542,8 +542,9 @@ func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerifi
|
||||
}
|
||||
if verif.SigningUser != nil {
|
||||
commitVerification.Signer = &api.PayloadUser{
|
||||
Name: verif.SigningUser.Name,
|
||||
Email: verif.SigningUser.Email,
|
||||
UserName: verif.SigningUser.Name,
|
||||
Name: verif.SigningUser.DisplayName(),
|
||||
Email: verif.SigningEmail, // Use the email from the signature, not from the user profile
|
||||
}
|
||||
}
|
||||
return commitVerification
|
||||
@@ -720,7 +721,7 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
|
||||
|
||||
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
||||
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
||||
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||
readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
log.Error("GetRepoReaders: %v", err)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||
@@ -59,6 +64,95 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixUnfinishedRunStatus(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||
total := 0
|
||||
inconsistent := 0
|
||||
fixed := 0
|
||||
|
||||
cond := builder.In("status", []actions_model.Status{
|
||||
actions_model.StatusWaiting,
|
||||
actions_model.StatusRunning,
|
||||
actions_model.StatusBlocked,
|
||||
}).And(builder.Lt{"updated": timeutil.TimeStampNow().AddDuration(-setting.Actions.ZombieTaskTimeout)})
|
||||
|
||||
err := db.Iterate(
|
||||
ctx,
|
||||
cond,
|
||||
func(ctx context.Context, run *actions_model.ActionRun) error {
|
||||
total++
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRunJobsByRunID: %w", err)
|
||||
}
|
||||
expected := actions_model.AggregateJobStatus(jobs)
|
||||
if expected == run.Status {
|
||||
return nil
|
||||
}
|
||||
|
||||
inconsistent++
|
||||
logger.Warn("Run %d (repo_id=%d, index=%d) has status %s, expected %s", run.ID, run.RepoID, run.Index, run.Status, expected)
|
||||
|
||||
if !autofix {
|
||||
return nil
|
||||
}
|
||||
|
||||
run.Started, run.Stopped = getRunTimestampsFromJobs(run, expected, jobs)
|
||||
run.Status = expected
|
||||
|
||||
if err := actions_model.UpdateRun(ctx, run, "status", "started", "stopped"); err != nil {
|
||||
return fmt.Errorf("UpdateRun: %w", err)
|
||||
}
|
||||
fixed++
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Critical("Unable to iterate unfinished runs: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if inconsistent == 0 {
|
||||
logger.Info("Checked %d unfinished runs; all statuses are consistent.", total)
|
||||
return nil
|
||||
}
|
||||
|
||||
if autofix {
|
||||
logger.Info("Checked %d unfinished runs; fixed %d of %d runs.", total, fixed, inconsistent)
|
||||
} else {
|
||||
logger.Warn("Checked %d unfinished runs; found %d runs need to be fixed", total, inconsistent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRunTimestampsFromJobs(run *actions_model.ActionRun, newStatus actions_model.Status, jobs actions_model.ActionJobList) (started, stopped timeutil.TimeStamp) {
|
||||
started = run.Started
|
||||
if (newStatus.IsRunning() || newStatus.IsDone()) && started.IsZero() {
|
||||
var earliest timeutil.TimeStamp
|
||||
for _, job := range jobs {
|
||||
if job.Started > 0 && (earliest.IsZero() || job.Started < earliest) {
|
||||
earliest = job.Started
|
||||
}
|
||||
}
|
||||
started = earliest
|
||||
}
|
||||
|
||||
stopped = run.Stopped
|
||||
if newStatus.IsDone() && stopped.IsZero() {
|
||||
var latest timeutil.TimeStamp
|
||||
for _, job := range jobs {
|
||||
if job.Stopped > latest {
|
||||
latest = job.Stopped
|
||||
}
|
||||
}
|
||||
stopped = latest
|
||||
}
|
||||
|
||||
return started, stopped
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(&Check{
|
||||
Title: "Disable the actions unit for all mirrors",
|
||||
@@ -67,4 +161,11 @@ func init() {
|
||||
Run: disableMirrorActionsUnit,
|
||||
Priority: 9,
|
||||
})
|
||||
Register(&Check{
|
||||
Title: "Fix inconsistent status for unfinished actions runs",
|
||||
Name: "fix-actions-unfinished-run-status",
|
||||
IsDefault: false,
|
||||
Run: fixUnfinishedRunStatus,
|
||||
Priority: 9,
|
||||
})
|
||||
}
|
||||
|
||||
24
services/doctor/actions_test.go
Normal file
24
services/doctor/actions_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_fixUnfinishedRunStatus(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
fixUnfinishedRunStatus(t.Context(), log.GetLogger(log.DEFAULT), true)
|
||||
|
||||
// check if the run is cancelled by id
|
||||
run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 796})
|
||||
assert.Equal(t, actions_model.StatusCancelled, run.Status)
|
||||
}
|
||||
@@ -449,10 +449,9 @@ func getCommitFileLineCountAndLimitedContent(commit *git.Commit, filePath string
|
||||
|
||||
// Diff represents a difference between two git trees.
|
||||
type Diff struct {
|
||||
Start, End string
|
||||
Files []*DiffFile
|
||||
IsIncomplete bool
|
||||
NumViewedFiles int // user-specific
|
||||
Start, End string
|
||||
Files []*DiffFile
|
||||
IsIncomplete bool
|
||||
}
|
||||
|
||||
// LoadComments loads comments into each line
|
||||
@@ -1342,19 +1341,20 @@ outer:
|
||||
// Check whether the file has already been viewed
|
||||
if fileViewedState == pull_model.Viewed {
|
||||
diffFile.IsViewed = true
|
||||
diff.NumViewedFiles++
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly store files that have changed in the database, if any is present at all.
|
||||
// This has the benefit that the "Has Changed" attribute will be present as long as the user does not explicitly mark this file as viewed, so it will even survive a page reload after marking another file as viewed.
|
||||
// On the other hand, this means that even if a commit reverting an unseen change is committed, the file will still be seen as changed.
|
||||
if len(filesChangedSinceLastDiff) > 0 {
|
||||
err := pull_model.UpdateReviewState(ctx, review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff)
|
||||
// Explicitly store files that have changed in the database, if any is present at all.
|
||||
// This has the benefit that the "Has Changed" attribute will be present as long as the user does not explicitly mark this file as viewed, so it will even survive a page reload after marking another file as viewed.
|
||||
// On the other hand, this means that even if a commit reverting an unseen change is committed, the file will still be seen as changed.
|
||||
updatedReview, err := pull_model.UpdateReviewState(ctx, review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff)
|
||||
if err != nil {
|
||||
log.Warn("Could not update review for user %d, pull %d, commit %s and the changed files %v: %v", review.UserID, review.PullID, review.CommitSHA, filesChangedSinceLastDiff, err)
|
||||
return nil, err
|
||||
}
|
||||
// Update the local review to reflect the changes immediately
|
||||
review = updatedReview
|
||||
}
|
||||
|
||||
return review, nil
|
||||
|
||||
@@ -270,16 +270,9 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update the total issue numbers
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
|
||||
if err := issues_model.DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true, issue.IsClosed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if the issue is closed, update the closed issue numbers
|
||||
if issue.IsClosed {
|
||||
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
|
||||
return nil, fmt.Errorf("error updating counters for milestone id %d: %w",
|
||||
|
||||
@@ -5,7 +5,6 @@ package issue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/issue/template"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -65,7 +65,7 @@ func GetTemplateConfig(gitRepo *git.Repository, path string, commit *git.Commit)
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
configContent, err := io.ReadAll(reader)
|
||||
configContent, err := util.ReadWithLimit(reader, 1024*1024)
|
||||
if err != nil {
|
||||
return GetDefaultTemplateConfig(), err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package incoming
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
net_mail "net/mail"
|
||||
"regexp"
|
||||
@@ -221,7 +222,7 @@ loop:
|
||||
err := func() error {
|
||||
r := msg.GetBody(section)
|
||||
if r == nil {
|
||||
return fmt.Errorf("could not get body from message: %w", err)
|
||||
return errors.New("could not get body from message")
|
||||
}
|
||||
|
||||
env, err := enmime.ReadEnvelope(r)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user