Compare commits

...

44 Commits

Author SHA1 Message Date
6543
1112fef93d Changelog v1.14.2 (#15794)
* changelog tool generate

* format & add

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
2021-05-09 11:26:49 +02:00
6543
af11549fb2 Ensure that ctx.Written is checked after issues(...) calls (#15797) (#15798)
Fix issue noted in #15783

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

Co-authored-by: zeripath <art27@cantab.net>
2021-05-09 09:48:52 +01:00
zeripath
76d6184cd0 Display conflict-free merge messages for pull requests (#15773) (#15796)
Backport #15773

Repositories using external issue tracker tend to use numeric issues in
commits. To prevent conflicts during issue reference parsing or inside
commit hooks, this change respects these configuration and uses the !
character to refer to pull requests in merge commit messages.

For repositories using squash merges, this was already handled.

Signed-off-by: JustusBunsi <61625851+justusbunsi@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>

Co-authored-by: Steven <61625851+justusbunsi@users.noreply.github.com>
2021-05-09 10:32:48 +08:00
6543
d644709b22 Exponential Backoff for ByteFIFO (#15724) (#15793)
This PR is another in the vein of queue improvements. It suggests an
exponential backoff for bytefifo queues to reduce the load from queue
polling. This will mostly be useful for redis queues.

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

Co-authored-by: Lauris BH <lauris@nix.lv>

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
2021-05-08 14:27:00 -04:00
6543
30584a6df8 [API] make change repo settings work on empty repos (#15778) (#15789)
* API: Fix #15602

* Add TEST
2021-05-08 15:14:42 +02:00
6543
78710946f2 Use pulls in commit graph unless pulls are disabled (#15734 & #15740 & #15774) (#15775)
* Commit Graph: Pull-Requests should not link to issues (#15734)

Use `/pulls` and simplify code.

* reverse #15734 partial and comment (#15740)

* reverse & comment

* Update templates/repo/graph/commits.tmpl

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

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

* Use pulls in commit graph unless pulls are disabled

Fix #15370

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

Co-authored-by: KN4CK3R <KN4CK3R@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>
2021-05-07 15:12:24 -04:00
6543
22d700edfd Set GIT_DIR correctly if it is not set (#15751) (#15769)
* Set GIT_DIR correctly if it is not set

* Expand out templates

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2021-05-07 20:01:25 +02:00
zeripath
6782a64a4a Defer closing the gitrepo until the end of the wrapped context functions (#15653) (#15746)
* Defer closing the gitrepo until the end of the wrapped context functions (#15653)

Backport #15653

There was a mistake in #15372 where deferral of gitrepo close occurs before it should.

This PR fixes this.
2021-05-07 18:28:02 +02:00
zeripath
1ec11ac87e Drop back to use IsAnInteractiveSession for SVC (#15749) (#15762)
Backport #15749

* Drop back to use IsAnInteractiveSession for SVC

There is an apparent permission change problem when using
IsWindowsService to determine if the SVC manager should be
used.

This PR simply drops back to using IsAnInteractiveSession as
this does not change behaviour.

Fix #15454

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

* Yes staticcheck I know this is deprecated

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

* Just leave me alone lint

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

Co-authored-by: 6543 <6543@obermui.de>
2021-05-07 17:44:35 +02:00
6543
2c2a30d6bb Fix bug where repositories appear unadopted (#15757) (#15767)
Fix bug where repositories with capital letters in their names appear unadopted.

Fix #15755

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-05-07 17:07:39 +02:00
6543
717b313c34 not show ref-in-new-issue pop when issue was disabled (#15761) (#15765)
fix #15718

Signed-off-by: a1012112796 <1012112796@qq.com>
Co-authored-by: a1012112796 <1012112796@qq.com>
2021-05-07 16:13:20 +02:00
6543
0a32861b28 Issue list alignment tweaks (#15483) (#15766)
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
2021-05-07 15:06:19 +02:00
zeripath
52ca7b9b65 Fix setting version table in dump (#15753) (#15759)
Backport #15753

* Fix setting version table in dump

As noted on Discord there is a problem with gitea dump where the version table
is not being dumped correctly.

This is due to a missing pointer in the TableInfo.

This PR fixes this.

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

* Update models_test.go
2021-05-07 14:04:17 +02:00
zeripath
e078d08ecd Fix close button change on delete in simplemde area (#15737) (#15747)
Backport #15737

* Fix close button change on delete in simplemde area

Fix issue with close button changing when deleting in the simplemde textarea.

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

* apply suggestion

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

Co-authored-by: 6543 <6543@obermui.de>
2021-05-06 23:14:15 +01:00
a1012112796
a83fb3a83a fix some ui bug about draft release (#15137) (#15745)
* fix some ui bug about draft release

- should not show draft release in tag list because
  it will't create real tag
- still show draft release without tag and commit message
  for draft release instead of 404 error
- remove tag load for attachement links because it's useless

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

* add test code

* fix test

That's because has added a new release in relaese test database.

* fix dropdown link for draft release
2021-05-06 21:23:26 +02:00
Tomás Warynyca
f9b1fac4ea Fix webkit calendar icon color on arc-green (#15728) 2021-05-05 13:10:01 +08:00
6543
f1e8b8c0d7 Only log Error on getLastCommitStatus error to let pull list still be visible (#15715) 2021-05-04 14:03:31 +02:00
Kyle D
dbbb75712d Move tooltip down to allow selection of Remove File on error (#15672) (#15714) 2021-05-04 07:00:29 +01:00
zeripath
462c6fdee2 Fix setting redis db path (#15698) (#15708)
Backport #15698

There is a bug setting the redis db in the common nosql manager whereby the db path
always fails.

This PR fixes this.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-05-03 22:30:30 +01:00
Kyle D
cead819cb5 Implement delete release attachments and update release attachments' name (#14130) (#15666)
* Implement delete release attachment

* Add attachments on release edit page

* Fix bug

* Finish del release attachments

* Fix frontend lint

* Fix tests

* Support edit release attachments

* Added tests

* Remove the unnecessary parameter isCreate from UpdateReleaseOrCreatReleaseFromTag

* Rename UpdateReleaseOrCreatReleaseFromTag to UpdateRelease

* Fix middle align

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-05-03 13:27:00 -04:00
zeripath
4fa2804238 Performance improvement for last commit cache and show-ref (#15455) (#15701)
Backport #15455

* Improve performance when there are multiple commits in the last commit cache

* read refs directly if we can

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-05-03 16:51:41 +02:00
zeripath
3ce46a7fbd Fix DB session cleanup (#15697) (#15700)
Backport #15697

The DB session clean up needs to check expiry not created_unix.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-05-02 10:43:01 +01:00
6543
15886ce048 Fixed several activation bugs (#15473) (#15685)
* Removed unneeded form tag.

* Fixed typo.

* Fixed NPE.

* Use better error page.

* Splitted GET and POST.

Co-authored-by: KN4CK3R <KN4CK3R@users.noreply.github.com>
2021-04-30 20:14:36 -04:00
6543
a725d31496 Delete references if repository gets deleted (#15681) (#15684)
* Remove DeletedBranch and LFSLocks.

* Sort beans.

Co-authored-by: KN4CK3R <KN4CK3R@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>
2021-05-01 00:09:58 +02:00
6543
8e27f6e814 Fix orphaned objects deletion bug (#15657) (#15683)
* Fix orphaned objects deletion bug

* extend test

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
2021-04-30 22:27:26 +01:00
KN4CK3R
54263ff123 Delete protected branch if repository gets removed (#15658) (#15676)
* Added missing error parameters.

* Delete protected branch if repository gets removed.

* Added doctor fix.
2021-04-30 19:59:42 +01:00
6543
3bde297121 [API] pull notification subject status: add "merged" (#15344) (#15654)
Current subject status can be "", "open" and "closed". This add "merged" to it.
2021-04-28 20:24:56 +01:00
zeripath
0dfde367c1 Remove spurious set name from eventsource.sharedworker.js (#15643) (#15652)
Backport #15643

Fix #15617

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-28 19:50:56 +02:00
zeripath
875501584b not update updated uinx for git gc (#15637) (#15641)
Backport #15637

fix #15634

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

Co-authored-by: a1012112796 <1012112796@qq.com>
2021-04-28 03:20:47 +03:00
zeripath
4190c134e6 Fix commit graph author link (#15627) (#15630)
Backport #15627

The author link on the commit graph is incorrect and isn't providing a link to the author.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-26 20:25:51 +01:00
Lunny Xiao
cae46216e4 fix webhook timeout bug (#15613) (#15621)
* Also fix the potential problem in httplib
2021-04-26 14:42:12 +02:00
techknowlogick
761111f9ed Resolve panic on failed interface conversion in migration v156 (#15604) (#15610)
go panics otherwise with `panic: interface conversion: error is git.ErrNotExist, not *git.ErrNotExist`, thanks to Codeberg/Andi for reporting this.

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-04-25 11:58:42 -04:00
Nathan Smith
57f1476093 Bump unrolled/render to v1.1.0 (#15581) (#15608)
v1.1.0 has improved buffer pooling
2021-04-25 14:01:52 +08:00
Lunny Xiao
bdba89452d Fix missing storage init (#15589) (#15598) 2021-04-23 20:56:21 +08:00
zeripath
6e2dacfef6 If the default branch is not present do not report error on stats indexing (follow-up of #15546) (#15583) (#15594)
Backport #15546
Backport #15583

 #15546 doesn't completely fix this problem because the error returned is an ObjectNotExist
error not a BranchNotExist error.

Add test for ErrObjectNotExist too

Fix #15257

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-22 22:30:18 +02:00
Lunny Xiao
c0869c295a Fix lfs management find (#15537) (#15578)
* Fix lfs management find (#15537)

Fix #15236

* Do not do 40byte conversion within ParseTreeLine
* Missed a to40ByteSHA

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

* Remove space

Co-authored-by: Andrew Thornton <art27@cantab.net>
2021-04-22 20:32:48 +02:00
zeripath
a719311f6d Add placeholder text to deploy key textarea (#15575) (#15576)
Backport #15575

Add placeholder text to deploy key textarea

Related #15574

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-21 23:59:50 +02:00
zeripath
248b67af6f Fix NPE on view commit with notes (#15561) (#15573)
Backport #15561

Fix #15558

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-21 15:11:43 -04:00
silverwind
990c6089db Project board improvements (#15429) (#15560)
* Project board improvements

- Fix link colors
- Extract CSS to own file
- Various minor tweaks to make it look better

Fixes: https://github.com/go-gitea/gitea/issues/15424
Fixes: https://github.com/go-gitea/gitea/issues/15506
Fixes: https://github.com/go-gitea/gitea/pull/15511

* fix squashed cards on small view area

* more css fixes, add second row from issue list

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-20 20:45:00 +01:00
KN4CK3R
5da024a019 Add ETag header (#15370) (#15552)
* Add ETag header.

* Comply with RFC 7232.

* Moved logic into httpcache.go

* Changed name.

* Lint

* Implemented If-None-Match list.

* Fixed missing header on *

* Removed weak etag support.

* Removed * support.

* Added unit test.

* Lint

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2021-04-20 12:01:58 -04:00
Lunny Xiao
eff2499be7 Fix bug on commit graph (#15517) (#15530) 2021-04-17 14:46:30 +02:00
zeripath
4a3c6384ac Send size to /avatars if requested (#15459) (#15528)
Backport #15459

If an avatar is requested in a particular size ensure that /avatars also gets the size request

Fix #15453

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

Co-authored-by: 6543 <6543@obermui.de>
2021-04-17 12:30:58 +01:00
zeripath
2b1989e59f Prevent migration 156 failure if tag commit missing (#15519) (#15527)
Backport #15519

It is possible that tag commits could be deleted or missing from repos. This causes
migration 156 to fail and breaks upgrade.

This PR simply logs the failure.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2021-04-17 12:13:15 +02:00
Mike L
340c4fc7c7 Repo branch page: label size, PR ref, new PR button alignment (#15363) (#15365) 2021-04-16 07:53:51 +02:00
84 changed files with 1441 additions and 410 deletions

View File

@@ -110,3 +110,7 @@ issues:
- text: "exitAfterDefer:"
linters:
- gocritic
- path: modules/graceful/manager_windows.go
linters:
- staticcheck
text: "svc.IsAnInteractiveSession is deprecated: Use IsWindowsService instead."

View File

@@ -4,6 +4,56 @@ 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.14.2](https://github.com/go-gitea/gitea/releases/tag/v1.14.2) - 2021-05-08
* API
* Make change repo settings work on empty repos (#15778) (#15789)
* Add pull "merged" notification subject status to API (#15344) (#15654)
* BUGFIXES
* Ensure that ctx.Written is checked after issues(...) calls (#15797) (#15798)
* Use pulls in commit graph unless pulls are disabled (#15734 & #15740 & #15774) (#15775)
* Set GIT_DIR correctly if it is not set (#15751) (#15769)
* Fix bug where repositories appear unadopted (#15757) (#15767)
* Not show `ref-in-new-issue` pop when issue was disabled (#15761) (#15765)
* Drop back to use IsAnInteractiveSession for SVC (#15749) (#15762)
* Fix setting version table in dump (#15753) (#15759)
* Fix close button change on delete in simplemde area (#15737) (#15747)
* Defer closing the gitrepo until the end of the wrapped context functions (#15653) (#15746)
* Fix some ui bug about draft release (#15137) (#15745)
* Only log Error on getLastCommitStatus error to let pull list still be visible (#15716) (#15715)
* Move tooltip down to allow selection of Remove File on error (#15672) (#15714)
* Fix setting redis db path (#15698) (#15708)
* Fix DB session cleanup (#15697) (#15700)
* Fixed several activation bugs (#15473) (#15685)
* Delete references if repository gets deleted (#15681) (#15684)
* Fix orphaned objects deletion bug (#15657) (#15683)
* Delete protected branch if repository gets removed (#15658) (#15676)
* Remove spurious set name from eventsource.sharedworker.js (#15643) (#15652)
* Not update updated uinx for `git gc` (#15637) (#15641)
* Fix commit graph author link (#15627) (#15630)
* Fix webhook timeout bug (#15613) (#15621)
* Resolve panic on failed interface conversion in migration v156 (#15604) (#15610)
* Fix missing storage init (#15589) (#15598)
* If the default branch is not present do not report error on stats indexing (#15546 & #15583) (#15594)
* Fix lfs management find (#15537) (#15578)
* Fix NPE on view commit with notes (#15561) (#15573)
* Fix bug on commit graph (#15517) (#15530)
* Send size to /avatars if requested (#15459) (#15528)
* Prevent migration 156 failure if tag commit missing (#15519) (#15527)
* ENHANCEMENTS
* Display conflict-free merge messages for pull requests (#15773) (#15796)
* Exponential Backoff for ByteFIFO (#15724) (#15793)
* Issue list alignment tweaks (#15483) (#15766)
* Implement delete release attachments and update release attachments' name (#14130) (#15666)
* Add placeholder text to deploy key textarea (#15575) (#15576)
* Project board improvements (#15429) (#15560)
* Repo branch page: label size, PR ref, new PR button alignment (#15363) (#15365)
* MISC
* Fix webkit calendar icon color on arc-green (#15713) (#15728)
* Performance improvement for last commit cache and show-ref (#15455) (#15701)
* Bump unrolled/render to v1.1.0 (#15581) (#15608)
* Add ETag header (#15370) (#15552)
## [1.14.1](https://github.com/go-gitea/gitea/releases/tag/v1.14.1) - 2021-04-15
* BUGFIXES

View File

@@ -21,6 +21,7 @@ import (
pwd "code.gitea.io/gitea/modules/password"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"github.com/urfave/cli"
)
@@ -489,6 +490,10 @@ func runDeleteUser(c *cli.Context) error {
return err
}
if err := storage.Init(); err != nil {
return err
}
var err error
var user *models.User
if c.IsSet("email") {

2
go.mod
View File

@@ -122,7 +122,7 @@ require (
github.com/unknwon/com v1.0.1
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
github.com/unrolled/render v1.0.3
github.com/unrolled/render v1.1.0
github.com/urfave/cli v1.22.5
github.com/willf/bitset v1.1.11 // indirect
github.com/xanzy/go-gitlab v0.44.0

2
go.sum
View File

@@ -1115,6 +1115,8 @@ github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54I
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae/go.mod h1:1fdkY6xxl6ExVs2QFv7R0F5IRZHKA8RahhB9fMC9RvM=
github.com/unrolled/render v1.0.3 h1:baO+NG1bZSF2WR4zwh+0bMWauWky7DVrTOfvE2w+aFo=
github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
github.com/unrolled/render v1.1.0 h1:gvpR9hHxTt6DcGqRYuVVFcfd8rtK+nyEPUJN06KB57Q=
github.com/unrolled/render v1.1.0/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=

View File

@@ -130,11 +130,14 @@ func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
func TestAPIRepoEdit(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
bFalse, bTrue := false, true
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16
user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org
user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos
repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo
repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo
repo15 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo
repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo
// Get user2's token
@@ -286,9 +289,8 @@ func TestAPIRepoEdit(t *testing.T) {
// Test making a repo public that is private
repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
assert.True(t, repo16.IsPrivate)
private := false
repoEditOption = &api.EditRepoOption{
Private: &private,
Private: &bFalse,
}
url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2)
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
@@ -296,11 +298,24 @@ func TestAPIRepoEdit(t *testing.T) {
repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository)
assert.False(t, repo16.IsPrivate)
// Make it private again
private = true
repoEditOption.Private = &private
repoEditOption.Private = &bTrue
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption)
_ = session.MakeRequest(t, req, http.StatusOK)
// Test to change empty repo
assert.False(t, repo15.IsArchived)
url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2)
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
Archived: &bTrue,
})
_ = session.MakeRequest(t, req, http.StatusOK)
repo15 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository)
assert.True(t, repo15.IsArchived)
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
Archived: &bFalse,
})
_ = session.MakeRequest(t, req, http.StatusOK)
// Test using org repo "user3/repo3" where user2 is a collaborator
origRepoEditOption = getRepoEditOptionFromRepo(repo3)
repoEditOption = getNewRepoEditOption(origRepoEditOption)

View File

@@ -10,9 +10,11 @@ import (
"testing"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/unknwon/i18n"
)
@@ -83,7 +85,7 @@ func TestCreateRelease(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 2)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 3)
}
func TestCreateReleasePreRelease(t *testing.T) {
@@ -92,7 +94,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 2)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 3)
}
func TestCreateReleaseDraft(t *testing.T) {
@@ -101,7 +103,7 @@ func TestCreateReleaseDraft(t *testing.T) {
session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 2)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 3)
}
func TestCreateReleasePaging(t *testing.T) {
@@ -127,3 +129,80 @@ func TestCreateReleasePaging(t *testing.T) {
session2 := loginUser(t, "user4")
checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", i18n.Tr("en", "repo.release.stable"), 10)
}
func TestViewReleaseListNoLogin(t *testing.T) {
defer prepareTestEnv(t)()
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
link := repo.Link() + "/releases"
req := NewRequest(t, "GET", link)
rsp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid")
assert.Equal(t, 1, releases.Length())
links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href")
if !exist {
return
}
links = append(links, link)
})
assert.EqualValues(t, []string{"/user2/repo1/releases/tag/v1.1"}, links)
}
func TestViewReleaseListLogin(t *testing.T) {
defer prepareTestEnv(t)()
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
link := repo.Link() + "/releases"
session := loginUser(t, "user1")
req := NewRequest(t, "GET", link)
rsp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid")
assert.Equal(t, 2, releases.Length())
links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href")
if !exist {
return
}
links = append(links, link)
})
assert.EqualValues(t, []string{"/user2/repo1/releases/tag/draft-release",
"/user2/repo1/releases/tag/v1.1"}, links)
}
func TestViewTagsList(t *testing.T) {
defer prepareTestEnv(t)()
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
link := repo.Link() + "/tags"
session := loginUser(t, "user1")
req := NewRequest(t, "GET", link)
rsp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body)
tags := htmlDoc.Find(".tag-list tr")
assert.Equal(t, 2, tags.Length())
tagNames := make([]string, 0, 5)
tags.Each(func(i int, s *goquery.Selection) {
tagNames = append(tagNames, s.Find(".tag a.df.ac").Text())
})
assert.EqualValues(t, []string{"delete-tag", "v1.1"}, tagNames)
}

View File

@@ -125,8 +125,8 @@ func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
}
// GetAttachmentsByUUIDs returns attachment by given UUID list.
func GetAttachmentsByUUIDs(uuids []string) ([]*Attachment, error) {
return getAttachmentsByUUIDs(x, uuids)
func GetAttachmentsByUUIDs(ctx DBContext, uuids []string) ([]*Attachment, error) {
return getAttachmentsByUUIDs(ctx.e, uuids)
}
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) {
@@ -183,12 +183,12 @@ func getAttachmentByReleaseIDFileName(e Engine, releaseID int64, fileName string
// DeleteAttachment deletes the given attachment and optionally the associated file.
func DeleteAttachment(a *Attachment, remove bool) error {
_, err := DeleteAttachments([]*Attachment{a}, remove)
_, err := DeleteAttachments(DefaultDBContext(), []*Attachment{a}, remove)
return err
}
// DeleteAttachments deletes the given attachments and optionally the associated files.
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
func DeleteAttachments(ctx DBContext, attachments []*Attachment, remove bool) (int, error) {
if len(attachments) == 0 {
return 0, nil
}
@@ -198,7 +198,7 @@ func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
ids = append(ids, a.ID)
}
cnt, err := x.In("id", ids).NoAutoCondition().Delete(attachments[0])
cnt, err := ctx.e.In("id", ids).NoAutoCondition().Delete(attachments[0])
if err != nil {
return 0, err
}
@@ -220,7 +220,7 @@ func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) {
return 0, err
}
return DeleteAttachments(attachments, remove)
return DeleteAttachments(DefaultDBContext(), attachments, remove)
}
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
@@ -230,7 +230,7 @@ func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) {
return 0, err
}
return DeleteAttachments(attachments, remove)
return DeleteAttachments(DefaultDBContext(), attachments, remove)
}
// UpdateAttachment updates the given attachment in database
@@ -238,6 +238,15 @@ func UpdateAttachment(atta *Attachment) error {
return updateAttachment(x, atta)
}
// UpdateAttachmentByUUID Updates attachment via uuid
func UpdateAttachmentByUUID(ctx DBContext, attach *Attachment, cols ...string) error {
if attach.UUID == "" {
return fmt.Errorf("Attachement uuid should not blank")
}
_, err := ctx.e.Where("uuid=?", attach.UUID).Cols(cols...).Update(attach)
return err
}
func updateAttachment(e Engine, atta *Attachment) error {
var sess *xorm.Session
if atta.ID != 0 && atta.UUID == "" {

View File

@@ -120,7 +120,7 @@ func TestUpdateAttachment(t *testing.T) {
func TestGetAttachmentsByUUIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
attachList, err := GetAttachmentsByUUIDs([]string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
attachList, err := GetAttachmentsByUUIDs(DefaultDBContext(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"})
assert.NoError(t, err)
assert.Equal(t, 2, len(attachList))
assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID)

View File

@@ -81,7 +81,7 @@ func LibravatarURL(email string) (*url.URL, error) {
}
// HashedAvatarLink returns an avatar link for a provided email
func HashedAvatarLink(email string) string {
func HashedAvatarLink(email string, size int) string {
lowerEmail := strings.ToLower(strings.TrimSpace(email))
sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail)))
_, _ = cache.GetString("Avatar:"+sum, func() (string, error) {
@@ -108,6 +108,9 @@ func HashedAvatarLink(email string) string {
}
return lowerEmail, nil
})
if size > 0 {
return setting.AppSubURL + "/avatar/" + url.PathEscape(sum) + "?size=" + strconv.Itoa(size)
}
return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
}
@@ -129,7 +132,7 @@ func SizedAvatarLink(email string, size int) string {
// This is the slow path that would need to call LibravatarURL() which
// does DNS lookups. Avoid it by issuing a redirect so we don't block
// the template render with network requests.
return HashedAvatarLink(email)
return HashedAvatarLink(email, size)
} else if !setting.DisableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
copyOfGravatarSourceURL := *setting.GravatarSourceURL

View File

@@ -296,11 +296,15 @@ func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
func DeleteOrphanedObjects(subject, refobject, joinCond string) error {
_, err := x.In("id", builder.Select("`"+subject+"`.id").
subQuery := builder.Select("`"+subject+"`.id").
From("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"})).
Delete("`" + subject + "`")
Where(builder.IsNull{"`" + refobject + "`.id"})
sql, args, err := builder.Delete(builder.In("id", subQuery)).From("`" + subject + "`").ToSQL()
if err != nil {
return err
}
_, err = x.Exec(append([]interface{}{sql}, args...)...)
return err
}

View File

@@ -0,0 +1,32 @@
// Copyright 2021 Gitea. 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 (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDeleteOrphanedObjects(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
countBefore, err := x.Count(&PullRequest{})
assert.NoError(t, err)
_, err = x.Insert(&PullRequest{IssueID: 1000}, &PullRequest{IssueID: 1001}, &PullRequest{IssueID: 1003})
assert.NoError(t, err)
orphaned, err := CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
assert.NoError(t, err)
assert.EqualValues(t, 3, orphaned)
err = DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
assert.NoError(t, err)
countAfter, err := x.Count(&PullRequest{})
assert.NoError(t, err)
assert.EqualValues(t, countBefore, countAfter)
}

View File

@@ -43,3 +43,15 @@
is_tag: true
created_unix: 946684800
-
id: 4
repo_id: 1
publisher_id: 2
tag_name: "draft-release"
lower_tag_name: "draft-release"
target: "master"
title: "draft-release"
is_draft: true
is_prerelease: false
is_tag: false
created_unix: 1619524806

View File

@@ -88,6 +88,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
repo = new(Repository)
has, err := sess.ID(release.RepoID).Get(repo)
if err != nil {
log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s", release.RepoID, release.ID, release.TagName)
return err
} else if !has {
log.Warn("Release[%d] is orphaned and refers to non-existing repository %d", release.ID, release.RepoID)
@@ -99,21 +100,29 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
// v120.go migration may not have been run correctly - we'll just replicate it here
// because this appears to be a common-ish problem.
if _, err := sess.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)"); err != nil {
log.Error("Error whilst updating repository[%d] owner name", repo.ID)
return err
}
if _, err := sess.ID(release.RepoID).Get(repo); err != nil {
log.Error("Error whilst loading repository[%d] for release[%d] with tag name %s", release.RepoID, release.ID, release.TagName)
return err
}
}
gitRepo, err = git.OpenRepository(repoPath(repo.OwnerName, repo.Name))
if err != nil {
log.Error("Error whilst opening git repo for %-v", repo)
return err
}
}
commit, err := gitRepo.GetTagCommit(release.TagName)
if err != nil {
if git.IsErrNotExist(err) {
log.Warn("Unable to find commit %s for Tag: %s in %-v. Cannot update publisher ID.", err.(git.ErrNotExist).ID, release.TagName, repo)
continue
}
log.Error("Error whilst getting commit for Tag: %s in %-v.", release.TagName, repo)
return fmt.Errorf("GetTagCommit: %v", err)
}
@@ -121,6 +130,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
user = new(User)
_, err = sess.Where("email=?", commit.Author.Email).Get(user)
if err != nil {
log.Error("Error whilst getting commit author by email: %s for Tag: %s in %-v.", commit.Author.Email, release.TagName, repo)
return err
}
@@ -133,6 +143,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
release.PublisherID = user.ID
if _, err := sess.ID(release.ID).Cols("publisher_id").Update(release); err != nil {
log.Error("Error whilst updating publisher[%d] for release[%d] with tag name %s", release.PublisherID, release.ID, release.TagName)
return err
}
}

View File

@@ -319,7 +319,7 @@ func DumpDatabase(filePath, dbType string) error {
ID int64 `xorm:"pk autoincr"`
Version int64
}
t, err := x.TableInfo(Version{})
t, err := x.TableInfo(&Version{})
if err != nil {
return err
}

View File

@@ -25,7 +25,7 @@ func TestDumpDatabase(t *testing.T) {
ID int64 `xorm:"pk autoincr"`
Version int64
}
assert.NoError(t, x.Sync2(Version{}))
assert.NoError(t, x.Sync2(new(Version)))
for _, dbName := range setting.SupportedDatabases {
dbType := setting.GetDBTypeByName(dbName)

View File

@@ -212,12 +212,21 @@ func (pr *PullRequest) GetDefaultMergeMessage() string {
log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
return ""
}
if pr.BaseRepoID == pr.HeadRepoID {
return fmt.Sprintf("Merge pull request '%s' (#%d) from %s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch)
if err := pr.LoadBaseRepo(); err != nil {
log.Error("LoadBaseRepo: %v", err)
return ""
}
return fmt.Sprintf("Merge pull request '%s' (#%d) from %s:%s into %s", pr.Issue.Title, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
issueReference := "#"
if pr.BaseRepo.UnitEnabled(UnitTypeExternalTracker) {
issueReference = "!"
}
if pr.BaseRepoID == pr.HeadRepoID {
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch)
}
return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch)
}
// ReviewCount represents a count of Reviews

View File

@@ -234,3 +234,36 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {
pr.Issue.Title = "[wip] " + original
assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix())
}
func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage())
pr.BaseRepoID = 1
pr.HeadRepoID = 2
assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
}
func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
externalTracker := RepoUnit{
Type: UnitTypeExternalTracker,
Config: &ExternalTrackerConfig{
ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
},
}
baseRepo := &Repository{Name: "testRepo", ID: 1}
baseRepo.Owner = &User{Name: "testOwner"}
baseRepo.Units = []*RepoUnit{&externalTracker}
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest)
assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage())
pr.BaseRepoID = 1
pr.HeadRepoID = 2
assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage())
}

View File

@@ -6,6 +6,7 @@
package models
import (
"errors"
"fmt"
"sort"
"strings"
@@ -117,17 +118,20 @@ func UpdateRelease(ctx DBContext, rel *Release) error {
}
// AddReleaseAttachments adds a release attachments
func AddReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) {
func AddReleaseAttachments(ctx DBContext, releaseID int64, attachmentUUIDs []string) (err error) {
// Check attachments
attachments, err := GetAttachmentsByUUIDs(attachmentUUIDs)
attachments, err := getAttachmentsByUUIDs(ctx.e, attachmentUUIDs)
if err != nil {
return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
}
for i := range attachments {
if attachments[i].ReleaseID != 0 {
return errors.New("release permission denied")
}
attachments[i].ReleaseID = releaseID
// No assign value could be 0, so ignore AllCols().
if _, err = x.ID(attachments[i].ID).Update(attachments[i]); err != nil {
if _, err = ctx.e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
}
}

View File

@@ -749,7 +749,7 @@ func (repo *Repository) updateSize(e Engine) error {
}
repo.Size = size
_, err = e.ID(repo.ID).Cols("size").Update(repo)
_, err = e.ID(repo.ID).Cols("size").NoAutoTime().Update(repo)
return err
}
@@ -1454,23 +1454,26 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
if err := deleteBeans(sess,
&Access{RepoID: repo.ID},
&Action{RepoID: repo.ID},
&Watch{RepoID: repoID},
&Star{RepoID: repoID},
&Mirror{RepoID: repoID},
&Milestone{RepoID: repoID},
&Release{RepoID: repoID},
&Collaboration{RepoID: repoID},
&PullRequest{BaseRepoID: repoID},
&RepoUnit{RepoID: repoID},
&RepoRedirect{RedirectRepoID: repoID},
&Webhook{RepoID: repoID},
&HookTask{RepoID: repoID},
&Notification{RepoID: repoID},
&CommitStatus{RepoID: repoID},
&RepoIndexerStatus{RepoID: repoID},
&LanguageStat{RepoID: repoID},
&Comment{RefRepoID: repoID},
&CommitStatus{RepoID: repoID},
&DeletedBranch{RepoID: repoID},
&HookTask{RepoID: repoID},
&LFSLock{RepoID: repoID},
&LanguageStat{RepoID: repoID},
&Milestone{RepoID: repoID},
&Mirror{RepoID: repoID},
&Notification{RepoID: repoID},
&ProtectedBranch{RepoID: repoID},
&PullRequest{BaseRepoID: repoID},
&Release{RepoID: repoID},
&RepoIndexerStatus{RepoID: repoID},
&RepoRedirect{RedirectRepoID: repoID},
&RepoUnit{RepoID: repoID},
&Star{RepoID: repoID},
&Task{RepoID: repoID},
&Watch{RepoID: repoID},
&Webhook{RepoID: repoID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
@@ -1486,10 +1489,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return err
}
if _, err := sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil {
return err
}
if repo.IsFork {
if _, err := sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
return fmt.Errorf("decrease fork count: %v", err)

View File

@@ -117,6 +117,6 @@ func CountSessions() (int64, error) {
// CleanupSessions cleans up expired sessions
func CleanupSessions(maxLifetime int64) error {
_, err := x.Where("created_unix <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
_, err := x.Where("expiry <= ?", timeutil.TimeStampNow().Add(-maxLifetime)).Delete(&Session{})
return err
}

View File

@@ -82,6 +82,9 @@ func (u *User) RealSizedAvatarLink(size int) string {
if u.Avatar == "" {
return DefaultAvatarLink()
}
if size > 0 {
return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
}
return setting.AppSubURL + "/avatars/" + u.Avatar
case setting.DisableGravatar, setting.OfflineMode:
if u.Avatar == "" {
@@ -89,7 +92,9 @@ func (u *User) RealSizedAvatarLink(size int) string {
log.Error("GenerateRandomAvatar: %v", err)
}
}
if size > 0 {
return setting.AppSubURL + "/avatars/" + u.Avatar + "?size=" + strconv.Itoa(size)
}
return setting.AppSubURL + "/avatars/" + u.Avatar
}
return SizedAvatarLink(u.AvatarEmail, size)

View File

@@ -6,6 +6,7 @@
package context
import (
"context"
"fmt"
"io/ioutil"
"net/url"
@@ -393,7 +394,7 @@ func RepoIDAssignment() func(ctx *Context) {
}
// RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) {
func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
var (
owner *models.User
err error
@@ -529,12 +530,12 @@ func RepoAssignment(ctx *Context) {
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
cancel = func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
@@ -619,6 +620,7 @@ func RepoAssignment(ctx *Context) {
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
}
return
}
// RepoRefType type of repo reference
@@ -643,7 +645,7 @@ const (
// RepoRef handles repository reference names when the ref name is not
// explicitly given
func RepoRef() func(*Context) {
func RepoRef() func(*Context) context.CancelFunc {
// since no ref name is explicitly specified, ok to just use branch
return RepoRefByType(RepoRefBranch)
}
@@ -722,8 +724,8 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
// RepoRefByType handles repository reference name for a specific type
// of repository reference
func RepoRefByType(refType RepoRefType) func(*Context) {
return func(ctx *Context) {
func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc {
return func(ctx *Context) (cancel context.CancelFunc) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
return
@@ -742,12 +744,12 @@ func RepoRefByType(refType RepoRefType) func(*Context) {
return
}
// We opened it, we should close it
defer func() {
cancel = func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
}
// Get default branch.
@@ -811,6 +813,9 @@ func RepoRefByType(refType RepoRefType) func(*Context) {
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
}
} else {
if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
return
}
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return
}
@@ -841,6 +846,7 @@ func RepoRefByType(refType RepoRefType) func(*Context) {
return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
return
}
}

View File

@@ -47,6 +47,11 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL()
}
pr, _ := n.Issue.GetPullRequest()
if pr != nil && pr.HasMerged {
result.Subject.State = "merged"
}
}
case models.NotificationSourceCommit:
result.Subject = &api.NotificationSubject{

View File

@@ -23,13 +23,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find labels without existing repo or org
count, err := models.CountOrphanedLabels()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned labels")
logger.Critical("Error: %v whilst counting orphaned labels", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedLabels(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned labels")
logger.Critical("Error: %v whilst deleting orphaned labels", err)
return err
}
logger.Info("%d labels without existing repository/organisation deleted", count)
@@ -41,13 +41,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find IssueLabels without existing label
count, err = models.CountOrphanedIssueLabels()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned issue_labels")
logger.Critical("Error: %v whilst counting orphaned issue_labels", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedIssueLabels(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned issue_labels")
logger.Critical("Error: %v whilst deleting orphaned issue_labels", err)
return err
}
logger.Info("%d issue_labels without existing label deleted", count)
@@ -59,13 +59,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find issues without existing repository
count, err = models.CountOrphanedIssues()
if err != nil {
logger.Critical("Error: %v whilst counting orphaned issues")
logger.Critical("Error: %v whilst counting orphaned issues", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedIssues(); err != nil {
logger.Critical("Error: %v whilst deleting orphaned issues")
logger.Critical("Error: %v whilst deleting orphaned issues", err)
return err
}
logger.Info("%d issues without existing repository deleted", count)
@@ -77,13 +77,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find pulls without existing issues
count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects")
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects")
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d pull requests without existing issue deleted", count)
@@ -95,13 +95,13 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find tracked times without existing issues/pulls
count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects")
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects")
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d tracked times without existing issue deleted", count)
@@ -113,14 +113,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find null archived repositories
count, err = models.CountNullArchivedRepository()
if err != nil {
logger.Critical("Error: %v whilst counting null archived repositories")
logger.Critical("Error: %v whilst counting null archived repositories", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixNullArchivedRepository()
if err != nil {
logger.Critical("Error: %v whilst fixing null archived repositories")
logger.Critical("Error: %v whilst fixing null archived repositories", err)
return err
}
logger.Info("%d repositories with null is_archived updated", updatedCount)
@@ -132,14 +132,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
// find label comments with empty labels
count, err = models.CountCommentTypeLabelWithEmptyLabel()
if err != nil {
logger.Critical("Error: %v whilst counting label comments with empty labels")
logger.Critical("Error: %v whilst counting label comments with empty labels", err)
return err
}
if count > 0 {
if autofix {
updatedCount, err := models.FixCommentTypeLabelWithEmptyLabel()
if err != nil {
logger.Critical("Error: %v whilst removing label comments with empty labels")
logger.Critical("Error: %v whilst removing label comments with empty labels", err)
return err
}
logger.Info("%d label comments with empty labels removed", updatedCount)
@@ -191,13 +191,14 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
if setting.Database.UsePostgreSQL {
count, err = models.CountBadSequences()
if err != nil {
logger.Critical("Error: %v whilst checking sequence values")
logger.Critical("Error: %v whilst checking sequence values", err)
return err
}
if count > 0 {
if autofix {
err := models.FixBadSequences()
if err != nil {
logger.Critical("Error: %v whilst attempting to fix sequences")
logger.Critical("Error: %v whilst attempting to fix sequences", err)
return err
}
logger.Info("%d sequences updated", count)
@@ -207,6 +208,60 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
}
}
// find protected branches without existing repository
count, err = models.CountOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("protected_branch", "repository", "protected_branch.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d protected branches without existing repository deleted", count)
} else {
logger.Warn("%d protected branches without existing repository", count)
}
}
// find deleted branches without existing repository
count, err = models.CountOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("deleted_branch", "repository", "deleted_branch.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d deleted branches without existing repository deleted", count)
} else {
logger.Warn("%d deleted branches without existing repository", count)
}
}
// find LFS locks without existing repository
count, err = models.CountOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id")
if err != nil {
logger.Critical("Error: %v whilst counting orphaned objects", err)
return err
}
if count > 0 {
if autofix {
if err = models.DeleteOrphanedObjects("lfs_lock", "repository", "lfs_lock.repo_id=repository.id"); err != nil {
logger.Critical("Error: %v whilst deleting orphaned objects", err)
return err
}
logger.Info("%d LFS locks without existing repository deleted", count)
} else {
logger.Warn("%d LFS locks without existing repository", count)
}
}
return nil
}

View File

@@ -149,10 +149,10 @@ headerLoop:
// constant hextable to help quickly convert between 20byte and 40byte hashes
const hextable = "0123456789abcdef"
// to40ByteSHA converts a 20-byte SHA in a 40-byte slice into a 40-byte sha in place
// To40ByteSHA converts a 20-byte SHA in a 40-byte slice into a 40-byte sha in place
// without allocations. This is at least 100x quicker that hex.EncodeToString
// NB This requires that sha is a 40-byte slice
func to40ByteSHA(sha []byte) []byte {
func To40ByteSHA(sha []byte) []byte {
for i := 19; i >= 0; i-- {
v := sha[i]
vhi, vlo := v>>4, v&0x0f

View File

@@ -102,10 +102,13 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache *LastCo
}
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
wr, rd, cancel := CatFileBatch(cache.repo.Path)
defer cancel()
var unHitEntryPaths []string
var results = make(map[string]*Commit)
for _, p := range paths {
lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
lastCommit, err := cache.Get(commitID, path.Join(treePath, p), wr, rd)
if err != nil {
return nil, nil, err
}
@@ -300,7 +303,7 @@ revListLoop:
commits[0] = string(commitID)
}
}
treeID = to40ByteSHA(treeID)
treeID = To40ByteSHA(treeID)
_, err = batchStdinWriter.Write(treeID)
if err != nil {
return nil, err

View File

@@ -7,6 +7,8 @@
package git
import (
"bufio"
"io"
"path"
)
@@ -34,7 +36,7 @@ func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl func() int64,
}
// Get get the last commit information by commit id and entry path
func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
func (c *LastCommitCache) Get(ref, entryPath string, wr *io.PipeWriter, rd *bufio.Reader) (interface{}, error) {
v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
if vs, ok := v.(string); ok {
log("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
@@ -46,7 +48,10 @@ func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
if err != nil {
return nil, err
}
commit, err := c.repo.getCommit(id)
if _, err := wr.Write([]byte(vs + "\n")); err != nil {
return nil, err
}
commit, err := c.repo.getCommitFromBatchReader(rd, id)
if err != nil {
return nil, err
}

View File

@@ -8,6 +8,7 @@ package git
import (
"io/ioutil"
"strings"
)
// GetNote retrieves the git-notes data for a given commit.
@@ -49,7 +50,13 @@ func GetNote(repo *Repository, commitID string, note *Note) error {
}
note.Message = d
lastCommits, err := GetLastCommitForPaths(notes, "", []string{path})
treePath := ""
if idx := strings.LastIndex(path, "/"); idx > -1 {
treePath = path[:idx]
path = path[idx+1:]
}
lastCommits, err := GetLastCommitForPaths(notes, treePath, []string{path})
if err != nil {
return err
}

View File

@@ -127,11 +127,12 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
case "tree":
var n int64
for n < size {
mode, fname, sha, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf)
mode, fname, sha20byte, count, err := git.ParseTreeLine(batchReader, modeBuf, fnameBuf, workingShaBuf)
if err != nil {
return nil, err
}
n += int64(count)
sha := git.To40ByteSHA(sha20byte)
if bytes.Equal(sha, []byte(hashStr)) {
result := LFSResult{
Name: curPath + string(fname),

View File

@@ -9,9 +9,10 @@ package git
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
@@ -34,6 +35,18 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func (repo *Repository) GetRefCommitID(name string) (string, error) {
if strings.HasPrefix(name, "refs/") {
// We're gonna try just reading the ref file as this is likely to be quicker than other options
fileInfo, err := os.Lstat(filepath.Join(repo.Path, name))
if err == nil && fileInfo.Mode().IsRegular() && fileInfo.Size() == 41 {
ref, err := ioutil.ReadFile(filepath.Join(repo.Path, name))
if err == nil && SHAPattern.Match(ref[:40]) && ref[40] == '\n' {
return string(ref[:40]), nil
}
}
}
stdout, err := NewCommand("show-ref", "--verify", "--hash", name).RunInDir(repo.Path)
if err != nil {
if strings.Contains(err.Error(), "not a valid ref") {
@@ -69,6 +82,11 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
}()
bufReader := bufio.NewReader(stdoutReader)
return repo.getCommitFromBatchReader(bufReader, id)
}
func (repo *Repository) getCommitFromBatchReader(bufReader *bufio.Reader, id SHA1) (*Commit, error) {
_, typ, size, err := ReadBatchLine(bufReader)
if err != nil {
if errors.Is(err, io.EOF) {
@@ -106,7 +124,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
case "commit":
return CommitFromReader(repo, id, io.LimitReader(bufReader, size))
default:
_ = stdoutReader.CloseWithError(fmt.Errorf("unknown typ: %s", typ))
log("Unknown typ: %s", typ)
return nil, ErrNotExist{
ID: id.String(),

View File

@@ -7,6 +7,7 @@ package gitgraph
import (
"bytes"
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
@@ -216,10 +217,10 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
continue
}
refName := string(refNameBytes)
if refName[0:5] == "tag: " {
refName = refName[5:]
} else if refName[0:8] == "HEAD -> " {
refName = refName[8:]
if strings.HasPrefix(refName, "tag: ") {
refName = strings.TrimPrefix(refName, "tag: ")
} else if strings.HasPrefix(refName, "HEAD -> ") {
refName = strings.TrimPrefix(refName, "HEAD -> ")
}
refs = append(refs, git.Reference{
Name: refName,

View File

@@ -74,12 +74,14 @@ func (g *Manager) start() {
// Make SVC process
run := svc.Run
isWindowsService, err := svc.IsWindowsService()
//lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
isAnInteractiveSession, err := svc.IsAnInteractiveSession()
if err != nil {
log.Error("Unable to ascertain if running as an Windows Service: %v", err)
return
}
if !isWindowsService {
if isAnInteractiveSession {
log.Trace("Not running a service ... using the debug SVC manager")
run = debug.Run
}

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/setting"
@@ -26,11 +27,13 @@ func GetCacheControl() string {
// generateETag generates an ETag based on size, filename and file modification time
func generateETag(fi os.FileInfo) string {
etag := fmt.Sprint(fi.Size()) + fi.Name() + fi.ModTime().UTC().Format(http.TimeFormat)
return base64.StdEncoding.EncodeToString([]byte(etag))
return `"` + base64.StdEncoding.EncodeToString([]byte(etag)) + `"`
}
// HandleTimeCache handles time-based caching for a HTTP request
func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
w.Header().Set("Cache-Control", GetCacheControl())
ifModifiedSince := req.Header.Get("If-Modified-Since")
if ifModifiedSince != "" {
t, err := time.Parse(http.TimeFormat, ifModifiedSince)
@@ -40,20 +43,40 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (
}
}
w.Header().Set("Cache-Control", GetCacheControl())
w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
return false
}
// HandleEtagCache handles ETag-based caching for a HTTP request
func HandleEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
// HandleFileETagCache handles ETag-based caching for a HTTP request
func HandleFileETagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
etag := generateETag(fi)
if req.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return true
}
return HandleGenericETagCache(req, w, etag)
}
// HandleGenericETagCache handles ETag-based caching for a HTTP request.
// It returns true if the request was handled.
func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) {
if len(etag) > 0 {
w.Header().Set("Etag", etag)
if checkIfNoneMatchIsValid(req, etag) {
w.WriteHeader(http.StatusNotModified)
return true
}
}
w.Header().Set("Cache-Control", GetCacheControl())
w.Header().Set("ETag", etag)
return false
}
// checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag
func checkIfNoneMatchIsValid(req *http.Request, etag string) bool {
ifNoneMatch := req.Header.Get("If-None-Match")
if len(ifNoneMatch) > 0 {
for _, item := range strings.Split(ifNoneMatch, ",") {
item = strings.TrimSpace(item)
if item == etag {
return true
}
}
}
return false
}

View File

@@ -0,0 +1,144 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package httpcache
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type mockFileInfo struct {
}
func (m mockFileInfo) Name() string { return "gitea.test" }
func (m mockFileInfo) Size() int64 { return int64(10) }
func (m mockFileInfo) Mode() os.FileMode { return os.ModePerm }
func (m mockFileInfo) ModTime() time.Time { return time.Time{} }
func (m mockFileInfo) IsDir() bool { return false }
func (m mockFileInfo) Sys() interface{} { return nil }
func TestHandleFileETagCache(t *testing.T) {
fi := mockFileInfo{}
etag := `"MTBnaXRlYS50ZXN0TW9uLCAwMSBKYW4gMDAwMSAwMDowMDowMCBHTVQ="`
t.Run("No_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
handled := HandleFileETagCache(req, w, fi)
assert.False(t, handled)
assert.Len(t, w.Header(), 2)
assert.Contains(t, w.Header(), "Cache-Control")
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
})
t.Run("Wrong_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", `"wrong etag"`)
handled := HandleFileETagCache(req, w, fi)
assert.False(t, handled)
assert.Len(t, w.Header(), 2)
assert.Contains(t, w.Header(), "Cache-Control")
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
})
t.Run("Correct_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", etag)
handled := HandleFileETagCache(req, w, fi)
assert.True(t, handled)
assert.Len(t, w.Header(), 1)
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
assert.Equal(t, http.StatusNotModified, w.Code)
})
}
func TestHandleGenericETagCache(t *testing.T) {
etag := `"test"`
t.Run("No_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
handled := HandleGenericETagCache(req, w, etag)
assert.False(t, handled)
assert.Len(t, w.Header(), 2)
assert.Contains(t, w.Header(), "Cache-Control")
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
})
t.Run("Wrong_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", `"wrong etag"`)
handled := HandleGenericETagCache(req, w, etag)
assert.False(t, handled)
assert.Len(t, w.Header(), 2)
assert.Contains(t, w.Header(), "Cache-Control")
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
})
t.Run("Correct_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", etag)
handled := HandleGenericETagCache(req, w, etag)
assert.True(t, handled)
assert.Len(t, w.Header(), 1)
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
assert.Equal(t, http.StatusNotModified, w.Code)
})
t.Run("Multiple_Wrong_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", `"wrong etag", "wrong etag "`)
handled := HandleGenericETagCache(req, w, etag)
assert.False(t, handled)
assert.Len(t, w.Header(), 2)
assert.Contains(t, w.Header(), "Cache-Control")
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
})
t.Run("Multiple_Correct_If-None-Match", func(t *testing.T) {
req := &http.Request{Header: make(http.Header)}
w := httptest.NewRecorder()
req.Header.Set("If-None-Match", `"wrong etag", `+etag)
handled := HandleGenericETagCache(req, w, etag)
assert.True(t, handled)
assert.Len(t, w.Header(), 1)
assert.Contains(t, w.Header(), "Etag")
assert.Equal(t, etag, w.Header().Get("Etag"))
assert.Equal(t, http.StatusNotModified, w.Code)
})
}

View File

@@ -325,7 +325,7 @@ func (r *Request) getResponse() (*http.Response, error) {
trans = &http.Transport{
TLSClientConfig: r.setting.TLSClientConfig,
Proxy: proxy,
Dial: TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout),
Dial: TimeoutDialer(r.setting.ConnectTimeout),
}
} else if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
@@ -335,7 +335,7 @@ func (r *Request) getResponse() (*http.Response, error) {
t.Proxy = r.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(r.setting.ConnectTimeout, r.setting.ReadWriteTimeout)
t.Dial = TimeoutDialer(r.setting.ConnectTimeout)
}
}
@@ -352,6 +352,7 @@ func (r *Request) getResponse() (*http.Response, error) {
client := &http.Client{
Transport: trans,
Jar: jar,
Timeout: r.setting.ReadWriteTimeout,
}
if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 {
@@ -457,12 +458,12 @@ func (r *Request) Response() (*http.Response, error) {
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
func TimeoutDialer(cTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
return func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, cTimeout)
if err != nil {
return nil, err
}
return conn, conn.SetDeadline(time.Now().Add(rwTimeout))
return conn, nil
}
}

View File

@@ -38,7 +38,11 @@ func (db *DBIndexer) Index(id int64) error {
// Get latest commit for default branch
commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
if err != nil {
log.Error("Unable to get commit ID for defaultbranch %s in %s", repo.DefaultBranch, repo.RepoPath())
if git.IsErrBranchNotExist(err) || git.IsErrNotExist((err)) {
log.Debug("Unable to get commit ID for defaultbranch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
return nil
}
log.Error("Unable to get commit ID for defaultbranch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err)
return err
}

View File

@@ -152,7 +152,7 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
if db, err := strconv.Atoi(uri.Path[1:]); err == nil {
opts.DB = db
}
}
@@ -168,7 +168,7 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
if db, err := strconv.Atoi(uri.Path[1:]); err == nil {
opts.DB = db
}
}
@@ -186,7 +186,7 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
if db, err := strconv.Atoi(uri.Path[1:]); err == nil {
opts.DB = db
}
}

View File

@@ -165,7 +165,7 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio
log.Println("[Static] Serving " + file)
}
if httpcache.HandleEtagCache(req, w, fi) {
if httpcache.HandleFileETagCache(req, w, fi) {
return true
}

View File

@@ -114,43 +114,73 @@ func (q *ByteFIFOQueue) Run(atShutdown, atTerminate func(context.Context, func()
}
func (q *ByteFIFOQueue) readToChan() {
// handle quick cancels
select {
case <-q.closed:
// tell the pool to shutdown.
q.cancel()
return
default:
}
backOffTime := time.Millisecond * 100
maxBackOffTime := time.Second * 3
for {
select {
case <-q.closed:
// tell the pool to shutdown.
q.cancel()
return
default:
q.lock.Lock()
bs, err := q.byteFIFO.Pop()
if err != nil {
q.lock.Unlock()
log.Error("%s: %s Error on Pop: %v", q.typ, q.name, err)
time.Sleep(time.Millisecond * 100)
continue
}
success, resetBackoff := q.doPop()
if resetBackoff {
backOffTime = 100 * time.Millisecond
}
if len(bs) == 0 {
q.lock.Unlock()
time.Sleep(time.Millisecond * 100)
continue
if success {
select {
case <-q.closed:
// tell the pool to shutdown.
q.cancel()
return
default:
}
data, err := unmarshalAs(bs, q.exemplar)
if err != nil {
log.Error("%s: %s Failed to unmarshal with error: %v", q.typ, q.name, err)
q.lock.Unlock()
time.Sleep(time.Millisecond * 100)
continue
} else {
select {
case <-q.closed:
// tell the pool to shutdown.
q.cancel()
return
case <-time.After(backOffTime):
}
backOffTime += backOffTime / 2
if backOffTime > maxBackOffTime {
backOffTime = maxBackOffTime
}
log.Trace("%s %s: Task found: %#v", q.typ, q.name, data)
q.WorkerPool.Push(data)
q.lock.Unlock()
}
}
}
func (q *ByteFIFOQueue) doPop() (success, resetBackoff bool) {
q.lock.Lock()
defer q.lock.Unlock()
bs, err := q.byteFIFO.Pop()
if err != nil {
log.Error("%s: %s Error on Pop: %v", q.typ, q.name, err)
return
}
if len(bs) == 0 {
return
}
resetBackoff = true
data, err := unmarshalAs(bs, q.exemplar)
if err != nil {
log.Error("%s: %s Failed to unmarshal with error: %v", q.typ, q.name, err)
return
}
log.Trace("%s %s: Task found: %#v", q.typ, q.name, data)
q.WorkerPool.Push(data)
success = true
return
}
// Shutdown processing from this queue
func (q *ByteFIFOQueue) Shutdown() {
log.Trace("%s: %s Shutting down", q.typ, q.name)

View File

@@ -228,7 +228,7 @@ func ListUnadoptedRepositories(query string, opts *models.ListOptions) ([]string
found := false
repoLoop:
for i, repo := range repos {
if repo.Name == name {
if repo.LowerName == name {
found = true
repos = append(repos[:i], repos[i+1:]...)
break repoLoop

View File

@@ -22,9 +22,53 @@ import (
func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf(`#!/usr/bin/env %s
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
fmt.Sprintf(`#!/usr/bin/env %s
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0/..)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
fmt.Sprintf(`#!/usr/bin/env %s
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done
`, setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n%s hook --config=%s pre-receive\n", setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)),

View File

@@ -5,6 +5,7 @@
package web
import (
goctx "context"
"fmt"
"net/http"
"reflect"
@@ -27,6 +28,7 @@ func Wrap(handlers ...interface{}) http.HandlerFunc {
switch t := handler.(type) {
case http.HandlerFunc, func(http.ResponseWriter, *http.Request),
func(ctx *context.Context),
func(ctx *context.Context) goctx.CancelFunc,
func(*context.APIContext),
func(*context.PrivateContext),
func(http.Handler) http.Handler:
@@ -48,6 +50,15 @@ func Wrap(handlers ...interface{}) http.HandlerFunc {
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
}
case func(ctx *context.Context) goctx.CancelFunc:
ctx := context.GetContext(req)
cancel := t(ctx)
if cancel != nil {
defer cancel()
}
if ctx.Written() {
return
}
case func(ctx *context.Context):
ctx := context.GetContext(req)
t(ctx)
@@ -94,6 +105,23 @@ func Middle(f func(ctx *context.Context)) func(netx http.Handler) http.Handler {
}
}
// MiddleCancel wrap a context function as a chi middleware
func MiddleCancel(f func(ctx *context.Context) goctx.CancelFunc) func(netx http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := context.GetContext(req)
cancel := f(ctx)
if cancel != nil {
defer cancel()
}
if ctx.Written() {
return
}
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}
// MiddleAPI wrap a context function as a chi middleware
func MiddleAPI(f func(ctx *context.APIContext)) func(netx http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
@@ -163,6 +191,8 @@ func (r *Route) Use(middlewares ...interface{}) {
r.R.Use(t)
case func(*context.Context):
r.R.Use(Middle(t))
case func(*context.Context) goctx.CancelFunc:
r.R.Use(MiddleCancel(t))
case func(*context.APIContext):
r.R.Use(MiddleAPI(t))
default:

View File

@@ -716,7 +716,7 @@ func Routes() *web.Route {
m.Group("/{username}/{reponame}", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete).
Patch(reqToken(), reqAdmin(), context.RepoRefForAPI, bind(api.EditRepoOption{}), repo.Edit)
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
m.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications).

View File

@@ -202,8 +202,8 @@ func CreateRelease(ctx *context.APIContext) {
rel.Repo = ctx.Repo.Repository
rel.Publisher = ctx.User
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, nil, true); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateReleaseOrCreatReleaseFromTag", err)
if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
return
}
}
@@ -277,8 +277,8 @@ func EditRelease(ctx *context.APIContext) {
if form.IsPrerelease != nil {
rel.IsPrerelease = *form.IsPrerelease
}
if err := releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, nil, false); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateReleaseOrCreatReleaseFromTag", err)
if err := releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
return
}

View File

@@ -578,7 +578,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
repo.IsTemplate = *opts.Template
}
if ctx.Repo.GitRepo == nil {
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
var err error
ctx.Repo.GitRepo, err = git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil {
@@ -589,13 +589,13 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
}
// Default branch only updated if changed and exist or the repository is empty
if opts.DefaultBranch != nil &&
repo.DefaultBranch != *opts.DefaultBranch &&
(ctx.Repo.Repository.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
return err
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
if !repo.IsEmpty {
if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
return err
}
}
}
repo.DefaultBranch = *opts.DefaultBranch

View File

@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
@@ -124,21 +125,25 @@ func GetAttachment(ctx *context.Context) {
}
}
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("IncreaseDownloadCount", err)
return
}
if setting.Attachment.ServeDirect {
//If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name)
if u != nil && err == nil {
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}
ctx.Redirect(u.String())
return
}
}
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`) {
return
}
//If we have matched and access to release or issue
fr, err := storage.Attachments.Open(attach.RelativePath())
if err != nil {
@@ -147,11 +152,6 @@ func GetAttachment(ctx *context.Context) {
}
defer fr.Close()
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}
if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil {
ctx.ServerError("ServeData", err)
return

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
)
@@ -31,6 +32,7 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
}
ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")
if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
@@ -71,6 +73,10 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
// ServeBlob download a git.Blob
func ServeBlob(ctx *context.Context, blob *git.Blob) error {
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
return nil
}
dataRc, err := blob.DataAsync()
if err != nil {
return err
@@ -86,6 +92,10 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error {
// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary
func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
return nil
}
dataRc, err := blob.DataAsync()
if err != nil {
return err
@@ -101,6 +111,9 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if meta == nil {
return ServeBlob(ctx, blob)
}
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+meta.Oid+`"`) {
return nil
}
lfsDataRc, err := lfs.ReadMetaObject(meta)
if err != nil {
return err

View File

@@ -374,6 +374,9 @@ func Issues(ctx *context.Context) {
}
issues(ctx, ctx.QueryInt64("milestone"), ctx.QueryInt64("project"), util.OptionalBoolOf(isPullList))
if ctx.Written() {
return
}
var err error
// Get milestones

View File

@@ -504,6 +504,7 @@ func createPointerResultsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg
_ = catFileBatchReader.CloseWithError(err)
break
}
sha = strings.TrimSpace(sha)
// Throw away the blob
if _, err := bufferedReader.ReadString(' '); err != nil {
_ = catFileBatchReader.CloseWithError(err)

View File

@@ -7,6 +7,7 @@ package repo
import (
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
@@ -84,7 +85,7 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
Page: ctx.QueryInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
},
IncludeDrafts: writeAccess,
IncludeDrafts: writeAccess && !isTagList,
IncludeTags: isTagList,
}
@@ -126,6 +127,11 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
}
cacheUsers[r.PublisherID] = r.Publisher
}
if r.IsDraft {
continue
}
if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil {
ctx.ServerError("calReleaseNumCommitsBehind", err)
return
@@ -176,9 +182,11 @@ func SingleRelease(ctx *context.Context) {
return
}
}
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
ctx.ServerError("calReleaseNumCommitsBehind", err)
return
if !release.IsDraft {
if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil {
ctx.ServerError("calReleaseNumCommitsBehind", err)
return
}
}
release.Note = markdown.RenderString(release.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())
@@ -206,7 +214,7 @@ func LatestRelease(ctx *context.Context) {
ctx.Redirect(release.HTMLURL())
}
// NewRelease render creating release page
// NewRelease render creating or edit release page
func NewRelease(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
ctx.Data["PageIsReleaseList"] = true
@@ -221,10 +229,17 @@ func NewRelease(ctx *context.Context) {
}
if rel != nil {
rel.Repo = ctx.Repo.Repository
if err := rel.LoadAttributes(); err != nil {
ctx.ServerError("LoadAttributes", err)
return
}
ctx.Data["tag_name"] = rel.TagName
ctx.Data["tag_target"] = rel.Target
ctx.Data["title"] = rel.Title
ctx.Data["content"] = rel.Note
ctx.Data["attachments"] = rel.Attachments
}
}
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
@@ -324,9 +339,9 @@ func NewReleasePost(ctx *context.Context) {
rel.PublisherID = ctx.User.ID
rel.IsTag = false
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs, true); err != nil {
if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil {
ctx.Data["Err_TagName"] = true
ctx.ServerError("UpdateReleaseOrCreatReleaseFromTag", err)
ctx.ServerError("UpdateRelease", err)
return
}
}
@@ -363,6 +378,13 @@ func EditRelease(ctx *context.Context) {
ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["IsDraft"] = rel.IsDraft
rel.Repo = ctx.Repo.Repository
if err := rel.LoadAttributes(); err != nil {
ctx.ServerError("LoadAttributes", err)
return
}
ctx.Data["attachments"] = rel.Attachments
ctx.HTML(200, tplReleaseNew)
}
@@ -400,16 +422,27 @@ func EditReleasePost(ctx *context.Context) {
return
}
var attachmentUUIDs []string
const delPrefix = "attachment-del-"
const editPrefix = "attachment-edit-"
var addAttachmentUUIDs, delAttachmentUUIDs []string
var editAttachments = make(map[string]string) // uuid -> new name
if setting.Attachment.Enabled {
attachmentUUIDs = form.Files
addAttachmentUUIDs = form.Files
for k, v := range ctx.Req.Form {
if strings.HasPrefix(k, delPrefix) && v[0] == "true" {
delAttachmentUUIDs = append(delAttachmentUUIDs, k[len(delPrefix):])
} else if strings.HasPrefix(k, editPrefix) {
editAttachments[k[len(editPrefix):]] = v[0]
}
}
}
rel.Title = form.Title
rel.Note = form.Content
rel.IsDraft = len(form.Draft) > 0
rel.IsPrerelease = form.Prerelease
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, attachmentUUIDs, false); err != nil {
if err = releaseservice.UpdateRelease(ctx.User, ctx.Repo.GitRepo,
rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil {
ctx.ServerError("UpdateRelease", err)
return
}

View File

@@ -470,7 +470,8 @@ func RegisterRoutes(m *web.Route) {
m.Group("/user", func() {
// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
m.Any("/activate", user.Activate, reqSignIn)
m.Get("/activate", user.Activate, reqSignIn)
m.Post("/activate", user.ActivatePost, reqSignIn)
m.Any("/activate_email", user.ActivateEmail)
m.Get("/avatar/{username}/{size}", user.Avatar)
m.Get("/email2user", user.Email2User)
@@ -900,8 +901,8 @@ func RegisterRoutes(m *web.Route) {
m.Get("/", repo.Releases)
m.Get("/tag/*", repo.SingleRelease)
m.Get("/latest", repo.LatestRelease)
m.Get("/attachments/{uuid}", repo.GetAttachment)
}, repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag))
}, repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true))
m.Get("/releases/attachments/{uuid}", repo.GetAttachment, repo.MustBeNotEmpty, reqRepoReleaseReader)
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)

View File

@@ -1233,12 +1233,11 @@ func SignUpPost(ctx *context.Context) {
// Activate render activate user page
func Activate(ctx *context.Context) {
code := ctx.Query("code")
password := ctx.Query("password")
if len(code) == 0 {
ctx.Data["IsActivatePage"] = true
if ctx.User.IsActive {
ctx.Error(404)
if ctx.User == nil || ctx.User.IsActive {
ctx.NotFound("invalid user", nil)
return
}
// Resend confirmation email.
@@ -1270,6 +1269,34 @@ func Activate(ctx *context.Context) {
// if account is local account, verify password
if user.LoginSource == 0 {
ctx.Data["Code"] = code
ctx.Data["NeedsPassword"] = true
ctx.HTML(http.StatusOK, TplActivate)
return
}
handleAccountActivation(ctx, user)
}
// ActivatePost handles account activation with password check
func ActivatePost(ctx *context.Context) {
code := ctx.Query("code")
if len(code) == 0 {
ctx.Redirect(setting.AppSubURL + "/user/activate")
return
}
user := models.VerifyUserActiveCode(code)
// if code is wrong
if user == nil {
ctx.Data["IsActivateFailed"] = true
ctx.HTML(http.StatusOK, TplActivate)
return
}
// if account is local account, verify password
if user.LoginSource == 0 {
password := ctx.Query("password")
if len(password) == 0 {
ctx.Data["Code"] = code
ctx.Data["NeedsPassword"] = true
@@ -1283,6 +1310,10 @@ func Activate(ctx *context.Context) {
}
}
handleAccountActivation(ctx, user)
}
func handleAccountActivation(ctx *context.Context, user *models.User) {
user.IsActive = true
var err error
if user.Rands, err = models.GetUserSalt(); err != nil {
@@ -1291,7 +1322,7 @@ func Activate(ctx *context.Context) {
}
if err := models.UpdateUserCols(user, "is_active", "rands"); err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(404)
ctx.NotFound("UpdateUserCols", err)
} else {
ctx.ServerError("UpdateUser", err)
}

View File

@@ -677,7 +677,8 @@ func GetIssuesLastCommitStatus(issues models.IssueList) (map[int64]*models.Commi
status, err := getLastCommitStatus(gitRepo, issue.PullRequest)
if err != nil {
return nil, err
log.Error("getLastCommitStatus: cant get last commit of pull [%d]: %v", issue.PullRequest.ID, err)
continue
}
res[issue.PullRequest.ID] = status
}

View File

@@ -5,6 +5,7 @@
package release
import (
"errors"
"fmt"
"strings"
@@ -17,13 +18,14 @@ import (
"code.gitea.io/gitea/modules/timeutil"
)
func createTag(gitRepo *git.Repository, rel *models.Release, msg string) error {
func createTag(gitRepo *git.Repository, rel *models.Release, msg string) (bool, error) {
var created bool
// Only actual create when publish.
if !rel.IsDraft {
if !gitRepo.IsTagExist(rel.TagName) {
commit, err := gitRepo.GetCommit(rel.Target)
if err != nil {
return fmt.Errorf("GetCommit: %v", err)
return false, fmt.Errorf("GetCommit: %v", err)
}
// Trim '--' prefix to prevent command line argument vulnerability.
@@ -31,25 +33,26 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) error {
if len(msg) > 0 {
if err = gitRepo.CreateAnnotatedTag(rel.TagName, msg, commit.ID.String()); err != nil {
if strings.Contains(err.Error(), "is not a valid tag name") {
return models.ErrInvalidTagName{
return false, models.ErrInvalidTagName{
TagName: rel.TagName,
}
}
return err
return false, err
}
} else if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
if strings.Contains(err.Error(), "is not a valid tag name") {
return models.ErrInvalidTagName{
return false, models.ErrInvalidTagName{
TagName: rel.TagName,
}
}
return err
return false, err
}
created = true
rel.LowerTagName = strings.ToLower(rel.TagName)
// Prepare Notify
if err := rel.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return err
return false, err
}
notification.NotifyPushCommits(
rel.Publisher, rel.Repo,
@@ -63,13 +66,13 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) error {
}
commit, err := gitRepo.GetTagCommit(rel.TagName)
if err != nil {
return fmt.Errorf("GetTagCommit: %v", err)
return false, fmt.Errorf("GetTagCommit: %v", err)
}
rel.Sha1 = commit.ID.String()
rel.NumCommits, err = commit.CommitsCount()
if err != nil {
return fmt.Errorf("CommitsCount: %v", err)
return false, fmt.Errorf("CommitsCount: %v", err)
}
if rel.PublisherID <= 0 {
@@ -78,11 +81,10 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) error {
rel.PublisherID = u.ID
}
}
} else {
rel.CreatedUnix = timeutil.TimeStampNow()
}
return nil
return created, nil
}
// CreateRelease creates a new release of repository.
@@ -96,7 +98,7 @@ func CreateRelease(gitRepo *git.Repository, rel *models.Release, attachmentUUIDs
}
}
if err = createTag(gitRepo, rel, msg); err != nil {
if _, err = createTag(gitRepo, rel, msg); err != nil {
return err
}
@@ -105,7 +107,7 @@ func CreateRelease(gitRepo *git.Repository, rel *models.Release, attachmentUUIDs
return err
}
if err = models.AddReleaseAttachments(rel.ID, attachmentUUIDs); err != nil {
if err = models.AddReleaseAttachments(models.DefaultDBContext(), rel.ID, attachmentUUIDs); err != nil {
return err
}
@@ -143,7 +145,7 @@ func CreateNewTag(doer *models.User, repo *models.Repository, commit, tagName, m
IsTag: true,
}
if err = createTag(gitRepo, rel, msg); err != nil {
if _, err = createTag(gitRepo, rel, msg); err != nil {
return err
}
@@ -154,22 +156,97 @@ func CreateNewTag(doer *models.User, repo *models.Repository, commit, tagName, m
return err
}
// UpdateReleaseOrCreatReleaseFromTag updates information of a release or create release from tag.
func UpdateReleaseOrCreatReleaseFromTag(doer *models.User, gitRepo *git.Repository, rel *models.Release, attachmentUUIDs []string, isCreate bool) (err error) {
if err = createTag(gitRepo, rel, ""); err != nil {
// UpdateRelease updates information, attachments of a release and will create tag if it's not a draft and tag not exist.
// addAttachmentUUIDs accept a slice of new created attachments' uuids which will be reassigned release_id as the created release
// delAttachmentUUIDs accept a slice of attachments' uuids which will be deleted from the release
// editAttachments accept a map of attachment uuid to new attachment name which will be updated with attachments.
func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Release,
addAttachmentUUIDs, delAttachmentUUIDs []string, editAttachments map[string]string) (err error) {
if rel.ID == 0 {
return errors.New("UpdateRelease only accepts an exist release")
}
isCreated, err := createTag(gitRepo, rel, "")
if err != nil {
return err
}
rel.LowerTagName = strings.ToLower(rel.TagName)
if err = models.UpdateRelease(models.DefaultDBContext(), rel); err != nil {
ctx, commiter, err := models.TxDBContext()
if err != nil {
return err
}
defer commiter.Close()
if err = models.UpdateRelease(ctx, rel); err != nil {
return err
}
if err = models.AddReleaseAttachments(rel.ID, attachmentUUIDs); err != nil {
log.Error("AddReleaseAttachments: %v", err)
if err = models.AddReleaseAttachments(ctx, rel.ID, addAttachmentUUIDs); err != nil {
return fmt.Errorf("AddReleaseAttachments: %v", err)
}
if !isCreate {
var deletedUUIDsMap = make(map[string]bool)
if len(delAttachmentUUIDs) > 0 {
// Check attachments
attachments, err := models.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs)
if err != nil {
return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", delAttachmentUUIDs, err)
}
for _, attach := range attachments {
if attach.ReleaseID != rel.ID {
return errors.New("delete attachement of release permission denied")
}
deletedUUIDsMap[attach.UUID] = true
}
if _, err := models.DeleteAttachments(ctx, attachments, false); err != nil {
return fmt.Errorf("DeleteAttachments [uuids: %v]: %v", delAttachmentUUIDs, err)
}
}
if len(editAttachments) > 0 {
var updateAttachmentsList = make([]string, 0, len(editAttachments))
for k := range editAttachments {
updateAttachmentsList = append(updateAttachmentsList, k)
}
// Check attachments
attachments, err := models.GetAttachmentsByUUIDs(ctx, updateAttachmentsList)
if err != nil {
return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", updateAttachmentsList, err)
}
for _, attach := range attachments {
if attach.ReleaseID != rel.ID {
return errors.New("update attachement of release permission denied")
}
}
for uuid, newName := range editAttachments {
if !deletedUUIDsMap[uuid] {
if err = models.UpdateAttachmentByUUID(ctx, &models.Attachment{
UUID: uuid,
Name: newName,
}, "name"); err != nil {
return err
}
}
}
}
if err = commiter.Commit(); err != nil {
return
}
for _, uuid := range delAttachmentUUIDs {
if err := storage.Attachments.Delete(models.AttachmentRelativePath(uuid)); err != nil {
// Even delete files failed, but the attachments has been removed from database, so we
// should not return error but only record the error on logs.
// users have to delete this attachments manually or we should have a
// synchronize between database attachment table and attachment storage
log.Error("delete attachment[uuid: %s] failed: %v", uuid, err)
}
}
if !isCreated {
notification.NotifyUpdateRelease(doer, rel)
return
}

View File

@@ -6,6 +6,7 @@ package release
import (
"path/filepath"
"strings"
"testing"
"time"
@@ -90,7 +91,13 @@ func TestRelease_Create(t *testing.T) {
IsTag: false,
}, nil, ""))
assert.NoError(t, CreateRelease(gitRepo, &models.Release{
attach, err := models.NewAttachment(&models.Attachment{
UploaderID: user.ID,
Name: "test.txt",
}, []byte{}, strings.NewReader("testtest"))
assert.NoError(t, err)
var release = models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v0.1.5",
@@ -100,7 +107,8 @@ func TestRelease_Create(t *testing.T) {
IsDraft: false,
IsPrerelease: false,
IsTag: true,
}, nil, "test"))
}
assert.NoError(t, CreateRelease(gitRepo, &release, []string{attach.UUID}, "test"))
}
func TestRelease_Update(t *testing.T) {
@@ -131,7 +139,7 @@ func TestRelease_Update(t *testing.T) {
releaseCreatedUnix := release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Note = "Changed note"
assert.NoError(t, UpdateReleaseOrCreatReleaseFromTag(user, gitRepo, release, nil, false))
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, nil, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
@@ -153,7 +161,7 @@ func TestRelease_Update(t *testing.T) {
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
assert.NoError(t, UpdateReleaseOrCreatReleaseFromTag(user, gitRepo, release, nil, false))
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, nil, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
@@ -176,10 +184,64 @@ func TestRelease_Update(t *testing.T) {
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
release.Note = "Changed note"
assert.NoError(t, UpdateReleaseOrCreatReleaseFromTag(user, gitRepo, release, nil, false))
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, nil, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test create release
release = &models.Release{
RepoID: repo.ID,
PublisherID: user.ID,
TagName: "v1.1.2",
Target: "master",
Title: "v1.1.2 is released",
Note: "v1.1.2 is released",
IsDraft: true,
IsPrerelease: false,
IsTag: false,
}
assert.NoError(t, CreateRelease(gitRepo, release, nil, ""))
assert.Greater(t, release.ID, int64(0))
release.IsDraft = false
tagName := release.TagName
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, nil, nil))
release, err = models.GetReleaseByID(release.ID)
assert.NoError(t, err)
assert.Equal(t, tagName, release.TagName)
// Add new attachments
attach, err := models.NewAttachment(&models.Attachment{
UploaderID: user.ID,
Name: "test.txt",
}, []byte{}, strings.NewReader("testtest"))
assert.NoError(t, err)
assert.NoError(t, UpdateRelease(user, gitRepo, release, []string{attach.UUID}, nil, nil))
assert.NoError(t, models.GetReleaseAttachments(release))
assert.EqualValues(t, 1, len(release.Attachments))
assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID)
assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID)
assert.EqualValues(t, attach.Name, release.Attachments[0].Name)
// update the attachment name
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, nil, map[string]string{
attach.UUID: "test2.txt",
}))
release.Attachments = nil
assert.NoError(t, models.GetReleaseAttachments(release))
assert.EqualValues(t, 1, len(release.Attachments))
assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID)
assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID)
assert.EqualValues(t, "test2.txt", release.Attachments[0].Name)
// delete the attachment
assert.NoError(t, UpdateRelease(user, gitRepo, release, nil, []string{attach.UUID}, nil))
release.Attachments = nil
assert.NoError(t, models.GetReleaseAttachments(release))
assert.EqualValues(t, 0, len(release.Attachments))
}
func TestRelease_createTag(t *testing.T) {
@@ -205,12 +267,14 @@ func TestRelease_createTag(t *testing.T) {
IsPrerelease: false,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
assert.NotEmpty(t, release.CreatedUnix)
releaseCreatedUnix := release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Note = "Changed note"
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed draft
@@ -225,11 +289,13 @@ func TestRelease_createTag(t *testing.T) {
IsPrerelease: false,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
// Test a changed pre-release
@@ -244,12 +310,14 @@ func TestRelease_createTag(t *testing.T) {
IsPrerelease: true,
IsTag: false,
}
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
releaseCreatedUnix = release.CreatedUnix
time.Sleep(2 * time.Second) // sleep 2 seconds to ensure a different timestamp
release.Title = "Changed title"
release.Note = "Changed note"
assert.NoError(t, createTag(gitRepo, release, ""))
_, err = createTag(gitRepo, release, "")
assert.NoError(t, err)
assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix))
}

View File

@@ -271,14 +271,10 @@ func InitDeliverHooks() {
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
Proxy: webhookProxy(),
Dial: func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, timeout)
if err != nil {
return nil, err
}
return conn, conn.SetDeadline(time.Now().Add(timeout))
return net.DialTimeout(netw, addr, timeout) // dial timeout
},
},
Timeout: timeout, // request timeout
}
go graceful.GetManager().RunWithShutdownContext(DeliverHooks)

View File

@@ -76,22 +76,22 @@
<td class="three wide right aligned">
{{if not .LatestPullRequest}}
{{if .IsIncluded}}
<a class="ui poping up orange small label" data-content="{{$.i18n.Tr "repo.branch.included_desc"}}" data-variation="tiny inverted" data-position="top right">
<a class="ui poping up orange large label" data-content="{{$.i18n.Tr "repo.branch.included_desc"}}" data-variation="tiny inverted" data-position="top right">
{{svg "octicon-git-pull-request"}} {{$.i18n.Tr "repo.branch.included"}}
</a>
{{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
<button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
<button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else}}
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="vm">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
{{if .LatestPullRequest.HasMerged}}
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui text-label purple large label vm">{{svg "octicon-git-merge" 16 "mr-2"}}{{$.i18n.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}

View File

@@ -33,14 +33,9 @@
{{range $commit.Refs}}
{{$refGroup := .RefGroup}}
{{if eq $refGroup "pull"}}
{{if $.HidePRRefs}}
{{if (containGeneric $.SelectedBranches .Name) }}
<a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/issues/{{.ShortName|PathEscape}}">
{{svg "octicon-git-pull-request" 16 "mr-2"}}#{{.ShortName}}
</a>
{{end}}
{{else}}
<a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/issues/{{.ShortName|PathEscape}}">
{{if or (not $.HidePRRefs) (containGeneric $.SelectedBranches .Name)}}
<!-- it's intended to use issues not pulls, if it's a pull you will get redirected -->
<a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/{{if $.Repository.UnitEnabled $.UnitTypePullRequests}}pulls{{else}}issues{{end}}/{{.ShortName|PathEscape}}">
{{svg "octicon-git-pull-request" 16 "mr-2"}}#{{.ShortName}}
</a>
{{end}}
@@ -68,7 +63,7 @@
{{$userName = $commit.User.FullName}}
{{end}}
{{avatar $commit.User}}
<a href="{{AppSubUrl}}/{{$commit.User}}">{{$userName}}</a>
<a href="{{$commit.User.HomeLink}}">{{$userName}}</a>
{{else}}
{{avatarByEmail $commit.Commit.Author.Email $userName}}
{{$userName}}

View File

@@ -73,13 +73,13 @@
{{ range $board := .Boards }}
<div class="ui segment board-column" data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}">
<div class="board-column-header">
<div class="ui large label board-label">{{.Title}}</div>
<div class="board-column-header df ac sb">
<div class="ui large label board-label py-2">{{.Title}}</div>
{{if and $.CanWriteProjects (not $.Repository.IsArchived) $.PageIsProjects (ne .ID 0)}}
<div class="ui dropdown jump item poping up right" data-variation="tiny inverted">
<span class="ui text">
<span class="fitted not-mobile" tabindex="-1">{{svg "octicon-kebab-horizontal" 24}}</span>
</span>
<div class="ui dropdown jump item poping up" data-variation="tiny inverted">
<div class="not-mobile px-3" tabindex="-1">
{{svg "octicon-kebab-horizontal"}}
</div>
<div class="menu user-menu" tabindex="-1">
<a class="item show-modal button" data-modal="#edit-project-board-modal-{{.ID}}">
{{svg "octicon-pencil"}}
@@ -156,9 +156,9 @@
<!-- start issue card -->
<div class="card board-card" data-issue="{{.ID}}">
<div class="content">
<div class="content p-0">
<div class="header">
<span>
<span class="dif ac vm {{if .IsClosed}}red{{else}}green{{end}}">
{{if .IsPull}}
{{if .PullRequest.HasMerged}}
{{svg "octicon-git-merge" 16 "text purple"}}
@@ -177,29 +177,47 @@
{{end}}
{{end}}
</span>
<a class="project-board-title" href="{{$.RepoLink}}/issues/{{.Index}}">#{{.Index}} {{.Title}}</a>
<a class="project-board-title vm" href="{{$.RepoLink}}/issues/{{.Index}}">
{{.Title}}
</a>
</div>
<div class="meta my-2">
<span class="text light grey">
#{{.Index}}
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }}
{{if .OriginalAuthor }}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor | Safe}}
{{else if gt .Poster.ID 0}}
{{$.i18n.Tr .GetLastEventLabel $timeStr .Poster.HomeLink (.Poster.GetDisplayName | Escape) | Safe}}
{{else}}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}
{{end}}
</span>
</div>
{{- if .MilestoneID }}
<div class="meta">
<div class="meta my-2">
<a class="milestone" href="{{$.RepoLink}}/milestone/{{ .MilestoneID}}">
{{svg "octicon-milestone"}} {{ .Milestone.Name }}
{{svg "octicon-milestone" 16 "mr-2 vm"}}
<span class="vm">{{ .Milestone.Name }}</span>
</a>
</div>
{{- end }}
{{- range index $.LinkedPRs .ID }}
<div class="meta">
<div class="meta my-2">
<a href="{{$.RepoLink}}/pulls/{{ .Index }}">
<span class="{{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge"}}</span>
{{ .Title}} (#{{ .Index }})
<span class="m-0 {{if .PullRequest.HasMerged}}purple{{else if .IsClosed}}red{{else}}green{{end}}">{{svg "octicon-git-merge" 16 "mr-2 vm"}}</span>
<span class="vm">{{ .Title}} <span class="text light grey">#{{.Index}}</span></span>
</a>
</div>
{{- end }}
</div>
<div class="extra content">
{{ range .Labels }}
<a class="ui label" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}; margin-bottom: 3px;" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
{{ end }}
</div>
{{if .Labels}}
<div class="extra content labels-list p-0 pt-2">
{{ range .Labels }}
<a class="ui label" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
{{ end }}
</div>
{{end}}
</div>
<!-- stop issue card -->

View File

@@ -75,11 +75,13 @@
<span class="ui green label">{{$.i18n.Tr "repo.release.stable"}}</span>
{{end}}
<span class="tag text blue">
<a class="df ac je" href="{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}" rel="nofollow">{{svg "octicon-tag" 16 "mr-2"}}{{.TagName}}</a>
</span>
<span class="commit">
<a class="mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .Sha1}}</a>
<a class="df ac je" href="{{if .IsDraft}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "mr-2"}}{{.TagName}}</a>
</span>
{{if not .IsDraft}}
<span class="commit">
<a class="mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .Sha1}}</a>
</span>
{{end}}
{{end}}
</div>
<div class="ui twelve wide column detail">
@@ -127,9 +129,11 @@
{{$.i18n.Tr "repo.released_this"}}
</span>
{{if .CreatedUnix}}
<span class="time">{{TimeSinceUnix .CreatedUnix $.Lang}}</span> |
<span class="time">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>
{{end}}
{{if not .IsDraft}}
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | EscapePound}}...{{.Target}}">{{$.i18n.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.i18n.Tr "repo.release.ahead.target" .Target}}</span>
{{end}}
<span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | EscapePound}}...{{.Target}}">{{$.i18n.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.i18n.Tr "repo.release.ahead.target" .Target}}</span>
</p>
<div class="markdown desc">
{{Str2html .Note}}
@@ -141,7 +145,7 @@
</h2>
<div class="content {{if eq $idx 0}}active{{end}}">
<ul class="list">
{{if $.Permission.CanRead $.UnitTypeCode}}
{{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}}
<li>
<a class="archive-link" data-url="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "mr-2"}}{{$.i18n.Tr "repo.release.source_code"}} (ZIP)</strong></a>
</li>
@@ -152,12 +156,14 @@
{{if .Attachments}}
{{range .Attachments}}
<li>
<span class="ui text right poping up" data-content="{{$.i18n.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}">
{{svg "octicon-info"}}
<span class="ui text middle aligned right">
<span class="ui text grey">{{.Size | FileSize}}</span>
<span class="poping up" data-content="{{$.i18n.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}">
{{svg "octicon-info"}}
</span>
</span>
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
<strong><span class="ui image" title='{{.Name}}'>{{svg "octicon-package" 16 "mr-2"}}</span>{{.Name}}</strong>
<span class="ui text grey right">{{.Size | FileSize}}</span>
</a>
</li>
{{end}}

View File

@@ -57,6 +57,23 @@
{{$.i18n.Tr "loading"}}
</div>
</div>
{{range .attachments}}
<div class="field" id="attachment-{{.ID}}">
<div class="ui right df ac wrap_remove">
<a class="ui mini compact red button remove-rel-attach" data-id="{{.ID}}" data-uuid="{{.UUID}}">
{{$.i18n.Tr "remove"}}
</a>
</div>
<div class="df ac">
<input name="attachment-edit-{{.UUID}}" class="mr-3 attachment_edit" required value="{{.Name}}"/>
<input name="attachment-del-{{.UUID}}" type="hidden" value="false"/>
<span class="ui text grey mr-3">{{.Size | FileSize}}</span>
<span class="poping up" data-content="{{$.i18n.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}">
{{svg "octicon-info"}}
</span>
</div>
</div>
{{end}}
{{if .IsAttachmentEnabled}}
<div class="field">
<div class="files"></div>

View File

@@ -60,7 +60,7 @@
</div>
<div class="field {{if .Err_Content}}error{{end}}">
<label for="content">{{.i18n.Tr "repo.settings.deploy_key_content"}}</label>
<textarea id="ssh-key-content" name="content" required>{{.content}}</textarea>
<textarea id="ssh-key-content" name="content" placeholder="{{.i18n.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
</div>
<div class="field">
<div class="ui checkbox {{if .Err_IsWritable}}error{{end}}">

View File

@@ -111,15 +111,17 @@
{{end}}
</tbody>
</table>
<div class="code-view-menu-list ui fluid popup transition hidden">
<div class="ui column relaxed equal height">
<div class="column">
<div class="ui link list">
<a class="item ref-in-new-issue" href="{{.RepoLink}}/issues/new?body={{URLJoin AppUrl .RepoLink}}/src/commit/{{.CommitID}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.issues.context.reference_issue"}}</a>
{{if $.Permission.CanRead $.UnitTypeIssues}}
<div class="code-view-menu-list ui fluid popup transition hidden">
<div class="ui column relaxed equal height">
<div class="column">
<div class="ui link list">
<a class="item ref-in-new-issue" href="{{.RepoLink}}/issues/new?body={{URLJoin AppUrl .RepoLink}}/src/commit/{{.CommitID}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.issues.context.reference_issue"}}</a>
</div>
</div>
</div>
</div>
</div>
</div>
{{end}}
{{end}}
{{end}}
</div>

View File

@@ -2,7 +2,7 @@
{{ $approvalCounts := .ApprovalCounts}}
{{range .Issues}}
<li class="item df py-3">
<div class="issue-item-left df py-1">
<div class="issue-item-left df">
{{if $.CanWriteIssuesOrPulls}}
<div class="ui checkbox issue-checkbox">
<input type="checkbox" data-issue-id={{.ID}}></input>

View File

@@ -19,17 +19,15 @@
{{end}}
{{else}}
{{if .NeedsPassword}}
<form class="ui form" action="{{AppSubUrl}}/user/activate" method="post">
<div class="required inline field">
<label for="password">{{.i18n.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="off" required>
</div>
<div class="inline field">
<label></label>
<button class="ui green button">{{.i18n.Tr "install.confirm_password"}}</button>
</div>
<input id="code" name="code" type="hidden" value="{{.Code}}">
</form>
<div class="required inline field">
<label for="password">{{.i18n.Tr "password"}}</label>
<input id="password" name="password" type="password" autocomplete="off" required>
</div>
<div class="inline field">
<label></label>
<button class="ui green button">{{.i18n.Tr "install.confirm_password"}}</button>
</div>
<input id="code" name="code" type="hidden" value="{{.Code}}">
{{else if .IsSendRegisterMail}}
<p>{{.i18n.Tr "auth.confirmation_mail_sent_prompt" (.Email|Escape) .ActiveCodeLives | Str2html}}</p>
{{else if .IsActivateFailed}}

View File

@@ -1,15 +0,0 @@
language: go
go:
- 1.11.x
- 1.12.x
- tip
env:
- GO111MODULE=on
install:
- go mod download
script:
- go test -v -race -tags=integration

View File

@@ -1,4 +1,5 @@
# Render [![GoDoc](http://godoc.org/github.com/unrolled/render?status.svg)](http://godoc.org/github.com/unrolled/render) [![Build Status](https://travis-ci.org/unrolled/render.svg)](https://travis-ci.org/unrolled/render)
# Render [![GoDoc](http://godoc.org/github.com/unrolled/render?status.svg)](http://godoc.org/github.com/unrolled/render) [![Test](https://github.com/unrolled/render/workflows/Test/badge.svg?branch=v1)](https://github.com/unrolled/render/actions)
Render is a package that provides functionality for easily rendering JSON, XML, text, binary data, and HTML templates. This package is based on the [Martini](https://github.com/go-martini/martini) [render](https://github.com/martini-contrib/render) work.

View File

@@ -2,9 +2,6 @@ package render
import "bytes"
// bufPool represents a reusable buffer pool for executing templates into.
var bufPool *BufferPool
// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
// Pulled from the github.com/oxtoacart/bpool package (Apache licensed).
type BufferPool struct {
@@ -39,8 +36,3 @@ func (bp *BufferPool) Put(b *bytes.Buffer) {
default: // Discard the buffer if the pool is full.
}
}
// Initialize buffer pool for writing templates into.
func init() {
bufPool = NewBufferPool(64)
}

View File

@@ -30,6 +30,8 @@ type HTML struct {
Head
Name string
Templates *template.Template
bp GenericBufferPool
}
// JSON built-in renderer.
@@ -82,9 +84,14 @@ func (d Data) Render(w io.Writer, v interface{}) error {
// Render a HTML response.
func (h HTML) Render(w io.Writer, binding interface{}) error {
// Retrieve a buffer from the pool to write to.
out := bufPool.Get()
err := h.Templates.ExecuteTemplate(out, h.Name, binding)
var buf *bytes.Buffer
if h.bp != nil {
// If we have a bufferpool, allocate from it
buf = h.bp.Get()
defer h.bp.Put(buf)
}
err := h.Templates.ExecuteTemplate(buf, h.Name, binding)
if err != nil {
return err
}
@@ -92,10 +99,8 @@ func (h HTML) Render(w io.Writer, binding interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
h.Head.Write(hw)
}
out.WriteTo(w)
buf.WriteTo(w)
// Return the buffer to the pool.
bufPool.Put(out)
return nil
}

View File

@@ -0,0 +1,9 @@
package render
import "bytes"
// GenericBufferPool abstracts buffer pool implementations
type GenericBufferPool interface {
Get() *bytes.Buffer
Put(*bytes.Buffer)
}

View File

@@ -103,6 +103,10 @@ type Options struct {
// Enables using partials without the current filename suffix which allows use of the same template in multiple files. e.g {{ partial "carosuel" }} inside the home template will match carosel-home or carosel.
// ***NOTE*** - This option should be named RenderPartialsWithoutSuffix as that is what it does. "Prefix" is a typo. Maintaining the existing name for backwards compatibility.
RenderPartialsWithoutPrefix bool
// BufferPool to use when rendering HTML templates. If none is supplied
// defaults to SizedBufferPool of size 32 with 512KiB buffers.
BufferPool GenericBufferPool
}
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
@@ -176,6 +180,10 @@ func (r *Render) prepareOptions() {
if len(r.opt.XMLContentType) == 0 {
r.opt.XMLContentType = ContentXML
}
if r.opt.BufferPool == nil {
// 32 buffers of size 512KiB each
r.opt.BufferPool = NewSizedBufferPool(32, 1<<19)
}
}
func (r *Render) compileTemplates() {
@@ -410,6 +418,7 @@ func (r *Render) HTML(w io.Writer, status int, name string, binding interface{},
Head: head,
Name: name,
Templates: r.templates,
bp: r.opt.BufferPool,
}
return r.Render(w, h, binding)

62
vendor/github.com/unrolled/render/sizedbufferpool.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
package render
import (
"bytes"
)
// Pulled from the github.com/oxtoacart/bpool package (Apache licensed).
// SizedBufferPool implements a pool of bytes.Buffers in the form of a bounded
// channel. Buffers are pre-allocated to the requested size.
type SizedBufferPool struct {
c chan *bytes.Buffer
a int
}
// NewSizedBufferPool creates a new BufferPool bounded to the given size.
// size defines the number of buffers to be retained in the pool and alloc sets
// the initial capacity of new buffers to minimize calls to make().
//
// The value of alloc should seek to provide a buffer that is representative of
// most data written to the the buffer (i.e. 95th percentile) without being
// overly large (which will increase static memory consumption). You may wish to
// track the capacity of your last N buffers (i.e. using an []int) prior to
// returning them to the pool as input into calculating a suitable alloc value.
func NewSizedBufferPool(size int, alloc int) (bp *SizedBufferPool) {
return &SizedBufferPool{
c: make(chan *bytes.Buffer, size),
a: alloc,
}
}
// Get gets a Buffer from the SizedBufferPool, or creates a new one if none are
// available in the pool. Buffers have a pre-allocated capacity.
func (bp *SizedBufferPool) Get() (b *bytes.Buffer) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
b = bytes.NewBuffer(make([]byte, 0, bp.a))
}
return
}
// Put returns the given Buffer to the SizedBufferPool.
func (bp *SizedBufferPool) Put(b *bytes.Buffer) {
b.Reset()
// Release buffers over our maximum capacity and re-create a pre-sized
// buffer to replace it.
// Note that the cap(b.Bytes()) provides the capacity from the read off-set
// only, but as we've called b.Reset() the full capacity of the underlying
// byte slice is returned.
if cap(b.Bytes()) > bp.a {
b = bytes.NewBuffer(make([]byte, 0, bp.a))
}
select {
case bp.c <- b:
default: // Discard the buffer if the pool is full.
}
}

2
vendor/modules.txt vendored
View File

@@ -777,7 +777,7 @@ github.com/unknwon/i18n
# github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae
## explicit
github.com/unknwon/paginater
# github.com/unrolled/render v1.0.3
# github.com/unrolled/render v1.1.0
## explicit
github.com/unrolled/render
# github.com/urfave/cli v1.22.5

View File

@@ -1,5 +1,3 @@
self.name = 'eventsource.sharedworker.js';
const sourcesByUrl = {};
const sourcesByPort = {};

View File

@@ -14,6 +14,7 @@ export default async function initProject() {
group: 'board-column',
draggable: '.board-column',
animation: 150,
ghostClass: 'card-ghost',
onSort: () => {
const board = document.getElementsByClassName('board')[0];
const boardColumns = board.getElementsByClassName('board-column');
@@ -42,6 +43,7 @@ export default async function initProject() {
{
group: 'shared',
animation: 150,
ghostClass: 'card-ghost',
onAdd: (e) => {
$.ajax(`${e.to.dataset.url}/${e.item.dataset.issue}`, {
headers: {

View File

@@ -1157,11 +1157,9 @@ async function initRepository() {
// Change status
const $statusButton = $('#status-button');
$('#comment-form textarea').on('keyup', function () {
if ($(this).val().length === 0) {
$statusButton.text($statusButton.data('status'));
} else {
$statusButton.text($statusButton.data('status-and-comment'));
}
const $simplemde = $(this).data('simplemde');
const value = ($simplemde && $simplemde.value()) ? $simplemde.value() : $(this).val();
$statusButton.text($statusButton.data(value.length === 0 ? 'status' : 'status-and-comment'));
});
$statusButton.on('click', () => {
$('#status').val($statusButton.data('status-val'));
@@ -1260,6 +1258,15 @@ function initPullRequestMergeInstruction() {
});
}
function initRelease() {
$(document).on('click', '.remove-rel-attach', function() {
const uuid = $(this).data('uuid');
const id = $(this).data('id');
$(`input[name='attachment-del-${uuid}']`).attr('value', true);
$(`#attachment-${id}`).hide();
});
}
function initPullRequestReview() {
if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
const commentDiv = $(window.location.hash);
@@ -1661,6 +1668,8 @@ function setCommentSimpleMDE($editArea) {
}
});
attachTribute(simplemde.codemirror.getInputField(), {mentions: true, emoji: true});
$editArea.data('simplemde', simplemde);
$(simplemde.codemirror.getInputField()).data('simplemde', simplemde);
return simplemde;
}
@@ -2171,6 +2180,10 @@ function searchRepositories() {
}
function showCodeViewMenu() {
if ($('.code-view-menu-list').length === 0) {
return;
}
// Get clicked tr
const $code_tr = $('.code-view td.lines-code.active').parent();
@@ -2758,6 +2771,7 @@ $(document).ready(async () => {
initNotificationsTable();
initPullRequestMergeInstruction();
initReleaseEditor();
initRelease();
const routes = {
'div.user.settings': initUserSettings,
@@ -2824,6 +2838,11 @@ function selectRange($list, $select, $from) {
// add hashchange to permalink
const $issue = $('a.ref-in-new-issue');
if ($issue.length === 0) {
return;
}
const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
if (matched) {
$issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23L${a}-L${b}`));
@@ -2839,6 +2858,11 @@ function selectRange($list, $select, $from) {
// add hashchange to permalink
const $issue = $('a.ref-in-new-issue');
if ($issue.length === 0) {
return;
}
const matched = $issue.attr('href').match(/%23L\d+$|%23L\d+-L\d+$/);
if (matched) {
$issue.attr('href', $issue.attr('href').replace($issue.attr('href').substr(matched.index), `%23${$select.attr('rel')}`));

View File

@@ -111,6 +111,7 @@
--color-expand-button: #d8efff;
--color-placeholder-text: #aaa;
--color-editor-line-highlight: var(--color-primary-light-6);
--color-project-board-bg: var(--color-secondary-light-4);
/* backgrounds */
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18" width="16" height="16"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>');
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z"></path></svg>');
@@ -442,6 +443,16 @@ a.muted:hover,
color: var(--color-text);
}
.ui.cards > .card .meta > a:not(.ui),
.ui.card .meta > a:not(.ui) {
color: var(--color-text-light-2);
}
.ui.cards > .card .meta > a:not(.ui):hover,
.ui.card .meta > a:not(.ui):hover {
color: var(--color-text);
}
.ui.cards a.card:hover,
.ui.link.cards .card:not(.icon):hover,
a.ui.card:hover,
@@ -450,6 +461,12 @@ a.ui.card:hover,
background: var(--color-card);
}
.ui.cards > .card > .extra,
.ui.card > .extra {
color: var(--color-text);
border-top-color: var(--color-secondary-light-1) !important;
}
.ui.comments .comment .text,
.ui.comments .comment .author {
color: var(--color-text);
@@ -1733,11 +1750,6 @@ a.ui.basic.label:hover {
margin-bottom: .4em;
}
.ui.cards > .card > .extra,
.ui.card > .extra {
color: var(--color-text);
}
.color-icon {
display: inline-block;
border-radius: 100%;

View File

@@ -1853,6 +1853,13 @@
margin-bottom: 1em;
}
}
.wrap_remove {
height: 38px;
}
.attachment_edit {
width: 450px !important;
}
}
}
@@ -2874,82 +2881,6 @@ tbody.commit-list {
padding-top: 0 !important;
}
.board {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
margin: 0 .5em;
}
.board-column {
background-color: rgba(0, 0, 0, .05) !important;
border: 1px solid var(--color-secondary) !important;
margin: 0 .5rem !important;
padding: .5rem !important;
width: 320px;
height: 60vh;
overflow-y: scroll;
flex: 0 0 auto;
overflow: visible;
display: flex;
flex-direction: column;
}
.board-column-header {
display: flex;
justify-content: space-between;
}
.board-label {
background: none !important;
line-height: 1.25 !important;
}
.board-column > .cards {
flex: 1;
display: flex;
flex-direction: column;
margin: 0 !important;
padding: 0 !important;
.card .meta > a.milestone {
color: #999999;
}
}
.board-column > .divider {
margin: 5px 0;
}
.board-column:first-child {
margin-left: auto !important;
}
.board-column:last-child {
margin-right: auto !important;
}
.board-card {
margin: 3px !important;
width: auto !important;
background-color: #fff;
border-radius: 5px;
cursor: pointer;
}
.board-card .header {
font-size: 1.1em !important;
}
.board-card .content {
padding: 8px 8px 5px !important;
}
.board-card .extra.content {
padding: 5px 8px !important;
}
td.blob-excerpt {
background-color: #fafafa;
}

View File

@@ -16,7 +16,7 @@
}
.dropzone .dz-error-message {
top: 120px !important;
top: 145px !important;
}
.dropzone .dz-image {

View File

@@ -0,0 +1,83 @@
.board {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
margin: 0 .5em;
}
.board-column {
background-color: var(--color-project-board-bg) !important;
border: 1px solid var(--color-secondary) !important;
margin: 0 .5rem !important;
padding: .5rem !important;
width: 320px;
height: 60vh;
overflow-y: scroll;
flex: 0 0 auto;
overflow: visible;
display: flex;
flex-direction: column;
}
.board-column-header {
display: flex;
justify-content: space-between;
}
.board-label {
background: none !important;
line-height: 1.25 !important;
}
.board-column > .cards {
flex: 1;
display: flex;
margin: 0 !important;
padding: 0 !important;
flex-wrap: nowrap !important;
flex-direction: column;
}
.project-board-title {
word-break: break-word;
}
.board-column > .divider {
margin: 5px 0;
}
.board-column:first-child {
margin-left: auto !important;
}
.board-column:last-child {
margin-right: auto !important;
}
.board-card {
margin: 4px 2px !important;
border-radius: 5px !important;
cursor: move;
width: calc(100% - 4px) !important;
padding: .5rem !important;
min-height: auto !important;
}
.board-card .meta * {
margin-right: 0 !important;
}
.board-card .header {
margin-top: 0 !important;
font-size: 16px !important;
}
.card-ghost {
border-style: dashed !important;
background: none !important;
}
.card-ghost * {
opacity: 0;
}

View File

@@ -8,6 +8,7 @@
@import "./features/heatmap.less";
@import "./features/imagediff.less";
@import "./features/codeeditor.less";
@import "./features/projects.less";
@import "./markdown/mermaid.less";
@import "./chroma/base.less";

View File

@@ -13,6 +13,7 @@
.issue-item-icon svg {
margin-right: .75rem;
margin-top: 1px;
}
.issue-item-icons-right > * + * {
@@ -29,10 +30,11 @@
font-size: 16px;
min-width: 0;
font-weight: 600;
}
> * {
vertical-align: middle;
}
.labels-list {
position: relative;
top: -1.5px;
}
.issue-item-bottom-row {

View File

@@ -107,6 +107,11 @@
--color-expand-button: #3c404d;
--color-placeholder-text: #6a737d;
--color-editor-line-highlight: var(--color-primary-light-5);
--color-project-board-bg: var(--color-secondary-light-2);
}
::-webkit-calendar-picker-indicator {
filter: invert(.8);
}
.ui.horizontal.segments > .segment {
@@ -690,10 +695,6 @@ footer .container .links > * {
border-color: #383c4a;
}
.board-column {
background-color: rgba(0, 0, 0, .2) !important;
}
.tribute-container {
box-shadow: 0 .25rem .5rem rgba(0, 0, 0, .6);
}