Compare commits

..

5 commits

Author SHA1 Message Date
cd4915dd94 gitea.nulo.in: Use NPM cache
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-30 16:40:42 -03:00
3446798647 gitea.nulo.in: Use APK cache 2022-12-30 16:40:42 -03:00
ae1c0ece04 gitea.nulo.in: Use script to upgrade 2022-12-30 16:40:42 -03:00
a2e6438c19 gitea.nulo.in: Use Alpine 3.16 2022-12-30 16:40:42 -03:00
5fd51c703f Add .woodpecker.yml for gitea.nulo.in 2022-12-30 16:40:42 -03:00
73 changed files with 713 additions and 1371 deletions

View file

@ -4,47 +4,6 @@ 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

View file

@ -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 '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 'netgo 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 '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 'netgo 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

File diff suppressed because one or more lines are too long

View file

@ -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 = 14 maxUnicodeVersion = 12
) )
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")

View file

@ -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. **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure. - `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only.
- `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)). **DEPRECATED [v1.18+]** moved to database. Use admin panel to configure. [http://www.libravatar.org](http://www.libravatar.org)).
- `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
View file

@ -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-2 replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix
exclude github.com/gofrs/uuid v3.2.0+incompatible exclude github.com/gofrs/uuid v3.2.0+incompatible

4
go.sum
View file

@ -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-2 h1:IRB+69BV7fTT5ccw35ca7TCBe2b7dm5Q5y5tUMQmCvU= github.com/zeripath/zapx/v15 v15.3.6-alignment-fix h1:fKZ9OxEDoJKgM0KBXRbSb5IgKUEXis6C3zEIiMtzzQ0=
github.com/zeripath/zapx/v15 v15.3.6-alignment-fix-2/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM= github.com/zeripath/zapx/v15 v15.3.6-alignment-fix/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=

View file

@ -68,16 +68,8 @@ 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(keyID)] + keyID return zeros[0:16-len(key.KeyID)] + key.KeyID
} }
// ListGPGKeys returns a list of public keys belongs to given user. // ListGPGKeys returns a list of public keys belongs to given user.

View file

@ -154,7 +154,8 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink() return DefaultAvatarLink()
} }
enableFederatedAvatar := system_model.GetSettingBool(system_model.KeyPictureEnableFederatedAvatar) enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
var err error var err error
if enableFederatedAvatar && system_model.LibravatarService != nil { if enableFederatedAvatar && system_model.LibravatarService != nil {
@ -175,7 +176,9 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return urlStr return urlStr
} }
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar) disableGravatarSetting, _ := system_model.GetSetting(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

View file

@ -24,7 +24,7 @@
fork_id: 0 fork_id: 0
is_template: false is_template: false
template_id: 0 template_id: 0
size: 6708 size: 0
is_fsck_enabled: true is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false close_issues_via_commit_in_any_branch: false

View file

@ -742,9 +742,17 @@ 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 {
if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil { // recalculate the latest official review for reviewer
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{
@ -762,22 +770,6 @@ 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()
@ -996,12 +988,6 @@ 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()
} }

View file

@ -201,38 +201,3 @@ 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)
}

View file

@ -26,7 +26,6 @@ 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 {
@ -55,15 +54,6 @@ 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
} }

View file

@ -15,7 +15,6 @@ 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"
@ -499,12 +498,8 @@ 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 are present. // Topic checking. Topics is non-null.
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": "[]"})) 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": ""})

View file

@ -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 getDirectorySize // UpdateRepoSize updates the repository size, calculating it using util.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,

View file

@ -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"
setting_module "code.gitea.io/gitea/modules/setting" "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[strings.ToLower(key)], nil return v[key], nil
} }
// GetSetting returns the setting value via the key // GetSetting returns the setting value via the key
func GetSetting(key string) (string, error) { func GetSetting(key string) (*Setting, error) {
return cache.GetString(genSettingCacheKey(key), func() (string, error) { return cache.Get(genSettingCacheKey(key), func() (*Setting, error) {
res, err := GetSettingNoCache(key) res, err := GetSettingNoCache(key)
if err != nil { if err != nil {
return "", err return nil, err
} }
return res.SettingValue, nil return res, nil
}) })
} }
@ -107,8 +107,7 @@ func GetSetting(key string) (string, 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)
v, _ := strconv.ParseBool(s) return s.GetValueBool()
return v
} }
// GetSettings returns specific settings // GetSettings returns specific settings
@ -132,7 +131,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[strings.ToLower(key)]; ok { if v, ok := settings[key]; ok {
return *v return *v
} }
return Setting{} return Setting{}
@ -185,17 +184,14 @@ 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 {
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil { _, err := cache.Set(genSettingCacheKey(setting.SettingKey), func() (*Setting, error) {
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
} }
@ -247,7 +243,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_module.GetDefaultDisableGravatar() disableGravatar = setting.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
@ -258,7 +254,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_module.GetDefaultEnableFederatedAvatar(disableGravatar) enableFederatedAvatar = setting.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
@ -266,20 +262,20 @@ func Init() error {
enableFederatedAvatar = disableGravatarSetting.GetValueBool() enableFederatedAvatar = disableGravatarSetting.GetValueBool()
} }
if setting_module.OfflineMode { if setting.OfflineMode {
disableGravatar = true disableGravatar = true
enableFederatedAvatar = false enableFederatedAvatar = false
} }
if enableFederatedAvatar || !disableGravatar { if disableGravatar || !enableFederatedAvatar {
var err error var err error
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource) GravatarSourceURL, err = url.Parse(setting.GravatarSource)
if err != nil { if err != nil {
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err) return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting.GravatarSource, err)
} }
} }
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() { if enableFederatedAvatarSetting.GetValueBool() {
LibravatarService = libravatar.New() LibravatarService = libravatar.New()
if GravatarSourceURL.Scheme == "https" { if GravatarSourceURL.Scheme == "https" {
LibravatarService.SetUseHTTPS(true) LibravatarService.SetUseHTTPS(true)

View file

@ -34,14 +34,10 @@ 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: settings[strings.ToLower(keyName)].Version} updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.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)

View file

@ -68,7 +68,9 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false useLocalAvatar := false
autoGenerateAvatar := false autoGenerateAvatar := false
disableGravatar := system_model.GetSettingBool(system_model.KeyPictureDisableGravatar) disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
disableGravatar := disableGravatarSetting.GetValueBool()
switch { switch {
case u.UseCustomAvatar: case u.UseCustomAvatar:

View file

@ -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) (string, error) { func GetSetting(uid int64, key string) (*Setting, error) {
return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) { return cache.Get(genSettingCacheKey(uid, key), func() (*Setting, error) {
res, err := GetSettingNoCache(uid, key) res, err := GetSettingNoCache(uid, key)
if err != nil { if err != nil {
return "", err return nil, err
} }
return res.SettingValue, nil return res, nil
}) })
} }
@ -155,7 +155,7 @@ func SetUserSetting(userID int64, key, value string) error {
return err return err
} }
_, err := cache.GetString(genSettingCacheKey(userID, key), func() (string, error) { _, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
return value, upsertUserSettingValue(userID, key, value) return value, upsertUserSettingValue(userID, key, value)
}) })

View file

@ -46,6 +46,39 @@ 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 {

View file

@ -1087,9 +1087,6 @@ 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)
} }
} }

View file

@ -89,10 +89,6 @@ 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

View file

@ -100,9 +100,6 @@ 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
} }

View file

@ -5,7 +5,6 @@
package external package external
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -134,13 +133,11 @@ 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\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) return fmt.Errorf("%s render run command %s %v failed: %w", p.Name(), commands[0], args, err)
} }
return nil return nil
} }

View file

@ -110,6 +110,32 @@ 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()

View file

@ -9,6 +9,7 @@ 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"
@ -167,6 +168,35 @@ 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)

View file

@ -464,43 +464,13 @@ 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 <-paused: case data := <-p.dataChan:
// 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)
} }
@ -526,7 +496,6 @@ 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)
@ -547,19 +516,8 @@ 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

View file

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -287,36 +286,9 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
return repo, nil return repo, nil
} }
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular // UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize
// 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 := getDirectorySize(repo.RepoPath()) size, err := util.GetDirectorySize(repo.RepoPath())
if err != nil { if err != nil {
return fmt.Errorf("updateSize: %w", err) return fmt.Errorf("updateSize: %w", err)
} }

View file

@ -169,13 +169,3 @@ 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)
}

View file

@ -13,7 +13,6 @@ 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.
@ -51,8 +50,8 @@ type Mailer struct {
// MailService the global mailer // MailService the global mailer
var MailService *Mailer var MailService *Mailer
func parseMailerConfig(rootCfg *ini.File) { func newMailService() {
sec := rootCfg.Section("mailer") sec := Cfg.Section("mailer")
// Check mailer setting. // Check mailer setting.
if !sec.Key("ENABLED").MustBool() { if !sec.Key("ENABLED").MustBool() {
return return
@ -72,14 +71,9 @@ func parseMailerConfig(rootCfg *ini.File) {
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 && strings.Contains(err.Error(), "missing port in address") { if err != nil {
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)
} }
@ -179,24 +173,12 @@ func parseMailerConfig(rootCfg *ini.File) {
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
// This check is not needed for sendmail
switch MailService.Protocol {
case "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", sec.Key("SENDMAIL_ARGS").String(), err)
}
case "smtp", "smtps", "smtp+starttls", "smtp+unix":
ips := tryResolveAddr(MailService.SMTPAddr) ips := tryResolveAddr(MailService.SMTPAddr)
if MailService.Protocol == "smtp" { if MailService.Protocol == "smtp" {
for _, ip := range ips { for _, ip := range ips {
@ -206,8 +188,6 @@ func parseMailerConfig(rootCfg *ini.File) {
} }
} }
} }
case "dummy": // just mention and do nothing
}
if MailService.From != "" { if MailService.From != "" {
parsed, err := mail.ParseAddress(MailService.From) parsed, err := mail.ParseAddress(MailService.From)
@ -235,6 +215,14 @@ func parseMailerConfig(rootCfg *ini.File) {
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")
} }

View file

@ -1,43 +0,0 @@
// 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)
})
}
}

View file

@ -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 {

View file

@ -1334,7 +1334,7 @@ func NewServices() {
newCacheService() newCacheService()
newSessionService() newSessionService()
newCORSService() newCORSService()
parseMailerConfig(Cfg) newMailService()
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()
parseMailerConfig(Cfg) newMailService()
} }

View file

@ -12,62 +12,48 @@ import (
"time" "time"
) )
const ( // sitemapFileLimit contains the maximum size of a sitemap file
sitemapFileLimit = 50 * 1024 * 1024 // the maximum size of a sitemap file const sitemapFileLimit = 50 * 1024 * 1024
urlsLimit = 50000
schemaURL = "http://www.sitemaps.org/schemas/sitemap/0.9" // Url represents a single sitemap entry
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"`
} }
// Sitemap represents a sitemap // SitemapUrl 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: urlsetName}, XMLName: xml.Name{Local: "urlset"},
Namespace: schemaURL, Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
} }
} }
// NewSitemapIndex creates a sitemap index. // NewSitemap creates a sitemap index.
func NewSitemapIndex() *Sitemap { func NewSitemapIndex() *Sitemap {
return &Sitemap{ return &Sitemap{
XMLName: xml.Name{Local: sitemapindexName}, XMLName: xml.Name{Local: "sitemapindex"},
Namespace: schemaURL, Namespace: "http://www.sitemaps.org/schemas/sitemap/0.9",
} }
} }
// 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) {
if s.XMLName.Local == sitemapindexName {
s.Sitemaps = append(s.Sitemaps, u)
} else {
s.URLs = append(s.URLs, u) s.URLs = append(s.URLs, u)
}
} }
// WriteTo writes the sitemap to a response // Write 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 l := len(s.URLs); l > urlsLimit { if len(s.URLs) > 50000 {
return 0, fmt.Errorf("The sitemap contains %d URLs, but only %d are allowed", l, urlsLimit) return 0, fmt.Errorf("The sitemap contains too many URLs: %d", len(s.URLs))
}
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 {
@ -77,7 +63,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 has %d bytes, but only %d are allowed", buf.Len(), sitemapFileLimit) return 0, fmt.Errorf("The sitemap is too big: %d", buf.Len())
} }
return buf.WriteTo(w) return buf.WriteTo(w)
} }

View file

@ -7,6 +7,7 @@ package sitemap
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -14,154 +15,63 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNewSitemap(t *testing.T) { func TestOk(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()
tests := []struct { test(
name string []URL{},
urls []URL "",
want string )
wantErr string test(
}{ []URL{
{
name: "empty",
urls: []URL{},
want: xml.Header + `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` +
"" +
"</urlset>\n",
},
{
name: "regular",
urls: []URL{
{URL: "https://gitea.io/test1", LastMod: &ts}, {URL: "https://gitea.io/test1", LastMod: &ts},
}, },
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/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" + )
"</urlset>\n", test(
[]URL{
{URL: "https://gitea.io/test2", LastMod: nil},
}, },
{ "<url><loc>https://gitea.io/test2</loc></url>",
name: "without lastmod", )
urls: []URL{ test(
{URL: "https://gitea.io/test1"}, []URL{
},
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/test1", LastMod: &ts},
{URL: "https://gitea.io/test2", LastMod: nil}, {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/test1</loc><lastmod>2022-04-30T12:33:28Z</lastmod></url>" + "<url><loc>https://gitea.io/test2</loc></url>",
"<url><loc>https://gitea.io/test2</loc></url>" + )
"</urlset>\n", }
},
{ func TestTooManyURLs(t *testing.T) {
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",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSitemap() s := NewSitemap()
for _, url := range tt.urls { for i := 0; i < 50001; i++ {
s.Add(url) s.Add(URL{URL: fmt.Sprintf("https://gitea.io/test%d", i)})
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
_, err := s.WriteTo(buf) _, err := s.WriteTo(buf)
if tt.wantErr != "" { assert.EqualError(t, err, "The sitemap contains too many URLs: 50001")
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equalf(t, tt.want, buf.String(), "NewSitemap()")
}
})
}
} }
func TestNewSitemapIndex(t *testing.T) { func TestSitemapTooBig(t *testing.T) {
ts := time.Unix(1651322008, 0).UTC() s := NewSitemap()
s.Add(URL{URL: strings.Repeat("b", sitemapFileLimit)})
tests := []struct {
name string
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{} buf := &bytes.Buffer{}
_, err := s.WriteTo(buf) _, err := s.WriteTo(buf)
if tt.wantErr != "" { assert.EqualError(t, err, "The sitemap is too big: 52428931")
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()")
}
})
}
} }

View file

@ -10,7 +10,6 @@ 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
@ -23,5 +22,4 @@ 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"`
} }

View file

@ -77,14 +77,7 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
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: func() { BetweenCallback: renderer.CompileTemplates,
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

View file

@ -23,6 +23,20 @@ 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()&notRegularFileMode) == 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) {

View file

@ -497,7 +497,6 @@ 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

View file

@ -34,60 +34,6 @@ 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
@ -138,21 +84,41 @@ func getOrCreateUploadVersion(pi *packages_service.PackageInfo) (*packages_model
return nil return nil
}) })
uploadVersionMutex.Unlock() uploadVersionMutex.Unlock()
if err != nil {
return nil, err
}
return uploadVersion, 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
}
}
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256)) filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
pf := &packages_model.PackageFile{ pf := &packages_model.PackageFile{
VersionID: pv.ID, VersionID: uploadVersion.ID,
BlobID: pb.ID, BlobID: pb.ID,
Name: filename, Name: filename,
LowerName: filename, LowerName: filename,
CompositeKey: packages_model.EmptyFileKey, CompositeKey: packages_model.EmptyFileKey,
} }
var err error
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil { if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
if err == packages_model.ErrDuplicatePackageFile { if err == packages_model.ErrDuplicatePackageFile {
return nil return nil
@ -167,6 +133,17 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
} }
return nil 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
} }
func deleteBlob(ownerID int64, image, digest string) error { func deleteBlob(ownerID int64, image, digest string) error {

View file

@ -196,15 +196,10 @@ 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{
Repository: from, Image: 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,

View file

@ -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(true), reqRepoReader(unit.TypeCode)) }, context.ReferencesGitRepo(), 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)

View file

@ -350,7 +350,6 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
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 {

View file

@ -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 = setting.DisableGravatar // when installing, there is no database connection so that given a default value form.DisableGravatar = 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.EnableFederatedAvatar = false // 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,13 +443,10 @@ 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.save_config_failed", err), tplInstall, &form) ctx.RenderWithErr(ctx.Tr("install.secret_key_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))

View file

@ -108,21 +108,14 @@ 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 err == nil { if organization.IsErrLastOrgOwner(err) {
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",
}) })
} else {
log.Error("RemoveOrgUser(%d,%d): %v", org.ID, ctx.Doer.ID, err)
}
return return
} }
}
if err != nil { if err != nil {
log.Error("Action(%s): %v", ctx.Params(":action"), err) log.Error("Action(%s): %v", ctx.Params(":action"), err)

View file

@ -784,10 +784,6 @@ 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, ",")

View file

@ -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("explore.code") ctx.Data["Title"] = ctx.Tr("code.title")
ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["ContextUser"] = ctx.ContextUser
language := ctx.FormTrim("l") language := ctx.FormTrim("l")

View file

@ -100,18 +100,14 @@ 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
keyID := err.(asymkey_model.ErrGPGInvalidTokenSignature).ID ctx.Data["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
keyID := err.(asymkey_model.ErrGPGNoEmailFound).ID ctx.Data["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)
@ -143,9 +139,7 @@ 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
keyID := err.(asymkey_model.ErrGPGInvalidTokenSignature).ID ctx.Data["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)

View file

@ -18,6 +18,7 @@ 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.
@ -200,7 +201,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, issue.Ref) issueRefURLs[issue.ID] = git.RefURL(repoLink, util.PathEscapeSegments(issue.Ref))
} }
} }
return issueRefEndNames, issueRefURLs return issueRefEndNames, issueRefURLs

View file

@ -34,14 +34,10 @@ 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, "/")
if len(fields) < 2 { oldOwner := fields[1]
return nil, fmt.Errorf("invalid path: %s", u.Path) oldName := strings.TrimSuffix(fields[2], ".git")
}
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
@ -76,7 +72,6 @@ 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,
} }

View file

@ -76,7 +76,6 @@ 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
@ -806,9 +805,6 @@ 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,
} }

View file

@ -73,21 +73,7 @@ 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:
} }
// we can ignore the error here because it will be logged in GitGCRepo log.Trace("Running git gc on %v", repo)
_ = GitGcRepo(ctx, repo, timeout, args)
return nil
},
); err != nil {
return err
}
log.Trace("Finished: GitGcRepos")
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...). command := git.NewCommand(ctx, args...).
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())) SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
var stdout string var stdout string
@ -95,9 +81,9 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du
stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()})
if err != nil { if err != nil {
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) 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) 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 { if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err) log.Error("CreateRepositoryNotice: %v", err)
} }
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err) return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
@ -105,14 +91,21 @@ func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Du
// Now update the size of the repository // Now update the size of the repository
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { 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) 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) 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 { if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err) 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 fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
} }
return nil
},
); err != nil {
return err
}
log.Trace("Finished: GitGcRepos")
return nil return nil
} }
@ -169,7 +162,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 %-v: Error: %v", repo, err) log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, 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)
} }

View file

@ -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,omitempty"` AvatarURL string `json:"avatar_url"`
TTS bool `json:"tts"` TTS bool `json:"tts"`
Embeds []DiscordEmbed `json:"embeds"` Embeds []DiscordEmbed `json:"embeds"`
} }

View file

@ -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.HookIssueReviewed: case api.HookIssueSynchronized:
action, err := parseHookPullRequestEventType(event) action, err := parseHookPullRequestEventType(event)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -77,7 +77,6 @@
</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">
@ -116,7 +115,6 @@
</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">

View file

@ -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 word-break"> <div class="ui warning message text left">
{{.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">

View file

@ -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"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}<span class="sr-mobile-only">{{.locale.Tr "repo.commit.actions"}}</span>
<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>

View file

@ -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-load-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-show-more-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"}}

View file

@ -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.FormatDate}}">{{.Issue.DeadlineUnix.FormatDate}}</time> <time data-format="date" datetime="{{.Issue.DeadlineUnix.FormatLong}}">{{.Issue.DeadlineUnix.FormatDate}}</time>
</div> </div>
<div> <div>
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}} {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}

View file

@ -18,17 +18,15 @@
{{end}} {{end}}
{{range $.LatestCommitStatuses}} {{range $.LatestCommitStatuses}}
<div class="ui attached segment pr-status"> <div class="ui attached segment">
{{template "repo/commit_status" .}} <span>{{template "repo/commit_status" .}}</span>
<div class="status-context"> <span class="ui">{{.Context}} <span class="text grey">{{.Description}}</span></span>
<span>{{.Context}} <span class="text grey">{{.Description}}</span></span> <div class="ui right">
<div class="ui status-details">
{{if $.is_context_required}} {{if $.is_context_required}}
{{if (call $.is_context_required .Context)}}<div class="ui label">{{$.locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}} {{if (call $.is_context_required .Context)}}<div class="ui label">{{$.locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}}
{{end}} {{end}}
<span class="ui">{{if .TargetURL}}<a href="{{.TargetURL}}">{{$.locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span> <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}}
{{end}} {{end}}

View file

@ -14831,10 +14831,6 @@
"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"
@ -18015,10 +18011,6 @@
"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"

View file

@ -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 "settings.gpg_token"}} <label for="token">{{.locale.Tr "setting.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 .KeyID}}</code></p> <p><code>{{$.locale.Tr "settings.gpg_token_code" .TokenToSign .PaddedKeyID}}</code></p>
</div> </div>
</div> </div>
<div class="field"> <div class="field">

View file

@ -17,13 +17,9 @@
{{range .Orgs}} {{range .Orgs}}
<div class="item"> <div class="item">
<div class="right floated content"> <div class="right floated content">
<form> <form method="post" action="{{.OrganisationLink}}/members/action/leave">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button class="ui red button delete-button" data-modal-id="leave-organization" <button type="submit" class="ui primary small button" name="uid" value="{{.ID}}">{{$.locale.Tr "org.members.leave"}}</button>
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"}}
@ -40,14 +36,4 @@
</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" .}}

View file

@ -257,32 +257,6 @@ 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) {
@ -471,6 +445,21 @@ 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)()

View file

@ -209,6 +209,8 @@ 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)
@ -223,8 +225,12 @@ 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 loginUserWithPassword(t, userName, userPassword) return session
}
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 {

View file

@ -22,4 +22,7 @@ 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")
} }

View file

@ -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 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> <span>{{ 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,9 +98,6 @@ 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() {

View file

@ -119,47 +119,26 @@ 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-files', (e) => { $(document).on('click', 'a.diff-show-more-button', (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);

View file

@ -200,7 +200,7 @@ function getRelativeColor(color) {
} }
function rgbToHex(rgb) { function rgbToHex(rgb) {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+).*\)$/); rgb = rgb.match(/^rgb\((\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])}`;
} }

View file

@ -24,7 +24,6 @@ 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.

View file

@ -1,6 +1,6 @@
function displayError(el, err) { function displayError(el, err) {
const target = targetElement(el); const target = targetElement(el);
target.classList.remove('is-loading'); target.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,16 +23,12 @@ export async function renderMath() {
for (const el of els) { for (const el of els) {
const source = el.textContent; const source = el.textContent;
const displayMode = el.classList.contains('display'); const options = {display: el.classList.contains('display')};
const nodeName = displayMode ? 'p' : 'span';
try { try {
const tempEl = document.createElement(nodeName); const markup = katex.renderToString(source, options);
katex.render(source, tempEl, { const tempEl = document.createElement(options.display ? 'p' : 'span');
maxSize: 25, tempEl.innerHTML = markup;
maxExpand: 50,
displayMode,
});
targetElement(el).replaceWith(tempEl); targetElement(el).replaceWith(tempEl);
} catch (error) { } catch (error) {
displayError(el, error); displayError(el, error);

View file

@ -1665,9 +1665,6 @@
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) {
@ -3494,41 +3491,3 @@ 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
}
}
}