Compare commits

..

83 Commits

Author SHA1 Message Date
6543
f460b7543e Changelog v1.16.4 (#19081) 2022-03-14 21:55:33 +01:00
6543
1cb649525d Restrict email address validation (#17688) (#19085)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-03-14 20:51:58 +01:00
6543
99861e3e06 Fix lfs bug (#19072) (#19080)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-03-14 15:59:54 +01:00
Gusted
66b8a43e5f Refactor mirror code & fix StartToMirror (#18904) (#19075)
- Backport #18904.
2022-03-14 20:04:41 +08:00
zeripath
d285905826 Update the webauthn_credential_id_sequence in Postgres (#19048) (#19060)
Backport #19048

There is (yet) another problem with v210 in that Postgres will silently allow preset
ID insertions ... but it will not update the sequence value.

This PR simply adds a little step to the end of the v210 migration to update the
sequence number.

Users who have already migrated who find that they cannot insert new
webauthn_credentials into the DB can either run:

```bash
gitea doctor recreate-table webauthn_credential
```

or

```bash
SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)
```

which will fix the bad sequence.

Fix #19012

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

Co-authored-by: 6543 <6543@obermui.de>
2022-03-13 12:02:19 +08:00
zeripath
4df2320ba6 Prevent 500 when there is an error during new auth source post (#19041) (#19059)
Backport #19041

Fix #19036

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-03-13 03:42:31 +01:00
zeripath
0fe99cc00c If rendering has failed due to a net.OpError stop rendering (attempt 2) (#19049) (#19056)
Backport #19049

Unfortunately #18642 does not work because a `*net.OpError` does not implement
the `Is` interface to make `errors.Is` work correctly - thus leading to the
irritating conclusion that a `*net.OpError` is not a `*net.OpError`.

Here we keep the `errors.Is` because presumably this will be fixed at
some point in the golang main source code but also we add a simply type
cast to also check.

Fix #18629

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-03-10 22:13:55 +01:00
Norwin
580401ecbf Fix flag validation (#19046) (#19051)
Regression from #5785
2022-03-10 20:23:55 +00:00
zeripath
7aa29720f0 Improve SyncMirrors logging (#19045) (#19050)
Backport #19045

Yet another issue has come up where the logging from SyncMirrors does not provide
enough context. This PR adds more context to these logging events.

Related #19038

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-03-10 16:06:35 +01:00
6543
3e5c844a77 fix pam authorization (#19040) (#19047)
Backport #19040 

The PAM module has previously only checked the results of the authentication module.

However, in normal PAM practice most users will expect account module authorization to also be checked. Without doing this check in almost every configuration expired accounts and accounts with expired passwords will still be able to login.

This is likely to represent a significant gotcha in most configurations and cause most users configurations to be potentially insecure. Therefore we should add in the account authorization check.

## ⚠️ **BREAKING** ⚠️ 

Users of the PAM module who rely on account modules not being checked will need to change their PAM configuration.

However, as it is likely that the vast majority of users of PAM will be expecting account authorization to be checked in addition to authentication we should make this breaking change to make the default behaviour correct for the majority.

---

I suggest we backport this despite the BREAKING nature because of the surprising nature of this.

Thanks to @ysf for bringing this to our attention.


Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: ysf <34326+ysf@users.noreply.github.com>
2022-03-10 08:15:35 +00:00
zeripath
4047c5c068 Ignore missing comment for user notifications (#18954) (#19043) 2022-03-10 01:48:27 -05:00
zeripath
03d924238c Set rel="nofollow noindex" on new issue links (#19023) (#19042)
Backport #19023

Fix #19018

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-03-09 23:01:30 +00:00
Lunny Xiao
bc1248ed9e Upgrading binding package (#19034) (#19035)
Backport #19034

Fix #18855
2022-03-09 18:07:46 +00:00
zeripath
dd52c08b74 Don't show context cancelled errors in attribute reader (#19006) (#19027)
Backport #19006

Fix #18997

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

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-03-08 19:20:37 +08:00
Lunny Xiao
b811b819e2 Fix update hint bug (#19002) 2022-03-04 18:28:17 +00:00
Otto Richter (fnetX)
da985b25ce Fix potential assignee query for repo (#18994) (#18999)
* Fix potential assignee query for repo

* Add tests for `GetRepoAssignees`

- As per https://github.com/go-gitea/gitea/pull/18994#issuecomment-1058506640

Co-authored-by: Gusted <williamzijl7@hotmail.com>
2022-03-05 00:12:34 +08:00
6543
ae9c51df7c allow overwrite artifacts for github releases (#18987) (#18988) 2022-03-03 16:18:55 +01:00
Lunny Xiao
ff1c5815bb Changelog for v1.16.3 (#18966)
* Changelog for v1.16.3

* Update CHANGELOG.md

* Apply suggestions from code review

* Apply suggestions from code review

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

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
2022-03-03 10:50:36 +08:00
6543
87f8d37be5 git backend ignore replace objects (#18979) (#18980)
Co-authored-by: zeripath <art27@cantab.net>
2022-03-02 21:31:50 +00:00
Otto Richter (fnetX)
f4b96c1041 Set max text height to prevent overflow (#18862) (#18977)
Sets a max height for review text boxes to prevent a very annoying bug where users cannot access the "submit" button.

Before:
![image](https://user-images.githubusercontent.com/12700993/155253001-e1dab086-aaf3-4338-889d-6a861728274a.png)

After:
![image](https://user-images.githubusercontent.com/12700993/155253144-5b9a3547-9582-412f-867f-41a45a14a0fe.png)

Interestingly, I don't see this bug on Firefox.

Co-authored-by: Kyle D <kdumontnu@gmail.com>
2022-03-02 20:12:48 +00:00
Otto Richter (fnetX)
a3f72303d1 Fix problem when self-assign notification (#18797) (#18976)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-03-02 20:11:55 +00:00
6543
4317806ade backport fix of #18973 (#18974) 2022-03-02 19:42:02 +00:00
Otto Richter (fnetX)
578f19a682 Refactor admin user filter query parameters (#18965) (#18975)
Only pass `status_filter` on admin page
Use a more general method to pass query parameters, remove hard-coded keys

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-03-02 19:57:18 +01:00
6543
f9b6404950 Accounts with WebAuthn only (no TOTP) now exist ... fix code to handle that case (#18897) (#18964) 2022-03-02 07:22:02 -06:00
Gusted
52517e3e23 Send 404 on /{org}.gpg (#18959) (#18962) 2022-03-01 20:37:47 -05:00
Otto Richter (fnetX)
36e96e3481 Fix admin user list pagination (#18957) (#18960) 2022-03-01 23:00:03 +00:00
Lunny Xiao
a765410d0f Fix lfs management setting (#18947) 2022-03-01 14:14:18 -05:00
6543
43fc2e528c Backport locales from master (#18944)
* update

* clean

* clean2

* clean2

* clean-next

* cleanup

* finish cleanup
2022-02-28 21:19:19 +00:00
Lunny Xiao
cb90eda213 Fix login with email panic when email is not exist (#18942)
Co-authored-by: 6543 <6543@obermui.de>
2022-02-28 18:14:50 +00:00
zeripath
5f9c18b2b3 Adjust error for already locked db and prevent level db lock on malformed connstr (#18923) (#18938)
Backport #18923

This PR adjusts the error returned when there is failure to lock the level db, and
permits a connections to the same leveldb where there is a different connection string.

Reference #18921
Reference #18917

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-28 15:45:38 +00:00
Gusted
4384b85046 Update go-org to v1.6.1 (#18932) (#18933)
Backport #18932
2022-02-27 16:57:06 +00:00
Gusted
e0973a84a0 Fix <strong> html in translation (#18929) (#18931)
Backport #18929
2022-02-27 15:46:34 +00:00
qwerty287
054bc55a1c Fix page and missing return on unadopted repos API (#18848) (#18927)
* Fix page and missing return on unadopted repos API

Page must be 1 if it's not specified and it should return after sending an internal server error.

* Allow ignore pages
2022-02-27 20:18:23 +08:00
Gusted
4fb718d405 Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
* Don't treat BOM escape sequence as hidden character. (#18909)

Backport #18909
2022-02-26 23:15:04 +01:00
Gusted
df35049196 Allow adminstrator teams members to see other teams (#18918) (#18919)
Allow adminstrator teams members to see other teams (#18918)
2022-02-26 22:45:34 +01:00
silverwind
ce75461380 Correctly link URLs to users/repos with dashes, dots or underscores (#18890) (#18908)
* Add tests for references with dashes

This commit adds tests for full URLs referencing repos names and user
names containing a dash.

* Extend regex to match URLs to repos/users with dashes

Co-authored-by: Alexander Neumann <62751754+rtpt-alexanderneumann@users.noreply.github.com>
2022-02-26 06:45:09 +01:00
Gusted
cea85c30a4 Don't update email for organisation (#18905) (#18906)
Backport #18905
2022-02-26 03:10:21 +01:00
Otto Richter (fnetX)
6039138323 Fix redirect when using lowercase reponame (#18775) (#18902)
* Previously,  `GET {username}/{reponame}/raw///file-path` (the middle two slashes are blank to get the default branch) when the repo name has uppercase letters, e.g., https://try.gitea.io/AbdulrhmnGhanem/CH330_Hardware, using a lowercase version of the name redirected to the correct URL
* In other words both
   * `GET https://try.gitea.io/AbdulrhmnGhanem/CH330_Hardware/raw///images/back.png`
   * `GET https://try.gitea.io/AbdulrhmnGhanem/ch330_hardware/raw///images/back.png`
were redirecting to ` GET https://try.gitea.io/AbdulrhmnGhanem/CH330_Hardware/raw/branch/master/images/back.png`
This isn't the case after  #17551. Specifically because of this [line](cbd5eecd14/modules/context/repo.go (L860)).

Co-authored-by: Ghanem <37152329+AbdulrhmnGhanem@users.noreply.github.com>
2022-02-26 08:16:22 +08:00
Lunny Xiao
eb43e73785 Fix team management UI (#18887) 2022-02-25 21:51:22 +01:00
Lunny Xiao
c077a0361a Fix migration v210 (#18893) 2022-02-25 15:08:00 +01:00
Jimmy Praet
6f21a94d18 BeforeSourcePath should point to base commit (#18880) 2022-02-25 14:45:20 +08:00
Lunny Xiao
8ebf0e68ec Add changelog for v1.16.2 (#18840)
Add changelog for v1.16.2

Co-authored-by: 6543 <6543@obermui.de>
2022-02-24 20:03:08 +01:00
Lunny Xiao
3685cc7660 Fix ldap user sync missed email in email_address table (#18786) (#18876)
* Fix ldap user sync missed email in email_address table (#18786)
2022-02-24 19:07:52 +01:00
zeripath
9d9ccdbe43 Don't report signal: killed errors in serviceRPC (#18850) (#18865)
Backport #18850

Fix #18849

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-24 16:59:50 +08:00
zeripath
81b29d6263 Update assignees check to include any writing team and change org sidebar (#18680) (#18873)
Backport #18680

Following the merging of #17811 teams can now have differing write and readonly permissions, however the assignee list will not include teams which have mixed perms.

Further the org sidebar is no longer helpful as it can't describe these mixed permissions situations.

Fix #18572

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-24 09:22:46 +08:00
Lunny Xiao
6591f87b28 Fix login with email for ldap users (#18800) (#18836)
`authenticator.Authenticate` has assume the login name is not an email, but `username` maybe an email. So when we find the user via email address, we should use `user.LoginName` instead of `username` which is an email address.

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-02-22 18:04:57 -05:00
Lunny Xiao
efc78c18c1 Fix ldap edit bug (#18859) 2022-02-22 17:31:29 -05:00
Lunny Xiao
f5a3c0dd6c Fix ldap loginname (#18789) (#18804)
* Use email_address table to check user's email when login with email adress

* Update services/auth/signin.go

* Fix test

* Fix test

* Fix logging in with ldap username != loginname

* Fix if user does not exist yet

* Make more clear this is loginName

* Fix formatting

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: Johan Van de Wauw <johan@gisky.be>
Co-authored-by: zeripath <art27@cantab.net>
2022-02-22 14:33:06 +01:00
zeripath
382101ecc7 In disk_channel queues synchronously push to disk on shutdown (#18415) (#18788)
Partial Backport of #18415

Instead of using an asynchronous goroutine to push to disk on shutdown
just close the datachan and immediately push to the disk.

Prevents messages of incompletely flushed queues.

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-22 20:08:35 +08:00
Lunny Xiao
86c3481eff Fix bug for get user by email (#18834)
Backport #18833

Fix #18830
2022-02-21 18:34:22 +00:00
zeripath
039eb66c8c Update go-org to 1.6.0 (#18824) (#18839)
Backport #18824

Fix #14074

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-21 22:21:43 +08:00
Wim
36148ed083 Show fullname on issue edits and gpg/ssh signing info (#18828)
Co-authored-by: zeripath <art27@cantab.net>
2022-02-20 21:47:17 +00:00
Lunny Xiao
db4c7dcf15 Put buttons back in org dashboard (#18817) (#18825)
Backport #18817

Fix #18523
2022-02-20 19:51:01 +00:00
zeripath
bec566282e Immediately Hammer if second kill is sent (#18823) (#18826)
Backport #18823

Currently Gitea will wait for HammerTime or nice shutdown if kill -1 or kill -2
is sent. We should just immediately hammer if there is a second kill.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-20 01:37:52 +08:00
zeripath
fa9be55018 Fix panic in EscapeReader (#18820) (#18821)
Backport #18820

There is a potential panic due to a mistaken resetting of the length parameter when
multibyte characters go over a read boundary.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-19 23:34:32 +08:00
singuliere
458239b46d remove redundant call to UpdateRepoStats during migration (#18591) (#18794)
There is no need to call UpdateRepoStats in the InsertIssues and
InsertPullRequests function. They are only called during migration by
the CreateIssues and CreateReviews methods of the gitea uploader.

The UpdateRepoStats function will be called by the Finish method of
the gitea uploader after all reviews and issues are inserted. Calling
it before is therefore redundant and the associated SQL requests are
not cheap.

The statistics tests done after inserting an issue or a pull request
are also removed. They predate the implementation of UpdateRepoStats,
back when the calculation of the statistics was an integral part of
the migration function. The UpdateRepoStats is now tested
independantly and these tests are no longer necessary.

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

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-02-17 17:57:05 +00:00
silverwind
ae85ee1c6f Allow mermaid render error to wrap (#18791) 2022-02-17 15:42:29 +08:00
zeripath
08d5a836ef Attempt to fix the webauthn migration again - part 3 (#18770) (#18771)
Backport #18770 

v208.go is seriously broken as it misses an ID() check. We need to no-op and remigrate all of the u2f keys.

See #18756

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-16 21:15:49 +00:00
Lunny Xiao
ad789542b8 Fix template bug of LFS lock (#18784) (#18787)
Backport #18784 

Fix #18782
2022-02-16 17:16:04 +00:00
silverwind
1f7802db97 Various Mermaid improvements (#18776) (#18780)
* Various Mermaid improvments

- Render into iframe for improved security
- Use built-in dark theme instead of color inversion
- Remove flexbox attributes, resulting in more consistent size rendering
- Update API usage and update to latest version

* restart ci

* misc tweaks

* remove unneccesary declaration

* make it work without allow-same-origin, add loading=lazy

* remove loading attribute, does not seem to work

* rename variable

* skip roundtrip to DOM for rendering

* don't guess chart height

* update comment to make it clear it's intentional

* tweak

* replace deprecated 'scrolling' property

* remove unused css file

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-16 17:26:53 +08:00
zeripath
c876124efe Ensure git tag tests and others create test repos in tmpdir (#18447) (#18767)
Backport #18447

* Ensure git tag tests and other create test repos in tmpdir

There are a few places where tests appear to reuse testing repos which
causes random CI failures.

This PR simply changes these tests to ensure that cloning always happens
into new temporary directories.

Fix #18444

* Change log root for integration tests to use the REPO_TEST_DIR

There is a potential race in the drone integration tests whereby test-mysql etc
will start writing to log files causing make test-check fail.

Fix #18077

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

Co-authored-by: 6543 <6543@obermui.de>
2022-02-15 19:16:38 +08:00
zeripath
3a78ac4b32 Increase the size of the webauthn_credential credential_id field (#18739) (#18756)
* Increase the size of the webauthn_credential credential_id field (#18739)

Backport #18739

Unfortunately credentialIDs in u2f are 255 bytes long which with base32 encoding
becomes 408 bytes. The default size of a xorm string field is only a VARCHAR(255)

This problem is not apparent on SQLite because strings get mapped to TEXT there.

Fix #18727

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

* Ignore the migrate if u2f_registration is not exist (#18760)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-14 21:08:49 +00:00
zeripath
7ebc3da7cb Prevent dangling GetAttribute calls (#18754) (#18755)
* Prevent dangling GetAttribute calls

It appears possible that there could be a hang due to unread data from the
repo-attribute command pipes. This PR simply closes these during the defer.

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

* move close into the defer

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

* lets try again

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-14 15:27:55 -05:00
zeripath
2e36ba0a00 Send mail to issue/pr assignee/reviewer also when OnMention is set (#18707) (#18765)
Backport #18707

Addresses #17892, where emails notifications are not sent to assignees (issue and PR) and reviewers (PR) when they have the email setting Only email on mention enabled.

From the user experience perspective, when a user gets a issue/PR assigned or a PR review request, he/she would expect to be implicitly mentioned since the assignment or request is personal and targeting a single person only. Thus I see #17892 as a bug. Could we therefore mark this ticket as such?

The changed code just explicitly checks for the EmailNotificationsOnMention setting beside the existing EmailNotificationsEnabled check. Too rude?

@lunny mentioned a mock mail server for tests, is there something ready. How could I make use of it?

See #12774 (comment)

Fix #17892

Co-authored-by: flozzone <flozzone@gmail.com>
2022-02-14 21:13:41 +08:00
wxiaoguang
69a158dcc2 Fix a broken link in commits_list_small.tmpl (#18764) 2022-02-14 12:03:51 +00:00
Lunny Xiao
913d6f3ff3 Fix isempty detection of git repository (#18746) (#18750)
* Fix isempty detection of git repository

* Fix IsEmpty check
2022-02-14 00:33:35 +08:00
zeripath
044cb09ae8 Prevent double encoding of branch names in delete branch (#18714) (#18738)
Backport #18714

* Prevent double encoding of branch names in delete branch

There is a double encoding issue in branch template whereby the branch name
ends up double encoded.

Fix #18709

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

* and tag name

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

* And fix #18704

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-12 15:17:45 +00:00
Lunny Xiao
9da8e478dd Fix forked repositories missed tags (#18719) (#18735)
* Fix forked repositories missed tags

* Add missed close

* Use ctx

Co-authored-by: 6543 <6543@obermui.de>
2022-02-12 13:48:38 +00:00
zeripath
c8f3672a88 Always set PullRequestWorkInProgressPrefixes in PrepareViewPullInfo (#18713) (#18737)
Backport #18713

Move setting PullRequestWorkInProgressPrefixes to the start of PrepareViewPullInfo.

Fix #18706

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-12 12:55:34 +00:00
Jimmy Praet
edf85b820d Fix source code line highlighting (#18729) (#18740)
Backport #18729

When the issues repo unit is disabled, or an external issue tracker is used, there is no "a.ref-in-new-issue".

Fixes #18721
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-02-12 12:24:49 +00:00
silverwind
c04a4afac1 Reduce CI go module downloads, add make targets (#18708, #18475, #18443) (#18741)
Backport #18708 
Backport #18475 
Backport #18443 

The CI currently downloads all go modules in each pipeline step because go modules reside outside the project directory. Fix this by introducing a volume for the `/go` directory [1] so modules are only downloaded once per pipeline using a new `deps-backend` make target.

For completeness, I also included new `deps` and `deps-frontend` targets and the frontend one is also triggered explicitly on CI where needed.

[1] https://docs.drone.io/pipeline/kubernetes/examples/language/golang/#dependencies

* Also backports #18475 and #18443 so that is was able to merge cleanly.
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-02-12 10:43:08 +00:00
zeripath
65ad6362d7 Separate the details links of commit-statuses in headers (#18661) (#18730)
Backport #18661
2022-02-12 11:40:55 +08:00
Lunny Xiao
f9a0ae1dd4 Fix release typo (#18728) (#18731) 2022-02-11 20:15:34 -05:00
wxiaoguang
fb26b01688 Update object repo with the migrated repository (#18684) (#18726)
When migrating a repository (from GitHub) using the API (**POST** `repos/migrate`), the Code Indexer is not updated. Searching in the user interface will not return any results.

When migrating the same repository using **+/New Migration** in the web interface, the search index is updated and searching works as expected.

Caused by the fact that object `repo` is never updated with the migrated repo so `setting.Indexer.RepoIndexerEnabled && !repo.IsEmpty` in `modules/notification/indexer/indexer.go:NotifyMigrateRepository` always evaluates to `false`.

Tested with gitea:1.16.1, MariaDB:10, Breve in `Run Mode: Dev`.

Co-authored-by: Hugo Hoitink <10838836+hoitih@users.noreply.github.com>
2022-02-11 17:23:41 +01:00
Lunny Xiao
63628fdf1c Fix bug for version update hint (#18701) (#18705)
* Fix bug for version update hint (#18701)
* Add translation for zh-CN

Co-authored-by: silverwind <me@silverwind.io>
2022-02-10 18:35:24 +00:00
zeripath
2e317d3f6e Prevent security failure due to bad APP_ID (#18678) (#18682)
Backport #18678

WebAuthn may cause a security exception if the provided APP_ID is not allowed for the
current origin. Therefore we should reattempt authentication without the appid
extension.

Also we should allow [u2f] as-well as [U2F] sections.

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-10 16:17:44 +01:00
zeripath
ce69882180 Fix issue with docker-rootless shimming script (#18690) (#18699)
Backport #18690

There is a problem with the current shimming script in that it will double quote the
provided GITEA_APP_INI due to a mistake in the bash. Here we change this to use a bash array.

Fix https://gitea.com/gitea/helm-chart/issues/287

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-10 19:37:53 +08:00
silverwind
649abeda40 C preprocessor colors improvement (#18671) (#18696)
* C preprocessor colors improvement

Fixes #18670

* Update web_src/less/chroma/light.less

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>

* typo

missing semi

* add color for #include filenames

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Bruno Raoult <braoult@users.noreply.github.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-02-10 10:12:03 +08:00
Gusted
4cfd62cddf Let return correct perm (#18675) (#18689)
Backport of #18675
2022-02-09 20:19:48 +00:00
zeripath
38fc6c75f3 Restart zero worker if there is still work to do (#18658) (#18672)
* Restart zero worker if there is still work to do (#18658)

Backport #18658

It is possible for the zero worker to timeout before all the work is finished.
This may mean that work may take a long time to complete because a worker will only
be induced on repushing.

Also ensure that requested count is reset after pulls and push mirror sync requests and add some more trace logging to the queue push.

Fix #18607

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

* Update modules/queue/workerpool.go
2022-02-08 23:28:21 +02:00
wxiaoguang
8671602ba9 Backport: fix the missing i18n key for update checker (#18646) (#18665) 2022-02-08 11:03:47 +02:00
wxiaoguang
3d08e3a08c No longer show the db-downgrade SQL in production (#18654) 2022-02-07 15:07:11 +01:00
zeripath
d4a075d738 If rendering has failed due to a net.OpError stop rendering (#18642) (#18645)
Backport #18642

When a net.OpError occurs during rendering the underlying connection is essentially
dead and therefore attempting to render further data will only cause further errors.

Therefore in serverErrorInternal detect if the passed in error is an OpError and
if so do not attempt any further rendering.

Fix #18629

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-02-07 09:25:05 +08:00
137 changed files with 2077 additions and 782 deletions

View File

@@ -13,12 +13,25 @@ trigger:
- tag
- pull_request
volumes:
- name: deps
temp: {}
steps:
- name: deps-frontend
pull: always
image: node:16
pull: always
commands:
- make node_modules
- make deps-frontend
- name: deps-backend
image: golang:1.17
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: lint-frontend
image: node:16
@@ -27,17 +40,17 @@ steps:
depends_on: [deps-frontend]
- name: lint-backend
pull: always
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
commands:
- make lint-backend
environment:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata sqlite sqlite_unlock_notify
depends_on: [deps-backend]
- name: lint-backend-windows
pull: always
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
commands:
- make golangci-lint vet
@@ -47,9 +60,9 @@ steps:
TAGS: bindata sqlite sqlite_unlock_notify
GOOS: windows
GOARCH: amd64
depends_on: [deps-backend]
- name: lint-backend-gogit
pull: always
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
commands:
- make lint-backend
@@ -57,6 +70,7 @@ steps:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata gogit sqlite sqlite_unlock_notify
depends_on: [deps-backend]
- name: checks-frontend
image: node:16
@@ -65,11 +79,13 @@ steps:
depends_on: [deps-frontend]
- name: checks-backend
pull: always
image: golang:1.17
commands:
- make checks-backend
depends_on: [lint-backend]
depends_on: [deps-backend]
volumes:
- name: deps
path: /go
- name: test-frontend
image: node:16
@@ -84,14 +100,17 @@ steps:
depends_on: [test-frontend]
- name: build-backend-no-gcc
pull: always
image: golang:1.16 # this step is kept as the lowest version of golang that we support
pull: always
environment:
GO111MODULE: on
GOPROXY: https://goproxy.cn
commands:
- go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
depends_on: [checks-backend]
depends_on: [deps-backend, checks-backend]
volumes:
- name: deps
path: /go
- name: build-backend-arm64
image: golang:1.17
@@ -104,7 +123,10 @@ steps:
commands:
- make backend # test cross compile
- rm ./gitea # clean
depends_on: [checks-backend]
depends_on: [deps-backend, checks-backend]
volumes:
- name: deps
path: /go
- name: build-backend-windows
image: golang:1.17
@@ -116,7 +138,10 @@ steps:
TAGS: bindata gogit
commands:
- go build -o gitea_windows
depends_on: [checks-backend]
depends_on: [deps-backend, checks-backend]
volumes:
- name: deps
path: /go
- name: build-backend-386
image: golang:1.17
@@ -127,7 +152,10 @@ steps:
GOARCH: 386
commands:
- go build -o gitea_linux_386 # test if compatible with 32 bit
depends_on: [checks-backend]
depends_on: [deps-backend, checks-backend]
volumes:
- name: deps
path: /go
---
kind: pipeline
@@ -147,21 +175,28 @@ trigger:
- tag
- pull_request
volumes:
- name: deps
temp: {}
services:
- name: mysql
image: mysql:5.7
pull: always
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: test
- name: mysql8
image: mysql:8
pull: always
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea
- name: mssql
image: mcr.microsoft.com/mssql/server:latest
pull: always
environment:
ACCEPT_EULA: Y
MSSQL_PID: Standard
@@ -169,14 +204,17 @@ services:
- name: ldap
image: gitea/test-openldap:latest
pull: always
- name: elasticsearch
image: elasticsearch:7.5.0
pull: always
environment:
discovery.type: single-node
image: elasticsearch:7.5.0
- name: minio
image: minio/minio:RELEASE.2021-03-12T00-00-47Z
pull: always
commands:
- minio server /data
environment:
@@ -186,6 +224,7 @@ services:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
when:
@@ -193,19 +232,28 @@ steps:
exclude:
- pull_request
- name: tag-pre-condition
- name: deps-backend
image: golang:1.17
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: tag-pre-condition
image: drone/git
pull: always
commands:
- git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA}
- name: prepare-test-env
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
commands:
- ./build/test-env-prepare.sh
- name: build
pull: always
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
@@ -215,8 +263,10 @@ steps:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata sqlite sqlite_unlock_notify
depends_on:
- prepare-test-env
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: unit-test
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
@@ -229,9 +279,12 @@ steps:
RACE_ENABLED: true
GITHUB_READ_TOKEN:
from_secret: github_read_token
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: unit-test-gogit
pull: always
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
@@ -242,6 +295,10 @@ steps:
RACE_ENABLED: true
GITHUB_READ_TOKEN:
from_secret: github_read_token
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: test-mysql
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
@@ -255,8 +312,10 @@ steps:
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
depends_on:
- build
depends_on: [build]
volumes:
- name: deps
path: /go
- name: test-mysql8
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
@@ -269,8 +328,10 @@ steps:
RACE_ENABLED: true
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on:
- build
depends_on: [build]
volumes:
- name: deps
path: /go
- name: test-mssql
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
@@ -283,8 +344,10 @@ steps:
RACE_ENABLED: true
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on:
- build
depends_on: [build]
volumes:
- name: deps
path: /go
- name: generate-coverage
image: golang:1.17
@@ -293,9 +356,7 @@ steps:
environment:
GOPROXY: https://goproxy.cn
TAGS: bindata
depends_on:
- unit-test
- test-mysql
depends_on: [unit-test, test-mysql]
when:
branch:
- main
@@ -304,15 +365,14 @@ steps:
- pull_request
- name: coverage-codecov
image: woodpeckerci/plugin-codecov:next-alpine
pull: always
image: plugins/codecov
settings:
files:
- coverage.all
token:
from_secret: codecov_token
depends_on:
- generate-coverage
depends_on: [generate-coverage]
when:
branch:
- main
@@ -337,6 +397,10 @@ trigger:
- tag
- pull_request
volumes:
- name: deps
temp: {}
services:
- name: pgsql
pull: default
@@ -352,6 +416,7 @@ services:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
when:
@@ -359,13 +424,22 @@ steps:
exclude:
- pull_request
- name: deps-backend
image: golang:1.17
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: prepare-test-env
image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env
pull: always
commands:
- ./build/test-env-prepare.sh
- name: build
pull: always
image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env
user: gitea
commands:
@@ -375,8 +449,10 @@ steps:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata gogit sqlite sqlite_unlock_notify
depends_on:
- prepare-test-env
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: test-sqlite
image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env
@@ -389,8 +465,10 @@ steps:
RACE_ENABLED: true
TEST_TAGS: gogit sqlite sqlite_unlock_notify
USE_REPO_TEST_DIR: 1
depends_on:
- build
depends_on: [build]
volumes:
- name: deps
path: /go
- name: test-pgsql
image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env
@@ -404,8 +482,10 @@ steps:
TEST_TAGS: gogit
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on:
- build
depends_on: [build]
volumes:
- name: deps
path: /go
---
kind: pipeline
@@ -425,8 +505,8 @@ trigger:
steps:
- name: download
pull: always
image: jonasfranz/crowdin
pull: always
settings:
download: true
export_dir: options/locale/
@@ -437,14 +517,14 @@ steps:
from_secret: crowdin_key
- name: update
pull: default
image: alpine:3.13
pull: always
commands:
- ./build/update-locales.sh
- name: push
pull: always
image: appleboy/drone-git-push
pull: always
settings:
author_email: "teabot@gitea.io"
author_name: GiteaBot
@@ -457,8 +537,8 @@ steps:
from_secret: git_push_ssh_key
- name: upload_translations
pull: always
image: jonasfranz/crowdin
pull: always
settings:
files:
locale_en-US.ini: options/locale/locale_en-US.ini
@@ -488,12 +568,13 @@ trigger:
steps:
- name: download
image: golang:1.17
pull: always
commands:
- timeout -s ABRT 40m make generate-license generate-gitignore
- name: push
pull: always
image: appleboy/drone-git-push
pull: always
settings:
author_email: "teabot@gitea.io"
author_name: GiteaBot
@@ -529,15 +610,35 @@ depends_on:
- testing-amd64
- testing-arm64
volumes:
- name: deps
temp: {}
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: static
- name: deps-frontend
image: node:16
pull: always
commands:
- make deps-frontend
- name: deps-backend
image: golang:1.17
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: static
image: techknowlogick/xgo:go-1.17.x
pull: always
commands:
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
- export PATH=$PATH:$GOPATH/bin
@@ -545,10 +646,13 @@ steps:
environment:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
TAGS: bindata sqlite sqlite_unlock_notify
volumes:
- name: deps
path: /go
- name: gpg-sign
pull: always
image: plugins/gpgsign:1
pull: always
settings:
detach_sign: true
excludes:
@@ -562,8 +666,8 @@ steps:
from_secret: gpgsign_passphrase
- name: release-branch
pull: always
image: woodpeckerci/plugin-s3:latest
pull: always
settings:
acl: public-read
bucket: gitea-artifacts
@@ -624,16 +728,35 @@ depends_on:
- testing-arm64
- testing-amd64
volumes:
- name: deps
temp: {}
steps:
- name: fetch-tags
pull: default
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: static
- name: deps-frontend
image: node:16
pull: always
commands:
- make deps-frontend
- name: deps-backend
image: golang:1.17
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: static
image: techknowlogick/xgo:go-1.17.x
pull: always
commands:
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
- export PATH=$PATH:$GOPATH/bin
@@ -641,10 +764,14 @@ steps:
environment:
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not
TAGS: bindata sqlite sqlite_unlock_notify
depends_on: [fetch-tags]
volumes:
- name: deps
path: /go
- name: gpg-sign
pull: always
image: plugins/gpgsign:1
pull: always
settings:
detach_sign: true
excludes:
@@ -656,10 +783,11 @@ steps:
from_secret: gpgsign_key
GPGSIGN_PASSPHRASE:
from_secret: gpgsign_passphrase
depends_on: [static]
- name: release-tag
pull: always
image: woodpeckerci/plugin-s3:latest
pull: always
settings:
acl: public-read
bucket: gitea-artifacts
@@ -673,16 +801,19 @@ steps:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
depends_on: [gpg-sign]
- name: github
image: plugins/github-release:latest
pull: always
image: plugins/github-release:1
settings:
files:
- "dist/release/*"
file_exists: overwrite
environment:
GITHUB_TOKEN:
from_secret: github_token
depends_on: [gpg-sign]
---
kind: pipeline
@@ -704,16 +835,16 @@ trigger:
steps:
- name: build-docs
pull: always
image: plugins/hugo:latest
pull: always
commands:
- apk add --no-cache make bash curl
- cd docs
- make trans-copy clean build
- name: publish-docs
pull: always
image: techknowlogick/drone-netlify:latest
pull: always
settings:
path: docs/public/
site_id: d2260bae-7861-4c02-8646-8f6440b12672
@@ -749,12 +880,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: true
auto_tag_suffix: linux-amd64
@@ -811,12 +943,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: false
tags: dev-linux-amd64
@@ -872,12 +1005,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64
@@ -929,8 +1063,8 @@ trigger:
steps:
- name: dryrun
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
dry_run: true
repo: gitea/gitea
@@ -967,12 +1101,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: true
auto_tag_suffix: linux-arm64
@@ -1029,12 +1164,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: false
tags: dev-linux-arm64
@@ -1090,12 +1226,13 @@ trigger:
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
pull: always
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: false
tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64
@@ -1140,8 +1277,8 @@ platform:
steps:
- name: manifest-rootless
pull: always
image: plugins/manifest
pull: always
settings:
auto_tag: true
ignore_missing: true
@@ -1186,6 +1323,7 @@ steps:
- name: manifest-rootless
pull: always
image: plugins/manifest
pull: always
settings:
auto_tag: false
ignore_missing: true
@@ -1260,8 +1398,8 @@ depends_on:
steps:
- name: discord
pull: always
image: appleboy/drone-discord:1.2.4
pull: always
settings:
message: "{{#success build.status}} ✅ Build #{{build.number}} of `{{repo.name}}` succeeded.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{else}} ❌ Build #{{build.number}} of `{{repo.name}}` failed.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{/success}}\n"
webhook_id:

2
.gitignore vendored
View File

@@ -36,6 +36,8 @@ _testmain.go
coverage.all
cpu.out
/modules/migration/bindata.go
/modules/migration/bindata.go.hash
/modules/options/bindata.go
/modules/options/bindata.go.hash
/modules/public/bindata.go

View File

@@ -23,6 +23,10 @@ linters:
run:
timeout: 3m
skip-dirs:
- node_modules
- public
- web_src
linters-settings:
gocritic:

View File

@@ -4,6 +4,105 @@ 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
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.16.4](https://github.com/go-gitea/gitea/releases/tag/v1.16.4) - 2022-03-14
* SECURITY
* Restrict email address validation (#17688) (#19085)
* Fix lfs bug (#19072) (#19080)
* ENHANCEMENTS
* Improve SyncMirrors logging (#19045) (#19050)
* BUGFIXES
* Refactor mirror code & fix `StartToMirror` (#18904) (#19075)
* Update the webauthn_credential_id_sequence in Postgres (#19048) (#19060)
* Prevent 500 when there is an error during new auth source post (#19041) (#19059)
* If rendering has failed due to a net.OpError stop rendering (attempt 2) (#19049) (#19056)
* Fix flag validation (#19046) (#19051)
* Add pam account authorization check (#19040) (#19047)
* Ignore missing comment for user notifications (#18954) (#19043)
* Set `rel="nofollow noindex"` on new issue links (#19023) (#19042)
* Upgrading binding package (#19034) (#19035)
* Don't show context cancelled errors in attribute reader (#19006) (#19027)
* Fix update hint bug (#18996) (#19002)
* MISC
* Fix potential assignee query for repo (#18994) (#18999)
## [1.16.3](https://github.com/go-gitea/gitea/releases/tag/v1.16.3) - 2022-03-02
* SECURITY
* Git backend ignore replace objects (#18979) (#18980)
* ENHANCEMENTS
* Adjust error for already locked db and prevent level db lock on malformed connstr (#18923) (#18938)
* BUGFIXES
* Set max text height to prevent overflow (#18862) (#18977)
* Fix newAttachmentPaths deletion for DeleteRepository() (#18973) (#18974)
* Accounts with WebAuthn only (no TOTP) now exist ... fix code to handle that case (#18897) (#18964)
* Send 404 on `/{org}.gpg` (#18959) (#18962)
* Fix admin user list pagination (#18957) (#18960)
* Fix lfs management setting (#18947) (#18946)
* Fix login with email panic when email is not exist (#18942)
* Update go-org to v1.6.1 (#18932) (#18933)
* Fix `<strong>` html in translation (#18929) (#18931)
* Fix page and missing return on unadopted repos API (#18848) (#18927)
* Allow adminstrator teams members to see other teams (#18918) (#18919)
* Don't treat BOM escape sequence as hidden character. (#18909) (#18910)
* Correctly link URLs to users/repos with dashes, dots or underscores (… (#18908)
* Fix redirect when using lowercase repo name (#18775) (#18902)
* Fix migration v210 (#18893) (#18892)
* Fix team management UI (#18887) (18886)
* BeforeSourcePath should point to base commit (#18880) (#18799)
* TRANSLATION
* Backport locales from master (#18944)
* MISC
* Don't update email for organisation (#18905) (#18906)
## [1.16.2](https://github.com/go-gitea/gitea/releases/tag/v1.16.2) - 2022-02-24
* ENHANCEMENTS
* Show fullname on issue edits and gpg/ssh signing info (#18828)
* Immediately Hammer if second kill is sent (#18823) (#18826)
* Allow mermaid render error to wrap (#18791)
* BUGFIXES
* Fix ldap user sync missed email in email_address table (#18786) (#18876)
* Update assignees check to include any writing team and change org sidebar (#18680) (#18873)
* Don't report signal: killed errors in serviceRPC (#18850) (#18865)
* Fix bug where certain LDAP settings were reverted (#18859)
* Update go-org to 1.6.0 (#18824) (#18839)
* Fix login with email for ldap users (#18800) (#18836)
* Fix bug for get user by email (#18834)
* Fix panic in EscapeReader (#18820) (#18821)
* Fix ldap loginname (#18789) (#18804)
* Remove redundant call to UpdateRepoStats during migration (#18591) (#18794)
* In disk_channel queues synchronously push to disk on shutdown (#18415) (#18788)
* Fix template bug of LFS lock (#18784) (#18787)
* Attempt to fix the webauthn migration again - part 3 (#18770) (#18771)
* Send mail to issue/pr assignee/reviewer also when OnMention is set (#18707) (#18765)
* Fix a broken link in commits_list_small.tmpl (#18763) (#18764)
* Increase the size of the webauthn_credential credential_id field (#18739) (#18756)
* Prevent dangling GetAttribute calls (#18754) (#18755)
* Fix isempty detection of git repository (#18746) (#18750)
* Fix source code line highlighting on external tracker (#18729) (#18740)
* Prevent double encoding of branch names in delete branch (#18714) (#18738)
* Always set PullRequestWorkInProgressPrefixes in PrepareViewPullInfo (#18713) (#18737)
* Fix forked repositories missed tags (#18719) (#18735)
* Fix release typo (#18728) (#18731)
* Separate the details links of commit-statuses in headers (#18661) (#18730)
* Update object repo with the migrated repository (#18684) (#18726)
* Fix bug for version update hint (#18701) (#18705)
* Fix issue with docker-rootless shimming script (#18690) (#18699)
* Let `MinUnitAccessMode` return correct perm (#18675) (#18689)
* Prevent security failure due to bad APP_ID (#18678) (#18682)
* Restart zero worker if there is still work to do (#18658) (#18672)
* If rendering has failed due to a net.OpError stop rendering (#18642) (#18645)
* TESTING
* Ensure git tag tests and others create test repos in tmpdir (#18447) (#18767)
* BUILD
* Reduce CI go module downloads, add make targets (#18708, #18475, #18443) (#18741)
* MISC
* Put buttons back in org dashboard (#18817) (#18825)
* Various Mermaid improvements (#18776) (#18780)
* C preprocessor colors improvement (#18671) (#18696)
* Fix the missing i18n key for update checker (#18646) (#18665)
## [1.16.1](https://github.com/go-gitea/gitea/releases/tag/v1.16.1) - 2022-02-06
* SECURITY

View File

@@ -166,6 +166,9 @@ help:
@echo " - watch-backend watch backend files and continuously rebuild"
@echo " - clean delete backend and integration files"
@echo " - clean-all delete backend, frontend and integration files"
@echo " - deps install dependencies"
@echo " - deps-frontend install frontend dependencies"
@echo " - deps-backend install backend dependencies"
@echo " - lint lint everything"
@echo " - lint-frontend lint frontend files"
@echo " - lint-backend lint backend files"
@@ -396,6 +399,11 @@ test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
.PHONY: test-sqlite-migration\#%
test-sqlite-migration\#%: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test -test.run $(subst .,/,$*)
generate-ini-mysql:
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
-e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \
@@ -656,6 +664,16 @@ docs:
fi
cd docs; make trans-copy clean build-offline;
.PHONY: deps
deps: deps-frontend deps-backend
.PHONY: deps-frontend
deps-frontend: node_modules
.PHONY: deps-backend
deps-backend:
$(GO) mod download
node_modules: package-lock.json
npm install --no-save
@touch node_modules

View File

@@ -31,7 +31,7 @@ func argsSet(c *cli.Context, args ...string) error {
return errors.New(a + " is not set")
}
if util.IsEmptyString(a) {
if util.IsEmptyString(c.String(a)) {
return errors.New(a + " is required")
}
}

View File

@@ -33,10 +33,8 @@ for i in "$@"; do
done
if [ -z "$APP_INI_SET" ]; then
CONF_ARG="-c \"$APP_INI\""
CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
fi
# Provide FHS compliant defaults
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"

View File

@@ -32,11 +32,9 @@ for i in "$@"; do
done
if [ -z "$APP_INI_SET" ]; then
CONF_ARG="-c \"${GITEA_APP_INI:-$APP_INI}\""
CONF_ARG=("-c" "${GITEA_APP_INI:-$APP_INI}")
fi
# Provide docker defaults
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" $CONF_ARG "$@"
GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" exec -a "$0" "$GITEA" "${CONF_ARG[@]}" "$@"

View File

@@ -42,7 +42,7 @@ To maintain understandable code and avoid circular dependencies it is important
- `modules/setting`: Store all system configurations read from ini files and has been referenced by everywhere. But they should be used as function parameters when possible.
- `modules/git`: Package to interactive with `Git` command line or Gogit package.
- `public`: Compiled frontend files (javascript, images, css, etc.)
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) shall not depend on routers.
- `routers`: Handling of server requests. As it uses other Gitea packages to serve the request, other packages (models, modules or services) must not depend on routers.
- `routers/api` Contains routers for `/api/v1` aims to handle RESTful API requests.
- `routers/install` Could only respond when system is in INSTALL mode (INSTALL_LOCK=false).
- `routers/private` will only be invoked by internal sub commands, especially `serv` and `hooks`.
@@ -106,10 +106,20 @@ i.e. `servcies/user`, `models/repository`.
Since there are some packages which use the same package name, it is possible that you find packages like `modules/user`, `models/user`, and `services/user`. When these packages are imported in one Go file, it's difficult to know which package we are using and if it's a variable name or an import name. So, we always recommend to use import aliases. To differ from package variables which are commonly in camelCase, just use **snake_case** for import aliases.
i.e. `import user_service "code.gitea.io/gitea/services/user"`
### Important Gotchas
- Never write `x.Update(exemplar)` without an explicit `WHERE` clause:
- This will cause all rows in the table to be updated with the non-zero values of the exemplar - including IDs.
- You should usually write `x.ID(id).Update(exemplar)`.
- If during a migration you are inserting into a table using `x.Insert(exemplar)` where the ID is preset:
- You will need to ``SET IDENTITY_INSERT `table` ON`` for the MSSQL variant (the migration will fail otherwise)
- However, you will also need to update the id sequence for postgres - the migration will silently pass here but later insertions will fail:
``SELECT setval('table_name_id_seq', COALESCE((SELECT MAX(id)+1 FROM `table_name`), 1), false)``
### Future Tasks
Currently, we are creating some refactors to do the following things:
- Correct that codes which doesn't follow the rules.
- There are too many files in `models`, so we are moving some of them into a sub package `models/xxx`.
- Some `modules` sub packages should be moved to `services` because they depends on `models`.
- Some `modules` sub packages should be moved to `services` because they depend on `models`.

8
go.mod
View File

@@ -6,7 +6,7 @@ require (
cloud.google.com/go v0.78.0 // indirect
code.gitea.io/gitea-vet v0.2.1
code.gitea.io/sdk/gitea v0.15.1
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1
gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5
gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8
@@ -85,7 +85,7 @@ require (
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/msteinert/pam v0.0.0-20201130170657-e61372126161
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/niklasfasching/go-org v1.5.0
github.com/niklasfasching/go-org v1.6.1
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/oliamb/cutter v0.2.2
github.com/olivere/elastic/v7 v7.0.25
@@ -122,9 +122,9 @@ require (
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.19.0 // indirect
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
golang.org/x/tools v0.1.0

20
go.sum
View File

@@ -41,8 +41,8 @@ code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFj
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be h1:IzSwPVzd2hE6e67ujY8ReBCrQ5IFNd0uiBmC7Ux5IaY=
gitea.com/go-chi/binding v0.0.0-20211013065440-d16dc407c2be/go.mod h1:/vR0YjlusOYvosKYW7QKcSnrY0nPLe4RQ/DGi3+i/Do=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs=
gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1 h1:Z7DcvTkxt8ovcENgPsQ7xzrGNSQmmIjGS9fJEb1l8jk=
gitea.com/go-chi/cache v0.0.0-20211013020926-78790b11abf1/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
@@ -489,8 +489,9 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
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/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.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik=
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
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/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -912,8 +913,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/niklasfasching/go-org v1.5.0 h1:V8IwoSPm/d61bceyWFxxnQLtlvNT+CjiYIhtZLdnMF0=
github.com/niklasfasching/go-org v1.5.0/go.mod h1:sSb8ylwnAG+h8MGFDB3R1D5bxf8wA08REfhjShg3kjA=
github.com/niklasfasching/go-org v1.6.1 h1:vaGWr6TPqprkAbOJ/+E08mgZxsLM0SCOqWo9D5plj4U=
github.com/niklasfasching/go-org v1.6.1/go.mod h1:3m8LIjGNz0ijv6UQzCpDl/bORThFI80/1xz2r1GgIT0=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
@@ -1353,8 +1354,9 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1460,11 +1462,13 @@ golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -445,7 +445,6 @@ func TestAPIRepoTransfer(t *testing.T) {
expectedStatus int
}{
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"
// Transfer to a user with teams in another org should fail
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
// Transfer to a user with non-existent team IDs should fail

View File

@@ -83,7 +83,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mssql/data/sessions
[log]
MODE = test,file
ROOT_PATH = mssql-log
ROOT_PATH = {{REPO_TEST_DIR}}mssql-log
ROUTER = ,
XORM = file
ENABLE_SSH_LOG = true

View File

@@ -101,7 +101,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions
[log]
MODE = test,file
ROOT_PATH = mysql-log
ROOT_PATH = {{REPO_TEST_DIR}}mysql-log
ROUTER = ,
XORM = file
ENABLE_SSH_LOG = true

View File

@@ -80,7 +80,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-mysql8/data/sessions
[log]
MODE = test,file
ROOT_PATH = mysql8-log
ROOT_PATH = {{REPO_TEST_DIR}}mysql8-log
ROUTER = ,
XORM = file
ENABLE_SSH_LOG = true

View File

@@ -84,7 +84,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-pgsql/data/sessions
[log]
MODE = test,file
ROOT_PATH = pgsql-log
ROOT_PATH = {{REPO_TEST_DIR}}pgsql-log
ROUTER = ,
XORM = file
ENABLE_SSH_LOG = true

View File

@@ -51,8 +51,6 @@ func TestSignin(t *testing.T) {
{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")},
{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")},
// test for duplicate email
{username: "user2@example.com", password: "password", message: i18n.Tr("en", "form.email_been_used")},
}
for _, s := range samples {

View File

@@ -79,7 +79,7 @@ PROVIDER_CONFIG = integrations/gitea-integration-sqlite/data/sessions
[log]
MODE = test,file
ROOT_PATH = sqlite-log
ROOT_PATH = {{REPO_TEST_DIR}}sqlite-log
ROUTER = ,
XORM = file
ENABLE_SSH_LOG = true

View File

@@ -43,7 +43,7 @@ type WebAuthnCredential struct {
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX"`
CredentialID string `xorm:"INDEX VARCHAR(410)"`
PublicKey []byte
AttestationType string
AAGUID []byte

View File

@@ -137,6 +137,7 @@ func QueryIssueContentHistoryEditedCountMap(dbCtx context.Context, issueID int64
type IssueContentListItem struct {
UserID int64
UserName string
UserFullName string
UserAvatarLink string
HistoryID int64
@@ -148,7 +149,7 @@ type IssueContentListItem struct {
// FetchIssueContentHistoryList fetch list
func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int64) ([]*IssueContentListItem, error) {
res := make([]*IssueContentListItem, 0)
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name,"+
err := db.GetEngine(dbCtx).Select("u.id as user_id, u.name as user_name, u.full_name as user_full_name,"+
"h.id as history_id, h.edited_unix, h.is_first_created, h.is_deleted").
Table([]string{"issue_content_history", "h"}).
Join("LEFT", []string{"user", "u"}, "h.poster_id = u.id").

View File

@@ -43,8 +43,9 @@ func TestContentHistory(t *testing.T) {
when the refactor of models are done, this test will be possible to be run then with a real `User` model.
*/
type User struct {
ID int64
Name string
ID int64
Name string
FullName string
}
_ = dbEngine.Sync2(&User{})

View File

@@ -193,12 +193,13 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
// even if error occurs, it won't hurt users and won't make things worse
for i := range metas {
p := lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size}
_, err = sess.Insert(&LFSMetaObject{
Pointer: lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size},
Pointer: p,
RepositoryID: repoID,
})
if err != nil {
log.Warn("failed to insert LFS meta object into database, err=%v", err)
log.Warn("failed to insert LFS meta object %-v for repo_id: %d into database, err=%v", p, repoID, err)
}
}
}

View File

@@ -52,10 +52,6 @@ func InsertIssues(issues ...*Issue) error {
return err
}
}
err = UpdateRepoStats(ctx, issues[0].RepoID)
if err != nil {
return err
}
return committer.Commit()
}
@@ -147,11 +143,6 @@ func InsertPullRequests(prs ...*PullRequest) error {
return err
}
}
err = UpdateRepoStats(ctx, prs[0].Issue.RepoID)
if err != nil {
return err
}
return committer.Commit()
}

View File

@@ -32,8 +32,9 @@ func TestMigrate_InsertMilestones(t *testing.T) {
unittest.CheckConsistencyFor(t, &Milestone{})
}
func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
func assertCreateIssues(t *testing.T, isPull bool) {
assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1"
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
@@ -63,38 +64,14 @@ func assertCreateIssues(t *testing.T, reponame string, isPull bool) {
i := unittest.AssertExistsAndLoadBean(t, &Issue{Title: title}).(*Issue)
unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID})
labelModified := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.EqualValues(t, label.NumIssues+1, labelModified.NumIssues)
assert.EqualValues(t, label.NumClosedIssues+1, labelModified.NumClosedIssues)
milestoneModified := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: milestone.ID}).(*Milestone)
assert.EqualValues(t, milestone.NumIssues+1, milestoneModified.NumIssues)
assert.EqualValues(t, milestone.NumClosedIssues+1, milestoneModified.NumClosedIssues)
}
func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1"
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
assertCreateIssues(t, reponame, false)
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
assert.EqualValues(t, repo.NumIssues+1, repoModified.NumIssues)
assert.EqualValues(t, repo.NumClosedIssues+1, repoModified.NumClosedIssues)
assertCreateIssues(t, false)
}
func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reponame := "repo1"
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
assertCreateIssues(t, reponame, true)
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
assert.EqualValues(t, repo.NumPulls+1, repoModified.NumPulls)
assert.EqualValues(t, repo.NumClosedPulls+1, repoModified.NumClosedPulls)
assertCreateIssues(t, true)
}
func TestMigrate_InsertIssueComments(t *testing.T) {

View File

@@ -0,0 +1,12 @@
-
id: 1
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
-
id: 2
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
-
id: 3
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
-
id: 4
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="

View File

@@ -0,0 +1,21 @@
-
id: 1
name: "u2fkey-correctly-migrated"
user_id: 1
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
counter: 0
- id: 2
name: "u2fkey-incorrectly-migrated"
user_id: 1
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
counter: 0
- id: 3
name: "u2fkey-deleted"
user_id: 1
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
counter: 0
- id: 4
name: "u2fkey-wrong-user-id"
user_id: 2
raw: 0x05040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf240efe2e213b889daf3fc88e3952e8dd6b4cfd82f1a1212e2ab4b19389455ecf3e67f0aeafc91b9c0d413c9d6215a45177c1d5076358aa6ee20e1b30e3d7467cae2308202bd308201a5a00302010202041e8f8734300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203531323732323734303059301306072a8648ce3d020106082a8648ce3d03010703420004a879f82338ed1494bac0704bcc7fc663d1b271715976243101c7605115d7c1529e281c1c67322d384b5cd55dd3e9818d5fd85c22af326e0c64fc20afe33f2366a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e373013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c010104041204102fc0579f811347eab116bb5a8db9202a300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101008693ff62df0d5779d4748d7fc8d10227318a8e580e6a3a57c108e94e03c38568b366894fce5624be4a3efd7f34118b3d993743f792a1989160c8fc9ae0b04e3df9ee15e3e88c04fc82a8dcbf5818e108dcc2968577ae79ff662b94734e3dec4597305d73e6e55ee2beb9cd9678ca0935e533eb638f8e26fabb817cda441fbe9831832ae5f6e2ad992f9ebbdb4c62238b8f8d7ab481d6d3263bcdbf9e4a57550370988ad5813440fa032cadb6723cadd8f8d7ba809f75b43cffa0a5b9add14232ef9d9e14812638233c4ca4a873b9f8ac98e32ba19167606e15909fcddb4a2dffbdae4620249f9a6646ac81e4832d1119febfaa731a882da25a77827d46d190173046022100b579338a44c236d3f214b2e150011a08cf251193ecfae2244edb0a5794e9b301022100fab468862c47d98204d437cf2be8c54a5a4ecd1ebb1c61a6c23da7b9c75f6841
counter: 0

View File

@@ -0,0 +1,30 @@
-
id: 1
lower_name: "u2fkey-correctly-migrated"
name: "u2fkey-correctly-migrated"
user_id: 1
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'fido-u2f'
sign_count: 1
clone_warning: false
-
id: 2
lower_name: "u2fkey-incorrectly-migrated"
name: "u2fkey-incorrectly-migrated"
user_id: 1
credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8A"
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'fido-u2f'
sign_count: 1
clone_warning: false
-
id: 4
lower_name: "u2fkey-wrong-user-id"
name: "u2fkey-wrong-user-id"
user_id: 1
credential_id: "THIS SHOULD CHANGE"
public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
attestation_type: 'fido-u2f'
sign_count: 1
clone_warning: false

View File

@@ -61,7 +61,6 @@ type Version struct {
// update minDBVersion accordingly
var migrations = []Migration{
// Gitea 1.5.0 ends at v69
// v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies),
// v71 -> v72
@@ -367,9 +366,13 @@ var migrations = []Migration{
// v206 -> v207
NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
// v207 -> v208
NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
NewMigration("Add webauthn table and migrate u2f data to webauthn - NO-OPED", addWebAuthnCred),
// v208 -> v209
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive", useBase32HexForCredIDInWebAuthnCredential),
NewMigration("Use base32.HexEncoding instead of base64 encoding for cred ID as it is case insensitive - NO-OPED", useBase32HexForCredIDInWebAuthnCredential),
// v209 -> v210
NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410),
// v210 -> v211
NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials),
}
// GetCurrentDBVersion returns the current db version
@@ -450,9 +453,12 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
// Downgrading Gitea's database version not supported
if int(v-minDBVersion) > len(migrations) {
msg := fmt.Sprintf("Downgrading database version from '%d' to '%d' is not supported and may result in loss of data integrity.\nIf you really know what you're doing, execute `UPDATE version SET version=%d WHERE id=1;`\n",
v, minDBVersion+len(migrations), minDBVersion+len(migrations))
fmt.Fprint(os.Stderr, msg)
msg := fmt.Sprintf("Your database (migration version: %d) is for a newer Gita, you can not use the newer database for this old Gitea release (%d).", v, minDBVersion+len(migrations))
msg += "\nGitea will exit to keep your database safe and unchanged. Please use the correct Gitea release, do not change the migration version manually (incorrect manual operation may lose data)."
if !setting.IsProd {
msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE version SET version=%d WHERE id=1;", minDBVersion+len(migrations))
}
_, _ = fmt.Fprintln(os.Stderr, msg)
log.Fatal(msg)
return nil
}

View File

@@ -5,87 +5,11 @@
package migrations
import (
"crypto/elliptic"
"encoding/base64"
"strings"
"code.gitea.io/gitea/modules/timeutil"
"github.com/tstranex/u2f"
"xorm.io/xorm"
)
func addWebAuthnCred(x *xorm.Engine) error {
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX"`
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
// Now migrate the old u2f registrations to the new format
type u2fRegistration struct {
ID int64 `xorm:"pk autoincr"`
Name string
UserID int64 `xorm:"INDEX"`
Raw []byte
Counter uint32 `xorm:"BIGINT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
var start int
regs := make([]*u2fRegistration, 0, 50)
for {
err := x.OrderBy("id").Limit(50, start).Find(&regs)
if err != nil {
return err
}
for _, reg := range regs {
parsed := new(u2f.Registration)
err = parsed.UnmarshalBinary(reg.Raw)
if err != nil {
continue
}
c := &webauthnCredential{
ID: reg.ID,
Name: reg.Name,
LowerName: strings.ToLower(reg.Name),
UserID: reg.UserID,
CredentialID: base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
AttestationType: "fido-u2f",
AAGUID: []byte{},
SignCount: reg.Counter,
}
_, err := x.Insert(c)
if err != nil {
return err
}
}
if len(regs) < 50 {
break
}
start += 50
regs = regs[:0]
}
// NO-OP Don't migrate here - let v210 do this.
return nil
}

View File

@@ -5,47 +5,10 @@
package migrations
import (
"encoding/base32"
"encoding/base64"
"xorm.io/xorm"
)
func useBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error {
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
CredentialID string `xorm:"INDEX"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
var start int
regs := make([]*webauthnCredential, 0, 50)
for {
err := x.OrderBy("id").Limit(50, start).Find(&regs)
if err != nil {
return err
}
for _, reg := range regs {
credID, _ := base64.RawStdEncoding.DecodeString(reg.CredentialID)
reg.CredentialID = base32.HexEncoding.EncodeToString(credID)
_, err := x.Update(reg)
if err != nil {
return err
}
}
if len(regs) < 50 {
break
}
start += 50
regs = regs[:0]
}
// noop
return nil
}

17
models/migrations/v209.go Normal file
View File

@@ -0,0 +1,17 @@
// 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 migrations
import (
"xorm.io/xorm"
)
func increaseCredentialIDTo410(x *xorm.Engine) error {
// no-op
// V208 is badly broken
// So now we have to no-op again.
return nil
}

184
models/migrations/v210.go Normal file
View File

@@ -0,0 +1,184 @@
// 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 migrations
import (
"crypto/elliptic"
"encoding/base32"
"fmt"
"strings"
"code.gitea.io/gitea/modules/timeutil"
"github.com/tstranex/u2f"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
// v208 migration was completely broken
func remigrateU2FCredentials(x *xorm.Engine) error {
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
switch x.Dialect().URI().DBType {
case schemas.MYSQL:
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)")
if err != nil {
return err
}
case schemas.ORACLE:
_, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)")
if err != nil {
return err
}
case schemas.MSSQL:
// This column has an index on it. I could write all of the code to attempt to change the index OR
// I could just use recreate table.
sess := x.NewSession()
if err := sess.Begin(); err != nil {
_ = sess.Close()
return err
}
if err := recreateTable(sess, new(webauthnCredential)); err != nil {
_ = sess.Close()
return err
}
if err := sess.Commit(); err != nil {
_ = sess.Close()
return err
}
if err := sess.Close(); err != nil {
return err
}
case schemas.POSTGRES:
_, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)")
if err != nil {
return err
}
default:
// SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed
// nor is there any need to re-migrate
}
exist, err := x.IsTableExist("u2f_registration")
if err != nil {
return err
}
if !exist {
return nil
}
// Now migrate the old u2f registrations to the new format
type u2fRegistration struct {
ID int64 `xorm:"pk autoincr"`
Name string
UserID int64 `xorm:"INDEX"`
Raw []byte
Counter uint32 `xorm:"BIGINT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
var start int
regs := make([]*u2fRegistration, 0, 50)
for {
err := x.OrderBy("id").Limit(50, start).Find(&regs)
if err != nil {
return err
}
err = func() error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return fmt.Errorf("unable to allow start session. Error: %w", err)
}
if x.Dialect().URI().DBType == schemas.MSSQL {
if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil {
return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err)
}
}
for _, reg := range regs {
parsed := new(u2f.Registration)
err = parsed.UnmarshalBinary(reg.Raw)
if err != nil {
continue
}
remigrated := &webauthnCredential{
ID: reg.ID,
Name: reg.Name,
LowerName: strings.ToLower(reg.Name),
UserID: reg.UserID,
CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle),
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
AttestationType: "fido-u2f",
AAGUID: []byte{},
SignCount: reg.Counter,
UpdatedUnix: reg.UpdatedUnix,
CreatedUnix: reg.CreatedUnix,
}
has, err := sess.ID(reg.ID).Get(new(webauthnCredential))
if err != nil {
return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err)
}
if !has {
has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
if err != nil {
return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
}
if !has {
_, err = sess.Insert(remigrated)
if err != nil {
return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err)
}
continue
}
}
_, err = sess.ID(remigrated.ID).AllCols().Update(remigrated)
if err != nil {
return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err)
}
}
return sess.Commit()
}()
if err != nil {
return err
}
if len(regs) < 50 {
break
}
start += 50
regs = regs[:0]
}
if x.Dialect().URI().DBType == schemas.POSTGRES {
if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,75 @@
// Copyright 2021 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 migrations
import (
"testing"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
"xorm.io/xorm/schemas"
)
func Test_remigrateU2FCredentials(t *testing.T) {
// Create webauthnCredential table
type WebauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
PublicKey []byte
AttestationType string
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
}
// Now migrate the old u2f registrations to the new format
type U2fRegistration struct {
ID int64 `xorm:"pk autoincr"`
Name string
UserID int64 `xorm:"INDEX"`
Raw []byte
Counter uint32 `xorm:"BIGINT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
type ExpectedWebauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety
}
// Prepare and load the testing database
x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential))
if x == nil || t.Failed() {
defer deferable()
return
}
defer deferable()
if x.Dialect().URI().DBType == schemas.SQLITE {
return
}
// Run the migration
if err := remigrateU2FCredentials(x); err != nil {
assert.NoError(t, err)
return
}
expected := []ExpectedWebauthnCredential{}
if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
return
}
got := []ExpectedWebauthnCredential{}
if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) {
return
}
assert.EqualValues(t, expected, got)
}

View File

@@ -498,14 +498,15 @@ func (n *Notification) APIURL() string {
type NotificationList []*Notification
// LoadAttributes load Repo Issue User and Comment if not loaded
func (nl NotificationList) LoadAttributes() (err error) {
func (nl NotificationList) LoadAttributes() error {
var err error
for i := 0; i < len(nl); i++ {
err = nl[i].LoadAttributes()
if err != nil && !IsErrCommentNotExist(err) {
return
return err
}
}
return
return nil
}
func (nl NotificationList) getPendingRepoIDs() []int64 {

View File

@@ -150,27 +150,56 @@ func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*us
}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err = e.
userIDs := make([]int64, 0, 10)
if err = e.Table("access").
Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite).
Find(&accesses); err != nil {
Select("user_id").
Find(&userIDs); err != nil {
return nil, err
}
additionalUserIDs := make([]int64, 0, 10)
if err = e.Table("team_user").
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite).
Distinct("`team_user`.uid").
Select("`team_user`.uid").
Find(&additionalUserIDs); err != nil {
return nil, err
}
uidMap := map[int64]bool{}
i := 0
for _, uid := range userIDs {
if uidMap[uid] {
continue
}
uidMap[uid] = true
userIDs[i] = uid
i++
}
userIDs = userIDs[:i]
userIDs = append(userIDs, additionalUserIDs...)
for _, uid := range additionalUserIDs {
if uidMap[uid] {
continue
}
userIDs[i] = uid
i++
}
userIDs = userIDs[:i]
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(accesses)+1)
if len(accesses) > 0 {
userIDs := make([]int64, len(accesses))
for i := 0; i < len(accesses); i++ {
userIDs[i] = accesses[i].UserID
}
users := make([]*user_model.User, 0, len(userIDs)+1)
if len(userIDs) > 0 {
if err = e.In("id", userIDs).Find(&users); err != nil {
return nil, err
}
}
if !repo.Owner.IsOrganization() {
if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
users = append(users, repo.Owner)
}
@@ -948,7 +977,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
// Remove attachment with no issue_id and release_id.
for i := range newAttachmentPaths {
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i])
admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachmentPaths[i])
}
if len(repo.Avatar) > 0 {

View File

@@ -167,3 +167,21 @@ func TestLinkedRepository(t *testing.T) {
})
}
}
func TestRepoAssignees(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
users, err := GetRepoAssignees(repo2)
assert.NoError(t, err)
assert.Len(t, users, 1)
assert.Equal(t, users[0].ID, int64(2))
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository)
users, err = GetRepoAssignees(repo21)
assert.NoError(t, err)
assert.Len(t, users, 3)
assert.Equal(t, users[0].ID, int64(15))
assert.Equal(t, users[1].ID, int64(18))
assert.Equal(t, users[2].ID, int64(16))
}

View File

@@ -328,7 +328,12 @@ func AllUnitKeyNames() []string {
// MinUnitAccessMode returns the minial permission of the permission map
func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
res := perm.AccessModeNone
for _, mode := range unitsMap {
for t, mode := range unitsMap {
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
if t == TypeExternalTracker || t == TypeExternalWiki {
continue
}
// get the minial permission great than AccessModeNone except all are AccessModeNone
if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
res = mode

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"net/mail"
"regexp"
"strings"
"code.gitea.io/gitea/models/db"
@@ -21,10 +22,23 @@ import (
"xorm.io/builder"
)
var (
// ErrEmailNotActivated e-mail address has not been activated error
ErrEmailNotActivated = errors.New("E-mail address has not been activated")
)
// ErrEmailNotActivated e-mail address has not been activated error
var ErrEmailNotActivated = errors.New("e-mail address has not been activated")
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
type ErrEmailCharIsNotSupported struct {
Email string
}
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
func IsErrEmailCharIsNotSupported(err error) bool {
_, ok := err.(ErrEmailCharIsNotSupported)
return ok
}
func (err ErrEmailCharIsNotSupported) Error() string {
return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
}
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
type ErrEmailInvalid struct {
@@ -108,12 +122,24 @@ func (email *EmailAddress) BeforeInsert() {
}
}
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
// ValidateEmail check if email is a allowed address
func ValidateEmail(email string) error {
if len(email) == 0 {
return nil
}
if !emailRegexp.MatchString(email) {
return ErrEmailCharIsNotSupported{email}
}
if !(email[0] >= 'a' && email[0] <= 'z') &&
!(email[0] >= 'A' && email[0] <= 'Z') &&
!(email[0] >= '0' && email[0] <= '9') {
return ErrEmailInvalid{email}
}
if _, err := mail.ParseAddress(email); err != nil {
return ErrEmailInvalid{email}
}

View File

@@ -252,3 +252,58 @@ func TestListEmails(t *testing.T) {
assert.Len(t, emails, 5)
assert.Greater(t, count, int64(len(emails)))
}
func TestEmailAddressValidate(t *testing.T) {
kases := map[string]error{
"abc@gmail.com": nil,
"132@hotmail.com": nil,
"1-3-2@test.org": nil,
"1.3.2@test.org": nil,
"a_123@test.org.cn": nil,
`first.last@iana.org`: nil,
`first!last@iana.org`: nil,
`first#last@iana.org`: nil,
`first$last@iana.org`: nil,
`first%last@iana.org`: nil,
`first&last@iana.org`: nil,
`first'last@iana.org`: nil,
`first*last@iana.org`: nil,
`first+last@iana.org`: nil,
`first/last@iana.org`: nil,
`first=last@iana.org`: nil,
`first?last@iana.org`: nil,
`first^last@iana.org`: nil,
"first`last@iana.org": nil,
`first{last@iana.org`: nil,
`first|last@iana.org`: nil,
`first}last@iana.org`: nil,
`first~last@iana.org`: nil,
`first;last@iana.org`: ErrEmailCharIsNotSupported{`first;last@iana.org`},
".233@qq.com": ErrEmailInvalid{".233@qq.com"},
"!233@qq.com": ErrEmailInvalid{"!233@qq.com"},
"#233@qq.com": ErrEmailInvalid{"#233@qq.com"},
"$233@qq.com": ErrEmailInvalid{"$233@qq.com"},
"%233@qq.com": ErrEmailInvalid{"%233@qq.com"},
"&233@qq.com": ErrEmailInvalid{"&233@qq.com"},
"'233@qq.com": ErrEmailInvalid{"'233@qq.com"},
"*233@qq.com": ErrEmailInvalid{"*233@qq.com"},
"+233@qq.com": ErrEmailInvalid{"+233@qq.com"},
"/233@qq.com": ErrEmailInvalid{"/233@qq.com"},
"=233@qq.com": ErrEmailInvalid{"=233@qq.com"},
"?233@qq.com": ErrEmailInvalid{"?233@qq.com"},
"^233@qq.com": ErrEmailInvalid{"^233@qq.com"},
"`233@qq.com": ErrEmailInvalid{"`233@qq.com"},
"{233@qq.com": ErrEmailInvalid{"{233@qq.com"},
"|233@qq.com": ErrEmailInvalid{"|233@qq.com"},
"}233@qq.com": ErrEmailInvalid{"}233@qq.com"},
"~233@qq.com": ErrEmailInvalid{"~233@qq.com"},
";233@qq.com": ErrEmailCharIsNotSupported{";233@qq.com"},
"Foo <foo@bar.com>": ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
string([]byte{0xE2, 0x84, 0xAA}): ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
}
for kase, err := range kases {
t.Run(kase, func(t *testing.T) {
assert.EqualValues(t, err, ValidateEmail(kase))
})
}
}

View File

@@ -30,13 +30,19 @@ func (users UserList) GetTwoFaStatus() map[int64]bool {
for _, user := range users {
results[user.ID] = false // Set default to false
}
tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext))
if err == nil {
if tokenMaps, err := users.loadTwoFactorStatus(db.GetEngine(db.DefaultContext)); err == nil {
for _, token := range tokenMaps {
results[token.UID] = true
}
}
if ids, err := users.userIDsWithWebAuthn(db.GetEngine(db.DefaultContext)); err == nil {
for _, id := range ids {
results[id] = true
}
}
return results
}
@@ -47,15 +53,23 @@ func (users UserList) loadTwoFactorStatus(e db.Engine) (map[int64]*auth.TwoFacto
userIDs := users.GetUserIDs()
tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
err := e.
In("uid", userIDs).
Find(&tokenMaps)
if err != nil {
if err := e.In("uid", userIDs).Find(&tokenMaps); err != nil {
return nil, fmt.Errorf("find two factor: %v", err)
}
return tokenMaps, nil
}
func (users UserList) userIDsWithWebAuthn(e db.Engine) ([]int64, error) {
if len(users) == 0 {
return nil, nil
}
ids := make([]int64, 0, len(users))
if err := e.Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
return nil, fmt.Errorf("find two factor: %v", err)
}
return ids, nil
}
// GetUsersByIDs returns all resolved users from a list of Ids.
func GetUsersByIDs(ids []int64) (UserList, error) {
ous := make([]*User, 0, len(ids))

View File

@@ -20,6 +20,7 @@ import (
// SearchUserOptions contains the options for searching
type SearchUserOptions struct {
db.ListOptions
Keyword string
Type UserType
UID int64
@@ -33,6 +34,8 @@ type SearchUserOptions struct {
IsRestricted util.OptionalBool
IsTwoFactorEnabled util.OptionalBool
IsProhibitLogin util.OptionalBool
ExtraParamStrings map[string]string
}
func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {

View File

@@ -644,6 +644,15 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
u.Visibility = overwriteDefault[0].Visibility
}
// validate data
if err := validateUser(u); err != nil {
return err
}
if err := ValidateEmail(u.Email); err != nil {
return err
}
ctx, committer, err := db.TxContext()
if err != nil {
return err
@@ -652,11 +661,6 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
sess := db.GetEngine(ctx)
// validate data
if err := validateUser(u); err != nil {
return err
}
isExist, err := isUserExist(sess, 0, u.Name)
if err != nil {
return err
@@ -827,8 +831,9 @@ func validateUser(u *User) error {
return ValidateEmail(u.Email)
}
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
if err := validateUser(u); err != nil {
func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error {
err := validateUser(u)
if err != nil {
return err
}
@@ -860,15 +865,35 @@ func updateUser(ctx context.Context, u *User, changePrimaryEmail bool) error {
}); err != nil {
return err
}
} else if !u.IsOrganization() { // check if primary email in email_address table
primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{})
if err != nil {
return err
}
if !primaryEmailExist {
if _, err = e.Insert(&EmailAddress{
Email: u.Email,
UID: u.ID,
IsActivated: true,
IsPrimary: true,
}); err != nil {
return err
}
}
}
_, err := e.ID(u.ID).AllCols().Update(u)
if len(cols) == 0 {
_, err = e.ID(u.ID).AllCols().Update(u)
} else {
_, err = e.ID(u.ID).Cols(cols...).Update(u)
}
return err
}
// UpdateUser updates user's information.
func UpdateUser(u *User, emailChanged bool) error {
return updateUser(db.DefaultContext, u, emailChanged)
func UpdateUser(u *User, emailChanged bool, cols ...string) error {
return updateUser(db.DefaultContext, u, emailChanged, cols...)
}
// UpdateUserCols update user according special columns
@@ -1104,19 +1129,9 @@ func GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
}
email = strings.ToLower(email)
// First try to find the user by primary email
user := &User{Email: email}
has, err := db.GetEngine(ctx).Get(user)
if err != nil {
return nil, err
}
if has {
return user, nil
}
// Otherwise, check in alternative list for activated email addresses
emailAddress := &EmailAddress{Email: email, IsActivated: true}
has, err = db.GetEngine(ctx).Get(emailAddress)
emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true}
has, err := db.GetEngine(ctx).Get(emailAddress)
if err != nil {
return nil, err
}

View File

@@ -232,7 +232,21 @@ func TestCreateUserInvalidEmail(t *testing.T) {
err := CreateUser(user)
assert.Error(t, err)
assert.True(t, IsErrEmailInvalid(err))
assert.True(t, IsErrEmailCharIsNotSupported(err))
}
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
// add new user with user2's email
user.Name = "testuser"
user.LowerName = strings.ToLower(user.Name)
user.ID = 0
err := CreateUser(user)
assert.Error(t, err)
assert.True(t, IsErrEmailAlreadyUsed(err))
}
func TestGetUserIDsByNames(t *testing.T) {

View File

@@ -35,6 +35,10 @@ func Auth(serviceName, userName, passwd string) (string, error) {
if err = t.Authenticate(0); err != nil {
return "", err
}
if err = t.AcctMgmt(0); err != nil {
return "", err
}
// PAM login names might suffer transformations in the PAM stack.
// We should take whatever the PAM stack returns for it.

View File

@@ -63,6 +63,7 @@ func EscapeControlBytes(text []byte) (EscapeStatus, []byte) {
func EscapeControlReader(text io.Reader, output io.Writer) (escaped EscapeStatus, err error) {
buf := make([]byte, 4096)
readStart := 0
runeCount := 0
var n int
var writePos int
@@ -74,10 +75,13 @@ readingloop:
for err == nil {
n, err = text.Read(buf[readStart:])
bs := buf[:n+readStart]
n = len(bs)
i := 0
for i < len(bs) {
r, size := utf8.DecodeRune(bs[i:])
runeCount++
// Now handle the codepoints
switch {
case r == utf8.RuneError:
@@ -112,6 +116,8 @@ readingloop:
lineHasRTLScript = false
lineHasLTRScript = false
case runeCount == 1 && r == 0xFEFF: // UTF BOM
// the first BOM is safe
case r == '\r' || r == '\t' || r == ' ':
// These are acceptable control characters and space characters
case unicode.IsSpace(r):
@@ -143,7 +149,8 @@ readingloop:
return
}
writePos = i + size
case unicode.Is(unicode.C, r):
// 65279 == BOM rune.
case unicode.Is(unicode.C, r) && r != rune(65279):
escaped.Escaped = true
escaped.HasControls = true
if writePos < i {

View File

@@ -129,6 +129,14 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
},
{
// UTF-8/16/32 all use the same codepoint for BOM
// Gitea could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
name: "UTF BOM",
text: "\xef\xbb\xbftest",
result: "\xef\xbb\xbftest",
status: EscapeStatus{HasLTRScript: true},
},
}
func TestEscapeControlString(t *testing.T) {
@@ -163,10 +171,18 @@ func TestEscapeControlReader(t *testing.T) {
// lets add some control characters to the tests
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
copy(tests, escapeControlTests)
// if there is a BOM, we should keep the BOM
addPrefix := func(prefix, s string) string {
if strings.HasPrefix(s, "\xef\xbb\xbf") {
return s[:3] + prefix + s[3:]
}
return prefix + s
}
for _, test := range escapeControlTests {
test.name += " (+Control)"
test.text = "\u001E" + test.text
test.result = `<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">` + "\u001e" + `</span></span>` + test.result
test.text = addPrefix("\u001E", test.text)
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
test.status.Escaped = true
test.status.HasControls = true
tests = append(tests, test)
@@ -174,8 +190,8 @@ func TestEscapeControlReader(t *testing.T) {
for _, test := range escapeControlTests {
test.name += " (+Mark)"
test.text = "\u0300" + test.text
test.result = `<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">` + "\u0300" + `</span></span>` + test.result
test.text = addPrefix("\u0300", test.text)
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
test.status.Escaped = true
test.status.HasMarks = true
tests = append(tests, test)
@@ -200,3 +216,12 @@ func TestEscapeControlReader(t *testing.T) {
})
}
}
func TestEscapeControlReader_panic(t *testing.T) {
bs := make([]byte, 0, 20479)
bs = append(bs, 'A')
for i := 0; i < 6826; i++ {
bs = append(bs, []byte("—")...)
}
_, _ = EscapeControlBytes(bs)
}

View File

@@ -9,9 +9,11 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"html"
"html/template"
"io"
"net"
"net/http"
"net/url"
"path"
@@ -264,6 +266,12 @@ func (ctx *Context) ServerError(logMsg string, logErr error) {
func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
if logErr != nil {
log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
// This is an error within the underlying connection
// and further rendering will not work so just return
return
}
if !setting.IsProd {
ctx.Data["ErrorMsg"] = logErr
}

View File

@@ -129,7 +129,23 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
// Team.
if ctx.Org.IsMember {
shouldSeeAllTeams := false
if ctx.Org.IsOwner {
shouldSeeAllTeams = true
} else {
teams, err := org.GetUserTeams(ctx.User.ID)
if err != nil {
ctx.ServerError("GetUserTeams", err)
return
}
for _, team := range teams {
if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
shouldSeeAllTeams = true
break
}
}
}
if shouldSeeAllTeams {
ctx.Org.Teams, err = org.LoadTeams()
if err != nil {
ctx.ServerError("LoadTeams", err)

View File

@@ -911,7 +911,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme
prefix := strings.TrimPrefix(setting.AppSubURL+strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Repo.RepoLink)
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
ctx.Redirect(path.Join(
ctx.Repo.RepoLink,

View File

@@ -163,6 +163,8 @@ func (c *Command) RunWithContext(rc *RunContext) error {
fmt.Sprintf("LC_ALL=%s", DefaultLocale),
// avoid prompting for credentials interactively, supported since git v2.3
"GIT_TERMINAL_PROMPT=0",
// ignore replace references (https://git-scm.com/docs/git-replace)
"GIT_NO_REPLACE_OBJECTS=1",
)
// TODO: verify if this is still needed in golang 1.15

View File

@@ -16,20 +16,25 @@ import (
"github.com/stretchr/testify/assert"
)
const testReposDir = "tests/repos/"
const benchmarkReposDir = "benchmark/repos/"
const (
testReposDir = "tests/repos/"
)
func cloneRepo(url, dir, name string) (string, error) {
repoDir := filepath.Join(dir, name)
if _, err := os.Stat(repoDir); err == nil {
return repoDir, nil
func cloneRepo(url, name string) (string, error) {
repoDir, err := os.MkdirTemp("", name)
if err != nil {
return "", err
}
return repoDir, Clone(url, repoDir, CloneRepoOptions{
if err := Clone(url, repoDir, CloneRepoOptions{
Mirror: false,
Bare: false,
Quiet: true,
Timeout: 5 * time.Minute,
})
}); err != nil {
_ = util.RemoveAll(repoDir)
return "", err
}
return repoDir, nil
}
func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
@@ -59,20 +64,35 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
}
for _, testCase := range testCases {
commit, err := repo1.GetCommit(testCase.CommitID)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err, "Unable to get commit: %s from testcase due to error: %v", testCase.CommitID, err)
// no point trying to do anything else for this test.
continue
}
assert.NotNil(t, commit)
assert.NotNil(t, commit.Tree)
assert.NotNil(t, commit.Tree.repo)
tree, err := commit.Tree.SubTree(testCase.Path)
if err != nil {
assert.NoError(t, err, "Unable to get subtree: %s of commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
// no point trying to do anything else for this test.
continue
}
assert.NotNil(t, tree, "tree is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
assert.NotNil(t, tree.repo, "repo is nil for testCase CommitID %s in Path %s", testCase.CommitID, testCase.Path)
assert.NoError(t, err)
entries, err := tree.ListEntries()
assert.NoError(t, err)
commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.Background(), commit, testCase.Path, nil)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err, "Unable to get entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
// no point trying to do anything else for this test.
continue
}
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.TODO(), commit, testCase.Path, nil)
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
if err != nil {
t.FailNow()
}
@@ -98,40 +118,52 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
testGetCommitsInfo(t, bareRepo1)
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestEntries_GetCommitsInfo")
assert.NoError(t, err)
clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestEntries_GetCommitsInfo")
if err != nil {
assert.NoError(t, err)
}
defer util.RemoveAll(clonedPath)
clonedRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
}
defer clonedRepo1.Close()
testGetCommitsInfo(t, clonedRepo1)
}
func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
benchmarks := []struct {
type benchmarkType struct {
url string
name string
}{
}
benchmarks := []benchmarkType{
{url: "https://github.com/go-gitea/gitea.git", name: "gitea"},
{url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"},
{url: "https://github.com/moby/moby.git", name: "moby"},
{url: "https://github.com/golang/go.git", name: "go"},
{url: "https://github.com/torvalds/linux.git", name: "linux"},
}
for _, benchmark := range benchmarks {
doBenchmark := func(benchmark benchmarkType) {
var commit *Commit
var entries Entries
var repo *Repository
if repoPath, err := cloneRepo(benchmark.url, benchmarkReposDir, benchmark.name); err != nil {
repoPath, err := cloneRepo(benchmark.url, benchmark.name)
if err != nil {
b.Fatal(err)
} else if repo, err = OpenRepository(repoPath); err != nil {
}
defer util.RemoveAll(repoPath)
if repo, err = OpenRepository(repoPath); err != nil {
b.Fatal(err)
} else if commit, err = repo.GetBranchCommit("master"); err != nil {
repo.Close()
}
defer repo.Close()
if commit, err = repo.GetBranchCommit("master"); err != nil {
b.Fatal(err)
} else if entries, err = commit.Tree.ListEntries(); err != nil {
repo.Close()
b.Fatal(err)
}
entries.Sort()
@@ -144,6 +176,9 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
}
}
})
repo.Close()
}
for _, benchmark := range benchmarks {
doBenchmark(benchmark)
}
}

View File

@@ -80,20 +80,19 @@ func InitRepository(repoPath string, bare bool) error {
// IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder
if err := NewCommandContext(repo.Ctx, "rev-list", "--all", "--count", "--max-count=1").RunWithContext(&RunContext{
if err := NewCommandContext(repo.Ctx, "show-ref", "--head", "^HEAD$").RunWithContext(&RunContext{
Timeout: -1,
Dir: repo.Path,
Stdout: &output,
Stderr: &errbuf,
}); err != nil {
if err.Error() == "exit status 1" && errbuf.String() == "" {
return true, nil
}
return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
}
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
return strings.TrimSpace(output.String()) == "", nil
}
// CloneRepoOptions options when clone a repository

View File

@@ -87,7 +87,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
return nil, fmt.Errorf("wrong number of fields in return from check-attr")
}
var name2attribute2info = make(map[string]map[string]string)
name2attribute2info := make(map[string]map[string]string)
for i := 0; i < (len(fields) / 3); i++ {
filename := string(fields[3*i])
@@ -179,17 +179,23 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
// Run run cmd
func (c *CheckAttributeReader) Run() error {
defer func() {
_ = c.Close()
_ = c.stdinReader.Close()
_ = c.stdOut.Close()
}()
stdErr := new(bytes.Buffer)
err := c.cmd.RunInDirTimeoutEnvFullPipelineFunc(c.env, -1, c.Repo.Path, c.stdOut, stdErr, c.stdinReader, func(_ context.Context, _ context.CancelFunc) error {
close(c.running)
select {
case <-c.running:
default:
close(c.running)
}
return nil
})
if err != nil && c.ctx.Err() != nil && err.Error() != "signal: killed" {
if err != nil && // If there is an error we need to return but:
c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
}
return nil
}
@@ -229,10 +235,8 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
// Close close pip after use
func (c *CheckAttributeReader) Close() error {
err := c.stdinWriter.Close()
_ = c.stdinReader.Close()
_ = c.stdOut.Close()
c.cancel()
err := c.stdinWriter.Close()
select {
case <-c.running:
default:

View File

@@ -17,17 +17,33 @@ import (
func TestGetFormatPatch(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestGetFormatPatch")
clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestGetFormatPatch")
if err != nil {
assert.NoError(t, err)
return
}
defer util.RemoveAll(clonedPath)
assert.NoError(t, err)
repo, err := OpenRepository(clonedPath)
if err != nil {
assert.NoError(t, err)
return
}
defer repo.Close()
assert.NoError(t, err)
rd := &bytes.Buffer{}
err = repo.GetPatch("8d92fc95^", "8d92fc95", rd)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
patchb, err := io.ReadAll(rd)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
patch := string(patchb)
assert.Regexp(t, "^From 8d92fc95", patch)
assert.Contains(t, patch, "Subject: [PATCH] Add file2.txt")
@@ -37,17 +53,25 @@ func TestReadPatch(t *testing.T) {
// Ensure we can read the patch files
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
if err != nil {
assert.NoError(t, err)
return
}
defer repo.Close()
assert.NoError(t, err)
// This patch doesn't exist
noFile, err := repo.ReadPatchCommit(0)
assert.Error(t, err)
// This patch is an empty one (sometimes it's a 404)
noCommit, err := repo.ReadPatchCommit(1)
assert.Error(t, err)
// This patch is legit and should return a commit
oldCommit, err := repo.ReadPatchCommit(2)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.Empty(t, noFile)
assert.Empty(t, noCommit)
@@ -58,23 +82,45 @@ func TestReadPatch(t *testing.T) {
func TestReadWritePullHead(t *testing.T) {
// Ensure we can write SHA1 head corresponding to PR and open them
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
repo, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err)
// As we are writing we should clone the repository first
clonedPath, err := cloneRepo(bareRepo1Path, "TestReadWritePullHead")
if err != nil {
assert.NoError(t, err)
return
}
defer util.RemoveAll(clonedPath)
repo, err := OpenRepository(clonedPath)
if err != nil {
assert.NoError(t, err)
return
}
defer repo.Close()
// Try to open non-existing Pull
_, err = repo.GetRefCommitID(PullPrefix + "0/head")
assert.Error(t, err)
// Write a fake sha1 with only 40 zeros
newCommit := "feaf4ba6bc635fec442f46ddd4512416ec43c2c2"
err = repo.SetReference(PullPrefix+"1/head", newCommit)
assert.NoError(t, err)
// Remove file after the test
defer func() {
_ = repo.RemoveReference(PullPrefix + "1/head")
}()
if err != nil {
assert.NoError(t, err)
return
}
// Read the file created
headContents, err := repo.GetRefCommitID(PullPrefix + "1/head")
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.Len(t, string(headContents), 40)
assert.True(t, string(headContents) == newCommit)
// Remove file after the test
err = repo.RemoveReference(PullPrefix + "1/head")
assert.NoError(t, err)
}

View File

@@ -88,7 +88,10 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
}()
}
defer cancel()
defer func() {
_ = checker.Close()
cancel()
}()
}
}

View File

@@ -16,11 +16,17 @@ import (
func TestRepository_GetTags(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepository(bareRepo1Path)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
defer bareRepo1.Close()
tags, total, err := bareRepo1.GetTagInfos(0, 0)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.Len(t, tags, 1)
assert.Equal(t, len(tags), total)
assert.EqualValues(t, "test", tags[0].Name)
@@ -31,40 +37,75 @@ func TestRepository_GetTags(t *testing.T) {
func TestRepository_GetTag(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
assert.NoError(t, err)
clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetTag")
if err != nil {
assert.NoError(t, err)
return
}
defer util.RemoveAll(clonedPath)
bareRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
defer bareRepo1.Close()
// LIGHTWEIGHT TAGS
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
lTagName := "lightweightTag"
bareRepo1.CreateTag(lTagName, lTagCommitID)
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
aTagName := "annotatedTag"
aTagMessage := "my annotated message \n - test two line"
bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
aTagID, _ := bareRepo1.GetTagID(aTagName)
// Create the lightweight tag
err = bareRepo1.CreateTag(lTagName, lTagCommitID)
if err != nil {
assert.NoError(t, err, "Unable to create the lightweight tag: %s for ID: %s. Error: %v", lTagName, lTagCommitID, err)
return
}
// and try to get the Tag for lightweight tag
lTag, err := bareRepo1.GetTag(lTagName)
assert.NoError(t, err)
assert.NotNil(t, lTag)
if err != nil {
assert.NoError(t, err)
return
}
if lTag == nil {
assert.NotNil(t, lTag)
assert.FailNow(t, "nil lTag: %s", lTagName)
return
}
assert.EqualValues(t, lTagName, lTag.Name)
assert.EqualValues(t, lTagCommitID, lTag.ID.String())
assert.EqualValues(t, lTagCommitID, lTag.Object.String())
assert.EqualValues(t, "commit", lTag.Type)
// ANNOTATED TAGS
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
aTagName := "annotatedTag"
aTagMessage := "my annotated message \n - test two line"
// Create the annotated tag
err = bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
if err != nil {
assert.NoError(t, err, "Unable to create the annotated tag: %s for ID: %s. Error: %v", aTagName, aTagCommitID, err)
return
}
// Now try to get the tag for the annotated Tag
aTagID, err := bareRepo1.GetTagID(aTagName)
if err != nil {
assert.NoError(t, err)
return
}
aTag, err := bareRepo1.GetTag(aTagName)
assert.NoError(t, err)
assert.NotNil(t, aTag)
if err != nil {
assert.NoError(t, err)
return
}
if aTag == nil {
assert.NotNil(t, aTag)
assert.FailNow(t, "nil aTag: %s", aTagName)
return
}
assert.EqualValues(t, aTagName, aTag.Name)
assert.EqualValues(t, aTagID, aTag.ID.String())
@@ -72,26 +113,47 @@ func TestRepository_GetTag(t *testing.T) {
assert.EqualValues(t, aTagCommitID, aTag.Object.String())
assert.EqualValues(t, "tag", aTag.Type)
// RELEASE TAGS
rTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
rTagName := "release/" + lTagName
bareRepo1.CreateTag(rTagName, rTagCommitID)
err = bareRepo1.CreateTag(rTagName, rTagCommitID)
if err != nil {
assert.NoError(t, err, "Unable to create the tag: %s for ID: %s. Error: %v", rTagName, rTagCommitID, err)
return
}
rTagID, err := bareRepo1.GetTagID(rTagName)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.EqualValues(t, rTagCommitID, rTagID)
oTagID, err := bareRepo1.GetTagID(lTagName)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.EqualValues(t, lTagCommitID, oTagID)
}
func TestRepository_GetAnnotatedTag(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
assert.NoError(t, err)
clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetAnnotatedTag")
if err != nil {
assert.NoError(t, err)
return
}
defer util.RemoveAll(clonedPath)
bareRepo1, err := OpenRepository(clonedPath)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
defer bareRepo1.Close()
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
@@ -106,7 +168,10 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
// Try an annotated tag
tag, err := bareRepo1.GetAnnotatedTag(aTagID)
assert.NoError(t, err)
if err != nil {
assert.NoError(t, err)
return
}
assert.NotNil(t, tag)
assert.EqualValues(t, aTagName, tag.Name)
assert.EqualValues(t, aTagID, tag.ID.String())

View File

@@ -192,6 +192,7 @@ func (g *Manager) RunAtHammer(hammer func()) {
}
func (g *Manager) doShutdown() {
if !g.setStateTransition(stateRunning, stateShuttingDown) {
g.DoImmediateHammer()
return
}
g.lock.Lock()

View File

@@ -168,8 +168,12 @@ func (g *Manager) DoGracefulRestart() {
if setting.GracefulRestartable {
log.Info("PID: %d. Forking...", os.Getpid())
err := g.doFork()
if err != nil && err.Error() != "another process already forked. Ignoring this one" {
log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
if err != nil {
if err.Error() == "another process already forked. Ignoring this one" {
g.DoImmediateHammer()
} else {
log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
}
}
} else {
log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())

View File

@@ -14,6 +14,8 @@ import (
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
)
const (
@@ -111,6 +113,17 @@ func (p Pointer) RelativePath() string {
return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid[4:])
}
// ColorFormat provides a basic color format for a Team
func (p Pointer) ColorFormat(s fmt.State) {
if p.Oid == "" && p.Size == 0 {
log.ColorFprintf(s, "<empty>")
return
}
log.ColorFprintf(s, "%s:%d",
log.NewColoredIDValue(p.Oid),
p.Size)
}
// GeneratePointer generates a pointer for arbitrary content
func GeneratePointer(content io.Reader) (Pointer, error) {
h := sha256.New()

View File

@@ -99,7 +99,7 @@ var issueFullPatternOnce sync.Once
func getIssueFullPattern() *regexp.Regexp {
issueFullPatternOnce.Do(func() {
issueFullPattern = regexp.MustCompile(regexp.QuoteMeta(setting.AppURL) +
`\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
`[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b`)
})
return issueFullPattern
}

View File

@@ -95,6 +95,15 @@ func TestRender_CrossReferences(t *testing.T) {
test(
"/home/gitea/go-gitea/gitea#12345",
`<p>/home/gitea/go-gitea/gitea#12345</p>`)
test(
util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345"),
`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p>`)
test(
util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345"),
`<p><a href="`+util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
test(
util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
}
func TestMisc_IsSameDomain(t *testing.T) {

View File

@@ -5,10 +5,12 @@
package nosql
import (
"fmt"
"path"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
@@ -20,8 +22,16 @@ func (m *Manager) CloseLevelDB(connection string) error {
defer m.mutex.Unlock()
db, ok := m.LevelDBConnections[connection]
if !ok {
connection = ToLevelDBURI(connection).String()
db, ok = m.LevelDBConnections[connection]
// Try the full URI
uri := ToLevelDBURI(connection)
db, ok = m.LevelDBConnections[uri.String()]
if !ok {
// Try the datadir directly
dataDir := path.Join(uri.Host, uri.Path)
db, ok = m.LevelDBConnections[dataDir]
}
}
if !ok {
return nil
@@ -40,6 +50,12 @@ func (m *Manager) CloseLevelDB(connection string) error {
// GetLevelDB gets a levelDB for a particular connection
func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
// Convert the provided connection description to the common format
uri := ToLevelDBURI(connection)
// Get the datadir
dataDir := path.Join(uri.Host, uri.Path)
m.mutex.Lock()
defer m.mutex.Unlock()
db, ok := m.LevelDBConnections[connection]
@@ -48,12 +64,28 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
return db.db, nil
}
uri := ToLevelDBURI(connection)
db = &levelDBHolder{
name: []string{connection, uri.String()},
db, ok = m.LevelDBConnections[uri.String()]
if ok {
db.count++
return db.db, nil
}
// if there is already a connection to this leveldb reuse that
// NOTE: if there differing options then only the first leveldb connection will be used
db, ok = m.LevelDBConnections[dataDir]
if ok {
db.count++
log.Warn("Duplicate connnection to level db: %s with different connection strings. Initial connection: %s. This connection: %s", dataDir, db.name[0], connection)
db.name = append(db.name, connection)
m.LevelDBConnections[connection] = db
return db.db, nil
}
db = &levelDBHolder{
name: []string{connection, uri.String(), dataDir},
}
dataDir := path.Join(uri.Host, uri.Path)
opts := &opt.Options{}
for k, v := range uri.Query() {
switch replacer.Replace(strings.ToLower(k)) {
@@ -134,7 +166,11 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
db.db, err = leveldb.OpenFile(dataDir, opts)
if err != nil {
if !errors.IsCorrupted(err) {
return nil, err
if strings.Contains(err.Error(), "resource temporarily unavailable") {
return nil, fmt.Errorf("unable to lock level db at %s: %w", dataDir, err)
}
return nil, fmt.Errorf("unable to open level db at %s: %w", dataDir, err)
}
db.db, err = leveldb.RecoverFile(dataDir, opts)
if err != nil {

View File

@@ -115,7 +115,7 @@ func (m *mailNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comm
func (m *mailNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
// mail only sent to added assignees and not self-assignee
if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == user_model.EmailNotificationsEnabled {
if !removed && doer.ID != assignee.ID && (assignee.EmailNotifications() == user_model.EmailNotificationsEnabled || assignee.EmailNotifications() == user_model.EmailNotificationsOnMention) {
ct := fmt.Sprintf("Assigned #%d.", issue.Index)
if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{assignee}); err != nil {
log.Error("Error in SendIssueAssignedMail for issue[%d] to assignee[%d]: %v", issue.ID, assignee.ID, err)
@@ -124,7 +124,7 @@ func (m *mailNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue *m
}
func (m *mailNotifier) NotifyPullReviewRequest(doer *user_model.User, issue *models.Issue, reviewer *user_model.User, isRequest bool, comment *models.Comment) {
if isRequest && doer.ID != reviewer.ID && reviewer.EmailNotifications() == user_model.EmailNotificationsEnabled {
if isRequest && doer.ID != reviewer.ID && (reviewer.EmailNotifications() == user_model.EmailNotificationsEnabled || reviewer.EmailNotifications() == user_model.EmailNotificationsOnMention) {
ct := fmt.Sprintf("Requested to review %s.", issue.HTMLURL())
if err := mailer.SendIssueAssignedMail(issue, doer, ct, comment, []*user_model.User{reviewer}); err != nil {
log.Error("Error in SendIssueAssignedMail for issue[%d] to reviewer[%d]: %v", issue.ID, reviewer.ID, err)

View File

@@ -204,7 +204,7 @@ func (ns *notificationService) NotifyPullRevieweDismiss(doer *user_model.User, r
}
func (ns *notificationService) NotifyIssueChangeAssignee(doer *user_model.User, issue *models.Issue, assignee *user_model.User, removed bool, comment *models.Comment) {
if !removed {
if !removed && doer.ID != assignee.ID {
var opts = issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: doer.ID,

View File

@@ -195,9 +195,11 @@ loop:
}
}
var errQueueEmpty = fmt.Errorf("empty queue")
var errEmptyBytes = fmt.Errorf("empty bytes")
var errUnmarshal = fmt.Errorf("failed to unmarshal")
var (
errQueueEmpty = fmt.Errorf("empty queue")
errEmptyBytes = fmt.Errorf("empty bytes")
errUnmarshal = fmt.Errorf("failed to unmarshal")
)
func (q *ByteFIFOQueue) doPop() error {
q.lock.Lock()

View File

@@ -251,8 +251,8 @@ func (q *PersistableChannelQueue) Shutdown() {
q.channelQueue.Wait()
q.internal.(*LevelQueue).Wait()
// Redirect all remaining data in the chan to the internal channel
close(q.channelQueue.dataChan)
log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name)
close(q.channelQueue.dataChan)
for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data)
atomic.AddInt64(&q.channelQueue.numInQueue, -1)

View File

@@ -188,5 +188,4 @@ func TestPersistableChannelQueue(t *testing.T) {
for _, callback := range callbacks {
callback()
}
}

View File

@@ -238,13 +238,12 @@ func (q *PersistableChannelUniqueQueue) Shutdown() {
q.channelQueue.Wait()
q.internal.(*LevelUniqueQueue).Wait()
// Redirect all remaining data in the chan to the internal channel
go func() {
log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name)
for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data)
}
log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
}()
close(q.channelQueue.dataChan)
log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name)
for data := range q.channelQueue.dataChan {
_ = q.internal.Push(data)
}
log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name)
log.Debug("PersistableChannelUniqueQueue: %s Shutdown", q.delayedStarter.name)
}

View File

@@ -87,6 +87,20 @@ func (p *WorkerPool) Push(data Data) {
}
}
// HasNoWorkerScaling will return true if the queue has no workers, and has no worker boosting
func (p *WorkerPool) HasNoWorkerScaling() bool {
p.lock.Lock()
defer p.lock.Unlock()
return p.hasNoWorkerScaling()
}
func (p *WorkerPool) hasNoWorkerScaling() bool {
return p.numberOfWorkers == 0 && (p.boostTimeout == 0 || p.boostWorkers == 0 || p.maxNumberOfWorkers == 0)
}
// zeroBoost will add a temporary boost worker for a no worker queue
// p.lock must be locked at the start of this function BUT it will be unlocked by the end of this function
// (This is because addWorkers has to be called whilst unlocked)
func (p *WorkerPool) zeroBoost() {
ctx, cancel := context.WithTimeout(p.baseCtx, p.boostTimeout)
mq := GetManager().GetManagedQueue(p.qid)
@@ -277,6 +291,21 @@ func (p *WorkerPool) addWorkers(ctx context.Context, cancel context.CancelFunc,
p.cond.Broadcast()
cancel()
}
select {
case <-p.baseCtx.Done():
// Don't warn if the baseCtx is shutdown
default:
if p.hasNoWorkerScaling() {
log.Warn(
"Queue: %d is configured to be non-scaling and has no workers - this configuration is likely incorrect.", p.qid)
} else if p.numberOfWorkers == 0 && atomic.LoadInt64(&p.numInQueue) > 0 {
// OK there are no workers but... there's still work to be done -> Reboost
p.zeroBoost()
// p.lock will be unlocked by zeroBoost
return
}
}
p.lock.Unlock()
}()
}

View File

@@ -254,7 +254,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
opts.Page = page
rels, err := models.GetReleasesByRepoID(repo.ID, opts)
if err != nil {
return fmt.Errorf("GetReleasesByRepoID: %v", err)
return fmt.Errorf("unable to GetReleasesByRepoID in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
if len(rels) == 0 {
break
@@ -265,11 +265,11 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
}
commitID, err := gitRepo.GetTagCommitID(rel.TagName)
if err != nil && !git.IsErrNotExist(err) {
return fmt.Errorf("GetTagCommitID: %s: %v", rel.TagName, err)
return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
}
if git.IsErrNotExist(err) || commitID != rel.Sha1 {
if err := models.PushUpdateDeleteTag(repo, rel.TagName); err != nil {
return fmt.Errorf("PushUpdateDeleteTag: %s: %v", rel.TagName, err)
return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
}
} else {
existingRelTags[strings.ToLower(rel.TagName)] = struct{}{}
@@ -278,12 +278,12 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
}
tags, err := gitRepo.GetTags(0, 0)
if err != nil {
return fmt.Errorf("GetTags: %v", err)
return fmt.Errorf("unable to GetTags in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
}
for _, tagName := range tags {
if _, ok := existingRelTags[strings.ToLower(tagName)]; !ok {
if err := PushUpdateAddTag(repo, gitRepo, tagName); err != nil {
return fmt.Errorf("pushUpdateAddTag: %v", err)
return fmt.Errorf("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %w", tagName, repo.ID, repo.OwnerName, repo.Name, err)
}
}
}
@@ -294,11 +294,11 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository)
func PushUpdateAddTag(repo *repo_model.Repository, gitRepo *git.Repository, tagName string) error {
tag, err := gitRepo.GetTag(tagName)
if err != nil {
return fmt.Errorf("GetTag: %v", err)
return fmt.Errorf("unable to GetTag: %w", err)
}
commit, err := tag.Commit(gitRepo)
if err != nil {
return fmt.Errorf("Commit: %v", err)
return fmt.Errorf("unable to get tag Commit: %w", err)
}
sig := tag.Tagger
@@ -315,14 +315,14 @@ func PushUpdateAddTag(repo *repo_model.Repository, gitRepo *git.Repository, tagN
if sig != nil {
author, err = user_model.GetUserByEmail(sig.Email)
if err != nil && !user_model.IsErrUserNotExist(err) {
return fmt.Errorf("GetUserByEmail: %v", err)
return fmt.Errorf("unable to GetUserByEmail for %q: %w", sig.Email, err)
}
createdAt = sig.When
}
commitsCount, err := commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)
return fmt.Errorf("unable to get CommitsCount: %w", err)
}
var rel = models.Release{
@@ -359,14 +359,14 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: p, RepositoryID: repo.ID})
if err != nil {
log.Error("Error creating LFS meta object %v: %v", p, err)
log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, p, err)
return err
}
if err := contentStore.Put(p, content); err != nil {
log.Error("Error storing content for LFS meta object %v: %v", p, err)
log.Error("Repo[%-v]: Error storing content for LFS meta object %-v: %v", repo, p, err)
if _, err2 := models.RemoveLFSMetaObjectByOid(repo.ID, p.Oid); err2 != nil {
log.Error("Error removing LFS meta object %v: %v", p, err2)
log.Error("Repo[%-v]: Error removing LFS meta object %-v: %v", repo, p, err2)
}
return err
}
@@ -386,32 +386,32 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
for pointerBlob := range pointerChan {
meta, err := models.GetLFSMetaObjectByOid(repo.ID, pointerBlob.Oid)
if err != nil && err != models.ErrLFSObjectNotExist {
log.Error("Error querying LFS meta object %v: %v", pointerBlob.Pointer, err)
log.Error("Repo[%-v]: Error querying LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
return err
}
if meta != nil {
log.Trace("Skipping unknown LFS meta object %v", pointerBlob.Pointer)
log.Trace("Repo[%-v]: Skipping unknown LFS meta object %-v", repo, pointerBlob.Pointer)
continue
}
log.Trace("LFS object %v not present in repository %s", pointerBlob.Pointer, repo.FullName())
log.Trace("Repo[%-v]: LFS object %-v not present in repository", repo, pointerBlob.Pointer)
exist, err := contentStore.Exists(pointerBlob.Pointer)
if err != nil {
log.Error("Error checking if LFS object %v exists: %v", pointerBlob.Pointer, err)
log.Error("Repo[%-v]: Error checking if LFS object %-v exists: %v", repo, pointerBlob.Pointer, err)
return err
}
if exist {
log.Trace("LFS object %v already present; creating meta object", pointerBlob.Pointer)
log.Trace("Repo[%-v]: LFS object %-v already present; creating meta object", repo, pointerBlob.Pointer)
_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: pointerBlob.Pointer, RepositoryID: repo.ID})
if err != nil {
log.Error("Error creating LFS meta object %v: %v", pointerBlob.Pointer, err)
log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
return err
}
} else {
if setting.LFS.MaxFileSize > 0 && pointerBlob.Size > setting.LFS.MaxFileSize {
log.Info("LFS object %v download denied because of LFS_MAX_FILE_SIZE=%d < size %d", pointerBlob.Pointer, setting.LFS.MaxFileSize, pointerBlob.Size)
log.Info("Repo[%-v]: LFS object %-v download denied because of LFS_MAX_FILE_SIZE=%d < size %d", repo, pointerBlob.Pointer, setting.LFS.MaxFileSize, pointerBlob.Size)
continue
}
@@ -432,7 +432,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
err, has := <-errChan
if has {
log.Error("Error enumerating LFS objects for repository: %v", err)
log.Error("Repo[%-v]: Error enumerating LFS objects for repository: %v", repo, err)
return err
}

View File

@@ -1022,8 +1022,13 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
}
sec = Cfg.Section("U2F")
U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
// FIXME: DEPRECATED to be removed in v1.18.0
U2F.AppID = strings.TrimSuffix(AppURL, "/")
if Cfg.Section("U2F").HasKey("APP_ID") {
U2F.AppID = Cfg.Section("U2F").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
} else if Cfg.Section("u2f").HasKey("APP_ID") {
U2F.AppID = Cfg.Section("u2f").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
}
}
func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
@@ -1162,7 +1167,6 @@ func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte {
},
},
})
if err != nil {
log.Error("unable to marshal manifest JSON. Error: %v", err)
return make([]byte, 0)

View File

@@ -6,18 +6,21 @@ package storage
import (
"context"
"errors"
"io"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
var (
_ ObjectStorage = &LocalStorage{}
)
// ErrLocalPathNotSupported represents an error that path is not supported
var ErrLocalPathNotSupported = errors.New("local path is not supported")
var _ ObjectStorage = &LocalStorage{}
// LocalStorageType is the type descriptor for local storage
const LocalStorageType Type = "local"
@@ -61,11 +64,18 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
// Open a file
func (l *LocalStorage) Open(path string) (Object, error) {
if !isLocalPathValid(path) {
return nil, ErrLocalPathNotSupported
}
return os.Open(filepath.Join(l.dir, path))
}
// Save a file
func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
if !isLocalPathValid(path) {
return 0, ErrLocalPathNotSupported
}
p := filepath.Join(l.dir, path)
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
return 0, err
@@ -109,8 +119,19 @@ func (l *LocalStorage) Stat(path string) (os.FileInfo, error) {
return os.Stat(filepath.Join(l.dir, path))
}
func isLocalPathValid(p string) bool {
a := path.Clean(p)
if strings.HasPrefix(a, "../") || strings.HasPrefix(a, "..\\") {
return false
}
return a == p
}
// Delete delete a file
func (l *LocalStorage) Delete(path string) error {
if !isLocalPathValid(path) {
return ErrLocalPathNotSupported
}
p := filepath.Join(l.dir, path)
return util.Remove(p)
}

View File

@@ -0,0 +1,45 @@
// 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 storage
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLocalPathIsValid(t *testing.T) {
kases := []struct {
path string
valid bool
}{
{
"a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
true,
},
{
"../a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
false,
},
{
"a\\0\\a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
true,
},
{
"b/../a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
false,
},
{
"..\\a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
false,
},
}
for _, k := range kases {
t.Run(k.path, func(t *testing.T) {
assert.EqualValues(t, k.valid, isLocalPathValid(k.path))
})
}
}

View File

@@ -41,6 +41,7 @@ webauthn_use_twofa=Zwei-Faktor-Authentifizierung via Handy verwenden
webauthn_error=Dein Sicherheitsschlüssel konnte nicht gelesen werden.
webauthn_unsupported_browser=Dein Browser unterstützt derzeit keinen WebAuthn.
webauthn_error_unknown=Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.
webauthn_error_insecure=WebAuthn unterstützt nur sichere Verbindungen. Zum Testen über HTTP kannst du "localhost" oder "127.0.0.1" als Host verwenden
webauthn_error_unable_to_process=Der Server konnte deine Anfrage nicht bearbeiten.
webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist.
webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut.
@@ -140,6 +141,7 @@ charset=Zeichensatz
path=Pfad
sqlite_helper=Dateipfad zur SQLite3 Datenbank.<br>Gebe einen absoluten Pfad an, wenn Gitea als Service gestartet wird.
reinstall_error=Du versuchst, in eine bereits existierende Gitea Datenbank zu installieren
reinstall_confirm_message=Eine Neuinstallation mit einer bestehenden Gitea-Datenbank kann mehrere Probleme verursachen. In den meisten Fällen solltest du deine vorhandene "app.ini" verwenden, um Gitea auszuführen. Wenn du weist, was du tust, bestätigen die folgenden Angaben:
reinstall_confirm_check_3=Du bestätigst, dass du absolut sicher bist, dass diese Gitea mit der richtigen app.ini läuft, und du sicher bist, dass du neu installieren musst. Du bestätigst, dass du die oben genannten Risiken anerkennst.
err_empty_db_path=Der SQLite3 Datenbankpfad darf nicht leer sein.
no_admin_and_disable_registration=Du kannst Selbst-Registrierungen nicht deaktivieren, ohne ein Administratorkonto zu erstellen.
@@ -2050,8 +2052,8 @@ settings.lfs_pointers.accessible=Nutzer hat Zugriff
settings.lfs_pointers.associateAccessible=Ordne %d zugängliche OIDs zu
settings.rename_branch_failed_exist=Kann den Branch nicht umbenennen, da der Zielbranch %s bereits existiert.
settings.rename_branch_failed_not_exist=Kann den Branch %s nicht umbenennen, da er nicht existiert.
settings.rename_branch_success=Zweig %s wurde erfolgreich in %s umbenannt.
settings.rename_branch_from=alter Zweigname
settings.rename_branch_success=Branch %s wurde erfolgreich in %s umbenannt.
settings.rename_branch_from=alter Branchname
settings.rename_branch_to=neuer Branchname
settings.rename_branch=Branch umbennen

View File

@@ -34,6 +34,20 @@ twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
twofa_scratch=Κωδικός Μίας Χρήσης Δύο Παραγόντων
passcode=Κωδικός
webauthn_insert_key=Εισάγετε το κλειδί ασφαλείας σας
webauthn_sign_in=Πατήστε το κουμπί στο κλειδί ασφαλείας. Αν το κλειδί ασφαλείας σας δεν έχει κουμπί, τοποθετήστε το ξανά.
webauthn_press_button=Παρακαλώ πατήστε το κουμπί στο κλειδί ασφαλείας…
webauthn_use_twofa=Χρησιμοποιήστε έναν κωδικό δύο παραγόντων από το τηλέφωνό σας
webauthn_error=Αδύνατη η ανάγνωση του κλειδιού ασφαλείας.
webauthn_unsupported_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει επί του παρόντος WebAuthn.
webauthn_error_unknown=Παρουσιάστηκε ένα άγνωστο σφάλμα. Παρακαλώ προσπαθήστε ξανά.
webauthn_error_insecure=Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση "localhost" ή "127.0.0.1"
webauthn_error_unable_to_process=Ο διακομιστής δεν μπόρεσε να επεξεργαστεί το αίτημά σας.
webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί.
webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί.
webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.
webauthn_u2f_deprecated=Το κλειδί: '%s' πιστοποιεί χρησιμοποιώντας το παρωχημένο πρωτόκολλο U2F. Θα πρέπει να καταχωρήσετε ξανά αυτό το κλειδί και να καταργήσετε την παλιά εγγραφή.
webauthn_reload=Ανανέωση
repository=Αποθετήριο
organization=Οργανισμός
@@ -305,6 +319,9 @@ oauth_signup_submit=Ολοκληρωμένος Λογαριασμός
oauth_signin_tab=Σύνδεση με υπάρχων λογαριασμό
oauth_signin_title=Συνδεθείτε για να εγκρίνετε τον Συνδεδεμένο Λογαριασμό
oauth_signin_submit=Σύνδεση Λογαριασμού
oauth.signin.error=Παρουσιάστηκε σφάλμα κατά την επεξεργασία του αιτήματος εξουσιοδότησης. Εάν αυτό το σφάλμα επιμένει, παρακαλούμε επικοινωνήστε με το διαχειριστή του ιστοτόπου.
oauth.signin.error.access_denied=Η αίτηση εξουσιοδότησης απορρίφθηκε.
oauth.signin.error.temporarily_unavailable=Η εξουσιοδότηση απέτυχε επειδή ο διακομιστής ταυτοποίησης δεν είναι διαθέσιμος προσωρινά. Παρακαλώ προσπαθήστε ξανά αργότερα.
openid_connect_submit=Σύνδεση
openid_connect_title=Σύνδεση σε υπάρχων λογαριασμό
openid_connect_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ.
@@ -510,6 +527,7 @@ twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
account_link=Συνδεδεμένοι Λογαριασμοί
organization=Οργανισμοί
uid=Uid
webauthn=Κλειδιά Ασφαλείας
public_profile=Δημόσιο Προφίλ
biography_placeholder=Πείτε μας λίγο για τον εαυτό σας
@@ -531,6 +549,7 @@ continue=Συνέχεια
cancel=Ακύρωση
language=Γλώσσα
ui=Θέμα Διεπαφής
saved_successfully=Οι ρυθμίσεις σας αποθηκεύτηκαν επιτυχώς.
privacy=Απόρρητο
keep_activity_private=Απόκρυψη της δραστηριότητας σας από τη σελίδα προφίλ
keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές
@@ -730,6 +749,11 @@ passcode_invalid=Ο κωδικός είναι λάθος. Δοκιμάστε ξ
twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά!
twofa_failed_get_secret=Αποτυχία λήψης μυστικού.
webauthn_desc=Τα κλειδιά ασφαλείας είναι συσκευές που περιέχουν κρυπτογραφικά κλειδιά. Μπορούν να χρησιμοποιηθούν για έλεγχο ταυτότητας δύο παραγόντων. Τα κλειδιά ασφαλείας πρέπει να υποστηρίζουν το <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">πρότυπο WebAuthn Authn Authenticator</a>.
webauthn_register_key=Προσθήκη Κλειδιού Ασφαλείας
webauthn_nickname=Ψευδώνυμο
webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας
webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια;
manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών
manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Gitea λογαριασμό σας.
@@ -986,7 +1010,16 @@ file_view_rendered=Προβολή Απόδοσης
file_view_raw=Προβολή Ακατέργαστου
file_permalink=Permalink
file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί.
bidi_bad_header=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Unicode!`
bidi_bad_description=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Bidirectional Unicode που ίσως να επεξεργάζονται διαφορετικά από ότι εμφανίζεται παρακάτω. Αν η χρήση αυτή είναι σκόπιμη και νόμιμη, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να αποκαλύψετε κρυμμένους χαρακτήρες.`
bidi_bad_description_escaped=`Αυτό το αρχείο περιέχει μη αναμενόμενους χαρακτήρες Bidirectional Unicode. Οι κρυμμένοι χαρακτήρες unicode εμφανίζονται κωδικοποιημένοι παρακάτω. Χρησιμοποιήστε το κουμπί Unescape για να δείτε πώς αποδίδονται.`
unicode_header=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode!`
unicode_description=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode που μπορεί να επεξεργάζονται διαφορετικά από όπως εμφανίζονται παρακάτω. Αν η χρήση είναι σκόπιμη και νόμιμη, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να αποκαλύψετε τους κρυφούς χαρακτήρες.`
unicode_description_escaped=`Αυτό το αρχείο περιέχει κρυφούς χαρακτήρες Unicode. Οι κρυφοί χαρακτήρες unicode εμφανίζονται κωδικοποιημένοι παρακάτω. Χρησιμοποιήστε το κουμπί Unescape για να δείτε πώς αποδίδονται.`
line_unicode=`Αυτή η γραμμή έχει κρυφούς χαρακτήρες unicode`
escape_control_characters=Escape
unescape_control_characters=Unescape
file_copy_permalink=Αντιγραφή Permalink
video_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'video'.
audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'.
@@ -1081,6 +1114,8 @@ commits.signed_by_untrusted_user_unmatched=Υπογράφηκε από ένα μ
commits.gpg_key_id=ID Κλειδιού GPG
commits.ssh_key_fingerprint=Αποτύπωμα Κλειδιού SSH
ext_issues=Πρόσβαση στα Εξωτερικά Ζητήματα
ext_issues.desc=Σύνδεση σε εξωτερικό εφαρμογή ζητημάτων.
projects=Έργα
@@ -1560,6 +1595,7 @@ signing.wont_sign.commitssigned=Η συγχώνευση δεν θα υπογρα
signing.wont_sign.approved=Η συγχώνευση δεν θα υπογραφεί καθώς το PR δεν εγκρίνεται
signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι
ext_wiki=Πρόσβαση στο Εξωτερικό Wiki
ext_wiki.desc=Σύνδεση σε ένα εξωτερικό wiki.
wiki=Wiki
@@ -1816,6 +1852,8 @@ settings.webhook.response=Απάντηση
settings.webhook.headers=Κεφαλίδες
settings.webhook.payload=Περιεχόμενο
settings.webhook.body=Σώμα
settings.webhook.replay.description=Επανάληψη αυτού του webhook.
settings.webhook.delivery.success=Ένα γεγονός έχει προστεθεί στην ουρά παράδοσης. Μπορεί να χρειαστούν λίγα δευτερόλεπτα μέχρι να εμφανιστεί στο ιστορικό.
settings.githooks_desc=Τα Άγκιστρα Git παρέχονται από το ίδιο το Git. Μπορείτε να επεξεργαστείτε τα αρχεία αγκίστρων παρακάτω για να ρυθμίσετε προσαρμοσμένες λειτουργίες.
settings.githook_edit_desc=Αν το hook είναι ανενεργό, θα παρουσιαστεί ένα παράδειγμα. Αφήνοντας το περιεχόμενο του hook κενό θα το απενεργοποιήσετε.
settings.githook_name=Όνομα Hook
@@ -2079,6 +2117,7 @@ diff.protected=Προστατευμένο
diff.image.side_by_side=Δίπλα Δίπλα
diff.image.swipe=Σύρσιμο
diff.image.overlay=Επικάλυψη
diff.has_escaped=Αυτή η γραμμή έχει κρυφούς χαρακτήρες Unicode
releases.desc=Παρακολούθηση εκδόσεων έργου και λήψεων.
release.releases=Κυκλοφορίες
@@ -2154,6 +2193,7 @@ branch.new_branch_from=Δημιουργία νέου κλάδου από '%s'
branch.renamed=Ο κλάδος %s μετονομάστηκε σε %s.
tag.create_tag=Δημιουργία ετικέτας <strong>%s</strong>
tag.create_success=Η ετικέτα '%s' έχει δημιουργηθεί.
topic.manage_topics=Διαχείριση Θεμάτων
@@ -2240,7 +2280,13 @@ teams.leave=Αποχώρηση
teams.leave.detail=Αποχώρηση από %s;
teams.can_create_org_repo=Δημιουργία αποθετηρίων
teams.can_create_org_repo_helper=Τα μέλη μπορούν να δημιουργήσουν νέα αποθετήρια στον οργανισμό. Ο δημιουργός θα αποκτήσει πρόσβαση διαχειριστή στο νέο αποθετήριο.
teams.none_access=Καμία Πρόσβαση
teams.none_access_helper=Τα μέλη δεν μπορούν να δουν ή να κάνουν οποιαδήποτε άλλη ενέργεια σε αυτή τη μονάδα.
teams.general_access=Γενική Πρόσβαση
teams.general_access_helper=Τα δικαιώματα των μελών αποφασίζονται από το παρακάτω πίνακα αδειών.
teams.read_access=Ανάγνωση
teams.read_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
teams.write_access=Εγγραφή
teams.write_access_helper=Τα μέλη μπορούν να δουν και να κλωνοποιήσουν τα αποθετήρια της ομάδας.
teams.admin_access=Πρόσβαση Διαχειριστή
teams.admin_access_helper=Τα μέλη μπορούν να κάνουν push και pull στα αποθετήρια της ομάδας όπως και να προσθέσουν συνεργάτες σε αυτά.
@@ -2869,6 +2915,7 @@ error.probable_bad_signature=ΠΡΟΣΟΧΗ! Αν και υπάρχει ένα
error.probable_bad_default_signature=ΠΡΟΣΟΧΗ! Αν και το προεπιλεγμένο κλειδί έχει αυτό το ID, δεν επαληθεύει αυτή την υποβολή! Αυτή η υποβολή είναι ΥΠΟΠΤΗ.
[units]
unit=Μονάδα
error.no_unit_allowed_repo=Δεν σας επιτρέπεται να έχετε πρόσβαση σε οποιαδήποτε ενότητα αυτού του αποθετηρίου.
error.unit_not_allowed=Δεν σας επιτρέπεται να έχετε πρόσβαση σε αυτήν την ενότητα αποθετηρίου.

View File

@@ -2334,6 +2334,7 @@ first_page = First
last_page = Last
total = Total: %d
dashboard.new_version_hint = Gitea %s is now available, you are running %s. Check the <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> for more details.
dashboard.statistic = Summary
dashboard.operations = Maintenance Operations
dashboard.system_status = System Status
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause = Last GC Pause
dashboard.gc_times = GC Times
dashboard.delete_old_actions = Delete all old actions from database
dashboard.delete_old_actions.started = Delete all old actions from database started.
dashboard.update_checker = Update checker
users.user_manage_panel = User Account Management
users.new_account = Create User Account

View File

@@ -34,6 +34,20 @@ twofa=2要素認証
twofa_scratch=2要素認証スクラッチコード
passcode=パスコード
webauthn_insert_key=セキュリティキーを挿入
webauthn_sign_in=セキュリティキーのボタンを押してください。セキュリティキーにボタンが無い場合は、挿入しなおしてください。
webauthn_press_button=セキュリティキーのボタンを押してください...
webauthn_use_twofa=携帯電話から2要素認証コードを使用する
webauthn_error=セキュリティキーを読み取ることができません。
webauthn_unsupported_browser=お使いのブラウザは現在 WebAuthn をサポートしていません。
webauthn_error_unknown=不明なエラーが発生しました。 もう一度やり直してください。
webauthn_error_insecure=WebAuthn はセキュアな接続のみをサポートしています。HTTP 経由でテストする場合は、"localhost" または "127.0.0.1" のオリジンが使用できます。
webauthn_error_unable_to_process=サーバーがリクエストを処理できませんでした。
webauthn_error_duplicated=このリクエストに対しては、許可されていないセキュリティキーです。 キーが未登録であることを確認してください。
webauthn_error_empty=このキーに名前を設定する必要があります。
webauthn_error_timeout=キーを読み取る前にタイムアウトになりました。 このページをリロードしてもう一度やり直してください。
webauthn_u2f_deprecated=キー: '%s' は非推奨のU2Fプロセスを使用して認証しています。このキーを再登録して古い登録を削除したほうが良いでしょう。
webauthn_reload=リロード
repository=リポジトリ
organization=組織
@@ -513,6 +527,7 @@ twofa=2要素認証
account_link=連携アカウント
organization=組織
uid=Uid
webauthn=セキュリティキー
public_profile=公開プロフィール
biography_placeholder=自己紹介を少しだけ
@@ -534,6 +549,8 @@ continue=続行
cancel=キャンセル
language=言語
ui=テーマ
hidden_comment_types=非表示にするコメントの種類
saved_successfully=設定は正常に保存されました。
privacy=プライバシー
keep_activity_private=プロフィールページのアクティビティ表示を隠す
keep_activity_private_popup=アクティビティを、あなたと管理者にのみ表示します
@@ -733,6 +750,11 @@ passcode_invalid=パスコードが間違っています。 再度お試しく
twofa_enrolled=あなたのアカウントに2要素認証が設定されました。 スクラッチトークン (%s) は一度しか表示しませんので安全な場所に保存してください!
twofa_failed_get_secret=シークレットが取得できません。
webauthn_desc=セキュリティキーは暗号化キーを内蔵するハードウェア ・ デバイスです。 2要素認証に使用できます。 セキュリティキーは<a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a>規格をサポートしている必要があります。
webauthn_register_key=セキュリティキーを追加
webauthn_nickname=ニックネーム
webauthn_delete_key=セキュリティキーの登録解除
webauthn_delete_key_desc=セキュリティキーの登録を解除すると、今後そのセキュリティキーでサインインすることはできなくなります。 続行しますか?
manage_account_links=連携アカウントの管理
manage_account_links_desc=これらの外部アカウントがGiteaアカウントと連携されています。
@@ -1038,6 +1060,10 @@ editor.add_tmpl='<ファイル名>' を追加
editor.add='%s' を追加
editor.update='%s' を更新
editor.delete='%s' を削除
editor.patch=パッチの適用
editor.patching=パッチ:
editor.fail_to_apply_patch=パッチを適用できません '%s'
editor.new_patch=新しいパッチ
editor.commit_message_desc=詳細な説明を追加…
editor.signoff_desc=コミットログメッセージの最後にコミッターの Signed-off-by 行を追加
editor.commit_directly_to_this_branch=ブランチ<strong class="branch-name">%s</strong>へ直接コミットする。
@@ -1073,6 +1099,8 @@ editor.cannot_commit_to_protected_branch=保護されたブランチ '%s' にコ
editor.no_commit_to_branch=ブランチに直接コミットすることはできません、なぜなら:
editor.user_no_push_to_branch=ユーザーはブランチにプッシュできません
editor.require_signed_commit=ブランチでは署名されたコミットが必須です
editor.cherry_pick=チェリーピック %s:
editor.revert=リバート %s:
commits.desc=ソースコードの変更履歴を参照します。
commits.commits=コミット
@@ -1093,6 +1121,7 @@ commits.signed_by_untrusted_user_unmatched=コミッターと一致しない信
commits.gpg_key_id=GPGキーID
commits.ssh_key_fingerprint=SSH鍵のフィンガープリント
ext_issues=外部イシューへのアクセス
ext_issues.desc=外部のイシュートラッカーへのリンク。
@@ -2314,6 +2343,7 @@ first_page=最初
last_page=最後
total=合計: %d
dashboard.new_version_hint=Gitea %s が入手可能になりました。 現在実行しているのは %s です。 詳細は <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">ブログ</a> を確認してください。
dashboard.statistic=サマリー
dashboard.operations=メンテナンス操作
dashboard.system_status=システム状況
@@ -2387,6 +2417,7 @@ dashboard.last_gc_pause=前回のGC停止時間
dashboard.gc_times=GC実行回数
dashboard.delete_old_actions=データベースから古い操作履歴をすべて削除
dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。
dashboard.update_checker=更新チェック
users.user_manage_panel=ユーザーアカウント管理
users.new_account=ユーザーアカウントを作成

View File

@@ -837,7 +837,7 @@ default_branch=Ramo principal
default_branch_helper=O ramo principal é o ramo base para pedidos de integração e cometimentos.
mirror_prune=Podar
mirror_prune_desc=Remover referências obsoletas de seguimento remoto
mirror_interval=Intervalo de espelhamento (as unidade de tempo válidas são 'h', 'm' e 's'). O valor zero desabilita a sincronização automática.
mirror_interval=Intervalo de espelhamento (as unidades de tempo válidas são 'h', 'm' e 's'). O valor zero desabilita a sincronização automática.
mirror_interval_invalid=O intervalo do espelhamento não é válido.
mirror_address=Clonar a partir do URL
mirror_address_desc=Coloque, na secção de Autorização, as credenciais que, eventualmente, sejam necessárias.
@@ -2334,6 +2334,7 @@ first_page=Primeira
last_page=Última
total=total: %d
dashboard.new_version_hint=O Gitea %s está agora disponível, você está a correr a versão %s. Verifique o <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> para mais detalhes.
dashboard.statistic=Resumo
dashboard.operations=Operações de manutenção
dashboard.system_status=Estado do sistema
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause=Última pausa da recolha de lixo
dashboard.gc_times=Tempos da recolha de lixo
dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados
dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados.
dashboard.update_checker=Verificador de novas versões
users.user_manage_panel=Gestão das contas de utilizadores
users.new_account=Criar conta de utilizador

View File

@@ -781,7 +781,7 @@ template_helper=Сделать репозиторий шаблоном
template_description=Шаблонные репозитории дают возможность пользователям создавать новые репозитории с той же структурой каталогов, файлами и дополнительными настройками.
visibility=Видимость
visibility_description=Только владелец или члены организации, при наличии прав, смогут увидеть это.
visibility_helper=Сделать репозиторий приватным
visibility_helper=Сделать репозиторий частным
visibility_helper_forced=Администратор сайта настроил параметр видимости новых репозиториев. Репозиторий приватный по умолчанию.
visibility_fork_helper=(Изменение этого повлияет на все форки.)
clone_helper=Нужна помощь в клонировании? Посетите страницу <a target="_blank" rel="noopener noreferrer" href="%s">помощи</a>.

View File

@@ -34,6 +34,7 @@ twofa=Двофакторна авторизація
twofa_scratch=Двофакторний одноразовий пароль
passcode=Код доступу
webauthn_reload=Оновити
repository=Репозиторій
organization=Організація
@@ -61,7 +62,7 @@ forks=Форки
activities=Дії
pull_requests=Запити на злиття
issues=Проблеми
issues=Задачі
milestones=Етапи
ok=OK
@@ -92,7 +93,9 @@ error404=Сторінка, до якої ви намагаєтеся зверн
never=Ніколи
[error]
occurred=Сталася помилка
missing_csrf=Некоректний запит: токен CSRF не задано
network_error=Помилка мережі
[startpage]
app_desc=Зручний власний сервіс хостингу репозиторіїв Git
@@ -346,7 +349,7 @@ reset_password.text=Перейдіть за цим посиланням, щоб
register_success=Реєстрація успішна
issue_assigned.pull=@%[1]s призначив вам запит злиття %[2]s в репозиторії %[3]s.
issue_assigned.issue=@%[1]s призначив вам завдання %[2]s у репозиторії %[3]s.
issue_assigned.issue=@%[1]s призначив вам задачу %[2]s у репозиторії %[3]s.
issue.x_mentioned_you=<b>@%s</b> згадав вас:
issue.action.force_push=<b>%[1]s</b> force-pushed <b>%[2]s</b> з %[3]s в %[4]s.
@@ -474,7 +477,7 @@ activity=Публічна активність
followers=Читачі
starred=Обрані Репозиторії
watched=Відстежувані репозиторії
projects=Проекти
projects=Проєкт
following=Читає
follow=Підписатися
unfollow=Відписатися
@@ -611,6 +614,7 @@ gpg_token_help=Ви можете створити підпис за допомо
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Текстовий (armored) підпис GPG
key_signature_gpg_placeholder=Починається з "-----BEGIN PGP SIGNATURE-----"
ssh_token=Токен
subkeys=Підключі
key_id=ID ключа
key_name=Ім'я ключа
@@ -743,7 +747,7 @@ visibility.private=Приватний
visibility.private_tooltip=Видимий лише членам організації
[repo]
new_repo_helper=Репозиторій містить усі файли проекту, включаючи історію ревізій. Ще десь є? <a href="%s">Мігрувати репозиторій.</a>
new_repo_helper=Репозиторій містить усі файли проєкту, включаючи історію ревізій. Ще десь є? <a href="%s">Мігрувати репозиторій.</a>
owner=Власник
owner_helper=Деякі організації можуть не відображатися у випадаючому списку через максимальну кількість репозиторііїв.
repo_name=Назва репозиторію
@@ -774,14 +778,14 @@ repo_desc_helper=Введіть короткий опис (опціональн
repo_lang=Мова
repo_gitignore_helper=Виберіть шаблон .gitignore.
repo_gitignore_helper_desc=Оберіть з списку мовних шаблонів файли, які не будуть відстежуватись. Типові артефакти, які генеруються за допомогою інструментів побудови кожної мови, за замовчуванням включені до .gitignor.
issue_labels=Мітки проблем
issue_labels_helper=Вибрати мітку для проблеми.
issue_labels=Мітки задачі
issue_labels_helper=Вибрати мітку для задачі.
license=Ліцензія
license_helper=Виберіть ліцензійний файл.
license_helper_desc=Ліцензія регулює те, що інші можуть і не можуть робити з вашим кодом. Не впевнені, що саме підходить для вашого проекту? Дивіться <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a>
license_helper_desc=Ліцензія регулює те, що інші можуть і не можуть робити з вашим кодом. Не впевнені, що саме підходить для вашого проєкту? Дивіться <a target="_blank" rel="noopener noreferrer" href="%s">Виберіть ліцензію.</a>
readme=README
readme_helper=Виберіть шаблон README.
readme_helper_desc=Це місце, де ви можете написати повний опис вашого проекту.
readme_helper_desc=Це місце, де ви можете написати повний опис вашого проєкту.
auto_init=Ініціалізувати репозиторій (Додає .gitignore, LICENSE та README)
trust_model_helper=Виберіть модель довіри для підтвердження підпису. Можливі варіанти:
trust_model_helper_collaborator=Співавтор: підписи довіри від співавторів
@@ -846,12 +850,12 @@ template.git_hooks=Перехоплювачі Git
template.webhooks=Webhook'и
template.topics=Теми
template.avatar=Аватар
template.issue_labels=Мітки проблем
template.issue_labels=Мітки задачі
template.one_item=Слід обрати хоча б один елемент шаблону
template.invalid=Слід обрати шаблонний репозиторій
archive.title=Це архівний репозитарій. Ви можете переглядати і клонувати файли, але не можете робити пуш або відкривати питання/запити.
archive.issue.nocomment=Це архівний репозитарій. Ви не можете коментувати запити.
archive.title=Цей репозиторій архівовано. Ви можете переглядати файли та клонувати його, але не можете виконувати push чи відкривати задачі та запити злиття.
archive.issue.nocomment=Цей репозиторій архівовано. Ви не можете коментувати задачі.
archive.pull.nocomment=Це архівний репозитарій. Ви не можете коментувати пулл-реквести.
form.reach_limit_of_creation_1=Ви вже досягли ліміту в %d репозиторіїв.
@@ -873,7 +877,7 @@ migrate_items=Деталі міграції
migrate_items_wiki=Вікі
migrate_items_milestones=Етапи
migrate_items_labels=Мітки
migrate_items_issues=Проблеми
migrate_items_issues=Задачі
migrate_items_pullrequests=Запити на злиття
migrate_items_merge_requests=Запити на злиття
migrate_items_releases=Релізи
@@ -906,7 +910,7 @@ migrate.migrating_topics=Міграція тем
migrate.migrating_milestones=Міграція етапів
migrate.migrating_labels=Міграція міток
migrate.migrating_releases=Міграція релізів
migrate.migrating_issues=Міграція проблем
migrate.migrating_issues=Міграція задач
migrate.migrating_pulls=Міграція запитів на злиття
mirror_from=дзеркало
@@ -939,7 +943,7 @@ filter_branch_and_tag=Фільтрувати гілку або тег
find_tag=Знайти тег
branches=Гілки
tags=Теги
issues=Проблеми
issues=Задачі
pulls=Запити на злиття
project_board=Проєкти
labels=Мітки
@@ -1053,10 +1057,12 @@ commits.signed_by_untrusted_user=Підписаний недовіреним к
commits.signed_by_untrusted_user_unmatched=Підписаний недовіреним користувачем, який не відповідає комітеру
commits.gpg_key_id=Ідентифікатор GPG ключа
ext_issues.desc=Посилання на зовнішню систему відстеження проблем.
ext_issues=Доступ до зовнішніх задач
ext_issues.desc=Посилання на зовнішню систему відстеження задач.
projects=Проєкти
projects.desc=Керуйте проблемами та запитами злиття на дошках проєкту.
projects.desc=Керуйте задачами та запитами злиття на дошках проєкту.
projects.description=Опис (необов'язково)
projects.description_placeholder=Опис
projects.create=Створити проєкт
@@ -1065,10 +1071,10 @@ projects.new=Новий проєкт
projects.new_subheader=Координуйте, відстежуйте та оновлюйте інформацію про виконувану роботу в одному місці, аби проєкти залишалися прозорими та за розкладом.
projects.create_success=Проєкт '%s' створено.
projects.deletion=Видалити проєкт
projects.deletion_desc=Видалення проєкту видаляє його з усіх пов'язаних проблем. Продовжити?
projects.deletion_desc=Видалення проєкту видаляє його з усіх пов'язаних задач. Продовжити?
projects.deletion_success=Проєкт видалено.
projects.edit=Редагувати проєкти
projects.edit_subheader=Проєкти призначені для організації проблем та відстеження поступу.
projects.edit_subheader=Проєкти організовують задачі та відстежують прогрес.
projects.modify=Оновити проєкт
projects.edit_success=Проєкт '%s' оновлено.
projects.type.none=Відсутній
@@ -1083,9 +1089,9 @@ projects.board.new_title=Назва нової дошки
projects.board.new_submit=Створити
projects.board.new=Нова дошка
projects.board.set_default=Встановити за замовчуванням
projects.board.set_default_desc=Встановити цю дошку за замовчуванням для проблем без категорії та витягувань
projects.board.set_default_desc=Встановити цю дошку за замовчуванням для задач без категорії та витягувань
projects.board.delete=Видалити дошку
projects.board.deletion_desc=Видалення дошки проєкту перенесе всі пов'язані проблеми в дошку 'Без категорії'. Продовжити?
projects.board.deletion_desc=Видалення дошки проєкту перенесе всі пов'язані задачі в дошку 'Без категорії'. Продовжити?
projects.board.color=Колір
projects.open=Відкрити
projects.close=Закрити
@@ -1096,7 +1102,7 @@ issues.filter_milestones=Фільтр етапів
issues.filter_projects=Фільтр проєктів
issues.filter_labels=Фільтр міток
issues.filter_reviewers=Фільтр рецензентів
issues.new=Нова проблема
issues.new=Нова задача
issues.new.title_empty=Заголовок не може бути пустим
issues.new.labels=Мітки
issues.new.add_labels_title=Застосувати мітки
@@ -1125,7 +1131,7 @@ issues.choose.get_started=Початок роботи
issues.choose.blank=Типово
issues.choose.blank_about=Створити задачу із шаблону за замовчуванням.
issues.no_ref=Не вказана гілка або тег
issues.create=Створити проблему
issues.create=Створити задачу
issues.new_label=Нова мітка
issues.new_label_placeholder=Назва мітки
issues.new_label_desc_placeholder=Опис
@@ -1167,7 +1173,7 @@ issues.filter_milestone_no_select=Всі етапи
issues.filter_assignee=Виконавець
issues.filter_assginee_no_select=Всі виконавці
issues.filter_type=Тип
issues.filter_type.all_issues=Всі проблеми
issues.filter_type.all_issues=Всі задачі
issues.filter_type.assigned_to_you=Призначене вам
issues.filter_type.created_by_you=Створено вами
issues.filter_type.mentioning_you=Вас згадано
@@ -1203,7 +1209,7 @@ issues.commented_at=`прокоментував(ла) <a href="#%s">%s</a>`
issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар?
issues.context.copy_link=Скопіювати посилання
issues.context.quote_reply=Цитувати відповідь
issues.context.reference_issue=Посилання в новій проблемі
issues.context.reference_issue=Посилання в новій задачі
issues.context.edit=Редагувати
issues.context.delete=Видалити
issues.no_content=Тут ще немає жодного змісту.
@@ -1214,15 +1220,15 @@ issues.close_comment_issue=Прокоментувати і закрити
issues.reopen_issue=Відкрити знову
issues.reopen_comment_issue=Прокоментувати та відкрити знову
issues.create_comment=Коментар
issues.closed_at=`закрив цю проблему <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`повторно відкрив цю проблему <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`згадано цю проблему в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">послався на цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_at=`закрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`повторно відкрив цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`згадано цю задачу в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">посилання на цю задачу %[4]</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">послався на цей запит злиття %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">послався на запит злиття %[4]s, який закриває цю проблему</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">послався на запит злиття %[4]s, який повторно відкриває цю проблему</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">закрив цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">повторно відкрив цю проблему %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closing_from=`<a href="%[3]s">згадав запит на злиття %[4]с, які закриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopening_from=`<a href="%[3]s">згадав запит на злиття %[4]с, які повторно відкриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_closed_from=`<a href="%[3]s">закрив цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_reopened_from=`<a href="%[3]s">повторно відкрито цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_from=`із %[1]s`
issues.poster=Автор
issues.collaborator=Співавтор
@@ -1241,12 +1247,12 @@ issues.label_title=Назва мітки
issues.label_description=Опис мітки
issues.label_color=Колір мітки
issues.label_count=%d міток
issues.label_open_issues=%d відкритих проблем
issues.label_open_issues=%d відкритих задач
issues.label_edit=Редагувати
issues.label_delete=Видалити
issues.label_modify=Редагувати мітку
issues.label_deletion=Видалити мітку
issues.label_deletion_desc=Видалення мітки видаляє її з усіх обговорень. Продовжити?
issues.label_deletion_desc=Видалення мітки видаляє її з усіх задач. Продовжити?
issues.label_deletion_success=Мітку було видалено.
issues.label.filter_sort.alphabetically=За алфавітом
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
@@ -1259,29 +1265,29 @@ issues.subscribe=Підписатися
issues.unsubscribe=Відписатися
issues.lock=Блокування обговорення
issues.unlock=Розблокування обговорення
issues.lock.unknown_reason=Неможливо заблокувати проблему з невідомою причиною.
issues.lock_duplicate=Проблема не може бути заблокованим двічі.
issues.unlock_error=Не можливо розблокувати проблему, яка не заблокована.
issues.lock.unknown_reason=Неможливо заблокувати задачу з невідомою причиною.
issues.lock_duplicate=Задача не може бути заблокованим двічі.
issues.unlock_error=Не можливо розблокувати задачу, яка не заблокована.
issues.lock_with_reason=заблоковано як <strong>%s</strong> та обмежене обговорення для співавторів %s
issues.lock_no_reason=заблоковано та обмежене обговорення для співавторів %s
issues.unlock_comment=розблоковане обговорення %s
issues.lock_confirm=Заблокувати
issues.unlock_confirm=Розблокувати
issues.lock.notice_1=- Інші користувачі не можуть додавати нові коментарі до цієї проблеми.
issues.lock.notice_1=- Інші користувачі не можуть додавати нові коментарі до цієї задачі.
issues.lock.notice_2=- Ви й інші співавтори, які мають доступ до цього репозиторію, можете залишати коментарі, які інші можуть бачити.
issues.lock.notice_3=- Ви завжди зможете розблокувати цю проблему в майбутньому.
issues.unlock.notice_1=- Кожен зможе прокоментувати це питання ще раз.
issues.unlock.notice_2=- Ви завжди зможете заблокувати цю проблему в майбутньому.
issues.lock.notice_3=- Ви завжди зможете розблокувати цю задачу в майбутньому.
issues.unlock.notice_1=- Кожен зможе прокоментувати цю задачу ще раз.
issues.unlock.notice_2=- Ви завжди зможете заблокувати цю задачу в майбутньому.
issues.lock.reason=Причина блокування
issues.lock.title=Заблокувати обговорення цієї проблеми.
issues.unlock.title=Розблокувати обговорення цієї проблеми.
issues.comment_on_locked=Ви не можете коментувати заблоковану проблему.
issues.lock.title=Заблокувати обговорення цієї задачі.
issues.unlock.title=Розблокувати обговорення цієї задачі.
issues.comment_on_locked=Ви не можете коментувати заблоковану задачу.
issues.tracker=Відстеження часу
issues.start_tracking_short=Запустити таймер
issues.start_tracking=Почати відстеження часу
issues.start_tracking_history=`почав працювати %s`
issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця проблема буде закрита
issues.tracking_already_started=`Ви вже почали відстежувати час для <a href="%s">іншої проблеми</a>!`
issues.tracker_auto_close=Таймер буде автоматично зупинено, коли ця задача буде закрита
issues.tracking_already_started=`Ви вже почали відстежувати час для <a href="%s">іншої задачі</a>!`
issues.stop_tracking=Зупинити таймер
issues.stop_tracking_history=`перестав(-ла) працювати %s`
issues.cancel_tracking=Скасувати
@@ -1308,7 +1314,7 @@ issues.due_date_form=рррр-мм-дд
issues.due_date_form_add=Додати дату завершення
issues.due_date_form_edit=Редагувати
issues.due_date_form_remove=Видалити
issues.due_date_not_writer=Вам потрібен доступ до запису в репозиторії, щоб оновити дату завершення проблем.
issues.due_date_not_writer=Вам потрібен доступ до запису в репозиторії, щоб оновити дату завершення задач.
issues.due_date_not_set=Термін виконання не встановлений.
issues.due_date_added=додав(ла) дату завершення %s %s
issues.due_date_modified=термін змінено з %s %s на %s
@@ -1316,7 +1322,7 @@ issues.due_date_remove=видалив(ла) дату завершення %s %s
issues.due_date_overdue=Прострочено
issues.due_date_invalid=Термін дії не дійсний або знаходиться за межами допустимого діапазону. Будь ласка використовуйте формат 'yyyy-mm-dd'.
issues.dependency.title=Залежності
issues.dependency.issue_no_dependencies=Ця проблема в даний час не має залежностей.
issues.dependency.issue_no_dependencies=Ця задача тепер не має залежностей.
issues.dependency.pr_no_dependencies=Цей запит на злиття в даний час не має залежностей.
issues.dependency.add=Додати залежність…
issues.dependency.cancel=Відмінити
@@ -1324,24 +1330,24 @@ issues.dependency.remove=Видалити
issues.dependency.remove_info=Видалити цю залежність
issues.dependency.added_dependency=`додав нову залежність %s`
issues.dependency.removed_dependency=`видалив залежність %s`
issues.dependency.pr_closing_blockedby=Закриття цього запиту злиття заблоковано наступними проблемами
issues.dependency.issue_closing_blockedby=Закриття цієї проблеми заблоковано наступними проблемами
issues.dependency.issue_close_blocks=Ця проблема блокує закриття залежних проблем
issues.dependency.pr_close_blocks=Цей пулл-реквест блокує закриття залежних проблем
issues.dependency.issue_close_blocked=Вам потрібно закрити всі проблеми, що блокують цю проблему, перед її закриттям.
issues.dependency.pr_close_blocked=Вам потрібно закрити всі проблеми, що блокують цей пулл-реквест, перед його злиттям.
issues.dependency.pr_closing_blockedby=Закриття цього запиту злиття заблоковано наступними задачами
issues.dependency.issue_closing_blockedby=Закриття цієї задачи заблоковано наступними задачами
issues.dependency.issue_close_blocks=Ця задача блокує закриття залежних задач
issues.dependency.pr_close_blocks=Цей запит на злиття блокує закриття залежних задач
issues.dependency.issue_close_blocked=Вам потрібно закрити всі задачі, що блокують цю задачу, перед її закриттям.
issues.dependency.pr_close_blocked=Вам потрібно закрити всі задачі, що блокують цей запит, перед його злиттям.
issues.dependency.blocks_short=Блоки
issues.dependency.blocked_by_short=Залежить від
issues.dependency.remove_header=Видалити залежність
issues.dependency.issue_remove_text=Це призведе до видалення залежності з цієї проблеми. Продовжити?
issues.dependency.issue_remove_text=Це призведе до видалення залежності з цієї задачі. Продовжити?
issues.dependency.pr_remove_text=Це призведе до видалення залежності з цього пулл-реквесту. Продовжити?
issues.dependency.setting=Увімкнути залежності для проблем та пулл-реквестів
issues.dependency.add_error_same_issue=Ви не можете зробити проблему залежною від себе.
issues.dependency.add_error_dep_issue_not_exist=Залежність для проблеми не існує.
issues.dependency.setting=Увімкнути залежності для задач та запитів на злиття
issues.dependency.add_error_same_issue=Ви не можете зробити задачу залежною від себе.
issues.dependency.add_error_dep_issue_not_exist=Залежність для задачі не існує.
issues.dependency.add_error_dep_not_exist=Залежність не існує.
issues.dependency.add_error_dep_exists=Залежність уже існує.
issues.dependency.add_error_cannot_create_circular=Ви не можете створити залежність з двома проблемами, які блокують одна одну.
issues.dependency.add_error_dep_not_same_repo=Обидві проблеми повинні бути в одному репозиторії.
issues.dependency.add_error_cannot_create_circular=Ви не можете створити залежність з двома задачами, які блокують одна одну.
issues.dependency.add_error_dep_not_same_repo=Обидві задачі повинні бути в одному репозиторії.
issues.review.self.approval=Ви не можете схвалити власний пулл-реквест.
issues.review.self.rejection=Ви не можете надіслати запит на зміну на власний пулл-реквест.
issues.review.approve=зміни затверджено %s
@@ -1475,7 +1481,7 @@ pulls.closed_at=`закрив цей запит на злиття <a id="%[1]s"
pulls.reopened_at=`повторно відкрив цей запит на злиття <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.merge_instruction_hint=`Також можна переглянути <a class="show-instruction">інструкції для командного рядка</a>.`
pulls.merge_instruction_step1_desc=У репозиторії вашого проекту перевірте нову гілку і протестуйте зміни.
pulls.merge_instruction_step1_desc=У репозиторії вашого проєкту перевірте нову гілку і протестуйте зміни.
pulls.merge_instruction_step2_desc=Об'єднати зміни і оновити на Gitea.
milestones.new=Новий етап
@@ -1486,7 +1492,7 @@ milestones.update_ago=Оновлено %s назад
milestones.no_due_date=Немає дати завершення
milestones.open=Відкрити
milestones.close=Закрити
milestones.new_subheader=Створюйте етапи для організації ваших завдань.
milestones.new_subheader=Створюйте етапи для організації ваших задач.
milestones.completeness=%d%% завершено
milestones.create=Створити етап
milestones.title=Заголовок
@@ -1496,19 +1502,19 @@ milestones.clear=Очистити
milestones.invalid_due_date_format=Дата завершення має бути в форматі 'рррр-мм-дд'.
milestones.create_success=Етап '%s' створений.
milestones.edit=Редагувати етап
milestones.edit_subheader=Використовуйте кращий опис контрольної точки, щоб уникнути нерозуміння з боку інших людей.
milestones.edit_subheader=Створюйте етапи для організації ваших задач.
milestones.cancel=Відмінити
milestones.modify=Оновити етап
milestones.edit_success=Етап '%s' був оновлений.
milestones.deletion=Видалити етап
milestones.deletion_desc=Видалення етапу призведе до його видалення з усіх пов'язаних завдань. Продовжити?
milestones.deletion_desc=Видалення етапу призведе до його видалення з усіх пов'язаних задач. Продовжити?
milestones.deletion_success=Етап успішно видалено.
milestones.filter_sort.closest_due_date=Найближче за датою
milestones.filter_sort.furthest_due_date=Далі за датою
milestones.filter_sort.least_complete=Менш повне
milestones.filter_sort.most_complete=Більш повне
milestones.filter_sort.most_issues=Найбільш проблем
milestones.filter_sort.least_issues=Найменш проблем
milestones.filter_sort.most_issues=Найбільш задач
milestones.filter_sort.least_issues=Найменш задач
signing.will_sign=Цей коміт буде підписано ключем '%s'
signing.wont_sign.error=Під час підписання коміту, сталася помилка
@@ -1574,21 +1580,21 @@ activity.title.prs_merged_by=%s злито %s
activity.title.prs_opened_by=%s запропоновано %s
activity.merged_prs_label=Злито
activity.opened_prs_label=Запропоновано
activity.active_issues_count_1=<strong>%d</strong> Активна проблема
activity.active_issues_count_n=<strong>%d</strong> Активні проблеми
activity.closed_issues_count_1=Закрита проблема
activity.closed_issues_count_n=Закриті проблеми
activity.title.issues_1=%d Проблема
activity.title.issues_n=%d Проблеми
activity.active_issues_count_1=<strong>%d</strong> Активна задача
activity.active_issues_count_n=<strong>%d</strong> Активні задачі
activity.closed_issues_count_1=Закрита задача
activity.closed_issues_count_n=Закриті задачі
activity.title.issues_1=%d Задач
activity.title.issues_n=%d Задач
activity.title.issues_closed_from=%s закрито %s
activity.title.issues_created_by=%s створена(і) %s
activity.closed_issue_label=Закрито
activity.new_issues_count_1=Нова Проблема
activity.new_issues_count_n=%d Проблем
activity.new_issues_count_1=Нова задача
activity.new_issues_count_n=Нові Задачі
activity.new_issue_label=Відкриті
activity.title.unresolved_conv_1=%d Незавершене обговорення
activity.title.unresolved_conv_n=%d Незавершених обговорень
activity.unresolved_conv_desc=Список всіх старих тікетів і Pull Request'ів з недавньої активністю, але ще не закритих або прийнятих.
activity.unresolved_conv_desc=Список всіх старих задач і Pull Request'ів з недавньої активністю, але ще не закритих або прийнятих.
activity.unresolved_conv_label=Відкрити
activity.title.releases_1=%d Реліз
activity.title.releases_n=%d Релізів
@@ -1635,7 +1641,7 @@ settings.hooks=Веб-хуки
settings.githooks=Git хуки
settings.basic_settings=Базові налаштування
settings.mirror_settings=Налаштування дзеркала
settings.mirror_settings.docs=Налаштуйте свій проект, щоб автоматично відправляти/отримувати зміни з іншого репозиторію. Гілки, теги і коміти будуть синхронізуватися автоматично. <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/repo-mirror/">Як я можу відзеркалити репозиторії?</a>
settings.mirror_settings.docs=Налаштуйте свій проєкт, щоб автоматично відправляти/отримувати зміни з іншого репозиторію. Гілки, теги та коміти будуть синхронізуватися автоматично. <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/repo-mirror/">Як я можу відзеркалити репозиторії?</a>
settings.mirror_settings.mirrored_repository=Віддзеркалений репозиторій
settings.mirror_settings.direction=Напрямок
settings.mirror_settings.direction.pull=Pull
@@ -1660,12 +1666,12 @@ settings.use_external_wiki=Використовувати зовнішні Ві
settings.external_wiki_url=URL зовнішньої вікі
settings.external_wiki_url_error=Зовнішня URL-адреса wiki не є допустимою URL-адресою.
settings.external_wiki_url_desc=Відвідувачі будуть перенаправлені на URL-адресу, коли вони клацають по вкладці.
settings.issues_desc=Увімкнути відстеження проблем в репозиторію
settings.use_internal_issue_tracker=Використовувати вбудовану систему відстеження проблем
settings.use_external_issue_tracker=Використовувати зовнішню систему обліку завдань
settings.external_tracker_url=URL зовнішньої системи відстеження проблем
settings.issues_desc=Увімкнути відстеження задач в репозиторію
settings.use_internal_issue_tracker=Використовувати вбудовану систему відстеження задач
settings.use_external_issue_tracker=Використовувати зовнішню систему обліку задач
settings.external_tracker_url=URL зовнішньої системи відстеження задач
settings.external_tracker_url_error=URL зовнішнього баг-трекера не є допустимою URL-адресою.
settings.external_tracker_url_desc=Відвідувачі перенаправляються на зовнішню URL-адресу, коли натискають вкладку 'Проблеми'.
settings.external_tracker_url_desc=Відвідувачі перенаправляються на зовнішню URL-адресу, коли натискають вкладку 'Задачі'.
settings.tracker_url_format=Формат URL зовнішнього трекера задач
settings.tracker_url_format_error=Неправильний формат URL-адреси зовнішнього баг-трекера.
settings.tracker_issue_style=Формат номеру для зовнішньої системи обліку задач
@@ -1686,7 +1692,7 @@ settings.pulls.default_delete_branch_after_merge=Видаляти гілку з
settings.projects_desc=Увімкнути проєкти у репозиторії
settings.admin_settings=Налаштування адміністратора
settings.admin_enable_health_check=Включити перевірки працездатності репозиторію (git fsck)
settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити проблему за допомогою коміта, зробленого не головній гілці
settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити задачу за допомогою коміта, зробленого не в головній гілці
settings.danger_zone=Небезпечна зона
settings.new_owner_has_same_repo=Новий власник вже має репозиторій з такою назвою. Будь ласка, виберіть інше ім'я.
settings.convert=Перетворити на звичайний репозиторій
@@ -1736,7 +1742,7 @@ settings.wiki_deletion_success=Дані wiki були видалені.
settings.delete=Видалити цей репозиторій
settings.delete_desc=Будьте уважні! Як тільки ви видалите репозиторій - шляху назад не буде.
settings.delete_notices_1=- Цю операцію <strong>НЕ МОЖНА</strong> відмінити.
settings.delete_notices_2=- Ця операція назавжди видалить все з репозиторію <strong>%s</strong>, включаючи дані Git, пов'язані з ним завдання, коментарі і права доступу для співробітників.
settings.delete_notices_2=- Ця операція остаточно видалить <strong>%s</strong> репозиторій, включаючи код, задачі, коментарі, вікі та налаштування співавторів.
settings.delete_notices_fork_1=- Всі форки стануть незалежними репозиторіями після видалення.
settings.deletion_success=Репозиторій успішно видалено.
settings.update_settings_success=Налаштування репозиторію було оновлено.
@@ -1806,16 +1812,16 @@ settings.event_push_desc=Git push до репозиторію.
settings.event_repository=Репозиторій
settings.event_repository_desc=Репозиторій створений або видалено.
settings.event_header_issue=Події задачі
settings.event_issues=Проблеми
settings.event_issues_desc=Проблема відкрита, закрита, повторно відкрита або відредагована.
settings.event_issue_assign=Проблема прив'язана
settings.event_issue_assign_desc=Проблема призначена або скасована.
settings.event_issue_label=Проблема з міткою
settings.event_issue_label_desc=Мітки проблем оновлено або видалено.
settings.event_issue_milestone=Проблеми етапу
settings.event_issue_milestone_desc=Проблема призначена на етап або видалена з етапу.
settings.event_issue_comment=Коментар проблеми
settings.event_issue_comment_desc=Коментар проблеми створено, видалено чи відредаговано.
settings.event_issues=Задачі
settings.event_issues_desc=Задача відкрита, закрита, повторно відкрита або відредагована.
settings.event_issue_assign=Задача прив'язана
settings.event_issue_assign_desc=Задачу призначено або скасовано.
settings.event_issue_label=Задача з міткою
settings.event_issue_label_desc=Мітки задачі оновлено або видалено.
settings.event_issue_milestone=Задача з етапом
settings.event_issue_milestone_desc=Задача призначена на етап або видалена з етапу.
settings.event_issue_comment=Коментар задачі
settings.event_issue_comment_desc=Коментар задачі створено, видалено чи відредаговано.
settings.event_header_pull_request=Події запиту злиття
settings.event_pull_request=Запити до злиття
settings.event_pull_request_desc=Запит до злиття відкрито, закрито, перевідкрито або відредаговано.
@@ -1942,7 +1948,7 @@ settings.matrix.access_token=Токен Доступу
settings.matrix.message_type=Тип повідомлення
settings.archive.button=Архівний репозиторій
settings.archive.header=Відправити репозиторій в архів
settings.archive.text=Архівування репозиторія зробить його доступним лише для читання. Він не відображається на панелі, в нього не можуть вноситись зміни і не можна створювати запити з проблем та пулл-реквести.
settings.archive.text=Архівування репозиторія зробить його доступним лише для читання. Він не відображається на панелі, в нього не можуть вноситись зміни і не можна створювати запити з задач та пулл-реквести.
settings.archive.success=Репозиторію успішно присвоєно статус архівного.
settings.archive.error=Сталася помилка при спробі архівувати репозиторій. Докладнішу інформацію див. у журналі.
settings.archive.error_ismirror=Неможливо архівувати дзеркальний репозиротрій.
@@ -1950,7 +1956,7 @@ settings.archive.branchsettings_unavailable=Параметри гілки не
settings.archive.tagsettings_unavailable=Параметри міток недоступні, якщо репозиторій архівний.
settings.unarchive.button=Зняти архівний статус
settings.unarchive.header=Зняти архівний статус для репозиторія
settings.unarchive.text=Зняття статусу архівного відновить запис в репозиторій, а також відкриє можливість створювати запити з нових проблем та пулл-запити.
settings.unarchive.text=Зняття статусу архівного відновить запис в репозиторій, а також відкриє можливість створювати запити з нових задачах та пулл-запити.
settings.unarchive.success=Статус архівний успішно знято.
settings.unarchive.error=Сталася помилка при спробі скасувати архівний статус репозиторія. Докладнішу інформацію див. у журналі.
settings.update_avatar_success=Аватар репозиторію оновлений.
@@ -2037,7 +2043,7 @@ diff.image.side_by_side=Пліч-о-пліч
diff.image.swipe=Свайп
diff.image.overlay=Оверлей
releases.desc=Відслідковувати версії проекту (релізи) та завантаження.
releases.desc=Відслідковувати версії проєкту і завантаження.
release.releases=Релізи
release.detail=Деталі релізу
release.tags=Теги
@@ -2050,8 +2056,8 @@ release.edit=редагувати
release.ahead.commits=<strong>%d</strong> коміт(ів)
release.ahead.target=до %s з моменту цього випуску
release.source_code=Код
release.new_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту.
release.edit_subheader=Публікація релізів допоможе зберігати чітку історію розвитку вашого проекту.
release.new_subheader=Публікація релізів допоможе вам організувати версію проєкту.
release.edit_subheader=Публікація релізів допоможе вам організувати версію проєкту.
release.tag_name=Назва тегу
release.target=Ціль
release.tag_helper=Виберіть існуючий тег або створіть новий.
@@ -2175,7 +2181,7 @@ settings.delete_org_title=Видалити організацію
settings.delete_org_desc=Ця організація буде безповоротно видалена. Продовжити?
settings.hooks_desc=Додайте webhooks, який буде викликатися для <strong>всіх репозиторіїв</strong> якими володіє ця організація.
settings.labels_desc=Додайте мітки, які можуть використовуватися для <strong>всіх репозиторіїв</strong> цієї органцізації.
settings.labels_desc=Додати мітки, які можуть бути використані для задач для <strong>всіх репозиторіїв</strong> в цій організації.
members.membership_visibility=Видимість учасника:
members.public=Показувати
@@ -2400,7 +2406,7 @@ repos.private=Приватний
repos.watches=Стежать
repos.stars=В обраному
repos.forks=Форки
repos.issues=Проблеми
repos.issues=Задачі
repos.size=Розмір
defaulthooks=Веб-хуки за замовчуванням
@@ -2583,7 +2589,7 @@ config.default_enable_timetracking=Увімкнути відстеження ч
config.default_allow_only_contributors_to_track_time=Враховувати тільки учасників розробки в підрахунку часу
config.no_reply_address=Прихований домен електронної пошти
config.default_visibility_organization=Видимість за замовчуванням для нових організацій
config.default_enable_dependencies=Увімкнути залежності проблем за замовчуванням
config.default_enable_dependencies=Увімкнути залежності задачі за замовчуванням
config.webhook_config=Конфігурація web-хуків
config.queue_length=Довжина черги
@@ -2738,13 +2744,13 @@ notices.delete_success=Сповіщення системи були видале
create_repo=створив(ла) репозиторій <a href="%s">%s</a>
rename_repo=репозиторій перейменовано з <code>%[1]s</code> на <a href="%[2]s">%[3]s</a>
commit_repo=надіслав зміни (push) до <a href="%[2]s">%[3]s</a> о <a href="%[1]s">%[4]s</a>
create_issue=`відкрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`закрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`повторно відкрив проблему <a href="%[1]s">%[3]s#%[2]s</a>`
create_issue=`відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`закрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`повторно відкрив задачу <a href="%[1]s">%[3]s#%[2]s</a>`
create_pull_request=`створив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
close_pull_request=`закрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_pull_request=`повторно відкрив запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue=`прокоментував проблему <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue=`прокоментував задачу <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull=`прокоментував запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request=`прийняв запит злиття <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo=перенесено репозиторій <code>%s</code> у <a href="%s">%s</a>

View File

@@ -940,14 +940,14 @@ migrate.migrating=正在从 <b>%s</b> 迁移...
migrate.migrating_failed=从 <b>%s</b> 迁移失败。
migrate.migrating_failed.error=错误:%s
migrate.migrating_failed_no_addr=迁移失败。
migrate.github.description=从 github.com 或其他 GitHub 实例迁移数据
migrate.github.description=从 github.com 或其他 GitHub 实例迁移数据
migrate.git.description=从任意 Git 服务迁移仓库。
migrate.gitlab.description=从 gitlab.com 或其他 GitLab 实例迁移数据
migrate.gitea.description=从 gitea.com 或其他 Gitea 实例迁移数据
migrate.gitlab.description=从 gitlab.com 或其他 GitLab 实例迁移数据
migrate.gitea.description=从 gitea.com 或其他 Gitea 实例迁移数据
migrate.gogs.description=从 notabug.org 或其他 Gogs 实例迁移数据。
migrate.onedev.description=从 code.onedev.io 或其他 OneDev 实例迁移数据
migrate.codebase.description=从 codebasehq.com 迁移数据
migrate.gitbucket.description=从 GitBucket 实例迁移数据
migrate.onedev.description=从 code.onedev.io 或其他 OneDev 实例迁移数据
migrate.codebase.description=从 codebasehq.com 迁移数据
migrate.gitbucket.description=从 GitBucket 实例迁移数据
migrate.migrating_git=迁移Git数据
migrate.migrating_topics=迁移主题
migrate.migrating_milestones=迁移里程碑
@@ -1729,7 +1729,7 @@ settings.use_internal_wiki=使用内置百科
settings.use_external_wiki=使用外部百科
settings.external_wiki_url=外部 Wiki 链接
settings.external_wiki_url_error=外部百科链接无效
settings.external_wiki_url_desc=当点击工单标签时,访问者将被重定向到外部工单系统的URL。
settings.external_wiki_url_desc=当点击百科标签时,访问者将被重定向到外部百科系统的URL。
settings.issues_desc=启用工单系统
settings.use_internal_issue_tracker=使用内置的轻量级工单管理系统
settings.use_external_issue_tracker=使用外部的工单管理系统
@@ -2334,6 +2334,7 @@ first_page=首页
last_page=末页
total=总计:%d
dashboard.new_version_hint=Gitea %s 现已可用,您正在运行 %s。查看 <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">博客</a> 了解更多详情。
dashboard.statistic=摘要
dashboard.operations=维护操作
dashboard.system_status=系统状态
@@ -2407,6 +2408,7 @@ dashboard.last_gc_pause=上次 GC 暂停时间
dashboard.gc_times=GC 执行次数
dashboard.delete_old_actions=从数据库中删除所有旧操作记录
dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。
dashboard.update_checker=更新检查器
users.user_manage_panel=用户帐户管理
users.new_account=创建新帐户

View File

@@ -34,6 +34,20 @@ twofa=兩步驟驗證
twofa_scratch=兩步驟驗證備用驗證碼
passcode=驗證碼
webauthn_insert_key=插入您的安全金鑰
webauthn_sign_in=按下您安全金鑰上的按鈕。如果您的安全金鑰沒有按鈕,請重新插入。
webauthn_press_button=請按下您安全金鑰上的按鈕…
webauthn_use_twofa=使用來自手機的兩步驟驗證碼
webauthn_error=無法讀取您的安全金鑰。
webauthn_unsupported_browser=您的瀏覽器還不支援 WebAuthn。
webauthn_error_unknown=發生未知的錯誤,請再試一次。
webauthn_error_insecure=WebAuthn 只支援安全連線。想在 HTTP 上測試您可以使用「localhost」或「127.0.0.1」
webauthn_error_unable_to_process=伺服器無法執行您的請求。
webauthn_error_duplicated=此請求不允許使用這個安全金鑰。請確保該金鑰尚未註冊。
webauthn_error_empty=您必須命名此金鑰。
webauthn_error_timeout=在成功讀取金鑰之前已逾時,請重新載入此頁面並重試。
webauthn_u2f_deprecated=「%s」金鑰使用已廢棄的 U2F 流程進行驗證。您應該重新註冊此金鑰並將先前註冊的移除。
webauthn_reload=重新載入
repository=儲存庫
organization=組織
@@ -389,8 +403,8 @@ repo.collaborator.added.subject=%s 把您加入到 %s
repo.collaborator.added.text=您已被新增為儲存庫的協作者:
[modal]
yes=確認操作
no=取消操作
yes=
no=
modify=更新
[form]
@@ -513,10 +527,11 @@ twofa=兩步驟驗證
account_link=已連結帳號
organization=組織
uid=用戶 ID
webauthn=安全金鑰
public_profile=公開的個人資料
biography_placeholder=告訴我們一些關於你的事
profile_desc=您的電子信箱將被用於通知提醒和其他作。
profile_desc=您的電子信箱將被用於通知提醒和其他作
password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
full_name=全名
website=個人網站
@@ -530,7 +545,7 @@ update_profile_success=已更新您的個人資料。
change_username=您的帳號已更改。
change_username_prompt=注意:修改帳號也會更改您的帳戶的 URL。
change_username_redirect_prompt=舊的帳號被領用前,會重新導向您的新帳號。
continue=繼續操作
continue=繼續
cancel=取消
language=語言
ui=佈景主題
@@ -561,7 +576,7 @@ emails=電子信箱
manage_emails=管理電子信箱
manage_themes=選擇預設佈景主題
manage_openid=管理 OpenID 位址
email_desc=您的主要電子信箱將被用於通知提醒和其他作。
email_desc=您的主要電子信箱將被用於通知提醒和其他作
theme_desc=這將是您在整個網站上的預設佈景主題。
primary=主要
activated=已啟用
@@ -706,7 +721,7 @@ oauth2_regenerate_secret_hint=遺失您的密鑰?
oauth2_client_secret_hint=請備份您的祕鑰。祕鑰在您離開這個頁面後將不會再顯示。
oauth2_application_edit=編輯
oauth2_application_create_description=OAuth2 應用程式讓您的第三方應用程式可以存取此 Gitea 上的帳戶。
oauth2_application_remove_description=刪除 OAuth2 應用會拒絕它存取此 Gitea 上已授權的帳戶。是否繼續?
oauth2_application_remove_description=刪除 OAuth2 應用程式會拒絕它存取此 Gitea 上已授權的帳戶。是否繼續?
authorized_oauth2_applications=已授權的 OAuth2 應用程式
authorized_oauth2_applications_description=您已授權給這些第三方應用程式存取您個人 Gitea 帳戶。請對不再需要的應用程式撤銷存取權。
@@ -733,6 +748,11 @@ passcode_invalid=無效的驗證碼,請重試。
twofa_enrolled=您的帳戶已經啟用了兩步驟驗證。請將備用驗證碼 (%s) 保存到一個安全的地方,它只會顯示這麼一次!
twofa_failed_get_secret=取得密鑰Secret失敗。
webauthn_desc=安全金鑰是包含加密密鑰的硬體設備,它們可以用於兩步驟驗證。安全金鑰必須支援 <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> 標準。
webauthn_register_key=新增安全金鑰
webauthn_nickname=暱稱
webauthn_delete_key=移除安全金鑰
webauthn_delete_key_desc=如果您移除安全金鑰,將不能再使用它登入。是否繼續?
manage_account_links=管理已連結的帳戶
manage_account_links_desc=這些外部帳戶已連結到您的 Gitea 帳戶。
@@ -920,14 +940,14 @@ migrate.migrating=正在從 <b>%s</b> 遷移...
migrate.migrating_failed=從 <b>%s</b> 遷移失敗
migrate.migrating_failed.error=錯誤:%s
migrate.migrating_failed_no_addr=遷移失敗。
migrate.github.description=從 github.com 或其他 GitHub 實例遷移資料。
migrate.github.description=從 github.com 或其他 GitHub 執行個體遷移資料。
migrate.git.description=從任何 Git 服務遷移儲存庫。
migrate.gitlab.description=從 gitlab.com 或其他 GitLab 實例遷移資料。
migrate.gitea.description=從 gitea.com 或其他 Gitea 實例遷移資料。
migrate.gogs.description=從 notabug.org 或其他 Gogs 實例遷移資料。
migrate.onedev.description=從 code.onedev.io 或其他 OneDev 實例遷移資料。
migrate.gitlab.description=從 gitlab.com 或其他 GitLab 執行個體遷移資料。
migrate.gitea.description=從 gitea.com 或其他 Gitea 執行個體遷移資料。
migrate.gogs.description=從 notabug.org 或其他 Gogs 執行個體遷移資料。
migrate.onedev.description=從 code.onedev.io 或其他 OneDev 執行個體遷移資料。
migrate.codebase.description=從 codebasehq.com 遷移資料。
migrate.gitbucket.description=從 GitBucket 實例遷移資料。
migrate.gitbucket.description=從 GitBucket 執行個體遷移資料。
migrate.migrating_git=正在遷移 Git 資料
migrate.migrating_topics=正在遷移主題
migrate.migrating_milestones=正在遷移里程碑
@@ -956,7 +976,7 @@ clone_this_repo=Clone 此儲存庫
create_new_repo_command=從命令列建立新儲存庫。
push_exist_repo=從命令行推送已經建立的儲存庫
empty_message=此儲存庫未包含任何內容。
broken_message=無法讀取此儲存庫底層的 Git 資料。請聯絡此 Gitea 實例的管理員或刪除此儲存庫。
broken_message=無法讀取此儲存庫底層的 Git 資料。請聯絡此 Gitea 執行個體的管理員或刪除此儲存庫。
code=程式碼
code.desc=存取原始碼、檔案、提交和分支。
@@ -1084,7 +1104,7 @@ commits.find=搜尋
commits.search_all=所有分支
commits.author=作者
commits.message=備註
commits.date=提交日期
commits.date=日期
commits.older=更舊的提交
commits.newer=更新的提交
commits.signed_by=簽署人
@@ -1132,7 +1152,7 @@ projects.open=開啟
projects.close=關閉
issues.desc=管理錯誤報告、任務和里程碑。
issues.filter_assignees=篩選成員
issues.filter_assignees=篩選負責人
issues.filter_milestones=篩選里程碑
issues.filter_projects=篩選專案
issues.filter_labels=篩選標籤
@@ -1156,10 +1176,10 @@ issues.new.no_milestone=未選擇里程碑
issues.new.clear_milestone=清除已選取里程碑
issues.new.open_milestone=開放中的里程碑
issues.new.closed_milestone=已關閉的里程碑
issues.new.assignees=成員
issues.new.add_assignees_title=指派成員
issues.new.clear_assignees=清除成員
issues.new.no_assignees=沒有成員
issues.new.assignees=負責人
issues.new.add_assignees_title=指派負責人
issues.new.clear_assignees=清除負責人
issues.new.no_assignees=沒有負責人
issues.new.no_reviewers=沒有審核者
issues.new.add_reviewer_title=請求審核
issues.choose.get_started=開始
@@ -1205,8 +1225,8 @@ issues.filter_label_exclude=`使用 <code>alt</code> + <code>click/enter</code>
issues.filter_label_no_select=所有標籤
issues.filter_milestone=里程碑
issues.filter_milestone_no_select=所有里程碑
issues.filter_assignee=成員
issues.filter_assginee_no_select=所有成員
issues.filter_assignee=負責人
issues.filter_assginee_no_select=所有負責人
issues.filter_type=類型
issues.filter_type.all_issues=所有問題
issues.filter_type.assigned_to_you=指派給您的
@@ -1231,8 +1251,8 @@ issues.action_close=關閉
issues.action_label=標籤
issues.action_milestone=里程碑
issues.action_milestone_no_select=無里程碑
issues.action_assignee=成員
issues.action_assignee_no_select=沒有成員
issues.action_assignee=負責人
issues.action_assignee_no_select=沒有負責人
issues.opened_by=建立於 %[1]s 由 <a href="%[2]s">%[3]s</a>
pulls.merged_by=由 <a href="%[2]s">%[3]s</a> 建立,合併於 %[1]s
pulls.merged_by_fake=由 %[2]s 建立,合併於 %[1]s
@@ -1411,7 +1431,7 @@ issues.review.hide_resolved=隱藏已解決
issues.review.resolve_conversation=解決對話
issues.review.un_resolve_conversation=取消解決對話
issues.review.resolved_by=標記了此對話為已解決
issues.assignee.error=因為未預期的錯誤,未能成功指派所有成員
issues.assignee.error=因為未預期的錯誤,未能成功加入所有負責人
issues.reference_issue.body=內容
issues.content_history.deleted=刪除
issues.content_history.edited=編輯
@@ -1552,8 +1572,8 @@ milestones.edit_success=已更新里程碑「%s」。
milestones.deletion=刪除里程碑
milestones.deletion_desc=刪除里程碑會從所有相關的問題移除它。是否繼續?
milestones.deletion_success=里程碑已刪除
milestones.filter_sort.closest_due_date=到期日由近到遠
milestones.filter_sort.furthest_due_date=到期日由遠到近
milestones.filter_sort.closest_due_date=截止日期由近到遠
milestones.filter_sort.furthest_due_date=截止日期由遠到近
milestones.filter_sort.least_complete=完成度由低到高
milestones.filter_sort.most_complete=完成度由高到低
milestones.filter_sort.most_issues=問題由多到少
@@ -1867,7 +1887,7 @@ settings.event_repository_desc=建立或刪除儲存庫。
settings.event_header_issue=問題事件
settings.event_issues=問題
settings.event_issues_desc=建立、編輯、關閉及重新開放問題。
settings.event_issue_assign=指派
settings.event_issue_assign=指派問題
settings.event_issue_assign_desc=指派或取消指派問題。
settings.event_issue_label=標籤
settings.event_issue_label_desc=更新或清除問題標籤。
@@ -1878,7 +1898,7 @@ settings.event_issue_comment_desc=已經建立、編輯或刪除的問題留言
settings.event_header_pull_request=合併請求事件
settings.event_pull_request=合併請求
settings.event_pull_request_desc=建立、編輯、關閉及重新開放合併請求。
settings.event_pull_request_assign=合併請求指派
settings.event_pull_request_assign=指派合併請求
settings.event_pull_request_assign_desc=指派或取消指派合併請求。
settings.event_pull_request_label=合併請求標籤
settings.event_pull_request_label_desc=更新或清除合併請求標籤。
@@ -2314,11 +2334,12 @@ first_page=首頁
last_page=末頁
total=總計:%d
dashboard.new_version_hint=現已推出 Gitea %s您正在執行 %s。查看<a target="_blank" rel="noreferrer" href="https://blog.gitea.io">部落格</a>以獲得更多資訊。
dashboard.statistic=摘要
dashboard.operations=維護
dashboard.operations=維護作
dashboard.system_status=系統狀態
dashboard.statistic_info=Gitea 資料庫統計:<b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 則留言,<b>%d</b> 個社群帳戶,<b>%d</b> 個用戶關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook <b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。
dashboard.operation_name=作名稱
dashboard.operation_name=名稱
dashboard.operation_switch=開關
dashboard.operation_run=執行
dashboard.clean_unbind_oauth=清理未綁定的 OAuth 連結
@@ -2387,6 +2408,7 @@ dashboard.last_gc_pause=上次 GC 暫停時間
dashboard.gc_times=GC 執行次數
dashboard.delete_old_actions=從資料庫刪除所有舊行為
dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。
dashboard.update_checker=更新檢查器
users.user_manage_panel=使用者帳戶管理
users.new_account=建立使用者帳戶
@@ -2567,7 +2589,7 @@ auths.tips.oauth2.general=OAuth2 認證
auths.tips.oauth2.general.tip=註冊新的 OAuth2 認證時callback/redirect 網址應為:<host>/user/oauth2/<Authentication Name>/callback
auths.tip.oauth2_provider=OAuth2 提供者
auths.tip.bitbucket=註冊新的 OAuth 客戶端並加入權限「Account - Read」。網址https://bitbucket.org/account/user/<your username>/oauth-consumers/new
auths.tip.nextcloud=在您的 Nextcloud 使用「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端
auths.tip.nextcloud=在您的執行個體中,於選單「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端
auths.tip.dropbox=建立一個新的 App。網址https://www.dropbox.com/developers/apps
auths.tip.facebook=註冊一個新的應用程式並新增產品「Facebook 登入」。網址https://developers.facebook.com/apps
auths.tip.github=註冊新的 OAuth 應用程式。網址https://github.com/settings/applications/new
@@ -2874,7 +2896,7 @@ notifications=通知
unread=未讀
read=已讀
no_unread=沒有未讀通知
no_read=沒有通知
no_read=沒有已讀通知
pin=固定通知
mark_as_read=標記為已讀
mark_as_unread=標記為未讀

30
package-lock.json generated
View File

@@ -23,7 +23,7 @@
"less": "4.1.2",
"less-loader": "10.2.0",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "8.13.10",
"mermaid": "8.14.0",
"mini-css-extract-plugin": "2.5.2",
"monaco-editor": "0.31.1",
"monaco-editor-webpack-plugin": "7.0.1",
@@ -3435,9 +3435,9 @@
}
},
"node_modules/dompurify": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz",
"integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ=="
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz",
"integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ=="
},
"node_modules/domutils": {
"version": "2.8.0",
@@ -6797,15 +6797,15 @@
}
},
"node_modules/mermaid": {
"version": "8.13.10",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.10.tgz",
"integrity": "sha512-2ANep359uML87+wiYaWSu83eg9Qc0xCLnNJdCh100m4v0orS3fp8SScsZLcDSElRGHi+1zuVJsEEVEWH05+COQ==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.14.0.tgz",
"integrity": "sha512-ITSHjwVaby1Li738sxhF48sLTxcNyUAoWfoqyztL1f7J6JOLpHOuQPNLBb6lxGPUA0u7xP9IRULgvod0dKu35A==",
"dependencies": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.4",
"dompurify": "2.3.5",
"graphlib": "^2.1.8",
"khroma": "^1.4.1",
"moment-mini": "^2.24.0",
@@ -12547,9 +12547,9 @@
}
},
"dompurify": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz",
"integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ=="
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz",
"integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ=="
},
"domutils": {
"version": "2.8.0",
@@ -15033,15 +15033,15 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"mermaid": {
"version": "8.13.10",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.10.tgz",
"integrity": "sha512-2ANep359uML87+wiYaWSu83eg9Qc0xCLnNJdCh100m4v0orS3fp8SScsZLcDSElRGHi+1zuVJsEEVEWH05+COQ==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.14.0.tgz",
"integrity": "sha512-ITSHjwVaby1Li738sxhF48sLTxcNyUAoWfoqyztL1f7J6JOLpHOuQPNLBb6lxGPUA0u7xP9IRULgvod0dKu35A==",
"requires": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.4",
"dompurify": "2.3.5",
"graphlib": "^2.1.8",
"khroma": "^1.4.1",
"moment-mini": "^2.24.0",

View File

@@ -23,7 +23,7 @@
"less": "4.1.2",
"less-loader": "10.2.0",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "8.13.10",
"mermaid": "8.14.0",
"mini-css-extract-plugin": "2.5.2",
"monaco-editor": "0.31.1",
"monaco-editor-webpack-plugin": "7.0.1",

View File

@@ -43,9 +43,13 @@ func ListUnadoptedRepositories(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
listOptions := utils.GetListOptions(ctx)
if listOptions.Page == 0 {
listOptions.Page = 1
}
repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx.FormString("query"), &listOptions)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.SetTotalCountHeader(int64(count))

View File

@@ -119,6 +119,7 @@ func CreateUser(ctx *context.APIContext) {
user_model.IsErrEmailAlreadyUsed(err) ||
db.IsErrNameReserved(err) ||
db.IsErrNameCharsNotAllowed(err) ||
user_model.IsErrEmailCharIsNotSupported(err) ||
user_model.IsErrEmailInvalid(err) ||
db.IsErrNamePatternNotAllowed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -265,7 +266,9 @@ func EditUser(ctx *context.APIContext) {
}
if err := user_model.UpdateUser(u, emailChanged); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailInvalid(err) {
if user_model.IsErrEmailAlreadyUsed(err) ||
user_model.IsErrEmailCharIsNotSupported(err) ||
user_model.IsErrEmailInvalid(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)

View File

@@ -121,7 +121,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
return
}
err = nl.LoadAttributes()
if err != nil && !models.IsErrCommentNotExist(err) {
if err != nil {
ctx.InternalServerError(err)
return
}

View File

@@ -203,7 +203,7 @@ func Migrate(ctx *context.APIContext) {
}
}()
if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts, nil); err != nil {
if repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, repoOwner.Name, opts, nil); err != nil {
handleMigrateError(ctx, repoOwner, remoteAddr, err)
return
}

View File

@@ -80,7 +80,8 @@ func AddEmail(ctx *context.APIContext) {
if err := user_model.AddEmailAddresses(emails); err != nil {
if user_model.IsErrEmailAlreadyUsed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
} else if user_model.IsErrEmailInvalid(err) {
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
user_model.IsErrEmailInvalid(err) {
errMsg := fmt.Sprintf("Email address %s invalid", err.(user_model.ErrEmailInvalid).Email)
ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
} else {

View File

@@ -93,7 +93,7 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["type"] = auth.LDAP
ctx.Data["type"] = auth.LDAP.Int()
ctx.Data["CurrentTypeName"] = auth.Names[auth.LDAP]
ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
ctx.Data["smtp_auth"] = "PLAIN"
@@ -112,7 +112,7 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["SSPIDefaultLanguage"] = ""
// only the first as default
ctx.Data["oauth2_provider"] = oauth2providers[0]
ctx.Data["oauth2_provider"] = oauth2providers[0].Name
ctx.HTML(http.StatusOK, tplAuthNew)
}

View File

@@ -41,10 +41,16 @@ func Users(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true
extraParamStrings := map[string]string{}
statusFilterKeys := []string{"is_active", "is_admin", "is_restricted", "is_2fa_enabled", "is_prohibit_login"}
statusFilterMap := map[string]string{}
for _, filterKey := range statusFilterKeys {
statusFilterMap[filterKey] = ctx.FormString("status_filter[" + filterKey + "]")
paramKey := "status_filter[" + filterKey + "]"
paramVal := ctx.FormString(paramKey)
statusFilterMap[filterKey] = paramVal
if paramVal != "" {
extraParamStrings[paramKey] = paramVal
}
}
sortType := ctx.FormString("sort")
@@ -68,6 +74,7 @@ func Users(ctx *context.Context) {
IsRestricted: util.OptionalBoolParse(statusFilterMap["is_restricted"]),
IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]),
IsProhibitLogin: util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]),
ExtraParamStrings: extraParamStrings,
}, tplUsers)
}
@@ -164,6 +171,9 @@ func NewUserPost(ctx *context.Context) {
case user_model.IsErrEmailAlreadyUsed(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
case user_model.IsErrEmailCharIsNotSupported(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
case user_model.IsErrEmailInvalid(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
@@ -217,15 +227,17 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
}
ctx.Data["Sources"] = sources
ctx.Data["TwoFactorEnabled"] = true
_, err = auth.GetTwoFactorByUID(u.ID)
hasTOTP, err := auth.HasTwoFactorByUID(u.ID)
if err != nil {
if !auth.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
return nil
}
ctx.Data["TwoFactorEnabled"] = false
ctx.ServerError("auth.HasTwoFactorByUID", err)
return nil
}
hasWebAuthn, err := auth.HasWebAuthnRegistrationsByUID(u.ID)
if err != nil {
ctx.ServerError("auth.HasWebAuthnRegistrationsByUID", err)
return nil
}
ctx.Data["TwoFactorEnabled"] = hasTOTP || hasWebAuthn
return u
}
@@ -327,14 +339,27 @@ func EditUserPost(ctx *context.Context) {
if form.Reset2FA {
tf, err := auth.GetTwoFactorByUID(u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("GetTwoFactorByUID", err)
ctx.ServerError("auth.GetTwoFactorByUID", err)
return
} else if tf != nil {
if err := auth.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
ctx.ServerError("auth.DeleteTwoFactorByID", err)
return
}
}
if err = auth.DeleteTwoFactorByID(tf.ID, u.ID); err != nil {
ctx.ServerError("DeleteTwoFactorByID", err)
wn, err := auth.GetWebAuthnCredentialsByUID(u.ID)
if err != nil {
ctx.ServerError("auth.GetTwoFactorByUID", err)
return
}
for _, cred := range wn {
if _, err := auth.DeleteCredential(cred.ID, u.ID); err != nil {
ctx.ServerError("auth.DeleteCredential", err)
return
}
}
}
u.LoginName = form.LoginName
@@ -364,7 +389,8 @@ func EditUserPost(ctx *context.Context) {
if user_model.IsErrEmailAlreadyUsed(err) {
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
} else if user_model.IsErrEmailInvalid(err) {
} else if user_model.IsErrEmailCharIsNotSupported(err) ||
user_model.IsErrEmailInvalid(err) {
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
} else {

View File

@@ -573,6 +573,9 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
case user_model.IsErrEmailAlreadyUsed(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
case user_model.IsErrEmailCharIsNotSupported(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
case user_model.IsErrEmailInvalid(err):
ctx.Data["Err_Email"] = true
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)

View File

@@ -84,6 +84,9 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
pager.SetDefaultParams(ctx)
for paramKey, paramVal := range opts.ExtraParamStrings {
pager.AddParamString(paramKey, paramVal)
}
ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplName)

View File

@@ -6,6 +6,7 @@ package org
import (
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -23,7 +24,14 @@ const (
// Home show organization home page
func Home(ctx *context.Context) {
ctx.SetParams(":org", ctx.Params(":username"))
uname := ctx.Params(":username")
if strings.HasSuffix(uname, ".keys") || strings.HasSuffix(uname, ".gpg") {
ctx.NotFound("", nil)
return
}
ctx.SetParams(":org", uname)
context.HandleOrgAssignment(ctx)
if ctx.Written() {
return
@@ -109,7 +117,7 @@ func Home(ctx *context.Context) {
return
}
var opts = &models.FindOrgMembersOpts{
opts := &models.FindOrgMembersOpts{
OrgID: org.ID,
PublicOnly: true,
ListOptions: db.ListOptions{Page: 1, PageSize: 25},

View File

@@ -311,6 +311,7 @@ func TeamMembers(ctx *context.Context) {
ctx.ServerError("GetMembers", err)
return
}
ctx.Data["Units"] = unit_model.Units
ctx.HTML(http.StatusOK, tplTeamMembers)
}
@@ -323,6 +324,7 @@ func TeamRepositories(ctx *context.Context) {
ctx.ServerError("GetRepositories", err)
return
}
ctx.Data["Units"] = unit_model.Units
ctx.HTML(http.StatusOK, tplTeamRepositories)
}

View File

@@ -77,8 +77,8 @@ func setPathsCompareContext(ctx *context.Context, base, head *git.Commit, headOw
ctx.Data["SourcePath"] = SourceCommitURL(headOwner, headName, head)
ctx.Data["RawPath"] = RawCommitURL(headOwner, headName, head)
if base != nil {
ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, head)
ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, head)
ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, base)
ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, base)
}
}

View File

@@ -103,7 +103,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
}
isWiki := false
var unitType = unit.TypeCode
unitType := unit.TypeCode
var wikiRepoName string
if strings.HasSuffix(reponame, ".wiki") {
isWiki = true
@@ -456,7 +456,6 @@ func serviceRPC(h serviceHandler, service string) {
if err := h.r.Body.Close(); err != nil {
log.Error("serviceRPC: Close: %v", err)
}
}()
if !hasAccess(service, h, true) {
@@ -467,7 +466,7 @@ func serviceRPC(h serviceHandler, service string) {
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
var err error
var reqBody = h.r.Body
reqBody := h.r.Body
// Handle GZIP.
if h.r.Header.Get("Content-Encoding") == "gzip" {
@@ -502,7 +501,9 @@ func serviceRPC(h serviceHandler, service string) {
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
if err.Error() != "signal: killed" {
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
}
return
}
}

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"html"
"net/http"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -16,6 +17,7 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"github.com/sergi/go-diff/diffmatchpatch"
@@ -68,9 +70,15 @@ func GetContentHistoryList(ctx *context.Context) {
actionText = i18n.Tr(lang, "repo.issues.content_history.edited")
}
timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, lang)
username := item.UserName
if setting.UI.DefaultShowFullName && strings.TrimSpace(item.UserFullName) != "" {
username = strings.TrimSpace(item.UserFullName)
}
results = append(results, map[string]interface{}{
"name": fmt.Sprintf("<img class='ui avatar image' src='%s'><strong>%s</strong> %s %s",
html.EscapeString(item.UserAvatarLink), html.EscapeString(item.UserName), actionText, timeSinceText),
html.EscapeString(item.UserAvatarLink), html.EscapeString(username), actionText, timeSinceText),
"value": item.HistoryID,
})
}

View File

@@ -253,6 +253,13 @@ func LFSFileGet(ctx *context.Context) {
}
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
oid := ctx.Params("oid")
p := lfs.Pointer{Oid: oid}
if !p.IsValid() {
ctx.NotFound("LFSFileGet", nil)
return
}
ctx.Data["Title"] = oid
ctx.Data["PageIsSettingsLFS"] = true
meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
@@ -343,6 +350,12 @@ func LFSDelete(ctx *context.Context) {
return
}
oid := ctx.Params("oid")
p := lfs.Pointer{Oid: oid}
if !p.IsValid() {
ctx.NotFound("LFSDelete", nil)
return
}
count, err := models.RemoveLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
if err != nil {
ctx.ServerError("LFSDelete", err)

View File

@@ -392,6 +392,8 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C
// PrepareViewPullInfo show meta information for a pull request preview page
func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
repo := ctx.Repo.Repository
pull := issue.PullRequest
@@ -575,8 +577,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
ctx.Data["IsNothingToCompare"] = true
}
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
if pull.IsWorkInProgress() {
ctx.Data["IsPullWorkInProgress"] = true
ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()

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