Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1112fef93d | ||
|
|
af11549fb2 | ||
|
|
76d6184cd0 | ||
|
|
d644709b22 | ||
|
|
30584a6df8 | ||
|
|
78710946f2 | ||
|
|
22d700edfd | ||
|
|
6782a64a4a | ||
|
|
1ec11ac87e | ||
|
|
2c2a30d6bb | ||
|
|
717b313c34 | ||
|
|
0a32861b28 | ||
|
|
52ca7b9b65 | ||
|
|
e078d08ecd | ||
|
|
a83fb3a83a | ||
|
|
f9b1fac4ea | ||
|
|
f1e8b8c0d7 | ||
|
|
dbbb75712d | ||
|
|
462c6fdee2 | ||
|
|
cead819cb5 | ||
|
|
4fa2804238 | ||
|
|
3ce46a7fbd | ||
|
|
15886ce048 | ||
|
|
a725d31496 | ||
|
|
8e27f6e814 | ||
|
|
54263ff123 | ||
|
|
3bde297121 | ||
|
|
0dfde367c1 | ||
|
|
875501584b | ||
|
|
4190c134e6 | ||
|
|
cae46216e4 | ||
|
|
761111f9ed | ||
|
|
57f1476093 | ||
|
|
bdba89452d | ||
|
|
6e2dacfef6 | ||
|
|
c0869c295a | ||
|
|
a719311f6d | ||
|
|
248b67af6f | ||
|
|
990c6089db | ||
|
|
5da024a019 | ||
|
|
eff2499be7 | ||
|
|
4a3c6384ac | ||
|
|
2b1989e59f | ||
|
|
340c4fc7c7 |
@@ -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."
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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
2
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
32
models/consistency_test.go
Normal file
32
models/consistency_test.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
144
modules/httpcache/httpcache_test.go
Normal file
144
modules/httpcache/httpcache_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
15
vendor/github.com/unrolled/render/.travis.yml
generated
vendored
15
vendor/github.com/unrolled/render/.travis.yml
generated
vendored
@@ -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
|
||||
3
vendor/github.com/unrolled/render/README.md
generated
vendored
3
vendor/github.com/unrolled/render/README.md
generated
vendored
@@ -1,4 +1,5 @@
|
||||
# Render [](http://godoc.org/github.com/unrolled/render) [](https://travis-ci.org/unrolled/render)
|
||||
# Render [](http://godoc.org/github.com/unrolled/render) [](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.
|
||||
|
||||
|
||||
8
vendor/github.com/unrolled/render/buffer.go
generated
vendored
8
vendor/github.com/unrolled/render/buffer.go
generated
vendored
@@ -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)
|
||||
}
|
||||
|
||||
17
vendor/github.com/unrolled/render/engine.go
generated
vendored
17
vendor/github.com/unrolled/render/engine.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
|
||||
9
vendor/github.com/unrolled/render/genericbufferpool.go
generated
vendored
Normal file
9
vendor/github.com/unrolled/render/genericbufferpool.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
package render
|
||||
|
||||
import "bytes"
|
||||
|
||||
// GenericBufferPool abstracts buffer pool implementations
|
||||
type GenericBufferPool interface {
|
||||
Get() *bytes.Buffer
|
||||
Put(*bytes.Buffer)
|
||||
}
|
||||
9
vendor/github.com/unrolled/render/render.go
generated
vendored
9
vendor/github.com/unrolled/render/render.go
generated
vendored
@@ -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
62
vendor/github.com/unrolled/render/sizedbufferpool.go
generated
vendored
Normal 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
2
vendor/modules.txt
vendored
@@ -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
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
self.name = 'eventsource.sharedworker.js';
|
||||
|
||||
const sourcesByUrl = {};
|
||||
const sourcesByPort = {};
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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')}`));
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.dropzone .dz-error-message {
|
||||
top: 120px !important;
|
||||
top: 145px !important;
|
||||
}
|
||||
|
||||
.dropzone .dz-image {
|
||||
|
||||
83
web_src/less/features/projects.less
Normal file
83
web_src/less/features/projects.less
Normal 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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user