Compare commits
44 commits
cd4915dd94
...
784c53032e
Author | SHA1 | Date | |
---|---|---|---|
784c53032e | |||
aa4cf41247 | |||
a677d56548 | |||
5c23058f0a | |||
ab14c47087 | |||
|
6992e72647 | ||
|
1bbf490926 | ||
|
45bdeac730 | ||
|
a32700d0fd | ||
|
a9400ba7a3 | ||
|
9a6d78eaa8 | ||
|
af8151cbb9 | ||
|
ee37edc465 | ||
|
29bbfcc118 | ||
|
f430050d24 | ||
|
510c811574 | ||
|
f93522ddae | ||
|
10c9f96a1e | ||
|
7b60d47c3c | ||
|
265d438a6e | ||
|
93e907de41 | ||
|
f3034b1fd9 | ||
|
d0c74dd2d2 | ||
|
2f91a12143 | ||
|
3ad62127df | ||
|
37e23c982f | ||
|
421d87933b | ||
|
426c0ad14c | ||
|
41a06d2e82 | ||
|
885082f7a7 | ||
|
32999e2511 | ||
|
16d7596635 | ||
|
adc0bcaebb | ||
|
0cca1e079b | ||
|
55c6433fac | ||
|
5b8763476a | ||
|
09c667eb45 | ||
|
791f290c26 | ||
|
58e642c1d6 | ||
|
72d1f9e63e | ||
|
0697075547 | ||
|
f1e07d8c87 | ||
|
443fd27a90 | ||
|
75f128ebf8 |
73 changed files with 1371 additions and 713 deletions
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -4,6 +4,47 @@ This changelog goes through all the changes that have been made in each release
|
||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||||
|
|
||||||
|
## [1.18.1](https://github.com/go-gitea/gitea/releases/tag/v1.18.1) - 2023-01-17
|
||||||
|
|
||||||
|
* API
|
||||||
|
* Add `sync_on_commit` option for push mirrors api (#22271) (#22292)
|
||||||
|
* BUGFIXES
|
||||||
|
* Update `github.com/zeripath/zapx/v15` (#22485)
|
||||||
|
* Fix pull request API field `closed_at` always being `null` (#22482) (#22483)
|
||||||
|
* Fix container blob mount (#22226) (#22476)
|
||||||
|
* Fix error when calculating repository size (#22392) (#22474)
|
||||||
|
* Fix Operator does not exist bug on explore page with ONLY_SHOW_RELEVANT_REPOS (#22454) (#22472)
|
||||||
|
* Fix environments for KaTeX and error reporting (#22453) (#22473)
|
||||||
|
* Remove the netgo tag for Windows build (#22467) (#22468)
|
||||||
|
* Fix migration from GitBucket (#22477) (#22465)
|
||||||
|
* Prevent panic on looking at api "git" endpoints for empty repos (#22457) (#22458)
|
||||||
|
* Fix PR status layout on mobile (#21547) (#22441)
|
||||||
|
* Fix wechatwork webhook sends empty content in PR review (#21762) (#22440)
|
||||||
|
* Remove duplicate "Actions" label in mobile view (#21974) (#22439)
|
||||||
|
* Fix leaving organization bug on user settings -> orgs (#21983) (#22438)
|
||||||
|
* Fixed colour transparency regex matching in project board sorting (#22092) (#22437)
|
||||||
|
* Correctly handle select on multiple channels in Queues (#22146) (#22428)
|
||||||
|
* Prepend refs/heads/ to issue template refs (#20461) (#22427)
|
||||||
|
* Restore function to "Show more" buttons (#22399) (#22426)
|
||||||
|
* Continue GCing other repos on error in one repo (#22422) (#22425)
|
||||||
|
* Allow HOST has no port (#22280) (#22409)
|
||||||
|
* Fix omit avatar_url in discord payload when empty (#22393) (#22394)
|
||||||
|
* Don't display stop watch top bar icon when disabled and hidden when click other place (#22374) (#22387)
|
||||||
|
* Don't lookup mail server when using sendmail (#22300) (#22383)
|
||||||
|
* Fix gravatar disable bug (#22337)
|
||||||
|
* Fix update settings table on install (#22326) (#22327)
|
||||||
|
* Fix sitemap (#22272) (#22320)
|
||||||
|
* Fix code search title translation (#22285) (#22316)
|
||||||
|
* Fix due date rendering the wrong date in issue (#22302) (#22306)
|
||||||
|
* Fix get system setting bug when enabled redis cache (#22298)
|
||||||
|
* Fix bug of DisableGravatar default value (#22297)
|
||||||
|
* Fix key signature error page (#22229) (#22230)
|
||||||
|
* TESTING
|
||||||
|
* Remove test session cache to reduce possible concurrent problem (#22199) (#22429)
|
||||||
|
* MISC
|
||||||
|
* Restore previous official review when an official review is deleted (#22449) (#22460)
|
||||||
|
* Log STDERR of external renderer when it fails (#22442) (#22444)
|
||||||
|
|
||||||
## [1.18.0](https://github.com/go-gitea/gitea/releases/tag/1.18.0) - 2022-12-22
|
## [1.18.0](https://github.com/go-gitea/gitea/releases/tag/1.18.0) - 2022-12-22
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -740,9 +740,9 @@ $(DIST_DIRS):
|
||||||
|
|
||||||
.PHONY: release-windows
|
.PHONY: release-windows
|
||||||
release-windows: | $(DIST_DIRS)
|
release-windows: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||||
ifeq (,$(findstring gogit,$(TAGS)))
|
ifeq (,$(findstring gogit,$(TAGS)))
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||||
endif
|
endif
|
||||||
ifeq ($(CI),true)
|
ifeq ($(CI),true)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
|
|
2
assets/emoji.json
generated
2
assets/emoji.json
generated
File diff suppressed because one or more lines are too long
|
@ -26,7 +26,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||||
maxUnicodeVersion = 12
|
maxUnicodeVersion = 14
|
||||||
)
|
)
|
||||||
|
|
||||||
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
|
||||||
|
|
|
@ -735,9 +735,9 @@ and
|
||||||
|
|
||||||
- `GRAVATAR_SOURCE`: **gravatar**: Can be `gravatar`, `duoshuo` or anything like
|
- `GRAVATAR_SOURCE`: **gravatar**: Can be `gravatar`, `duoshuo` or anything like
|
||||||
`http://cn.gravatar.com/avatar/`.
|
`http://cn.gravatar.com/avatar/`.
|
||||||
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only.
|
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure.
|
||||||
- `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see
|
- `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see
|
||||||
[http://www.libravatar.org](http://www.libravatar.org)).
|
[http://www.libravatar.org](http://www.libravatar.org)). **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure.
|
||||||
|
|
||||||
- `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`.
|
- `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`.
|
||||||
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.
|
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -302,7 +302,7 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
|
||||||
|
|
||||||
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
|
replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
|
|
||||||
replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix
|
replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2
|
||||||
|
|
||||||
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
exclude github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
|
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1482,8 +1482,8 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.m
|
||||||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix h1:fKZ9OxEDoJKgM0KBXRbSb5IgKUEXis6C3zEIiMtzzQ0=
|
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2 h1:IRB+69BV7fTT5ccw35ca7TCBe2b7dm5Q5y5tUMQmCvU=
|
||||||
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
|
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||||
|
|
|
@ -68,8 +68,16 @@ func (key *GPGKey) PaddedKeyID() string {
|
||||||
if len(key.KeyID) > 15 {
|
if len(key.KeyID) > 15 {
|
||||||
return key.KeyID
|
return key.KeyID
|
||||||
}
|
}
|
||||||
|
return PaddedKeyID(key.KeyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaddedKeyID show KeyID padded to 16 characters
|
||||||
|
func PaddedKeyID(keyID string) string {
|
||||||
|
if len(keyID) > 15 {
|
||||||
|
return keyID
|
||||||
|
}
|
||||||
zeros := "0000000000000000"
|
zeros := "0000000000000000"
|
||||||
return zeros[0:16-len(key.KeyID)] + key.KeyID
|
return zeros[0:16-len(keyID)] + keyID
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||||
|
|
|
@ -154,8 +154,7 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
||||||
return DefaultAvatarLink()
|
return DefaultAvatarLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatar := system_model.GetSettingBool(system_model.KeyPictureEnableFederatedAvatar)
|
||||||
enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||||
|
@ -176,9 +175,7 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
|
||||||
return urlStr
|
return urlStr
|
||||||
}
|
}
|
||||||
|
|
||||||
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||||
|
|
||||||
disableGravatar := disableGravatarSetting.GetValueBool()
|
|
||||||
if !disableGravatar {
|
if !disableGravatar {
|
||||||
// copy GravatarSourceURL, because we will modify its Path.
|
// copy GravatarSourceURL, because we will modify its Path.
|
||||||
avatarURLCopy := *system_model.GravatarSourceURL
|
avatarURLCopy := *system_model.GravatarSourceURL
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
fork_id: 0
|
fork_id: 0
|
||||||
is_template: false
|
is_template: false
|
||||||
template_id: 0
|
template_id: 0
|
||||||
size: 0
|
size: 6708
|
||||||
is_fsck_enabled: true
|
is_fsck_enabled: true
|
||||||
close_issues_via_commit_in_any_branch: false
|
close_issues_via_commit_in_any_branch: false
|
||||||
|
|
||||||
|
|
|
@ -742,17 +742,9 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if official {
|
} else if official {
|
||||||
// recalculate the latest official review for reviewer
|
if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil {
|
||||||
review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
|
|
||||||
if err != nil && !IsErrReviewNotExist(err) {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if review != nil {
|
|
||||||
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
|
comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
|
||||||
|
@ -770,6 +762,22 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
|
||||||
return comment, committer.Commit()
|
return comment, committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recalculate the latest official review for reviewer
|
||||||
|
func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64) error {
|
||||||
|
review, err := GetReviewByIssueIDAndUserID(ctx, issueID, reviewerID)
|
||||||
|
if err != nil && !IsErrReviewNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if review != nil {
|
||||||
|
if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddTeamReviewRequest add a review request from one team
|
// AddTeamReviewRequest add a review request from one team
|
||||||
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
|
||||||
ctx, committer, err := db.TxContext()
|
ctx, committer, err := db.TxContext()
|
||||||
|
@ -988,6 +996,12 @@ func DeleteReview(r *Review) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.Official {
|
||||||
|
if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,3 +201,38 @@ func TestDismissReview(t *testing.T) {
|
||||||
assert.False(t, requestReviewExample.Dismissed)
|
assert.False(t, requestReviewExample.Dismissed)
|
||||||
assert.True(t, approveReviewExample.Dismissed)
|
assert.True(t, approveReviewExample.Dismissed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeleteReview(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
review1, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
|
||||||
|
Content: "Official rejection",
|
||||||
|
Type: issues_model.ReviewTypeReject,
|
||||||
|
Official: false,
|
||||||
|
Issue: issue,
|
||||||
|
Reviewer: user,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
review2, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
|
||||||
|
Content: "Official approval",
|
||||||
|
Type: issues_model.ReviewTypeApprove,
|
||||||
|
Official: true,
|
||||||
|
Issue: issue,
|
||||||
|
Reviewer: user,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, issues_model.DeleteReview(review2))
|
||||||
|
|
||||||
|
_, err = issues_model.GetReviewByID(db.DefaultContext, review2.ID)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, issues_model.IsErrReviewNotExist(err), "IsErrReviewNotExist")
|
||||||
|
|
||||||
|
review1, err = issues_model.GetReviewByID(db.DefaultContext, review1.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, review1.Official)
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ type BlobSearchOptions struct {
|
||||||
Digest string
|
Digest string
|
||||||
Tag string
|
Tag string
|
||||||
IsManifest bool
|
IsManifest bool
|
||||||
|
Repository string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
||||||
|
@ -54,6 +55,15 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
|
||||||
|
|
||||||
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||||
}
|
}
|
||||||
|
if opts.Repository != "" {
|
||||||
|
var propsCond builder.Cond = builder.Eq{
|
||||||
|
"package_property.ref_type": packages.PropertyTypePackage,
|
||||||
|
"package_property.name": container_module.PropertyRepository,
|
||||||
|
"package_property.value": opts.Repository,
|
||||||
|
}
|
||||||
|
|
||||||
|
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||||
|
}
|
||||||
|
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -498,8 +499,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||||
// Only show a repo that either has a topic or description.
|
// Only show a repo that either has a topic or description.
|
||||||
subQueryCond := builder.NewCond()
|
subQueryCond := builder.NewCond()
|
||||||
|
|
||||||
// Topic checking. Topics is non-null.
|
// Topic checking. Topics are present.
|
||||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
if setting.Database.UsePostgreSQL { // postgres stores the topics as json and not as text
|
||||||
|
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
|
||||||
|
} else {
|
||||||
|
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||||
|
}
|
||||||
|
|
||||||
// Description checking. Description not empty.
|
// Description checking. Description not empty.
|
||||||
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
|
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
|
||||||
|
|
|
@ -185,7 +185,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
|
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||||
func UpdateRepoSize(ctx context.Context, repoID, size int64) error {
|
func UpdateRepoSize(ctx context.Context, repoID, size int64) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{
|
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{
|
||||||
Size: size,
|
Size: size,
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
setting_module "code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"strk.kbt.io/projects/go/libravatar"
|
"strk.kbt.io/projects/go/libravatar"
|
||||||
|
@ -89,17 +89,17 @@ func GetSettingNoCache(key string) (*Setting, error) {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
return nil, ErrSettingIsNotExist{key}
|
return nil, ErrSettingIsNotExist{key}
|
||||||
}
|
}
|
||||||
return v[key], nil
|
return v[strings.ToLower(key)], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSetting returns the setting value via the key
|
// GetSetting returns the setting value via the key
|
||||||
func GetSetting(key string) (*Setting, error) {
|
func GetSetting(key string) (string, error) {
|
||||||
return cache.Get(genSettingCacheKey(key), func() (*Setting, error) {
|
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
|
||||||
res, err := GetSettingNoCache(key)
|
res, err := GetSettingNoCache(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res.SettingValue, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,8 @@ func GetSetting(key string) (*Setting, error) {
|
||||||
// none existing keys and errors are ignored and result in false
|
// none existing keys and errors are ignored and result in false
|
||||||
func GetSettingBool(key string) bool {
|
func GetSettingBool(key string) bool {
|
||||||
s, _ := GetSetting(key)
|
s, _ := GetSetting(key)
|
||||||
return s.GetValueBool()
|
v, _ := strconv.ParseBool(s)
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSettings returns specific settings
|
// GetSettings returns specific settings
|
||||||
|
@ -131,7 +132,7 @@ func GetSettings(keys []string) (map[string]*Setting, error) {
|
||||||
type AllSettings map[string]*Setting
|
type AllSettings map[string]*Setting
|
||||||
|
|
||||||
func (settings AllSettings) Get(key string) Setting {
|
func (settings AllSettings) Get(key string) Setting {
|
||||||
if v, ok := settings[key]; ok {
|
if v, ok := settings[strings.ToLower(key)]; ok {
|
||||||
return *v
|
return *v
|
||||||
}
|
}
|
||||||
return Setting{}
|
return Setting{}
|
||||||
|
@ -184,14 +185,17 @@ func SetSettingNoVersion(key, value string) error {
|
||||||
|
|
||||||
// SetSetting updates a users' setting for a specific key
|
// SetSetting updates a users' setting for a specific key
|
||||||
func SetSetting(setting *Setting) error {
|
func SetSetting(setting *Setting) error {
|
||||||
_, err := cache.Set(genSettingCacheKey(setting.SettingKey), func() (*Setting, error) {
|
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
|
||||||
return setting, upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.Version++
|
setting.Version++
|
||||||
|
|
||||||
|
cc := cache.GetCache()
|
||||||
|
if cc != nil {
|
||||||
|
return cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds())
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +247,7 @@ func Init() error {
|
||||||
var disableGravatar bool
|
var disableGravatar bool
|
||||||
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
|
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
disableGravatar = setting.GetDefaultDisableGravatar()
|
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -254,7 +258,7 @@ func Init() error {
|
||||||
var enableFederatedAvatar bool
|
var enableFederatedAvatar bool
|
||||||
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
|
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
|
||||||
if IsErrSettingIsNotExist(err) {
|
if IsErrSettingIsNotExist(err) {
|
||||||
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
|
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -262,20 +266,20 @@ func Init() error {
|
||||||
enableFederatedAvatar = disableGravatarSetting.GetValueBool()
|
enableFederatedAvatar = disableGravatarSetting.GetValueBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.OfflineMode {
|
if setting_module.OfflineMode {
|
||||||
disableGravatar = true
|
disableGravatar = true
|
||||||
enableFederatedAvatar = false
|
enableFederatedAvatar = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if disableGravatar || !enableFederatedAvatar {
|
if enableFederatedAvatar || !disableGravatar {
|
||||||
var err error
|
var err error
|
||||||
GravatarSourceURL, err = url.Parse(setting.GravatarSource)
|
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting.GravatarSource, err)
|
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if enableFederatedAvatarSetting.GetValueBool() {
|
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() {
|
||||||
LibravatarService = libravatar.New()
|
LibravatarService = libravatar.New()
|
||||||
if GravatarSourceURL.Scheme == "https" {
|
if GravatarSourceURL.Scheme == "https" {
|
||||||
LibravatarService.SetUseHTTPS(true)
|
LibravatarService.SetUseHTTPS(true)
|
||||||
|
|
|
@ -34,10 +34,14 @@ func TestSettings(t *testing.T) {
|
||||||
assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
|
assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
|
||||||
|
|
||||||
// updated setting
|
// updated setting
|
||||||
updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.Version}
|
updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: settings[strings.ToLower(keyName)].Version}
|
||||||
err = system.SetSetting(updatedSetting)
|
err = system.SetSetting(updatedSetting)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
value, err := system.GetSetting(keyName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, updatedSetting.SettingValue, value)
|
||||||
|
|
||||||
// get all settings
|
// get all settings
|
||||||
settings, err = system.GetAllSettings()
|
settings, err = system.GetAllSettings()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -68,9 +68,7 @@ func (u *User) AvatarLinkWithSize(size int) string {
|
||||||
useLocalAvatar := false
|
useLocalAvatar := false
|
||||||
autoGenerateAvatar := false
|
autoGenerateAvatar := false
|
||||||
|
|
||||||
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
|
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
|
||||||
|
|
||||||
disableGravatar := disableGravatarSetting.GetValueBool()
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case u.UseCustomAvatar:
|
case u.UseCustomAvatar:
|
||||||
|
|
|
@ -54,13 +54,13 @@ func genSettingCacheKey(userID int64, key string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSetting returns the setting value via the key
|
// GetSetting returns the setting value via the key
|
||||||
func GetSetting(uid int64, key string) (*Setting, error) {
|
func GetSetting(uid int64, key string) (string, error) {
|
||||||
return cache.Get(genSettingCacheKey(uid, key), func() (*Setting, error) {
|
return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) {
|
||||||
res, err := GetSettingNoCache(uid, key)
|
res, err := GetSettingNoCache(uid, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res.SettingValue, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ func SetUserSetting(userID int64, key, value string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
|
_, err := cache.GetString(genSettingCacheKey(userID, key), func() (string, error) {
|
||||||
return value, upsertUserSettingValue(userID, key, value)
|
return value, upsertUserSettingValue(userID, key, value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
33
modules/cache/cache.go
vendored
33
modules/cache/cache.go
vendored
|
@ -46,39 +46,6 @@ func GetCache() mc.Cache {
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the key value from cache with callback when no key exists in cache
|
|
||||||
func Get[V interface{}](key string, getFunc func() (V, error)) (V, error) {
|
|
||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
|
||||||
return getFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
cached := conn.Get(key)
|
|
||||||
if value, ok := cached.(V); ok {
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := getFunc()
|
|
||||||
if err != nil {
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set updates and returns the key value in the cache with callback. The old value is only removed if the updateFunc() is successful
|
|
||||||
func Set[V interface{}](key string, valueFunc func() (V, error)) (V, error) {
|
|
||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
|
||||||
return valueFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := valueFunc()
|
|
||||||
if err != nil {
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the key value from cache with callback when no key exists in cache
|
// GetString returns the key value from cache with callback when no key exists in cache
|
||||||
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
func GetString(key string, getFunc func() (string, error)) (string, error) {
|
||||||
if conn == nil || setting.CacheService.TTL == 0 {
|
if conn == nil || setting.CacheService.TTL == 0 {
|
||||||
|
|
|
@ -1087,6 +1087,9 @@ func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplat
|
||||||
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
|
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
|
||||||
invalidFiles[fullName] = err
|
invalidFiles[fullName] = err
|
||||||
} else {
|
} else {
|
||||||
|
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
|
||||||
|
it.Ref = git.BranchPrefix + it.Ref
|
||||||
|
}
|
||||||
issueTemplates = append(issueTemplates, it)
|
issueTemplates = append(issueTemplates, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,10 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr.Issue.ClosedUnix != 0 {
|
||||||
|
apiPullRequest.Closed = pr.Issue.ClosedUnix.AsTimePtr()
|
||||||
|
}
|
||||||
|
|
||||||
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
|
gitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
|
log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -100,6 +100,9 @@ func RefURL(repoURL, ref string) string {
|
||||||
return repoURL + "/src/branch/" + refName
|
return repoURL + "/src/branch/" + refName
|
||||||
case strings.HasPrefix(ref, TagPrefix):
|
case strings.HasPrefix(ref, TagPrefix):
|
||||||
return repoURL + "/src/tag/" + refName
|
return repoURL + "/src/tag/" + refName
|
||||||
|
case !IsValidSHAPattern(ref):
|
||||||
|
// assume they mean a branch
|
||||||
|
return repoURL + "/src/branch/" + refName
|
||||||
default:
|
default:
|
||||||
return repoURL + "/src/commit/" + refName
|
return repoURL + "/src/commit/" + refName
|
||||||
}
|
}
|
||||||
|
|
5
modules/markup/external/external.go
vendored
5
modules/markup/external/external.go
vendored
|
@ -5,6 +5,7 @@
|
||||||
package external
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -133,11 +134,13 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
|
||||||
if !p.IsInputFile {
|
if !p.IsInputFile {
|
||||||
cmd.Stdin = input
|
cmd.Stdin = input
|
||||||
}
|
}
|
||||||
|
var stderr bytes.Buffer
|
||||||
cmd.Stdout = output
|
cmd.Stdout = output
|
||||||
|
cmd.Stderr = &stderr
|
||||||
process.SetSysProcAttribute(cmd)
|
process.SetSysProcAttribute(cmd)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return fmt.Errorf("%s render run command %s %v failed: %w", p.Name(), commands[0], args, err)
|
return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,32 +110,6 @@ func (q *ChannelQueue) Flush(timeout time.Duration) error {
|
||||||
return q.FlushWithContext(ctx)
|
return q.FlushWithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
|
||||||
func (q *ChannelQueue) FlushWithContext(ctx context.Context) error {
|
|
||||||
log.Trace("ChannelQueue: %d Flush", q.qid)
|
|
||||||
paused, _ := q.IsPausedIsResumed()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-paused:
|
|
||||||
return nil
|
|
||||||
case data, ok := <-q.dataChan:
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if unhandled := q.handle(data); unhandled != nil {
|
|
||||||
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
|
|
||||||
}
|
|
||||||
atomic.AddInt64(&q.numInQueue, -1)
|
|
||||||
case <-q.baseCtx.Done():
|
|
||||||
return q.baseCtx.Err()
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown processing from this queue
|
// Shutdown processing from this queue
|
||||||
func (q *ChannelQueue) Shutdown() {
|
func (q *ChannelQueue) Shutdown() {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
@ -168,35 +167,6 @@ func (q *ChannelUniqueQueue) Flush(timeout time.Duration) error {
|
||||||
return q.FlushWithContext(ctx)
|
return q.FlushWithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
|
||||||
func (q *ChannelUniqueQueue) FlushWithContext(ctx context.Context) error {
|
|
||||||
log.Trace("ChannelUniqueQueue: %d Flush", q.qid)
|
|
||||||
paused, _ := q.IsPausedIsResumed()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-paused:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case data, ok := <-q.dataChan:
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if unhandled := q.handle(data); unhandled != nil {
|
|
||||||
log.Error("Unhandled Data whilst flushing queue %d", q.qid)
|
|
||||||
}
|
|
||||||
atomic.AddInt64(&q.numInQueue, -1)
|
|
||||||
case <-q.baseCtx.Done():
|
|
||||||
return q.baseCtx.Err()
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown processing from this queue
|
// Shutdown processing from this queue
|
||||||
func (q *ChannelUniqueQueue) Shutdown() {
|
func (q *ChannelUniqueQueue) Shutdown() {
|
||||||
log.Trace("ChannelUniqueQueue: %s Shutting down", q.name)
|
log.Trace("ChannelUniqueQueue: %s Shutting down", q.name)
|
||||||
|
|
|
@ -464,13 +464,43 @@ func (p *WorkerPool) IsEmpty() bool {
|
||||||
return atomic.LoadInt64(&p.numInQueue) == 0
|
return atomic.LoadInt64(&p.numInQueue) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// contextError returns either ctx.Done(), the base context's error or nil
|
||||||
|
func (p *WorkerPool) contextError(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-p.baseCtx.Done():
|
||||||
|
return p.baseCtx.Err()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
// FlushWithContext is very similar to CleanUp but it will return as soon as the dataChan is empty
|
||||||
// NB: The worker will not be registered with the manager.
|
// NB: The worker will not be registered with the manager.
|
||||||
func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
|
func (p *WorkerPool) FlushWithContext(ctx context.Context) error {
|
||||||
log.Trace("WorkerPool: %d Flush", p.qid)
|
log.Trace("WorkerPool: %d Flush", p.qid)
|
||||||
|
paused, _ := p.IsPausedIsResumed()
|
||||||
for {
|
for {
|
||||||
|
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
|
||||||
select {
|
select {
|
||||||
case data := <-p.dataChan:
|
case <-paused:
|
||||||
|
// Ensure that even if paused that the cancelled error is still sent
|
||||||
|
return p.contextError(ctx)
|
||||||
|
case <-p.baseCtx.Done():
|
||||||
|
return p.baseCtx.Err()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-paused:
|
||||||
|
return p.contextError(ctx)
|
||||||
|
case data, ok := <-p.dataChan:
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if unhandled := p.handle(data); unhandled != nil {
|
if unhandled := p.handle(data); unhandled != nil {
|
||||||
log.Error("Unhandled Data whilst flushing queue %d", p.qid)
|
log.Error("Unhandled Data whilst flushing queue %d", p.qid)
|
||||||
}
|
}
|
||||||
|
@ -496,6 +526,7 @@ func (p *WorkerPool) doWork(ctx context.Context) {
|
||||||
paused, _ := p.IsPausedIsResumed()
|
paused, _ := p.IsPausedIsResumed()
|
||||||
data := make([]Data, 0, p.batchLength)
|
data := make([]Data, 0, p.batchLength)
|
||||||
for {
|
for {
|
||||||
|
// Because select will return any case that is satisified at random we precheck here before looking at dataChan.
|
||||||
select {
|
select {
|
||||||
case <-paused:
|
case <-paused:
|
||||||
log.Trace("Worker for Queue %d Pausing", p.qid)
|
log.Trace("Worker for Queue %d Pausing", p.qid)
|
||||||
|
@ -516,8 +547,19 @@ func (p *WorkerPool) doWork(ctx context.Context) {
|
||||||
log.Trace("Worker shutting down")
|
log.Trace("Worker shutting down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
if len(data) > 0 {
|
||||||
|
log.Trace("Handling: %d data, %v", len(data), data)
|
||||||
|
if unhandled := p.handle(data...); unhandled != nil {
|
||||||
|
log.Error("Unhandled Data in queue %d", p.qid)
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&p.numInQueue, -1*int64(len(data)))
|
||||||
|
}
|
||||||
|
log.Trace("Worker shutting down")
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-paused:
|
case <-paused:
|
||||||
// go back around
|
// go back around
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
@ -286,9 +287,36 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
|
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
||||||
|
|
||||||
|
// getDirectorySize returns the disk consumption for a given path
|
||||||
|
func getDirectorySize(path string) (int64, error) {
|
||||||
|
var size int64
|
||||||
|
err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, err := info.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (f.Mode() & notRegularFileMode) == 0 {
|
||||||
|
size += f.Size()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||||
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
|
func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
size, err := util.GetDirectorySize(repo.RepoPath())
|
size, err := getDirectorySize(repo.RepoPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("updateSize: %w", err)
|
return fmt.Errorf("updateSize: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,3 +169,13 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, act.IsPrivate)
|
assert.True(t, act.IsPrivate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDirectorySize(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
repo, err := repo_model.GetRepositoryByID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
size, err := getDirectorySize(repo.RepoPath())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, size, repo.Size)
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
|
ini "gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mailer represents mail service.
|
// Mailer represents mail service.
|
||||||
|
@ -50,8 +51,8 @@ type Mailer struct {
|
||||||
// MailService the global mailer
|
// MailService the global mailer
|
||||||
var MailService *Mailer
|
var MailService *Mailer
|
||||||
|
|
||||||
func newMailService() {
|
func parseMailerConfig(rootCfg *ini.File) {
|
||||||
sec := Cfg.Section("mailer")
|
sec := rootCfg.Section("mailer")
|
||||||
// Check mailer setting.
|
// Check mailer setting.
|
||||||
if !sec.Key("ENABLED").MustBool() {
|
if !sec.Key("ENABLED").MustBool() {
|
||||||
return
|
return
|
||||||
|
@ -71,9 +72,14 @@ func newMailService() {
|
||||||
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
|
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
|
||||||
givenHost := sec.Key("HOST").String()
|
givenHost := sec.Key("HOST").String()
|
||||||
addr, port, err := net.SplitHostPort(givenHost)
|
addr, port, err := net.SplitHostPort(givenHost)
|
||||||
if err != nil {
|
if err != nil && strings.Contains(err.Error(), "missing port in address") {
|
||||||
|
addr = givenHost
|
||||||
|
} else if err != nil {
|
||||||
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
||||||
}
|
}
|
||||||
|
if addr == "" {
|
||||||
|
addr = "127.0.0.1"
|
||||||
|
}
|
||||||
sec.Key("SMTP_ADDR").MustString(addr)
|
sec.Key("SMTP_ADDR").MustString(addr)
|
||||||
sec.Key("SMTP_PORT").MustString(port)
|
sec.Key("SMTP_PORT").MustString(port)
|
||||||
}
|
}
|
||||||
|
@ -173,20 +179,34 @@ func newMailService() {
|
||||||
default:
|
default:
|
||||||
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
||||||
MailService.Protocol = "smtps"
|
MailService.Protocol = "smtps"
|
||||||
|
if MailService.SMTPPort == "" {
|
||||||
|
MailService.SMTPPort = "465"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to warn if users use SMTP on a non-local IP;
|
// we want to warn if users use SMTP on a non-local IP;
|
||||||
// we might as well take the opportunity to check that it has an IP at all
|
// we might as well take the opportunity to check that it has an IP at all
|
||||||
ips := tryResolveAddr(MailService.SMTPAddr)
|
// This check is not needed for sendmail
|
||||||
if MailService.Protocol == "smtp" {
|
switch MailService.Protocol {
|
||||||
for _, ip := range ips {
|
case "sendmail":
|
||||||
if !ip.IsLoopback() {
|
var err error
|
||||||
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
|
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
|
||||||
break
|
if err != nil {
|
||||||
|
log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
|
||||||
|
}
|
||||||
|
case "smtp", "smtps", "smtp+starttls", "smtp+unix":
|
||||||
|
ips := tryResolveAddr(MailService.SMTPAddr)
|
||||||
|
if MailService.Protocol == "smtp" {
|
||||||
|
for _, ip := range ips {
|
||||||
|
if !ip.IsLoopback() {
|
||||||
|
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "dummy": // just mention and do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
if MailService.From != "" {
|
if MailService.From != "" {
|
||||||
|
@ -215,14 +235,6 @@ func newMailService() {
|
||||||
MailService.EnvelopeFrom = parsed.Address
|
MailService.EnvelopeFrom = parsed.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
if MailService.Protocol == "sendmail" {
|
|
||||||
var err error
|
|
||||||
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to parse Sendmail args: %s with error %v", CustomConf, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Mail Service Enabled")
|
log.Info("Mail Service Enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
modules/setting/mailer_test.go
Normal file
43
modules/setting/mailer_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
ini "gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseMailerConfig(t *testing.T) {
|
||||||
|
iniFile := ini.Empty()
|
||||||
|
kases := map[string]*Mailer{
|
||||||
|
"smtp.mydomain.com": {
|
||||||
|
SMTPAddr: "smtp.mydomain.com",
|
||||||
|
SMTPPort: "465",
|
||||||
|
},
|
||||||
|
"smtp.mydomain.com:123": {
|
||||||
|
SMTPAddr: "smtp.mydomain.com",
|
||||||
|
SMTPPort: "123",
|
||||||
|
},
|
||||||
|
":123": {
|
||||||
|
SMTPAddr: "127.0.0.1",
|
||||||
|
SMTPPort: "123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for host, kase := range kases {
|
||||||
|
t.Run(host, func(t *testing.T) {
|
||||||
|
iniFile.DeleteSection("mailer")
|
||||||
|
sec := iniFile.Section("mailer")
|
||||||
|
sec.NewKey("ENABLED", "true")
|
||||||
|
sec.NewKey("HOST", host)
|
||||||
|
|
||||||
|
// Check mailer setting
|
||||||
|
parseMailerConfig(iniFile)
|
||||||
|
|
||||||
|
assert.EqualValues(t, kase.SMTPAddr, MailService.SMTPAddr)
|
||||||
|
assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ func newPictureService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultDisableGravatar() bool {
|
func GetDefaultDisableGravatar() bool {
|
||||||
return !OfflineMode
|
return OfflineMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool {
|
func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool {
|
||||||
|
|
|
@ -1334,7 +1334,7 @@ func NewServices() {
|
||||||
newCacheService()
|
newCacheService()
|
||||||
newSessionService()
|
newSessionService()
|
||||||
newCORSService()
|
newCORSService()
|
||||||
newMailService()
|
parseMailerConfig(Cfg)
|
||||||
newRegisterMailService()
|
newRegisterMailService()
|
||||||
newNotifyMailService()
|
newNotifyMailService()
|
||||||
newProxyService()
|
newProxyService()
|
||||||
|
@ -1351,5 +1351,5 @@ func NewServices() {
|
||||||
// NewServicesForInstall initializes the services for install
|
// NewServicesForInstall initializes the services for install
|
||||||
func NewServicesForInstall() {
|
func NewServicesForInstall() {
|
||||||
newService()
|
newService()
|
||||||
newMailService()
|
parseMailerConfig(Cfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,48 +12,62 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sitemapFileLimit contains the maximum size of a sitemap file
|
const (
|
||||||
const sitemapFileLimit = 50 * 1024 * 1024
|
sitemapFileLimit = 50 * 1024 * 1024 // the maximum size of a sitemap file
|
||||||
|
urlsLimit = 50000
|
||||||
|
|
||||||
// Url represents a single sitemap entry
|
schemaURL = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
urlsetName = "urlset"
|
||||||
|
sitemapindexName = "sitemapindex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URL represents a single sitemap entry
|
||||||
type URL struct {
|
type URL struct {
|
||||||
URL string `xml:"loc"`
|
URL string `xml:"loc"`
|
||||||
LastMod *time.Time `xml:"lastmod,omitempty"`
|
LastMod *time.Time `xml:"lastmod,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SitemapUrl represents a sitemap
|
// Sitemap represents a sitemap
|
||||||
type Sitemap struct {
|
type Sitemap struct {
|
||||||
XMLName xml.Name
|
XMLName xml.Name
|
||||||
Namespace string `xml:"xmlns,attr"`
|
Namespace string `xml:"xmlns,attr"`
|
||||||
|
|
||||||
URLs []URL `xml:"url"`
|
URLs []URL `xml:"url"`
|
||||||
|
Sitemaps []URL `xml:"sitemap"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSitemap creates a sitemap
|
// NewSitemap creates a sitemap
|
||||||
func NewSitemap() *Sitemap {
|
func NewSitemap() *Sitemap {
|
||||||
return &Sitemap{
|
return &Sitemap{
|
||||||
XMLName: xml.Name{Local: "urlset"},
|
XMLName: xml.Name{Local: urlsetName},
|
||||||
Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
Namespace: schemaURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSitemap creates a sitemap index.
|
// NewSitemapIndex creates a sitemap index.
|
||||||
func NewSitemapIndex() *Sitemap {
|
func NewSitemapIndex() *Sitemap {
|
||||||
return &Sitemap{
|
return &Sitemap{
|
||||||
XMLName: xml.Name{Local: "sitemapindex"},
|
XMLName: xml.Name{Local: sitemapindexName},
|
||||||
Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
Namespace: schemaURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a URL to the sitemap
|
// Add adds a URL to the sitemap
|
||||||
func (s *Sitemap) Add(u URL) {
|
func (s *Sitemap) Add(u URL) {
|
||||||
s.URLs = append(s.URLs, u)
|
if s.XMLName.Local == sitemapindexName {
|
||||||
|
s.Sitemaps = append(s.Sitemaps, u)
|
||||||
|
} else {
|
||||||
|
s.URLs = append(s.URLs, u)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the sitemap to a response
|
// WriteTo writes the sitemap to a response
|
||||||
func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
||||||
if len(s.URLs) > 50000 {
|
if l := len(s.URLs); l > urlsLimit {
|
||||||
return 0, fmt.Errorf("The sitemap contains too many URLs: %d", len(s.URLs))
|
return 0, fmt.Errorf("The sitemap contains %d URLs, but only %d are allowed", l, urlsLimit)
|
||||||
|
}
|
||||||
|
if l := len(s.Sitemaps); l > urlsLimit {
|
||||||
|
return 0, fmt.Errorf("The sitemap contains %d sub-sitemaps, but only %d are allowed", l, urlsLimit)
|
||||||
}
|
}
|
||||||
buf := bytes.NewBufferString(xml.Header)
|
buf := bytes.NewBufferString(xml.Header)
|
||||||
if err := xml.NewEncoder(buf).Encode(s); err != nil {
|
if err := xml.NewEncoder(buf).Encode(s); err != nil {
|
||||||
|
@ -63,7 +77,7 @@ func (s *Sitemap) WriteTo(w io.Writer) (int64, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if buf.Len() > sitemapFileLimit {
|
if buf.Len() > sitemapFileLimit {
|
||||||
return 0, fmt.Errorf("The sitemap is too big: %d", buf.Len())
|
return 0, fmt.Errorf("The sitemap has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit)
|
||||||
}
|
}
|
||||||
return buf.WriteTo(w)
|
return buf.WriteTo(w)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ package sitemap
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,63 +14,154 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOk(t *testing.T) {
|
func TestNewSitemap(t *testing.T) {
|
||||||
testReal := func(s *Sitemap, name string, urls []URL, expected string) {
|
|
||||||
for _, url := range urls {
|
|
||||||
s.Add(url)
|
|
||||||
}
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
_, err := s.WriteTo(buf)
|
|
||||||
assert.NoError(t, nil, err)
|
|
||||||
assert.Equal(t, xml.Header+"<"+name+" xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"+expected+"</"+name+">\n", buf.String())
|
|
||||||
}
|
|
||||||
test := func(urls []URL, expected string) {
|
|
||||||
testReal(NewSitemap(), "urlset", urls, expected)
|
|
||||||
testReal(NewSitemapIndex(), "sitemapindex", urls, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Unix(1651322008, 0).UTC()
|
ts := time.Unix(1651322008, 0).UTC()
|
||||||
|
|
||||||
test(
|
tests := []struct {
|
||||||
[]URL{},
|
name string
|
||||||
"",
|
urls []URL
|
||||||
)
|
want string
|
||||||
test(
|
wantErr string
|
||||||
[]URL{
|
}{
|
||||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
{
|
||||||
|
name: "empty",
|
||||||
|
urls: []URL{},
|
||||||
|
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"" +
|
||||||
|
"</urlset>\n",
|
||||||
},
|
},
|
||||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>",
|
{
|
||||||
)
|
name: "regular",
|
||||||
test(
|
urls: []URL{
|
||||||
[]URL{
|
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
},
|
||||||
|
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
|
||||||
|
"</urlset>\n",
|
||||||
},
|
},
|
||||||
"<url><loc>https://gitea.io/test2</loc></url>",
|
{
|
||||||
)
|
name: "without lastmod",
|
||||||
test(
|
urls: []URL{
|
||||||
[]URL{
|
{URL: "https://gitea.io/test1"},
|
||||||
{URL: "https://gitea.io/test1", LastMod: &ts},
|
},
|
||||||
{URL: "https://gitea.io/test2", LastMod: nil},
|
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<url><loc>https://gitea.io/test1</loc></url>" +
|
||||||
|
"</urlset>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||||
|
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||||
|
},
|
||||||
|
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" +
|
||||||
|
"<url><loc>https://gitea.io/test2</loc></url>" +
|
||||||
|
"</urlset>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too many urls",
|
||||||
|
urls: make([]URL, 50001),
|
||||||
|
wantErr: "The sitemap contains 50001 URLs, but only 50000 are allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too big file",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||||
|
},
|
||||||
|
wantErr: "The sitemap has 52428932 bytes, but only 52428800 are allowed",
|
||||||
},
|
},
|
||||||
"<url><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>"+
|
|
||||||
"<url><loc>https://gitea.io/test2</loc></url>",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTooManyURLs(t *testing.T) {
|
|
||||||
s := NewSitemap()
|
|
||||||
for i := 0; i < 50001; i++ {
|
|
||||||
s.Add(URL{URL: fmt.Sprintf("https://gitea.io/test%d", i)})
|
|
||||||
}
|
}
|
||||||
buf := &bytes.Buffer{}
|
for _, tt := range tests {
|
||||||
_, err := s.WriteTo(buf)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.EqualError(t, err, "The sitemap contains too many URLs: 50001")
|
s := NewSitemap()
|
||||||
|
for _, url := range tt.urls {
|
||||||
|
s.Add(url)
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
_, err := s.WriteTo(buf)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equalf(t, tt.want, buf.String(), "NewSitemap()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSitemapTooBig(t *testing.T) {
|
func TestNewSitemapIndex(t *testing.T) {
|
||||||
s := NewSitemap()
|
ts := time.Unix(1651322008, 0).UTC()
|
||||||
s.Add(URL{URL: strings.Repeat("b", sitemapFileLimit)})
|
|
||||||
buf := &bytes.Buffer{}
|
tests := []struct {
|
||||||
_, err := s.WriteTo(buf)
|
name string
|
||||||
assert.EqualError(t, err, "The sitemap is too big: 52428931")
|
urls []URL
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
urls: []URL{},
|
||||||
|
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"" +
|
||||||
|
"</sitemapindex>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "regular",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||||
|
},
|
||||||
|
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
|
||||||
|
"</sitemapindex>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without lastmod",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: "https://gitea.io/test1"},
|
||||||
|
},
|
||||||
|
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<sitemap><loc>https://gitea.io/test1</loc></sitemap>" +
|
||||||
|
"</sitemapindex>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: "https://gitea.io/test1", LastMod: &ts},
|
||||||
|
{URL: "https://gitea.io/test2", LastMod: nil},
|
||||||
|
},
|
||||||
|
want: xml.Header + `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
|
||||||
|
"<sitemap><loc>https://gitea.io/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></sitemap>" +
|
||||||
|
"<sitemap><loc>https://gitea.io/test2</loc></sitemap>" +
|
||||||
|
"</sitemapindex>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too many sitemaps",
|
||||||
|
urls: make([]URL, 50001),
|
||||||
|
wantErr: "The sitemap contains 50001 sub-sitemaps, but only 50000 are allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "too big file",
|
||||||
|
urls: []URL{
|
||||||
|
{URL: strings.Repeat("b", 50*1024*1024+1)},
|
||||||
|
},
|
||||||
|
wantErr: "The sitemap has 52428952 bytes, but only 52428800 are allowed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := NewSitemapIndex()
|
||||||
|
for _, url := range tt.urls {
|
||||||
|
s.Add(url)
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
_, err := s.WriteTo(buf)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ type CreatePushMirrorOption struct {
|
||||||
RemoteUsername string `json:"remote_username"`
|
RemoteUsername string `json:"remote_username"`
|
||||||
RemotePassword string `json:"remote_password"`
|
RemotePassword string `json:"remote_password"`
|
||||||
Interval string `json:"interval"`
|
Interval string `json:"interval"`
|
||||||
|
SyncOnCommit bool `json:"sync_on_commit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushMirror represents information of a push mirror
|
// PushMirror represents information of a push mirror
|
||||||
|
@ -22,4 +23,5 @@ type PushMirror struct {
|
||||||
LastUpdateUnix string `json:"last_update"`
|
LastUpdateUnix string `json:"last_update"`
|
||||||
LastError string `json:"last_error"`
|
LastError string `json:"last_error"`
|
||||||
Interval string `json:"interval"`
|
Interval string `json:"interval"`
|
||||||
|
SyncOnCommit bool `json:"sync_on_commit"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,15 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
|
||||||
compilingTemplates = false
|
compilingTemplates = false
|
||||||
if !setting.IsProd {
|
if !setting.IsProd {
|
||||||
watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{
|
watcher.CreateWatcher(ctx, "HTML Templates", &watcher.CreateWatcherOpts{
|
||||||
PathsCallback: walkTemplateFiles,
|
PathsCallback: walkTemplateFiles,
|
||||||
BetweenCallback: renderer.CompileTemplates,
|
BetweenCallback: func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("PANIC: %v\n%s", err, log.Stack(2))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
renderer.CompileTemplates()
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return context.WithValue(ctx, rendererKey, renderer), renderer
|
return context.WithValue(ctx, rendererKey, renderer), renderer
|
||||||
|
|
|
@ -23,20 +23,6 @@ func EnsureAbsolutePath(path, absoluteBase string) string {
|
||||||
return filepath.Join(absoluteBase, path)
|
return filepath.Join(absoluteBase, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
const notRegularFileMode os.FileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
|
||||||
|
|
||||||
// GetDirectorySize returns the disk consumption for a given path
|
|
||||||
func GetDirectorySize(path string) (int64, error) {
|
|
||||||
var size int64
|
|
||||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
|
||||||
if info != nil && (info.Mode()¬RegularFileMode) == 0 {
|
|
||||||
size += info.Size()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns true if given path is a directory,
|
// IsDir returns true if given path is a directory,
|
||||||
// or returns false when it's a file or does not exist.
|
// or returns false when it's a file or does not exist.
|
||||||
func IsDir(dir string) (bool, error) {
|
func IsDir(dir string) (bool, error) {
|
||||||
|
|
|
@ -497,6 +497,7 @@ team_not_exist = The team does not exist.
|
||||||
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
|
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization.
|
||||||
cannot_add_org_to_team = An organization cannot be added as a team member.
|
cannot_add_org_to_team = An organization cannot be added as a team member.
|
||||||
duplicate_invite_to_team = The user was already invited as a team member.
|
duplicate_invite_to_team = The user was already invited as a team member.
|
||||||
|
organization_leave_success = You have successfully left the organization %s.
|
||||||
|
|
||||||
invalid_ssh_key = Can not verify your SSH key: %s
|
invalid_ssh_key = Can not verify your SSH key: %s
|
||||||
invalid_gpg_key = Can not verify your GPG key: %s
|
invalid_gpg_key = Can not verify your GPG key: %s
|
||||||
|
|
|
@ -34,6 +34,60 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||||
|
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
|
|
||||||
|
uploadVersion, err := getOrCreateUploadVersion(pi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.WithTx(func(ctx context.Context) error {
|
||||||
|
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error inserting package blob: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// FIXME: Workaround to be removed in v1.20
|
||||||
|
// https://github.com/go-gitea/gitea/issues/19586
|
||||||
|
if exists {
|
||||||
|
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||||
|
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
||||||
|
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||||
|
log.Error("Error saving package blob in content store: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createFileForBlob(ctx, uploadVersion, pb)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if !exists {
|
||||||
|
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||||
|
log.Error("Error deleting package blob from content store: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountBlob mounts the specific blob to a different package
|
||||||
|
func mountBlob(pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
|
||||||
|
uploadVersion, err := getOrCreateUploadVersion(pi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(func(ctx context.Context) error {
|
||||||
|
return createFileForBlob(ctx, uploadVersion, pb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrCreateUploadVersion(pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
||||||
var uploadVersion *packages_model.PackageVersion
|
var uploadVersion *packages_model.PackageVersion
|
||||||
|
|
||||||
// FIXME: Replace usage of mutex with database transaction
|
// FIXME: Replace usage of mutex with database transaction
|
||||||
|
@ -84,66 +138,35 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
uploadVersionMutex.Unlock()
|
uploadVersionMutex.Unlock()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return uploadVersion, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
|
||||||
|
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||||
|
|
||||||
|
pf := &packages_model.PackageFile{
|
||||||
|
VersionID: pv.ID,
|
||||||
|
BlobID: pb.ID,
|
||||||
|
Name: filename,
|
||||||
|
LowerName: filename,
|
||||||
|
CompositeKey: packages_model.EmptyFileKey,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
||||||
|
if err == packages_model.ErrDuplicatePackageFile {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Error("Error inserting package file: %v", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.WithTx(func(ctx context.Context) error {
|
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
|
||||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
log.Error("Error setting package file property: %v", err)
|
||||||
if err != nil {
|
return err
|
||||||
log.Error("Error inserting package blob: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// FIXME: Workaround to be removed in v1.20
|
|
||||||
// https://github.com/go-gitea/gitea/issues/19586
|
|
||||||
if exists {
|
|
||||||
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
|
||||||
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
|
||||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
|
||||||
exists = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
|
||||||
log.Error("Error saving package blob in content store: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
|
||||||
|
|
||||||
pf := &packages_model.PackageFile{
|
|
||||||
VersionID: uploadVersion.ID,
|
|
||||||
BlobID: pb.ID,
|
|
||||||
Name: filename,
|
|
||||||
LowerName: filename,
|
|
||||||
CompositeKey: packages_model.EmptyFileKey,
|
|
||||||
}
|
|
||||||
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
|
||||||
if err == packages_model.ErrDuplicatePackageFile {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Error("Error inserting package file: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeFile, pf.ID, container_module.PropertyDigest, digestFromPackageBlob(pb)); err != nil {
|
|
||||||
log.Error("Error setting package file property: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if !exists {
|
|
||||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
|
||||||
log.Error("Error deleting package blob from content store: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteBlob(ownerID int64, image, digest string) error {
|
func deleteBlob(ownerID int64, image, digest string) error {
|
||||||
|
|
|
@ -196,10 +196,15 @@ func InitiateUploadBlob(ctx *context.Context) {
|
||||||
from := ctx.FormTrim("from")
|
from := ctx.FormTrim("from")
|
||||||
if mount != "" {
|
if mount != "" {
|
||||||
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||||
Image: from,
|
Repository: from,
|
||||||
Digest: mount,
|
Digest: mount,
|
||||||
})
|
})
|
||||||
if blob != nil {
|
if blob != nil {
|
||||||
|
if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||||
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
|
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
|
||||||
ContentDigest: mount,
|
ContentDigest: mount,
|
||||||
|
|
|
@ -1042,7 +1042,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
m.Get("/blobs/{sha}", repo.GetBlob)
|
m.Get("/blobs/{sha}", repo.GetBlob)
|
||||||
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
||||||
m.Get("/notes/{sha}", repo.GetNote)
|
m.Get("/notes/{sha}", repo.GetNote)
|
||||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
||||||
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
|
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch)
|
||||||
m.Group("/contents", func() {
|
m.Group("/contents", func() {
|
||||||
m.Get("", repo.GetContentsList)
|
m.Get("", repo.GetContentsList)
|
||||||
|
|
|
@ -346,10 +346,11 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
|
||||||
}
|
}
|
||||||
|
|
||||||
pushMirror := &repo_model.PushMirror{
|
pushMirror := &repo_model.PushMirror{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix),
|
RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix),
|
||||||
Interval: interval,
|
Interval: interval,
|
||||||
|
SyncOnCommit: mirrorOption.SyncOnCommit,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil {
|
if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil {
|
||||||
|
|
|
@ -149,8 +149,8 @@ func Install(ctx *context.Context) {
|
||||||
|
|
||||||
// Server and other services settings
|
// Server and other services settings
|
||||||
form.OfflineMode = setting.OfflineMode
|
form.OfflineMode = setting.OfflineMode
|
||||||
form.DisableGravatar = false // when installing, there is no database connection so that given a default value
|
form.DisableGravatar = setting.DisableGravatar // when installing, there is no database connection so that given a default value
|
||||||
form.EnableFederatedAvatar = false // when installing, there is no database connection so that given a default value
|
form.EnableFederatedAvatar = setting.EnableFederatedAvatar // when installing, there is no database connection so that given a default value
|
||||||
|
|
||||||
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
|
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
|
||||||
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
|
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
|
||||||
|
@ -443,10 +443,13 @@ func SubmitInstall(ctx *context.Context) {
|
||||||
cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode))
|
cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode))
|
||||||
// if you are reinstalling, this maybe not right because of missing version
|
// if you are reinstalling, this maybe not right because of missing version
|
||||||
if err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, strconv.FormatBool(form.DisableGravatar)); err != nil {
|
if err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, strconv.FormatBool(form.DisableGravatar)); err != nil {
|
||||||
ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := system_model.SetSettingNoVersion(system_model.KeyPictureEnableFederatedAvatar, strconv.FormatBool(form.EnableFederatedAvatar)); err != nil {
|
||||||
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(fmt.Sprint(form.EnableFederatedAvatar))
|
|
||||||
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
|
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
|
||||||
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
|
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
|
||||||
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
|
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
|
||||||
|
|
|
@ -108,13 +108,20 @@ func MembersAction(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
case "leave":
|
case "leave":
|
||||||
err = models.RemoveOrgUser(org.ID, ctx.Doer.ID)
|
err = models.RemoveOrgUser(org.ID, ctx.Doer.ID)
|
||||||
if organization.IsErrLastOrgOwner(err) {
|
if err == nil {
|
||||||
|
ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName()))
|
||||||
|
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"redirect": "", // keep the user stay on current page, in case they want to do other operations.
|
||||||
|
})
|
||||||
|
} else if organization.IsErrLastOrgOwner(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||||
"redirect": ctx.Org.OrgLink + "/members",
|
"redirect": ctx.Org.OrgLink + "/members",
|
||||||
})
|
})
|
||||||
return
|
} else {
|
||||||
|
log.Error("RemoveOrgUser(%d,%d): %v", org.ID, ctx.Doer.ID, err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -784,6 +784,10 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
|
||||||
|
template.Ref = git.BranchPrefix + template.Ref
|
||||||
}
|
}
|
||||||
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
|
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
|
||||||
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
|
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
|
||||||
|
|
|
@ -27,7 +27,7 @@ func CodeSearch(ctx *context.Context) {
|
||||||
|
|
||||||
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
|
||||||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||||
ctx.Data["Title"] = ctx.Tr("code.title")
|
ctx.Data["Title"] = ctx.Tr("explore.code")
|
||||||
ctx.Data["ContextUser"] = ctx.ContextUser
|
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||||
|
|
||||||
language := ctx.FormTrim("l")
|
language := ctx.FormTrim("l")
|
||||||
|
|
|
@ -100,14 +100,18 @@ func KeysPost(ctx *context.Context) {
|
||||||
loadKeysData(ctx)
|
loadKeysData(ctx)
|
||||||
ctx.Data["Err_Content"] = true
|
ctx.Data["Err_Content"] = true
|
||||||
ctx.Data["Err_Signature"] = true
|
ctx.Data["Err_Signature"] = true
|
||||||
ctx.Data["KeyID"] = err.(asymkey_model.ErrGPGInvalidTokenSignature).ID
|
keyID := err.(asymkey_model.ErrGPGInvalidTokenSignature).ID
|
||||||
|
ctx.Data["KeyID"] = keyID
|
||||||
|
ctx.Data["PaddedKeyID"] = asymkey_model.PaddedKeyID(keyID)
|
||||||
ctx.RenderWithErr(ctx.Tr("settings.gpg_invalid_token_signature"), tplSettingsKeys, &form)
|
ctx.RenderWithErr(ctx.Tr("settings.gpg_invalid_token_signature"), tplSettingsKeys, &form)
|
||||||
case asymkey_model.IsErrGPGNoEmailFound(err):
|
case asymkey_model.IsErrGPGNoEmailFound(err):
|
||||||
loadKeysData(ctx)
|
loadKeysData(ctx)
|
||||||
|
|
||||||
ctx.Data["Err_Content"] = true
|
ctx.Data["Err_Content"] = true
|
||||||
ctx.Data["Err_Signature"] = true
|
ctx.Data["Err_Signature"] = true
|
||||||
ctx.Data["KeyID"] = err.(asymkey_model.ErrGPGNoEmailFound).ID
|
keyID := err.(asymkey_model.ErrGPGNoEmailFound).ID
|
||||||
|
ctx.Data["KeyID"] = keyID
|
||||||
|
ctx.Data["PaddedKeyID"] = asymkey_model.PaddedKeyID(keyID)
|
||||||
ctx.RenderWithErr(ctx.Tr("settings.gpg_no_key_email_found"), tplSettingsKeys, &form)
|
ctx.RenderWithErr(ctx.Tr("settings.gpg_no_key_email_found"), tplSettingsKeys, &form)
|
||||||
default:
|
default:
|
||||||
ctx.ServerError("AddPublicKey", err)
|
ctx.ServerError("AddPublicKey", err)
|
||||||
|
@ -139,7 +143,9 @@ func KeysPost(ctx *context.Context) {
|
||||||
loadKeysData(ctx)
|
loadKeysData(ctx)
|
||||||
ctx.Data["VerifyingID"] = form.KeyID
|
ctx.Data["VerifyingID"] = form.KeyID
|
||||||
ctx.Data["Err_Signature"] = true
|
ctx.Data["Err_Signature"] = true
|
||||||
ctx.Data["KeyID"] = err.(asymkey_model.ErrGPGInvalidTokenSignature).ID
|
keyID := err.(asymkey_model.ErrGPGInvalidTokenSignature).ID
|
||||||
|
ctx.Data["KeyID"] = keyID
|
||||||
|
ctx.Data["PaddedKeyID"] = asymkey_model.PaddedKeyID(keyID)
|
||||||
ctx.RenderWithErr(ctx.Tr("settings.gpg_invalid_token_signature"), tplSettingsKeys, &form)
|
ctx.RenderWithErr(ctx.Tr("settings.gpg_invalid_token_signature"), tplSettingsKeys, &form)
|
||||||
default:
|
default:
|
||||||
ctx.ServerError("VerifyGPG", err)
|
ctx.ServerError("VerifyGPG", err)
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
// NewIssue creates new issue with labels for repository.
|
||||||
|
@ -201,7 +200,7 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
if issue.Ref != "" {
|
if issue.Ref != "" {
|
||||||
issueRefEndNames[issue.ID] = git.RefEndName(issue.Ref)
|
issueRefEndNames[issue.ID] = git.RefEndName(issue.Ref)
|
||||||
issueRefURLs[issue.ID] = git.RefURL(repoLink, util.PathEscapeSegments(issue.Ref))
|
issueRefURLs[issue.ID] = git.RefURL(repoLink, issue.Ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return issueRefEndNames, issueRefURLs
|
return issueRefEndNames, issueRefURLs
|
||||||
|
|
|
@ -34,10 +34,14 @@ func (f *GitBucketDownloaderFactory) New(ctx context.Context, opts base.MigrateO
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := u.Scheme + "://" + u.Host
|
|
||||||
fields := strings.Split(u.Path, "/")
|
fields := strings.Split(u.Path, "/")
|
||||||
oldOwner := fields[1]
|
if len(fields) < 2 {
|
||||||
oldName := strings.TrimSuffix(fields[2], ".git")
|
return nil, fmt.Errorf("invalid path: %s", u.Path)
|
||||||
|
}
|
||||||
|
baseURL := u.Scheme + "://" + u.Host + strings.TrimSuffix(strings.Join(fields[:len(fields)-2], "/"), "/git")
|
||||||
|
|
||||||
|
oldOwner := fields[len(fields)-2]
|
||||||
|
oldName := strings.TrimSuffix(fields[len(fields)-1], ".git")
|
||||||
|
|
||||||
log.Trace("Create GitBucket downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, oldOwner, oldName)
|
log.Trace("Create GitBucket downloader. BaseURL: %s RepoOwner: %s RepoName: %s", baseURL, oldOwner, oldName)
|
||||||
return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
|
return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
|
||||||
|
@ -72,6 +76,7 @@ func (g *GitBucketDownloader) ColorFormat(s fmt.State) {
|
||||||
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
|
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
|
||||||
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
|
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
|
||||||
githubDownloader.SkipReactions = true
|
githubDownloader.SkipReactions = true
|
||||||
|
githubDownloader.SkipReviews = true
|
||||||
return &GitBucketDownloader{
|
return &GitBucketDownloader{
|
||||||
githubDownloader,
|
githubDownloader,
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ type GithubDownloaderV3 struct {
|
||||||
curClientIdx int
|
curClientIdx int
|
||||||
maxPerPage int
|
maxPerPage int
|
||||||
SkipReactions bool
|
SkipReactions bool
|
||||||
|
SkipReviews bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGithubDownloaderV3 creates a github Downloader via github v3 API
|
// NewGithubDownloaderV3 creates a github Downloader via github v3 API
|
||||||
|
@ -805,6 +806,9 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques
|
||||||
// GetReviews returns pull requests review
|
// GetReviews returns pull requests review
|
||||||
func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) {
|
func (g *GithubDownloaderV3) GetReviews(reviewable base.Reviewable) ([]*base.Review, error) {
|
||||||
allReviews := make([]*base.Review, 0, g.maxPerPage)
|
allReviews := make([]*base.Review, 0, g.maxPerPage)
|
||||||
|
if g.SkipReviews {
|
||||||
|
return allReviews, nil
|
||||||
|
}
|
||||||
opt := &github.ListOptions{
|
opt := &github.ListOptions{
|
||||||
PerPage: g.maxPerPage,
|
PerPage: g.maxPerPage,
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,32 +73,8 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...git.CmdArg)
|
||||||
return db.ErrCancelledf("before GC of %s", repo.FullName())
|
return db.ErrCancelledf("before GC of %s", repo.FullName())
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
log.Trace("Running git gc on %v", repo)
|
// we can ignore the error here because it will be logged in GitGCRepo
|
||||||
command := git.NewCommand(ctx, args...).
|
_ = GitGcRepo(ctx, repo, timeout, args)
|
||||||
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
|
|
||||||
var stdout string
|
|
||||||
var err error
|
|
||||||
stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
|
|
||||||
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
|
||||||
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update the size of the repository
|
|
||||||
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
|
||||||
log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
|
|
||||||
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
|
||||||
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -109,6 +85,37 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...git.CmdArg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository
|
||||||
|
func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args []git.CmdArg) error {
|
||||||
|
log.Trace("Running git gc on %-v", repo)
|
||||||
|
command := git.NewCommand(ctx, args...).
|
||||||
|
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
|
||||||
|
var stdout string
|
||||||
|
var err error
|
||||||
|
stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||||
|
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
||||||
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now update the size of the repository
|
||||||
|
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
||||||
|
log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
||||||
|
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
|
||||||
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
||||||
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func gatherMissingRepoRecords(ctx context.Context) ([]*repo_model.Repository, error) {
|
func gatherMissingRepoRecords(ctx context.Context) ([]*repo_model.Repository, error) {
|
||||||
repos := make([]*repo_model.Repository, 0, 10)
|
repos := make([]*repo_model.Repository, 0, 10)
|
||||||
if err := db.Iterate(
|
if err := db.Iterate(
|
||||||
|
@ -162,7 +169,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error
|
||||||
}
|
}
|
||||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||||
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
|
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
|
||||||
log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
|
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
|
||||||
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
log.Error("CreateRepositoryNotice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ type (
|
||||||
Wait bool `json:"wait"`
|
Wait bool `json:"wait"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
TTS bool `json:"tts"`
|
TTS bool `json:"tts"`
|
||||||
Embeds []DiscordEmbed `json:"embeds"`
|
Embeds []DiscordEmbed `json:"embeds"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ func (f *WechatworkPayload) PullRequest(p *api.PullRequestPayload) (api.Payloade
|
||||||
func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_model.HookEventType) (api.Payloader, error) {
|
func (f *WechatworkPayload) Review(p *api.PullRequestPayload, event webhook_model.HookEventType) (api.Payloader, error) {
|
||||||
var text, title string
|
var text, title string
|
||||||
switch p.Action {
|
switch p.Action {
|
||||||
case api.HookIssueSynchronized:
|
case api.HookIssueReviewed:
|
||||||
action, err := parseHookPullRequestEventType(event)
|
action, err := parseHookPullRequestEventType(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{else if .IsSigned}}
|
{{else if .IsSigned}}
|
||||||
<div class="right stackable menu">
|
<div class="right stackable menu">
|
||||||
|
{{if EnableTimetracking}}
|
||||||
<a class="active-stopwatch-trigger item ui label {{if not .ActiveStopwatch}}hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}">
|
<a class="active-stopwatch-trigger item ui label {{if not .ActiveStopwatch}}hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}">
|
||||||
<span class="text">
|
<span class="text">
|
||||||
<span class="fitted item">
|
<span class="fitted item">
|
||||||
|
@ -115,6 +116,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<a href="{{AppSubUrl}}/notifications" class="item tooltip not-mobile" data-content="{{.locale.Tr "notifications"}}" aria-label="{{.locale.Tr "notifications"}}">
|
<a href="{{AppSubUrl}}/notifications" class="item tooltip not-mobile" data-content="{{.locale.Tr "notifications"}}" aria-label="{{.locale.Tr "notifications"}}">
|
||||||
<span class="text">
|
<span class="text">
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
{{.locale.Tr "packages.settings.delete"}}
|
{{.locale.Tr "packages.settings.delete"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="ui warning message text left">
|
<div class="ui warning message text left word-break">
|
||||||
{{.locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
{{.locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
||||||
</div>
|
</div>
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</a>
|
</a>
|
||||||
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}{{- /* */ -}}
|
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}{{- /* */ -}}
|
||||||
<div class="ui primary tiny floating dropdown icon button">{{.locale.Tr "repo.commit.actions"}}
|
<div class="ui primary tiny floating dropdown icon button">{{.locale.Tr "repo.commit.actions"}}
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}<span class="sr-mobile-only">{{.locale.Tr "repo.commit.actions"}}</span>
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="ui header">{{.locale.Tr "repo.commit.actions"}}</div>
|
<div class="ui header">{{.locale.Tr "repo.commit.actions"}}</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
|
@ -143,7 +143,7 @@
|
||||||
{{$.locale.Tr "repo.diff.file_suppressed_line_too_long"}}
|
{{$.locale.Tr "repo.diff.file_suppressed_line_too_long"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$.locale.Tr "repo.diff.file_suppressed"}}
|
{{$.locale.Tr "repo.diff.file_suppressed"}}
|
||||||
<a class="ui basic tiny button diff-show-more-button" data-href="{{$.Link}}?file-only=true&files={{$file.Name}}&files={{$file.OldName}}">{{$.locale.Tr "repo.diff.load"}}</a>
|
<a class="ui basic tiny button diff-load-button" data-href="{{$.Link}}?file-only=true&files={{$file.Name}}&files={{$file.OldName}}">{{$.locale.Tr "repo.diff.load"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$.locale.Tr "repo.diff.bin_not_shown"}}
|
{{$.locale.Tr "repo.diff.bin_not_shown"}}
|
||||||
|
|
|
@ -413,7 +413,7 @@
|
||||||
<div class="df sb ac">
|
<div class="df sb ac">
|
||||||
<div class="due-date tooltip {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
<div class="due-date tooltip {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-content="{{.locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||||
{{svg "octicon-calendar" 16 "mr-3"}}
|
{{svg "octicon-calendar" 16 "mr-3"}}
|
||||||
<time data-format="date" datetime="{{.Issue.DeadlineUnix.FormatLong}}">{{.Issue.DeadlineUnix.FormatDate}}</time>
|
<time data-format="date" datetime="{{.Issue.DeadlineUnix.FormatDate}}">{{.Issue.DeadlineUnix.FormatDate}}</time>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||||
|
|
|
@ -18,14 +18,16 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range $.LatestCommitStatuses}}
|
{{range $.LatestCommitStatuses}}
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment pr-status">
|
||||||
<span>{{template "repo/commit_status" .}}</span>
|
{{template "repo/commit_status" .}}
|
||||||
<span class="ui">{{.Context}} <span class="text grey">{{.Description}}</span></span>
|
<div class="status-context">
|
||||||
<div class="ui right">
|
<span>{{.Context}} <span class="text grey">{{.Description}}</span></span>
|
||||||
{{if $.is_context_required}}
|
<div class="ui status-details">
|
||||||
{{if (call $.is_context_required .Context)}}<div class="ui label">{{$.locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
|
{{if $.is_context_required}}
|
||||||
{{end}}
|
{{if (call $.is_context_required .Context)}}<div class="ui label">{{$.locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
|
||||||
<span class="ui">{{if .TargetURL}}<a href="{{.TargetURL}}">{{$.locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
|
{{end}}
|
||||||
|
<span class="ui">{{if .TargetURL}}<a href="{{.TargetURL}}">{{$.locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -14831,6 +14831,10 @@
|
||||||
"remote_username": {
|
"remote_username": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "RemoteUsername"
|
"x-go-name": "RemoteUsername"
|
||||||
|
},
|
||||||
|
"sync_on_commit": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "SyncOnCommit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -18011,6 +18015,10 @@
|
||||||
"repo_name": {
|
"repo_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "RepoName"
|
"x-go-name": "RepoName"
|
||||||
|
},
|
||||||
|
"sync_on_commit": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "SyncOnCommit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
<p>{{.locale.Tr "settings.gpg_token_required"}}</p>
|
<p>{{.locale.Tr "settings.gpg_token_required"}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="token">{{.locale.Tr "setting.gpg_token"}}
|
<label for="token">{{.locale.Tr "settings.gpg_token"}}
|
||||||
<input readonly="" value="{{.TokenToSign}}">
|
<input readonly="" value="{{.TokenToSign}}">
|
||||||
<div class="help">
|
<div class="help">
|
||||||
<p>{{.locale.Tr "settings.gpg_token_help"}}</p>
|
<p>{{.locale.Tr "settings.gpg_token_help"}}</p>
|
||||||
<p><code>{{$.locale.Tr "settings.gpg_token_code" .TokenToSign .PaddedKeyID}}</code></p>
|
<p><code>{{$.locale.Tr "settings.gpg_token_code" .TokenToSign .KeyID}}</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -17,9 +17,13 @@
|
||||||
{{range .Orgs}}
|
{{range .Orgs}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="right floated content">
|
<div class="right floated content">
|
||||||
<form method="post" action="{{.OrganisationLink}}/members/action/leave">
|
<form>
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<button type="submit" class="ui primary small button" name="uid" value="{{.ID}}">{{$.locale.Tr "org.members.leave"}}</button>
|
<button class="ui red button delete-button" data-modal-id="leave-organization"
|
||||||
|
data-url="{{.OrganisationLink}}/members/action/leave" data-datauid="{{$.SignedUser.ID}}"
|
||||||
|
data-name="{{$.SignedUser.DisplayName}}"
|
||||||
|
data-data-organization-name="{{.DisplayName}}">{{$.locale.Tr "org.members.leave"}}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{avatar . 28 "mini"}}
|
{{avatar . 28 "mini"}}
|
||||||
|
@ -36,4 +40,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ui small basic delete modal" id="leave-organization">
|
||||||
|
<div class="ui icon header">
|
||||||
|
{{svg "octicon-x" 16 "close inside"}}
|
||||||
|
{{$.locale.Tr "org.members.leave"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{$.locale.Tr "org.members.leave.detail" `<span class="dataOrganizationName"></span>` | Safe}}</p>
|
||||||
|
</div>
|
||||||
|
{{template "base/delete_modal_actions" .}}
|
||||||
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
|
@ -257,6 +257,32 @@ func TestPackageContainer(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("UploadBlob/Mount", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, blobDigest))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
||||||
|
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s&from=%s", url, unknownDigest, "unknown/image"))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s&from=%s/%s", url, blobDigest, user.Name, image))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
||||||
|
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
||||||
|
})
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
t.Run(fmt.Sprintf("[Tag:%s]", tag), func(t *testing.T) {
|
t.Run(fmt.Sprintf("[Tag:%s]", tag), func(t *testing.T) {
|
||||||
t.Run("UploadManifest", func(t *testing.T) {
|
t.Run("UploadManifest", func(t *testing.T) {
|
||||||
|
@ -445,21 +471,6 @@ func TestPackageContainer(t *testing.T) {
|
||||||
assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest))
|
assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UploadBlob/Mount", func(t *testing.T) {
|
|
||||||
defer tests.PrintCurrentTest(t)()
|
|
||||||
|
|
||||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
MakeRequest(t, req, http.StatusAccepted)
|
|
||||||
|
|
||||||
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, blobDigest))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
resp := MakeRequest(t, req, http.StatusCreated)
|
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
|
||||||
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("HeadBlob", func(t *testing.T) {
|
t.Run("HeadBlob", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
|
|
@ -209,8 +209,6 @@ func (s *TestSession) MakeRequestNilResponseHashSumRecorder(t testing.TB, req *h
|
||||||
|
|
||||||
const userPassword = "password"
|
const userPassword = "password"
|
||||||
|
|
||||||
var loginSessionCache = make(map[string]*TestSession, 10)
|
|
||||||
|
|
||||||
func emptyTestSession(t testing.TB) *TestSession {
|
func emptyTestSession(t testing.TB) *TestSession {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
jar, err := cookiejar.New(nil)
|
jar, err := cookiejar.New(nil)
|
||||||
|
@ -225,12 +223,8 @@ func getUserToken(t testing.TB, userName string) string {
|
||||||
|
|
||||||
func loginUser(t testing.TB, userName string) *TestSession {
|
func loginUser(t testing.TB, userName string) *TestSession {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if session, ok := loginSessionCache[userName]; ok {
|
|
||||||
return session
|
return loginUserWithPassword(t, userName, userPassword)
|
||||||
}
|
|
||||||
session := loginUserWithPassword(t, userName, userPassword)
|
|
||||||
loginSessionCache[userName] = session
|
|
||||||
return session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
|
func loginUserWithPassword(t testing.TB, userName, password string) *TestSession {
|
||||||
|
|
|
@ -22,7 +22,4 @@ func TestSignOut(t *testing.T) {
|
||||||
// try to view a private repo, should fail
|
// try to view a private repo, should fail
|
||||||
req = NewRequest(t, "GET", "/user2/repo2")
|
req = NewRequest(t, "GET", "/user2/repo2")
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
|
||||||
// invalidate cached cookies for user2, for subsequent tests
|
|
||||||
delete(loginSessionCache, "user2")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item" />
|
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isIncomplete" id="diff-too-many-files-stats" class="pt-2">
|
<div v-if="isIncomplete" id="diff-too-many-files-stats" class="pt-2">
|
||||||
<span>{{ tooManyFilesMessage }}</span><a :class="['ui', 'basic', 'tiny', 'button', isLoadingNewData === true ? 'disabled' : '']" id="diff-show-more-files-stats" @click.stop="loadMoreData">{{ showMoreMessage }}</a>
|
<span class="mr-2">{{ tooManyFilesMessage }}</span><a :class="['ui', 'basic', 'tiny', 'button', isLoadingNewData === true ? 'disabled' : '']" id="diff-show-more-files-stats" @click.stop="loadMoreData">{{ showMoreMessage }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -98,6 +98,9 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
// ensure correct buttons when we are mounted to the dom
|
// ensure correct buttons when we are mounted to the dom
|
||||||
this.adjustToggleButton(this.fileTreeIsVisible);
|
this.adjustToggleButton(this.fileTreeIsVisible);
|
||||||
|
// replace the pageData.diffFileInfo.files with our watched data so we get updates
|
||||||
|
pageData.diffFileInfo.files = this.files;
|
||||||
|
|
||||||
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', this.toggleVisibility);
|
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', this.toggleVisibility);
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
|
|
@ -119,26 +119,47 @@ function onShowMoreFiles() {
|
||||||
|
|
||||||
export function doLoadMoreFiles(link, diffEnd, callback) {
|
export function doLoadMoreFiles(link, diffEnd, callback) {
|
||||||
const url = `${link}?skip-to=${diffEnd}&file-only=true`;
|
const url = `${link}?skip-to=${diffEnd}&file-only=true`;
|
||||||
|
loadMoreFiles(url, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMoreFiles(url, callback) {
|
||||||
|
const $target = $('a#diff-show-more-files');
|
||||||
|
if ($target.hasClass('disabled')) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$target.addClass('disabled');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url,
|
url,
|
||||||
}).done((resp) => {
|
}).done((resp) => {
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
|
$target.removeClass('disabled');
|
||||||
callback(resp);
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$('#diff-incomplete').replaceWith($(resp).find('#diff-file-boxes').children());
|
||||||
// By simply rerunning the script we add the new data to our existing
|
// By simply rerunning the script we add the new data to our existing
|
||||||
// pagedata object. this triggers vue and the filetree and filelist will
|
// pagedata object. this triggers vue and the filetree and filelist will
|
||||||
// render the new elements.
|
// render the new elements.
|
||||||
$('body').append($(resp).find('script#diff-data-script'));
|
$('body').append($(resp).find('script#diff-data-script'));
|
||||||
|
onShowMoreFiles();
|
||||||
callback(resp);
|
callback(resp);
|
||||||
}).fail(() => {
|
}).fail(() => {
|
||||||
|
$target.removeClass('disabled');
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoDiffShowMore() {
|
export function initRepoDiffShowMore() {
|
||||||
$(document).on('click', 'a.diff-show-more-button', (e) => {
|
$(document).on('click', 'a#diff-show-more-files', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const $target = $(e.target);
|
||||||
|
loadMoreFiles($target.data('href'), () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', 'a.diff-load-button', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const $target = $(e.target);
|
const $target = $(e.target);
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,7 @@ function getRelativeColor(color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function rgbToHex(rgb) {
|
function rgbToHex(rgb) {
|
||||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+).*\)$/);
|
||||||
return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`;
|
return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export function initStopwatch() {
|
||||||
trigger: 'click',
|
trigger: 'click',
|
||||||
maxWidth: 'none',
|
maxWidth: 'none',
|
||||||
interactive: true,
|
interactive: true,
|
||||||
|
hideOnClick: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used.
|
// global stop watch (in the head_navbar), it should always work in any case either the EventSource or the PeriodicPoller is used.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
function displayError(el, err) {
|
function displayError(el, err) {
|
||||||
const target = targetElement(el);
|
const target = targetElement(el);
|
||||||
target.remove('is-loading');
|
target.classList.remove('is-loading');
|
||||||
const errorNode = document.createElement('div');
|
const errorNode = document.createElement('div');
|
||||||
errorNode.setAttribute('class', 'ui message error markup-block-error mono');
|
errorNode.setAttribute('class', 'ui message error markup-block-error mono');
|
||||||
errorNode.textContent = err.str || err.message || String(err);
|
errorNode.textContent = err.str || err.message || String(err);
|
||||||
|
@ -23,12 +23,16 @@ export async function renderMath() {
|
||||||
|
|
||||||
for (const el of els) {
|
for (const el of els) {
|
||||||
const source = el.textContent;
|
const source = el.textContent;
|
||||||
const options = {display: el.classList.contains('display')};
|
const displayMode = el.classList.contains('display');
|
||||||
|
const nodeName = displayMode ? 'p' : 'span';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const markup = katex.renderToString(source, options);
|
const tempEl = document.createElement(nodeName);
|
||||||
const tempEl = document.createElement(options.display ? 'p' : 'span');
|
katex.render(source, tempEl, {
|
||||||
tempEl.innerHTML = markup;
|
maxSize: 25,
|
||||||
|
maxExpand: 50,
|
||||||
|
displayMode,
|
||||||
|
});
|
||||||
targetElement(el).replaceWith(tempEl);
|
targetElement(el).replaceWith(tempEl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
displayError(el, error);
|
displayError(el, error);
|
||||||
|
|
|
@ -1665,6 +1665,9 @@
|
||||||
background-color: var(--color-teal);
|
background-color: var(--color-teal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.diff-box .header:not(.resolved-placeholder) {
|
.diff-box .header:not(.resolved-placeholder) {
|
||||||
|
@ -3491,3 +3494,41 @@ td.blob-excerpt {
|
||||||
max-width: 165px;
|
max-width: 165px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-status {
|
||||||
|
padding: 0 !important; // To clear fomantic's padding on .ui.segment elements
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.commit-status {
|
||||||
|
margin: 1em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-context {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding: 1em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details {
|
||||||
|
display: flex;
|
||||||
|
padding-right: .5em;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
@media @mediaSm {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding-right: .5em; // To match the alignment with the "required" label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue