Compare commits

...

38 Commits

Author SHA1 Message Date
Lunny Xiao
bb77e6c12d Add changelog for v1.16.1 (#18614)
Add changelog for v1.16.1

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
2022-02-06 12:35:24 +00:00
singuliere
fabc0ad157 comments on migrated issues/prs must link to the comment ID (#18637)
Instead of the issue ID which is not a valid anchor.

Signed-off-by: singuliere <singuliere@autistici.org>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-06 19:40:08 +08:00
zeripath
a13fb154ae Stop logging an error when notes are not found (#18626) (#18635)
Backport #18626

This is an unnecessary logging event.

Fix #18616

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-06 09:43:15 +00:00
zeripath
36c66303df Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
* Only attempt to flush queue if the underlying worker pool is not finished (#18593)

Backport #18593

There is a possible race whereby a worker pool could be cancelled but yet the
underlying queue is not empty. This will lead to flush-all cycling because it
cannot empty the pool.

* On shutdown of Persistant Channel Queues close datachan and empty

Partial Backport #18415

Although we attempt to empty the datachan in queues - due to
races we are better off just closing the channel and forcibly emptying
it in shutdown.

Fix #18618

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Move zero workers warning to debug

Fix #18617

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Update modules/queue/manager.go

Co-authored-by: Gusted <williamzijl7@hotmail.com>

* Update modules/queue/manager.go

Co-authored-by: Gusted <williamzijl7@hotmail.com>

Co-authored-by: Gusted <williamzijl7@hotmail.com>
2022-02-06 14:55:44 +08:00
zeripath
f65e29c077 Ensure that blob-excerpt links work for wiki (#18587) (#18624)
Backport #18587

It appears that the blob-excerpt links do not work on the wiki - likely since their
introduction.

This PR adds support for the wiki on these links.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-06 04:22:20 +00:00
zeripath
a97c8a8966 Attempt to prevent intermittent failure TestGit/xxx/BranchProtectMerge/MergePR (#18451) (#18619)
Backport #18451

One of the repeated intermittent failures we see in testing is a failure due to
branches not being ready to merge.

Prior to the immediate queue implementation we would attempt to flush all the queues
and this would prevent the issue. However, the immediate queue is not flushable so
the flushall is not successful at preventing this.

This PR proposes an alternative solution - wait some time and try again up to 5 times.

If this fails then there is a genuine issue and we should fail.

Related #17719

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-05 20:49:53 +00:00
zeripath
69b7776af5 Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
* Ensure commit-statuses box is sized correctly in headers (#18538)

Backport #18538
Backport #18605

* Ensure commit-statuses box is sized correctly in headers

When viewing commits as commits the commit-status box will be fixed at 30px in height
due to being forced to be this size by a fomantic selector. This PR simply adds a
few more selectors to force this to have height auto.

Fix #18498

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>

* Remove the spurious space in the .ui.right additional selector

Somehow a spurious space sneaked in to #18538
this PR simply removes it.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-02-04 18:03:41 +01:00
zeripath
18c1edf15c Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
Backport #18566

Gitea will currrently resend the same message-id for the closed/merged/reopened
messages for issues. This will cause the merged message to leap to the top of an
email chain and become out of sync.

This PR adds specific suffices for these actions.

Fix #18560

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-04 08:30:36 +00:00
zeripath
70ffec4509 Fix pushing to 1-x-dev docker tag (#18578) (#18579)
* Fix pushing to 1-x-dev docker tag

It appears that #18551 and #18573 have a mistake in that raymond does not have
an {{else}} on {{#equal}}. This PR notes that Sprig has a hasPrefix function
and so we use this with another if.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Fix pushing to 1-x-dev docker tag (part 2)

Although we now have the manifest working, we need to create the images.

Here we adjust the .drone.yml to force building of the images

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Fix pushing to 1-x-dev docker tag

OK now we have the images building we should make sure that the main ones stays
dev and the release/v* ones become *-dev-*

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-03 21:46:24 +00:00
zeripath
bc196a35e1 Collaborator trust model should trust collaborators (#18539) (#18557)
Backport #18539

There was an unintended regression in #17917 which leads to only
repository admin commits being trusted. This PR restores the old logic.

Fix #18501

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-03 11:20:37 -05:00
zeripath
8d31cfbfff Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
Backport #18562

There was an unfortunate regression in #17962 where following detection of the
UserProhibitLogin error the err is cast to a pointer by mistake.

This causes a panic due to an interface error.

Fix #18561

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-03 21:36:42 +08:00
zeripath
e84a432f76 Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
Backport #18551

(Backporting this will enable this target to create 1.16-dev)

One of the problems with our current docker tagging is that although we
have strict version tags, latest and dev we do not have a way for docker
users to track the current release branch. This PR simply suggests that
we use the 1.x-dev tag for these and we build and push these. This will
give users who want or need unreleased bug fixes the option of tracking
the pre-release version instead of simply jumping to dev.

(Also contains backport for #18573)

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-03 12:00:33 +00:00
fnetX (aka fralix)
1fc9f11253 Add dropdown icon to template loading dropdown (#18571) 2022-02-03 11:28:27 +01:00
zeripath
0dfe5fa2d6 Detect conflicts with 3way merge (#18536) (#18537)
Backport #18536

Unforunately git apply --3way reports conflicts differently than standard patches
resulting in conflicts being missed.

Adjust the conflict detection code to account for this different error reporting.

Fix #18514

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-02 14:35:25 +00:00
silverwind
1d17313949 Update JS dependencies, fix lint (#18389) (#18540)
- Update all JS dependencies, including a security issue in mermaid
- Fix new linter errors related to value-keyword-case
- Tested Mermaid and Swagger

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-02-02 08:42:23 +00:00
zeripath
9c318a17f5 Add GetUserTeams (#18499) (#18531)
Backport #18499

* Correct use `UserID` in `SearchTeams`

- Use `UserID` in the `SearchTeams` function, currently it was useless
to pass such information. Now it does a INNER statement to `team_user`
which obtains UserID -> TeamID data.
- Make OrgID optional.
- Resolves #18484

* Seperate searching specific user

* Add condition back

* Use correct struct type

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-02-02 08:40:04 +00:00
zeripath
72fa108cbc Fix review excerpt (#18502) (#18530)
Backport #18502

Currently the "File Changed" tab of a PR is somehow broken. This is also true for the current release 1.16.0.

When you are on the "File Changed" tab, and want to look at code excerpt before or after the code changes, the layout breaks. You can test this on try.gitea.io here: https://try.gitea.io/testnotexisting/magic_enum/pulls/2/files

The problem occurs for the unified view and for the split view.

Kind of the same problem was there for commenting a line of code, this was fixed in #18321 and #18403.

For consistency, I changed the solution of #18321, I removed the ``colspan`` and instead added a ``<td>``. The goal was to have code similarly with the split view.

Also the separator line in the split view was in the wrong column, this was fixed too.* more consistent unified review comment

Fix #18516

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: confusedsushi <confused.sushi@googlemail.com>
2022-02-02 08:38:28 +00:00
zeripath
db134c5d71 Fix for AvatarURL database type (#18487) (#18529)
Backport #18487

Co-authored-by: Viktor Kuzmin <kvaster@gmail.com>
2022-02-02 11:30:52 +08:00
zeripath
73b68015de In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
Currently when calling `gitea` from any shell in rootless docker image it won't respect my `$GITEA_APP_INI`. Which this change it will use that value when defined instead of the default value.

- https://discourse.gitea.io/t/gitea-1-16-0-unable-to-find-configuration-file/4543
- https://gitea.com/gitea/helm-chart/issues/287

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-02-01 17:46:45 -05:00
zeripath
e4919e414f Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
Backport #18533

Unfortunately #17846 was determined to be breaking due to affecting ssh passthrough
however, this discovery happened after the changelog was created. Update the
Changelog to mark this as breaking.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-01 17:09:07 -05:00
Gusted
f7606de13a Use "read" value for General Access (#18496) (#18500)
- Backport of #18496
2022-02-01 20:24:27 +00:00
Gusted
483bda4b2d Use ImagedProvider for gplus oauth2 provider (#18504) (#18505)
- Bacport of #18504

Co-authored-by: 6543 <6543@obermui.de>
2022-02-01 10:45:58 +08:00
techknowlogick
edd57028a1 point to s3 endpoint directly (#18497) (#18510) 2022-01-31 17:50:41 -05:00
zeripath
083b85c655 Fix OAuth Source Edit Page (#18495) (#18503)
Backport #18495

* Fix OAuth Source Edit Page to ensure restricted and group settings are set
* Also tolerate []interface in the groups

Fix #18432

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-01-31 23:36:34 +02:00
Gusted
d5027b6c09 Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
* Don't panic & allow shorter sha1 (#18472)

- Backport of #18472

* Improve comment

Co-authored-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Andrew Thornton <art27@cantab.net>
2022-01-31 01:49:17 +02:00
zeripath
a044ec8b53 Changelog 1.16.0 (#18468)
* Changelog for 1.16.0

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-01-31 01:42:12 +08:00
Aravinth Manivannan
f93d72c09b GitLab reviews may not have the updated_at field set (#18450) (#18461)
Fallback to created_at if that the case and to time.Now() if it is
also missing.

Fixes: #18434

Co-authored-by: Loïc Dachary <loic@dachary.org>

Conflicts:
	services/migrations/gitlab.go
	trivial context conflict because var reviews became reviews := in 1.17
2022-01-30 14:56:39 +01:00
Lunny Xiao
2f22337125 Fix broken when no commits and default branch is not master (#18423)
* Fix broken when no commits and default branch is not master

* Fix IsEmpty check

* Improve codes
2022-01-28 14:48:36 +08:00
zeripath
781ad8a79e Fix broken oauth2 authentication source edit page (#18412) (#18419)
Backport #18412

It appears that there was a broken merge of the edit.tmpl page during the merge
of #16594 - I am not entirely sure how this happened as the PR was correct.

This PR fixes the broken template.

Fix #18388

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-01-26 23:48:33 +00:00
zeripath
cada7202aa Only view milestones from current repo (#18414) (#18417)
Backport #18414

The endpoint /{username}/{reponame}/milestone/{id} is not currently restricted to
the repo. This PR restricts the milestones to those within the repo.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-01-26 22:09:07 +00:00
zeripath
0b331e2213 Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
Backport #18403

Fix #18391
Fix #18320

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-01-25 12:44:18 +00:00
Lunny Xiao
0734ca0132 Fix restore without topic failure (#18387) (#18400)
Co-authored-by: zeripath <art27@cantab.net>
2022-01-25 09:28:28 +02:00
Gusted
0b83cc21be Fix commit's time (#18375) (#18392)
- Backport of #18375
2022-01-25 05:48:56 +00:00
wxiaoguang
b68e605d56 Prevent showing webauthn error for every time visiting /user/settings/security (#18385) (#18386)
Backport #18385
2022-01-25 00:11:49 +00:00
Gusted
42991dc89a Fix partial cloning a repo (#18373) (#18377)
* Fix partial cloning a repo (#18373)

- Backport from: #18373
- Backport isn't 1-1, because the frontport had a refactor in that area,
which v1.16 doesn't have.

* Include diff & use copy

* Add partial clone test

* patch

* Apply suggestions from code review

* globalArgs first

* avoid copy but make GlobalCMDArgs append first

* please linter

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
2022-01-23 21:46:09 +00:00
wxiaoguang
160de9fbda Fix mime-type detection for HTTP server (#18371) 2022-01-23 21:17:20 +08:00
Gusted
d644289fcb Backport: Disable content sniffing on PlainTextBytes (#18365)
- Backport of #18359
2022-01-23 01:58:09 +02:00
6543
fd9ff7cd6f Update github.com/duo-labs/webauthn (#18357) (#18364) 2022-01-22 13:32:10 -05:00
71 changed files with 1267 additions and 577 deletions

View File

@@ -567,7 +567,7 @@ steps:
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint: https://ams3.digitaloceanspaces.com
path_style: true path_style: true
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
@@ -588,7 +588,7 @@ steps:
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint: https://ams3.digitaloceanspaces.com
path_style: true path_style: true
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
@@ -663,7 +663,7 @@ steps:
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint: https://ams3.digitaloceanspaces.com
path_style: true path_style: true
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
@@ -850,6 +850,67 @@ steps:
exclude: exclude:
- pull_request - pull_request
---
kind: pipeline
name: docker-linux-amd64-release-branch
platform:
os: linux
arch: amd64
depends_on:
- testing-amd64
- testing-arm64
trigger:
ref:
- "refs/heads/release/v*"
event:
exclude:
- cron
steps:
- name: fetch-tags
image: docker:git
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
settings:
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.cn
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: techknowlogick/drone-docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.cn
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
event:
exclude:
- pull_request
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
@@ -1006,6 +1067,68 @@ steps:
event: event:
exclude: exclude:
- pull_request - pull_request
---
kind: pipeline
name: docker-linux-arm64-release-branch
platform:
os: linux
arch: arm64
depends_on:
- testing-amd64
- testing-arm64
trigger:
ref:
- "refs/heads/release/v*"
event:
exclude:
- cron
steps:
- name: fetch-tags
image: docker:git
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
settings:
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.cn
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: techknowlogick/drone-docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.cn
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
event:
exclude:
- pull_request
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
@@ -1086,6 +1209,7 @@ steps:
trigger: trigger:
ref: ref:
- refs/heads/main - refs/heads/main
- "refs/heads/release/v*"
event: event:
exclude: exclude:
- cron - cron
@@ -1093,6 +1217,8 @@ trigger:
depends_on: depends_on:
- docker-linux-amd64-release - docker-linux-amd64-release
- docker-linux-arm64-release - docker-linux-arm64-release
- docker-linux-amd64-release-branch
- docker-linux-arm64-release-branch
--- ---
kind: pipeline kind: pipeline
@@ -1126,6 +1252,8 @@ depends_on:
- docker-linux-arm64-release - docker-linux-arm64-release
- docker-linux-amd64-release-version - docker-linux-amd64-release-version
- docker-linux-arm64-release-version - docker-linux-arm64-release-version
- docker-linux-amd64-release-branch
- docker-linux-arm64-release-branch
- docker-manifest - docker-manifest
- docker-manifest-version - docker-manifest-version
- docs - docs

View File

@@ -4,13 +4,45 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io). been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.16.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.16.0-rc1) - 2022-01-19 ## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
* SECURITY
* Update JS dependencies, fix lint (#18389) (#18540)
* ENHANCEMENTS
* Add dropdown icon to label set template dropdown (#18564) (#18571)
* BUGFIXES
* Comments on migrated issues/prs must link to the comment ID (#18630) (#18637)
* Stop logging an error when notes are not found (#18626) (#18635)
* Ensure that blob-excerpt links work for wiki (#18587) (#18624)
* Only attempt to flush queue if the underlying worker pool is not finished (#18593) (#18620)
* Ensure commit-statuses box is sized correctly in headers (#18538) (#18606)
* Prevent merge messages from being sorted to the top of email chains (#18566) (#18588)
* Prevent panic on prohibited user login with oauth2 (#18562) (#18563)
* Collaborator trust model should trust collaborators (#18539) (#18557)
* Detect conflicts with 3way merge (#18536) (#18537)
* In docker rootless use $GITEA_APP_INI if provided (#18524) (#18535)
* Add `GetUserTeams` (#18499) (#18531)
* Fix review excerpt (#18502) (#18530)
* Fix for AvatarURL database type (#18487) (#18529)
* Use `ImagedProvider` for gplus oauth2 provider (#18504) (#18505)
* Fix OAuth Source Edit Page (#18495) (#18503)
* Use "read" value for General Access (#18496) (#18500)
* Prevent NPE on partial match of compare URL and allow short SHA1 compare URLs (#18472) (#18473)
* BUILD
* Make docker gitea/gitea:v1.16-dev etc refer to the latest build on that branch (#18551) (#18569)
* DOCS
* Update 1.16.0 changelog to set #17846 as breaking (#18533) (#18534)
## [1.16.0](https://github.com/go-gitea/gitea/releases/tag/v1.16.0) - 2022-01-30
* BREAKING * BREAKING
* Remove golang vendored directory (#18277) * Remove golang vendored directory (#18277)
* Paginate releases page & set default page size to 10 (#16857) * Paginate releases page & set default page size to 10 (#16857)
* Use shadowing script for docker (#17846)
* Only allow webhook to send requests to allowed hosts (#17482) * Only allow webhook to send requests to allowed hosts (#17482)
* SECURITY * SECURITY
* Disable content sniffing on `PlainTextBytes` (#18359) (#18365)
* Only view milestones from current repo (#18414) (#18417)
* Sanitize user-input on file name (#17666) * Sanitize user-input on file name (#17666)
* Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605) * Use `hostmatcher` to replace `matchlist` to improve blocking of bad hosts in Webhooks (#17605)
* FEATURES * FEATURES
@@ -228,6 +260,16 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Add left padding for chunk header of split diff view (#13397) * Add left padding for chunk header of split diff view (#13397)
* Allow U2F 2FA without TOTP (#11573) * Allow U2F 2FA without TOTP (#11573)
* BUGFIXES * BUGFIXES
* GitLab reviews may not have the updated_at field set (#18450) (#18461)
* Fix detection of no commits when the default branch is not master (#18422) (#18423)
* Fix broken oauth2 authentication source edit page (#18412) (#18419)
* Place inline diff comment dialogs on split diff in 4th and 8th columns (#18403) (#18404)
* Fix restore without topic failure (#18387) (#18400)
* Fix commit's time (#18375) (#18392)
* Fix partial cloning a repo (#18373) (#18377)
* Stop trimming preceding and suffixing spaces from editor filenames (#18334)
* Prevent showing webauthn error for every time visiting `/user/settings/security` (#18386)
* Fix mime-type detection for HTTP server (#18370) (#18371)
* Stop trimming preceding and suffixing spaces from editor filenames (#18334) * Stop trimming preceding and suffixing spaces from editor filenames (#18334)
* Restore propagation of ErrDependenciesLeft (#18325) * Restore propagation of ErrDependenciesLeft (#18325)
* Fix PR comments UI (#18323) * Fix PR comments UI (#18323)
@@ -295,10 +337,22 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* BUILD * BUILD
* Add lockfile-check (#18285) * Add lockfile-check (#18285)
* Don't store assets modified time into generated files (#18193) * Don't store assets modified time into generated files (#18193)
* Use shadowing script for docker (#17846)
* MISC * MISC
* Update JS dependencies (#17611) * Update JS dependencies (#17611)
## [1.15.11](https://github.com/go-gitea/gitea/releases/tag/v1.15.11) - 2022-01-29
* SECURITY
* Only view milestones from current repo (#18414) (#18418)
* BUGFIXES
* Fix broken when no commits and default branch is not master (#18422) (#18424)
* Fix commit's time (#18375) (#18409)
* Fix restore without topic failure (#18387) (#18401)
* Fix mermaid import in 1.15 (it uses ESModule now) (#18382)
* Update to go/text 0.3.7 (#18336)
* MISC
* Upgrade EasyMDE to 2.16.1 (#18278) (#18279)
## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14 ## [1.15.10](https://github.com/go-gitea/gitea/releases/tag/v1.15.10) - 2022-01-14
* BUGFIXES * BUGFIXES

View File

@@ -1,4 +1,4 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
{{#if build.tags}} {{#if build.tags}}
tags: tags:
{{#each build.tags}} {{#each build.tags}}
@@ -8,12 +8,12 @@ tags:
{{/if}} {{/if}}
manifests: manifests:
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-amd64-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64-rootless
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}}-linux-arm64-rootless image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64-rootless
platform: platform:
architecture: arm64 architecture: arm64
os: linux os: linux

View File

@@ -1,4 +1,4 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}dev{{/if}} image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
{{#if build.tags}} {{#if build.tags}}
tags: tags:
{{#each build.tags}} {{#each build.tags}}
@@ -8,13 +8,13 @@ tags:
{{/if}} {{/if}}
manifests: manifests:
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-amd64 image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-amd64
platform: platform:
architecture: amd64 architecture: amd64
os: linux os: linux
- -
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{else}}dev-{{/if}}linux-arm64 image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-linux-arm64
platform: platform:
architecture: arm64 architecture: arm64
os: linux os: linux
variant: v8 variant: v8

View File

@@ -32,7 +32,7 @@ for i in "$@"; do
done done
if [ -z "$APP_INI_SET" ]; then if [ -z "$APP_INI_SET" ]; then
CONF_ARG="-c \"$APP_INI\"" CONF_ARG="-c \"${GITEA_APP_INI:-$APP_INI}\""
fi fi

View File

@@ -18,7 +18,7 @@ params:
description: Git with a cup of tea description: Git with a cup of tea
author: The Gitea Authors author: The Gitea Authors
website: https://docs.gitea.io website: https://docs.gitea.io
version: 1.15.10 version: 1.16.0
minGoVersion: 1.16 minGoVersion: 1.16
goVersion: 1.17 goVersion: 1.17
minNodeVersion: 12.17 minNodeVersion: 12.17

View File

@@ -32,7 +32,7 @@ image as a service. Since there is no database available, one can be initialized
Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`. Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`.
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/` Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/`
If you don't give the volume correct permissions, the container may not start. If you don't give the volume correct permissions, the container may not start.
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
```yaml ```yaml
version: "2" version: "2"

View File

@@ -34,7 +34,7 @@ image as a service. Since there is no database available, one can be initialized
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`. Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
If you don't give the volume correct permissions, the container may not start. If you don't give the volume correct permissions, the container may not start.
For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag. For a stable release you can use `:latest`, `:1` or specify a certain release like `:{{< version >}}`, but if you'd like to use the latest development version of Gitea then you could use the `:dev` tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev`)
```yaml ```yaml
version: "3" version: "3"

6
go.mod
View File

@@ -30,7 +30,7 @@ require (
github.com/denisenkom/go-mssqldb v0.10.0 github.com/denisenkom/go-mssqldb v0.10.0
github.com/djherbis/buffer v1.2.0 github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1 github.com/djherbis/nio/v3 v3.0.1
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/editorconfig/editorconfig-core-go/v2 v2.4.2 github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
github.com/emirpasic/gods v1.12.0 github.com/emirpasic/gods v1.12.0
@@ -54,7 +54,7 @@ require (
github.com/golang-jwt/jwt/v4 v4.2.0 github.com/golang-jwt/jwt/v4 v4.2.0
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-github/v39 v39.2.0 github.com/google/go-github/v39 v39.2.0
github.com/google/uuid v1.2.0 github.com/google/uuid v1.3.0
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/sessions v1.2.1 github.com/gorilla/sessions v1.2.1
@@ -145,8 +145,6 @@ replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
exclude github.com/gofrs/uuid v3.2.0+incompatible exclude github.com/gofrs/uuid v3.2.0+incompatible

9
go.sum
View File

@@ -131,8 +131,6 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4 h1:u3eFvgr4A8IjlAokbFt6XY6VdurX7DEYnQMQ4K2yobc=
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
@@ -276,6 +274,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951 h1:17esZ09oW+29rklBtCVphIguql2u3NxYH2OasFPPZoo=
github.com/duo-labs/webauthn v0.0.0-20220122034320-81aea484c951/go.mod h1:nHy3JdztZWcsjenDeBuE8gn171OAwg12LBN027UP5AE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -491,7 +491,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k= github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -585,8 +584,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=

View File

@@ -8,6 +8,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"os" "os"
"testing" "testing"
@@ -262,23 +263,26 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64)
return func(t *testing.T) { return func(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s",
owner, repo, index, ctx.Token) owner, repo, index, ctx.Token)
req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
MergeMessageField: "doAPIMergePullRequest Merge",
Do: string(repo_model.MergeStyleMerge),
})
resp := ctx.Session.MakeRequest(t, req, NoExpectedStatus) var req *http.Request
var resp *httptest.ResponseRecorder
if resp.Code == http.StatusMethodNotAllowed { for i := 0; i < 6; i++ {
err := api.APIError{}
DecodeJSON(t, resp, &err)
assert.EqualValues(t, "Please try again later", err.Message)
queue.GetManager().FlushAll(context.Background(), 5*time.Second)
req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
MergeMessageField: "doAPIMergePullRequest Merge", MergeMessageField: "doAPIMergePullRequest Merge",
Do: string(repo_model.MergeStyleMerge), Do: string(repo_model.MergeStyleMerge),
}) })
resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus) resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
if resp.Code != http.StatusMethodNotAllowed {
break
}
err := api.APIError{}
DecodeJSON(t, resp, &err)
assert.EqualValues(t, "Please try again later", err.Message)
queue.GetManager().FlushAll(context.Background(), 5*time.Second)
<-time.After(1 * time.Second)
} }
expected := ctx.ExpectedCode expected := ctx.ExpectedCode

View File

@@ -123,6 +123,17 @@ func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
} }
} }
func doPartialGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
return func(t *testing.T) {
assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{
Filter: "blob:none",
}))
exist, err := util.IsExist(filepath.Join(dstLocalPath, "README.md"))
assert.NoError(t, err)
assert.True(t, exist)
}
}
func doGitCloneFail(u *url.URL) func(*testing.T) { func doGitCloneFail(u *url.URL) func(*testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "doGitCloneFail") tmpDir, err := os.MkdirTemp("", "doGitCloneFail")

View File

@@ -69,6 +69,12 @@ func testGit(t *testing.T, u *url.URL) {
t.Run("Clone", doGitClone(dstPath, u)) t.Run("Clone", doGitClone(dstPath, u))
dstPath2, err := os.MkdirTemp("", httpContext.Reponame)
assert.NoError(t, err)
defer util.RemoveAll(dstPath2)
t.Run("Partial Clone", doPartialGitClone(dstPath2, u))
little, big := standardCommitAndPushTest(t, dstPath) little, big := standardCommitAndPushTest(t, dstPath)
littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath) littleLFS, bigLFS := lfsCommitAndPushTest(t, dstPath)
rawTest(t, &httpContext, little, big, littleLFS, bigLFS) rawTest(t, &httpContext, little, big, littleLFS, bigLFS)

View File

@@ -71,7 +71,7 @@ const (
) )
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. // ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error)) []*SignCommit { func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
newCommits := make([]*SignCommit, 0, len(oldCommits)) newCommits := make([]*SignCommit, 0, len(oldCommits))
keyMap := map[string]bool{} keyMap := map[string]bool{}
@@ -81,7 +81,7 @@ func ParseCommitsWithSignature(oldCommits []*user_model.UserCommit, repoTrustMod
Verification: ParseCommitWithSignature(c.Commit), Verification: ParseCommitWithSignature(c.Commit),
} }
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isCodeReader, &keyMap) _ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
newCommits = append(newCommits, signCommit) newCommits = append(newCommits, signCommit)
} }
@@ -455,7 +455,7 @@ func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *use
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
// There are several trust models in Gitea // There are several trust models in Gitea
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isCodeReader func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) { func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) (err error) {
if !verification.Verified { if !verification.Verified {
return return
} }
@@ -500,11 +500,11 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
var has bool var has bool
isMember, has = (*keyMap)[verification.SigningKey.KeyID] isMember, has = (*keyMap)[verification.SigningKey.KeyID]
if !has { if !has {
isMember, err = isCodeReader(verification.SigningUser) isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
(*keyMap)[verification.SigningKey.KeyID] = isMember (*keyMap)[verification.SigningKey.KeyID] = isMember
} }
} else { } else {
isMember, err = isCodeReader(verification.SigningUser) isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
} }
if !isMember { if !isMember {

View File

@@ -18,7 +18,7 @@ func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []
user_model.ValidateCommitsWithEmails(commits), user_model.ValidateCommitsWithEmails(commits),
repo.GetTrustModel(), repo.GetTrustModel(),
func(user *user_model.User) (bool, error) { func(user *user_model.User) (bool, error) {
return IsUserRepoAdmin(repo, user) return IsOwnerMemberCollaborator(repo, user.ID)
}, },
), ),
repo, repo,

View File

@@ -134,22 +134,6 @@ func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error)
return &mile, nil return &mile, nil
} }
// GetMilestoneByID returns the milestone via id .
func GetMilestoneByID(id int64) (*Milestone, error) {
return getMilestoneByID(db.GetEngine(db.DefaultContext), id)
}
func getMilestoneByID(e db.Engine, id int64) (*Milestone, error) {
var m Milestone
has, err := e.ID(id).Get(&m)
if err != nil {
return nil, err
} else if !has {
return nil, ErrMilestoneNotExist{ID: id, RepoID: 0}
}
return &m, nil
}
// UpdateMilestone updates information of given milestone. // UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone, oldIsClosed bool) error { func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
ctx, committer, err := db.TxContext() ctx, committer, err := db.TxContext()

View File

@@ -49,22 +49,67 @@ func init() {
db.RegisterModel(new(TeamUnit)) db.RegisterModel(new(TeamUnit))
} }
// SearchTeamOptions holds the search options // SearchOrgTeamOptions holds the search options
type SearchTeamOptions struct { type SearchOrgTeamOptions struct {
db.ListOptions db.ListOptions
UserID int64
Keyword string Keyword string
OrgID int64 OrgID int64
IncludeDesc bool IncludeDesc bool
} }
// GetUserTeamOptions holds the search options.
type GetUserTeamOptions struct {
db.ListOptions
UserID int64
}
// SearchMembersOptions holds the search options // SearchMembersOptions holds the search options
type SearchMembersOptions struct { type SearchMembersOptions struct {
db.ListOptions db.ListOptions
} }
// SearchTeam search for teams. Caller is responsible to check permissions. // GetUserTeams search for org teams. Caller is responsible to check permissions.
func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
if opts.Page <= 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
// Default limit
opts.PageSize = 10
}
sess := db.GetEngine(db.DefaultContext)
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
And("team_user.uid=?", opts.UserID)
count, err := sess.
Count(new(Team))
if err != nil {
return nil, 0, err
}
if opts.PageSize == -1 {
opts.PageSize = int(count)
} else {
sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}
sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
And("team_user.uid=?", opts.UserID)
teams := make([]*Team, 0, opts.PageSize)
if err = sess.
OrderBy("lower_name").
Find(&teams); err != nil {
return nil, 0, err
}
return teams, count, nil
}
// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
if opts.Page <= 0 { if opts.Page <= 0 {
opts.Page = 1 opts.Page = 1
} }
@@ -196,7 +241,7 @@ func (t *Team) getRepositories(e db.Engine) error {
} }
// GetRepositories returns paginated repositories in team of organization. // GetRepositories returns paginated repositories in team of organization.
func (t *Team) GetRepositories(opts *SearchTeamOptions) error { func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
if opts.Page == 0 { if opts.Page == 0 {
return t.getRepositories(db.GetEngine(db.DefaultContext)) return t.getRepositories(db.GetEngine(db.DefaultContext))
} }
@@ -716,7 +761,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
// DeleteTeam deletes given team. // DeleteTeam deletes given team.
// It's caller's responsibility to assign organization ID. // It's caller's responsibility to assign organization ID.
func DeleteTeam(t *Team) error { func DeleteTeam(t *Team) error {
if err := t.GetRepositories(&SearchTeamOptions{}); err != nil { if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
return err return err
} }
@@ -858,7 +903,7 @@ func AddTeamMember(team *Team, userID int64) error {
} }
// Get team and its repositories. // Get team and its repositories.
if err := team.GetRepositories(&SearchTeamOptions{}); err != nil { if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
return err return err
} }

View File

@@ -46,7 +46,7 @@ func TestTeam_GetRepositories(t *testing.T) {
test := func(teamID int64) { test := func(teamID int64) {
team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team) team := unittest.AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.GetRepositories(&SearchTeamOptions{})) assert.NoError(t, team.GetRepositories(&SearchOrgTeamOptions{}))
assert.Len(t, team.Repos, team.NumRepos) assert.Len(t, team.Repos, team.NumRepos)
for _, repo := range team.Repos { for _, repo := range team.Repos {
unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID}) unittest.AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
@@ -292,7 +292,7 @@ func TestGetTeamMembers(t *testing.T) {
func TestGetUserTeams(t *testing.T) { func TestGetUserTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
test := func(userID int64) { test := func(userID int64) {
teams, _, err := SearchTeam(&SearchTeamOptions{UserID: userID}) teams, _, err := GetUserTeams(&GetUserTeamOptions{UserID: userID})
assert.NoError(t, err) assert.NoError(t, err)
for _, team := range teams { for _, team := range teams {
unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID}) unittest.AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})

View File

@@ -60,7 +60,7 @@ type ExternalLoginUser struct {
LastName string LastName string
NickName string NickName string
Description string Description string
AvatarURL string AvatarURL string `xorm:"TEXT"`
Location string Location string
AccessToken string `xorm:"TEXT"` AccessToken string `xorm:"TEXT"`
AccessTokenSecret string `xorm:"TEXT"` AccessTokenSecret string `xorm:"TEXT"`

View File

@@ -291,6 +291,7 @@ func (ctx *Context) PlainTextBytes(status int, bs []byte) {
} }
ctx.Resp.WriteHeader(status) ctx.Resp.WriteHeader(status)
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
if _, err := ctx.Resp.Write(bs); err != nil { if _, err := ctx.Resp.Write(bs); err != nil {
log.Error("Write bytes failed: %v", err) log.Error("Write bytes failed: %v", err)
} }

View File

@@ -59,27 +59,28 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path)) ctx, _, finished := process.GetManager().AddContext(repo.Ctx, fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path))
defer finished() defer finished()
var cmd *exec.Cmd cmd := exec.CommandContext(ctx, GitExecutable, GlobalCommandArgs...)
switch diffType { switch diffType {
case RawDiffNormal: case RawDiffNormal:
if len(startCommit) != 0 { if len(startCommit) != 0 {
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
} else if commit.ParentCount() == 0 { } else if commit.ParentCount() == 0 {
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"show", endCommit}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"show", endCommit}, fileArgs...)...)
} else { } else {
c, _ := commit.Parent(0) c, _ := commit.Parent(0)
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
} }
case RawDiffPatch: case RawDiffPatch:
if len(startCommit) != 0 { if len(startCommit) != 0 {
query := fmt.Sprintf("%s...%s", endCommit, startCommit) query := fmt.Sprintf("%s...%s", endCommit, startCommit)
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
} else if commit.ParentCount() == 0 { } else if commit.ParentCount() == 0 {
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
} else { } else {
c, _ := commit.Parent(0) c, _ := commit.Parent(0)
query := fmt.Sprintf("%s...%s", endCommit, c.ID.String()) query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...) cmd.Args = append(cmd.Args, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
} }
default: default:
return fmt.Errorf("invalid diffType: %s", diffType) return fmt.Errorf("invalid diffType: %s", diffType)

View File

@@ -22,6 +22,9 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef) notes, err := repo.GetCommit(NotesRef)
if err != nil { if err != nil {
if IsErrNotExist(err) {
return err
}
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
return err return err
} }

View File

@@ -21,6 +21,9 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path) log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef) notes, err := repo.GetCommit(NotesRef)
if err != nil { if err != nil {
if IsErrNotExist(err) {
return err
}
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
return err return err
} }

View File

@@ -79,16 +79,21 @@ func InitRepository(repoPath string, bare bool) error {
// IsEmpty Check if repository is empty. // IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) { func (repo *Repository) IsEmpty() (bool, error) {
var errbuf strings.Builder var errbuf, output strings.Builder
if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil { if err := NewCommandContext(repo.Ctx, "rev-list", "--all", "--count", "--max-count=1").RunWithContext(&RunContext{
if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") || Timeout: -1,
strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") { Dir: repo.Path,
return true, nil Stdout: &output,
} Stderr: &errbuf,
}); err != nil {
return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String()) return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
} }
return false, nil c, err := strconv.Atoi(strings.TrimSpace(output.String()))
if err != nil {
return true, fmt.Errorf("check empty: convert %s to count failed: %v", output.String(), err)
}
return c == 0, nil
} }
// CloneRepoOptions options when clone a repository // CloneRepoOptions options when clone a repository
@@ -101,6 +106,7 @@ type CloneRepoOptions struct {
Shared bool Shared bool
NoCheckout bool NoCheckout bool
Depth int Depth int
Filter string
} }
// Clone clones original repository to target path. // Clone clones original repository to target path.
@@ -141,7 +147,9 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
if opts.Depth > 0 { if opts.Depth > 0 {
cmd.AddArguments("--depth", strconv.Itoa(opts.Depth)) cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
} }
if opts.Filter != "" {
cmd.AddArguments("--filter", opts.Filter)
}
if len(opts.Branch) > 0 { if len(opts.Branch) > 0 {
cmd.AddArguments("-b", opts.Branch) cmd.AddArguments("-b", opts.Branch)
} }

View File

@@ -117,7 +117,7 @@ func (graph *Graph) LoadAndProcessCommits(repository *repo_model.Repository, git
c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit) c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit)
_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) { _ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
return models.IsUserRepoAdmin(repository, user) return models.IsOwnerMemberCollaborator(repository, user.ID)
}, &keyMap) }, &keyMap)
statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{}) statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})

View File

@@ -55,7 +55,7 @@ var (
anySHA1Pattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`) anySHA1Pattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40})(\.\.\.?)([0-9a-f]{40})?(#[-+~_%.a-zA-Z0-9]+)?`) comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,40})(\.\.\.?)([0-9a-f]{7,40})?(#[-+~_%.a-zA-Z0-9]+)?`)
validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`) validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`)
@@ -944,6 +944,13 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
return return
} }
// Ensure that every group (m[0]...m[7]) has a match
for i := 0; i < 8; i++ {
if m[i] == -1 {
return
}
}
urlFull := node.Data[m[0]:m[1]] urlFull := node.Data[m[0]:m[1]]
text1 := base.ShortSha(node.Data[m[2]:m[3]]) text1 := base.ShortSha(node.Data[m[2]:m[3]])
textDots := base.ShortSha(node.Data[m[4]:m[5]]) textDots := base.ShortSha(node.Data[m[4]:m[5]])

View File

@@ -546,3 +546,16 @@ func TestFuzz(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestIssue18471(t *testing.T) {
data := `http://domain/org/repo/compare/783b039...da951ce`
var res strings.Builder
err := PostProcess(&RenderContext{
URLPrefix: "https://example.com",
Metas: localMetas,
}, strings.NewReader(data), &res)
assert.NoError(t, err)
assert.Equal(t, res.String(), "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>")
}

View File

@@ -0,0 +1,41 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package public
import "strings"
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType
var wellKnownMimeTypesLower = map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json",
".mjs": "text/javascript; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",
// well, there are some types missing from the builtin list
".txt": "text/plain; charset=utf-8",
}
// detectWellKnownMimeType will return the mime-type for a well-known file ext name
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
// mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document).
// If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers.
// For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons.
// detectWellKnownMimeType makes the Content-Type for well-known files stable.
func detectWellKnownMimeType(ext string) string {
ext = strings.ToLower(ext)
return wellKnownMimeTypesLower[ext]
}

View File

@@ -95,6 +95,15 @@ func parseAcceptEncoding(val string) map[string]bool {
return types return types
} }
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
// See the comments of detectWellKnownMimeType
func setWellKnownContentType(w http.ResponseWriter, file string) {
mimeType := detectWellKnownMimeType(filepath.Ext(file))
if mimeType != "" {
w.Header().Set("Content-Type", mimeType)
}
}
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool { func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
// use clean to keep the file is a valid path with no . or .. // use clean to keep the file is a valid path with no . or ..
f, err := fs.Open(path.Clean(file)) f, err := fs.Open(path.Clean(file))
@@ -125,6 +134,8 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi
return true return true
} }
setWellKnownContentType(w, file)
serveContent(w, req, fi, fi.ModTime(), f) serveContent(w, req, fi, fi.ModTime(), f)
return true return true
} }

View File

@@ -9,15 +9,12 @@ package public
import ( import (
"bytes" "bytes"
"compress/gzip"
"io" "io"
"mime"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
) )
@@ -66,24 +63,16 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
if encodings["gzip"] { if encodings["gzip"] {
if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok { if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
rd := bytes.NewReader(cf.GzipBytes()) rdGzip := bytes.NewReader(cf.GzipBytes())
w.Header().Set("Content-Encoding", "gzip") // all static files are managed by Gitea, so we can make sure every file has the correct ext name
ctype := mime.TypeByExtension(filepath.Ext(fi.Name())) // then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
if ctype == "" { mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
// read a chunk to decide between utf-8 text and binary if mimeType == "" {
var buf [512]byte mimeType = "application/octet-stream"
grd, _ := gzip.NewReader(rd)
n, _ := io.ReadFull(grd, buf[:])
ctype = http.DetectContentType(buf[:n])
_, err := rd.Seek(0, io.SeekStart) // rewind to output whole file
if err != nil {
log.Error("rd.Seek error: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
} }
w.Header().Set("Content-Type", ctype) w.Header().Set("Content-Type", mimeType)
http.ServeContent(w, req, fi.Name(), modtime, rd) w.Header().Set("Content-Encoding", "gzip")
http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
return return
} }
} }

View File

@@ -72,6 +72,8 @@ type ManagedPool interface {
BoostWorkers() int BoostWorkers() int
// SetPoolSettings sets the user updatable settings for the pool // SetPoolSettings sets the user updatable settings for the pool
SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration) SetPoolSettings(maxNumberOfWorkers, boostWorkers int, timeout time.Duration)
// Done returns a channel that will be closed when the Pool's baseCtx is closed
Done() <-chan struct{}
} }
// ManagedQueueList implements the sort.Interface // ManagedQueueList implements the sort.Interface
@@ -141,7 +143,6 @@ func (m *Manager) Remove(qid int64) {
delete(m.Queues, qid) delete(m.Queues, qid)
m.mutex.Unlock() m.mutex.Unlock()
log.Trace("Queue Manager removed: QID: %d", qid) log.Trace("Queue Manager removed: QID: %d", qid)
} }
// GetManagedQueue by qid // GetManagedQueue by qid
@@ -193,6 +194,17 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
wg.Done() wg.Done()
continue continue
} }
if pool, ok := mq.Managed.(ManagedPool); ok {
// No point into flushing pools when their base's ctx is already done.
select {
case <-pool.Done():
wg.Done()
continue
default:
}
}
allEmpty = false allEmpty = false
if flushable, ok := mq.Managed.(Flushable); ok { if flushable, ok := mq.Managed.(Flushable); ok {
log.Debug("Flushing (flushable) queue: %s", mq.Name) log.Debug("Flushing (flushable) queue: %s", mq.Name)
@@ -225,7 +237,6 @@ func (m *Manager) FlushAll(baseCtx context.Context, timeout time.Duration) error
wg.Wait() wg.Wait()
} }
return nil return nil
} }
// ManagedQueues returns the managed queues // ManagedQueues returns the managed queues

View File

@@ -173,7 +173,6 @@ func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) {
q.internal.(*LevelQueue).Shutdown() q.internal.(*LevelQueue).Shutdown()
GetManager().Remove(q.internal.(*LevelQueue).qid) GetManager().Remove(q.internal.(*LevelQueue).qid)
} }
} }
// Flush flushes the queue and blocks till the queue is empty // Flush flushes the queue and blocks till the queue is empty
@@ -252,14 +251,13 @@ func (q *PersistableChannelQueue) Shutdown() {
q.channelQueue.Wait() q.channelQueue.Wait()
q.internal.(*LevelQueue).Wait() q.internal.(*LevelQueue).Wait()
// Redirect all remaining data in the chan to the internal channel // Redirect all remaining data in the chan to the internal channel
go func() { close(q.channelQueue.dataChan)
log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name) log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
for data := range q.channelQueue.dataChan { for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data) _ = q.internal.Push(data)
atomic.AddInt64(&q.channelQueue.numInQueue, -1) atomic.AddInt64(&q.channelQueue.numInQueue, -1)
} }
log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name) log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
}()
log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name) log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name)
} }

View File

@@ -65,6 +65,11 @@ func NewWorkerPool(handle HandlerFunc, config WorkerPoolConfiguration) *WorkerPo
return pool return pool
} }
// Done returns when this worker pool's base context has been cancelled
func (p *WorkerPool) Done() <-chan struct{} {
return p.baseCtx.Done()
}
// Push pushes the data to the internal channel // Push pushes the data to the internal channel
func (p *WorkerPool) Push(data Data) { func (p *WorkerPool) Push(data Data) {
atomic.AddInt64(&p.numInQueue, 1) atomic.AddInt64(&p.numInQueue, 1)
@@ -90,7 +95,7 @@ func (p *WorkerPool) zeroBoost() {
boost = p.maxNumberOfWorkers - p.numberOfWorkers boost = p.maxNumberOfWorkers - p.numberOfWorkers
} }
if mq != nil { if mq != nil {
log.Warn("WorkerPool: %d (for %s) has zero workers - adding %d temporary workers for %s", p.qid, mq.Name, boost, p.boostTimeout) log.Debug("WorkerPool: %d (for %s) has zero workers - adding %d temporary workers for %s", p.qid, mq.Name, boost, p.boostTimeout)
start := time.Now() start := time.Now()
pid := mq.RegisterWorkers(boost, start, true, start.Add(p.boostTimeout), cancel, false) pid := mq.RegisterWorkers(boost, start, true, start.Add(p.boostTimeout), cancel, false)
@@ -98,7 +103,7 @@ func (p *WorkerPool) zeroBoost() {
mq.RemoveWorkers(pid) mq.RemoveWorkers(pid)
} }
} else { } else {
log.Warn("WorkerPool: %d has zero workers - adding %d temporary workers for %s", p.qid, p.boostWorkers, p.boostTimeout) log.Debug("WorkerPool: %d has zero workers - adding %d temporary workers for %s", p.qid, p.boostWorkers, p.boostTimeout)
} }
p.lock.Unlock() p.lock.Unlock()
p.addWorkers(ctx, cancel, boost) p.addWorkers(ctx, cancel, boost)
@@ -326,7 +331,10 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
log.Trace("WorkerPool: %d Flush", p.qid) log.Trace("WorkerPool: %d Flush", p.qid)
for { for {
select { select {
case data := <-p.dataChan: case data, ok := <-p.dataChan:
if !ok {
return nil
}
p.handle(data) p.handle(data)
atomic.AddInt64(&p.numInQueue, -1) atomic.AddInt64(&p.numInQueue, -1)
case <-p.baseCtx.Done(): case <-p.baseCtx.Done():
@@ -341,7 +349,7 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
func (p *WorkerPool) doWork(ctx context.Context) { func (p *WorkerPool) doWork(ctx context.Context) {
delay := time.Millisecond * 300 delay := time.Millisecond * 300
var data = make([]Data, 0, p.batchLength) data := make([]Data, 0, p.batchLength)
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():

View File

@@ -23,7 +23,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
testTeamRepositories := func(teamID int64, repoIds []int64) { testTeamRepositories := func(teamID int64, repoIds []int64) {
team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team) team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
assert.NoError(t, team.GetRepositories(&models.SearchTeamOptions{}), "%s: GetRepositories", team.Name) assert.NoError(t, team.GetRepositories(&models.SearchOrgTeamOptions{}), "%s: GetRepositories", team.Name)
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name) assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
for i, rid := range repoIds { for i, rid := range repoIds {

732
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"@claviska/jquery-minicolors": "2.3.6", "@claviska/jquery-minicolors": "2.3.6",
"@primer/octicons": "16.2.0", "@primer/octicons": "16.2.0",
"add-asset-webpack-plugin": "2.0.1", "add-asset-webpack-plugin": "2.0.1",
"codemirror": "5.65.0", "codemirror": "5.65.1",
"css-loader": "6.5.1", "css-loader": "6.5.1",
"dropzone": "6.0.0-beta.2", "dropzone": "6.0.0-beta.2",
"easymde": "2.16.1", "easymde": "2.16.1",
@@ -23,13 +23,13 @@
"less": "4.1.2", "less": "4.1.2",
"less-loader": "10.2.0", "less-loader": "10.2.0",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"mermaid": "8.13.9", "mermaid": "8.13.10",
"mini-css-extract-plugin": "2.5.2", "mini-css-extract-plugin": "2.5.2",
"monaco-editor": "0.31.1", "monaco-editor": "0.31.1",
"monaco-editor-webpack-plugin": "7.0.1", "monaco-editor-webpack-plugin": "7.0.1",
"pretty-ms": "7.0.1", "pretty-ms": "7.0.1",
"sortablejs": "1.14.0", "sortablejs": "1.14.0",
"swagger-ui-dist": "4.1.3", "swagger-ui-dist": "4.2.1",
"tributejs": "5.1.3", "tributejs": "5.1.3",
"uint8-to-base64": "0.2.0", "uint8-to-base64": "0.2.0",
"vue": "2.6.14", "vue": "2.6.14",
@@ -37,8 +37,8 @@
"vue-calendar-heatmap": "0.8.4", "vue-calendar-heatmap": "0.8.4",
"vue-loader": "15.9.8", "vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14", "vue-template-compiler": "2.6.14",
"webpack": "5.66.0", "webpack": "5.67.0",
"webpack-cli": "4.9.1", "webpack-cli": "4.9.2",
"workbox-routing": "6.4.2", "workbox-routing": "6.4.2",
"workbox-strategies": "6.4.2", "workbox-strategies": "6.4.2",
"worker-loader": "3.0.8", "worker-loader": "3.0.8",
@@ -55,7 +55,7 @@
"jest-extended": "1.2.0", "jest-extended": "1.2.0",
"jest-raw-loader": "1.0.1", "jest-raw-loader": "1.0.1",
"postcss-less": "6.0.0", "postcss-less": "6.0.0",
"stylelint": "14.2.0", "stylelint": "14.3.0",
"stylelint-config-standard": "24.0.0", "stylelint-config-standard": "24.0.0",
"svgo": "2.8.0", "svgo": "2.8.0",
"updates": "13.0.0" "updates": "13.0.0"

View File

@@ -47,7 +47,7 @@ func ListTeams(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/TeamList" // "$ref": "#/responses/TeamList"
teams, count, err := models.SearchTeam(&models.SearchTeamOptions{ teams, count, err := models.SearchOrgTeams(&models.SearchOrgTeamOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: utils.GetListOptions(ctx),
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
}) })
@@ -90,7 +90,7 @@ func ListUserTeams(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/TeamList" // "$ref": "#/responses/TeamList"
teams, count, err := models.SearchTeam(&models.SearchTeamOptions{ teams, count, err := models.GetUserTeams(&models.GetUserTeamOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: utils.GetListOptions(ctx),
UserID: ctx.User.ID, UserID: ctx.User.ID,
}) })
@@ -533,7 +533,7 @@ func GetTeamRepos(ctx *context.APIContext) {
// "$ref": "#/responses/RepositoryList" // "$ref": "#/responses/RepositoryList"
team := ctx.Org.Team team := ctx.Org.Team
if err := team.GetRepositories(&models.SearchTeamOptions{ if err := team.GetRepositories(&models.SearchOrgTeamOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: utils.GetListOptions(ctx),
}); err != nil { }); err != nil {
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
@@ -707,15 +707,14 @@ func SearchTeam(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
opts := &models.SearchTeamOptions{ opts := &models.SearchOrgTeamOptions{
UserID: ctx.User.ID,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
ListOptions: listOptions, ListOptions: listOptions,
} }
teams, maxResults, err := models.SearchTeam(opts) teams, maxResults, err := models.SearchOrgTeams(opts)
if err != nil { if err != nil {
log.Error("SearchTeam failed: %v", err) log.Error("SearchTeam failed: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{

View File

@@ -192,6 +192,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
RequiredClaimName: form.Oauth2RequiredClaimName, RequiredClaimName: form.Oauth2RequiredClaimName,
RequiredClaimValue: form.Oauth2RequiredClaimValue, RequiredClaimValue: form.Oauth2RequiredClaimValue,
SkipLocalTwoFA: form.SkipLocalTwoFA, SkipLocalTwoFA: form.SkipLocalTwoFA,
GroupClaimName: form.Oauth2GroupClaimName,
RestrictedGroup: form.Oauth2RestrictedGroup,
AdminGroup: form.Oauth2AdminGroup,
} }
} }

View File

@@ -826,7 +826,7 @@ func SignInOAuthCallback(ctx *context.Context) {
u, gothUser, err := oAuth2UserLoginCallback(authSource, ctx.Req, ctx.Resp) u, gothUser, err := oAuth2UserLoginCallback(authSource, ctx.Req, ctx.Resp)
if err != nil { if err != nil {
if user_model.IsErrUserProhibitLogin(err) { if user_model.IsErrUserProhibitLogin(err) {
uplerr := err.(*user_model.ErrUserProhibitLogin) uplerr := err.(user_model.ErrUserProhibitLogin)
log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err) log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(http.StatusOK, "user/auth/prohibit_login") ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
@@ -904,6 +904,10 @@ func claimValueToStringSlice(claimValue interface{}) []string {
switch rawGroup := claimValue.(type) { switch rawGroup := claimValue.(type) {
case []string: case []string:
groups = rawGroup groups = rawGroup
case []interface{}:
for _, group := range rawGroup {
groups = append(groups, fmt.Sprintf("%s", group))
}
default: default:
str := fmt.Sprintf("%s", rawGroup) str := fmt.Sprintf("%s", rawGroup)
groups = strings.Split(str, ",") groups = strings.Split(str, ",")

View File

@@ -319,7 +319,7 @@ func TeamRepositories(ctx *context.Context) {
ctx.Data["Title"] = ctx.Org.Team.Name ctx.Data["Title"] = ctx.Org.Team.Name
ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeams"] = true
ctx.Data["PageIsOrgTeamRepos"] = true ctx.Data["PageIsOrgTeamRepos"] = true
if err := ctx.Org.Team.GetRepositories(&models.SearchTeamOptions{}); err != nil { if err := ctx.Org.Team.GetRepositories(&models.SearchOrgTeamOptions{}); err != nil {
ctx.ServerError("GetRepositories", err) ctx.ServerError("GetRepositories", err)
return return
} }

View File

@@ -351,7 +351,7 @@ func Diff(ctx *context.Context) {
ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0 ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
return models.IsUserRepoAdmin(ctx.Repo.Repository, user) return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
}, nil); err != nil { }, nil); err != nil {
ctx.ServerError("CalculateTrustStatus", err) ctx.ServerError("CalculateTrustStatus", err)
return return

View File

@@ -786,6 +786,15 @@ func ExcerptBlob(ctx *context.Context) {
direction := ctx.FormString("direction") direction := ctx.FormString("direction")
filePath := ctx.FormString("path") filePath := ctx.FormString("path")
gitRepo := ctx.Repo.GitRepo gitRepo := ctx.Repo.GitRepo
if ctx.FormBool("wiki") {
var err error
gitRepo, err = git.OpenRepositoryCtx(ctx, ctx.Repo.Repository.WikiPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return
}
defer gitRepo.Close()
}
chunkSize := gitdiff.BlobExcerptChunkSize chunkSize := gitdiff.BlobExcerptChunkSize
commit, err := gitRepo.GetCommit(commitID) commit, err := gitRepo.GetCommit(commitID)
if err != nil { if err != nil {

View File

@@ -491,7 +491,10 @@ func serviceRPC(h serviceHandler, service string) {
defer finished() defer finished()
var stderr bytes.Buffer var stderr bytes.Buffer
cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir) args := make([]string, len(git.GlobalCommandArgs))
copy(args, git.GlobalCommandArgs)
args = append(args, []string{service, "--stateless-rpc", h.dir}...)
cmd := exec.CommandContext(ctx, git.GitExecutable, args...)
cmd.Dir = h.dir cmd.Dir = h.dir
cmd.Env = append(os.Environ(), h.environ...) cmd.Env = append(os.Environ(), h.environ...)
cmd.Stdout = h.w cmd.Stdout = h.w

View File

@@ -802,7 +802,7 @@ func NewIssue(ctx *context.Context) {
milestoneID := ctx.FormInt64("milestone") milestoneID := ctx.FormInt64("milestone")
if milestoneID > 0 { if milestoneID > 0 {
milestone, err := models.GetMilestoneByID(milestoneID) milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
if err != nil { if err != nil {
log.Error("GetMilestoneByID: %d: %v", milestoneID, err) log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
} else { } else {
@@ -889,7 +889,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
// Check milestone. // Check milestone.
milestoneID := form.MilestoneID milestoneID := form.MilestoneID
if milestoneID > 0 { if milestoneID > 0 {
milestone, err := models.GetMilestoneByID(milestoneID) milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
if err != nil { if err != nil {
ctx.ServerError("GetMilestoneByID", err) ctx.ServerError("GetMilestoneByID", err)
return nil, nil, 0, 0 return nil, nil, 0, 0

View File

@@ -264,7 +264,7 @@ func DeleteMilestone(ctx *context.Context) {
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone // MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
func MilestoneIssuesAndPulls(ctx *context.Context) { func MilestoneIssuesAndPulls(ctx *context.Context) {
milestoneID := ctx.ParamsInt64(":id") milestoneID := ctx.ParamsInt64(":id")
milestone, err := models.GetMilestoneByID(milestoneID) milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
if err != nil { if err != nil {
if models.IsErrMilestoneNotExist(err) { if models.IsErrMilestoneNotExist(err) {
ctx.NotFound("GetMilestoneByID", err) ctx.NotFound("GetMilestoneByID", err)

View File

@@ -800,7 +800,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
verification := asymkey_model.ParseCommitWithSignature(latestCommit) verification := asymkey_model.ParseCommitWithSignature(latestCommit)
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
return models.IsUserRepoAdmin(ctx.Repo.Repository, user) return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
}, nil); err != nil { }, nil); err != nil {
ctx.ServerError("CalculateTrustStatus", err) ctx.ServerError("CalculateTrustStatus", err)
return nil return nil

View File

@@ -5,6 +5,7 @@
package web package web
import ( import (
gocontext "context"
"net/http" "net/http"
"os" "os"
"path" "path"
@@ -952,7 +953,25 @@ func RegisterRoutes(m *web.Route) {
m.Group("/blob_excerpt", func() { m.Group("/blob_excerpt", func() {
m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) }, func(ctx *context.Context) (cancel gocontext.CancelFunc) {
if ctx.FormBool("wiki") {
ctx.Data["PageIsWiki"] = true
repo.MustEnableWiki(ctx)
return
}
reqRepoCodeReader(ctx)
if ctx.Written() {
return
}
cancel = context.RepoRef()(ctx)
if ctx.Written() {
return
}
repo.MustBeNotEmpty(ctx)
return
})
m.Group("/pulls/{index}", func() { m.Group("/pulls/{index}", func() {
m.Get(".diff", repo.DownloadPullDiff) m.Get(".diff", repo.DownloadPullDiff)

View File

@@ -70,13 +70,13 @@ func init() {
})) }))
// named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work
RegisterGothProvider(NewSimpleProvider("gplus", "Google", []string{"email"}, RegisterGothProvider(NewImagedProvider("/assets/img/auth/google.png", NewSimpleProvider("gplus", "Google", []string{"email"},
func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider {
if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration {
scopes = append(scopes, "profile") scopes = append(scopes, "profile")
} }
return google.New(clientKey, secret, callbackURL, scopes...) return google.New(clientKey, secret, callbackURL, scopes...)
})) })))
RegisterGothProvider(NewSimpleProvider("twitter", "Twitter", nil, RegisterGothProvider(NewSimpleProvider("twitter", "Twitter", nil,
func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider {
@@ -107,5 +107,4 @@ func init() {
return microsoftonline.New(clientID, secret, callbackURL, scopes...) return microsoftonline.New(clientID, secret, callbackURL, scopes...)
}, },
)) ))
} }

View File

@@ -14,6 +14,7 @@ import (
"strconv" "strconv"
"strings" "strings"
texttmpl "text/template" texttmpl "text/template"
"time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@@ -297,13 +298,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
} }
// Make sure to compose independent messages to avoid leaking user emails // Make sure to compose independent messages to avoid leaking user emails
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
reference := createReference(ctx.Issue, nil, models.ActionType(0))
msgs := make([]*Message, 0, len(recipients)) msgs := make([]*Message, 0, len(recipients))
for _, recipient := range recipients { for _, recipient := range recipients {
msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String()) msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
msg.SetHeader("Message-ID", "<"+createReference(ctx.Issue, ctx.Comment)+">") msg.SetHeader("Message-ID", "<"+msgID+">")
reference := createReference(ctx.Issue, nil)
msg.SetHeader("In-Reply-To", "<"+reference+">") msg.SetHeader("In-Reply-To", "<"+reference+">")
msg.SetHeader("References", "<"+reference+">") msg.SetHeader("References", "<"+reference+">")
@@ -317,7 +320,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
return msgs, nil return msgs, nil
} }
func createReference(issue *models.Issue, comment *models.Comment) string { func createReference(issue *models.Issue, comment *models.Comment, actionType models.ActionType) string {
var path string var path string
if issue.IsPull { if issue.IsPull {
path = "pulls" path = "pulls"
@@ -328,6 +331,17 @@ func createReference(issue *models.Issue, comment *models.Comment) string {
var extra string var extra string
if comment != nil { if comment != nil {
extra = fmt.Sprintf("/comment/%d", comment.ID) extra = fmt.Sprintf("/comment/%d", comment.ID)
} else {
switch actionType {
case models.ActionCloseIssue, models.ActionClosePullRequest:
extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6)
case models.ActionReopenIssue, models.ActionReopenPullRequest:
extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6)
case models.ActionMergePullRequest:
extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6)
case models.ActionPullRequestReadyForReview:
extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6)
}
} }
return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain) return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)

View File

@@ -6,7 +6,9 @@ package mailer
import ( import (
"bytes" "bytes"
"fmt"
"html/template" "html/template"
"strings"
"testing" "testing"
texttmpl "text/template" texttmpl "text/template"
@@ -15,7 +17,6 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -236,3 +237,115 @@ func TestGenerateAdditionalHeaders(t *testing.T) {
} }
} }
} }
func Test_createReference(t *testing.T) {
_, _, issue, comment := prepareMailerTest(t)
_, _, pullIssue, _ := prepareMailerTest(t)
pullIssue.IsPull = true
type args struct {
issue *models.Issue
comment *models.Comment
actionType models.ActionType
}
tests := []struct {
name string
args args
prefix string
suffix string
}{
{
name: "Open Issue",
args: args{
issue: issue,
actionType: models.ActionCreateIssue,
},
prefix: fmt.Sprintf("%s/issues/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
},
{
name: "Open Pull",
args: args{
issue: pullIssue,
actionType: models.ActionCreatePullRequest,
},
prefix: fmt.Sprintf("%s/pulls/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
},
{
name: "Comment Issue",
args: args{
issue: issue,
comment: comment,
actionType: models.ActionCommentIssue,
},
prefix: fmt.Sprintf("%s/issues/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
},
{
name: "Comment Pull",
args: args{
issue: pullIssue,
comment: comment,
actionType: models.ActionCommentPull,
},
prefix: fmt.Sprintf("%s/pulls/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
},
{
name: "Close Issue",
args: args{
issue: issue,
actionType: models.ActionCloseIssue,
},
prefix: fmt.Sprintf("%s/issues/%d/close/", issue.Repo.FullName(), issue.Index),
},
{
name: "Close Pull",
args: args{
issue: pullIssue,
actionType: models.ActionClosePullRequest,
},
prefix: fmt.Sprintf("%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index),
},
{
name: "Reopen Issue",
args: args{
issue: issue,
actionType: models.ActionReopenIssue,
},
prefix: fmt.Sprintf("%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index),
},
{
name: "Reopen Pull",
args: args{
issue: pullIssue,
actionType: models.ActionReopenPullRequest,
},
prefix: fmt.Sprintf("%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index),
},
{
name: "Merge Pull",
args: args{
issue: pullIssue,
actionType: models.ActionMergePullRequest,
},
prefix: fmt.Sprintf("%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index),
},
{
name: "Ready Pull",
args: args{
issue: pullIssue,
actionType: models.ActionPullRequestReadyForReview,
},
prefix: fmt.Sprintf("%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := createReference(tt.args.issue, tt.args.comment, tt.args.actionType)
if !strings.HasPrefix(got, tt.prefix) {
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
}
if !strings.HasSuffix(got, tt.suffix) {
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
}
})
}
}

View File

@@ -32,8 +32,7 @@ func init() {
} }
// GitlabDownloaderFactory defines a gitlab downloader factory // GitlabDownloaderFactory defines a gitlab downloader factory
type GitlabDownloaderFactory struct { type GitlabDownloaderFactory struct{}
}
// New returns a Downloader related to this factory according MigrateOptions // New returns a Downloader related to this factory according MigrateOptions
func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { func (f *GitlabDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
@@ -184,16 +183,17 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) {
// GetMilestones returns milestones // GetMilestones returns milestones
func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) { func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
var perPage = g.maxPerPage perPage := g.maxPerPage
var state = "all" state := "all"
var milestones = make([]*base.Milestone, 0, perPage) milestones := make([]*base.Milestone, 0, perPage)
for i := 1; ; i++ { for i := 1; ; i++ {
ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{ ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
State: &state, State: &state,
ListOptions: gitlab.ListOptions{ ListOptions: gitlab.ListOptions{
Page: i, Page: i,
PerPage: perPage, PerPage: perPage,
}}, nil, gitlab.WithContext(g.ctx)) },
}, nil, gitlab.WithContext(g.ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -203,7 +203,7 @@ func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
if m.Description != "" { if m.Description != "" {
desc = m.Description desc = m.Description
} }
var state = "open" state := "open"
var closedAt *time.Time var closedAt *time.Time
if m.State != "" { if m.State != "" {
state = m.State state = m.State
@@ -255,8 +255,8 @@ func (g *GitlabDownloader) normalizeColor(val string) string {
// GetLabels returns labels // GetLabels returns labels
func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) { func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
var perPage = g.maxPerPage perPage := g.maxPerPage
var labels = make([]*base.Label, 0, perPage) labels := make([]*base.Label, 0, perPage)
for i := 1; ; i++ { for i := 1; ; i++ {
ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{ ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{ListOptions: gitlab.ListOptions{
Page: i, Page: i,
@@ -327,8 +327,8 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
// GetReleases returns releases // GetReleases returns releases
func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
var perPage = g.maxPerPage perPage := g.maxPerPage
var releases = make([]*base.Release, 0, perPage) releases := make([]*base.Release, 0, perPage)
for i := 1; ; i++ { for i := 1; ; i++ {
ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{ ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
Page: i, Page: i,
@@ -381,7 +381,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
}, },
} }
var allIssues = make([]*base.Issue, 0, perPage) allIssues := make([]*base.Issue, 0, perPage)
issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
if err != nil { if err != nil {
@@ -389,7 +389,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
} }
for _, issue := range issues { for _, issue := range issues {
var labels = make([]*base.Label, 0, len(issue.Labels)) labels := make([]*base.Label, 0, len(issue.Labels))
for _, l := range issue.Labels { for _, l := range issue.Labels {
labels = append(labels, &base.Label{ labels = append(labels, &base.Label{
Name: l, Name: l,
@@ -402,7 +402,7 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er
} }
var reactions []*base.Reaction var reactions []*base.Reaction
var awardPage = 1 awardPage := 1
for { for {
awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) awards, _, err := g.client.AwardEmoji.ListIssueAwardEmoji(g.repoID, issue.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
if err != nil { if err != nil {
@@ -456,9 +456,9 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context) return nil, false, fmt.Errorf("unexpected context: %+v", opts.Context)
} }
var allComments = make([]*base.Comment, 0, g.maxPerPage) allComments := make([]*base.Comment, 0, g.maxPerPage)
var page = 1 page := 1
for { for {
var comments []*gitlab.Discussion var comments []*gitlab.Discussion
@@ -503,7 +503,6 @@ func (g *GitlabDownloader) GetComments(opts base.GetCommentOptions) ([]*base.Com
Created: *c.CreatedAt, Created: *c.CreatedAt,
}) })
} }
} }
if resp.NextPage == 0 { if resp.NextPage == 0 {
break break
@@ -526,7 +525,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
}, },
} }
var allPRs = make([]*base.PullRequest, 0, perPage) allPRs := make([]*base.PullRequest, 0, perPage)
prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx))
if err != nil { if err != nil {
@@ -534,7 +533,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
} }
for _, pr := range prs { for _, pr := range prs {
var labels = make([]*base.Label, 0, len(pr.Labels)) labels := make([]*base.Label, 0, len(pr.Labels))
for _, l := range pr.Labels { for _, l := range pr.Labels {
labels = append(labels, &base.Label{ labels = append(labels, &base.Label{
Name: l, Name: l,
@@ -547,12 +546,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
pr.State = "closed" pr.State = "closed"
} }
var mergeTime = pr.MergedAt mergeTime := pr.MergedAt
if merged && pr.MergedAt == nil { if merged && pr.MergedAt == nil {
mergeTime = pr.UpdatedAt mergeTime = pr.UpdatedAt
} }
var closeTime = pr.ClosedAt closeTime := pr.ClosedAt
if merged && pr.ClosedAt == nil { if merged && pr.ClosedAt == nil {
closeTime = pr.UpdatedAt closeTime = pr.UpdatedAt
} }
@@ -568,7 +567,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
} }
var reactions []*base.Reaction var reactions []*base.Reaction
var awardPage = 1 awardPage := 1
for { for {
awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx))
if err != nil { if err != nil {
@@ -641,13 +640,22 @@ func (g *GitlabDownloader) GetReviews(context base.IssueContext) ([]*base.Review
return nil, err return nil, err
} }
var reviews = make([]*base.Review, 0, len(approvals.ApprovedBy)) var createdAt time.Time
if approvals.CreatedAt != nil {
createdAt = *approvals.CreatedAt
} else if approvals.UpdatedAt != nil {
createdAt = *approvals.UpdatedAt
} else {
createdAt = time.Now()
}
reviews := make([]*base.Review, 0, len(approvals.ApprovedBy))
for _, user := range approvals.ApprovedBy { for _, user := range approvals.ApprovedBy {
reviews = append(reviews, &base.Review{ reviews = append(reviews, &base.Review{
IssueIndex: context.LocalID(), IssueIndex: context.LocalID(),
ReviewerID: int64(user.User.ID), ReviewerID: int64(user.User.ID),
ReviewerName: user.User.Username, ReviewerName: user.User.Username,
CreatedAt: *approvals.UpdatedAt, CreatedAt: createdAt,
// All we get are approvals // All we get are approvals
State: base.ReviewStateApproved, State: base.ReviewStateApproved,
}) })

View File

@@ -8,13 +8,16 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"os" "os"
"strconv"
"testing" "testing"
"time" "time"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/xanzy/go-gitlab"
) )
func TestGitlabDownloadRepo(t *testing.T) { func TestGitlabDownloadRepo(t *testing.T) {
@@ -310,12 +313,14 @@ func TestGitlabDownloadRepo(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assertReviewsEqual(t, []*base.Review{ assertReviewsEqual(t, []*base.Review{
{ {
IssueIndex: 1,
ReviewerID: 4102996, ReviewerID: 4102996,
ReviewerName: "zeripath", ReviewerName: "zeripath",
CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC), CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
State: "APPROVED", State: "APPROVED",
}, },
{ {
IssueIndex: 1,
ReviewerID: 527793, ReviewerID: 527793,
ReviewerName: "axifive", ReviewerName: "axifive",
CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC), CreatedAt: time.Date(2019, 11, 28, 16, 2, 8, 377000000, time.UTC),
@@ -327,6 +332,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assertReviewsEqual(t, []*base.Review{ assertReviewsEqual(t, []*base.Review{
{ {
IssueIndex: 2,
ReviewerID: 4575606, ReviewerID: 4575606,
ReviewerName: "real6543", ReviewerName: "real6543",
CreatedAt: time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC), CreatedAt: time.Date(2020, 4, 19, 19, 24, 21, 108000000, time.UTC),
@@ -334,3 +340,137 @@ func TestGitlabDownloadRepo(t *testing.T) {
}, },
}, rvs) }, rvs)
} }
func gitlabClientMockSetup(t *testing.T) (*http.ServeMux, *httptest.Server, *gitlab.Client) {
// mux is the HTTP request multiplexer used with the test server.
mux := http.NewServeMux()
// server is a test HTTP server used to provide mock API responses.
server := httptest.NewServer(mux)
// client is the Gitlab client being tested.
client, err := gitlab.NewClient("", gitlab.WithBaseURL(server.URL))
if err != nil {
server.Close()
t.Fatalf("Failed to create client: %v", err)
}
return mux, server, client
}
func gitlabClientMockTeardown(server *httptest.Server) {
server.Close()
}
type reviewTestCase struct {
repoID, prID, reviewerID int
reviewerName string
createdAt, updatedAt *time.Time
expectedCreatedAt time.Time
}
func convertTestCase(t reviewTestCase) (func(w http.ResponseWriter, r *http.Request), base.Review) {
var updatedAtField string
if t.updatedAt == nil {
updatedAtField = ""
} else {
updatedAtField = `"updated_at": "` + t.updatedAt.Format(time.RFC3339) + `",`
}
var createdAtField string
if t.createdAt == nil {
createdAtField = ""
} else {
createdAtField = `"created_at": "` + t.createdAt.Format(time.RFC3339) + `",`
}
handler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `
{
"id": 5,
"iid": `+strconv.Itoa(t.prID)+`,
"project_id": `+strconv.Itoa(t.repoID)+`,
"title": "Approvals API",
"description": "Test",
"state": "opened",
`+createdAtField+`
`+updatedAtField+`
"merge_status": "cannot_be_merged",
"approvals_required": 2,
"approvals_left": 1,
"approved_by": [
{
"user": {
"name": "Administrator",
"username": "`+t.reviewerName+`",
"id": `+strconv.Itoa(t.reviewerID)+`,
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
"web_url": "http://localhost:3000/root"
}
}
]
}`)
}
review := base.Review{
IssueIndex: int64(t.prID),
ReviewerID: int64(t.reviewerID),
ReviewerName: t.reviewerName,
CreatedAt: t.expectedCreatedAt,
State: "APPROVED",
}
return handler, review
}
func TestGitlabGetReviews(t *testing.T) {
mux, server, client := gitlabClientMockSetup(t)
defer gitlabClientMockTeardown(server)
repoID := 1324
downloader := &GitlabDownloader{
ctx: context.Background(),
client: client,
repoID: repoID,
}
createdAt := time.Date(2020, 4, 19, 19, 24, 21, 0, time.UTC)
for _, testCase := range []reviewTestCase{
{
repoID: repoID,
prID: 1,
reviewerID: 801,
reviewerName: "someone1",
createdAt: nil,
updatedAt: &createdAt,
expectedCreatedAt: createdAt,
},
{
repoID: repoID,
prID: 2,
reviewerID: 802,
reviewerName: "someone2",
createdAt: &createdAt,
updatedAt: nil,
expectedCreatedAt: createdAt,
},
{
repoID: repoID,
prID: 3,
reviewerID: 803,
reviewerName: "someone3",
createdAt: nil,
updatedAt: nil,
expectedCreatedAt: time.Now(),
},
} {
mock, review := convertTestCase(testCase)
mux.HandleFunc(fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/approvals", testCase.repoID, testCase.prID), mock)
rvs, err := downloader.GetReviews(base.BasicIssueContext(testCase.prID))
assert.NoError(t, err)
assertReviewsEqual(t, []*base.Review{&review}, rvs)
}
}

View File

@@ -223,15 +223,15 @@ func assertRepositoryEqual(t *testing.T, expected, actual *base.Repository) {
} }
func assertReviewEqual(t *testing.T, expected, actual *base.Review) { func assertReviewEqual(t *testing.T, expected, actual *base.Review) {
assert.Equal(t, expected.ID, actual.ID) assert.Equal(t, expected.ID, actual.ID, "ID")
assert.Equal(t, expected.IssueIndex, actual.IssueIndex) assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IsssueIndex")
assert.Equal(t, expected.ReviewerID, actual.ReviewerID) assert.Equal(t, expected.ReviewerID, actual.ReviewerID, "ReviewerID")
assert.Equal(t, expected.ReviewerName, actual.ReviewerName) assert.Equal(t, expected.ReviewerName, actual.ReviewerName, "ReviewerName")
assert.Equal(t, expected.Official, actual.Official) assert.Equal(t, expected.Official, actual.Official, "Official")
assert.Equal(t, expected.CommitID, actual.CommitID) assert.Equal(t, expected.CommitID, actual.CommitID, "CommitID")
assert.Equal(t, expected.Content, actual.Content) assert.Equal(t, expected.Content, actual.Content, "Content")
assertTimeEqual(t, expected.CreatedAt, actual.CreatedAt) assert.WithinDuration(t, expected.CreatedAt, actual.CreatedAt, 10*time.Second)
assert.Equal(t, expected.State, actual.State) assert.Equal(t, expected.State, actual.State, "State")
assertReviewCommentsEqual(t, expected.Comments, actual.Comments) assertReviewCommentsEqual(t, expected.Comments, actual.Comments)
} }

View File

@@ -97,6 +97,9 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) {
bs, err := os.ReadFile(p) bs, err := os.ReadFile(p)
if err != nil { if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err return nil, err
} }

View File

@@ -339,8 +339,10 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
if prConfig.IgnoreWhitespaceConflicts { if prConfig.IgnoreWhitespaceConflicts {
args = append(args, "--ignore-whitespace") args = append(args, "--ignore-whitespace")
} }
is3way := false
if git.CheckGitVersionAtLeast("2.32.0") == nil { if git.CheckGitVersionAtLeast("2.32.0") == nil {
args = append(args, "--3way") args = append(args, "--3way")
is3way = true
} }
args = append(args, patchPath) args = append(args, patchPath)
pr.ConflictedFiles = make([]string, 0, 5) pr.ConflictedFiles = make([]string, 0, 5)
@@ -379,6 +381,9 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
const prefix = "error: patch failed:" const prefix = "error: patch failed:"
const errorPrefix = "error: " const errorPrefix = "error: "
const threewayFailed = "Failed to perform three-way merge..."
const appliedPatchPrefix = "Applied patch to '"
const withConflicts = "' with conflicts."
conflictMap := map[string]bool{} conflictMap := map[string]bool{}
@@ -390,6 +395,8 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
conflict = true conflict = true
filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0])
conflictMap[filepath] = true conflictMap[filepath] = true
} else if is3way && line == threewayFailed {
conflict = true
} else if strings.HasPrefix(line, errorPrefix) { } else if strings.HasPrefix(line, errorPrefix) {
conflict = true conflict = true
for _, suffix := range patchErrorSuffices { for _, suffix := range patchErrorSuffices {
@@ -401,6 +408,12 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
break break
} }
} }
} else if is3way && strings.HasPrefix(line, appliedPatchPrefix) && strings.HasSuffix(line, withConflicts) {
conflict = true
filepath := strings.TrimPrefix(strings.TrimSuffix(line, withConflicts), appliedPatchPrefix)
if filepath != "" {
conflictMap[filepath] = true
}
} }
// only list 10 conflicted files // only list 10 conflicted files
if len(conflictMap) >= 10 { if len(conflictMap) >= 10 {

View File

@@ -286,6 +286,10 @@
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div>
<div class="oauth2_use_custom_url inline field">
<div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}> <input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
</div> </div>
</div> </div>

View File

@@ -56,7 +56,7 @@
<br> <br>
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
<input type="radio" name="permission" value="{{if .PageIsOrgTeamsNew}}read{{else}}{{.Team.AccessMode}}{{end}}" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}> <input type="radio" name="permission" value="read" {{if or .PageIsOrgTeamsNew (eq .Team.AccessMode 1) (eq .Team.AccessMode 2)}}checked{{end}}>
<label>{{.i18n.Tr "org.teams.general_access"}}</label> <label>{{.i18n.Tr "org.teams.general_access"}}</label>
<span class="help">{{.i18n.Tr "org.teams.general_access_helper"}}</span> <span class="help">{{.i18n.Tr "org.teams.general_access_helper"}}</span>
</div> </div>

View File

@@ -75,7 +75,11 @@
<pre class="commit-body" style="display: none;">{{RenderCommitBody .Message $commitRepoLink $.Repository.ComposeMetas}}</pre> <pre class="commit-body" style="display: none;">{{RenderCommitBody .Message $commitRepoLink $.Repository.ComposeMetas}}</pre>
{{end}} {{end}}
</td> </td>
<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td> {{if .Committer}}
<td class="text right aligned">{{TimeSince .Committer.When $.Lang}}</td>
{{else}}
<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td>
{{end}}
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View File

@@ -4,40 +4,35 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"> <td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}
</td> </td>
<td colspan="5" class="lines-code lines-code-old ">{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td> <td colspan="5" class="lines-code lines-code-old ">{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
{{else}} {{else}}
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td> <td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="blob-excerpt lines-escape lines-escape-old">{{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
<td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td> <td class="blob-excerpt lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td>
<td class="blob-excerpt lines-code lines-code-old halfwidth">{{/* <td class="blob-excerpt lines-code lines-code-old halfwidth">{{/*
*/}}{{if $line.LeftIdx}}{{/* */}}<code {{if and $line.LeftIdx $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{if $line.LeftIdx}}{{$inlineDiff.Content}}{{end}}</code>{{/*
*/}}{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code>{{/* */}}</td>
*/}}{{else}}{{/*
*/}}<code class="code-inner"></code>{{/*
*/}}{{end}}{{/*
*/}}</td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td> <td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td>
<td class="blob-excerpt lines-escape lines-escape-new">{{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
<td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td> <td class="blob-excerpt lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="mono" data-type-marker=""></span>{{end}}</td>
<td class="blob-excerpt lines-code lines-code-new halfwidth">{{/* <td class="blob-excerpt lines-code lines-code-new halfwidth">{{/*
*/}}{{if $line.RightIdx}}{{/* */}}<code {{if and $line.RightIdx $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{if $line.RightIdx}}{{$inlineDiff.Content}}{{end}}</code>{{/*
*/}}{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code>{{/*
*/}}{{else}}{{/*
*/}}<code class="code-inner"></code>{{/*
*/}}{{end}}{{/*
*/}}</td> */}}</td>
{{end}} {{end}}
</tr> </tr>
@@ -48,17 +43,17 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td colspan="2" class="lines-num"> <td colspan="2" class="lines-num">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.PageIsWiki}}" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}
@@ -67,8 +62,10 @@
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td> <td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"><span rel="{{if $line.LeftIdx}}diff-{{Sha1 $.fileName}}L{{$line.LeftIdx}}{{end}}"></span></td>
<td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td> <td class="lines-num lines-num-new" data-line-num="{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}"><span rel="{{if $line.RightIdx}}diff-{{Sha1 $.fileName}}R{{$line.RightIdx}}{{end}}"></span></td>
{{end}} {{end}}
{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}
<td class="blob-excerpt lines-escape">{{if $inlineDiff.EscapeStatus.Escaped}}<a href="" class="toggle-escape-button" title="{{$.i18n.Tr "repo.line_unicode"}}"></a>{{end}}</td>
<td class="blob-excerpt lines-type-marker"><span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td> <td class="blob-excerpt lines-type-marker"><span class="mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
<td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">{{$inlineDiff := $.section.GetComputedInlineDiffFor $line}}<code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td> <td class="blob-excerpt lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}"><code {{if $inlineDiff.EscapeStatus.Escaped}}class="code-inner has-escaped" title="{{$.i18n.Tr "repo.line_unicode"}}"{{else}}class="code-inner"{{end}}>{{$inlineDiff.Content}}</code></td>
</tr> </tr>
{{end}} {{end}}
{{end}} {{end}}

View File

@@ -7,17 +7,17 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td class="lines-num lines-num-old"> <td class="lines-num lines-num-old">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}

View File

@@ -6,17 +6,17 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td colspan="2" class="lines-num"> <td colspan="2" class="lines-num">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}"> <a role="button" class="blob-excerpt" data-url="{{$.root.RepoLink}}/blob_excerpt/{{PathEscape $.root.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=&wiki={{$.root.PageIsWiki}}" data-anchor="diff-{{Sha1 $file.Name}}K{{$line.SectionInfo.RightIdx}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}

View File

@@ -1,12 +1,6 @@
<div class="ui centered grid"> <div class="ui centered grid">
<div class="twelve wide computer column"> <div class="twelve wide computer column">
<div class="ui attached left aligned segment"> <div class="ui attached left aligned segment">
<!-- <h4 class="ui header">
{{.i18n.Tr "repo.issues.label_templates.title"}}
<a target="_blank" rel="noopener noreferrer" href="https://discuss.gogs.io/t/how-to-use-predefined-label-templates/599">
<span class="octicon octicon-question"></span>
</a>
</h4> -->
<p>{{.i18n.Tr "repo.issues.label_templates.info"}}</p> <p>{{.i18n.Tr "repo.issues.label_templates.info"}}</p>
<br/> <br/>
<form class="ui form center" action="{{.Link}}/initialize" method="post"> <form class="ui form center" action="{{.Link}}/initialize" method="post">
@@ -20,6 +14,7 @@
<div class="item" data-value="{{$template}}">{{$template}}<br/><i>({{$labels}})</i></div> <div class="item" data-value="{{$template}}">{{$template}}<br/><i>({{$labels}})</i></div>
{{end}} {{end}}
</div> </div>
{{svg "octicon-triangle-down" 18 "dropdown icon"}}
</div> </div>
</div> </div>
<button type="submit" class="ui blue button">{{.i18n.Tr "repo.issues.label_templates.use"}}</button> <button type="submit" class="ui blue button">{{.i18n.Tr "repo.issues.label_templates.use"}}</button>

View File

@@ -28,7 +28,7 @@
{{ .OriginalAuthor }} {{ .OriginalAuthor }}
</span> </span>
<span class="text grey"> <span class="text grey">
{{$.i18n.Tr "repo.issues.commented_at" (.Issue.HashTag|Escape) $createdStr | Safe}} {{if $.Repository.OriginalURL}} {{$.i18n.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}} {{if $.Repository.OriginalURL}}
</span> </span>
<span class="text migrate"> <span class="text migrate">
({{$.i18n.Tr "repo.migrated_from" ($.Repository.OriginalURL|Escape) ($.Repository.GetOriginalURLHostname|Escape) | Safe }}){{end}} ({{$.i18n.Tr "repo.migrated_from" ($.Repository.OriginalURL|Escape) ($.Repository.GetOriginalURLHostname|Escape) | Safe }}){{end}}

View File

@@ -34,7 +34,7 @@
</span> </span>
{{end}} {{end}}
</th> </th>
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}{{end}}</th> <th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When $.Lang}}{{end}}{{end}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -496,13 +496,17 @@ export function initRepoPullRequestReview() {
<tr class="add-comment" data-line-type="${lineType}"> <tr class="add-comment" data-line-type="${lineType}">
${isSplit ? ` ${isSplit ? `
<td class="lines-num"></td> <td class="lines-num"></td>
<td class="lines-escape"></td>
<td class="lines-type-marker"></td> <td class="lines-type-marker"></td>
<td class="add-comment-left"></td> <td class="add-comment-left"></td>
<td class="lines-num"></td> <td class="lines-num"></td>
<td class="lines-escape"></td>
<td class="lines-type-marker"></td> <td class="lines-type-marker"></td>
<td class="add-comment-right"></td> <td class="add-comment-right"></td>
` : ` ` : `
<td colspan="3" class="lines-num"></td> <td class="lines-num"></td>
<td class="lines-num"></td>
<td class="lines-escape"></td>
<td class="add-comment-left add-comment-right" colspan="2"></td> <td class="add-comment-left add-comment-right" colspan="2"></td>
`} `}
</tr>`); </tr>`);

View File

@@ -150,13 +150,12 @@ export function initUserAuthWebAuthnRegister() {
return; return;
} }
if (!detectWebAuthnSupport()) {
return;
}
$('#webauthn-error').modal({allowMultiple: false}); $('#webauthn-error').modal({allowMultiple: false});
$('#register-webauthn').on('click', (e) => { $('#register-webauthn').on('click', (e) => {
e.preventDefault(); e.preventDefault();
if (!detectWebAuthnSupport()) {
return;
}
webAuthnRegisterRequest(); webAuthnRegisterRequest();
}); });
} }

View File

@@ -4,6 +4,13 @@
// otherwise some part of the popup will be hidden by viewport boundary // otherwise some part of the popup will be hidden by viewport boundary
max-height: 45vh; max-height: 45vh;
max-width: 60vw; max-width: 60vw;
&.ui.right {
// Override `.ui.attached.header .right:not(.dropdown) height: 30px;` which would otherwise lead to
// the status popup box having its height fixed at 30px. See https://github.com/go-gitea/gitea/issues/18498
height: auto;
}
overflow: auto; overflow: auto;
padding: 0; padding: 0;
@@ -3039,7 +3046,7 @@ td.blob-excerpt {
} }
.file-info-entry + .file-info-entry { .file-info-entry + .file-info-entry {
border-left: 1px solid currentColor; border-left: 1px solid currentcolor;
margin-left: 8px; margin-left: 8px;
padding-left: 8px; padding-left: 8px;
} }
@@ -3186,7 +3193,7 @@ td.blob-excerpt {
background: var(--color-diff-inactive); background: var(--color-diff-inactive);
} }
.code-diff-split tbody tr td:nth-child(4) { .code-diff-split tbody tr td:nth-child(5) {
border-left: 1px solid var(--color-secondary); border-left: 1px solid var(--color-secondary);
} }

View File

@@ -1,7 +1,7 @@
.svg { .svg {
display: inline-block; display: inline-block;
vertical-align: text-top; vertical-align: text-top;
fill: currentColor; fill: currentcolor;
.middle & { .middle & {
vertical-align: middle; vertical-align: middle;

View File

@@ -17,7 +17,7 @@
} }
text { text {
fill: currentColor !important; fill: currentcolor !important;
} }
.total-contributions { .total-contributions {

View File

@@ -26,6 +26,6 @@ body {
.swagger-back-link svg { .swagger-back-link svg {
color: inherit; color: inherit;
fill: currentColor; fill: currentcolor;
margin-right: .5rem; margin-right: .5rem;
} }