Compare commits

...

83 Commits

Author SHA1 Message Date
Lunny Xiao
98b3d8d5e1 Add changelog for v1.13.3 (#14877)
Add changelog for v1.13.3

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: techknowlogick <matti@mdranta.net>
2021-03-04 15:42:57 +01:00
zeripath
e663f7459a Fix paging of file commit logs (#14831) (#14879)
Backport #14831

Unfortunately `git log revision ... --skip=x -- path` skips the number of commits
not the number of commits relating to the path.

This PR changes the function to have a reader that reads and skips the
necessary number of commits by hand instead.

Fix #8716

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: 6543 <6543@obermui.de>
2021-03-04 19:53:28 +08:00
6543
7e85cba3e5 Print usefull error if SQLite is used in settings but not supported (#14476) (#14874)
* move log output to points where they are relefant

* check explicit of sqlite3 in settings
2021-03-03 21:54:32 +00:00
zeripath
26628aa1d1 Fix display since time round (#14226) (#14873)
Backport #14226

* Fix display since time round

* Fix since time

* Fix tests

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-03-03 21:17:34 +00:00
zeripath
d9d2e8f1e8 When Deleting Repository only explicitly close PRs whose base is not this repository (#14823) (#14842)
Backport #14823

When Deleting Repository only explicitly close PRs whose base is not this repository

Fix #14775

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-02 21:44:14 +08:00
zeripath
4558eeb21a Set HCaptchaSiteKey on Link Account pages (#14834) (#14839)
Backport #14834

When using HCaptcha on link account pages the site key needs to be passed
in. This PR ensures that HCaptchaSiteKey is set in the data.

Fix #14766

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-03-01 16:12:48 +01:00
zeripath
be25afc6de Fix a couple of CommentAsPatch issues. (#14804) (#14820)
Backport #14804

* CutDiffAroundLine makes the incorrect assumption that `---` and `+++` always represent part of the header of a diff.

This PR adds a flag to its parsing to prevent this problem and adds a streaming parsing technique to CutDiffAroundLine using an io.pipe instead of just sending data to an unbounded buffer.

Fix #14711

* Handle unquoted comment patch files

When making comment patches unfortunately the patch does not always quote the filename
This makes the diff --git header ambiguous again.

This PR finally adds handling for ambiguity in to parse patch

Fix #14812

* Add in testing for no error

There is no way currently for CutDiffAroundLine in this test to cause an
error however, it should still be tested.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-02-28 15:19:51 +02:00
zeripath
90bf1e7961 Disable broken OAuth2 providers at startup (#14802) (#14811)
Backport #14802

Instead of causing a log.Fatal, we should handle broken OAuth2
providers by disabling them.

Fix #8930

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-02-26 11:44:45 +01:00
6543
77ce08976d Re-enable transfer repo back from org to user account (#14807)
* re-enable transfer repo back from org to user account

* add test case
2021-02-26 11:08:09 +02:00
6543
8f389c5dfa Build for only available darwin target (#14771) (#14798)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-02-25 15:29:03 +01:00
6543
edef62e69e Backport: Repo Transfer permission checks (#14792) (#14794)
* Backport: Repo Transfer permission checks (#14792)

* update tests
2021-02-25 15:49:27 +08:00
a1012112796
cdff144f76 Fix double alert in oauth2 application edit view (#14764) (#14768)
Signed-off-by: a1012112796 <1012112796@qq.com>
2021-02-23 00:22:49 +01:00
zeripath
ad6084a222 Fix broken spans in diffs (#14678) (#14683)
Backport #14678

Gitea runs diff on highlighted code fragment for each line in order to
provide code highlight diffs. Unfortunately this diff algorithm is not
aware that span tags and entities are atomic and cannot be split.

The current fixup code makes some attempt to fix these broken tags
however, it cannot handle situations where a tag is split over multiple
blocks.

This PR provides a more algorithmic fixup mechanism whereby spans and
entities are completely coalesced into their respective blocks.

This may result in a incompletely reduced diff but - it will definitely
prevent the broken entities and spans that are currently possible.

As a result of this fixup several inconsistencies were discovered in our
testcases and these were also fixed.

Fix #14231

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

Co-authored-by: 6543 <6543@obermui.de>
2021-02-15 00:30:07 +01:00
zeripath
d3200db041 HasPreviousCommit causes recursive load of commits unnecessarily (#14598) (#14649)
This PR improves HasPreviousCommit to prevent the automatic and recursive loading
of previous commits using git merge-base --is-ancestor and git rev-list

Fix #13684

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

Co-authored-by: 6543 <6543@obermui.de>
2021-02-15 00:44:26 +02:00
zeripath
f305cffcaf Prevent race in PersistableChannelUniqueQueue.Has (#14651) (#14676)
Backport #14651

There is potentially a race with a slow starting internal
queue causing a NPE if Has is checked before the internal
queue has been setup.

This PR adds a lock on the Has() fn.

Fix #14311

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

Co-authored-by: 6543 <6543@obermui.de>
2021-02-14 01:50:50 +01:00
Lunny Xiao
c0320065b6 Turn default hash password algorightm back to pbkdf2 from argon2 until we found a better one (#14673) (#14675)
* Turn default hash password algorightm back to pbkdf2 from argon2 until we found a better one

* Add a warning on document

Co-authored-by: zeripath <art27@cantab.net>
2021-02-13 21:19:33 +01:00
zeripath
a1b74c5509 Allow org labels to be set with issue templates (#14593) (#14647)
Backport #14593

Fix #13688

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-02-13 19:34:47 +01:00
zeripath
101fb0d7e2 Do not assume all 40 char strings are SHA1s (#14624) (#14648)
Backport #14624

GetCommit() assumes that all 40 char strings are SHA1s. This leads to an
error if you try to do a PR on a branch which is 40 characters long.

This PR attempts the SHA first - and if it fails will switch to using rev-parse.

Fix #14470

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-02-14 01:25:47 +08:00
zeripath
82637c240a Accept multiple SSH keys in single LDAP SSHPublicKey attribute (#13989) (#14607)
Backport #13989

Fix #13984

Fix #14566

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-02-08 09:25:30 +08:00
6543
d0174d45ed Fix bug about ListOptions and stars/watchers pagnation (#14556) (#14573)
* Fix bug about ListOptions and stars/watchers pagnation

* fix unit test
2021-02-05 21:11:15 +00:00
Anton Khimich
da7a525c5c Fix GPG key deletion during account deletion (#14561) (#14569)
Per #14531, deleting a user account will delete the user's GPG keys
from the `gpg_key` table but not from `gpg_key_import`, which causes
an error when creating an account with the same email and attempting
to re-add the same key. This commit deletes all entries from
`gpg_key_import` that match any GPG key IDs belonging to the user.

Co-authored-by: Anton Khimich <anton.khimicha@mail.utoronto.ca>
2021-02-04 21:28:48 +01:00
6543
014313134f Changelog v1.13.2 (#14535) 2021-02-02 01:11:05 +02:00
Stefan
7dddf2186b configure internal ssh server w/ macs and ciphers, backport of #14523 (#14530) 2021-01-30 21:57:31 +02:00
6543
446c06b817 Set the name Mapper in migrations (#14526) (#14529)
Migrations currently uses the default Xorm mapper which is
not the same as the mapper Gitea actually uses.

This means that there is a difference between the struct
parsing and mapping to database tables in migrations as
compared to normal Sync2.

This was the cause for the catastrophic problem in v168 -
untagged fields are not mapped in the same way in migrations
as compared to outside of migrations.

This is also likely the cause of some weird subtle failures
in other migrations as any untagged field may not be being
mapped exactly the same way.

This PR suggests that we ensure that the mapper is set at
the start of the migrations code - but also enforces a strict
clean mapper between each migration.

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

Co-authored-by: zeripath <art27@cantab.net>
2021-01-30 21:47:11 +02:00
Lunny Xiao
9569607abb Fix wiki preview (#14515)
Co-authored-by: Lauris BH <lauris@nix.lv>
2021-01-29 22:37:20 +08:00
6543
8ff4f82e05 update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 (#14497) 2021-01-28 23:14:57 +08:00
6543
2595c70868 ChangeUserName: rename user files back on DB issue (#14447) 2021-01-25 01:36:16 +02:00
6543
00dc35e2de Fix migration v141 (#14387) (#14388)
* Fix mig 141

* temporary fix dump
2021-01-23 13:33:03 +02:00
zeripath
841efac895 ensure timeout error is shown on u2f timeout (#14417) (#14431)
Backport #14417

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
2021-01-23 05:11:57 +01:00
6543
dd827d6f2f Fix lfs preview bug (#14428) (#14433)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-01-23 03:55:52 +01:00
6543
4d2a6c40f8 [Backport] Fix Deadlock & Delete affected reactions on comment deletion (#14392) (#14425)
* Enhance Ghost comment mitigation Settings (#14392)

* refactor models.DeleteComment and delete related reactions too

* use deleteComment for UserDeleteWithCommentsMaxDays in DeleteUser

* Resolve Fixme & fix potential deadlock

* rm refactor

* make diff eaven less
2021-01-23 10:03:29 +08:00
6543
fb274ec54b Prevent panic on fuzzer provided string (#14405) (#14409)
* Prevent panic on fuzzer provided string

The fuzzer has found that providing a <body> tag with an attribute to
PostProcess causes a panic. This PR removes any rendered html or body
tags from the output.

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

* Placate lint

* placate lint again

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

* minor cleanup

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

Co-authored-by: zeripath <art27@cantab.net>
2021-01-20 20:47:30 +02:00
6543
0c3f95034a Use path not filepath in routers/editor (#14390) (#14396)
The incorrect use of filepath instead of path means that
it is possible to cause a stackoverflow on Windows

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

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
2021-01-19 17:00:13 +08:00
6543
4583caa077 Removed invalid form tag (#14391) (#14395)
introduced by #5073

Co-authored-by: KN4CK3R <KN4CK3R@users.noreply.github.com>
2021-01-19 07:12:04 +02:00
6543
cf20ebc8ba Check if label template exist first (#14384) (#14389) 2021-01-19 00:27:33 +02:00
Norwin
5ee09d3c81 check release publisher exists (#14375)
fixes #14365
was silently fixed in the feature PR #12096 for v1.14
2021-01-18 14:14:27 +01:00
Kyungmin Bae
e846b712fc Use Request.URL.RequestURI() for fcgi (#14312) (#14347)
Co-authored-by: Lauris BH <lauris@nix.lv>
2021-01-15 20:26:45 +08:00
Lunny Xiao
49d113945f Use ServerError provided by Context (#14333) (#14345)
... instead of InternalServerError by macaron
2021-01-15 17:36:30 +08:00
Norwin
096aa18249 Fix edit-label form init (#14337) 2021-01-14 15:03:16 +02:00
a1012112796
bf853db450 fix mailIssueCommentBatch for pull request (#14252) (#14296)
fix #14250

Signed-off-by: a1012112796 <1012112796@qq.com>
2021-01-11 08:46:19 +08:00
6543
fb656b5124 Add secure/httpOnly attributes to the lang cookie (#14279) (#14280)
* Add secure/httpOnly attributes to the lang cookie (#9690) (#14279)

* apply to InitLocales() too

Co-authored-by: Timo Gurr <timo.gurr@gmail.com>
2021-01-07 15:35:02 +01:00
Nuno Silva
4be59eb5d9 Render links for commit hashes followed by comma (#14224) (#14227)
Regex test cases: https://regex101.com/r/mVbPxM/2/

fixes #14223
2021-01-03 17:58:39 +01:00
Jimmy Praet
450b32c1a1 Send notifications for mentions in pulls, issues, (code-)comments (#14218) (#14221)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-01-03 15:44:40 +02:00
6543
06673cbccb Fix avatar bugs (#14217) (#14220)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-01-02 20:21:39 -05:00
zeripath
2fd708a397 Ensure that schema search path is set with every connection on postgres (#14131) (#14216)
Backport #14131

Unfortunately every connection to postgres requires that the search path is
set appropriately.

This PR shadows the postgres driver to ensure that as soon as a connection
is open, the search_path is set appropriately.

Fix #14088

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-01-02 18:58:53 +01:00
Lunny Xiao
7a0a133d7c Fix dashboard issues labels filter bug (#14210) (#14214) 2021-01-02 18:08:04 +01:00
Lunny Xiao
17022f8b62 When visit /favicon.ico but the static file is not exist return 404 but not continue to handle the route (#14211) (#14213)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-01-02 18:52:36 +08:00
a1012112796
5568dd6475 fix branch selector on new issue page (#14194) (#14207)
fix #14185

Signed-off-by: a1012112796 <1012112796@qq.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-01-01 16:14:49 +02:00
zeripath
58c105d4bf Check for notExist on profile repository page (#14197) (#14203)
Backport #14197

Fix #14189
2020-12-31 21:03:56 +08:00
Lunny Xiao
afa7f22dd8 Add changelog for v1.13.1 (#14172)
* Add changelog for v1.13.1

* Update CHANGELOG.md

Co-authored-by: John Olheiser <john.olheiser@gmail.com>

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: John Olheiser <john.olheiser@gmail.com>

* Update CHANGELOG.md

Co-authored-by: John Olheiser <john.olheiser@gmail.com>

* Update CHANGELOG.md

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-12-28 12:36:22 -05:00
Lunny Xiao
182be90655 Fix bug of link query order on markdown render (#14156) (#14171)
* Fix bug of link query order on markdown render

* Fix bluemonday bug and fix one wrong test

Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: 6543 <6543@obermui.de>
2020-12-28 12:08:55 -05:00
6543
4a738a8f16 Migration: drop too long repo topics (#14152) (#14155)
* Migration: drop to long repo topics

* Update modules/migrations/gitea_uploader.go
2020-12-26 21:57:06 -05:00
zeripath
206b66a184 Fix escaping issue in diff (#14154)
Ensure that linecontent is escaped before passing to template.HTML

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-12-26 22:15:42 +00:00
Daniil Pankratov
205be63bc1 Fix creation OAuth2 auth source from CLI. (#14146)
Fix #8356
2020-12-25 20:02:52 +08:00
zeripath
bf1441b1e1 Ensure that search term and page are not lost on adoption page-turn (#14133) (#14143)
Backport #14133

Fix #14111

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-12-24 21:54:15 +00:00
6543
fae18bdac0 more test case for STORAGE_TYPE overrides (and fixes) (#14096) (#14104)
Signed-off-by: 胡玮文 <huww98@outlook.com>

Co-authored-by: 胡玮文 <huww98@outlook.com>
2020-12-22 09:13:57 +02:00
6543
661e3e2bdc Fix storage config implementation (#14091) (#14095)
The design is very flexible, but not implemented correctly.
This commit fixes several issues:
* Costom storage type stated in https://docs.gitea.io/en-us/config-cheat-sheet/#storage-storage
  not working
* [storage.attachments], [storage.minio] section not respected

Signed-off-by: 胡玮文 <huww98@outlook.com>

Co-authored-by: 胡玮文 <huww98@outlook.com>
2020-12-22 00:56:18 +02:00
techknowlogick
70038719bf dep: update crypto. info: https://golangtutorial.dev/news/fix-in-crypto-package/ (#14078) 2020-12-21 14:02:40 +08:00
silverwind
55d7e53d99 Fix panic in BasicAuthDecode (#14046) (#14048)
* Fix panic in BasicAuthDecode

If the string does not contain ":" that function would run into an
`index out of range [1] with length 1` error. prevent that.

* Update BasicAuthDecode()

Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
2020-12-19 00:19:43 +08:00
6543
96d41287e5 [API] GetCombinedCommitStatusByRef always return json & swagger doc fixes (#14047)
* Fix swagger docs

* always return json
2020-12-18 13:38:47 +00:00
6543
df11075389 HotFix: Hide private partisipation in Orgs (#13994) (#14031)
* HotFix: Hide private partisipation in Orgs

Co-authored-by: zeripath <art27@cantab.net>
2020-12-17 22:32:24 +01:00
zeripath
b8a2cd9f40 Always wait for the cmd to finish (#14006) (#14039)
Backport #14006

After cancelling the context we still need to wait for the
command to finish otherwise zombie processes may occur

Fix #13987
2020-12-17 21:06:51 +01:00
mrsdizzie
4f296f7436 Don't use simpleMDE editor on mobile devices for 1.13 (#14029)
* Don't use simpleMDE editor on mobile devices

simpleMDE doesn't work properly on mobile devices -- We've replaced it with the slightly more working easyMDE in 1.14 but since that change can't be backported to 1.13 we will just disable the editor on mobile here.

* make isMobile function per code review -- disable simpleMDE for code review and replies

* Fix issue with plain text and wiki

Co-authored-by: silverwind <me@silverwind.io>
2020-12-17 17:39:12 +01:00
6543
78b9ef3586 Add emoji in label to project boards (#13978) (#14021)
* Update view.tmpl

Added rendering of emoji to project label

* Add RenderEmojiPlain to the title and remove has-emoji

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

Co-authored-by: Rakshith Ravi <rakshith.ravi@gmx.com>
Co-authored-by: zeripath <art27@cantab.net>
2020-12-16 15:15:58 -05:00
Cirno the Strongest
90dfe445c2 Send webhook when tag is removed via Web UI (#14015) (#14019)
* Send webhook when tag is removed via Web UI

* Stray code (cherry picked from commit 53308de0bf)

* Fix for 1.13
2020-12-16 18:24:02 +01:00
Jimmy Praet
a728d1e046 always use headCommitID for review comment diff (#14011) 2020-12-16 18:50:30 +08:00
zeripath
7f85728cf9 Trim the branch prefix from action.GetBranch (#13981) (#13986)
Backport #13981

 #13882 has revealed that the refname of an action is actually only a
refname pattern and necessarily a branch. For examplem pushing to
refs/heads/master will result in action with refname refs/heads/master
but pushing to master will result in a refname master.

The simplest solution to providing a fix here is to trim the prefix
therefore this PR proposes this.

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: a1012112796 <1012112796@qq.com>

Co-authored-by: a1012112796 <1012112796@qq.com>
2020-12-14 15:35:40 -05:00
zeripath
d2b308ae35 Ensure template renderer is available before storage handler (#13982)
`ctx.Error` requires that templates are available for this to
render the error page otherwise there will be a panic at this
time.

This was fixed in #13164 but was not completely backported.

Fix #13971

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-12-14 20:45:33 +08:00
zeripath
8e8e8ee150 Whenever the password is updated ensure that the hash algorithm is too (#13966) (#13967)
Backport #13966

`user.HashPassword` may potentially - and in fact now likely does - change
the `passwd_hash_algo` therefore whenever the `passwd` is updated, this
also needs to be updated.

Fix #13832

Thanks @fblaese for the hint

Signed-off-by: Andrew Thornton <art27@cantab.net>
2020-12-13 01:01:44 +01:00
6543
05ee88e576 Enforce setting HEAD in wiki to master (#13950) (#13961)
The default branch in wikis must be master - therefore forcibly set the HEAD
to master.

Fix #13846

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

Co-authored-by: 6543 <6543@obermui.de>

Co-authored-by: zeripath <art27@cantab.net>
2020-12-12 17:21:26 +00:00
Lunny Xiao
0d7cb2323f Fix feishu webhook caused by API changed (#13937) (#13938)
fix #13858
2020-12-11 16:11:32 +01:00
Lunny Xiao
5cdffc2b0c log error when login failed (#13903) (#13913)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
2020-12-09 10:37:15 -05:00
Jimmy Praet
a0101c61a4 Fix Quote Reply button on review diff (#13830) (#13898)
Backport of #13830 

Co-authored-by: 6543 <6543@obermui.de>
2020-12-08 22:12:35 +00:00
a1012112796
c0b1197a64 Fix Pull Merge when tag with same name as base branch exist (#13882) (#13896)
fix dst refspec error in 'Push back to upstream' when base branch have
same name with a tag.

fix #13851
Signed-off-by: a1012112796 <1012112796@qq.com>

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
2020-12-08 12:58:44 +01:00
6543
e39ed0b1d9 [API] return original URL of Repositories (#13885) (#13886) 2020-12-08 05:59:19 +01:00
manuelluis
cb24cbc1fc Fix branch/tag notifications in mirror sync (#13855) (#13862)
Co-authored-by: Gitea <gitea@fake.local>
Co-authored-by: 6543 <6543@obermui.de>
2020-12-05 23:30:28 -05:00
silverwind
584d01cf2c Fix mermaid chart size (#13865) 2020-12-05 22:13:31 -05:00
mrsdizzie
798fdeae45 Fix crash in short link processor (#13839) (#13841)
Fixes #13819
2020-12-04 04:08:48 +01:00
silverwind
87997cccbb Update font stack to bootstrap's latest (#13834) (#13837)
Backport #13834
2020-12-04 02:21:34 +01:00
John Olheiser
0d5111c5c3 Make sure email recipients can see issue (#13820) (#13827)
* Initial pass

* Remove over-op

Signed-off-by: jolheiser <john.olheiser@gmail.com>
2020-12-03 22:37:33 +01:00
Jimmy Praet
10fff12da4 Reply button is not removed when deleting a code review comment (#13824)
Backport #13774
2020-12-03 20:26:47 +00:00
zeripath
0d43a2a069 When reinitialising DBConfig reset the database use flags (#13796) (#13811)
Backport #13796

One perennial issue is users running the install page,
changing the database dialect and then suffering with issues

This PR simply resets all of the database.Use flags on
initDBConfig. This should prevent this issue from occuring.

Fix #13788
Fix #5480

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

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-12-03 11:13:19 +01:00
6543
8396b792f8 Migrations: Use Process Manager to create own Context (#13793) 2020-12-02 15:11:11 -06:00
170 changed files with 2719 additions and 787 deletions

View File

@@ -4,6 +4,97 @@ 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.13.3](https://github.com/go-gitea/gitea/releases/tag/v1.13.3) - 2021-03-04
* BREAKING
* Turn default hash password algorithm back to pbkdf2 from argon2 until we find a better one (#14673) (#14675)
* BUGFIXES
* Fix paging of file commit logs (#14831) (#14879)
* Print useful error if SQLite is used in settings but not supported (#14476) (#14874)
* Fix display since time round (#14226) (#14873)
* When Deleting Repository only explicitly close PRs whose base is not this repository (#14823) (#14842)
* Set HCaptchaSiteKey on Link Account pages (#14834) (#14839)
* Fix a couple of CommentAsPatch issues. (#14804) (#14820)
* Disable broken OAuth2 providers at startup (#14802) (#14811)
* Repo Transfer permission checks (#14792) (#14794)
* Fix double alert in oauth2 application edit view (#14764) (#14768)
* Fix broken spans in diffs (#14678) (#14683)
* Prevent race in PersistableChannelUniqueQueue.Has (#14651) (#14676)
* HasPreviousCommit causes recursive load of commits unnecessarily (#14598) (#14649)
* Do not assume all 40 char strings are SHA1s (#14624) (#14648)
* Allow org labels to be set with issue templates (#14593) (#14647)
* Accept multiple SSH keys in single LDAP SSHPublicKey attribute (#13989) (#14607)
* Fix bug about ListOptions and stars/watchers pagnation (#14556) (#14573)
* Fix GPG key deletion during account deletion (#14561) (#14569)
## [1.13.2](https://github.com/go-gitea/gitea/releases/tag/v1.13.2) - 2021-01-31
* SECURITY
* Prevent panic on fuzzer provided string (#14405) (#14409)
* Add secure/httpOnly attributes to the lang cookie (#14279) (#14280)
* API
* If release publisher is deleted use ghost user (#14375)
* BUGFIXES
* Internal ssh server respect Ciphers, MACs and KeyExchanges settings (#14523) (#14530)
* Set the name Mapper in migrations (#14526) (#14529)
* Fix wiki preview (#14515)
* Update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 (#14497)
* ChangeUserName: rename user files back on DB issue (#14447)
* Fix lfs preview bug (#14428) (#14433)
* Ensure timeout error is shown on u2f timeout (#14417) (#14431)
* Fix Deadlock & Delete affected reactions on comment deletion (#14392) (#14425)
* Use path not filepath in routers/editor (#14390) (#14396)
* Check if label template exist first (#14384) (#14389)
* Fix migration v141 (#14387) (#14388)
* Use Request.URL.RequestURI() for fcgi (#14347)
* Use ServerError provided by Context (#14333) (#14345)
* Fix edit-label form init (#14337)
* Fix mailIssueCommentBatch for pull request (#14252) (#14296)
* Render links for commit hashes followed by comma (#14224) (#14227)
* Send notifications for mentions in pulls, issues, (code-)comments (#14218) (#14221)
* Fix avatar bugs (#14217) (#14220)
* Ensure that schema search path is set with every connection on postgres (#14131) (#14216)
* Fix dashboard issues labels filter bug (#14210) (#14214)
* When visit /favicon.ico but the static file is not exist return 404 but not continue to handle the route (#14211) (#14213)
* Fix branch selector on new issue page (#14194) (#14207)
* Check for notExist on profile repository page (#14197) (#14203)
## [1.13.1](https://github.com/go-gitea/gitea/releases/tag/v1.13.1) - 2020-12-29
* SECURITY
* Hide private participation in Orgs (#13994) (#14031)
* Fix escaping issue in diff (#14153) (#14154)
* BUGFIXES
* Fix bug of link query order on markdown render (#14156) (#14171)
* Drop long repo topics during migration (#14152) (#14155)
* Ensure that search term and page are not lost on adoption page-turn (#14133) (#14143)
* Fix storage config implementation (#14091) (#14095)
* Fix panic in BasicAuthDecode (#14046) (#14048)
* Always wait for the cmd to finish (#14006) (#14039)
* Don't use simpleMDE editor on mobile devices for 1.13 (#14029)
* Fix incorrect review comment diffs (#14002) (#14011)
* Trim the branch prefix from action.GetBranch (#13981) (#13986)
* Ensure template renderer is available before storage handler (#13164) (#13982)
* Whenever the password is updated ensure that the hash algorithm is too (#13966) (#13967)
* Enforce setting HEAD in wiki to master (#13950) (#13961)
* Fix feishu webhook caused by API changed (#13938)
* Fix Quote Reply button on review diff (#13830) (#13898)
* Fix Pull Merge when tag with same name as base branch exist (#13882) (#13896)
* Fix mermaid chart size (#13865)
* Fix branch/tag notifications in mirror sync (#13855) (#13862)
* Fix crash in short link processor (#13839) (#13841)
* Update font stack to bootstrap's latest (#13834) (#13837)
* Make sure email recipients can see issue (#13820) (#13827)
* Reply button is not removed when deleting a code review comment (#13824)
* When reinitialising DBConfig reset the database use flags (#13796) (#13811)
* ENHANCEMENTS
* Add emoji in label to project boards (#13978) (#14021)
* Send webhook when tag is removed via Web UI (#14015) (#14019)
* Use Process Manager to create own Context (#13792) (#13793)
* API
* GetCombinedCommitStatusByRef always return json & swagger doc fixes (#14047)
* Return original URL of Repositories (#13885) (#13886)
## [1.13.0](https://github.com/go-gitea/gitea/releases/tag/v1.13.0) - 2020-12-01
* SECURITY
* Add Allow-/Block-List for Migrate & Mirrors (#13610) (#13776)

View File

@@ -585,7 +585,7 @@ release-darwin: | $(DIST_DIRS)
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
fi
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/amd64' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
cp /build/* $(DIST)/binaries
endif

View File

@@ -283,7 +283,7 @@ func runChangePassword(c *cli.Context) error {
}
user.HashPassword(c.String("password"))
if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil {
if err := models.UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err
}

View File

@@ -548,7 +548,7 @@ ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true
;Classes include "lower,upper,digit,spec"
PASSWORD_COMPLEXITY = off
; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
PASSWORD_HASH_ALGO = argon2
PASSWORD_HASH_ALGO = pbkdf2
; Set false to allow JavaScript to read CSRF cookie
CSRF_COOKIE_HTTP_ONLY = true
; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed
@@ -850,7 +850,7 @@ MACARON = file
ROUTER_LOG_LEVEL = Info
ROUTER = console
ENABLE_ACCESS_LOG = false
ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"
ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"
ACCESS = file
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
LEVEL = Info

View File

@@ -402,7 +402,7 @@ relation to port exhaustion.
- `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server.
- `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary.
- `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`)
- `PASSWORD_HASH_ALGO`: **argon2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\].
- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others.
- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie.
- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users.
- `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off):

6
go.mod
View File

@@ -4,7 +4,7 @@ go 1.14
require (
code.gitea.io/gitea-vet v0.2.1
code.gitea.io/sdk/gitea v0.13.1
code.gitea.io/sdk/gitea v0.13.2
gitea.com/lunny/levelqueue v0.3.0
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76
@@ -104,7 +104,7 @@ require (
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
go.jolheiser.com/hcaptcha v0.0.4
go.jolheiser.com/pwn v0.0.3
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff
@@ -124,3 +124,5 @@ require (
)
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.2.4
replace github.com/microcosm-cc/bluemonday => github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8

14
go.sum
View File

@@ -15,8 +15,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA=
code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
@@ -598,6 +598,8 @@ github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc h1:ERSU1OvZ6MdWhHieo2oT7xwR/HCksqKdgK6iYPU5pHI=
github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8 h1:1omo92DLtxQu6VwVPSZAmduHaK5zssed6cvkHyl1XOg=
github.com/lunny/bluemonday v1.0.5-0.20201227154428-ca34796141e8/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk=
@@ -649,8 +651,6 @@ github.com/mgechev/revive v1.0.3-0.20200921231451-246eac737dc7 h1:ydVkpU/M4/c45y
github.com/mgechev/revive v1.0.3-0.20200921231451-246eac737dc7/go.mod h1:no/hfevHbndpXR5CaJahkYCfM/FFpmM/dSOwFGU7Z1o=
github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig=
github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao=
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 h1:hJde9rA24hlTcAYSwJoXpDUyGtfKQ/jsofw+WaDqGrI=
github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
@@ -937,8 +937,9 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1053,6 +1054,8 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -1197,6 +1200,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs=

View File

@@ -445,11 +445,12 @@ func TestAPIRepoTransfer(t *testing.T) {
expectedStatus int
}{
{ctxUserID: 1, newOwner: "user2", teams: nil, expectedStatus: http.StatusAccepted},
{ctxUserID: 2, newOwner: "user1", teams: nil, expectedStatus: http.StatusAccepted},
{ctxUserID: 2, newOwner: "user1", teams: nil, expectedStatus: http.StatusForbidden},
{ctxUserID: 2, newOwner: "user6", teams: nil, expectedStatus: http.StatusForbidden},
{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
{ctxUserID: 2, newOwner: "user2", teams: nil, expectedStatus: http.StatusAccepted},
}
defer prepareTestEnv(t)()

View File

@@ -237,6 +237,6 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text())
}
assert.ElementsMatch(t, u.SSHKeys, syncedKeys)
assert.ElementsMatch(t, u.SSHKeys, syncedKeys, "Unequal number of keys synchronized for user: %s", u.UserName)
}
}

View File

@@ -111,7 +111,7 @@ func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bo
func doGitClone(dstLocalPath string, u *url.URL) func(*testing.T) {
return func(t *testing.T) {
assert.NoError(t, git.CloneWithArgs(u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{}))
assert.NoError(t, git.CloneWithArgs(context.Background(), u.String(), dstLocalPath, allowLFSFilters(), git.CloneRepoOptions{}))
assert.True(t, com.IsExist(filepath.Join(dstLocalPath, "README.md")))
}
}

View File

@@ -13,6 +13,7 @@ import (
"time"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -243,7 +244,7 @@ func (a *Action) getCommentLink(e Engine) string {
// GetBranch returns the action's repository branch.
func (a *Action) GetBranch() string {
return a.RefName
return strings.TrimPrefix(a.RefName, git.BranchPrefix)
}
// GetContent returns the action's content.

View File

@@ -77,7 +77,7 @@ func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path
if err := bucket.Delete(path); err != nil {
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
log.Warn(title+" [%s]: %v", path, err)
if err = createNotice(x, NoticeRepository, desc); err != nil {
if err = createNotice(e, NoticeRepository, desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
}

View File

@@ -18,7 +18,7 @@ func TestGetCommitStatuses(t *testing.T) {
sha1 := "1234123412341234123412341234123412341234"
statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{})
statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{ListOptions: ListOptions{Page: 1, PageSize: 50}})
assert.NoError(t, err)
assert.Equal(t, int(maxResults), 5)
assert.Len(t, statuses, 5)

View File

@@ -65,7 +65,11 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) {
// ListGPGKeys returns a list of public keys belongs to given user.
func ListGPGKeys(uid int64, listOptions ListOptions) ([]*GPGKey, error) {
sess := x.Where("owner_id=? AND primary_key_id=''", uid)
return listGPGKeys(x, uid, listOptions)
}
func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error) {
sess := e.Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)
if listOptions.Page != 0 {
sess = listOptions.setSessionPagination(sess)
}

View File

@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
@@ -1491,6 +1492,7 @@ type UserIssueStatsOptions struct {
IsPull bool
IsClosed bool
IssueIDs []int64
LabelIDs []int64
}
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
@@ -1507,29 +1509,38 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
cond = cond.And(builder.In("issue.id", opts.IssueIDs))
}
sess := func(cond builder.Cond) *xorm.Session {
s := x.Where(cond)
if len(opts.LabelIDs) > 0 {
s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id").
In("issue_label.label_id", opts.LabelIDs)
}
return s
}
switch opts.FilterMode {
case FilterModeAll:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
return nil, err
}
case FilterModeAssign:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
@@ -1537,27 +1548,27 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
return nil, err
}
case FilterModeCreate:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
And("issue.poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
And("issue.poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
case FilterModeMention:
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
@@ -1567,7 +1578,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
}
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
stats.AssignCount, err = x.Where(cond).
stats.AssignCount, err = sess(cond).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
@@ -1575,14 +1586,14 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
return nil, err
}
stats.CreateCount, err = x.Where(cond).
stats.CreateCount, err = sess(cond).
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.MentionCount, err = x.Where(cond).
stats.MentionCount, err = sess(cond).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
@@ -1590,7 +1601,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
return nil, err
}
stats.YourRepositoriesCount, err = x.Where(cond).
stats.YourRepositoriesCount, err = sess(cond).
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
Count(new(Issue))
if err != nil {
@@ -1829,6 +1840,19 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) {
return
}
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
func (issue *Issue) FindAndUpdateIssueMentions(ctx DBContext, doer *User, content string) (mentions []*User, err error) {
rawMentions := references.FindAllMentionsMarkdown(content)
mentions, err = issue.ResolveMentionsByVisibility(ctx, doer, rawMentions)
if err != nil {
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
}
if err = UpdateIssueMentions(ctx, issue.ID, mentions); err != nil {
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
}
return
}
// ResolveMentionsByVisibility returns the users mentioned in an issue, removing those that
// don't have access to reading it. Teams are expanded into their users, but organizations are ignored.
func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, mentions []string) (users []*User, err error) {

View File

@@ -82,7 +82,7 @@ func isUserAssignedToIssue(e Engine, issue *Issue, user *User) (isAssigned bool,
}
// ClearAssigneeByUserID deletes all assignments of an user
func clearAssigneeByUserID(sess *xorm.Session, userID int64) (err error) {
func clearAssigneeByUserID(sess Engine, userID int64) (err error) {
_, err = sess.Delete(&IssueAssignees{AssigneeID: userID})
return
}

View File

@@ -1077,6 +1077,10 @@ func DeleteComment(comment *Comment, doer *User) error {
return err
}
if err := deleteReaction(sess, &ReactionOptions{Comment: comment}); err != nil {
return err
}
return sess.Commit()
}

View File

@@ -47,7 +47,7 @@ type Label struct {
func GetLabelTemplateFile(name string) ([][3]string, error) {
data, err := GetRepoInitFile("label", name)
if err != nil {
return nil, fmt.Errorf("GetRepoInitFile: %v", err)
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %v", err)}
}
lines := strings.Split(string(data), "\n")
@@ -62,7 +62,7 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
fields := strings.SplitN(parts[0], " ", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("line is malformed: %s", line)
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
}
color := strings.Trim(fields[0], " ")
@@ -70,7 +70,7 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
color = "#" + color
}
if !LabelColorPattern.MatchString(color) {
return nil, fmt.Errorf("bad HTML color code in line: %s", line)
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)}
}
var description string
@@ -167,7 +167,7 @@ func (label *Label) ForegroundColor() template.CSS {
func loadLabels(labelTemplate string) ([]string, error) {
list, err := GetLabelTemplateFile(labelTemplate)
if err != nil {
return nil, ErrIssueLabelTemplateLoad{labelTemplate, err}
return nil, err
}
labels := make([]string, len(list))
@@ -186,7 +186,7 @@ func LoadLabelsFormatted(labelTemplate string) (string, error) {
func initializeLabels(e Engine, id int64, labelTemplate string, isOrg bool) error {
list, err := GetLabelTemplateFile(labelTemplate)
if err != nil {
return ErrIssueLabelTemplateLoad{labelTemplate, err}
return err
}
labels := make([]*Label, len(list))

View File

@@ -178,11 +178,15 @@ func CreateCommentReaction(doer *User, issue *Issue, comment *Comment, content s
})
}
func deleteReaction(e *xorm.Session, opts *ReactionOptions) error {
func deleteReaction(e Engine, opts *ReactionOptions) error {
reaction := &Reaction{
Type: opts.Type,
UserID: opts.Doer.ID,
IssueID: opts.Issue.ID,
Type: opts.Type,
}
if opts.Doer != nil {
reaction.UserID = opts.Doer.ID
}
if opts.Issue != nil {
reaction.IssueID = opts.Issue.ID
}
if opts.Comment != nil {
reaction.CommentID = opts.Comment.ID

View File

@@ -16,13 +16,13 @@ type ListOptions struct {
Page int // start from 1
}
func (opts ListOptions) getPaginatedSession() *xorm.Session {
func (opts *ListOptions) getPaginatedSession() *xorm.Session {
opts.setDefaultValues()
return x.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}
func (opts ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
func (opts *ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
opts.setDefaultValues()
if opts.PageSize <= 0 {
@@ -31,21 +31,21 @@ func (opts ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
return sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}
func (opts ListOptions) setEnginePagination(e Engine) Engine {
func (opts *ListOptions) setEnginePagination(e Engine) Engine {
opts.setDefaultValues()
return e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}
// GetStartEnd returns the start and end of the ListOptions
func (opts ListOptions) GetStartEnd() (start, end int) {
func (opts *ListOptions) GetStartEnd() (start, end int) {
opts.setDefaultValues()
start = (opts.Page - 1) * opts.PageSize
end = start + opts.Page
return
}
func (opts ListOptions) setDefaultValues() {
func (opts *ListOptions) setDefaultValues() {
if opts.PageSize <= 0 {
opts.PageSize = setting.API.DefaultPagingNum
}

View File

@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
"xorm.io/xorm/names"
)
const minDBVersion = 70 // Gitea 1.5.3
@@ -296,6 +297,8 @@ func EnsureUpToDate(x *xorm.Engine) error {
// Migrate database to current version
func Migrate(x *xorm.Engine) error {
// Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
x.SetMapper(names.GonicMapper{})
if err := x.Sync(new(Version)); err != nil {
return fmt.Errorf("sync: %v", err)
}
@@ -334,6 +337,8 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
// Migrate
for i, m := range migrations[v-minDBVersion:] {
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
// Reset the mapper between each migration - migrations are not supposed to depend on each other
x.SetMapper(names.GonicMapper{})
if err = m.Migrate(x); err != nil {
return fmt.Errorf("do migrate: %v", err)
}

View File

@@ -12,7 +12,7 @@ import (
func addKeepActivityPrivateUserColumn(x *xorm.Engine) error {
type User struct {
KeepActivityPrivate bool
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
}
if err := x.Sync2(new(User)); err != nil {

View File

@@ -15,12 +15,14 @@ import (
"code.gitea.io/gitea/modules/setting"
// Needed for the MySQL driver
_ "github.com/go-sql-driver/mysql"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
// Needed for the MySQL driver
_ "github.com/go-sql-driver/mysql"
// Needed for the Postgresql driver
_ "github.com/lib/pq"
@@ -145,7 +147,16 @@ func getEngine() (*xorm.Engine, error) {
return nil, err
}
engine, err := xorm.NewEngine(setting.Database.Type, connStr)
var engine *xorm.Engine
if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 {
// OK whilst we sort out our schema issues - create a schema aware postgres
registerPostgresSchemaDriver()
engine, err = xorm.NewEngine("postgresschema", connStr)
} else {
engine, err = xorm.NewEngine(setting.Database.Type, connStr)
}
if err != nil {
return nil, err
}
@@ -155,16 +166,6 @@ func getEngine() (*xorm.Engine, error) {
engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"})
}
engine.SetSchema(setting.Database.Schema)
if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 {
// Add the schema to the search path
if _, err := engine.Exec(`SELECT set_config(
'search_path',
? || ',' || current_setting('search_path'),
false)`,
setting.Database.Schema); err != nil {
return nil, err
}
}
return engine, nil
}
@@ -313,6 +314,13 @@ func DumpDatabase(filePath string, dbType string) error {
tbs = append(tbs, t)
}
// temporary fix for v1.13.x (https://github.com/go-gitea/gitea/issues/14069)
if _, err := x.Where(builder.IsNull{"keep_activity_private"}).
Cols("keep_activity_private").
Update(User{KeepActivityPrivate: false}); err != nil {
return err
}
type Version struct {
ID int64 `xorm:"pk autoincr"`
Version int64

View File

@@ -8,6 +8,7 @@ import (
"sort"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/log"
)
// OAuth2Provider describes the display values of a single OAuth2 provider
@@ -119,13 +120,28 @@ func InitOAuth2() error {
if err := oauth2.Init(x); err != nil {
return err
}
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
return initOAuth2LoginSources()
}
// ResetOAuth2 clears existing OAuth2 providers and loads them from DB
func ResetOAuth2() error {
oauth2.ClearProviders()
return initOAuth2LoginSources()
}
// initOAuth2LoginSources is used to load and register all active OAuth2 providers
func initOAuth2LoginSources() error {
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
for _, source := range loginSources {
oAuth2Config := source.OAuth2()
err := oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
if err != nil {
return err
log.Critical("Unable to register source: %s due to Error: %v. This source will be disabled.", source.Name, err)
source.IsActived = false
if err = UpdateSource(source); err != nil {
log.Critical("Unable to update source %s to disable it. Error: %v", err)
return err
}
}
}
return nil

View File

@@ -54,7 +54,11 @@ func (r *Release) loadAttributes(e Engine) error {
if r.Publisher == nil {
r.Publisher, err = getUserByID(e, r.PublisherID)
if err != nil {
return err
if IsErrUserNotExist(err) {
r.Publisher = NewGhostUser()
} else {
return err
}
}
}
return getReleaseAttachments(e, r)

View File

@@ -426,6 +426,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
HTMLURL: repo.HTMLURL(),
SSHURL: cloneLink.SSH,
CloneURL: cloneLink.HTTPS,
OriginalURL: repo.SanitizedOriginalURL(),
Website: repo.Website,
Stars: repo.NumStars,
Forks: repo.NumForks,
@@ -1599,26 +1600,27 @@ func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes [
}
// DeleteRepository deletes a repository for a user or organization.
// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
func DeleteRepository(doer *User, uid, repoID int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// In case is a organization.
org, err := GetUserByID(uid)
org, err := getUserByID(sess, uid)
if err != nil {
return err
}
if org.IsOrganization() {
if err = org.GetTeams(&SearchTeamOptions{}); err != nil {
if err = org.getTeams(sess); err != nil {
return err
}
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
repo := &Repository{ID: repoID, OwnerID: uid}
has, err := sess.Get(repo)
repo := &Repository{OwnerID: uid}
has, err := sess.ID(repoID).Get(repo)
if err != nil {
return err
} else if !has {
@@ -1767,14 +1769,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
}
if err = sess.Commit(); err != nil {
sess.Close()
if len(deployKeys) > 0 {
// We need to rewrite the public keys because the commit failed
if err2 := RewriteAllPublicKeys(); err2 != nil {
return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2)
}
}
return fmt.Errorf("Commit: %v", err)
return err
}
sess.Close()

View File

@@ -0,0 +1,75 @@
// Copyright 2020 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 models
import (
"database/sql"
"database/sql/driver"
"sync"
"code.gitea.io/gitea/modules/setting"
"github.com/lib/pq"
"xorm.io/xorm/dialects"
)
var registerOnce sync.Once
func registerPostgresSchemaDriver() {
registerOnce.Do(func() {
sql.Register("postgresschema", &postgresSchemaDriver{})
dialects.RegisterDriver("postgresschema", dialects.QueryDriver("postgres"))
})
}
type postgresSchemaDriver struct {
pq.Driver
}
// Open opens a new connection to the database. name is a connection string.
// This function opens the postgres connection in the default manner but immediately
// runs set_config to set the search_path appropriately
func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
conn, err := d.Driver.Open(name)
if err != nil {
return conn, err
}
schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema)
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
// and in any case pq does not implement it
if execer, ok := conn.(driver.Execer); ok { //nolint
_, err := execer.Exec(`SELECT set_config(
'search_path',
$1 || ',' || current_setting('search_path'),
false)`, []driver.Value{schemaValue}) //nolint
if err != nil {
_ = conn.Close()
return nil, err
}
return conn, nil
}
stmt, err := conn.Prepare(`SELECT set_config(
'search_path',
$1 || ',' || current_setting('search_path'),
false)`)
if err != nil {
_ = conn.Close()
return nil, err
}
defer stmt.Close()
// driver.String.ConvertValue will never return err for string
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint
if err != nil {
_ = conn.Close()
return nil, err
}
return conn, nil
}

View File

@@ -40,7 +40,6 @@ import (
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/ssh"
"xorm.io/builder"
"xorm.io/xorm"
)
// UserType defines the user type
@@ -551,6 +550,7 @@ func (u *User) GetOwnedOrganizations() (err error) {
}
// GetOrganizations returns paginated organizations that user belongs to.
// TODO: does not respect All and show orgs you privately participate
func (u *User) GetOrganizations(opts *SearchOrganizationsOptions) error {
sess := x.NewSession()
defer sess.Close()
@@ -922,6 +922,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
// ChangeUserName changes all corresponding setting from old user name to new one.
func ChangeUserName(u *User, newUserName string) (err error) {
oldUserName := u.Name
if err = IsUsableUsername(newUserName); err != nil {
return err
}
@@ -939,16 +940,24 @@ func ChangeUserName(u *User, newUserName string) (err error) {
return err
}
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, u.Name); err != nil {
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
return fmt.Errorf("Change repo owner name: %v", err)
}
// Do not fail if directory does not exist
if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
if err = os.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Rename user directory: %v", err)
}
return sess.Commit()
if err = sess.Commit(); err != nil {
if err2 := os.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
}
return err
}
return nil
}
// checkDupEmail checks whether there are the same email with the user
@@ -1019,8 +1028,7 @@ func deleteBeans(e Engine, beans ...interface{}) (err error) {
return nil
}
// FIXME: need some kind of mechanism to record failure. HINT: system notice
func deleteUser(e *xorm.Session, u *User) error {
func deleteUser(e Engine, u *User) error {
// Note: A user owns any repository or belongs to any organization
// cannot perform delete operation.
@@ -1114,6 +1122,16 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** END: PublicKey *****
// ***** START: GPGPublicKey *****
keys, err := listGPGKeys(e, u.ID, ListOptions{})
if err != nil {
return fmt.Errorf("ListGPGKeys: %v", err)
}
// Delete GPGKeyImport(s).
for _, key := range keys {
if _, err = e.Delete(&GPGKeyImport{KeyID: key.KeyID}); err != nil {
return fmt.Errorf("deleteGPGKeyImports: %v", err)
}
}
if _, err = e.Delete(&GPGKey{OwnerID: u.ID}); err != nil {
return fmt.Errorf("deleteGPGKeys: %v", err)
}
@@ -1134,18 +1152,21 @@ func deleteUser(e *xorm.Session, u *User) error {
return fmt.Errorf("Delete: %v", err)
}
// FIXME: system notice
// Note: There are something just cannot be roll back,
// so just keep error logs of those operations.
path := UserPath(u.Name)
if err := util.RemoveAll(path); err != nil {
return fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
if err = util.RemoveAll(path); err != nil {
err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
return err
}
if len(u.Avatar) > 0 {
avatarPath := u.CustomAvatarRelativePath()
if err := storage.Avatars.Delete(avatarPath); err != nil {
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
if err = storage.Avatars.Delete(avatarPath); err != nil {
err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
return err
}
}
@@ -1601,20 +1622,34 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
func addLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
var sshKeysNeedUpdate bool
for _, sshKey := range sshPublicKeys {
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey))
if err == nil {
sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40])
if _, err := AddPublicKey(usr.ID, sshKeyName, sshKey, s.ID); err != nil {
var err error
found := false
keys := []byte(sshKey)
loop:
for len(keys) > 0 && err == nil {
var out ssh.PublicKey
// We ignore options as they are not relevant to Gitea
out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
if err != nil {
break loop
}
found = true
marshalled := string(ssh.MarshalAuthorizedKey(out))
marshalled = marshalled[:len(marshalled)-1]
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
if IsErrKeyAlreadyExist(err) {
log.Trace("addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user", s.Name, usr.Name)
log.Trace("addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user", sshKeyName, usr.Name)
} else {
log.Error("addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err)
log.Error("addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
}
} else {
log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", s.Name, usr.Name)
log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", sshKeyName, usr.Name)
sshKeysNeedUpdate = true
}
} else {
}
if !found && err != nil {
log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
}
}

View File

@@ -40,10 +40,9 @@ func (u *User) generateRandomAvatar(e Engine) error {
return fmt.Errorf("RandomImage: %v", err)
}
if u.Avatar == "" {
u.Avatar = base.HashEmail(u.AvatarEmail)
}
u.Avatar = base.HashEmail(seed)
// Don't share the images so that we can delete them easily
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
if err := png.Encode(w, img); err != nil {
log.Error("Encode: %v", err)
@@ -133,7 +132,7 @@ func (u *User) UploadAvatar(data []byte) error {
// Otherwise, if any of the users delete his avatar
// Other users will lose their avatars too.
u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
if err = updateUser(sess, u); err != nil {
if err = updateUserCols(sess, u, "use_custom_avatar", "avatar"); err != nil {
return fmt.Errorf("updateUser: %v", err)
}

View File

@@ -421,3 +421,71 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
assert.Equal(t, results[1].ID, 4)
}
}
func TestAddLdapSSHPublicKeys(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
s := &LoginSource{ID: 1}
testCases := []struct {
keyString string
number int
keyContents []string
}{
{
keyString: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
number: 1,
keyContents: []string{
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
},
},
{
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
number: 2,
keyContents: []string{
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
},
},
{
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
# comment asmdna,ndp
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
number: 2,
keyContents: []string{
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
},
},
{
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
382488320jasdj1lasmva/vasodifipi4193-fksma.cm
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
number: 2,
keyContents: []string{
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
},
},
}
for i, kase := range testCases {
s.ID = int64(i) + 20
addLdapSSHPublicKeys(user, s, []string{kase.keyString})
keys, err := ListPublicLdapSSHKeys(user.ID, s.ID)
assert.NoError(t, err)
if err != nil {
continue
}
assert.Equal(t, kase.number, len(keys))
for _, key := range keys {
assert.Contains(t, kase.keyContents, key.Content)
}
for _, key := range keys {
DeletePublicKey(user, key.ID)
}
}
}

View File

@@ -118,6 +118,11 @@ func RemoveProvider(providerName string) {
delete(goth.GetProviders(), providerName)
}
// ClearProviders clears all OAuth2 providers from the goth lib
func ClearProviders() {
goth.ClearProviders()
}
// used to create different types of goth providers
func createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) (goth.Provider, error) {
callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback"

View File

@@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net/http"
"net/url"
@@ -65,6 +66,11 @@ func BasicAuthDecode(encoded string) (string, string, error) {
}
auth := strings.SplitN(string(s), ":", 2)
if len(auth) != 2 {
return "", "", errors.New("invalid basic authentication")
}
return auth[0], auth[1], nil
}

View File

@@ -46,6 +46,12 @@ func TestBasicAuthDecode(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "foo", user)
assert.Equal(t, "bar", pass)
_, _, err = BasicAuthDecode("aW52YWxpZA==")
assert.Error(t, err)
_, _, err = BasicAuthDecode("invalid")
assert.Error(t, err)
}
func TestBasicAuthEncode(t *testing.T) {

View File

@@ -153,6 +153,7 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.
err := fn(ctx, cancel)
if err != nil {
cancel()
_ = cmd.Wait()
return err
}
}

View File

@@ -9,6 +9,7 @@ import (
"bufio"
"bytes"
"container/list"
"errors"
"fmt"
"image"
"image/color"
@@ -17,6 +18,7 @@ import (
_ "image/png" // for processing png images
"io"
"net/http"
"os/exec"
"strconv"
"strings"
@@ -309,23 +311,33 @@ func (c *Commit) CommitsBefore() (*list.List, error) {
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
for i := 0; i < c.ParentCount(); i++ {
commit, err := c.Parent(i)
if err != nil {
return false, err
}
if commit.ID == commitHash {
return true, nil
}
commitInParentCommit, err := commit.HasPreviousCommit(commitHash)
if err != nil {
return false, err
}
if commitInParentCommit {
return true, nil
}
this := c.ID.String()
that := commitHash.String()
if this == that {
return false, nil
}
return false, nil
if err := CheckGitVersionConstraint(">= 1.8.0"); err == nil {
_, err := NewCommand("merge-base", "--is-ancestor", that, this).RunInDir(c.repo.Path)
if err == nil {
return true, nil
}
var exitError *exec.ExitError
if errors.As(err, &exitError) {
if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 {
return false, nil
}
}
return false, err
}
result, err := NewCommand("rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunInDir(c.repo.Path)
if err != nil {
return false, err
}
return len(strings.TrimSpace(result)) > 0, nil
}
// CommitsBeforeLimit returns num commits before current revision

View File

@@ -125,30 +125,39 @@ var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+
const cmdDiffHead = "diff --git "
func isHeader(lof string) bool {
return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
func isHeader(lof string, inHunk bool) bool {
return strings.HasPrefix(lof, cmdDiffHead) || (!inHunk && (strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")))
}
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
// it also recalculates hunks and adds the appropriate headers to the new diff.
// Warning: Only one-file diffs are allowed.
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) (string, error) {
if line == 0 || numbersOfLine == 0 {
// no line or num of lines => no diff
return ""
return "", nil
}
scanner := bufio.NewScanner(originalDiff)
hunk := make([]string, 0)
// begin is the start of the hunk containing searched line
// end is the end of the hunk ...
// currentLine is the line number on the side of the searched line (differentiated by old)
// otherLine is the line number on the opposite side of the searched line (differentiated by old)
var begin, end, currentLine, otherLine int64
var headerLines int
inHunk := false
for scanner.Scan() {
lof := scanner.Text()
// Add header to enable parsing
if isHeader(lof) {
if isHeader(lof, inHunk) {
if strings.HasPrefix(lof, cmdDiffHead) {
inHunk = false
}
hunk = append(hunk, lof)
headerLines++
}
@@ -157,6 +166,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
}
// Detect "hunk" with contains commented lof
if strings.HasPrefix(lof, "@@") {
inHunk = true
// Already got our hunk. End of hunk detected!
if len(hunk) > headerLines {
break
@@ -213,15 +223,19 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
}
}
}
err := scanner.Err()
if err != nil {
return "", err
}
// No hunk found
if currentLine == 0 {
return ""
return "", nil
}
// headerLines + hunkLine (1) = totalNonCodeLines
if len(hunk)-headerLines-1 <= numbersOfLine {
// No need to cut the hunk => return existing hunk
return strings.Join(hunk, "\n")
return strings.Join(hunk, "\n"), nil
}
var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
if old {
@@ -256,5 +270,5 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
// construct the new hunk header
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
return strings.Join(newHunk, "\n")
return strings.Join(newHunk, "\n"), nil
}

View File

@@ -23,8 +23,28 @@ const exampleDiff = `diff --git a/README.md b/README.md
+ cut off
+ cut off`
const breakingDiff = `diff --git a/aaa.sql b/aaa.sql
index d8e4c92..19dc8ad 100644
--- a/aaa.sql
+++ b/aaa.sql
@@ -1,9 +1,10 @@
--some comment
--- some comment 5
+--some coment 2
+-- some comment 3
create or replace procedure test(p1 varchar2)
is
begin
---new comment
dbms_output.put_line(p1);
+--some other comment
end;
/
`
func TestCutDiffAroundLine(t *testing.T) {
result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
result, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
assert.NoError(t, err)
resultByLine := strings.Split(result, "\n")
assert.Len(t, resultByLine, 7)
// Check if headers got transferred
@@ -37,18 +57,50 @@ func TestCutDiffAroundLine(t *testing.T) {
assert.Equal(t, "+ Build Status", resultByLine[4])
// Must be same result as before since old line 3 == new line 5
newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
newResult, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
assert.NoError(t, err)
assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5")
newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
newResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
assert.NoError(t, err)
assert.Equal(t, exampleDiff, newResult)
emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
emptyResult, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
assert.NoError(t, err)
assert.Empty(t, emptyResult)
// Line is out of scope
emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
emptyResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
assert.NoError(t, err)
assert.Empty(t, emptyResult)
// Handle minus diffs properly
minusDiff, err := CutDiffAroundLine(strings.NewReader(breakingDiff), 2, false, 4)
assert.NoError(t, err)
expected := `diff --git a/aaa.sql b/aaa.sql
--- a/aaa.sql
+++ b/aaa.sql
@@ -1,9 +1,10 @@
--some comment
--- some comment 5
+--some coment 2`
assert.Equal(t, expected, minusDiff)
// Handle minus diffs properly
minusDiff, err = CutDiffAroundLine(strings.NewReader(breakingDiff), 3, false, 4)
assert.NoError(t, err)
expected = `diff --git a/aaa.sql b/aaa.sql
--- a/aaa.sql
+++ b/aaa.sql
@@ -1,9 +1,10 @@
--some comment
--- some comment 5
+--some coment 2
+-- some comment 3`
assert.Equal(t, expected, minusDiff)
}
func BenchmarkCutDiffAroundLine(b *testing.B) {
@@ -69,7 +121,7 @@ func ExampleCutDiffAroundLine() {
Docker Pulls
+ cut off
+ cut off`
result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
result, _ := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
println(result)
}

View File

@@ -32,6 +32,7 @@ var (
GitExecutable = "git"
// DefaultContext is the default context to run git commands in
// will be overwritten by Init with HammerContext
DefaultContext = context.Background()
gitVersion *version.Version

View File

@@ -8,6 +8,7 @@ package git
import (
"bytes"
"container/list"
"context"
"errors"
"fmt"
"os"
@@ -166,19 +167,24 @@ type CloneRepoOptions struct {
// Clone clones original repository to target path.
func Clone(from, to string, opts CloneRepoOptions) (err error) {
return CloneWithContext(DefaultContext, from, to, opts)
}
// CloneWithContext clones original repository to target path.
func CloneWithContext(ctx context.Context, from, to string, opts CloneRepoOptions) (err error) {
cargs := make([]string, len(GlobalCommandArgs))
copy(cargs, GlobalCommandArgs)
return CloneWithArgs(from, to, cargs, opts)
return CloneWithArgs(ctx, from, to, cargs, opts)
}
// CloneWithArgs original repository to target path.
func CloneWithArgs(from, to string, args []string, opts CloneRepoOptions) (err error) {
func CloneWithArgs(ctx context.Context, from, to string, args []string, opts CloneRepoOptions) (err error) {
toDir := path.Dir(to)
if err = os.MkdirAll(toDir, os.ModePerm); err != nil {
return err
}
cmd := NewCommandNoGlobals(args...).AddArguments("clone")
cmd := NewCommandContextNoGlobals(ctx, args...).AddArguments("clone")
if opts.Mirror {
cmd.AddArguments("--mirror")
}

View File

@@ -9,6 +9,8 @@ import (
"bytes"
"container/list"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
@@ -129,19 +131,23 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
// ConvertToSHA1 returns a Hash object from a potential ID string
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
if len(commitID) != 40 {
var err error
actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path)
if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
return SHA1{}, ErrNotExist{commitID, ""}
}
return SHA1{}, err
if len(commitID) == 40 {
sha1, err := NewIDFromString(commitID)
if err == nil {
return sha1, nil
}
commitID = actualCommitID
}
return NewIDFromString(commitID)
actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path)
if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") ||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
return SHA1{}, ErrNotExist{commitID, ""}
}
return SHA1{}, err
}
return NewIDFromString(actualCommitID)
}
// GetCommit returns commit object of by ID string.
@@ -323,8 +329,41 @@ func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
// CommitsByFileAndRange return the commits according revison file and the page
func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
stdout, err := NewCommand("log", revision, "--follow", "--skip="+strconv.Itoa((page-1)*50),
"--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
skip := (page - 1) * CommitsRangeSize
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
go func() {
stderr := strings.Builder{}
err := NewCommand("log", revision, "--follow",
"--max-count="+strconv.Itoa(CommitsRangeSize*page),
prettyLogFormat, "--", file).
RunInDirPipeline(repo.Path, stdoutWriter, &stderr)
if err != nil {
if stderr.Len() > 0 {
err = fmt.Errorf("%v - %s", err, stderr.String())
}
_ = stdoutWriter.CloseWithError(err)
} else {
_ = stdoutWriter.Close()
}
}()
if skip > 0 {
_, err := io.CopyN(ioutil.Discard, stdoutReader, int64(skip*41))
if err != nil {
if err == io.EOF {
return list.New(), nil
}
_ = stdoutReader.CloseWithError(err)
return nil, err
}
}
stdout, err := ioutil.ReadAll(stdoutReader)
if err != nil {
return nil, err
}

View File

@@ -43,7 +43,7 @@ var (
// sha1CurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
// Although SHA1 hashes are 40 chars long, the regex matches the hash from 7 to 40 chars in length
// so that abbreviated hash links can be used as well. This matches git and github useability.
sha1CurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,40})(?:\s|$|\)|\]|\.(\s|$))`)
sha1CurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,40})(?:\s|$|\)|\]|[.,](\s|$))`)
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
@@ -298,9 +298,6 @@ func RenderEmoji(
return ctx.postProcess(rawHTML)
}
var byteBodyTag = []byte("<body>")
var byteBodyTagClosing = []byte("</body>")
func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
if ctx.procs == nil {
ctx.procs = defaultProcessors
@@ -308,9 +305,9 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
// give a generous extra 50 bytes
res := make([]byte, 0, len(rawHTML)+50)
res = append(res, byteBodyTag...)
res = append(res, "<html><body>"...)
res = append(res, rawHTML...)
res = append(res, byteBodyTagClosing...)
res = append(res, "</body></html>"...)
// parse the HTML
nodes, err := html.ParseFragment(bytes.NewReader(res), nil)
@@ -322,6 +319,31 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
ctx.visitNode(node, true)
}
newNodes := make([]*html.Node, 0, len(nodes))
for _, node := range nodes {
if node.Data == "html" {
node = node.FirstChild
for node != nil && node.Data != "body" {
node = node.NextSibling
}
}
if node == nil {
continue
}
if node.Data == "body" {
child := node.FirstChild
for child != nil {
newNodes = append(newNodes, child)
child = child.NextSibling
}
} else {
newNodes = append(newNodes, node)
}
}
nodes = newNodes
// Create buffer in which the data will be placed again. We know that the
// length will be at least that of res; to spare a few alloc+copy, we
// reuse res, resetting its length to 0.
@@ -334,12 +356,8 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
}
}
// remove initial parts - because Render creates a whole HTML page.
res = buf.Bytes()
res = res[bytes.Index(res, byteBodyTag)+len(byteBodyTag) : bytes.LastIndex(res, byteBodyTagClosing)]
// Everything done successfully, return parsed data.
return res, nil
return buf.Bytes(), nil
}
func (ctx *postProcessCtx) visitNode(node *html.Node, visitText bool) {
@@ -632,16 +650,18 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) {
// When parsing HTML, x/net/html will change all quotes which are
// not used for syntax into UTF-8 quotes. So checking val[0] won't
// be enough, since that only checks a single byte.
if (strings.HasPrefix(val, "“") && strings.HasSuffix(val, "”")) ||
(strings.HasPrefix(val, "") && strings.HasSuffix(val, "")) {
const lenQuote = len("")
val = val[lenQuote : len(val)-lenQuote]
} else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) ||
(strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) {
val = val[1 : len(val)-1]
} else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "") {
const lenQuote = len("")
val = val[1 : len(val)-lenQuote]
if len(val) > 1 {
if (strings.HasPrefix(val, "") && strings.HasSuffix(val, "")) ||
(strings.HasPrefix(val, "") && strings.HasSuffix(val, "")) {
const lenQuote = len("")
val = val[lenQuote : len(val)-lenQuote]
} else if (strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"")) ||
(strings.HasPrefix(val, "'") && strings.HasSuffix(val, "'")) {
val = val[1 : len(val)-1]
} else if strings.HasPrefix(val, "'") && strings.HasSuffix(val, "") {
const lenQuote = len("")
val = val[1 : len(val)-lenQuote]
}
}
props[key] = val
}

View File

@@ -46,6 +46,12 @@ func TestRender_Commits(t *testing.T) {
test("/home/gitea/"+sha, "<p>/home/gitea/"+sha+"</p>")
test("deadbeef", `<p>deadbeef</p>`)
test("d27ace93", `<p>d27ace93</p>`)
test(sha[:14]+".x", `<p>`+sha[:14]+`.x</p>`)
expected14 := `<a href="` + commit[:len(commit)-(40-14)] + `" rel="nofollow"><code>` + sha[:10] + `</code></a>`
test(sha[:14]+".", `<p>`+expected14+`.</p>`)
test(sha[:14]+",", `<p>`+expected14+`,</p>`)
test("["+sha[:14]+"]", `<p>[`+expected14+`]</p>`)
}
func TestRender_CrossReferences(t *testing.T) {
@@ -142,7 +148,7 @@ func TestRender_links(t *testing.T) {
`<p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p>`)
test(
"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
`<p><a href="magnet:?dn=download&xt=urn%3Abtih%3A5dee65101db281ac9c46344cd6b175cdcadabcde" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download</a></p>`)
`<p><a href="magnet:?xt=urn%3Abtih%3A5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download</a></p>`)
// Test that should *not* be turned into URL
test(
@@ -377,3 +383,28 @@ func TestRender_ShortLinks(t *testing.T) {
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
}
func Test_ParseClusterFuzz(t *testing.T) {
setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
var localMetas = map[string]string{
"user": "go-gitea",
"repo": "gitea",
}
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
val, err := PostProcess([]byte(data), "https://example.com", localMetas, false)
assert.NoError(t, err)
assert.NotContains(t, string(val), "<html")
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
val, err = PostProcess([]byte(data), "https://example.com", localMetas, false)
assert.NoError(t, err)
assert.NotContains(t, string(val), "<html")
}

View File

@@ -6,7 +6,6 @@
package migrations
import (
"bytes"
"context"
"fmt"
"io"
@@ -125,7 +124,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
}
r.DefaultBranch = repo.DefaultBranch
r, err = repository.MigrateRepositoryGitData(g.doer, owner, r, base.MigrateOptions{
r, err = repository.MigrateRepositoryGitData(g.ctx, owner, r, base.MigrateOptions{
RepoName: g.repoName,
Description: repo.Description,
OriginalURL: repo.OriginalURL,
@@ -154,6 +153,15 @@ func (g *GiteaLocalUploader) Close() {
// CreateTopics creates topics
func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
// ignore topics to long for the db
c := 0
for i := range topics {
if len(topics[i]) <= 25 {
topics[c] = topics[i]
c++
}
}
topics = topics[:c]
return models.SaveTopics(g.repo.ID, topics...)
}
@@ -802,13 +810,20 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
}
var patch string
patchBuf := new(bytes.Buffer)
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
// We should ignore the error since the commit maybe removed when force push to the pull request
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
} else {
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
}
reader, writer := io.Pipe()
defer func() {
_ = reader.Close()
_ = writer.Close()
}()
go func() {
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, writer); err != nil {
// We should ignore the error since the commit maybe removed when force push to the pull request
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
}
_ = writer.Close()
}()
patch, _ = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
var c = models.Comment{
Type: models.CommentTypeCode,

View File

@@ -29,7 +29,7 @@ func NewNotifier() base.Notifier {
return &actionNotifier{}
}
func (a *actionNotifier) NotifyNewIssue(issue *models.Issue) {
func (a *actionNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
if err := issue.LoadPoster(); err != nil {
log.Error("issue.LoadPoster: %v", err)
return
@@ -88,7 +88,7 @@ func (a *actionNotifier) NotifyIssueChangeStatus(doer *models.User, issue *model
// NotifyCreateIssueComment notifies comment on an issue to notifiers
func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
act := &models.Action{
ActUserID: doer.ID,
ActUser: doer,
@@ -120,7 +120,7 @@ func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *model
}
}
func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest, mentions []*models.User) {
if err := pull.LoadIssue(); err != nil {
log.Error("pull.LoadIssue: %v", err)
return
@@ -203,7 +203,7 @@ func (a *actionNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo *
}
}
func (a *actionNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
func (a *actionNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
if err := review.LoadReviewer(); err != nil {
log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
return

View File

@@ -20,7 +20,7 @@ type Notifier interface {
NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string)
NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string)
NotifyNewIssue(*models.Issue)
NotifyNewIssue(issue *models.Issue, mentions []*models.User)
NotifyIssueChangeStatus(*models.User, *models.Issue, *models.Comment, bool)
NotifyIssueChangeMilestone(doer *models.User, issue *models.Issue, oldMilestoneID int64)
NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment)
@@ -32,15 +32,16 @@ type Notifier interface {
NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
addedLabels []*models.Label, removedLabels []*models.Label)
NotifyNewPullRequest(*models.PullRequest)
NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User)
NotifyMergePullRequest(*models.PullRequest, *models.User)
NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest)
NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment)
NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User)
NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User)
NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string)
NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment)
NotifyCreateIssueComment(*models.User, *models.Repository,
*models.Issue, *models.Comment)
NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment, mentions []*models.User)
NotifyUpdateComment(*models.User, *models.Comment, string)
NotifyDeleteComment(*models.User, *models.Comment)

View File

@@ -23,11 +23,11 @@ func (*NullNotifier) Run() {
// NotifyCreateIssueComment places a place holder function
func (*NullNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
}
// NotifyNewIssue places a place holder function
func (*NullNotifier) NotifyNewIssue(issue *models.Issue) {
func (*NullNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
}
// NotifyIssueChangeStatus places a place holder function
@@ -35,11 +35,15 @@ func (*NullNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Is
}
// NotifyNewPullRequest places a place holder function
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
}
// NotifyPullRequestReview places a place holder function
func (*NullNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment) {
func (*NullNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment, mentions []*models.User) {
}
// NotifyPullRequestCodeComment places a place holder function
func (*NullNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
}
// NotifyMergePullRequest places a place holder function

View File

@@ -30,7 +30,7 @@ func NewNotifier() base.Notifier {
}
func (r *indexerNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
if comment.Type == models.CommentTypeComment {
if issue.Comments == nil {
if err := issue.LoadDiscussComments(); err != nil {
@@ -45,11 +45,11 @@ func (r *indexerNotifier) NotifyCreateIssueComment(doer *models.User, repo *mode
}
}
func (r *indexerNotifier) NotifyNewIssue(issue *models.Issue) {
func (r *indexerNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
issue_indexer.UpdateIssueIndexer(issue)
}
func (r *indexerNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
func (r *indexerNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
issue_indexer.UpdateIssueIndexer(pr.Issue)
}

View File

@@ -27,7 +27,7 @@ func NewNotifier() base.Notifier {
}
func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
var act models.ActionType
if comment.Type == models.CommentTypeClose {
act = models.ActionCloseIssue
@@ -41,13 +41,13 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.
act = 0
}
if err := mailer.MailParticipantsComment(comment, act, issue); err != nil {
if err := mailer.MailParticipantsComment(comment, act, issue, mentions); err != nil {
log.Error("MailParticipantsComment: %v", err)
}
}
func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) {
if err := mailer.MailParticipants(issue, issue.Poster, models.ActionCreateIssue); err != nil {
func (m *mailNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
if err := mailer.MailParticipants(issue, issue.Poster, models.ActionCreateIssue, mentions); err != nil {
log.Error("MailParticipants: %v", err)
}
}
@@ -69,18 +69,18 @@ func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.
}
}
if err := mailer.MailParticipants(issue, doer, actionType); err != nil {
if err := mailer.MailParticipants(issue, doer, actionType, nil); err != nil {
log.Error("MailParticipants: %v", err)
}
}
func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
if err := mailer.MailParticipants(pr.Issue, pr.Issue.Poster, models.ActionCreatePullRequest); err != nil {
func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
if err := mailer.MailParticipants(pr.Issue, pr.Issue.Poster, models.ActionCreatePullRequest, mentions); err != nil {
log.Error("MailParticipants: %v", err)
}
}
func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment) {
func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment, mentions []*models.User) {
var act models.ActionType
if comment.Type == models.CommentTypeClose {
act = models.ActionCloseIssue
@@ -89,11 +89,17 @@ func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models
} else if comment.Type == models.CommentTypeComment {
act = models.ActionCommentPull
}
if err := mailer.MailParticipantsComment(comment, act, pr.Issue); err != nil {
if err := mailer.MailParticipantsComment(comment, act, pr.Issue, mentions); err != nil {
log.Error("MailParticipantsComment: %v", err)
}
}
func (m *mailNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
if err := mailer.MailMentionsComment(pr, comment, mentions); err != nil {
log.Error("MailMentionsComment: %v", err)
}
}
func (m *mailNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) {
// mail only sent to added assignees and not self-assignee
if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == models.EmailNotificationsEnabled {
@@ -115,7 +121,7 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode
return
}
pr.Issue.Content = ""
if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest); err != nil {
if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest, nil); err != nil {
log.Error("MailParticipants: %v", err)
}
}
@@ -143,7 +149,7 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(doer *models.User, pr *model
}
comment.Content = ""
m.NotifyCreateIssueComment(doer, comment.Issue.Repo, comment.Issue, comment)
m.NotifyCreateIssueComment(doer, comment.Issue.Repo, comment.Issue, comment, nil)
}
func (m *mailNotifier) NotifyNewRelease(rel *models.Release) {

View File

@@ -39,16 +39,16 @@ func NewContext() {
// NotifyCreateIssueComment notifies issue comment related message to notifiers
func NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
for _, notifier := range notifiers {
notifier.NotifyCreateIssueComment(doer, repo, issue, comment)
notifier.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
}
}
// NotifyNewIssue notifies new issue to notifiers
func NotifyNewIssue(issue *models.Issue) {
func NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
for _, notifier := range notifiers {
notifier.NotifyNewIssue(issue)
notifier.NotifyNewIssue(issue, mentions)
}
}
@@ -67,9 +67,9 @@ func NotifyMergePullRequest(pr *models.PullRequest, doer *models.User) {
}
// NotifyNewPullRequest notifies new pull request to notifiers
func NotifyNewPullRequest(pr *models.PullRequest) {
func NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
for _, notifier := range notifiers {
notifier.NotifyNewPullRequest(pr)
notifier.NotifyNewPullRequest(pr, mentions)
}
}
@@ -81,9 +81,16 @@ func NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
}
// NotifyPullRequestReview notifies new pull request review
func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
for _, notifier := range notifiers {
notifier.NotifyPullRequestReview(pr, review, comment)
notifier.NotifyPullRequestReview(pr, review, comment, mentions)
}
}
// NotifyPullRequestCodeComment notifies new pull request code comment
func NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
for _, notifier := range notifiers {
notifier.NotifyPullRequestCodeComment(pr, comment, mentions)
}
}

View File

@@ -51,7 +51,7 @@ func (ns *notificationService) Run() {
}
func (ns *notificationService) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
var opts = issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: doer.ID,
@@ -60,13 +60,31 @@ func (ns *notificationService) NotifyCreateIssueComment(doer *models.User, repo
opts.CommentID = comment.ID
}
_ = ns.issueQueue.Push(opts)
for _, mention := range mentions {
var opts = issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: doer.ID,
ReceiverID: mention.ID,
}
if comment != nil {
opts.CommentID = comment.ID
}
_ = ns.issueQueue.Push(opts)
}
}
func (ns *notificationService) NotifyNewIssue(issue *models.Issue) {
func (ns *notificationService) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: issue.Poster.ID,
})
for _, mention := range mentions {
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: issue.ID,
NotificationAuthorID: issue.Poster.ID,
ReceiverID: mention.ID,
})
}
}
func (ns *notificationService) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) {
@@ -83,7 +101,7 @@ func (ns *notificationService) NotifyMergePullRequest(pr *models.PullRequest, do
})
}
func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest) {
func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
if err := pr.LoadIssue(); err != nil {
log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err)
return
@@ -92,9 +110,16 @@ func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest) {
IssueID: pr.Issue.ID,
NotificationAuthorID: pr.Issue.PosterID,
})
for _, mention := range mentions {
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: pr.Issue.PosterID,
ReceiverID: mention.ID,
})
}
}
func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, c *models.Comment) {
func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, c *models.Comment, mentions []*models.User) {
var opts = issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: r.Reviewer.ID,
@@ -103,6 +128,28 @@ func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r
opts.CommentID = c.ID
}
_ = ns.issueQueue.Push(opts)
for _, mention := range mentions {
var opts = issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: r.Reviewer.ID,
ReceiverID: mention.ID,
}
if c != nil {
opts.CommentID = c.ID
}
_ = ns.issueQueue.Push(opts)
}
}
func (ns *notificationService) NotifyPullRequestCodeComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) {
for _, mention := range mentions {
_ = ns.issueQueue.Push(issueNotificationOpts{
IssueID: pr.Issue.ID,
NotificationAuthorID: c.Poster.ID,
CommentID: c.ID,
ReceiverID: mention.ID,
})
}
}
func (ns *notificationService) NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) {

View File

@@ -249,7 +249,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode
}
}
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
if err := issue.LoadRepo(); err != nil {
log.Error("issue.LoadRepo: %v", err)
return
@@ -271,7 +271,7 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
}
}
func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mentions []*models.User) {
if err := pull.LoadIssue(); err != nil {
log.Error("pull.LoadIssue: %v", err)
return
@@ -387,7 +387,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme
}
func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
issue *models.Issue, comment *models.Comment) {
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
mode, _ := models.AccessLevel(doer, repo)
var err error
@@ -639,7 +639,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User,
}
}
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
var reviewHookType models.HookEventType
switch review.Type {

View File

@@ -38,6 +38,7 @@ var KnownPublicEntries = []string{
"js",
"serviceworker.js",
"vendor",
"favicon.ico",
}
// Custom implements the macaron static handler for serving custom assets.

View File

@@ -149,6 +149,11 @@ func (q *PersistableChannelUniqueQueue) Has(data Data) (bool, error) {
if err != nil || has {
return has, err
}
q.lock.Lock()
defer q.lock.Unlock()
if q.internal == nil {
return false, nil
}
return q.internal.(UniqueQueue).Has(data)
}

View File

@@ -29,6 +29,13 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
opts.DefaultBranch = setting.Repository.DefaultBranch
}
// Check if label template exist
if len(opts.IssueLabels) > 0 {
if _, err := models.GetLabelTemplateFile(opts.IssueLabels); err != nil {
return nil, err
}
}
repo := &models.Repository{
OwnerID: u.ID,
Owner: u,
@@ -47,6 +54,8 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
TrustModel: opts.TrustModel,
}
var rollbackRepo *models.Repository
if err := models.WithTx(func(ctx models.DBContext) error {
if err := models.CreateRepository(ctx, doer, u, repo, false); err != nil {
return err
@@ -85,9 +94,8 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("InitializeLabels: %v", err)
}
}
@@ -96,13 +104,18 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil {
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
}
return nil
}); err != nil {
if rollbackRepo != nil {
if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}
return nil, err
}

View File

@@ -5,6 +5,7 @@
package repository
import (
"context"
"fmt"
"path"
"strings"
@@ -41,7 +42,7 @@ func WikiRemoteURL(remote string) string {
}
// MigrateRepositoryGitData starts migrating git related data after created migrating repository
func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opts migration.MigrateOptions) (*models.Repository, error) {
func MigrateRepositoryGitData(ctx context.Context, u *models.User, repo *models.Repository, opts migration.MigrateOptions) (*models.Repository, error) {
repoPath := models.RepoPath(u.Name, opts.RepoName)
if u.IsOrganization() {
@@ -61,7 +62,7 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err)
}
if err = git.Clone(opts.CloneAddr, repoPath, git.CloneRepoOptions{
if err = git.CloneWithContext(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
Timeout: migrateTimeout,
@@ -77,7 +78,7 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err)
}
if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{
if err = git.CloneWithContext(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
Timeout: migrateTimeout,

View File

@@ -62,6 +62,11 @@ func InitDBConfig() {
sec := Cfg.Section("database")
Database.Type = sec.Key("DB_TYPE").String()
defaultCharset := "utf8"
Database.UseMySQL = false
Database.UseSQLite3 = false
Database.UsePostgreSQL = false
Database.UseMSSQL = false
switch Database.Type {
case "sqlite3":
Database.UseSQLite3 = true

View File

@@ -771,7 +771,7 @@ func NewContext() {
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true)
OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true)
PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("argon2")
PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2")
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)

View File

@@ -31,22 +31,10 @@ func (s *Storage) MapTo(v interface{}) error {
return nil
}
func getStorage(name, typ string, overrides ...*ini.Section) Storage {
func getStorage(name, typ string, targetSec *ini.Section) Storage {
const sectionName = "storage"
sec := Cfg.Section(sectionName)
if len(overrides) == 0 {
overrides = []*ini.Section{
Cfg.Section(sectionName + "." + typ),
Cfg.Section(sectionName + "." + name),
}
}
var storage Storage
storage.Type = sec.Key("STORAGE_TYPE").MustString(typ)
storage.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false)
// Global Defaults
sec.Key("MINIO_ENDPOINT").MustString("localhost:9000")
sec.Key("MINIO_ACCESS_KEY_ID").MustString("")
@@ -55,17 +43,37 @@ func getStorage(name, typ string, overrides ...*ini.Section) Storage {
sec.Key("MINIO_LOCATION").MustString("us-east-1")
sec.Key("MINIO_USE_SSL").MustBool(false)
storage.Section = sec
var storage Storage
storage.Section = targetSec
storage.Type = typ
overrides := make([]*ini.Section, 0, 3)
nameSec, err := Cfg.GetSection(sectionName + "." + name)
if err == nil {
overrides = append(overrides, nameSec)
}
typeSec, err := Cfg.GetSection(sectionName + "." + typ)
if err == nil {
overrides = append(overrides, typeSec)
nextType := typeSec.Key("STORAGE_TYPE").String()
if len(nextType) > 0 {
storage.Type = nextType // Support custom STORAGE_TYPE
}
}
overrides = append(overrides, sec)
for _, override := range overrides {
for _, key := range storage.Section.Keys() {
if !override.HasKey(key.Name()) {
_, _ = override.NewKey(key.Name(), key.Value())
for _, key := range override.Keys() {
if !targetSec.HasKey(key.Name()) {
_, _ = targetSec.NewKey(key.Name(), key.Value())
}
}
storage.ServeDirect = override.Key("SERVE_DIRECT").MustBool(false)
storage.Section = override
if len(storage.Type) == 0 {
storage.Type = override.Key("STORAGE_TYPE").String()
}
}
storage.ServeDirect = storage.Section.Key("SERVE_DIRECT").MustBool(false)
// Specific defaults
storage.Path = storage.Section.Key("PATH").MustString(filepath.Join(AppDataPath, name))

View File

@@ -0,0 +1,197 @@
// Copyright 2020 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 setting
import (
"testing"
"github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
)
func Test_getStorageCustomType(t *testing.T) {
iniStr := `
[attachment]
STORAGE_TYPE = my_minio
MINIO_BUCKET = gitea-attachment
[storage.my_minio]
STORAGE_TYPE = minio
MINIO_ENDPOINT = my_minio:9000
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
assert.EqualValues(t, "my_minio:9000", storage.Section.Key("MINIO_ENDPOINT").String())
assert.EqualValues(t, "gitea-attachment", storage.Section.Key("MINIO_BUCKET").String())
}
func Test_getStorageNameSectionOverridesTypeSection(t *testing.T) {
iniStr := `
[attachment]
STORAGE_TYPE = minio
[storage.attachments]
MINIO_BUCKET = gitea-attachment
[storage.minio]
MINIO_BUCKET = gitea
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
assert.EqualValues(t, "gitea-attachment", storage.Section.Key("MINIO_BUCKET").String())
}
func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) {
iniStr := `
[attachment]
STORAGE_TYPE = minio
[storage.minio]
MINIO_BUCKET = gitea-minio
[storage]
MINIO_BUCKET = gitea
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
assert.EqualValues(t, "gitea-minio", storage.Section.Key("MINIO_BUCKET").String())
}
func Test_getStorageSpecificOverridesStorage(t *testing.T) {
iniStr := `
[attachment]
STORAGE_TYPE = minio
MINIO_BUCKET = gitea-attachment
[storage.attachments]
MINIO_BUCKET = gitea
[storage]
STORAGE_TYPE = local
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
assert.EqualValues(t, "gitea-attachment", storage.Section.Key("MINIO_BUCKET").String())
}
func Test_getStorageGetDefaults(t *testing.T) {
Cfg, _ = ini.Load([]byte(""))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "gitea", storage.Section.Key("MINIO_BUCKET").String())
}
func Test_getStorageMultipleName(t *testing.T) {
iniStr := `
[lfs]
MINIO_BUCKET = gitea-lfs
[attachment]
MINIO_BUCKET = gitea-attachment
[storage]
MINIO_BUCKET = gitea-storage
`
Cfg, _ = ini.Load([]byte(iniStr))
{
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "gitea-attachment", storage.Section.Key("MINIO_BUCKET").String())
}
{
sec := Cfg.Section("lfs")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("lfs", storageType, sec)
assert.EqualValues(t, "gitea-lfs", storage.Section.Key("MINIO_BUCKET").String())
}
{
sec := Cfg.Section("avatar")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("avatars", storageType, sec)
assert.EqualValues(t, "gitea-storage", storage.Section.Key("MINIO_BUCKET").String())
}
}
func Test_getStorageUseOtherNameAsType(t *testing.T) {
iniStr := `
[attachment]
STORAGE_TYPE = lfs
[storage.lfs]
MINIO_BUCKET = gitea-storage
`
Cfg, _ = ini.Load([]byte(iniStr))
{
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "gitea-storage", storage.Section.Key("MINIO_BUCKET").String())
}
{
sec := Cfg.Section("lfs")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("lfs", storageType, sec)
assert.EqualValues(t, "gitea-storage", storage.Section.Key("MINIO_BUCKET").String())
}
}
func Test_getStorageInheritStorageType(t *testing.T) {
iniStr := `
[storage]
STORAGE_TYPE = minio
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
}
func Test_getStorageInheritNameSectionType(t *testing.T) {
iniStr := `
[storage.attachments]
STORAGE_TYPE = minio
`
Cfg, _ = ini.Load([]byte(iniStr))
sec := Cfg.Section("attachment")
storageType := sec.Key("STORAGE_TYPE").MustString("")
storage := getStorage("attachments", storageType, sec)
assert.EqualValues(t, "minio", storage.Type)
}

View File

@@ -196,13 +196,17 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
// Listen starts a SSH server listens on given port.
func Listen(host string, port int, ciphers []string, keyExchanges []string, macs []string) {
// TODO: Handle ciphers, keyExchanges, and macs
srv := ssh.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
PublicKeyHandler: publicKeyHandler,
Handler: sessionHandler,
ServerConfigCallback: func(ctx ssh.Context) *gossh.ServerConfig {
config := &gossh.ServerConfig{}
config.KeyExchanges = keyExchanges
config.MACs = macs
config.Ciphers = ciphers
return config
},
// We need to explicitly disable the PtyCallback so text displays
// properly.
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {

View File

@@ -105,7 +105,7 @@ type CreateRepoOption struct {
Description string `json:"description" binding:"MaxSize(255)"`
// Whether the repository is private
Private bool `json:"private"`
// Issue Label set to use
// Label-Set to use
IssueLabels string `json:"issue_labels"`
// Whether the repository should be auto-intialized?
AutoInit bool `json:"auto_init"`

View File

@@ -5,6 +5,7 @@
package task
import (
"context"
"errors"
"fmt"
"strings"
@@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/migrations"
migration "code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@@ -82,11 +84,6 @@ func runMigrateTask(t *models.Task) (err error) {
if err = t.LoadOwner(); err != nil {
return
}
t.StartTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusRunning
if err = t.UpdateCols("start_time", "status"); err != nil {
return
}
var opts *migration.MigrateOptions
opts, err = t.MigrateConfig()
@@ -96,7 +93,20 @@ func runMigrateTask(t *models.Task) (err error) {
opts.MigrateToRepoID = t.RepoID
var repo *models.Repository
repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), t.Doer, t.Owner.Name, *opts)
ctx, cancel := context.WithCancel(graceful.GetManager().ShutdownContext())
defer cancel()
pm := process.GetManager()
pid := pm.Add(fmt.Sprintf("MigrateTask: %s/%s", t.Owner.Name, opts.RepoName), cancel)
defer pm.Remove(pid)
t.StartTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusRunning
if err = t.UpdateCols("start_time", "status"); err != nil {
return
}
repo, err = migrations.MigrateRepository(ctx, t.Doer, t.Owner.Name, *opts)
if err == nil {
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name)
return

View File

@@ -7,6 +7,7 @@ package timeutil
import (
"fmt"
"html/template"
"math"
"strings"
"time"
@@ -25,7 +26,11 @@ const (
Year = 12 * Month
)
func computeTimeDiff(diff int64, lang string) (int64, string) {
func round(s float64) int64 {
return int64(math.Round(s))
}
func computeTimeDiffFloor(diff int64, lang string) (int64, string) {
diffStr := ""
switch {
case diff <= 0:
@@ -83,6 +88,94 @@ func computeTimeDiff(diff int64, lang string) (int64, string) {
return diff, diffStr
}
func computeTimeDiff(diff int64, lang string) (int64, string) {
diffStr := ""
switch {
case diff <= 0:
diff = 0
diffStr = i18n.Tr(lang, "tool.now")
case diff < 2:
diff = 0
diffStr = i18n.Tr(lang, "tool.1s")
case diff < 1*Minute:
diffStr = i18n.Tr(lang, "tool.seconds", diff)
diff = 0
case diff < Minute+Minute/2:
diff -= 1 * Minute
diffStr = i18n.Tr(lang, "tool.1m")
case diff < 1*Hour:
minutes := round(float64(diff) / Minute)
if minutes > 1 {
diffStr = i18n.Tr(lang, "tool.minutes", minutes)
} else {
diffStr = i18n.Tr(lang, "tool.1m")
}
diff -= diff / Minute * Minute
case diff < Hour+Hour/2:
diff -= 1 * Hour
diffStr = i18n.Tr(lang, "tool.1h")
case diff < 1*Day:
hours := round(float64(diff) / Hour)
if hours > 1 {
diffStr = i18n.Tr(lang, "tool.hours", hours)
} else {
diffStr = i18n.Tr(lang, "tool.1h")
}
diff -= diff / Hour * Hour
case diff < Day+Day/2:
diff -= 1 * Day
diffStr = i18n.Tr(lang, "tool.1d")
case diff < 1*Week:
days := round(float64(diff) / Day)
if days > 1 {
diffStr = i18n.Tr(lang, "tool.days", days)
} else {
diffStr = i18n.Tr(lang, "tool.1d")
}
diff -= diff / Day * Day
case diff < Week+Week/2:
diff -= 1 * Week
diffStr = i18n.Tr(lang, "tool.1w")
case diff < 1*Month:
weeks := round(float64(diff) / Week)
if weeks > 1 {
diffStr = i18n.Tr(lang, "tool.weeks", weeks)
} else {
diffStr = i18n.Tr(lang, "tool.1w")
}
diff -= diff / Week * Week
case diff < 1*Month+Month/2:
diff -= 1 * Month
diffStr = i18n.Tr(lang, "tool.1mon")
case diff < 1*Year:
months := round(float64(diff) / Month)
if months > 1 {
diffStr = i18n.Tr(lang, "tool.months", months)
} else {
diffStr = i18n.Tr(lang, "tool.1mon")
}
diff -= diff / Month * Month
case diff < Year+Year/2:
diff -= 1 * Year
diffStr = i18n.Tr(lang, "tool.1y")
default:
years := round(float64(diff) / Year)
if years > 1 {
diffStr = i18n.Tr(lang, "tool.years", years)
} else {
diffStr = i18n.Tr(lang, "tool.1y")
}
diff -= (diff / Year) * Year
}
return diff, diffStr
}
// MinutesToFriendly returns a user friendly string with number of minutes
// converted to hours and minutes.
func MinutesToFriendly(minutes int, lang string) string {
@@ -111,7 +204,7 @@ func timeSincePro(then, now time.Time, lang string) string {
break
}
diff, diffStr = computeTimeDiff(diff, lang)
diff, diffStr = computeTimeDiffFloor(diff, lang)
timeStr += ", " + diffStr
}
return strings.TrimPrefix(timeStr, ", ")

View File

@@ -5,6 +5,7 @@
package timeutil
import (
"fmt"
"os"
"testing"
"time"
@@ -47,27 +48,39 @@ func TestTimeSince(t *testing.T) {
// test that each diff in `diffs` yields the expected string
test := func(expected string, diffs ...time.Duration) {
for _, diff := range diffs {
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
}
t.Run(expected, func(t *testing.T) {
for _, diff := range diffs {
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
}
})
}
test("1 second", time.Second, time.Second+50*time.Millisecond)
test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond)
test("1 minute", time.Minute, time.Minute+30*time.Second)
test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second)
test("1 hour", time.Hour, time.Hour+30*time.Minute)
test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute)
test("1 day", DayDur, DayDur+12*time.Hour)
test("2 days", 2*DayDur, 2*DayDur+12*time.Hour)
test("1 minute", time.Minute, time.Minute+29*time.Second)
test("2 minutes", 2*time.Minute, time.Minute+30*time.Second)
test("2 minutes", 2*time.Minute, 2*time.Minute+29*time.Second)
test("1 hour", time.Hour, time.Hour+29*time.Minute)
test("2 hours", 2*time.Hour, time.Hour+30*time.Minute)
test("2 hours", 2*time.Hour, 2*time.Hour+29*time.Minute)
test("3 hours", 3*time.Hour, 2*time.Hour+30*time.Minute)
test("1 day", DayDur, DayDur+11*time.Hour)
test("2 days", 2*DayDur, DayDur+12*time.Hour)
test("2 days", 2*DayDur, 2*DayDur+11*time.Hour)
test("3 days", 3*DayDur, 2*DayDur+12*time.Hour)
test("1 week", WeekDur, WeekDur+3*DayDur)
test("2 weeks", 2*WeekDur, WeekDur+4*DayDur)
test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur)
test("1 month", MonthDur, MonthDur+15*DayDur)
test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur)
test("1 year", YearDur, YearDur+6*MonthDur)
test("2 years", 2*YearDur, 2*YearDur+6*MonthDur)
test("3 weeks", 3*WeekDur, 2*WeekDur+4*DayDur)
test("1 month", MonthDur, MonthDur+14*DayDur)
test("2 months", 2*MonthDur, MonthDur+15*DayDur)
test("2 months", 2*MonthDur, 2*MonthDur+14*DayDur)
test("1 year", YearDur, YearDur+5*MonthDur)
test("2 years", 2*YearDur, YearDur+6*MonthDur)
test("2 years", 2*YearDur, 2*YearDur+5*MonthDur)
test("3 years", 3*YearDur, 2*YearDur+6*MonthDur)
}
func TestTimeSincePro(t *testing.T) {
@@ -114,11 +127,11 @@ func TestHtmlTimeSince(t *testing.T) {
}
test("1 second", time.Second)
test("3 minutes", 3*time.Minute+5*time.Second)
test("1 day", DayDur+18*time.Hour)
test("1 week", WeekDur+6*DayDur)
test("3 months", 3*MonthDur+3*WeekDur)
test("1 day", DayDur+11*time.Hour)
test("1 week", WeekDur+3*DayDur)
test("3 months", 3*MonthDur+2*WeekDur)
test("2 years", 2*YearDur)
test("3 years", 3*YearDur+11*MonthDur+4*WeekDur)
test("3 years", 2*YearDur+11*MonthDur+4*WeekDur)
}
func TestComputeTimeDiff(t *testing.T) {
@@ -126,26 +139,35 @@ func TestComputeTimeDiff(t *testing.T) {
// computeTimeDiff(base + offset) == (offset, str)
test := func(base int64, str string, offsets ...int64) {
for _, offset := range offsets {
diff, diffStr := computeTimeDiff(base+offset, "en")
assert.Equal(t, offset, diff)
assert.Equal(t, str, diffStr)
t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) {
diff, diffStr := computeTimeDiff(base+offset, "en")
assert.Equal(t, offset, diff)
assert.Equal(t, str, diffStr)
})
}
}
test(0, "now", 0)
test(1, "1 second", 0)
test(2, "2 seconds", 0)
test(Minute, "1 minute", 0, 1, 30, Minute-1)
test(2*Minute, "2 minutes", 0, Minute-1)
test(Hour, "1 hour", 0, 1, Hour-1)
test(5*Hour, "5 hours", 0, Hour-1)
test(Day, "1 day", 0, 1, Day-1)
test(5*Day, "5 days", 0, Day-1)
test(Week, "1 week", 0, 1, Week-1)
test(3*Week, "3 weeks", 0, 4*Day+25000)
test(Month, "1 month", 0, 1, Month-1)
test(10*Month, "10 months", 0, Month-1)
test(Year, "1 year", 0, Year-1)
test(3*Year, "3 years", 0, Year-1)
test(Minute, "1 minute", 0, 1, 29)
test(Minute, "2 minutes", 30, Minute-1)
test(2*Minute, "2 minutes", 0, 29)
test(2*Minute, "3 minutes", 30, Minute-1)
test(Hour, "1 hour", 0, 1, 29*Minute)
test(Hour, "2 hours", 30*Minute, Hour-1)
test(5*Hour, "5 hours", 0, 29*Minute)
test(Day, "1 day", 0, 1, 11*Hour)
test(Day, "2 days", 12*Hour, Day-1)
test(5*Day, "5 days", 0, 11*Hour)
test(Week, "1 week", 0, 1, 3*Day)
test(Week, "2 weeks", 4*Day, Week-1)
test(3*Week, "3 weeks", 0, 3*Day)
test(Month, "1 month", 0, 1)
test(Month, "2 months", 16*Day, Month-1)
test(10*Month, "10 months", 0, 13*Day)
test(Year, "1 year", 0, 179*Day)
test(Year, "2 years", 180*Day, Year-1)
test(3*Year, "3 years", 0, 179*Day)
}
func TestMinutesToFriendly(t *testing.T) {

View File

@@ -17,11 +17,24 @@ import (
type (
// FeishuPayload represents
FeishuPayload struct {
Title string `json:"title"`
Text string `json:"text"`
MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive
Content struct {
Text string `json:"text"`
} `json:"content"`
}
)
func newFeishuTextPayload(text string) *FeishuPayload {
return &FeishuPayload{
MsgType: "text",
Content: struct {
Text string `json:"text"`
}{
Text: text,
},
}
}
// SetSecret sets the Feishu secret
func (f *FeishuPayload) SetSecret(_ string) {}
@@ -42,34 +55,25 @@ var (
func (f *FeishuPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
// created tag/branch
refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
text := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
return &FeishuPayload{
Text: title,
Title: title,
}, nil
return newFeishuTextPayload(text), nil
}
// Delete implements PayloadConvertor Delete method
func (f *FeishuPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
// created tag/branch
refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
text := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
return &FeishuPayload{
Text: title,
Title: title,
}, nil
return newFeishuTextPayload(text), nil
}
// Fork implements PayloadConvertor Fork method
func (f *FeishuPayload) Fork(p *api.ForkPayload) (api.Payloader, error) {
title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
text := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
return &FeishuPayload{
Text: title,
Title: title,
}, nil
return newFeishuTextPayload(text), nil
}
// Push implements PayloadConvertor Push method
@@ -79,9 +83,7 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
commitDesc string
)
title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
var text string
var text = fmt.Sprintf("[%s:%s] %s\n", p.Repo.FullName, branchName, commitDesc)
// for each commit, generate attachment text
for i, commit := range p.Commits {
var authorName string
@@ -96,40 +98,28 @@ func (f *FeishuPayload) Push(p *api.PushPayload) (api.Payloader, error) {
}
}
return &FeishuPayload{
Text: text,
Title: title,
}, nil
return newFeishuTextPayload(text), nil
}
// Issue implements PayloadConvertor Issue method
func (f *FeishuPayload) Issue(p *api.IssuePayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getIssuesPayloadInfo(p, noneLinkFormatter, true)
return &FeishuPayload{
Text: text + "\r\n\r\n" + attachmentText,
Title: issueTitle,
}, nil
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil
}
// IssueComment implements PayloadConvertor IssueComment method
func (f *FeishuPayload) IssueComment(p *api.IssueCommentPayload) (api.Payloader, error) {
text, issueTitle, _ := getIssueCommentPayloadInfo(p, noneLinkFormatter, true)
return &FeishuPayload{
Text: text + "\r\n\r\n" + p.Comment.Body,
Title: issueTitle,
}, nil
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + p.Comment.Body), nil
}
// PullRequest implements PayloadConvertor PullRequest method
func (f *FeishuPayload) PullRequest(p *api.PullRequestPayload) (api.Payloader, error) {
text, issueTitle, attachmentText, _ := getPullRequestPayloadInfo(p, noneLinkFormatter, true)
return &FeishuPayload{
Text: text + "\r\n\r\n" + attachmentText,
Title: issueTitle,
}, nil
return newFeishuTextPayload(issueTitle + "\r\n" + text + "\r\n\r\n" + attachmentText), nil
}
// Review implements PayloadConvertor Review method
@@ -147,28 +137,19 @@ func (f *FeishuPayload) Review(p *api.PullRequestPayload, event models.HookEvent
}
return &FeishuPayload{
Text: title + "\r\n\r\n" + text,
Title: title,
}, nil
return newFeishuTextPayload(title + "\r\n\r\n" + text), nil
}
// Repository implements PayloadConvertor Repository method
func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, error) {
var title string
var text string
switch p.Action {
case api.HookRepoCreated:
title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
return &FeishuPayload{
Text: title,
Title: title,
}, nil
text = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
return newFeishuTextPayload(text), nil
case api.HookRepoDeleted:
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
return &FeishuPayload{
Title: title,
Text: title,
}, nil
text = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
return newFeishuTextPayload(text), nil
}
return nil, nil
@@ -178,10 +159,7 @@ func (f *FeishuPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err
func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
return &FeishuPayload{
Text: text,
Title: text,
}, nil
return newFeishuTextPayload(text), nil
}
// GetFeishuPayload converts a ding talk webhook into a FeishuPayload

View File

@@ -5,6 +5,8 @@
package admin
import (
"net/url"
"strconv"
"strings"
"code.gitea.io/gitea/models"
@@ -71,6 +73,8 @@ func UnadoptedRepos(ctx *context.Context) {
opts.Page = 1
}
ctx.Data["CurrentPage"] = opts.Page
doSearch := ctx.QueryBool("search")
ctx.Data["search"] = doSearch
@@ -79,6 +83,7 @@ func UnadoptedRepos(ctx *context.Context) {
if !doSearch {
pager := context.NewPagination(0, opts.PageSize, opts.Page, 5)
pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "search", "search")
ctx.Data["Page"] = pager
ctx.HTML(200, tplUnadoptedRepos)
return
@@ -92,6 +97,7 @@ func UnadoptedRepos(ctx *context.Context) {
ctx.Data["Dirs"] = repoNames
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "search", "search")
ctx.Data["Page"] = pager
ctx.HTML(200, tplUnadoptedRepos)
}
@@ -100,6 +106,9 @@ func UnadoptedRepos(ctx *context.Context) {
func AdoptOrDeleteRepository(ctx *context.Context) {
dir := ctx.Query("id")
action := ctx.Query("action")
page := ctx.QueryInt("page")
q := ctx.Query("q")
dirSplit := strings.SplitN(dir, "/", 2)
if len(dirSplit) != 2 {
ctx.Redirect(setting.AppSubURL + "/admin/repos")
@@ -141,5 +150,5 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir))
}
ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted")
ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted?search=true&q=" + url.QueryEscape(q) + "&page=" + strconv.Itoa(page))
}

View File

@@ -17,19 +17,28 @@ import (
"code.gitea.io/gitea/routers/api/v1/utils"
)
func listUserOrgs(ctx *context.APIContext, u *models.User, all bool) {
if err := u.GetOrganizations(&models.SearchOrganizationsOptions{
ListOptions: utils.GetListOptions(ctx),
All: all,
}); err != nil {
ctx.Error(http.StatusInternalServerError, "GetOrganizations", err)
func listUserOrgs(ctx *context.APIContext, u *models.User) {
listOptions := utils.GetListOptions(ctx)
showPrivate := ctx.IsSigned && (ctx.User.IsAdmin || ctx.User.ID == u.ID)
orgs, err := models.GetOrgsByUserID(u.ID, showPrivate)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetOrgsByUserID", err)
return
}
maxResults := len(orgs)
apiOrgs := make([]*api.Organization, len(u.Orgs))
for i := range u.Orgs {
apiOrgs[i] = convert.ToOrganization(u.Orgs[i])
orgs = utils.PaginateUserSlice(orgs, listOptions.Page, listOptions.PageSize)
apiOrgs := make([]*api.Organization, len(orgs))
for i := range orgs {
apiOrgs[i] = convert.ToOrganization(orgs[i])
}
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", maxResults))
ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
ctx.JSON(http.StatusOK, &apiOrgs)
}
@@ -53,7 +62,7 @@ func ListMyOrgs(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/OrganizationList"
listUserOrgs(ctx, ctx.User, true)
listUserOrgs(ctx, ctx.User)
}
// ListUserOrgs list user's orgs
@@ -85,7 +94,7 @@ func ListUserOrgs(ctx *context.APIContext) {
if ctx.Written() {
return
}
listUserOrgs(ctx, u, ctx.User != nil && (ctx.User.IsAdmin || ctx.User.ID == u.ID))
listUserOrgs(ctx, u)
}
// GetAll return list of all public organizations

View File

@@ -244,7 +244,7 @@ type combinedCommitStatus struct {
// GetCombinedCommitStatusByRef returns the combined status for any given commit hash
func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/commits/{ref}/statuses repository repoGetCombinedStatusByRef
// swagger:operation GET /repos/{owner}/{repo}/commits/{ref}/status repository repoGetCombinedStatusByRef
// ---
// summary: Get a commit's combined status, by branch/tag/commit reference
// produces:
@@ -272,7 +272,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
// required: false
// responses:
// "200":
// "$ref": "#/responses/Status"
// "$ref": "#/responses/CombinedStatus"
// "400":
// "$ref": "#/responses/error"
@@ -292,7 +292,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
}
if len(statuses) == 0 {
ctx.Status(http.StatusOK)
ctx.JSON(http.StatusOK, &api.CombinedStatus{})
return
}

View File

@@ -93,7 +93,12 @@ func Transfer(ctx *context.APIContext, opts api.TransferRepoOption) {
}
}
if err = repo_service.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository, teams); err != nil {
if err = repo_service.StartRepositoryTransfer(ctx.User, newOwner, ctx.Repo.Repository, teams); err != nil {
if models.IsErrCancelled(err) {
ctx.Error(http.StatusForbidden, "transfer", "user has no right to create repo for new owner")
return
}
ctx.InternalServerError(err)
return
}

View File

@@ -309,3 +309,10 @@ type swaggerLanguageStatistics struct {
// in: body
Body map[string]int64 `json:"body"`
}
// CombinedStatus
// swagger:response CombinedStatus
type swaggerCombinedStatus struct {
// in: body
Body api.CombinedStatus `json:"body"`
}

View File

@@ -66,3 +66,22 @@ func GetListOptions(ctx *context.APIContext) models.ListOptions {
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
}
}
// PaginateUserSlice cut a slice of Users as per pagination options
// TODO: make it generic
func PaginateUserSlice(items []*models.User, page, pageSize int) []*models.User {
if page != 0 {
page--
}
if page*pageSize >= len(items) {
return items[len(items):]
}
items = items[page*pageSize:]
if len(items) > pageSize {
return items[:pageSize]
}
return items
}

View File

@@ -110,13 +110,15 @@ func InitLocales() {
}
}
i18n.I18n(i18n.Options{
SubURL: setting.AppSubURL,
Files: localFiles,
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: false,
CookieDomain: setting.SessionConfig.Domain,
SubURL: setting.AppSubURL,
Files: localFiles,
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: false,
CookieHttpOnly: true,
Secure: setting.SessionConfig.Secure,
CookieDomain: setting.SessionConfig.Domain,
})
}
@@ -131,12 +133,19 @@ func GlobalInit(ctx context.Context) {
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
log.Trace("Custom path: %s", setting.CustomPath)
log.Trace("Log path: %s", setting.LogRootPath)
checkRunMode()
// Setup i18n
InitLocales()
NewServices()
if setting.EnableSQLite3 {
log.Info("SQLite3 Supported")
} else if setting.Database.UseSQLite3 {
log.Fatal("SQLite3 is set in settings but NOT Supported")
}
if setting.InstallLock {
highlight.NewContext()
external.RegisterParsers()
@@ -170,10 +179,6 @@ func GlobalInit(ctx context.Context) {
}
eventsource.GetManager().Init()
}
if setting.EnableSQLite3 {
log.Info("SQLite3 Supported")
}
checkRunMode()
if err := repo_migrations.Init(); err != nil {
log.Fatal("Failed to initialize repository migrations: %v", err)

View File

@@ -9,7 +9,6 @@ import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
@@ -502,7 +501,7 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
} else {
treePath := filepath.Dir(ctx.Repo.TreePath)
treePath := path.Dir(ctx.Repo.TreePath)
if treePath == "." {
treePath = "" // the file deleted was in the root, so we return the user to the root directory
}
@@ -805,10 +804,10 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
// see if the tree has entries
if tree, err := commit.SubTree(treePath); err != nil {
// failed to get tree, going up a dir
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
return GetClosestParentWithFiles(path.Dir(treePath), commit)
} else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
// no files in this dir, going up a dir
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
return GetClosestParentWithFiles(path.Dir(treePath), commit)
}
return treePath
}

View File

@@ -725,6 +725,14 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [
ctx.Data[ctxDataKey] = templateBody
labelIDs := make([]string, 0, len(meta.Labels))
if repoLabels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, "", models.ListOptions{}); err == nil {
ctx.Data["Labels"] = repoLabels
if ctx.Repo.Owner.IsOrganization() {
if orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}); err == nil {
ctx.Data["OrgLabels"] = orgLabels
repoLabels = append(repoLabels, orgLabels...)
}
}
for _, metaLabel := range meta.Labels {
for _, repoLabel := range repoLabels {
if strings.EqualFold(repoLabel.Name, metaLabel) {
@@ -734,7 +742,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [
}
}
}
ctx.Data["Labels"] = repoLabels
}
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
@@ -1105,7 +1112,7 @@ func ViewIssue(ctx *context.Context) {
iw.IssueID = issue.ID
iw.IsWatching, err = models.CheckIssueWatch(ctx.User, issue)
if err != nil {
ctx.InternalServerError(err)
ctx.ServerError("CheckIssueWatch", err)
return
}
}

View File

@@ -353,7 +353,7 @@ func DeleteProjectBoard(ctx *context.Context) {
pb, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.InternalServerError(err)
ctx.ServerError("GetProjectBoard", err)
return
}
if pb.ProjectID != ctx.ParamsInt64(":id") {
@@ -443,7 +443,7 @@ func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitle
board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.InternalServerError(err)
ctx.ServerError("GetProjectBoard", err)
return
}
if board.ProjectID != ctx.ParamsInt64(":id") {

View File

@@ -696,11 +696,11 @@ func UpdatePullRequest(ctx *context.Context) {
}
if err := issue.PullRequest.LoadBaseRepo(); err != nil {
ctx.InternalServerError(err)
ctx.ServerError("LoadBaseRepo", err)
return
}
if err := issue.PullRequest.LoadHeadRepo(); err != nil {
ctx.InternalServerError(err)
ctx.ServerError("LoadHeadRepo", err)
return
}

View File

@@ -475,9 +475,12 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Repo.GitRepo.Close()
ctx.Repo.GitRepo = nil
}
if err = repo_service.TransferOwnership(ctx.User, newOwner, repo, nil); err != nil {
if err = repo_service.StartRepositoryTransfer(ctx.User, newOwner, repo, nil); err != nil {
if models.IsErrRepoAlreadyExist(err) {
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
} else if models.IsErrCancelled(err) {
// this err msg is not translated, since it was introduced in a backport
ctx.RenderWithErr("user has no right to create repo for new owner", tplSettingsOptions, nil)
} else {
ctx.ServerError("TransferOwnership", err)
}

View File

@@ -10,6 +10,7 @@ import (
"encoding/base64"
"fmt"
gotemplate "html/template"
"io"
"io/ioutil"
"net/url"
"path"
@@ -420,7 +421,9 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
buf = make([]byte, 1024)
n, err = dataRc.Read(buf)
if err != nil {
// Error EOF don't mean there is an error, it just means we read to
// the end
if err != nil && err != io.EOF {
ctx.ServerError("Data", err)
return
}
@@ -689,7 +692,10 @@ func RenderUserCards(ctx *context.Context, total int, getter func(opts models.Li
pager := context.NewPagination(total, models.ItemsPerPage, page, 5)
ctx.Data["Page"] = pager
items, err := getter(models.ListOptions{Page: pager.Paginater.Current()})
items, err := getter(models.ListOptions{
Page: pager.Paginater.Current(),
PageSize: models.ItemsPerPage,
})
if err != nil {
ctx.ServerError("getter", err)
return
@@ -720,6 +726,7 @@ func Stars(ctx *context.Context) {
func Forks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repos.forks")
// TODO: need pagination
forks, err := ctx.Repo.Repository.GetForks(models.ListOptions{})
if err != nil {
ctx.ServerError("GetForks", err)

View File

@@ -121,11 +121,11 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
return
}
if !strings.HasPrefix(req.RequestURI, "/"+prefix) {
if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
return
}
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix)
rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
u, err := objStore.URL(rPath, path.Base(rPath))
if err != nil {
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
@@ -152,11 +152,11 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
return
}
if !strings.HasPrefix(req.RequestURI, "/"+prefix) {
if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
return
}
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix)
rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
rPath = strings.TrimPrefix(rPath, "/")
//If we have matched and access to release or issue
fr, err := objStore.Open(rPath)
@@ -223,10 +223,11 @@ func NewMacaron() *macaron.Macaron {
},
))
m.Use(templates.HTMLRenderer())
m.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
m.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
m.Use(templates.HTMLRenderer())
mailer.InitMailRender(templates.Mailer())
localeNames, err := options.Dir("locale")
@@ -246,13 +247,15 @@ func NewMacaron() *macaron.Macaron {
}
m.Use(i18n.I18n(i18n.Options{
SubURL: setting.AppSubURL,
Files: localFiles,
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: false,
CookieDomain: setting.SessionConfig.Domain,
SubURL: setting.AppSubURL,
Files: localFiles,
Langs: setting.Langs,
Names: setting.Names,
DefaultLang: "en-US",
Redirect: false,
CookieHttpOnly: true,
Secure: setting.SessionConfig.Secure,
CookieDomain: setting.SessionConfig.Domain,
}))
m.Use(cache.Cacher(cache.Options{
Adapter: setting.CacheService.Adapter,

View File

@@ -174,12 +174,12 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr())
log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
} else if models.IsErrEmailAlreadyUsed(err) {
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form)
log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr())
log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
} else if models.IsErrUserProhibitLogin(err) {
log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr())
log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(200, "user/auth/prohibit_login")
} else if models.IsErrUserInactive(err) {
@@ -187,7 +187,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(200, TplActivate)
} else {
log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr())
log.Info("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(200, "user/auth/prohibit_login")
}
@@ -570,8 +570,17 @@ func SignInOAuth(ctx *context.Context) {
return
}
err = oauth2.Auth(loginSource.Name, ctx.Req.Request, ctx.Resp)
if err != nil {
if err = oauth2.Auth(loginSource.Name, ctx.Req.Request, ctx.Resp); err != nil {
if strings.Contains(err.Error(), "no provider for ") {
if err = models.ResetOAuth2(); err != nil {
ctx.ServerError("SignIn", err)
return
}
if err = oauth2.Auth(loginSource.Name, ctx.Req.Request, ctx.Resp); err != nil {
ctx.ServerError("SignIn", err)
}
return
}
ctx.ServerError("SignIn", err)
}
// redirect is done in oauth2.Auth
@@ -737,6 +746,7 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["ShowRegistrationButton"] = false
@@ -788,6 +798,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["ShowRegistrationButton"] = false
@@ -872,6 +883,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["ShowRegistrationButton"] = false
@@ -1496,7 +1508,7 @@ func ResetPasswdPost(ctx *context.Context) {
}
u.HashPassword(passwd)
u.MustChangePassword = false
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil {
ctx.ServerError("UpdateUser", err)
return
}
@@ -1572,7 +1584,7 @@ func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form aut
u.HashPassword(form.Password)
u.MustChangePassword = false
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "salt"); err != nil {
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
ctx.ServerError("UpdateUser", err)
return
}

View File

@@ -567,6 +567,7 @@ func Issues(ctx *context.Context) {
FilterMode: filterMode,
IsPull: isPullList,
IsClosed: isShowClosed,
LabelIDs: opts.LabelIDs,
}
if len(repoIDs) > 0 {
userIssueStatsOpts.UserRepoIDs = repoIDs
@@ -586,6 +587,7 @@ func Issues(ctx *context.Context) {
IsPull: isPullList,
IsClosed: isShowClosed,
IssueIDs: issueIDsFromSearch,
LabelIDs: opts.LabelIDs,
}
if len(repoIDs) > 0 {
statsOpts.RepoIDs = repoIDs
@@ -608,6 +610,7 @@ func Issues(ctx *context.Context) {
IsPull: isPullList,
IsClosed: isShowClosed,
IssueIDs: issueIDsFromSearch,
LabelIDs: opts.LabelIDs,
})
if err != nil {
ctx.ServerError("GetUserIssueStats All", err)
@@ -659,6 +662,7 @@ func Issues(ctx *context.Context) {
ctx.Data["RepoIDs"] = repoIDs
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["TotalIssueCount"] = totalIssues
ctx.Data["SelectLabels"] = selectLabels
if isShowClosed {
ctx.Data["State"] = "closed"

View File

@@ -68,7 +68,7 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
return
}
ctx.User.HashPassword(form.Password)
if err := models.UpdateUserCols(ctx.User, "salt", "passwd"); err != nil {
if err := models.UpdateUserCols(ctx.User, "salt", "passwd_hash_algo", "passwd"); err != nil {
ctx.ServerError("UpdateUser", err)
return
}

View File

@@ -227,6 +227,9 @@ func Repos(ctx *context.Context) {
root := filepath.Join(models.UserPath(ctxUser.Name))
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if !info.IsDir() || path == root {

View File

@@ -22,8 +22,11 @@ func CreateIssueComment(doer *models.User, repo *models.Repository, issue *model
if err != nil {
return nil, err
}
notification.NotifyCreateIssueComment(doer, repo, issue, comment)
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content)
if err != nil {
return nil, err
}
notification.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
return comment, nil
}

View File

@@ -10,6 +10,7 @@ import (
"bytes"
"context"
"fmt"
"html"
"html/template"
"io"
"io/ioutil"
@@ -164,9 +165,9 @@ func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int
// escape a line's content or return <br> needed for copy/paste purposes
func getLineContent(content string) string {
if len(content) > 0 {
return content
return html.EscapeString(content)
}
return "\n"
return "<br>"
}
// DiffSection represents a section of a DiffFile.
@@ -181,6 +182,8 @@ var (
removedCodePrefix = []byte(`<span class="removed-code">`)
codeTagSuffix = []byte(`</span>`)
)
var unfinishedtagRegex = regexp.MustCompile(`<[^>]*$`)
var trailingSpanRegex = regexp.MustCompile(`<span\s*[[:alpha:]="]*?[>]?$`)
var entityRegex = regexp.MustCompile(`&[#]*?[0-9[:alpha:]]*$`)
@@ -195,10 +198,218 @@ func shouldWriteInline(diff diffmatchpatch.Diff, lineType DiffLineType) bool {
return false
}
func fixupBrokenSpans(diffs []diffmatchpatch.Diff) []diffmatchpatch.Diff {
// Create a new array to store our fixed up blocks
fixedup := make([]diffmatchpatch.Diff, 0, len(diffs))
// semantically label some numbers
const insert, delete, equal = 0, 1, 2
// record the positions of the last type of each block in the fixedup blocks
last := []int{-1, -1, -1}
operation := []diffmatchpatch.Operation{diffmatchpatch.DiffInsert, diffmatchpatch.DiffDelete, diffmatchpatch.DiffEqual}
// create a writer for insert and deletes
toWrite := []strings.Builder{
{},
{},
}
// make some flags for insert and delete
unfinishedTag := []bool{false, false}
unfinishedEnt := []bool{false, false}
// store stores the provided text in the writer for the typ
store := func(text string, typ int) {
(&(toWrite[typ])).WriteString(text)
}
// hasStored returns true if there is stored content
hasStored := func(typ int) bool {
return (&toWrite[typ]).Len() > 0
}
// stored will return that content
stored := func(typ int) string {
return (&toWrite[typ]).String()
}
// empty will empty the stored content
empty := func(typ int) {
(&toWrite[typ]).Reset()
}
// pop will remove the stored content appending to a diff block for that typ
pop := func(typ int, fixedup []diffmatchpatch.Diff) []diffmatchpatch.Diff {
if hasStored(typ) {
if last[typ] > last[equal] {
fixedup[last[typ]].Text += stored(typ)
} else {
fixedup = append(fixedup, diffmatchpatch.Diff{
Type: operation[typ],
Text: stored(typ),
})
}
empty(typ)
}
return fixedup
}
// Now we walk the provided diffs and check the type of each block in turn
for _, diff := range diffs {
typ := delete // flag for handling insert or delete typs
switch diff.Type {
case diffmatchpatch.DiffEqual:
// First check if there is anything stored
if hasStored(insert) || hasStored(delete) {
// There are two reasons for storing content:
// 1. Unfinished Entity <- Could be more efficient here by not doing this if we're looking for a tag
if unfinishedEnt[insert] || unfinishedEnt[delete] {
// we look for a ';' to finish an entity
idx := strings.IndexRune(diff.Text, ';')
if idx >= 0 {
// if we find a ';' store the preceding content to both insert and delete
store(diff.Text[:idx+1], insert)
store(diff.Text[:idx+1], delete)
// and remove it from this block
diff.Text = diff.Text[idx+1:]
// reset the ent flags
unfinishedEnt[insert] = false
unfinishedEnt[delete] = false
} else {
// otherwise store it all on insert and delete
store(diff.Text, insert)
store(diff.Text, delete)
// and empty this block
diff.Text = ""
}
}
// 2. Unfinished Tag
if unfinishedTag[insert] || unfinishedTag[delete] {
// we look for a '>' to finish a tag
idx := strings.IndexRune(diff.Text, '>')
if idx >= 0 {
store(diff.Text[:idx+1], insert)
store(diff.Text[:idx+1], delete)
diff.Text = diff.Text[idx+1:]
unfinishedTag[insert] = false
unfinishedTag[delete] = false
} else {
store(diff.Text, insert)
store(diff.Text, delete)
diff.Text = ""
}
}
// If we've completed the required tag/entities
if !(unfinishedTag[insert] || unfinishedTag[delete] || unfinishedEnt[insert] || unfinishedEnt[delete]) {
// pop off the stack
fixedup = pop(insert, fixedup)
fixedup = pop(delete, fixedup)
}
// If that has left this diff block empty then shortcut
if len(diff.Text) == 0 {
continue
}
}
// check if this block ends in an unfinished tag?
idx := unfinishedtagRegex.FindStringIndex(diff.Text)
if idx != nil {
unfinishedTag[insert] = true
unfinishedTag[delete] = true
} else {
// otherwise does it end in an unfinished entity?
idx = entityRegex.FindStringIndex(diff.Text)
if idx != nil {
unfinishedEnt[insert] = true
unfinishedEnt[delete] = true
}
}
// If there is an unfinished component
if idx != nil {
// Store the fragment
store(diff.Text[idx[0]:], insert)
store(diff.Text[idx[0]:], delete)
// and remove it from this block
diff.Text = diff.Text[:idx[0]]
}
// If that hasn't left the block empty
if len(diff.Text) > 0 {
// store the position of the last equal block and store it in our diffs
last[equal] = len(fixedup)
fixedup = append(fixedup, diff)
}
continue
case diffmatchpatch.DiffInsert:
typ = insert
fallthrough
case diffmatchpatch.DiffDelete:
// First check if there is anything stored for this type
if hasStored(typ) {
// if there is prepend it to this block, empty the storage and reset our flags
diff.Text = stored(typ) + diff.Text
empty(typ)
unfinishedEnt[typ] = false
unfinishedTag[typ] = false
}
// check if this block ends in an unfinished tag
idx := unfinishedtagRegex.FindStringIndex(diff.Text)
if idx != nil {
unfinishedTag[typ] = true
} else {
// otherwise does it end in an unfinished entity
idx = entityRegex.FindStringIndex(diff.Text)
if idx != nil {
unfinishedEnt[typ] = true
}
}
// If there is an unfinished component
if idx != nil {
// Store the fragment
store(diff.Text[idx[0]:], typ)
// and remove it from this block
diff.Text = diff.Text[:idx[0]]
}
// If that hasn't left the block empty
if len(diff.Text) > 0 {
// if the last block of this type was after the last equal block
if last[typ] > last[equal] {
// store this blocks content on that block
fixedup[last[typ]].Text += diff.Text
} else {
// otherwise store the position of the last block of this type and store the block
last[typ] = len(fixedup)
fixedup = append(fixedup, diff)
}
}
continue
}
}
// pop off any remaining stored content
fixedup = pop(insert, fixedup)
fixedup = pop(delete, fixedup)
return fixedup
}
func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
buf := bytes.NewBuffer(nil)
match := ""
diffs = fixupBrokenSpans(diffs)
for _, diff := range diffs {
if shouldWriteInline(diff, lineType) {
if len(match) > 0 {
@@ -357,8 +568,6 @@ func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) tem
diffRecord := diffMatchPatch.DiffMain(highlight.Code(diffSection.FileName, diff1[1:]), highlight.Code(diffSection.FileName, diff2[1:]), true)
diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
return diffToHTML(diffSection.FileName, diffRecord, diffLine.Type)
}
@@ -374,6 +583,7 @@ type DiffFile struct {
IsBin bool
IsLFSFile bool
IsRenamed bool
IsAmbiguous bool
IsSubmodule bool
Sections []*DiffSection
IsIncomplete bool
@@ -567,12 +777,32 @@ parsingLoop:
if strings.HasSuffix(line, " 160000\n") {
curFile.IsSubmodule = true
}
case strings.HasPrefix(line, "rename from "):
curFile.IsRenamed = true
curFile.Type = DiffFileRename
if curFile.IsAmbiguous {
curFile.OldName = line[len("rename from ") : len(line)-1]
}
case strings.HasPrefix(line, "rename to "):
curFile.IsRenamed = true
curFile.Type = DiffFileRename
if curFile.IsAmbiguous {
curFile.Name = line[len("rename to ") : len(line)-1]
curFile.IsAmbiguous = false
}
case strings.HasPrefix(line, "copy from "):
curFile.IsRenamed = true
curFile.Type = DiffFileCopy
if curFile.IsAmbiguous {
curFile.OldName = line[len("copy from ") : len(line)-1]
}
case strings.HasPrefix(line, "copy to "):
curFile.IsRenamed = true
curFile.Type = DiffFileCopy
if curFile.IsAmbiguous {
curFile.Name = line[len("copy to ") : len(line)-1]
curFile.IsAmbiguous = false
}
case strings.HasPrefix(line, "new file"):
curFile.Type = DiffFileAdd
curFile.IsCreated = true
@@ -594,9 +824,35 @@ parsingLoop:
case strings.HasPrefix(line, "Binary"):
curFile.IsBin = true
case strings.HasPrefix(line, "--- "):
// Do nothing with this line
// Handle ambiguous filenames
if curFile.IsAmbiguous {
if len(line) > 6 && line[4] == 'a' {
curFile.OldName = line[6 : len(line)-1]
if line[len(line)-2] == '\t' {
curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]
}
} else {
curFile.OldName = ""
}
}
// Otherwise do nothing with this line
case strings.HasPrefix(line, "+++ "):
// Do nothing with this line
// Handle ambiguous filenames
if curFile.IsAmbiguous {
if len(line) > 6 && line[4] == 'b' {
curFile.Name = line[6 : len(line)-1]
if line[len(line)-2] == '\t' {
curFile.Name = curFile.Name[:len(curFile.Name)-1]
}
if curFile.OldName == "" {
curFile.OldName = curFile.Name
}
} else {
curFile.Name = curFile.OldName
}
curFile.IsAmbiguous = false
}
// Otherwise do nothing with this line, but now switch to parsing hunks
lineBytes, isFragment, err := parseHunks(curFile, maxLines, maxLineCharacters, input)
diff.TotalAddition += curFile.Addition
diff.TotalDeletion += curFile.Deletion
@@ -847,13 +1103,33 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
rd := strings.NewReader(line[len(cmdDiffHead):] + " ")
curFile.Type = DiffFileChange
curFile.OldName = readFileName(rd)
curFile.Name = readFileName(rd)
oldNameAmbiguity := false
newNameAmbiguity := false
curFile.OldName, oldNameAmbiguity = readFileName(rd)
curFile.Name, newNameAmbiguity = readFileName(rd)
if oldNameAmbiguity && newNameAmbiguity {
curFile.IsAmbiguous = true
// OK we should bet that the oldName and the newName are the same if they can be made to be same
// So we need to start again ...
if (len(line)-len(cmdDiffHead)-1)%2 == 0 {
// diff --git a/b b/b b/b b/b b/b b/b
//
midpoint := (len(line) + len(cmdDiffHead) - 1) / 2
new, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:]
if len(new) > 2 && len(old) > 2 && new[2:] == old[2:] {
curFile.OldName = old[2:]
curFile.Name = old[2:]
}
}
}
curFile.IsRenamed = curFile.Name != curFile.OldName
return curFile
}
func readFileName(rd *strings.Reader) string {
func readFileName(rd *strings.Reader) (string, bool) {
ambiguity := false
var name string
char, _ := rd.ReadByte()
_ = rd.UnreadByte()
@@ -863,9 +1139,24 @@ func readFileName(rd *strings.Reader) string {
name = name[1:]
}
} else {
// This technique is potentially ambiguous it may not be possible to uniquely identify the filenames from the diff line alone
ambiguity = true
fmt.Fscanf(rd, "%s ", &name)
char, _ := rd.ReadByte()
_ = rd.UnreadByte()
for !(char == 0 || char == '"' || char == 'b') {
var suffix string
fmt.Fscanf(rd, "%s ", &suffix)
name += " " + suffix
char, _ = rd.ReadByte()
_ = rd.UnreadByte()
}
}
return name[2:]
if len(name) < 2 {
log.Error("Unable to determine name from reader: %v", rd)
return "", true
}
return name[2:], ambiguity
}
// GetDiffRange builds a Diff between two commits of a repository.
@@ -976,6 +1267,7 @@ func CommentAsDiff(c *models.Comment) (*Diff, error) {
diff, err := ParsePatch(setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch))
if err != nil {
log.Error("Unable to parse patch: %v", err)
return nil, err
}
if len(diff.Files) == 0 {

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/setting"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
@@ -23,7 +24,7 @@ import (
func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
if s1 != string(s2) {
t.Errorf("%s should be equal %s", s2, s1)
t.Errorf("Did not receive expected results:\nExpected: %s\nActual: %s", s1, s2)
}
}
@@ -61,7 +62,7 @@ func TestDiffToHTML(t *testing.T) {
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">)</span>"},
}, DiffLineDel))
assertEqual(t, "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"removed-code\"><span class=\"nx\">language</span></span><span class=\"removed-code\"><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
assertEqual(t, "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"removed-code\"><span class=\"nx\">language</span><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffEqual, Text: "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"nx\">"},
{Type: dmp.DiffDelete, Text: "language</span><span "},
{Type: dmp.DiffEqual, Text: "c"},
@@ -69,14 +70,14 @@ func TestDiffToHTML(t *testing.T) {
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>"},
}, DiffLineDel))
assertEqual(t, "<span class=\"added-code\">language</span></span><span class=\"added-code\"><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
assertEqual(t, "<span class=\"added-code\">language</span><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffInsert, Text: "language</span><span "},
{Type: dmp.DiffEqual, Text: "c"},
{Type: dmp.DiffInsert, Text: "lass=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs"},
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>"},
}, DiffLineAdd))
assertEqual(t, "<span class=\"k\">print</span><span class=\"added-code\"></span><span class=\"added-code\"><span class=\"p\">(</span></span><span class=\"sa\"></span><span class=\"s2\">&#34;</span><span class=\"s2\">// </span><span class=\"s2\">&#34;</span><span class=\"p\">,</span> <span class=\"n\">sys</span><span class=\"o\">.</span><span class=\"n\">argv</span><span class=\"added-code\"><span class=\"p\">)</span></span>", diffToHTML("", []dmp.Diff{
assertEqual(t, "<span class=\"k\">print</span><span class=\"added-code\"><span class=\"p\">(</span></span><span class=\"sa\"></span><span class=\"s2\">&#34;</span><span class=\"s2\">// </span><span class=\"s2\">&#34;</span><span class=\"p\">,</span> <span class=\"n\">sys</span><span class=\"o\">.</span><span class=\"n\">argv</span><span class=\"added-code\"><span class=\"p\">)</span></span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffEqual, Text: "<span class=\"k\">print</span>"},
{Type: dmp.DiffInsert, Text: "<span"},
{Type: dmp.DiffEqual, Text: " "},
@@ -85,14 +86,14 @@ func TestDiffToHTML(t *testing.T) {
{Type: dmp.DiffInsert, Text: "<span class=\"p\">)</span>"},
}, DiffLineAdd))
assertEqual(t, "sh <span class=\"added-code\">&#39;useradd -u $(stat -c &#34;%u&#34; .gitignore) jenkins</span>&#39;", diffToHTML("", []dmp.Diff{
assertEqual(t, "sh <span class=\"added-code\">&#39;useradd -u $(stat -c &#34;%u&#34; .gitignore) jenkins&#39;</span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffEqual, Text: "sh &#3"},
{Type: dmp.DiffDelete, Text: "4;useradd -u 111 jenkins&#34"},
{Type: dmp.DiffInsert, Text: "9;useradd -u $(stat -c &#34;%u&#34; .gitignore) jenkins&#39"},
{Type: dmp.DiffEqual, Text: ";"},
}, DiffLineAdd))
assertEqual(t, "<span class=\"x\"> &lt;h<span class=\"added-code\">4 class=</span><span class=\"added-code\">&#34;release-list-title df ac&#34;</span>&gt;</span>", diffToHTML("", []dmp.Diff{
assertEqual(t, "<span class=\"x\"> &lt;h<span class=\"added-code\">4 class=&#34;release-list-title df ac&#34;</span>&gt;</span>", diffToHTML("", []dmp.Diff{
{Type: dmp.DiffEqual, Text: "<span class=\"x\"> &lt;h"},
{Type: dmp.DiffInsert, Text: "4 class=&#"},
{Type: dmp.DiffEqual, Text: "3"},
@@ -207,6 +208,66 @@ rename to a b/a a/file b/b file
oldFilename: "a b/file b/a a/file",
filename: "a b/a a/file b/b file",
},
{
name: "ambiguous deleted",
gitdiff: `diff --git a/b b/b b/b b/b
deleted file mode 100644
index 92e798b..0000000
--- a/b b/b` + "\t" + `
+++ /dev/null
@@ -1 +0,0 @@
-b b/b
`,
oldFilename: "b b/b",
filename: "b b/b",
addition: 0,
deletion: 1,
},
{
name: "ambiguous addition",
gitdiff: `diff --git a/b b/b b/b b/b
new file mode 100644
index 0000000..92e798b
--- /dev/null
+++ b/b b/b` + "\t" + `
@@ -0,0 +1 @@
+b b/b
`,
oldFilename: "b b/b",
filename: "b b/b",
addition: 1,
deletion: 0,
},
{
name: "rename",
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
similarity index 100%
rename from b b/b b/b b/b b/b
rename to b
`,
oldFilename: "b b/b b/b b/b b/b",
filename: "b",
},
{
name: "ambiguous 1",
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
similarity index 100%
rename from b b/b b/b b/b b/b
rename to b
`,
oldFilename: "b b/b b/b b/b b/b",
filename: "b",
},
{
name: "ambiguous 2",
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
similarity index 100%
rename from b b/b b/b b/b
rename to b b/b
`,
oldFilename: "b b/b b/b b/b",
filename: "b b/b",
},
{
name: "minuses-and-pluses",
gitdiff: `diff --git a/minuses-and-pluses b/minuses-and-pluses
@@ -234,32 +295,32 @@ index 6961180..9ba1a00 100644
t.Run(testcase.name, func(t *testing.T) {
got, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff))
if (err != nil) != testcase.wantErr {
t.Errorf("ParsePatch() error = %v, wantErr %v", err, testcase.wantErr)
t.Errorf("ParsePatch(%q) error = %v, wantErr %v", testcase.name, err, testcase.wantErr)
return
}
gotMarshaled, _ := json.MarshalIndent(got, " ", " ")
if got.NumFiles != 1 {
t.Errorf("ParsePath() did not receive 1 file:\n%s", string(gotMarshaled))
t.Errorf("ParsePath(%q) did not receive 1 file:\n%s", testcase.name, string(gotMarshaled))
return
}
if got.TotalAddition != testcase.addition {
t.Errorf("ParsePath() does not have correct totalAddition %d, wanted %d", got.TotalAddition, testcase.addition)
t.Errorf("ParsePath(%q) does not have correct totalAddition %d, wanted %d", testcase.name, got.TotalAddition, testcase.addition)
}
if got.TotalDeletion != testcase.deletion {
t.Errorf("ParsePath() did not have correct totalDeletion %d, wanted %d", got.TotalDeletion, testcase.deletion)
t.Errorf("ParsePath(%q) did not have correct totalDeletion %d, wanted %d", testcase.name, got.TotalDeletion, testcase.deletion)
}
file := got.Files[0]
if file.Addition != testcase.addition {
t.Errorf("ParsePath() does not have correct file addition %d, wanted %d", file.Addition, testcase.addition)
t.Errorf("ParsePath(%q) does not have correct file addition %d, wanted %d", testcase.name, file.Addition, testcase.addition)
}
if file.Deletion != testcase.deletion {
t.Errorf("ParsePath() did not have correct file deletion %d, wanted %d", file.Deletion, testcase.deletion)
t.Errorf("ParsePath(%q) did not have correct file deletion %d, wanted %d", testcase.name, file.Deletion, testcase.deletion)
}
if file.OldName != testcase.oldFilename {
t.Errorf("ParsePath() did not have correct OldName %s, wanted %s", file.OldName, testcase.oldFilename)
t.Errorf("ParsePath(%q) did not have correct OldName %q, wanted %q", testcase.name, file.OldName, testcase.oldFilename)
}
if file.Name != testcase.filename {
t.Errorf("ParsePath() did not have correct Name %s, wanted %s", file.Name, testcase.filename)
t.Errorf("ParsePath(%q) did not have correct Name %q, wanted %q", testcase.name, file.Name, testcase.filename)
}
})
}
@@ -462,3 +523,14 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
}
}
}
func TestDiffToHTML_14231(t *testing.T) {
setting.Cfg = ini.Empty()
diffRecord := diffMatchPatch.DiffMain(highlight.Code("main.v", " run()\n"), highlight.Code("main.v", " run(db)\n"), true)
diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
expected := ` <span class="n">run</span><span class="added-code"><span class="o">(</span><span class="n">db</span></span><span class="o">)</span>`
output := diffToHTML("main.v", diffRecord, DiffLineAdd)
assertEqual(t, expected, output)
}

View File

@@ -23,7 +23,12 @@ func NewIssue(repo *models.Repository, issue *models.Issue, labelIDs []int64, uu
}
}
notification.NotifyNewIssue(issue)
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), issue.Poster, issue.Content)
if err != nil {
return err
}
notification.NotifyNewIssue(issue, mentions)
return nil
}

View File

@@ -5,31 +5,20 @@
package mailer
import (
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references"
)
// MailParticipantsComment sends new comment emails to repository watchers
// and mentioned people.
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue) error {
return mailParticipantsComment(models.DefaultDBContext(), c, opType, issue)
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) error {
return mailParticipantsComment(c, opType, issue, mentions)
}
func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType models.ActionType, issue *models.Issue) (err error) {
rawMentions := references.FindAllMentionsMarkdown(c.Content)
userMentions, err := issue.ResolveMentionsByVisibility(ctx, c.Poster, rawMentions)
if err != nil {
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", c.IssueID, err)
}
if err = models.UpdateIssueMentions(ctx, c.IssueID, userMentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
}
mentions := make([]int64, len(userMentions))
for i, u := range userMentions {
mentions[i] = u.ID
func mailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
if err = mailIssueCommentToParticipants(
&mailCommentContext{
@@ -38,8 +27,29 @@ func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType mod
ActionType: opType,
Content: c.Content,
Comment: c,
}, mentions); err != nil {
}, mentionedIDs); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
return nil
}
// MailMentionsComment sends email to users mentioned in a code comment
func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
visited := make(map[int64]bool, len(mentions)+1)
visited[c.Poster.ID] = true
if err = mailIssueCommentBatch(
&mailCommentContext{
Issue: pr.Issue,
Doer: c.Poster,
ActionType: models.ActionCommentPull,
Content: c.Content,
Comment: c,
}, mentionedIDs, visited, true); err != nil {
log.Error("mailIssueCommentBatch: %v", err)
}
return nil
}

View File

@@ -9,7 +9,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references"
)
func fallbackMailSubject(issue *models.Issue) string {
@@ -80,6 +79,12 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e
// Avoid mailing the doer
visited[ctx.Doer.ID] = true
// =========== Mentions ===========
if err = mailIssueCommentBatch(ctx, mentions, visited, true); err != nil {
return fmt.Errorf("mailIssueCommentBatch() mentions: %v", err)
}
// Avoid mailing explicit unwatched
ids, err = models.GetIssueWatchersIDs(ctx.Issue.ID, false)
if err != nil {
@@ -93,11 +98,6 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e
return fmt.Errorf("mailIssueCommentBatch(): %v", err)
}
// =========== Mentions ===========
if err = mailIssueCommentBatch(ctx, mentions, visited, true); err != nil {
return fmt.Errorf("mailIssueCommentBatch() mentions: %v", err)
}
return nil
}
@@ -122,7 +122,21 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int
if err != nil {
return err
}
// TODO: Check issue visibility for each user
checkUnit := models.UnitTypeIssues
if ctx.Issue.IsPull {
checkUnit = models.UnitTypePullRequests
}
// Make sure all recipients can still see the issue
idx := 0
for _, r := range recipients {
if ctx.Issue.Repo.CheckUnitUser(r, checkUnit) {
recipients[idx] = r
idx++
}
}
recipients = recipients[:idx]
// TODO: Separate recipients by language for i18n mail templates
tos := make([]string, len(recipients))
for i := range recipients {
@@ -135,22 +149,14 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType) error {
return mailParticipants(models.DefaultDBContext(), issue, doer, opType)
func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) error {
return mailParticipants(issue, doer, opType, mentions)
}
func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.User, opType models.ActionType) (err error) {
rawMentions := references.FindAllMentionsMarkdown(issue.Content)
userMentions, err := issue.ResolveMentionsByVisibility(ctx, doer, rawMentions)
if err != nil {
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", issue.ID, err)
}
if err = models.UpdateIssueMentions(ctx, issue.ID, userMentions); err != nil {
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
}
mentions := make([]int64, len(userMentions))
for i, u := range userMentions {
mentions[i] = u.ID
func mailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) (err error) {
mentionedIDs := make([]int64, len(mentions))
for i, u := range mentions {
mentionedIDs[i] = u.ID
}
if err = mailIssueCommentToParticipants(
&mailCommentContext{
@@ -159,7 +165,7 @@ func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.Us
ActionType: opType,
Content: issue.Content,
Comment: nil,
}, mentions); err != nil {
}, mentionedIDs); err != nil {
log.Error("mailIssueCommentToParticipants: %v", err)
}
return nil

View File

@@ -149,6 +149,11 @@ func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
switch {
case strings.HasPrefix(lines[i], " * "): // New reference
if strings.HasPrefix(lines[i], " * [new tag]") {
refName = git.TagPrefix + refName
} else if strings.HasPrefix(lines[i], " * [new branch]") {
refName = git.BranchPrefix + refName
}
results = append(results, &mirrorSyncResult{
refName: refName,
oldCommitID: gitShortEmptySha,
@@ -434,6 +439,17 @@ func syncMirror(repoID string) {
// Create reference
if result.oldCommitID == gitShortEmptySha {
if tp == git.TagPrefix {
tp = "tag"
} else if tp == git.BranchPrefix {
tp = "branch"
}
commitID, err := gitRepo.GetRefCommitID(result.refName)
if err != nil {
log.Error("gitRepo.GetRefCommitID [repo_id: %s, ref_name: %s]: %v", m.RepoID, result.refName, err)
continue
}
notification.NotifySyncPushCommits(m.Repo.MustOwner(), m.Repo, result.refName, git.EmptySHA, commitID, repo_module.NewPushCommits())
notification.NotifySyncCreateRef(m.Repo.MustOwner(), m.Repo, tp, result.refName)
continue
}

View File

@@ -5,6 +5,7 @@
package mirror
import (
"context"
"path/filepath"
"testing"
@@ -47,7 +48,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
})
assert.NoError(t, err)
mirror, err := repository.MigrateRepositoryGitData(user, user, mirrorRepo, opts)
mirror, err := repository.MigrateRepositoryGitData(context.Background(), user, mirrorRepo, opts)
assert.NoError(t, err)
gitRepo, err := git.OpenRepository(repoPath)

View File

@@ -411,7 +411,7 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
)
// Push back to upstream.
if err := git.NewCommand("push", "origin", baseBranch+":"+pr.BaseBranch).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil {
if err := git.NewCommand("push", "origin", baseBranch+":refs/heads/"+pr.BaseBranch).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil {
if strings.Contains(errbuf.String(), "non-fast-forward") {
return "", &git.ErrPushOutOfDate{
StdOut: outbuf.String(),

View File

@@ -54,7 +54,12 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6
return err
}
notification.NotifyNewPullRequest(pr)
mentions, err := pull.FindAndUpdateIssueMentions(models.DefaultDBContext(), pull.Poster, pull.Content)
if err != nil {
return err
}
notification.NotifyNewPullRequest(pr, mentions)
// add first push codes comment
baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
@@ -470,7 +475,7 @@ func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
return nil
}
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
branches, err := git.GetBranchesByPath(repo.RepoPath())
if err != nil {
@@ -489,6 +494,11 @@ func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
}
for _, pr := range prs {
// If the base repository for this pr is this repository there is no need to close it
// as it is going to be deleted anyway
if pr.BaseRepoID == repo.ID {
continue
}
if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
errs = append(errs, err)
}

View File

@@ -6,13 +6,14 @@
package pull
import (
"bytes"
"fmt"
"io"
"regexp"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
)
@@ -57,7 +58,12 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models
return nil, err
}
notification.NotifyCreateIssueComment(doer, issue.Repo, issue, comment)
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content)
if err != nil {
return nil, err
}
notification.NotifyCreateIssueComment(doer, issue.Repo, issue, comment, mentions)
return comment, nil
}
@@ -167,18 +173,31 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models
// Only fetch diff if comment is review comment
if len(patch) == 0 && reviewID != 0 {
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
}
if len(commitID) == 0 {
commitID, err = gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
commitID = headCommitID
}
reader, writer := io.Pipe()
defer func() {
_ = reader.Close()
_ = writer.Close()
}()
go func() {
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, writer); err != nil {
_ = writer.CloseWithError(fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, headCommitID, treePath, err))
return
}
}
_ = writer.Close()
}()
patchBuf := new(bytes.Buffer)
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, commitID, git.RawDiffNormal, treePath, patchBuf); err != nil {
return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, commitID, treePath, err)
patch, err = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
if err != nil {
log.Error("Error whilst generating patch: %v", err)
return nil, err
}
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
}
return models.CreateComment(&models.CreateCommentOptions{
Type: models.CommentTypeCode,
@@ -226,7 +245,25 @@ func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issu
return nil, nil, err
}
notification.NotifyPullRequestReview(pr, review, comm)
ctx := models.DefaultDBContext()
mentions, err := issue.FindAndUpdateIssueMentions(ctx, doer, comm.Content)
if err != nil {
return nil, nil, err
}
notification.NotifyPullRequestReview(pr, review, comm, mentions)
for _, lines := range review.CodeComments {
for _, comments := range lines {
for _, codeComment := range comments {
mentions, err := issue.FindAndUpdateIssueMentions(ctx, doer, codeComment.Content)
if err != nil {
return nil, nil, err
}
notification.NotifyPullRequestCodeComment(pr, codeComment, mentions)
}
}
}
return review, comm, nil
}

View File

@@ -142,6 +142,10 @@ func DeleteReleaseByID(id int64, doer *models.User, delTag bool) error {
return fmt.Errorf("git tag -d: %v", err)
}
notification.NotifyPushCommits(
doer, repo, git.TagPrefix+rel.TagName, rel.Sha1, git.EmptySHA, repository.NewPushCommits())
notification.NotifyDeleteRef(doer, repo, "tag", git.TagPrefix+rel.TagName)
if err := models.DeleteReleaseByID(id); err != nil {
return fmt.Errorf("DeleteReleaseByID: %v", err)
}

View File

@@ -72,3 +72,31 @@ func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoNam
return nil
}
// StartRepositoryTransfer transfer a repo from one owner to a new one.
// it make repository into pending transfer state, if doer can not create repo for new owner.
func StartRepositoryTransfer(doer, newOwner *models.User, repo *models.Repository, teams []*models.Team) error {
if repo.Status != models.RepositoryReady {
return fmt.Errorf("repository is not ready for transfer")
}
// Admin is always allowed to transfer || user transfer repo back to his account
if doer.IsAdmin || doer.ID == newOwner.ID {
return TransferOwnership(doer, newOwner, repo, teams)
}
// If new owner is an org and user can create repos he can transfer directly too
if newOwner.IsOrganization() {
allowed, err := models.CanCreateOrgRepo(newOwner.ID, doer.ID)
if err != nil {
return err
}
if allowed {
return TransferOwnership(doer, newOwner, repo, teams)
}
}
// Block Transfer, new feature will come in v1.14.0
// https://github.com/go-gitea/gitea/pull/14792
return models.ErrCancelled{}
}

View File

@@ -77,6 +77,8 @@ func InitWiki(repo *models.Repository) error {
return fmt.Errorf("InitRepository: %v", err)
} else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
return fmt.Errorf("createDelegateHooks: %v", err)
} else if _, err = git.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunInDir(repo.WikiPath()); err != nil {
return fmt.Errorf("unable to set default wiki branch to master: %v", err)
}
return nil
}

View File

@@ -41,6 +41,8 @@
{{$.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{$dir}}">
<input type="hidden" name="action" value="adopt">
<input type="hidden" name="q" value="{{$.Keyword}}">
<input type="hidden" name="page" value="{{$.CurrentPage}}">
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i>
@@ -66,6 +68,8 @@
{{$.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{$dir}}">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="q" value="{{$.Keyword}}">
<input type="hidden" name="page" value="{{$.CurrentPage}}">
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i>

View File

@@ -166,74 +166,78 @@
<td class="lines-num"></td>
<td class="lines-type-marker"></td>
<td class="add-comment-left">
{{if and $resolved (eq $line.GetCommentSide "previous")}}
<div class="ui top attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
{{if eq $line.GetCommentSide "previous"}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
<div class="conversation-holder">
{{if and $resolved (eq $line.GetCommentSide "previous")}}
<div class="ui top attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
{{end}}
</div>
{{end}}
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
{{if eq $line.GetCommentSide "previous"}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
</div>
{{end}}
</div>
</td>
<td class="lines-num"></td>
<td class="lines-type-marker"></td>
<td class="add-comment-right">
{{if and $resolved (eq $line.GetCommentSide "proposed")}}
<div class="ui top attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
{{if eq $line.GetCommentSide "proposed"}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
<div class="conversation-holder">
{{if and $resolved (eq $line.GetCommentSide "proposed")}}
<div class="ui top attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
{{end}}
</div>
{{end}}
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
{{if eq $line.GetCommentSide "proposed"}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $ "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "reply" (index $line.Comments 0).ReviewID "hidden" true "root" $ "comment" (index $line.Comments 0)}}
{{if and $.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
</div>
{{end}}
</div>
</td>
</tr>
{{end}}

View File

@@ -39,35 +39,37 @@
<tr>
<td colspan="2" class="lines-num"></td>
<td class="add-comment-left add-comment-right" colspan="2">
{{if $resolved}}
<div class = "ui attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.root.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.root.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.root.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $.root "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index $line.Comments 0).ReviewID "root" $.root "comment" (index $line.Comments 0)}}
{{if and $.root.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.root.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.root.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.root.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
<div class="conversation-holder">
{{if $resolved}}
<div class = "ui attached header">
<span class="ui grey text left"><b>{{$resolveDoer.Name}}</b> {{$.root.i18n.Tr "repo.issues.review.resolved_by"}}</span>
<button id="show-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="ui compact right labeled button show-outdated">
{{svg "octicon-unfold"}}
{{$.root.i18n.Tr "repo.issues.review.show_resolved"}}
</button>
<button id="hide-outdated-{{(index $line.Comments 0).ID}}" data-comment="{{(index $line.Comments 0).ID}}" class="hide ui compact right labeled button hide-outdated">
{{svg "octicon-fold"}}
{{$.root.i18n.Tr "repo.issues.review.hide_resolved"}}
</button>
</div>
{{end}}
<div id="code-comments-{{(index $line.Comments 0).ID}}" class="field comment-code-cloud {{if $resolved}}hide{{end}}">
<div class="comment-list">
<ui class="ui comments">
{{ template "repo/diff/comments" dict "root" $.root "comments" $line.Comments}}
</ui>
</div>
{{template "repo/diff/comment_form_datahandler" dict "hidden" true "reply" (index $line.Comments 0).ReviewID "root" $.root "comment" (index $line.Comments 0)}}
{{if and $.root.CanMarkConversation $isNotPending}}
<button class="ui icon tiny button resolve-conversation" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{(index $line.Comments 0).ID}}" data-update-url="{{$.root.RepoLink}}/issues/resolve_conversation" >
{{if $resolved}}
{{$.root.i18n.Tr "repo.issues.review.un_resolve_conversation"}}
{{else}}
{{$.root.i18n.Tr "repo.issues.review.resolve_conversation"}}
{{end}}
</button>
{{end}}
</div>
</div>
</td>
</tr>

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