Compare commits
25 Commits
v1.22.3
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26437a03b0 | ||
|
|
b48df1082e | ||
|
|
eb5733636b | ||
|
|
62d8433194 | ||
|
|
22a93c1cdc | ||
|
|
16e51e91a1 | ||
|
|
936847b3da | ||
|
|
7430d069b3 | ||
|
|
a3b7b98336 | ||
|
|
898f852d03 | ||
|
|
9d62d7a443 | ||
|
|
bf53ab26fa | ||
|
|
0d11ba93dd | ||
|
|
b7d12347f3 | ||
|
|
b6f8372d7d | ||
|
|
0c12252c23 | ||
|
|
99cac1f50c | ||
|
|
2a99607add | ||
|
|
c1023b97aa | ||
|
|
7e0fd4c208 | ||
|
|
db7349bc0d | ||
|
|
55562f9c79 | ||
|
|
24b65f122a | ||
|
|
bcfe1f91d2 | ||
|
|
f15d5f0c4a |
2
go.mod
2
go.mod
@@ -40,7 +40,7 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.8.7
|
||||
github.com/go-enry/go-enry/v2 v2.9.1
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -288,8 +288,8 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-enry/go-enry/v2 v2.8.7 h1:vbab0pcf5Yo1cHQLzbWZ+QomUh3EfEU8EiR5n7W0lnQ=
|
||||
github.com/go-enry/go-enry/v2 v2.8.7/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-enry/v2 v2.9.1 h1:G9iDteJ/Mc0F4Di5NeQknf83R2OkRbwY9cAYmcqVG6U=
|
||||
github.com/go-enry/go-enry/v2 v2.9.1/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
|
||||
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
|
||||
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
|
||||
@@ -69,7 +69,7 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||
OwnerID: t.OwnerID,
|
||||
CommitSHA: t.CommitSHA,
|
||||
Status: int64(ArtifactStatusUploadPending),
|
||||
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays),
|
||||
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
|
||||
}
|
||||
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
|
||||
return nil, err
|
||||
@@ -78,6 +78,13 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(artifact.ID).Cols("expired_unix").Update(&ActionArtifact{
|
||||
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ func (m *Milestone) BeforeUpdate() {
|
||||
// this object.
|
||||
func (m *Milestone) AfterLoad() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
if m.DeadlineUnix.Year() == 9999 {
|
||||
if m.DeadlineUnix.Year() >= 9999 {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -110,26 +110,28 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
||||
return nil, err
|
||||
}
|
||||
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uniqueUserIDs := make(container.Set[int64])
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
|
||||
if repo.Owner.IsOrganization() {
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
}
|
||||
|
||||
// Leave a seat for owner itself to append later, but if owner is an organization
|
||||
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||
if len(userIDs) > 0 {
|
||||
if len(uniqueUserIDs) > 0 {
|
||||
if err = e.In("id", uniqueUserIDs.Values()).
|
||||
Where(builder.Eq{"`user`.is_active": true}).
|
||||
OrderBy(user_model.GetOrderByName()).
|
||||
|
||||
@@ -136,8 +136,16 @@ func parsePackage(r io.Reader) (*Package, error) {
|
||||
|
||||
dependencies := make([]*Dependency, 0, len(meta.Deps))
|
||||
for _, dep := range meta.Deps {
|
||||
// https://doc.rust-lang.org/cargo/reference/registry-web-api.html#publish
|
||||
// It is a string of the new package name if the dependency is renamed, otherwise empty
|
||||
name := dep.ExplicitNameInToml
|
||||
pkg := &dep.Name
|
||||
if name == "" {
|
||||
name = dep.Name
|
||||
pkg = nil
|
||||
}
|
||||
dependencies = append(dependencies, &Dependency{
|
||||
Name: dep.Name,
|
||||
Name: name,
|
||||
Req: dep.VersionReq,
|
||||
Features: dep.Features,
|
||||
Optional: dep.Optional,
|
||||
@@ -145,6 +153,7 @@ func parsePackage(r io.Reader) (*Package, error) {
|
||||
Target: dep.Target,
|
||||
Kind: dep.Kind,
|
||||
Registry: dep.Registry,
|
||||
Package: pkg,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
description = "Package Description"
|
||||
author = "KN4CK3R"
|
||||
homepage = "https://gitea.io/"
|
||||
license = "MIT"
|
||||
)
|
||||
|
||||
func TestParsePackage(t *testing.T) {
|
||||
createPackage := func(name, version string) io.Reader {
|
||||
metadata := `{
|
||||
const (
|
||||
description = "Package Description"
|
||||
author = "KN4CK3R"
|
||||
homepage = "https://gitea.io/"
|
||||
license = "MIT"
|
||||
payload = "gitea test dummy payload" // a fake payload for test only
|
||||
)
|
||||
makeDefaultPackageMeta := func(name, version string) string {
|
||||
return `{
|
||||
"name":"` + name + `",
|
||||
"vers":"` + version + `",
|
||||
"description":"` + description + `",
|
||||
@@ -36,18 +36,19 @@ func TestParsePackage(t *testing.T) {
|
||||
"homepage":"` + homepage + `",
|
||||
"license":"` + license + `"
|
||||
}`
|
||||
|
||||
}
|
||||
createPackage := func(metadata string) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.LittleEndian, uint32(len(metadata)))
|
||||
buf.WriteString(metadata)
|
||||
binary.Write(&buf, binary.LittleEndian, uint32(4))
|
||||
buf.WriteString("test")
|
||||
binary.Write(&buf, binary.LittleEndian, uint32(len(payload)))
|
||||
buf.WriteString(payload)
|
||||
return &buf
|
||||
}
|
||||
|
||||
t.Run("InvalidName", func(t *testing.T) {
|
||||
for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} {
|
||||
data := createPackage(name, "1.0.0")
|
||||
data := createPackage(makeDefaultPackageMeta(name, "1.0.0"))
|
||||
|
||||
cp, err := ParsePackage(data)
|
||||
assert.Nil(t, cp)
|
||||
@@ -57,7 +58,7 @@ func TestParsePackage(t *testing.T) {
|
||||
|
||||
t.Run("InvalidVersion", func(t *testing.T) {
|
||||
for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} {
|
||||
data := createPackage("test", version)
|
||||
data := createPackage(makeDefaultPackageMeta("test", version))
|
||||
|
||||
cp, err := ParsePackage(data)
|
||||
assert.Nil(t, cp)
|
||||
@@ -66,7 +67,7 @@ func TestParsePackage(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
data := createPackage("test", "1.0.0")
|
||||
data := createPackage(makeDefaultPackageMeta("test", "1.0.0"))
|
||||
|
||||
cp, err := ParsePackage(data)
|
||||
assert.NotNil(t, cp)
|
||||
@@ -78,9 +79,34 @@ func TestParsePackage(t *testing.T) {
|
||||
assert.Equal(t, []string{author}, cp.Metadata.Authors)
|
||||
assert.Len(t, cp.Metadata.Dependencies, 1)
|
||||
assert.Equal(t, "dep", cp.Metadata.Dependencies[0].Name)
|
||||
assert.Nil(t, cp.Metadata.Dependencies[0].Package)
|
||||
assert.Equal(t, homepage, cp.Metadata.ProjectURL)
|
||||
assert.Equal(t, license, cp.Metadata.License)
|
||||
content, _ := io.ReadAll(cp.Content)
|
||||
assert.Equal(t, "test", string(content))
|
||||
assert.Equal(t, payload, string(content))
|
||||
})
|
||||
|
||||
t.Run("Renamed", func(t *testing.T) {
|
||||
data := createPackage(`{
|
||||
"name":"test-pkg",
|
||||
"vers":"1.0",
|
||||
"description":"test-desc",
|
||||
"authors": ["test-author"],
|
||||
"deps":[
|
||||
{
|
||||
"name":"dep-renamed",
|
||||
"explicit_name_in_toml":"dep-explicit",
|
||||
"version_req":"1.0"
|
||||
}
|
||||
],
|
||||
"homepage":"https://gitea.io/",
|
||||
"license":"MIT"
|
||||
}`)
|
||||
cp, err := ParsePackage(data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-pkg", cp.Name)
|
||||
assert.Equal(t, "https://gitea.io/", cp.Metadata.ProjectURL)
|
||||
assert.Equal(t, "dep-explicit", cp.Metadata.Dependencies[0].Name)
|
||||
assert.Equal(t, "dep-renamed", *cp.Metadata.Dependencies[0].Package)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func (s *ContentStore) ShouldServeDirect() bool {
|
||||
return setting.Packages.Storage.MinioConfig.ServeDirect
|
||||
}
|
||||
|
||||
func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string) (*url.URL, error) {
|
||||
return s.store.URL(KeyToRelativePath(key), filename)
|
||||
func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename string, reqParams url.Values) (*url.URL, error) {
|
||||
return s.store.URL(KeyToRelativePath(key), filename, reqParams)
|
||||
}
|
||||
|
||||
// FIXME: Workaround to be removed in v1.20
|
||||
|
||||
@@ -340,9 +340,10 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
|
||||
|
||||
for _, tag := range updates {
|
||||
if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)).
|
||||
Cols("sha1").
|
||||
Cols("sha1", "created_unix").
|
||||
Update(&repo_model.Release{
|
||||
Sha1: tag.Object.String(),
|
||||
Sha1: tag.Object.String(),
|
||||
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (s discardStorage) Delete(_ string) error {
|
||||
return fmt.Errorf("%s", s)
|
||||
}
|
||||
|
||||
func (s discardStorage) URL(_, _ string) (*url.URL, error) {
|
||||
func (s discardStorage) URL(_, _ string, _ url.Values) (*url.URL, error) {
|
||||
return nil, fmt.Errorf("%s", s)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ func Test_discardStorage(t *testing.T) {
|
||||
assert.Error(t, err, string(tt))
|
||||
}
|
||||
{
|
||||
got, err := tt.URL("path", "name")
|
||||
got, err := tt.URL("path", "name", nil)
|
||||
assert.Nil(t, got)
|
||||
assert.Errorf(t, err, string(tt))
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (l *LocalStorage) Delete(path string) error {
|
||||
}
|
||||
|
||||
// URL gets the redirect URL to a file
|
||||
func (l *LocalStorage) URL(path, name string) (*url.URL, error) {
|
||||
func (l *LocalStorage) URL(path, name string, reqParams url.Values) (*url.URL, error) {
|
||||
return nil, ErrURLNotSupported
|
||||
}
|
||||
|
||||
|
||||
@@ -235,8 +235,12 @@ func (m *MinioStorage) Delete(path string) error {
|
||||
}
|
||||
|
||||
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
||||
func (m *MinioStorage) URL(path, name string) (*url.URL, error) {
|
||||
reqParams := make(url.Values)
|
||||
func (m *MinioStorage) URL(path, name string, serveDirectReqParams url.Values) (*url.URL, error) {
|
||||
// copy serveDirectReqParams
|
||||
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
|
||||
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"")
|
||||
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams)
|
||||
|
||||
@@ -63,7 +63,7 @@ type ObjectStorage interface {
|
||||
Save(path string, r io.Reader, size int64) (int64, error)
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
Delete(path string) error
|
||||
URL(path, name string) (*url.URL, error)
|
||||
URL(path, name string, reqParams url.Values) (*url.URL, error)
|
||||
IterateObjects(path string, iterator func(path string, obj Object) error) error
|
||||
}
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
|
||||
for _, artifact := range artifacts {
|
||||
var downloadURL string
|
||||
if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
|
||||
u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName)
|
||||
u, err := ar.fs.URL(artifact.StoragePath, artifact.ArtifactName, nil)
|
||||
if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
|
||||
log.Error("Error getting serve direct url: %v", err)
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
|
||||
respData := GetSignedArtifactURLResponse{}
|
||||
|
||||
if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
|
||||
u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath)
|
||||
u, err := storage.ActionsArtifacts.URL(artifact.StoragePath, artifact.ArtifactPath, nil)
|
||||
if u != nil && err == nil {
|
||||
respData.SignedUrl = u.String()
|
||||
}
|
||||
|
||||
@@ -715,7 +715,9 @@ func DeleteManifest(ctx *context.Context) {
|
||||
}
|
||||
|
||||
func serveBlob(ctx *context.Context, pfd *packages_model.PackageFileDescriptor) {
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob)
|
||||
serveDirectReqParams := make(url.Values)
|
||||
serveDirectReqParams.Set("response-content-type", pfd.Properties.GetByName(container_module.PropertyMediaType))
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pfd.File, pfd.Blob, serveDirectReqParams)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
||||
@@ -215,7 +215,7 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool
|
||||
return
|
||||
}
|
||||
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb)
|
||||
s, u, _, err := packages_service.GetPackageBlobStream(ctx, pf, pb, nil)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
||||
@@ -356,12 +356,20 @@ func reqToken() func(ctx *context.APIContext) {
|
||||
|
||||
func reqExploreSignIn() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
|
||||
if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
|
||||
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reqUsersExploreEnabled() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if setting.Service.Explore.DisableUsersPage {
|
||||
ctx.NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
||||
@@ -955,7 +963,7 @@ func Routes() *web.Route {
|
||||
|
||||
// Users (requires user scope)
|
||||
m.Group("/users", func() {
|
||||
m.Get("/search", reqExploreSignIn(), user.Search)
|
||||
m.Get("/search", reqExploreSignIn(), reqUsersExploreEnabled(), user.Search)
|
||||
|
||||
m.Group("/{username}", func() {
|
||||
m.Get("", reqExploreSignIn(), user.GetInfo)
|
||||
|
||||
@@ -203,7 +203,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
||||
|
||||
if setting.LFS.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name())
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
@@ -328,7 +328,7 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
|
||||
rPath := archiver.RelativePath()
|
||||
if setting.RepoArchive.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.RepoArchives.URL(rPath, downloadName)
|
||||
u, err := storage.RepoArchives.URL(rPath, downloadName, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
|
||||
@@ -41,80 +41,93 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
// parameters:
|
||||
// - name: state
|
||||
// in: query
|
||||
// description: whether issue is open or closed
|
||||
// description: State of the issue
|
||||
// type: string
|
||||
// enum: [open, closed, all]
|
||||
// default: open
|
||||
// - name: labels
|
||||
// in: query
|
||||
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
|
||||
// description: Comma-separated list of label names. Fetch only issues that have any of these labels. Non existent labels are discarded.
|
||||
// type: string
|
||||
// - name: milestones
|
||||
// in: query
|
||||
// description: comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded
|
||||
// description: Comma-separated list of milestone names. Fetch only issues that have any of these milestones. Non existent milestones are discarded.
|
||||
// type: string
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: search string
|
||||
// description: Search string
|
||||
// type: string
|
||||
// - name: priority_repo_id
|
||||
// in: query
|
||||
// description: repository to prioritize in the results
|
||||
// description: Repository ID to prioritize in the results
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: type
|
||||
// in: query
|
||||
// description: filter by type (issues / pulls) if set
|
||||
// description: Filter by issue type
|
||||
// type: string
|
||||
// enum: [issues, pulls]
|
||||
// - name: since
|
||||
// in: query
|
||||
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
|
||||
// description: Only show issues updated after the given time (RFC 3339 format)
|
||||
// type: string
|
||||
// format: date-time
|
||||
// required: false
|
||||
// - name: before
|
||||
// in: query
|
||||
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
|
||||
// description: Only show issues updated before the given time (RFC 3339 format)
|
||||
// type: string
|
||||
// format: date-time
|
||||
// required: false
|
||||
// - name: assigned
|
||||
// in: query
|
||||
// description: filter (issues / pulls) assigned to you, default is false
|
||||
// description: Filter issues or pulls assigned to the authenticated user
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - name: created
|
||||
// in: query
|
||||
// description: filter (issues / pulls) created by you, default is false
|
||||
// description: Filter issues or pulls created by the authenticated user
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - name: mentioned
|
||||
// in: query
|
||||
// description: filter (issues / pulls) mentioning you, default is false
|
||||
// description: Filter issues or pulls mentioning the authenticated user
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - name: review_requested
|
||||
// in: query
|
||||
// description: filter pulls requesting your review, default is false
|
||||
// description: Filter pull requests where the authenticated user's review was requested
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - name: reviewed
|
||||
// in: query
|
||||
// description: filter pulls reviewed by you, default is false
|
||||
// description: Filter pull requests reviewed by the authenticated user
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - name: owner
|
||||
// in: query
|
||||
// description: filter by owner
|
||||
// description: Filter by repository owner
|
||||
// type: string
|
||||
// - name: team
|
||||
// in: query
|
||||
// description: filter by team (requires organization owner parameter to be provided)
|
||||
// description: Filter by team (requires organization owner parameter)
|
||||
// type: string
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// description: Page number of results to return (1-based)
|
||||
// type: integer
|
||||
// minimum: 1
|
||||
// default: 1
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// description: Number of items per page
|
||||
// type: integer
|
||||
// minimum: 0
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/IssueList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
|
||||
@@ -39,7 +39,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = util.PathJoinRelX(rPath)
|
||||
|
||||
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||
u, err := objStore.URL(rPath, path.Base(rPath), nil)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@@ -33,7 +34,7 @@ func Organizations(ctx *context.Context) {
|
||||
)
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if sortOrder == "" {
|
||||
sortOrder = "newest"
|
||||
sortOrder = util.Iif(supportedSortOrders.Contains(setting.UI.ExploreDefaultSort), setting.UI.ExploreDefaultSort, "newest")
|
||||
ctx.SetFormString("sort", sortOrder)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/sitemap"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@@ -147,7 +148,7 @@ func Users(ctx *context.Context) {
|
||||
)
|
||||
sortOrder := ctx.FormString("sort")
|
||||
if sortOrder == "" {
|
||||
sortOrder = "newest"
|
||||
sortOrder = util.Iif(supportedSortOrders.Contains(setting.UI.ExploreDefaultSort), setting.UI.ExploreDefaultSort, "newest")
|
||||
ctx.SetFormString("sort", sortOrder)
|
||||
}
|
||||
|
||||
|
||||
@@ -626,7 +626,7 @@ func ArtifactsDownloadView(ctx *context_module.Context) {
|
||||
if len(artifacts) == 1 && artifacts[0].ArtifactName+".zip" == artifacts[0].ArtifactPath && artifacts[0].ContentEncoding == "application/zip" {
|
||||
art := artifacts[0]
|
||||
if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
|
||||
u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath)
|
||||
u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
|
||||
@@ -129,7 +129,7 @@ func ServeAttachment(ctx *context.Context, uuid string) {
|
||||
|
||||
if setting.Attachment.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name)
|
||||
u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name, nil)
|
||||
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
|
||||
@@ -55,7 +55,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim
|
||||
|
||||
if setting.LFS.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name())
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return nil
|
||||
|
||||
@@ -494,7 +494,7 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
||||
rPath := archiver.RelativePath()
|
||||
if setting.RepoArchive.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.RepoArchives.URL(rPath, downloadName)
|
||||
u, err := storage.RepoArchives.URL(rPath, downloadName, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
|
||||
@@ -8,37 +8,24 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// Search search users
|
||||
func Search(ctx *context.Context) {
|
||||
listOptions := db.ListOptions{
|
||||
Page: ctx.FormInt("page"),
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
}
|
||||
|
||||
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
// SearchCandidates searches candidate users for dropdown list
|
||||
func SearchCandidates(ctx *context.Context) {
|
||||
users, _, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: ctx.FormInt64("uid"),
|
||||
Type: user_model.UserTypeIndividual,
|
||||
IsActive: ctx.FormOptionalBool("active"),
|
||||
ListOptions: listOptions,
|
||||
IsActive: optional.Some(true),
|
||||
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
ctx.ServerError("Unable to search users", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
"data": convert.ToUsers(ctx, ctx.Doer, users),
|
||||
})
|
||||
ctx.JSON(http.StatusOK, map[string]any{"data": convert.ToUsers(ctx, ctx.Doer, users)})
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
} else {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,8 +64,9 @@ func DisableTwoFactor(ctx *context.Context) {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
} else {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,8 +75,9 @@ func DisableTwoFactor(ctx *context.Context) {
|
||||
// There is a potential DB race here - we must have been disabled by another request in the intervening period
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
} else {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
|
||||
}
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -668,7 +668,7 @@ func registerRoutes(m *web.Route) {
|
||||
m.Post("/forgot_password", auth.ForgotPasswdPost)
|
||||
m.Post("/logout", auth.SignOut)
|
||||
m.Get("/stopwatches", reqSignIn, user.GetStopwatches)
|
||||
m.Get("/search", ignExploreSignIn, user.Search)
|
||||
m.Get("/search_candidates", ignExploreSignIn, user.SearchCandidates)
|
||||
m.Group("/oauth2", func() {
|
||||
m.Get("/{provider}", auth.SignInOAuth)
|
||||
m.Get("/{provider}/callback", auth.SignInOAuthCallback)
|
||||
@@ -1454,6 +1454,35 @@ func registerRoutes(m *web.Route) {
|
||||
)
|
||||
// end "/{username}/{reponame}/activity"
|
||||
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/pulls/{index}", func() {
|
||||
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
|
||||
m.Get(".diff", repo.DownloadPullDiff)
|
||||
m.Get(".patch", repo.DownloadPullPatch)
|
||||
m.Group("/commits", func() {
|
||||
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
|
||||
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
|
||||
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
||||
})
|
||||
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
||||
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
||||
m.Post("/update", repo.UpdatePullRequest)
|
||||
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
||||
m.Group("/files", func() {
|
||||
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
|
||||
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
|
||||
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
|
||||
m.Group("/reviews", func() {
|
||||
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
|
||||
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
|
||||
m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
|
||||
}, context.RepoMustNotBeArchived())
|
||||
})
|
||||
})
|
||||
}, ignSignIn, context.RepoAssignment, repo.MustAllowPulls, reqRepoPullsReader)
|
||||
// end "/{username}/{reponame}/pulls/{index}": repo pull request
|
||||
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/activity_author_data", func() {
|
||||
m.Get("", repo.ActivityAuthors)
|
||||
@@ -1492,32 +1521,6 @@ func registerRoutes(m *web.Route) {
|
||||
return cancel
|
||||
})
|
||||
|
||||
m.Group("/pulls/{index}", func() {
|
||||
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
|
||||
m.Get(".diff", repo.DownloadPullDiff)
|
||||
m.Get(".patch", repo.DownloadPullPatch)
|
||||
m.Group("/commits", func() {
|
||||
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
|
||||
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
|
||||
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
||||
})
|
||||
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
||||
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
||||
m.Post("/update", repo.UpdatePullRequest)
|
||||
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
||||
m.Group("/files", func() {
|
||||
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
|
||||
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
|
||||
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
|
||||
m.Group("/reviews", func() {
|
||||
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
|
||||
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
|
||||
m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
|
||||
}, context.RepoMustNotBeArchived())
|
||||
})
|
||||
}, repo.MustAllowPulls)
|
||||
|
||||
m.Group("/media", func() {
|
||||
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
|
||||
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
|
||||
|
||||
@@ -115,11 +115,20 @@ func (input *notifyInput) Notify(ctx context.Context) {
|
||||
}
|
||||
|
||||
func notify(ctx context.Context, input *notifyInput) error {
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
if input.Doer.IsActions() {
|
||||
// avoiding triggering cyclically, for example:
|
||||
// a comment of an issue will trigger the runner to add a new comment as reply,
|
||||
// and the new comment will trigger the runner again.
|
||||
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
|
||||
|
||||
// we should update schedule tasks in this case, because
|
||||
// 1. schedule tasks cannot be triggered by other events, so cyclic triggering will not occur
|
||||
// 2. some schedule tasks may update the repo periodically, so the refs of schedule tasks need to be updated
|
||||
if shouldDetectSchedules {
|
||||
return DetectAndHandleSchedules(ctx, input.Repo)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if input.Repo.IsEmpty || input.Repo.IsArchived {
|
||||
@@ -173,7 +182,6 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
|
||||
var detectedWorkflows []*actions_module.DetectedWorkflow
|
||||
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
|
||||
input.Event,
|
||||
input.Payload,
|
||||
|
||||
@@ -27,6 +27,9 @@ var (
|
||||
|
||||
// CheckOAuthAccessToken returns uid of user from oauth token
|
||||
func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 {
|
||||
if !setting.OAuth2.Enabled {
|
||||
return 0
|
||||
}
|
||||
// JWT tokens require a "."
|
||||
if !strings.Contains(accessToken, ".") {
|
||||
return 0
|
||||
|
||||
@@ -455,7 +455,7 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
|
||||
var link *lfs_module.Link
|
||||
if setting.LFS.Storage.MinioConfig.ServeDirect {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid)
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), pointer.Oid, nil)
|
||||
if u != nil && err == nil {
|
||||
// Presigned url does not need the Authorization header
|
||||
// https://github.com/go-gitea/gitea/issues/21525
|
||||
|
||||
@@ -596,12 +596,12 @@ func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return GetPackageBlobStream(ctx, pf, pb)
|
||||
return GetPackageBlobStream(ctx, pf, pb, nil)
|
||||
}
|
||||
|
||||
// GetPackageBlobStream returns the content of the specific package blob
|
||||
// If the storage supports direct serving and it's enabled, only the direct serving url is returned.
|
||||
func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
key := packages_module.BlobHash256Key(pb.HashSHA256)
|
||||
|
||||
cs := packages_module.NewContentStore()
|
||||
@@ -611,7 +611,7 @@ func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, p
|
||||
var err error
|
||||
|
||||
if cs.ShouldServeDirect() {
|
||||
u, err = cs.GetServeDirectURL(key, pf.Name)
|
||||
u, err = cs.GetServeDirectURL(key, pf.Name, serveDirectReqParams)
|
||||
if err != nil && !errors.Is(err, storage.ErrURLNotSupported) {
|
||||
log.Error("Error getting serve direct url: %v", err)
|
||||
}
|
||||
|
||||
@@ -320,9 +320,10 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
@@ -409,13 +410,17 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
if rel.IsTag {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = false
|
||||
}
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
|
||||
@@ -32,6 +32,10 @@ import (
|
||||
|
||||
// RenameUser renames a user
|
||||
func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
|
||||
if newUserName == u.Name {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Non-local users are not allowed to change their username.
|
||||
if !u.IsOrganization() && !u.IsLocal() {
|
||||
return user_model.ErrUserIsNotLocal{
|
||||
@@ -40,10 +44,6 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string) err
|
||||
}
|
||||
}
|
||||
|
||||
if newUserName == u.Name {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := user_model.IsUsableUsername(newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,6 +289,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
|
||||
if err = DeleteUser(ctx, u, false); err != nil {
|
||||
// Ignore inactive users that were ever active but then were set inactive by admin
|
||||
if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) {
|
||||
log.Warn("Inactive user %q has repositories, organizations or packages, skipping deletion: %v", u.Name, err)
|
||||
continue
|
||||
}
|
||||
select {
|
||||
|
||||
@@ -151,7 +151,7 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
||||
// for each commit, generate attachment text
|
||||
for i, commit := range p.Commits {
|
||||
// limit the commit message display to just the summary, otherwise it would be hard to read
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 1)[0], "\r")
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 2)[0], "\r")
|
||||
|
||||
// a limit of 50 is set because GitHub does the same
|
||||
if utf8.RuneCountInString(message) > 50 {
|
||||
|
||||
@@ -80,12 +80,26 @@ func TestDiscordPayload(t *testing.T) {
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithLongCommitMessage", func(t *testing.T) {
|
||||
t.Run("PushWithMultilineCommitMessage", func(t *testing.T) {
|
||||
p := pushTestMultilineCommitMessagePayload()
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1", pl.Embeds[0].Description)
|
||||
assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithLongCommitSummary", func(t *testing.T) {
|
||||
p := pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body")
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", pl.Embeds[0].Description)
|
||||
|
||||
@@ -68,7 +68,7 @@ func pushTestPayload() *api.PushPayload {
|
||||
}
|
||||
|
||||
func pushTestMultilineCommitMessagePayload() *api.PushPayload {
|
||||
return pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body.")
|
||||
return pushTestPayloadWithCommitMessage("chore: This is a commit summary\n\nThis is a commit description.")
|
||||
}
|
||||
|
||||
func pushTestPayloadWithCommitMessage(message string) *api.PushPayload {
|
||||
|
||||
@@ -29,15 +29,16 @@
|
||||
<div class="default text">empty multiple dropdown</div>
|
||||
<div class="menu">
|
||||
<div class="item">item</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui multiple clearable search selection dropdown">
|
||||
<input type="hidden" value="1">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
{{svg "octicon-x" 14 "remove icon"}}
|
||||
<div class="default text">clearable search dropdown</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="1">item</div>
|
||||
<div class="item">sm1</div>
|
||||
<div class="item">sm2</div>
|
||||
<div class="item">medium1</div>
|
||||
<div class="item">medium2</div>
|
||||
<div class="item">large item1</div>
|
||||
<div class="item">large item2</div>
|
||||
<div class="item">large item3</div>
|
||||
<div class="item">very large item test 1</div>
|
||||
<div class="item">very large item test 2</div>
|
||||
<div class="item">very large item test 3</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui buttons">
|
||||
@@ -50,6 +51,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="ui multiple clearable search selection dropdown tw-max-w-[220px]">
|
||||
<input type="hidden" value="1,2,3,4,5,10">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
{{svg "octicon-x" 14 "remove icon"}}
|
||||
<div class="default text">clearable search dropdown</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="1">item</div>
|
||||
<div class="item" data-value="2">sm1</div>
|
||||
<div class="item" data-value="3">sm2</div>
|
||||
<div class="item" data-value="4">medium1</div>
|
||||
<div class="item" data-value="5">medium2</div>
|
||||
<div class="item" data-value="6">large item1</div>
|
||||
<div class="item" data-value="7">large item2</div>
|
||||
<div class="item" data-value="8">large item3</div>
|
||||
<div class="item" data-value="9">very large item test 1</div>
|
||||
<div class="item" data-value="10">very large item test 2</div>
|
||||
<div class="item" data-value="11">very large item test 3</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Selection</h2>
|
||||
<div>
|
||||
|
||||
58
templates/swagger/v1_json.tmpl
generated
58
templates/swagger/v1_json.tmpl
generated
@@ -3444,107 +3444,125 @@
|
||||
"operationId": "issueSearchIssues",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"open",
|
||||
"closed",
|
||||
"all"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "whether issue is open or closed",
|
||||
"default": "open",
|
||||
"description": "State of the issue",
|
||||
"name": "state",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded",
|
||||
"description": "Comma-separated list of label names. Fetch only issues that have any of these labels. Non existent labels are discarded.",
|
||||
"name": "labels",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded",
|
||||
"description": "Comma-separated list of milestone names. Fetch only issues that have any of these milestones. Non existent milestones are discarded.",
|
||||
"name": "milestones",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "search string",
|
||||
"description": "Search string",
|
||||
"name": "q",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "repository to prioritize in the results",
|
||||
"description": "Repository ID to prioritize in the results",
|
||||
"name": "priority_repo_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"issues",
|
||||
"pulls"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "filter by type (issues / pulls) if set",
|
||||
"description": "Filter by issue type",
|
||||
"name": "type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
|
||||
"description": "Only show issues updated after the given time (RFC 3339 format)",
|
||||
"name": "since",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
|
||||
"description": "Only show issues updated before the given time (RFC 3339 format)",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "filter (issues / pulls) assigned to you, default is false",
|
||||
"default": false,
|
||||
"description": "Filter issues or pulls assigned to the authenticated user",
|
||||
"name": "assigned",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "filter (issues / pulls) created by you, default is false",
|
||||
"default": false,
|
||||
"description": "Filter issues or pulls created by the authenticated user",
|
||||
"name": "created",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "filter (issues / pulls) mentioning you, default is false",
|
||||
"default": false,
|
||||
"description": "Filter issues or pulls mentioning the authenticated user",
|
||||
"name": "mentioned",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "filter pulls requesting your review, default is false",
|
||||
"default": false,
|
||||
"description": "Filter pull requests where the authenticated user's review was requested",
|
||||
"name": "review_requested",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "filter pulls reviewed by you, default is false",
|
||||
"default": false,
|
||||
"description": "Filter pull requests reviewed by the authenticated user",
|
||||
"name": "reviewed",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filter by owner",
|
||||
"description": "Filter by repository owner",
|
||||
"name": "owner",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "filter by team (requires organization owner parameter to be provided)",
|
||||
"description": "Filter by team (requires organization owner parameter)",
|
||||
"name": "team",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"default": 1,
|
||||
"description": "Page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 0,
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"description": "Number of items per page",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
@@ -3552,6 +3570,12 @@
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/IssueList"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"422": {
|
||||
"$ref": "#/responses/validationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/release"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@@ -117,3 +120,47 @@ func TestCreateNewTagProtected(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepushTag(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, owner.LowerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
// create and push a tag
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// create a release for the tag
|
||||
createdRelease := createNewReleaseUsingAPI(t, session, token, owner, repo, "v2.0", "", "Release of v2.0", "desc")
|
||||
assert.False(t, createdRelease.IsDraft)
|
||||
// delete the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--delete", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// query the release by API and it should be a draft
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var respRelease *api.Release
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.True(t, respRelease.IsDraft)
|
||||
// re-push the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// query the release by API and it should not be a draft
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.False(t, respRelease.IsDraft)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1354,6 +1354,10 @@ table th[data-sortt-desc] .svg {
|
||||
min-width: 0; /* make ellipsis work */
|
||||
}
|
||||
|
||||
.ui.multiple.selection.dropdown {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ui.ui.dropdown.selection {
|
||||
min-width: 14em; /* match the default min width */
|
||||
}
|
||||
|
||||
@@ -362,9 +362,9 @@ export default sfc; // activate the IDE's Vue plugin
|
||||
<div class="menu">
|
||||
<a class="item" @click="toggleArchivedFilter()">
|
||||
<div class="ui checkbox" ref="checkboxArchivedFilter" :title="checkboxArchivedFilterTitle">
|
||||
<!--the "hidden" is necessary to make the checkbox work without Fomantic UI js,
|
||||
<!--the "tw-pointer-events-none" is necessary to prevent the checkbox from handling user's input,
|
||||
otherwise if the "input" handles click event for intermediate status, it breaks the internal state-->
|
||||
<input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps">
|
||||
<input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxArchivedFilterProps">
|
||||
<label>
|
||||
<svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/>
|
||||
{{ textShowArchived }}
|
||||
@@ -373,7 +373,7 @@ export default sfc; // activate the IDE's Vue plugin
|
||||
</a>
|
||||
<a class="item" @click="togglePrivateFilter()">
|
||||
<div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle">
|
||||
<input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps">
|
||||
<input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxPrivateFilterProps">
|
||||
<label>
|
||||
<svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/>
|
||||
{{ textShowPrivate }}
|
||||
|
||||
@@ -453,3 +453,11 @@ export function checkAppUrl() {
|
||||
showGlobalErrorMessage(`Your ROOT_URL in app.ini is "${appUrl}", it's unlikely matching the site you are visiting.
|
||||
Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`, 'warning');
|
||||
}
|
||||
|
||||
export function checkAppUrlScheme() {
|
||||
const curUrl = window.location.href;
|
||||
// some users visit "http://domain" while appUrl is "https://domain", COOKIE_SECURE makes it impossible to sign in
|
||||
if (curUrl.startsWith('http:') && appUrl.startsWith('https:')) {
|
||||
showGlobalErrorMessage(`This instance is configured to run under HTTPS (by ROOT_URL config), you are accessing by HTTP. Mismatched scheme might cause problems for sign-in/sign-up.`, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,41 +8,38 @@ export function initCompSearchUserBox() {
|
||||
const searchUserBox = document.getElementById('search-user-box');
|
||||
if (!searchUserBox) return;
|
||||
|
||||
const $searchUserBox = $(searchUserBox);
|
||||
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
|
||||
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
|
||||
$searchUserBox.search({
|
||||
$(searchUserBox).search({
|
||||
minCharacters: 2,
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/user/search?active=1&q={query}`,
|
||||
url: `${appSubUrl}/user/search_candidates?q={query}`,
|
||||
onResponse(response) {
|
||||
const items = [];
|
||||
const searchQuery = $searchUserBox.find('input').val();
|
||||
const resultItems = [];
|
||||
const searchQuery = searchUserBox.querySelector('input').value;
|
||||
const searchQueryUppercase = searchQuery.toUpperCase();
|
||||
$.each(response.data, (_i, item) => {
|
||||
for (const item of response.data) {
|
||||
const resultItem = {
|
||||
title: item.login,
|
||||
image: item.avatar_url,
|
||||
description: htmlEscape(item.full_name),
|
||||
};
|
||||
if (item.full_name) {
|
||||
resultItem.description = htmlEscape(item.full_name);
|
||||
}
|
||||
if (searchQueryUppercase === item.login.toUpperCase()) {
|
||||
items.unshift(resultItem);
|
||||
resultItems.unshift(resultItem); // add the exact match to the top
|
||||
} else {
|
||||
items.push(resultItem);
|
||||
resultItems.push(resultItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (allowEmailInput && !items.length && looksLikeEmailAddressCheck.test(searchQuery)) {
|
||||
if (allowEmailInput && !resultItems.length && looksLikeEmailAddressCheck.test(searchQuery)) {
|
||||
const resultItem = {
|
||||
title: searchQuery,
|
||||
description: allowEmailDescription,
|
||||
};
|
||||
items.push(resultItem);
|
||||
resultItems.push(resultItem);
|
||||
}
|
||||
|
||||
return {results: items};
|
||||
return {results: resultItems};
|
||||
},
|
||||
},
|
||||
searchFields: ['login', 'full_name'],
|
||||
|
||||
@@ -8,7 +8,9 @@ export function initDiffFileTree() {
|
||||
|
||||
const fileTreeView = createApp(DiffFileTree);
|
||||
fileTreeView.mount(el);
|
||||
}
|
||||
|
||||
export function initDiffFileList() {
|
||||
const fileListElement = document.getElementById('diff-file-list');
|
||||
if (!fileListElement) return;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import {initCompReactionSelector} from './comp/ReactionSelector.js';
|
||||
import {initRepoIssueContentHistory} from './repo-issue-content.js';
|
||||
import {initDiffFileTree} from './repo-diff-filetree.js';
|
||||
import {initDiffFileTree, initDiffFileList} from './repo-diff-filetree.js';
|
||||
import {initDiffCommitSelect} from './repo-diff-commitselect.js';
|
||||
import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
|
||||
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
|
||||
@@ -220,6 +220,7 @@ export function initRepoDiffView() {
|
||||
initRepoDiffConversationForm();
|
||||
if (!$('#diff-file-list').length) return;
|
||||
initDiffFileTree();
|
||||
initDiffFileList();
|
||||
initDiffCommitSelect();
|
||||
initRepoDiffShowMore();
|
||||
initRepoDiffReviewButton();
|
||||
|
||||
@@ -4,6 +4,7 @@ import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkd
|
||||
import {createDropzone} from './dropzone.js';
|
||||
import {GET, POST} from '../modules/fetch.js';
|
||||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
import {isImageFile} from '../utils/image.js';
|
||||
import {attachRefIssueContextPopup} from './contextpopup.js';
|
||||
import {initCommentContent, initMarkupContent} from '../markup/content.js';
|
||||
|
||||
@@ -84,10 +85,12 @@ async function onEditContent(event) {
|
||||
for (const attachment of data) {
|
||||
const imgSrc = `${dropzone.getAttribute('data-link-url')}/${attachment.uuid}`;
|
||||
dz.emit('addedfile', attachment);
|
||||
dz.emit('thumbnail', attachment, imgSrc);
|
||||
if (isImageFile(attachment.name)) {
|
||||
dz.emit('thumbnail', attachment, imgSrc);
|
||||
dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
|
||||
}
|
||||
dz.emit('complete', attachment);
|
||||
fileUuidDict[attachment.uuid] = {submitted: true};
|
||||
dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
|
||||
const input = document.createElement('input');
|
||||
input.id = attachment.uuid;
|
||||
input.name = 'files';
|
||||
|
||||
@@ -188,14 +188,17 @@ export function initRepoIssueCommentDelete() {
|
||||
const path = conversationHolder.getAttribute('data-path');
|
||||
const side = conversationHolder.getAttribute('data-side');
|
||||
const idx = conversationHolder.getAttribute('data-idx');
|
||||
const lineType = conversationHolder.closest('tr').getAttribute('data-line-type');
|
||||
const lineType = conversationHolder.closest('tr')?.getAttribute('data-line-type');
|
||||
|
||||
if (lineType === 'same') {
|
||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
|
||||
} else {
|
||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
|
||||
// the conversation holder could appear either on the "Conversation" page, or the "Files Changed" page
|
||||
// on the Conversation page, there is no parent "tr", so no need to do anything for "add-code-comment"
|
||||
if (lineType) {
|
||||
if (lineType === 'same') {
|
||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
|
||||
} else {
|
||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
|
||||
}
|
||||
}
|
||||
|
||||
conversationHolder.remove();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import {checkAppUrl} from './common-global.js';
|
||||
import {checkAppUrl, checkAppUrlScheme} from './common-global.js';
|
||||
|
||||
export function initUserCheckAppUrl() {
|
||||
if (!document.querySelector('.page-content.user.signin, .page-content.user.signup, .page-content.user.link-account')) return;
|
||||
checkAppUrlScheme();
|
||||
}
|
||||
|
||||
export function initUserAuthOauth2() {
|
||||
const outer = document.getElementById('oauth2-login-navigator');
|
||||
|
||||
@@ -23,7 +23,7 @@ import {initFindFileInRepo} from './features/repo-findfile.js';
|
||||
import {initCommentContent, initMarkupContent} from './markup/content.js';
|
||||
import {initPdfViewer} from './render/pdf.js';
|
||||
|
||||
import {initUserAuthOauth2} from './features/user-auth.js';
|
||||
import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.js';
|
||||
import {
|
||||
initRepoIssueDue,
|
||||
initRepoIssueReferenceRepositorySearch,
|
||||
@@ -184,6 +184,7 @@ onDomReady(() => {
|
||||
initCommitStatuses();
|
||||
initCaptcha();
|
||||
|
||||
initUserCheckAppUrl();
|
||||
initUserAuthOauth2();
|
||||
initUserAuthWebAuthn();
|
||||
initUserAuthWebAuthnRegister();
|
||||
|
||||
@@ -56,10 +56,21 @@ export async function renderMermaid() {
|
||||
btn.setAttribute('data-clipboard-text', source);
|
||||
mermaidBlock.append(btn);
|
||||
|
||||
const updateIframeHeight = () => {
|
||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
||||
};
|
||||
|
||||
// update height when element's visibility state changes, for example when the diagram is inside
|
||||
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
|
||||
// would initially set a incorrect height and the correct height is set during this callback.
|
||||
(new IntersectionObserver(() => {
|
||||
updateIframeHeight();
|
||||
}, {root: document.documentElement})).observe(iframe);
|
||||
|
||||
iframe.addEventListener('load', () => {
|
||||
pre.replaceWith(mermaidBlock);
|
||||
mermaidBlock.classList.remove('tw-hidden');
|
||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
||||
updateIframeHeight();
|
||||
setTimeout(() => { // avoid flash of iframe background
|
||||
mermaidBlock.classList.remove('is-loading');
|
||||
iframe.classList.remove('tw-invisible');
|
||||
|
||||
@@ -45,3 +45,7 @@ export async function imageInfo(blob) {
|
||||
|
||||
return {width, dppx};
|
||||
}
|
||||
|
||||
export function isImageFile(name) {
|
||||
return /\.(jpe?g|png|gif|webp|svg|heic)$/i.test(name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user