mirror of
https://github.com/go-gitea/gitea
synced 2025-07-03 09:07:19 +00:00
Merge branch 'main' into lunny/automerge_support_delete_branch
This commit is contained in:
@ -2650,7 +2650,7 @@ LEVEL = Info
|
|||||||
;; Limit the number of pointers in each batch request to this number
|
;; Limit the number of pointers in each batch request to this number
|
||||||
;BATCH_SIZE = 20
|
;BATCH_SIZE = 20
|
||||||
;; Limit the number of concurrent upload/download operations within a batch
|
;; Limit the number of concurrent upload/download operations within a batch
|
||||||
;BATCH_OPERATION_CONCURRENCY = 3
|
;BATCH_OPERATION_CONCURRENCY = 8
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -7,19 +7,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActionScheduleSpec_Parse(t *testing.T) {
|
func TestActionScheduleSpec_Parse(t *testing.T) {
|
||||||
// Mock the local timezone is not UTC
|
// Mock the local timezone is not UTC
|
||||||
local := time.Local
|
|
||||||
tz, err := time.LoadLocation("Asia/Shanghai")
|
tz, err := time.LoadLocation("Asia/Shanghai")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer test.MockVariableValue(&time.Local, tz)()
|
||||||
time.Local = local
|
|
||||||
}()
|
|
||||||
time.Local = tz
|
|
||||||
|
|
||||||
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -171,7 +171,10 @@ func (a *Action) TableIndices() []*schemas.Index {
|
|||||||
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||||
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex}
|
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||||
|
cuIndex.AddColumn("user_id", "is_deleted")
|
||||||
|
|
||||||
|
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex}
|
||||||
|
|
||||||
return indices
|
return indices
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,9 @@ func (m *Milestone) BeforeUpdate() {
|
|||||||
// this object.
|
// this object.
|
||||||
func (m *Milestone) AfterLoad() {
|
func (m *Milestone) AfterLoad() {
|
||||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||||
if m.DeadlineUnix.Year() == 9999 {
|
if m.DeadlineUnix == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.DeadlineString = m.DeadlineUnix.FormatDate()
|
m.DeadlineString = m.DeadlineUnix.FormatDate()
|
||||||
if m.IsClosed {
|
if m.IsClosed {
|
||||||
m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix
|
m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix
|
||||||
|
@ -364,7 +364,9 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(304, "Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
newMigration(304, "Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
||||||
newMigration(305, "Add Repository Licenses", v1_23.AddRepositoryLicenses),
|
newMigration(305, "Add Repository Licenses", v1_23.AddRepositoryLicenses),
|
||||||
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
||||||
newMigration(307, "Add DeleteBranchAfterMerge to AutoMerge", v1_23.AddDeleteBranchAfterMergeForAutoMerge),
|
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
||||||
|
newMigration(308, "Add index(user_id, is_deleted) for action table", v1_23.AddNewIndexForUserDashboard),
|
||||||
|
newMigration(309, "Add DeleteBranchAfterMerge to AutoMerge", v1_23.AddDeleteBranchAfterMergeForAutoMerge),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,13 @@ import (
|
|||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pullAutoMerge struct {
|
func FixMilestoneNoDueDate(x *xorm.Engine) error {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
type Milestone struct {
|
||||||
PullID int64 `xorm:"UNIQUE"`
|
DeadlineUnix timeutil.TimeStamp
|
||||||
DoerID int64 `xorm:"INDEX NOT NULL"`
|
}
|
||||||
MergeStyle string `xorm:"varchar(30)"`
|
// Wednesday, December 1, 9999 12:00:00 AM GMT+00:00
|
||||||
Message string `xorm:"LONGTEXT"`
|
_, err := x.Table("milestone").Where("deadline_unix > 253399622400").
|
||||||
DeleteBranchAfterMerge bool
|
Cols("deadline_unix").
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
Update(&Milestone{DeadlineUnix: 0})
|
||||||
}
|
return err
|
||||||
|
|
||||||
// TableName return database table name for xorm
|
|
||||||
func (pullAutoMerge) TableName() string {
|
|
||||||
return "pull_auto_merge"
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
|
||||||
return x.Sync(new(pullAutoMerge))
|
|
||||||
}
|
}
|
||||||
|
52
models/migrations/v1_23/v308.go
Normal file
52
models/migrations/v1_23/v308.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type improveActionTableIndicesAction struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"INDEX"` // Receiver user id.
|
||||||
|
OpType int
|
||||||
|
ActUserID int64 // Action user id.
|
||||||
|
RepoID int64
|
||||||
|
CommentID int64 `xorm:"INDEX"`
|
||||||
|
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
RefName string
|
||||||
|
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
Content string `xorm:"TEXT"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName sets the name of this table
|
||||||
|
func (*improveActionTableIndicesAction) TableName() string {
|
||||||
|
return "action"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
||||||
|
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||||
|
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||||
|
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||||
|
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||||
|
cuIndex.AddColumn("user_id", "is_deleted")
|
||||||
|
|
||||||
|
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex}
|
||||||
|
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNewIndexForUserDashboard(x *xorm.Engine) error {
|
||||||
|
return x.Sync(new(improveActionTableIndicesAction))
|
||||||
|
}
|
29
models/migrations/v1_23/v309.go
Normal file
29
models/migrations/v1_23/v309.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pullAutoMerge struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
PullID int64 `xorm:"UNIQUE"`
|
||||||
|
DoerID int64 `xorm:"INDEX NOT NULL"`
|
||||||
|
MergeStyle string `xorm:"varchar(30)"`
|
||||||
|
Message string `xorm:"LONGTEXT"`
|
||||||
|
DeleteBranchAfterMerge bool
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName return database table name for xorm
|
||||||
|
func (pullAutoMerge) TableName() string {
|
||||||
|
return "pull_auto_merge"
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
||||||
|
return x.Sync(new(pullAutoMerge))
|
||||||
|
}
|
@ -60,3 +60,6 @@ func ParseAccessMode(permission string, allowed ...AccessMode) AccessMode {
|
|||||||
}
|
}
|
||||||
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
|
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrInvalidAccessMode is returned when an invalid access mode is used
|
||||||
|
var ErrInvalidAccessMode = util.NewInvalidArgumentErrorf("Invalid access mode")
|
||||||
|
@ -110,26 +110,28 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
|||||||
return nil, err
|
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 := make(container.Set[int64])
|
||||||
uniqueUserIDs.AddMultiple(userIDs...)
|
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
|
// 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.
|
// and just waste 1 unit is cheaper than re-allocate memory once.
|
||||||
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
|
||||||
if len(userIDs) > 0 {
|
if len(uniqueUserIDs) > 0 {
|
||||||
if err = e.In("id", uniqueUserIDs.Values()).
|
if err = e.In("id", uniqueUserIDs.Values()).
|
||||||
Where(builder.Eq{"`user`.is_active": true}).
|
Where(builder.Eq{"`user`.is_active": true}).
|
||||||
OrderBy(user_model.GetOrderByName()).
|
OrderBy(user_model.GetOrderByName()).
|
||||||
|
@ -32,7 +32,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
|||||||
graphCmd.AddArguments("--all")
|
graphCmd.AddArguments("--all")
|
||||||
}
|
}
|
||||||
|
|
||||||
graphCmd.AddArguments("-C", "-M", "--date=iso").
|
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
||||||
AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page).
|
AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page).
|
||||||
AddOptionFormat("--pretty=format:%s", format)
|
AddOptionFormat("--pretty=format:%s", format)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -192,6 +193,14 @@ var RelationCommit = &Commit{
|
|||||||
Row: -1,
|
Row: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseGitTime(timeStr string) time.Time {
|
||||||
|
t, err := time.Parse(time.RFC3339, timeStr)
|
||||||
|
if err != nil {
|
||||||
|
return time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// NewCommit creates a new commit from a provided line
|
// NewCommit creates a new commit from a provided line
|
||||||
func NewCommit(row, column int, line []byte) (*Commit, error) {
|
func NewCommit(row, column int, line []byte) (*Commit, error) {
|
||||||
data := bytes.SplitN(line, []byte("|"), 5)
|
data := bytes.SplitN(line, []byte("|"), 5)
|
||||||
@ -206,7 +215,7 @@ func NewCommit(row, column int, line []byte) (*Commit, error) {
|
|||||||
// 1 matches git log --pretty=format:%H => commit hash
|
// 1 matches git log --pretty=format:%H => commit hash
|
||||||
Rev: string(data[1]),
|
Rev: string(data[1]),
|
||||||
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
|
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
|
||||||
Date: string(data[2]),
|
Date: parseGitTime(string(data[2])),
|
||||||
// 3 matches git log --pretty=format:%h => abbreviated commit hash
|
// 3 matches git log --pretty=format:%h => abbreviated commit hash
|
||||||
ShortRev: string(data[3]),
|
ShortRev: string(data[3]),
|
||||||
// 4 matches git log --pretty=format:%s => subject
|
// 4 matches git log --pretty=format:%s => subject
|
||||||
@ -245,7 +254,7 @@ type Commit struct {
|
|||||||
Column int
|
Column int
|
||||||
Refs []git.Reference
|
Refs []git.Reference
|
||||||
Rev string
|
Rev string
|
||||||
Date string
|
Date time.Time
|
||||||
ShortRev string
|
ShortRev string
|
||||||
Subject string
|
Subject string
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||||
|
"github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||||
"github.com/blevesearch/bleve/v2/mapping"
|
"github.com/blevesearch/bleve/v2/mapping"
|
||||||
"github.com/blevesearch/bleve/v2/search/query"
|
"github.com/blevesearch/bleve/v2/search/query"
|
||||||
@ -69,7 +70,7 @@ const (
|
|||||||
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
||||||
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
||||||
repoIndexerDocType = "repoIndexerDocType"
|
repoIndexerDocType = "repoIndexerDocType"
|
||||||
repoIndexerLatestVersion = 7
|
repoIndexerLatestVersion = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
||||||
@ -105,7 +106,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
|||||||
} else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{
|
} else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{
|
||||||
"type": analyzer_custom.Name,
|
"type": analyzer_custom.Name,
|
||||||
"char_filters": []string{},
|
"char_filters": []string{},
|
||||||
"tokenizer": unicode.Name,
|
"tokenizer": letter.Name,
|
||||||
"token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
|
"token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
esRepoIndexerLatestVersion = 2
|
esRepoIndexerLatestVersion = 3
|
||||||
// multi-match-types, currently only 2 types are used
|
// multi-match-types, currently only 2 types are used
|
||||||
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
|
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
|
||||||
esMultiMatchTypeBestFields = "best_fields"
|
esMultiMatchTypeBestFields = "best_fields"
|
||||||
@ -60,6 +60,10 @@ const (
|
|||||||
"settings": {
|
"settings": {
|
||||||
"analysis": {
|
"analysis": {
|
||||||
"analyzer": {
|
"analyzer": {
|
||||||
|
"content_analyzer": {
|
||||||
|
"tokenizer": "content_tokenizer",
|
||||||
|
"filter" : ["lowercase"]
|
||||||
|
},
|
||||||
"filename_path_analyzer": {
|
"filename_path_analyzer": {
|
||||||
"tokenizer": "path_tokenizer"
|
"tokenizer": "path_tokenizer"
|
||||||
},
|
},
|
||||||
@ -68,6 +72,10 @@ const (
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tokenizer": {
|
"tokenizer": {
|
||||||
|
"content_tokenizer": {
|
||||||
|
"type": "simple_pattern_split",
|
||||||
|
"pattern": "[^a-zA-Z0-9]"
|
||||||
|
},
|
||||||
"path_tokenizer": {
|
"path_tokenizer": {
|
||||||
"type": "path_hierarchy",
|
"type": "path_hierarchy",
|
||||||
"delimiter": "/"
|
"delimiter": "/"
|
||||||
@ -104,7 +112,8 @@ const (
|
|||||||
"content": {
|
"content": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"term_vector": "with_positions_offsets",
|
"term_vector": "with_positions_offsets",
|
||||||
"index": true
|
"index": true,
|
||||||
|
"analyzer": "content_analyzer"
|
||||||
},
|
},
|
||||||
"commit_id": {
|
"commit_id": {
|
||||||
"type": "keyword",
|
"type": "keyword",
|
||||||
|
@ -181,6 +181,55 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Search for matches on the contents of files regardless of case.
|
||||||
|
{
|
||||||
|
RepoIDs: nil,
|
||||||
|
Keyword: "dESCRIPTION",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "README.md",
|
||||||
|
Content: "# repo1\n\nDescription for repo1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Search for an exact match on the filename within the repo '62' (case insenstive).
|
||||||
|
// This scenario yields a single result (the file avocado.md on the repo '62')
|
||||||
|
{
|
||||||
|
RepoIDs: []int64{62},
|
||||||
|
Keyword: "AVOCADO.MD",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "avocado.md",
|
||||||
|
Content: "# repo1\n\npineaple pie of cucumber juice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Search for matches on the contents of files when the criteria is a expression.
|
||||||
|
{
|
||||||
|
RepoIDs: []int64{62},
|
||||||
|
Keyword: "console.log",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "example-file.js",
|
||||||
|
Content: "console.log(\"Hello, World!\")",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Search for matches on the contents of files when the criteria is part of a expression.
|
||||||
|
{
|
||||||
|
RepoIDs: []int64{62},
|
||||||
|
Keyword: "log",
|
||||||
|
Langs: 1,
|
||||||
|
Results: []codeSearchResult{
|
||||||
|
{
|
||||||
|
Filename: "example-file.js",
|
||||||
|
Content: "console.log(\"Hello, World!\")",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, kw := range keywords {
|
for _, kw := range keywords {
|
||||||
|
@ -6,12 +6,13 @@ package bleve
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/blevesearch/bleve/v2"
|
"github.com/blevesearch/bleve/v2"
|
||||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
unicode_tokenizer "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||||
"github.com/blevesearch/bleve/v2/index/upsidedown"
|
"github.com/blevesearch/bleve/v2/index/upsidedown"
|
||||||
"github.com/ethantkoenig/rupture"
|
"github.com/ethantkoenig/rupture"
|
||||||
)
|
)
|
||||||
@ -57,7 +58,7 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
|
|||||||
// may be different on two string and they still be considered equivalent.
|
// may be different on two string and they still be considered equivalent.
|
||||||
// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
|
// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
|
||||||
func GuessFuzzinessByKeyword(s string) int {
|
func GuessFuzzinessByKeyword(s string) int {
|
||||||
tokenizer := unicode.NewUnicodeTokenizer()
|
tokenizer := unicode_tokenizer.NewUnicodeTokenizer()
|
||||||
tokens := tokenizer.Tokenize([]byte(s))
|
tokens := tokenizer.Tokenize([]byte(s))
|
||||||
|
|
||||||
if len(tokens) > 0 {
|
if len(tokens) > 0 {
|
||||||
@ -77,8 +78,10 @@ func guessFuzzinessByKeyword(s string) int {
|
|||||||
// according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2
|
// according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2
|
||||||
// magic number 4 was chosen to determine the levenshtein distance per each character of a keyword
|
// magic number 4 was chosen to determine the levenshtein distance per each character of a keyword
|
||||||
// BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot.
|
// BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot.
|
||||||
|
// Likewise, queries whose terms contains characters that are *not* letters should not use fuzziness
|
||||||
|
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
if r >= 128 {
|
if r >= 128 || !unicode.IsLetter(r) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,14 @@ func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
|
|||||||
Input: "갃갃갃",
|
Input: "갃갃갃",
|
||||||
Fuzziness: 0,
|
Fuzziness: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Input: "repo1",
|
||||||
|
Fuzziness: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "avocado.md",
|
||||||
|
Fuzziness: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
|
@ -136,6 +136,9 @@ func (c *HTTPClient) performOperation(ctx context.Context, objects []Pointer, dc
|
|||||||
return fmt.Errorf("TransferAdapter not found: %s", result.Transfer)
|
return fmt.Errorf("TransferAdapter not found: %s", result.Transfer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setting.LFSClient.BatchOperationConcurrency <= 0 {
|
||||||
|
panic("BatchOperationConcurrency must be greater than 0, forgot to init?")
|
||||||
|
}
|
||||||
errGroup, groupCtx := errgroup.WithContext(ctx)
|
errGroup, groupCtx := errgroup.WithContext(ctx)
|
||||||
errGroup.SetLimit(setting.LFSClient.BatchOperationConcurrency)
|
errGroup.SetLimit(setting.LFSClient.BatchOperationConcurrency)
|
||||||
for _, object := range result.Objects {
|
for _, object := range result.Objects {
|
||||||
|
@ -237,7 +237,7 @@ func TestHTTPClientDownload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 3)()
|
defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.endpoint, func(t *testing.T) {
|
t.Run(c.endpoint, func(t *testing.T) {
|
||||||
client := &HTTPClient{
|
client := &HTTPClient{
|
||||||
@ -337,7 +337,7 @@ func TestHTTPClientUpload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 3)()
|
defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)()
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.endpoint, func(t *testing.T) {
|
t.Run(c.endpoint, func(t *testing.T) {
|
||||||
client := &HTTPClient{
|
client := &HTTPClient{
|
||||||
|
@ -442,7 +442,10 @@ func createLink(href, content, class string) *html.Node {
|
|||||||
a := &html.Node{
|
a := &html.Node{
|
||||||
Type: html.ElementNode,
|
Type: html.ElementNode,
|
||||||
Data: atom.A.String(),
|
Data: atom.A.String(),
|
||||||
Attr: []html.Attribute{{Key: "href", Val: href}},
|
Attr: []html.Attribute{
|
||||||
|
{Key: "href", Val: href},
|
||||||
|
{Key: "data-markdown-generated-content"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if class != "" {
|
if class != "" {
|
||||||
|
@ -30,5 +30,5 @@ func TestRenderCodePreview(t *testing.T) {
|
|||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
}
|
}
|
||||||
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
|
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
|
||||||
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
|
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" data-markdown-generated-content="" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
|
||||||
}
|
}
|
||||||
|
@ -33,11 +33,9 @@ func numericIssueLink(baseURL, class string, index int, marker string) string {
|
|||||||
|
|
||||||
// link an HTML link
|
// link an HTML link
|
||||||
func link(href, class, contents string) string {
|
func link(href, class, contents string) string {
|
||||||
if class != "" {
|
extra := ` data-markdown-generated-content=""`
|
||||||
class = " class=\"" + class + "\""
|
extra += util.Iif(class != "", ` class="`+class+`"`, "")
|
||||||
}
|
return fmt.Sprintf(`<a href="%s"%s>%s</a>`, href, extra, contents)
|
||||||
|
|
||||||
return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var numericMetas = map[string]string{
|
var numericMetas = map[string]string{
|
||||||
@ -353,7 +351,9 @@ func TestRender_FullIssueURLs(t *testing.T) {
|
|||||||
Metas: localMetas,
|
Metas: localMetas,
|
||||||
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expected, result.String())
|
actual := result.String()
|
||||||
|
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6",
|
test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6",
|
||||||
"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6")
|
"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6")
|
||||||
|
@ -116,7 +116,9 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||||||
Metas: localMetas,
|
Metas: localMetas,
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
actual := strings.TrimSpace(buffer)
|
||||||
|
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
test(
|
test(
|
||||||
@ -156,7 +158,9 @@ func TestRender_links(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
actual := strings.TrimSpace(buffer)
|
||||||
|
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldCustomURLSchemes := setting.Markdown.CustomURLSchemes
|
oldCustomURLSchemes := setting.Markdown.CustomURLSchemes
|
||||||
@ -267,7 +271,9 @@ func TestRender_email(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, input)
|
}, input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
actual := strings.TrimSpace(res)
|
||||||
|
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), actual)
|
||||||
}
|
}
|
||||||
// Text that should be turned into email link
|
// Text that should be turned into email link
|
||||||
|
|
||||||
@ -616,7 +622,9 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
|||||||
Metas: localMetas,
|
Metas: localMetas,
|
||||||
}, strings.NewReader(input), &res)
|
}, strings.NewReader(input), &res)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
actual := strings.TrimSpace(res.String())
|
||||||
|
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue index shouldn't be post processing in a document.
|
// Issue index shouldn't be post processing in a document.
|
||||||
|
@ -311,7 +311,8 @@ func TestTotal_RenderWiki(t *testing.T) {
|
|||||||
IsWiki: true,
|
IsWiki: true,
|
||||||
}, sameCases[i])
|
}, sameCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, template.HTML(answers[i]), line)
|
actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, answers[i], actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []string{
|
testCases := []string{
|
||||||
@ -336,7 +337,8 @@ func TestTotal_RenderWiki(t *testing.T) {
|
|||||||
IsWiki: true,
|
IsWiki: true,
|
||||||
}, testCases[i])
|
}, testCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, template.HTML(testCases[i+1]), line)
|
actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.EqualValues(t, testCases[i+1], actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +358,8 @@ func TestTotal_RenderString(t *testing.T) {
|
|||||||
Metas: localMetas,
|
Metas: localMetas,
|
||||||
}, sameCases[i])
|
}, sameCases[i])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, template.HTML(answers[i]), line)
|
actual := strings.ReplaceAll(string(line), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, answers[i], actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []string{}
|
testCases := []string{}
|
||||||
@ -996,7 +999,8 @@ space</p>
|
|||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
|
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
|
||||||
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
|
assert.NoError(t, err, "Unexpected error in testcase: %v", i)
|
||||||
assert.Equal(t, c.Expected, string(result), "Unexpected result in testcase %v", i)
|
actual := strings.ReplaceAll(string(result), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, c.Expected, actual, "Unexpected result in testcase %v", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|||||||
"start", "summary", "tabindex", "target",
|
"start", "summary", "tabindex", "target",
|
||||||
"title", "type", "usemap", "valign", "value",
|
"title", "type", "usemap", "valign", "value",
|
||||||
"vspace", "width", "itemprop",
|
"vspace", "width", "itemprop",
|
||||||
|
"data-markdown-generated-content",
|
||||||
}
|
}
|
||||||
|
|
||||||
generalSafeElements := []string{
|
generalSafeElements := []string{
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error {
|
|
||||||
if err := repo.LoadOwner(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user_model.IsUserBlockedBy(ctx, u, repo.OwnerID) || user_model.IsUserBlockedBy(ctx, repo.Owner, u.ID) {
|
|
||||||
return user_model.ErrBlockedUser
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
||||||
has, err := db.Exist[repo_model.Collaboration](ctx, builder.Eq{
|
|
||||||
"repo_id": repo.ID,
|
|
||||||
"user_id": u.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if has {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.Insert(ctx, &repo_model.Collaboration{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
UserID: u.ID,
|
|
||||||
Mode: perm.AccessModeWrite,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return access_model.RecalculateUserAccess(ctx, repo, u.ID)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
perm_model "code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRepository_AddCollaborator(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
testSuccess := func(repoID, userID int64) {
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
|
||||||
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
|
|
||||||
assert.NoError(t, AddCollaborator(db.DefaultContext, repo, user))
|
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}, &user_model.User{ID: userID})
|
|
||||||
}
|
|
||||||
testSuccess(1, 4)
|
|
||||||
testSuccess(1, 4)
|
|
||||||
testSuccess(3, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
// public non-organization repo
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
|
||||||
assert.NoError(t, repo.LoadUnits(db.DefaultContext))
|
|
||||||
|
|
||||||
// plain user
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// change to collaborator
|
|
||||||
assert.NoError(t, AddCollaborator(db.DefaultContext, repo, user))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// collaborator
|
|
||||||
collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, collaborator)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// owner
|
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// admin
|
|
||||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
// private non-organization repo
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
|
||||||
assert.NoError(t, repo.LoadUnits(db.DefaultContext))
|
|
||||||
|
|
||||||
// plain user
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
||||||
perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.False(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// change to collaborator to default write access
|
|
||||||
assert.NoError(t, AddCollaborator(db.DefaultContext, repo, user))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, user.ID, perm_model.AccessModeRead))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// owner
|
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// admin
|
|
||||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepoPermissionPublicOrgRepo(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
// public organization repo
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32})
|
|
||||||
assert.NoError(t, repo.LoadUnits(db.DefaultContext))
|
|
||||||
|
|
||||||
// plain user
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
|
||||||
perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// change to collaborator to default write access
|
|
||||||
assert.NoError(t, AddCollaborator(db.DefaultContext, repo, user))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, user.ID, perm_model.AccessModeRead))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// org member team owner
|
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// org member team tester
|
|
||||||
member := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, member)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
}
|
|
||||||
assert.True(t, perm.CanWrite(unit.TypeIssues))
|
|
||||||
assert.False(t, perm.CanWrite(unit.TypeCode))
|
|
||||||
|
|
||||||
// admin
|
|
||||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
// private organization repo
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
|
|
||||||
assert.NoError(t, repo.LoadUnits(db.DefaultContext))
|
|
||||||
|
|
||||||
// plain user
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
|
||||||
perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.False(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// change to collaborator to default write access
|
|
||||||
assert.NoError(t, AddCollaborator(db.DefaultContext, repo, user))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, user.ID, perm_model.AccessModeRead))
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.False(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// org member team owner
|
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// update team information and then check permission
|
|
||||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5})
|
|
||||||
err = organization.UpdateTeamUnits(db.DefaultContext, team, nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// org member team tester
|
|
||||||
tester := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, tester)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, perm.CanWrite(unit.TypeIssues))
|
|
||||||
assert.False(t, perm.CanWrite(unit.TypeCode))
|
|
||||||
assert.False(t, perm.CanRead(unit.TypeCode))
|
|
||||||
|
|
||||||
// org member team reviewer
|
|
||||||
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, reviewer)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, perm.CanRead(unit.TypeIssues))
|
|
||||||
assert.False(t, perm.CanWrite(unit.TypeCode))
|
|
||||||
assert.True(t, perm.CanRead(unit.TypeCode))
|
|
||||||
|
|
||||||
// admin
|
|
||||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
||||||
perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
for _, unit := range repo.Units {
|
|
||||||
assert.True(t, perm.CanRead(unit.Type))
|
|
||||||
assert.True(t, perm.CanWrite(unit.Type))
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,8 +42,8 @@ func NewPushCommits() *PushCommits {
|
|||||||
return &PushCommits{}
|
return &PushCommits{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// toAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
|
// ToAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
|
||||||
func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
|
func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
|
||||||
var err error
|
var err error
|
||||||
authorUsername := ""
|
authorUsername := ""
|
||||||
author, ok := emailUsers[commit.AuthorEmail]
|
author, ok := emailUsers[commit.AuthorEmail]
|
||||||
@ -105,7 +105,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
|
|||||||
emailUsers := make(map[string]*user_model.User)
|
emailUsers := make(map[string]*user_model.User)
|
||||||
|
|
||||||
for i, commit := range pc.Commits {
|
for i, commit := range pc.Commits {
|
||||||
apiCommit, err := pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
|
apiCommit, err := ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
|
|||||||
}
|
}
|
||||||
if pc.HeadCommit != nil && headCommit == nil {
|
if pc.HeadCommit != nil && headCommit == nil {
|
||||||
var err error
|
var err error
|
||||||
headCommit, err = pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
|
headCommit, err = ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -11,160 +11,17 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/models/webhook"
|
|
||||||
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateRepositoryByExample creates a repository for the user/organization.
|
|
||||||
func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) {
|
|
||||||
if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
|
||||||
} else if has {
|
|
||||||
return repo_model.ErrRepoAlreadyExist{
|
|
||||||
Uname: u.Name,
|
|
||||||
Name: repo.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repoPath := repo_model.RepoPath(u.Name, repo.Name)
|
|
||||||
isExist, err := util.IsExist(repoPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !overwriteOrAdopt && isExist {
|
|
||||||
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
|
|
||||||
return repo_model.ErrRepoFilesAlreadyExist{
|
|
||||||
Uname: u.Name,
|
|
||||||
Name: repo.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.Insert(ctx, repo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert units for repo
|
|
||||||
defaultUnits := unit.DefaultRepoUnits
|
|
||||||
if isFork {
|
|
||||||
defaultUnits = unit.DefaultForkRepoUnits
|
|
||||||
}
|
|
||||||
units := make([]repo_model.RepoUnit, 0, len(defaultUnits))
|
|
||||||
for _, tp := range defaultUnits {
|
|
||||||
if tp == unit.TypeIssues {
|
|
||||||
units = append(units, repo_model.RepoUnit{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: tp,
|
|
||||||
Config: &repo_model.IssuesConfig{
|
|
||||||
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
|
|
||||||
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
|
|
||||||
EnableDependencies: setting.Service.DefaultEnableDependencies,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if tp == unit.TypePullRequests {
|
|
||||||
units = append(units, repo_model.RepoUnit{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: tp,
|
|
||||||
Config: &repo_model.PullRequestsConfig{
|
|
||||||
AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
|
|
||||||
DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
|
|
||||||
AllowRebaseUpdate: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if tp == unit.TypeProjects {
|
|
||||||
units = append(units, repo_model.RepoUnit{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: tp,
|
|
||||||
Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
units = append(units, repo_model.RepoUnit{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: tp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = db.Insert(ctx, units); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remember visibility preference.
|
|
||||||
u.LastRepoVisibility = repo.IsPrivate
|
|
||||||
if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
|
|
||||||
return fmt.Errorf("UpdateUserCols: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = user_model.IncrUserRepoNum(ctx, u.ID); err != nil {
|
|
||||||
return fmt.Errorf("IncrUserRepoNum: %w", err)
|
|
||||||
}
|
|
||||||
u.NumRepos++
|
|
||||||
|
|
||||||
// Give access to all members in teams with access to all repositories.
|
|
||||||
if u.IsOrganization() {
|
|
||||||
teams, err := organization.FindOrgTeams(ctx, u.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("FindOrgTeams: %w", err)
|
|
||||||
}
|
|
||||||
for _, t := range teams {
|
|
||||||
if t.IncludesAllRepositories {
|
|
||||||
if err := models.AddRepository(ctx, t, repo); err != nil {
|
|
||||||
return fmt.Errorf("AddRepository: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
|
|
||||||
return fmt.Errorf("IsUserRepoAdmin: %w", err)
|
|
||||||
} else if !isAdmin {
|
|
||||||
// Make creator repo admin if it wasn't assigned automatically
|
|
||||||
if err = AddCollaborator(ctx, repo, doer); err != nil {
|
|
||||||
return fmt.Errorf("AddCollaborator: %w", err)
|
|
||||||
}
|
|
||||||
if err = repo_model.ChangeCollaborationAccessMode(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
|
|
||||||
return fmt.Errorf("ChangeCollaborationAccessModeCtx: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
|
||||||
// Organization automatically called this in AddRepository method.
|
|
||||||
return fmt.Errorf("RecalculateAccesses: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.Service.AutoWatchNewRepos {
|
|
||||||
if err = repo_model.WatchRepo(ctx, doer, repo, true); err != nil {
|
|
||||||
return fmt.Errorf("WatchRepo: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
|
|
||||||
return fmt.Errorf("CopyDefaultWebhooksToRepo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
|
||||||
|
|
||||||
// getDirectorySize returns the disk consumption for a given path
|
// getDirectorySize returns the disk consumption for a given path
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models"
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if LFSClient.BatchOperationConcurrency < 1 {
|
if LFSClient.BatchOperationConcurrency < 1 {
|
||||||
// match the default git-lfs's `lfs.concurrenttransfers`
|
// match the default git-lfs's `lfs.concurrenttransfers` https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.adoc#upload-and-download-transfer-settings
|
||||||
LFSClient.BatchOperationConcurrency = 3
|
LFSClient.BatchOperationConcurrency = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
|
LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(24 * time.Hour)
|
||||||
|
@ -114,7 +114,7 @@ BATCH_SIZE = 0
|
|||||||
assert.NoError(t, loadLFSFrom(cfg))
|
assert.NoError(t, loadLFSFrom(cfg))
|
||||||
assert.EqualValues(t, 100, LFS.MaxBatchSize)
|
assert.EqualValues(t, 100, LFS.MaxBatchSize)
|
||||||
assert.EqualValues(t, 20, LFSClient.BatchSize)
|
assert.EqualValues(t, 20, LFSClient.BatchSize)
|
||||||
assert.EqualValues(t, 3, LFSClient.BatchOperationConcurrency)
|
assert.EqualValues(t, 8, LFSClient.BatchOperationConcurrency)
|
||||||
|
|
||||||
iniStr = `
|
iniStr = `
|
||||||
[lfs_client]
|
[lfs_client]
|
||||||
|
@ -262,13 +262,6 @@ func (p *ReleasePayload) JSONPayload() ([]byte, error) {
|
|||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// __________ .__
|
|
||||||
// \______ \__ __ _____| |__
|
|
||||||
// | ___/ | \/ ___/ | \
|
|
||||||
// | | | | /\___ \| Y \
|
|
||||||
// |____| |____//____ >___| /
|
|
||||||
// \/ \/
|
|
||||||
|
|
||||||
// PushPayload represents a payload information of push event.
|
// PushPayload represents a payload information of push event.
|
||||||
type PushPayload struct {
|
type PushPayload struct {
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
@ -509,3 +502,26 @@ type WorkflowDispatchPayload struct {
|
|||||||
func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) {
|
func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) {
|
||||||
return json.MarshalIndent(p, "", " ")
|
return json.MarshalIndent(p, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitStatusPayload represents a payload information of commit status event.
|
||||||
|
type CommitStatusPayload struct {
|
||||||
|
// TODO: add Branches per https://docs.github.com/en/webhooks/webhook-events-and-payloads#status
|
||||||
|
Commit *PayloadCommit `json:"commit"`
|
||||||
|
Context string `json:"context"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Repo *Repository `json:"repository"`
|
||||||
|
Sender *User `json:"sender"`
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
State string `json:"state"`
|
||||||
|
TargetURL string `json:"target_url"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONPayload implements Payload
|
||||||
|
func (p *CommitStatusPayload) JSONPayload() ([]byte, error) {
|
||||||
|
return json.MarshalIndent(p, "", " ")
|
||||||
|
}
|
||||||
|
@ -73,11 +73,6 @@ func NewFuncMap() template.FuncMap {
|
|||||||
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
||||||
},
|
},
|
||||||
|
|
||||||
// for backward compatibility only, do not use them anymore
|
|
||||||
"TimeSince": timeSinceLegacy,
|
|
||||||
"TimeSinceUnix": timeSinceLegacy,
|
|
||||||
"DateTime": dateTimeLegacy,
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// setting
|
// setting
|
||||||
"AppName": func() string {
|
"AppName": func() string {
|
||||||
@ -156,18 +151,8 @@ func NewFuncMap() template.FuncMap {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// render
|
// render
|
||||||
"RenderCommitMessage": RenderCommitMessage,
|
"RenderCodeBlock": renderCodeBlock,
|
||||||
"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubject,
|
"ReactionToEmoji": reactionToEmoji,
|
||||||
|
|
||||||
"RenderCommitBody": renderCommitBody,
|
|
||||||
"RenderCodeBlock": renderCodeBlock,
|
|
||||||
"RenderIssueTitle": renderIssueTitle,
|
|
||||||
"RenderEmoji": renderEmoji,
|
|
||||||
"ReactionToEmoji": reactionToEmoji,
|
|
||||||
|
|
||||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
|
||||||
"RenderLabel": renderLabel,
|
|
||||||
"RenderLabels": RenderLabels,
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// misc
|
// misc
|
||||||
@ -179,6 +164,22 @@ func NewFuncMap() template.FuncMap {
|
|||||||
|
|
||||||
"FilenameIsImage": filenameIsImage,
|
"FilenameIsImage": filenameIsImage,
|
||||||
"TabSizeClass": tabSizeClass,
|
"TabSizeClass": tabSizeClass,
|
||||||
|
|
||||||
|
// for backward compatibility only, do not use them anymore
|
||||||
|
"TimeSince": timeSinceLegacy,
|
||||||
|
"TimeSinceUnix": timeSinceLegacy,
|
||||||
|
"DateTime": dateTimeLegacy,
|
||||||
|
|
||||||
|
"RenderEmoji": renderEmojiLegacy,
|
||||||
|
"RenderLabel": renderLabelLegacy,
|
||||||
|
"RenderLabels": renderLabelsLegacy,
|
||||||
|
"RenderIssueTitle": renderIssueTitleLegacy,
|
||||||
|
|
||||||
|
"RenderMarkdownToHtml": renderMarkdownToHtmlLegacy,
|
||||||
|
|
||||||
|
"RenderCommitMessage": renderCommitMessageLegacy,
|
||||||
|
"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubjectLegacy,
|
||||||
|
"RenderCommitBody": renderCommitBodyLegacy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,3 +297,9 @@ func userThemeName(user *user_model.User) string {
|
|||||||
}
|
}
|
||||||
return setting.UI.DefaultTheme
|
return setting.UI.DefaultTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func panicIfDevOrTesting() {
|
||||||
|
if !setting.IsProd || setting.IsInTesting {
|
||||||
|
panic("legacy template functions are for backward compatibility only, do not use them in new code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DateUtils struct{}
|
type DateUtils struct{}
|
||||||
@ -28,7 +27,7 @@ func (du *DateUtils) AbsoluteShort(time any) template.HTML {
|
|||||||
|
|
||||||
// AbsoluteLong renders in "January 01, 2006" format
|
// AbsoluteLong renders in "January 01, 2006" format
|
||||||
func (du *DateUtils) AbsoluteLong(time any) template.HTML {
|
func (du *DateUtils) AbsoluteLong(time any) template.HTML {
|
||||||
return dateTimeFormat("short", time)
|
return dateTimeFormat("long", time)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FullTime renders in "Jan 01, 2006 20:33:44" format
|
// FullTime renders in "Jan 01, 2006 20:33:44" format
|
||||||
@ -54,23 +53,6 @@ func parseLegacy(datetime string) time.Time {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
|
|
||||||
if !setting.IsProd || setting.IsInTesting {
|
|
||||||
panic("dateTimeLegacy is for backward compatibility only, do not use it in new code")
|
|
||||||
}
|
|
||||||
if s, ok := datetime.(string); ok {
|
|
||||||
datetime = parseLegacy(s)
|
|
||||||
}
|
|
||||||
return dateTimeFormat(format, datetime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
|
|
||||||
if !setting.IsProd || setting.IsInTesting {
|
|
||||||
panic("timeSinceLegacy is for backward compatibility only, do not use it in new code")
|
|
||||||
}
|
|
||||||
return TimeSince(time)
|
|
||||||
}
|
|
||||||
|
|
||||||
func anyToTime(any any) (t time.Time, isZero bool) {
|
func anyToTime(any any) (t time.Time, isZero bool) {
|
||||||
switch v := any.(type) {
|
switch v := any.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
|
23
modules/templates/util_date_legacy.go
Normal file
23
modules/templates/util_date_legacy.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
if s, ok := datetime.(string); ok {
|
||||||
|
datetime = parseLegacy(s)
|
||||||
|
}
|
||||||
|
return dateTimeFormat(format, datetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return TimeSince(time)
|
||||||
|
}
|
@ -24,13 +24,21 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RenderUtils struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderUtils(ctx context.Context) *RenderUtils {
|
||||||
|
return &RenderUtils{ctx: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||||
func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string) template.HTML {
|
||||||
cleanMsg := template.HTMLEscapeString(msg)
|
cleanMsg := template.HTMLEscapeString(msg)
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, cleanMsg)
|
}, cleanMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -44,9 +52,9 @@ func RenderCommitMessage(ctx context.Context, msg string, metas map[string]strin
|
|||||||
return renderCodeBlock(template.HTML(msgLines[0]))
|
return renderCodeBlock(template.HTML(msgLines[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderCommitMessageLinkSubject renders commit message as a XSS-safe link to
|
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
|
||||||
// the provided default url, handling for special links without email to links.
|
// the provided default url, handling for special links without email to links.
|
||||||
func renderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, metas map[string]string) template.HTML {
|
||||||
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
|
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
|
||||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||||
if lineEnd > 0 {
|
if lineEnd > 0 {
|
||||||
@ -60,7 +68,7 @@ func renderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
|
|||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ut.ctx,
|
||||||
DefaultLink: urlDefault,
|
DefaultLink: urlDefault,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(msgLine))
|
}, template.HTMLEscapeString(msgLine))
|
||||||
@ -71,8 +79,8 @@ func renderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string,
|
|||||||
return renderCodeBlock(template.HTML(renderedMessage))
|
return renderCodeBlock(template.HTML(renderedMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderCommitBody extracts the body of a commit message without its title.
|
// RenderCommitBody extracts the body of a commit message without its title.
|
||||||
func renderCommitBody(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) template.HTML {
|
||||||
msgLine := strings.TrimSpace(msg)
|
msgLine := strings.TrimSpace(msg)
|
||||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||||
if lineEnd > 0 {
|
if lineEnd > 0 {
|
||||||
@ -86,7 +94,7 @@ func renderCommitBody(ctx context.Context, msg string, metas map[string]string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(msgLine))
|
}, template.HTMLEscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,22 +113,22 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
|||||||
return template.HTML(htmlWithCodeTags)
|
return template.HTML(htmlWithCodeTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderIssueTitle renders issue/pull title with defined post processors
|
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||||
func renderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
||||||
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: metas,
|
Metas: metas,
|
||||||
}, template.HTMLEscapeString(text))
|
}, template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderIssueTitle: %v", err)
|
log.Error("RenderIssueTitle: %v", err)
|
||||||
return template.HTML("")
|
return ""
|
||||||
}
|
}
|
||||||
return template.HTML(renderedText)
|
return template.HTML(renderedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderLabel renders a label
|
// RenderLabel renders a label
|
||||||
// locale is needed due to an import cycle with our context providing the `Tr` function
|
func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
|
||||||
func renderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
|
||||||
var extraCSSClasses string
|
var extraCSSClasses string
|
||||||
textColor := util.ContrastColor(label.Color)
|
textColor := util.ContrastColor(label.Color)
|
||||||
labelScope := label.ExclusiveScope()
|
labelScope := label.ExclusiveScope()
|
||||||
@ -134,12 +142,12 @@ func renderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
|||||||
if labelScope == "" {
|
if labelScope == "" {
|
||||||
// Regular label
|
// Regular label
|
||||||
return HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`,
|
return HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`,
|
||||||
extraCSSClasses, textColor, label.Color, descriptionText, renderEmoji(ctx, label.Name))
|
extraCSSClasses, textColor, label.Color, descriptionText, ut.RenderEmoji(label.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scoped label
|
// Scoped label
|
||||||
scopeHTML := renderEmoji(ctx, labelScope)
|
scopeHTML := ut.RenderEmoji(labelScope)
|
||||||
itemHTML := renderEmoji(ctx, label.Name[len(labelScope)+1:])
|
itemHTML := ut.RenderEmoji(label.Name[len(labelScope)+1:])
|
||||||
|
|
||||||
// Make scope and item background colors slightly darker and lighter respectively.
|
// Make scope and item background colors slightly darker and lighter respectively.
|
||||||
// More contrast needed with higher luminance, empirically tweaked.
|
// More contrast needed with higher luminance, empirically tweaked.
|
||||||
@ -176,13 +184,12 @@ func renderLabel(ctx context.Context, locale translation.Locale, label *issues_m
|
|||||||
textColor, itemColor, itemHTML)
|
textColor, itemColor, itemHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderEmoji renders html text with emoji post processors
|
// RenderEmoji renders html text with emoji post processors
|
||||||
func renderEmoji(ctx context.Context, text string) template.HTML {
|
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
||||||
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
|
renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ut.ctx}, template.HTMLEscapeString(text))
|
||||||
template.HTMLEscapeString(text))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderEmoji: %v", err)
|
log.Error("RenderEmoji: %v", err)
|
||||||
return template.HTML("")
|
return ""
|
||||||
}
|
}
|
||||||
return template.HTML(renderedText)
|
return template.HTML(renderedText)
|
||||||
}
|
}
|
||||||
@ -200,9 +207,9 @@ func reactionToEmoji(reaction string) template.HTML {
|
|||||||
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
|
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive
|
func (ut *RenderUtils) MarkdownToHtml(input string) template.HTML { //nolint:revive
|
||||||
output, err := markdown.RenderString(&markup.RenderContext{
|
output, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ut.ctx,
|
||||||
Metas: map[string]string{"mode": "document"},
|
Metas: map[string]string{"mode": "document"},
|
||||||
}, input)
|
}, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,7 +218,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
func (ut *RenderUtils) RenderLabels(labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
||||||
isPullRequest := issue != nil && issue.IsPull
|
isPullRequest := issue != nil && issue.IsPull
|
||||||
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
|
baseLink := fmt.Sprintf("%s/%s", repoLink, util.Iif(isPullRequest, "pulls", "issues"))
|
||||||
htmlCode := `<span class="labels-list">`
|
htmlCode := `<span class="labels-list">`
|
||||||
@ -220,7 +227,7 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
|
|||||||
if label == nil {
|
if label == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, renderLabel(ctx, locale, label))
|
htmlCode += fmt.Sprintf(`<a href="%s?labels=%d">%s</a>`, baseLink, label.ID, ut.RenderLabel(label))
|
||||||
}
|
}
|
||||||
htmlCode += "</span>"
|
htmlCode += "</span>"
|
||||||
return template.HTML(htmlCode)
|
return template.HTML(htmlCode)
|
||||||
|
52
modules/templates/util_render_legacy.go
Normal file
52
modules/templates/util_render_legacy.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func renderEmojiLegacy(ctx context.Context, text string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderEmoji(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderLabelLegacy(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderLabel(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderLabelsLegacy(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderLabels(labels, repoLink, issue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML { //nolint:revive
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).MarkdownToHtml(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderCommitMessage(msg, metas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderIssueTitle(text, metas)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||||
|
panicIfDevOrTesting()
|
||||||
|
return NewRenderUtils(ctx).RenderCommitBody(msg, metas)
|
||||||
|
}
|
@ -65,9 +65,14 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newTestRenderUtils() *RenderUtils {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, translation.ContextKey, &translation.MockLocale{})
|
||||||
|
return NewRenderUtils(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderCommitBody(t *testing.T) {
|
func TestRenderCommitBody(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
|
||||||
msg string
|
msg string
|
||||||
metas map[string]string
|
metas map[string]string
|
||||||
}
|
}
|
||||||
@ -79,7 +84,6 @@ func TestRenderCommitBody(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "multiple lines",
|
name: "multiple lines",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
|
||||||
msg: "first line\nsecond line",
|
msg: "first line\nsecond line",
|
||||||
},
|
},
|
||||||
want: "second line",
|
want: "second line",
|
||||||
@ -87,7 +91,6 @@ func TestRenderCommitBody(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "multiple lines with leading newlines",
|
name: "multiple lines with leading newlines",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
|
||||||
msg: "\n\n\n\nfirst line\nsecond line",
|
msg: "\n\n\n\nfirst line\nsecond line",
|
||||||
},
|
},
|
||||||
want: "second line",
|
want: "second line",
|
||||||
@ -95,15 +98,15 @@ func TestRenderCommitBody(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "multiple lines with trailing newlines",
|
name: "multiple lines with trailing newlines",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
|
||||||
msg: "first line\nsecond line\n\n\n",
|
msg: "first line\nsecond line\n\n\n",
|
||||||
},
|
},
|
||||||
want: "second line",
|
want: "second line",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
ut := newTestRenderUtils()
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.Equalf(t, tt.want, renderCommitBody(tt.args.ctx, tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.metas)
|
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v)", tt.args.msg, tt.args.metas)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,20 +129,20 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||||||
<a href="/mention-user" class="mention">@mention-user</a> test
|
<a href="/mention-user" class="mention">@mention-user</a> test
|
||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
space`
|
space`
|
||||||
|
actual := strings.ReplaceAll(string(newTestRenderUtils().RenderCommitBody(testInput(), testMetas)), ` data-markdown-generated-content=""`, "")
|
||||||
assert.EqualValues(t, expected, renderCommitBody(context.Background(), testInput(), testMetas))
|
assert.EqualValues(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderCommitMessage(t *testing.T) {
|
func TestRenderCommitMessage(t *testing.T) {
|
||||||
expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
|
expected := `space <a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a> `
|
||||||
|
|
||||||
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput(), testMetas))
|
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessage(testInput(), testMetas))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
||||||
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`
|
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" data-markdown-generated-content="" class="mention">@mention-user</a>`
|
||||||
|
|
||||||
assert.EqualValues(t, expected, renderCommitMessageLinkSubject(context.Background(), testInput(), "https://example.com/link", testMetas))
|
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderIssueTitle(t *testing.T) {
|
func TestRenderIssueTitle(t *testing.T) {
|
||||||
@ -165,7 +168,8 @@ mail@domain.com
|
|||||||
space<SPACE><SPACE>
|
space<SPACE><SPACE>
|
||||||
`
|
`
|
||||||
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
||||||
assert.EqualValues(t, expected, renderIssueTitle(context.Background(), testInput(), testMetas))
|
actual := strings.ReplaceAll(string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.EqualValues(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
@ -190,25 +194,24 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||||||
#123
|
#123
|
||||||
space</p>
|
space</p>
|
||||||
`
|
`
|
||||||
assert.Equal(t, expected, string(RenderMarkdownToHtml(context.Background(), testInput())))
|
actual := strings.ReplaceAll(string(newTestRenderUtils().MarkdownToHtml(testInput())), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderLabels(t *testing.T) {
|
func TestRenderLabels(t *testing.T) {
|
||||||
ctx := context.Background()
|
ut := newTestRenderUtils()
|
||||||
locale := &translation.MockLocale{}
|
|
||||||
|
|
||||||
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
||||||
issue := &issues.Issue{}
|
issue := &issues.Issue{}
|
||||||
expected := `/owner/repo/issues?labels=123`
|
expected := `/owner/repo/issues?labels=123`
|
||||||
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
|
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
|
||||||
|
|
||||||
label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
||||||
issue = &issues.Issue{IsPull: true}
|
issue = &issues.Issue{IsPull: true}
|
||||||
expected = `/owner/repo/pulls?labels=123`
|
expected = `/owner/repo/pulls?labels=123`
|
||||||
assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
|
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserMention(t *testing.T) {
|
func TestUserMention(t *testing.T) {
|
||||||
rendered := RenderMarkdownToHtml(context.Background(), "@no-such-user @mention-user @mention-user")
|
rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
||||||
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" data-markdown-generated-content="" rel="nofollow">@mention-user</a> <a href="/mention-user" data-markdown-generated-content="" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ const (
|
|||||||
HookEventRelease HookEventType = "release"
|
HookEventRelease HookEventType = "release"
|
||||||
HookEventPackage HookEventType = "package"
|
HookEventPackage HookEventType = "package"
|
||||||
HookEventSchedule HookEventType = "schedule"
|
HookEventSchedule HookEventType = "schedule"
|
||||||
|
HookEventStatus HookEventType = "status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event returns the HookEventType as an event string
|
// Event returns the HookEventType as an event string
|
||||||
|
@ -1172,7 +1172,7 @@ func Routes() *web.Router {
|
|||||||
m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
|
m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
|
||||||
m.Group("/{collaborator}", func() {
|
m.Group("/{collaborator}", func() {
|
||||||
m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
|
m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
|
||||||
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
|
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddOrUpdateCollaborator).
|
||||||
Delete(reqAdmin(), repo.DeleteCollaborator)
|
Delete(reqAdmin(), repo.DeleteCollaborator)
|
||||||
m.Get("/permission", repo.GetRepoPermissions)
|
m.Get("/permission", repo.GetRepoPermissions)
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,8 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe
|
|||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
Markup(ctx)
|
Markup(ctx)
|
||||||
assert.Equal(t, expectedBody, resp.Body.String())
|
actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, expectedBody, actual)
|
||||||
assert.Equal(t, expectedCode, resp.Code)
|
assert.Equal(t, expectedCode, resp.Code)
|
||||||
resp.Body.Reset()
|
resp.Body.Reset()
|
||||||
}
|
}
|
||||||
@ -58,7 +59,8 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody
|
|||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
Markdown(ctx)
|
Markdown(ctx)
|
||||||
assert.Equal(t, responseBody, resp.Body.String())
|
actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
|
||||||
|
assert.Equal(t, responseBody, actual)
|
||||||
assert.Equal(t, responseCode, resp.Code)
|
assert.Equal(t, responseCode, resp.Code)
|
||||||
resp.Body.Reset()
|
resp.Body.Reset()
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
@ -123,11 +122,11 @@ func IsCollaborator(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCollaborator add a collaborator to a repository
|
// AddOrUpdateCollaborator add or update a collaborator to a repository
|
||||||
func AddCollaborator(ctx *context.APIContext) {
|
func AddOrUpdateCollaborator(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator
|
// swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator
|
||||||
// ---
|
// ---
|
||||||
// summary: Add a collaborator to a repository
|
// summary: Add or Update a collaborator to a repository
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
// parameters:
|
// parameters:
|
||||||
@ -177,20 +176,18 @@ func AddCollaborator(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_module.AddCollaborator(ctx, ctx.Repo.Repository, collaborator); err != nil {
|
p := perm.AccessModeWrite
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if form.Permission != nil {
|
||||||
ctx.Error(http.StatusForbidden, "AddCollaborator", err)
|
p = perm.ParseAccessMode(*form.Permission)
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "AddCollaborator", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Permission != nil {
|
if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil {
|
||||||
if err := repo_model.ChangeCollaborationAccessMode(ctx, ctx.Repo.Repository, collaborator.ID, perm.ParseAccessMode(*form.Permission)); err != nil {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.Error(http.StatusInternalServerError, "ChangeCollaborationAccessMode", err)
|
ctx.Error(http.StatusForbidden, "AddOrUpdateCollaborator", err)
|
||||||
return
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "AddOrUpdateCollaborator", err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
|
@ -56,12 +56,12 @@ func GetRawFile(ctx *context.APIContext) {
|
|||||||
// required: true
|
// required: true
|
||||||
// - name: filepath
|
// - name: filepath
|
||||||
// in: path
|
// in: path
|
||||||
// description: filepath of the file to get
|
// description: path of the file to get, it should be "{ref}/{filepath}". If there is no ref could be inferred, it will be treated as the default branch
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// - name: ref
|
// - name: ref
|
||||||
// in: query
|
// in: query
|
||||||
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
|
// description: "The name of the commit/branch/tag. Default the repository’s default branch"
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
@ -109,12 +109,12 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
|||||||
// required: true
|
// required: true
|
||||||
// - name: filepath
|
// - name: filepath
|
||||||
// in: path
|
// in: path
|
||||||
// description: filepath of the file to get
|
// description: path of the file to get, it should be "{ref}/{filepath}". If there is no ref could be inferred, it will be treated as the default branch
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// - name: ref
|
// - name: ref
|
||||||
// in: query
|
// in: query
|
||||||
// description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)"
|
// description: "The name of the commit/branch/tag. Default the repository’s default branch"
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
// responses:
|
// responses:
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
@ -1046,18 +1047,11 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadlineUnix timeutil.TimeStamp
|
deadlineUnix, _ := common.ParseAPIDeadlineToEndOfDay(form.Deadline)
|
||||||
var deadline time.Time
|
|
||||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
|
||||||
deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
|
||||||
23, 59, 59, 0, time.Local)
|
|
||||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
|
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
|
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: deadlineUnix.AsTimePtr()})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/context/upload"
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
@ -181,7 +181,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
|||||||
filename = query
|
filename = query
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, err := attachment.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||||
Name: filename,
|
Name: filename,
|
||||||
UploaderID: ctx.Doer.ID,
|
UploaderID: ctx.Doer.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
@ -247,6 +247,8 @@ func EditIssueAttachment(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
@ -261,8 +263,13 @@ func EditIssueAttachment(ctx *context.APIContext) {
|
|||||||
attachment.Name = form.Name
|
attachment.Name = form.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_model.UpdateAttachment(ctx, attachment); err != nil {
|
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attachment); err != nil {
|
||||||
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err)
|
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
|
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/context/upload"
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
@ -189,7 +189,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
|||||||
filename = query
|
filename = query
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, err := attachment.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
attachment, err := attachment_service.UploadAttachment(ctx, file, setting.Attachment.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||||
Name: filename,
|
Name: filename,
|
||||||
UploaderID: ctx.Doer.ID,
|
UploaderID: ctx.Doer.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
@ -263,6 +263,8 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
attach := getIssueCommentAttachmentSafeWrite(ctx)
|
attach := getIssueCommentAttachmentSafeWrite(ctx)
|
||||||
@ -275,8 +277,13 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
|
|||||||
attach.Name = form.Name
|
attach.Name = form.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
|
if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attach); err != nil {
|
||||||
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
|
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
|
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ package repo
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
@ -16,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
)
|
)
|
||||||
@ -155,16 +155,16 @@ func CreateMilestone(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
form := web.GetForm(ctx).(*api.CreateMilestoneOption)
|
form := web.GetForm(ctx).(*api.CreateMilestoneOption)
|
||||||
|
|
||||||
if form.Deadline == nil {
|
var deadlineUnix int64
|
||||||
defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
|
if form.Deadline != nil {
|
||||||
form.Deadline = &defaultDeadline
|
deadlineUnix = form.Deadline.Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
milestone := &issues_model.Milestone{
|
milestone := &issues_model.Milestone{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
Name: form.Title,
|
Name: form.Title,
|
||||||
Content: form.Description,
|
Content: form.Description,
|
||||||
DeadlineUnix: timeutil.TimeStamp(form.Deadline.Unix()),
|
DeadlineUnix: timeutil.TimeStamp(deadlineUnix),
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.State == "closed" {
|
if form.State == "closed" {
|
||||||
@ -225,9 +225,7 @@ func EditMilestone(ctx *context.APIContext) {
|
|||||||
if form.Description != nil {
|
if form.Description != nil {
|
||||||
milestone.Content = *form.Description
|
milestone.Content = *form.Description
|
||||||
}
|
}
|
||||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
milestone.DeadlineUnix, _ = common.ParseAPIDeadlineToEndOfDay(form.Deadline)
|
||||||
milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
oldIsClosed := milestone.IsClosed
|
oldIsClosed := milestone.IsClosed
|
||||||
if form.State != nil {
|
if form.State != nil {
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/attachment"
|
attachment_service "code.gitea.io/gitea/services/attachment"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/context/upload"
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
@ -234,7 +234,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new attachment and save the file
|
// Create a new attachment and save the file
|
||||||
attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
|
attach, err := attachment_service.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
|
||||||
Name: filename,
|
Name: filename,
|
||||||
UploaderID: ctx.Doer.ID,
|
UploaderID: ctx.Doer.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
@ -291,6 +291,8 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
|||||||
// responses:
|
// responses:
|
||||||
// "201":
|
// "201":
|
||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
@ -322,8 +324,13 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
|||||||
attach.Name = form.Name
|
attach.Name = form.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
|
if err := attachment_service.UpdateAttachment(ctx, setting.Repository.Release.AllowedTypes, attach); err != nil {
|
||||||
|
if upload.IsErrFileTypeForbidden(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
|
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
|
ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
|
||||||
}
|
}
|
||||||
|
31
routers/common/deadline.go
Normal file
31
routers/common/deadline.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseDeadlineDateToEndOfDay(date string) (timeutil.TimeStamp, error) {
|
||||||
|
if date == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
deadline, err := time.ParseInLocation("2006-01-02", date, setting.DefaultUILocation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
||||||
|
return timeutil.TimeStamp(deadline.Unix()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAPIDeadlineToEndOfDay(t *time.Time) (timeutil.TimeStamp, error) {
|
||||||
|
if t == nil || t.IsZero() || t.Unix() == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
deadline := time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, setting.DefaultUILocation)
|
||||||
|
return timeutil.TimeStamp(deadline.Unix()), nil
|
||||||
|
}
|
@ -71,6 +71,7 @@ func renderMarkdown(ctx *context.Context, act *activities_model.Action, content
|
|||||||
|
|
||||||
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
// feedActionsToFeedItems convert gitea's Action feed to feeds Item
|
||||||
func feedActionsToFeedItems(ctx *context.Context, actions activities_model.ActionList) (items []*feeds.Item, err error) {
|
func feedActionsToFeedItems(ctx *context.Context, actions activities_model.ActionList) (items []*feeds.Item, err error) {
|
||||||
|
renderUtils := templates.NewRenderUtils(ctx)
|
||||||
for _, act := range actions {
|
for _, act := range actions {
|
||||||
act.LoadActUser(ctx)
|
act.LoadActUser(ctx)
|
||||||
|
|
||||||
@ -215,7 +216,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
|
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
|
||||||
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
|
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
|
||||||
commit.Sha1,
|
commit.Sha1,
|
||||||
templates.RenderCommitMessage(ctx, commit.Message, nil),
|
renderUtils.RenderCommitMessage(commit.Message, nil),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func goGet(ctx *context.Context) {
|
|||||||
insecure = "--insecure "
|
insecure = "--insecure "
|
||||||
}
|
}
|
||||||
|
|
||||||
goGetImport := context.ComposeGoGetImport(ownerName, trimmedRepoName)
|
goGetImport := context.ComposeGoGetImport(ctx, ownerName, trimmedRepoName)
|
||||||
|
|
||||||
var cloneURL string
|
var cloneURL string
|
||||||
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
|
if setting.Repository.GoGetCloneURLProtocol == "ssh" {
|
||||||
|
@ -103,9 +103,9 @@ func Projects(ctx *context.Context) {
|
|||||||
} else {
|
} else {
|
||||||
ctx.Data["State"] = "open"
|
ctx.Data["State"] = "open"
|
||||||
}
|
}
|
||||||
|
renderUtils := templates.NewRenderUtils(ctx)
|
||||||
for _, project := range projects {
|
for _, project := range projects {
|
||||||
project.RenderedContent = templates.RenderMarkdownToHtml(ctx, project.Description)
|
project.RenderedContent = renderUtils.MarkdownToHtml(project.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = shared_user.LoadHeaderCount(ctx)
|
err = shared_user.LoadHeaderCount(ctx)
|
||||||
@ -435,7 +435,7 @@ func ViewProject(ctx *context.Context) {
|
|||||||
ctx.Data["SelectLabels"] = selectLabels
|
ctx.Data["SelectLabels"] = selectLabels
|
||||||
ctx.Data["AssigneeID"] = assigneeID
|
ctx.Data["AssigneeID"] = assigneeID
|
||||||
|
|
||||||
project.RenderedContent = templates.RenderMarkdownToHtml(ctx, project.Description)
|
project.RenderedContent = templates.NewRenderUtils(ctx).MarkdownToHtml(project.Description)
|
||||||
ctx.Data["LinkedPRs"] = linkedPrsMap
|
ctx.Data["LinkedPRs"] = linkedPrsMap
|
||||||
ctx.Data["PageIsViewProjects"] = true
|
ctx.Data["PageIsViewProjects"] = true
|
||||||
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
|
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -45,9 +44,9 @@ import (
|
|||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/templates/vars"
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
"code.gitea.io/gitea/routers/utils"
|
||||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
@ -2215,7 +2214,7 @@ func GetIssueInfo(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.JSON(http.StatusOK, map[string]any{
|
ctx.JSON(http.StatusOK, map[string]any{
|
||||||
"convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue),
|
"convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue),
|
||||||
"renderedLabels": templates.RenderLabels(ctx, ctx.Locale, issue.Labels, ctx.Repo.RepoLink, issue),
|
"renderedLabels": templates.NewRenderUtils(ctx).RenderLabels(issue.Labels, ctx.Repo.RepoLink, issue),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2329,7 +2328,6 @@ func UpdateIssueContent(ctx *context.Context) {
|
|||||||
|
|
||||||
// UpdateIssueDeadline updates an issue deadline
|
// UpdateIssueDeadline updates an issue deadline
|
||||||
func UpdateIssueDeadline(ctx *context.Context) {
|
func UpdateIssueDeadline(ctx *context.Context) {
|
||||||
form := web.GetForm(ctx).(*api.EditDeadlineOption)
|
|
||||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrIssueNotExist(err) {
|
if issues_model.IsErrIssueNotExist(err) {
|
||||||
@ -2345,20 +2343,13 @@ func UpdateIssueDeadline(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadlineUnix timeutil.TimeStamp
|
deadlineUnix, _ := common.ParseDeadlineDateToEndOfDay(ctx.FormString("deadline"))
|
||||||
var deadline time.Time
|
|
||||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
|
||||||
deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
|
||||||
23, 59, 59, 0, time.Local)
|
|
||||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error())
|
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
|
ctx.JSONRedirect("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIssueMilestone change issue's milestone
|
// UpdateIssueMilestone change issue's milestone
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
@ -16,8 +15,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
"code.gitea.io/gitea/services/issue"
|
"code.gitea.io/gitea/services/issue"
|
||||||
@ -134,22 +133,18 @@ func NewMilestonePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Deadline) == 0 {
|
deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
|
||||||
form.Deadline = "9999-12-31"
|
|
||||||
}
|
|
||||||
deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data["Err_Deadline"] = true
|
ctx.Data["Err_Deadline"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
if err := issues_model.NewMilestone(ctx, &issues_model.Milestone{
|
||||||
if err = issues_model.NewMilestone(ctx, &issues_model.Milestone{
|
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
Name: form.Title,
|
Name: form.Title,
|
||||||
Content: form.Content,
|
Content: form.Content,
|
||||||
DeadlineUnix: timeutil.TimeStamp(deadline.Unix()),
|
DeadlineUnix: deadlineUnix,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("NewMilestone", err)
|
ctx.ServerError("NewMilestone", err)
|
||||||
return
|
return
|
||||||
@ -194,17 +189,13 @@ func EditMilestonePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Deadline) == 0 {
|
deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
|
||||||
form.Deadline = "9999-12-31"
|
|
||||||
}
|
|
||||||
deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data["Err_Deadline"] = true
|
ctx.Data["Err_Deadline"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
|
||||||
m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
|
m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if issues_model.IsErrMilestoneNotExist(err) {
|
if issues_model.IsErrMilestoneNotExist(err) {
|
||||||
@ -216,7 +207,7 @@ func EditMilestonePost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
m.Name = form.Title
|
m.Name = form.Title
|
||||||
m.Content = form.Content
|
m.Content = form.Content
|
||||||
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
m.DeadlineUnix = deadlineUnix
|
||||||
if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil {
|
if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil {
|
||||||
ctx.ServerError("UpdateMilestone", err)
|
ctx.ServerError("UpdateMilestone", err)
|
||||||
return
|
return
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "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/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
@ -100,12 +99,12 @@ func CollaborationPost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil {
|
if err = repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, u, perm.AccessModeWrite); err != nil {
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator.blocked_user"))
|
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator.blocked_user"))
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
} else {
|
} else {
|
||||||
ctx.ServerError("AddCollaborator", err)
|
ctx.ServerError("AddOrUpdateCollaborator", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1208,7 +1208,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Group("/{index}", func() {
|
m.Group("/{index}", func() {
|
||||||
m.Post("/title", repo.UpdateIssueTitle)
|
m.Post("/title", repo.UpdateIssueTitle)
|
||||||
m.Post("/content", repo.UpdateIssueContent)
|
m.Post("/content", repo.UpdateIssueContent)
|
||||||
m.Post("/deadline", web.Bind(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
m.Post("/deadline", repo.UpdateIssueDeadline)
|
||||||
m.Post("/watch", repo.IssueWatch)
|
m.Post("/watch", repo.IssueWatch)
|
||||||
m.Post("/ref", repo.UpdateIssueRef)
|
m.Post("/ref", repo.UpdateIssueRef)
|
||||||
m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin)
|
m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin)
|
||||||
@ -1323,7 +1323,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get(".rss", feedEnabled, repo.TagsListFeedRSS)
|
m.Get(".rss", feedEnabled, repo.TagsListFeedRSS)
|
||||||
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
|
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
|
||||||
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
||||||
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
|
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
|
||||||
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
|
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
|
||||||
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
|
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
|
||||||
}, ignSignIn, context.RepoAssignment, reqRepoCodeReader)
|
}, ignSignIn, context.RepoAssignment, reqRepoCodeReader)
|
||||||
@ -1337,7 +1337,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
|
m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
|
||||||
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
|
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
|
||||||
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
||||||
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
|
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
|
||||||
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
|
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
|
||||||
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
|
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
@ -1535,7 +1535,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
|
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
|
||||||
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS)
|
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS)
|
||||||
// "/*" route is deprecated, and kept for backward compatibility
|
// "/*" route is deprecated, and kept for backward compatibility
|
||||||
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownloadOrLFS)
|
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownloadOrLFS)
|
||||||
}, repo.MustBeNotEmpty)
|
}, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Group("/raw", func() {
|
m.Group("/raw", func() {
|
||||||
@ -1544,7 +1544,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
|
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
|
||||||
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID)
|
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID)
|
||||||
// "/*" route is deprecated, and kept for backward compatibility
|
// "/*" route is deprecated, and kept for backward compatibility
|
||||||
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload)
|
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownload)
|
||||||
}, repo.MustBeNotEmpty)
|
}, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Group("/render", func() {
|
m.Group("/render", func() {
|
||||||
@ -1559,7 +1559,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
|
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
|
||||||
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
|
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
|
||||||
// "/*" route is deprecated, and kept for backward compatibility
|
// "/*" route is deprecated, and kept for backward compatibility
|
||||||
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits)
|
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.RefCommits)
|
||||||
}, repo.MustBeNotEmpty)
|
}, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Group("/blame", func() {
|
m.Group("/blame", func() {
|
||||||
@ -1582,7 +1582,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
|
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
|
||||||
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
|
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
|
||||||
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
|
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
|
||||||
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
|
m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
|
||||||
}, repo.SetEditorconfigIfExists)
|
}, repo.SetEditorconfigIfExists)
|
||||||
|
|
||||||
m.Get("/forks", context.RepoRef(), repo.Forks)
|
m.Get("/forks", context.RepoRef(), repo.Forks)
|
||||||
|
@ -128,18 +128,16 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
|
return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
|
||||||
}
|
}
|
||||||
if err := commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &git_model.CommitStatus{
|
status := git_model.CommitStatus{
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index),
|
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index),
|
||||||
Description: description,
|
Description: description,
|
||||||
Context: ctxname,
|
Context: ctxname,
|
||||||
CreatorID: creator.ID,
|
CreatorID: creator.ID,
|
||||||
State: state,
|
State: state,
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("NewCommitStatus: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
|
func toCommitStatus(status actions_model.Status) api.CommitStatusState {
|
||||||
|
@ -50,3 +50,12 @@ func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string,
|
|||||||
|
|
||||||
return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
|
return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAttachment updates an attachment, verifying that its name is among the allowed types.
|
||||||
|
func UpdateAttachment(ctx context.Context, allowedTypes string, attach *repo_model.Attachment) error {
|
||||||
|
if err := upload.Verify(nil, attach.Name, allowedTypes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo_model.UpdateAttachment(ctx, attach)
|
||||||
|
}
|
||||||
|
@ -6,9 +6,12 @@ package automerge
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/repository"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,3 +47,11 @@ func (n *automergeNotifier) PullReviewDismiss(ctx context.Context, doer *user_mo
|
|||||||
// as reviews could have blocked a pending automerge let's recheck
|
// as reviews could have blocked a pending automerge let's recheck
|
||||||
StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest)
|
StartPRCheckAndAutoMerge(ctx, review.Issue.PullRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *automergeNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
||||||
|
if status.State.IsSuccess() {
|
||||||
|
if err := StartPRCheckAndAutoMergeBySHA(ctx, commit.Sha1, repo); err != nil {
|
||||||
|
log.Error("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, sender.ID, commit.Sha1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,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/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -306,24 +305,8 @@ func RepoRefForAPI(next http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
|
// NOTICE: the "ref" here for internal usage only (e.g. woodpecker)
|
||||||
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
|
refName, _ := getRefNameLegacy(ctx.Base, ctx.Repo, ctx.FormTrim("ref"))
|
||||||
if err != nil {
|
|
||||||
if git.IsErrNotExist(err) {
|
|
||||||
ctx.NotFound()
|
|
||||||
} else {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Repo.Commit = commit
|
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
|
||||||
ctx.Repo.TreePath = ctx.PathParam("*")
|
|
||||||
next.ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||||
|
@ -100,6 +100,7 @@ func NewTemplateContextForWeb(ctx *Context) TemplateContext {
|
|||||||
tmplCtx := NewTemplateContext(ctx)
|
tmplCtx := NewTemplateContext(ctx)
|
||||||
tmplCtx["Locale"] = ctx.Base.Locale
|
tmplCtx["Locale"] = ctx.Base.Locale
|
||||||
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
||||||
|
tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx)
|
||||||
tmplCtx["RootData"] = ctx.Data
|
tmplCtx["RootData"] = ctx.Data
|
||||||
tmplCtx["Consts"] = map[string]any{
|
tmplCtx["Consts"] = map[string]any{
|
||||||
"RepoUnitTypeCode": unit.TypeCode,
|
"RepoUnitTypeCode": unit.TypeCode,
|
||||||
@ -154,7 +155,9 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||||||
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
|
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
|
||||||
|
|
||||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
if setting.IsProd && !setting.IsInTesting {
|
||||||
|
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||||
|
}
|
||||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||||
ctx.Data["Link"] = ctx.Link
|
ctx.Data["Link"] = ctx.Link
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
@ -306,11 +307,9 @@ func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ComposeGoGetImport returns go-get-import meta content.
|
// ComposeGoGetImport returns go-get-import meta content.
|
||||||
func ComposeGoGetImport(owner, repo string) string {
|
func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
|
||||||
/// setting.AppUrl is guaranteed to be parse as url
|
curAppURL, _ := url.Parse(httplib.GuessCurrentAppURL(ctx))
|
||||||
appURL, _ := url.Parse(setting.AppURL)
|
return path.Join(curAppURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
|
||||||
|
|
||||||
return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
||||||
@ -332,7 +331,7 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
|
|||||||
} else {
|
} else {
|
||||||
cloneURL = repo_model.ComposeHTTPSCloneURL(username, reponame)
|
cloneURL = repo_model.ComposeHTTPSCloneURL(username, reponame)
|
||||||
}
|
}
|
||||||
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), cloneURL)
|
goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
|
||||||
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
|
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
|
||||||
ctx.PlainText(http.StatusOK, htmlMeta)
|
ctx.PlainText(http.StatusOK, htmlMeta)
|
||||||
}
|
}
|
||||||
@ -744,7 +743,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctx.FormString("go-get") == "1" {
|
if ctx.FormString("go-get") == "1" {
|
||||||
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
|
ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, owner.Name, repo.Name)
|
||||||
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||||
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
|
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
|
||||||
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
|
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
|
||||||
@ -756,19 +755,11 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
|||||||
type RepoRefType int
|
type RepoRefType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RepoRefLegacy unknown type, make educated guess and redirect.
|
// RepoRefUnknown is for legacy support, makes the code to "guess" the ref type
|
||||||
// for backward compatibility with previous URL scheme
|
RepoRefUnknown RepoRefType = iota
|
||||||
RepoRefLegacy RepoRefType = iota
|
|
||||||
// RepoRefAny is for usage where educated guess is needed
|
|
||||||
// but redirect can not be made
|
|
||||||
RepoRefAny
|
|
||||||
// RepoRefBranch branch
|
|
||||||
RepoRefBranch
|
RepoRefBranch
|
||||||
// RepoRefTag tag
|
|
||||||
RepoRefTag
|
RepoRefTag
|
||||||
// RepoRefCommit commit
|
|
||||||
RepoRefCommit
|
RepoRefCommit
|
||||||
// RepoRefBlob blob
|
|
||||||
RepoRefBlob
|
RepoRefBlob
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -781,22 +772,6 @@ func RepoRef() func(*Context) context.CancelFunc {
|
|||||||
return RepoRefByType(RepoRefBranch)
|
return RepoRefByType(RepoRefBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefTypeIncludesBranches returns true if ref type can be a branch
|
|
||||||
func (rt RepoRefType) RefTypeIncludesBranches() bool {
|
|
||||||
if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefTypeIncludesTags returns true if ref type can be a tag
|
|
||||||
func (rt RepoRefType) RefTypeIncludesTags() bool {
|
|
||||||
if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
|
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
|
||||||
refName := ""
|
refName := ""
|
||||||
parts := strings.Split(path, "/")
|
parts := strings.Split(path, "/")
|
||||||
@ -810,28 +785,50 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isStringLikelyCommitID(objFmt git.ObjectFormat, s string, minLength ...int) bool {
|
||||||
|
minLen := util.OptionalArg(minLength, objFmt.FullLength())
|
||||||
|
if len(s) < minLen || len(s) > objFmt.FullLength() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
||||||
|
if !isHex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
|
||||||
|
extraRef := util.OptionalArg(optionalExtraRef)
|
||||||
|
reqPath := ctx.PathParam("*")
|
||||||
|
reqPath = path.Join(extraRef, reqPath)
|
||||||
|
|
||||||
|
if refName := getRefName(ctx, repo, RepoRefBranch); refName != "" {
|
||||||
|
return refName, RepoRefBranch
|
||||||
|
}
|
||||||
|
if refName := getRefName(ctx, repo, RepoRefTag); refName != "" {
|
||||||
|
return refName, RepoRefTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// For legacy support only full commit sha
|
||||||
|
parts := strings.Split(reqPath, "/")
|
||||||
|
if isStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
|
||||||
|
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
||||||
|
repo.TreePath = strings.Join(parts[1:], "/")
|
||||||
|
return parts[0], RepoRefCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
|
||||||
|
return refName, RepoRefBlob
|
||||||
|
}
|
||||||
|
repo.TreePath = reqPath
|
||||||
|
return repo.Repository.DefaultBranch, RepoRefBranch
|
||||||
|
}
|
||||||
|
|
||||||
func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
||||||
path := ctx.PathParam("*")
|
path := ctx.PathParam("*")
|
||||||
switch pathType {
|
switch pathType {
|
||||||
case RepoRefLegacy, RepoRefAny:
|
|
||||||
if refName := getRefName(ctx, repo, RepoRefBranch); len(refName) > 0 {
|
|
||||||
return refName
|
|
||||||
}
|
|
||||||
if refName := getRefName(ctx, repo, RepoRefTag); len(refName) > 0 {
|
|
||||||
return refName
|
|
||||||
}
|
|
||||||
// For legacy and API support only full commit sha
|
|
||||||
parts := strings.Split(path, "/")
|
|
||||||
|
|
||||||
if len(parts) > 0 && len(parts[0]) == git.ObjectFormatFromName(repo.Repository.ObjectFormatName).FullLength() {
|
|
||||||
repo.TreePath = strings.Join(parts[1:], "/")
|
|
||||||
return parts[0]
|
|
||||||
}
|
|
||||||
if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
|
|
||||||
return refName
|
|
||||||
}
|
|
||||||
repo.TreePath = path
|
|
||||||
return repo.Repository.DefaultBranch
|
|
||||||
case RepoRefBranch:
|
case RepoRefBranch:
|
||||||
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
|
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
|
||||||
if len(ref) == 0 {
|
if len(ref) == 0 {
|
||||||
@ -866,13 +863,13 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
|||||||
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
|
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
|
||||||
case RepoRefCommit:
|
case RepoRefCommit:
|
||||||
parts := strings.Split(path, "/")
|
parts := strings.Split(path, "/")
|
||||||
|
if isStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
|
||||||
if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GetObjectFormat().FullLength() {
|
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
|
||||||
repo.TreePath = strings.Join(parts[1:], "/")
|
repo.TreePath = strings.Join(parts[1:], "/")
|
||||||
return parts[0]
|
return parts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parts) > 0 && parts[0] == headRefName {
|
if parts[0] == headRefName {
|
||||||
// HEAD ref points to last default branch commit
|
// HEAD ref points to last default branch commit
|
||||||
commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch)
|
commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -888,15 +885,21 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
default:
|
default:
|
||||||
log.Error("Unrecognized path type: %v", path)
|
panic(fmt.Sprintf("Unrecognized path type: %v", pathType))
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RepoRefByTypeOptions struct {
|
||||||
|
IgnoreNotExistErr bool
|
||||||
|
}
|
||||||
|
|
||||||
// RepoRefByType handles repository reference name for a specific type
|
// RepoRefByType handles repository reference name for a specific type
|
||||||
// of repository reference
|
// of repository reference
|
||||||
func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc {
|
func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) context.CancelFunc {
|
||||||
|
opt := util.OptionalArg(opts)
|
||||||
return func(ctx *Context) (cancel context.CancelFunc) {
|
return func(ctx *Context) (cancel context.CancelFunc) {
|
||||||
|
refType := detectRefType
|
||||||
// Empty repository does not have reference information.
|
// Empty repository does not have reference information.
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
// assume the user is viewing the (non-existent) default branch
|
// assume the user is viewing the (non-existent) default branch
|
||||||
@ -956,7 +959,12 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
}
|
}
|
||||||
ctx.Repo.IsViewBranch = true
|
ctx.Repo.IsViewBranch = true
|
||||||
} else {
|
} else {
|
||||||
refName = getRefName(ctx.Base, ctx.Repo, refType)
|
guessLegacyPath := refType == RepoRefUnknown
|
||||||
|
if guessLegacyPath {
|
||||||
|
refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo)
|
||||||
|
} else {
|
||||||
|
refName = getRefName(ctx.Base, ctx.Repo, refType)
|
||||||
|
}
|
||||||
ctx.Repo.RefName = refName
|
ctx.Repo.RefName = refName
|
||||||
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
|
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
|
||||||
if isRenamedBranch && has {
|
if isRenamedBranch && has {
|
||||||
@ -967,7 +975,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||||
ctx.Repo.IsViewBranch = true
|
ctx.Repo.IsViewBranch = true
|
||||||
ctx.Repo.BranchName = refName
|
ctx.Repo.BranchName = refName
|
||||||
|
|
||||||
@ -977,7 +985,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||||
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
|
} else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||||
ctx.Repo.IsViewTag = true
|
ctx.Repo.IsViewTag = true
|
||||||
ctx.Repo.TagName = refName
|
ctx.Repo.TagName = refName
|
||||||
|
|
||||||
@ -991,7 +999,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||||
} else if len(refName) >= 7 && len(refName) <= ctx.Repo.GetObjectFormat().FullLength() {
|
} else if isStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
|
||||||
ctx.Repo.IsViewCommit = true
|
ctx.Repo.IsViewCommit = true
|
||||||
ctx.Repo.CommitID = refName
|
ctx.Repo.CommitID = refName
|
||||||
|
|
||||||
@ -1002,18 +1010,18 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||||||
}
|
}
|
||||||
// If short commit ID add canonical link header
|
// If short commit ID add canonical link header
|
||||||
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
|
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
|
||||||
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
|
||||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
|
ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
|
if opt.IgnoreNotExistErr {
|
||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||||
return cancel
|
return cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
if refType == RepoRefLegacy {
|
if guessLegacyPath {
|
||||||
// redirect from old URL scheme to new URL scheme
|
// redirect from old URL scheme to new URL scheme
|
||||||
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.PathParam("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.PathParam("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
||||||
redirect := path.Join(
|
redirect := path.Join(
|
||||||
|
@ -28,12 +28,13 @@ func IsErrFileTypeForbidden(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrFileTypeForbidden) Error() string {
|
func (err ErrFileTypeForbidden) Error() string {
|
||||||
return "This file extension or type is not allowed to be uploaded."
|
return "This file cannot be uploaded or modified due to a forbidden file extension or type."
|
||||||
}
|
}
|
||||||
|
|
||||||
var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`)
|
var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`)
|
||||||
|
|
||||||
// Verify validates whether a file is allowed to be uploaded.
|
// Verify validates whether a file is allowed to be uploaded. If buf is empty, it will just check if the file
|
||||||
|
// has an allowed file extension.
|
||||||
func Verify(buf []byte, fileName, allowedTypesStr string) error {
|
func Verify(buf []byte, fileName, allowedTypesStr string) error {
|
||||||
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format
|
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format
|
||||||
|
|
||||||
@ -56,21 +57,31 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error {
|
|||||||
return ErrFileTypeForbidden{Type: fullMimeType}
|
return ErrFileTypeForbidden{Type: fullMimeType}
|
||||||
}
|
}
|
||||||
extension := strings.ToLower(path.Ext(fileName))
|
extension := strings.ToLower(path.Ext(fileName))
|
||||||
|
isBufEmpty := len(buf) <= 1
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers
|
||||||
for _, allowEntry := range allowedTypes {
|
for _, allowEntry := range allowedTypes {
|
||||||
if allowEntry == "*/*" {
|
if allowEntry == "*/*" {
|
||||||
return nil // everything allowed
|
return nil // everything allowed
|
||||||
} else if strings.HasPrefix(allowEntry, ".") && allowEntry == extension {
|
}
|
||||||
|
if strings.HasPrefix(allowEntry, ".") && allowEntry == extension {
|
||||||
return nil // extension is allowed
|
return nil // extension is allowed
|
||||||
} else if mimeType == allowEntry {
|
}
|
||||||
|
if isBufEmpty {
|
||||||
|
continue // skip mime type checks if buffer is empty
|
||||||
|
}
|
||||||
|
if mimeType == allowEntry {
|
||||||
return nil // mime type is allowed
|
return nil // mime type is allowed
|
||||||
} else if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) {
|
}
|
||||||
|
if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) {
|
||||||
return nil // wildcard match, e.g. image/*
|
return nil // wildcard match, e.g. image/*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Attachment with type %s blocked from upload", fullMimeType)
|
if !isBufEmpty {
|
||||||
|
log.Info("Attachment with type %s blocked from upload", fullMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
return ErrFileTypeForbidden{Type: fullMimeType}
|
return ErrFileTypeForbidden{Type: fullMimeType}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
|
|||||||
if m.IsClosed {
|
if m.IsClosed {
|
||||||
apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
|
apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
|
||||||
}
|
}
|
||||||
if m.DeadlineUnix.Year() < 9999 {
|
if m.DeadlineUnix > 0 {
|
||||||
apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
|
apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
|
||||||
}
|
}
|
||||||
return apiMilestone
|
return apiMilestone
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models"
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models"
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models"
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -74,4 +75,6 @@ type Notifier interface {
|
|||||||
PackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor)
|
PackageDelete(ctx context.Context, doer *user_model.User, pd *packages_model.PackageDescriptor)
|
||||||
|
|
||||||
ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository)
|
ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository)
|
||||||
|
|
||||||
|
CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -367,3 +368,9 @@ func ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
|
|||||||
notifier.ChangeDefaultBranch(ctx, repo)
|
notifier.ChangeDefaultBranch(ctx, repo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
||||||
|
for _, notifier := range notifiers {
|
||||||
|
notifier.CreateCommitStatus(ctx, repo, commit, sender, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package notify
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -208,3 +209,6 @@ func (*NullNotifier) PackageDelete(ctx context.Context, doer *user_model.User, p
|
|||||||
// ChangeDefaultBranch places a place holder function
|
// ChangeDefaultBranch places a place holder function
|
||||||
func (*NullNotifier) ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
|
func (*NullNotifier) ChangeDefaultBranch(ctx context.Context, repo *repo_model.Repository) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*NullNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
||||||
|
}
|
||||||
|
@ -66,7 +66,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil {
|
if err := CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,60 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func AddOrUpdateCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User, mode perm.AccessMode) error {
|
||||||
|
// only allow valid access modes, read, write and admin
|
||||||
|
if mode < perm.AccessModeRead || mode > perm.AccessModeAdmin {
|
||||||
|
return perm.ErrInvalidAccessMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.LoadOwner(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user_model.IsUserBlockedBy(ctx, u, repo.OwnerID) || user_model.IsUserBlockedBy(ctx, repo.Owner, u.ID) {
|
||||||
|
return user_model.ErrBlockedUser
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
collaboration, has, err := db.Get[repo_model.Collaboration](ctx, builder.Eq{
|
||||||
|
"repo_id": repo.ID,
|
||||||
|
"user_id": u.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
if collaboration.Mode == mode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err = db.GetEngine(ctx).
|
||||||
|
Where("repo_id=?", repo.ID).
|
||||||
|
And("user_id=?", u.ID).
|
||||||
|
Cols("mode").
|
||||||
|
Update(&repo_model.Collaboration{
|
||||||
|
Mode: mode,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err = db.Insert(ctx, &repo_model.Collaboration{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
UserID: u.ID,
|
||||||
|
Mode: mode,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return access_model.RecalculateUserAccess(ctx, repo, u.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||||
func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User) (err error) {
|
func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User) (err error) {
|
||||||
collaboration := &repo_model.Collaboration{
|
collaboration := &repo_model.Collaboration{
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -14,6 +15,21 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestRepository_AddCollaborator(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
testSuccess := func(repoID, userID int64) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||||
|
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
|
||||||
|
assert.NoError(t, AddOrUpdateCollaborator(db.DefaultContext, repo, user, perm.AccessModeWrite))
|
||||||
|
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}, &user_model.User{ID: userID})
|
||||||
|
}
|
||||||
|
testSuccess(1, 4)
|
||||||
|
testSuccess(1, 4)
|
||||||
|
testSuccess(3, 4)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRepository_DeleteCollaboration(t *testing.T) {
|
func TestRepository_DeleteCollaboration(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
@ -18,8 +18,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/services/automerge"
|
"code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCacheKey(repoID int64, brancheName string) string {
|
func getCacheKey(repoID int64, brancheName string) string {
|
||||||
@ -103,6 +104,8 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notify.CreateCommitStatus(ctx, repo, repo_module.CommitToPushCommit(commit), creator, status)
|
||||||
|
|
||||||
defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err)
|
return fmt.Errorf("GetBranchCommit[%s]: %w", repo.DefaultBranch, err)
|
||||||
@ -114,12 +117,6 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.State.IsSuccess() {
|
|
||||||
if err := automerge.StartPRCheckAndAutoMergeBySHA(ctx, sha, repo); err != nil {
|
|
||||||
return fmt.Errorf("MergeScheduledPullRequest[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"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/models/webhook"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -243,7 +249,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt
|
|||||||
var rollbackRepo *repo_model.Repository
|
var rollbackRepo *repo_model.Repository
|
||||||
|
|
||||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil {
|
if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,3 +341,136 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt
|
|||||||
|
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRepositoryByExample creates a repository for the user/organization.
|
||||||
|
func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) {
|
||||||
|
if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||||
|
} else if has {
|
||||||
|
return repo_model.ErrRepoAlreadyExist{
|
||||||
|
Uname: u.Name,
|
||||||
|
Name: repo.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repoPath := repo_model.RepoPath(u.Name, repo.Name)
|
||||||
|
isExist, err := util.IsExist(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !overwriteOrAdopt && isExist {
|
||||||
|
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
|
||||||
|
return repo_model.ErrRepoFilesAlreadyExist{
|
||||||
|
Uname: u.Name,
|
||||||
|
Name: repo.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.Insert(ctx, repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert units for repo
|
||||||
|
defaultUnits := unit.DefaultRepoUnits
|
||||||
|
if isFork {
|
||||||
|
defaultUnits = unit.DefaultForkRepoUnits
|
||||||
|
}
|
||||||
|
units := make([]repo_model.RepoUnit, 0, len(defaultUnits))
|
||||||
|
for _, tp := range defaultUnits {
|
||||||
|
if tp == unit.TypeIssues {
|
||||||
|
units = append(units, repo_model.RepoUnit{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: tp,
|
||||||
|
Config: &repo_model.IssuesConfig{
|
||||||
|
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
|
||||||
|
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
|
||||||
|
EnableDependencies: setting.Service.DefaultEnableDependencies,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if tp == unit.TypePullRequests {
|
||||||
|
units = append(units, repo_model.RepoUnit{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: tp,
|
||||||
|
Config: &repo_model.PullRequestsConfig{
|
||||||
|
AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true,
|
||||||
|
DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle),
|
||||||
|
AllowRebaseUpdate: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if tp == unit.TypeProjects {
|
||||||
|
units = append(units, repo_model.RepoUnit{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: tp,
|
||||||
|
Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
units = append(units, repo_model.RepoUnit{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: tp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.Insert(ctx, units); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember visibility preference.
|
||||||
|
u.LastRepoVisibility = repo.IsPrivate
|
||||||
|
if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
|
||||||
|
return fmt.Errorf("UpdateUserCols: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = user_model.IncrUserRepoNum(ctx, u.ID); err != nil {
|
||||||
|
return fmt.Errorf("IncrUserRepoNum: %w", err)
|
||||||
|
}
|
||||||
|
u.NumRepos++
|
||||||
|
|
||||||
|
// Give access to all members in teams with access to all repositories.
|
||||||
|
if u.IsOrganization() {
|
||||||
|
teams, err := organization.FindOrgTeams(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("FindOrgTeams: %w", err)
|
||||||
|
}
|
||||||
|
for _, t := range teams {
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
if err := models.AddRepository(ctx, t, repo); err != nil {
|
||||||
|
return fmt.Errorf("AddRepository: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
|
||||||
|
return fmt.Errorf("IsUserRepoAdmin: %w", err)
|
||||||
|
} else if !isAdmin {
|
||||||
|
// Make creator repo admin if it wasn't assigned automatically
|
||||||
|
if err = AddOrUpdateCollaborator(ctx, repo, doer, perm.AccessModeAdmin); err != nil {
|
||||||
|
return fmt.Errorf("AddCollaborator: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
||||||
|
// Organization automatically called this in AddRepository method.
|
||||||
|
return fmt.Errorf("RecalculateAccesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.Service.AutoWatchNewRepos {
|
||||||
|
if err = repo_model.WatchRepo(ctx, doer, repo, true); err != nil {
|
||||||
|
return fmt.Errorf("WatchRepo: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
|
||||||
|
return fmt.Errorf("CopyDefaultWebhooksToRepo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -134,7 +134,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
err = db.WithTx(ctx, func(txCtx context.Context) error {
|
err = db.WithTx(ctx, func(txCtx context.Context) error {
|
||||||
if err = repo_module.CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil {
|
if err = CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ func generateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
|||||||
ObjectFormatName: templateRepo.ObjectFormatName,
|
ObjectFormatName: templateRepo.ObjectFormatName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo_module.CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil {
|
if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/globallock"
|
"code.gitea.io/gitea/modules/globallock"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
)
|
)
|
||||||
@ -419,10 +418,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !hasAccess {
|
if !hasAccess {
|
||||||
if err := repo_module.AddCollaborator(ctx, repo, newOwner); err != nil {
|
if err := AddOrUpdateCollaborator(ctx, repo, newOwner, perm.AccessModeRead); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := repo_model.ChangeCollaborationAccessMode(ctx, repo, newOwner.ID, perm.AccessModeRead); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package webhook
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
@ -861,6 +862,36 @@ func (m *webhookNotifier) SyncPushCommits(ctx context.Context, pusher *user_mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *webhookNotifier) CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, commit *repository.PushCommit, sender *user_model.User, status *git_model.CommitStatus) {
|
||||||
|
apiSender := convert.ToUser(ctx, sender, nil)
|
||||||
|
apiCommit, err := repository.ToAPIPayloadCommit(ctx, map[string]*user_model.User{}, repo.RepoPath(), repo.HTMLURL(), commit)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("commits.ToAPIPayloadCommits failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := api.CommitStatusPayload{
|
||||||
|
Context: status.Context,
|
||||||
|
CreatedAt: status.CreatedUnix.AsTime().UTC(),
|
||||||
|
Description: status.Description,
|
||||||
|
ID: status.ID,
|
||||||
|
SHA: commit.Sha1,
|
||||||
|
State: status.State.String(),
|
||||||
|
TargetURL: status.TargetURL,
|
||||||
|
|
||||||
|
Commit: apiCommit,
|
||||||
|
Repo: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
|
||||||
|
Sender: apiSender,
|
||||||
|
}
|
||||||
|
if !status.UpdatedUnix.IsZero() {
|
||||||
|
t := status.UpdatedUnix.AsTime().UTC()
|
||||||
|
payload.UpdatedAt = &t
|
||||||
|
}
|
||||||
|
if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventStatus, &payload); err != nil {
|
||||||
|
log.Error("PrepareWebhooks: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *webhookNotifier) SyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
|
func (m *webhookNotifier) SyncCreateRef(ctx context.Context, pusher *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) {
|
||||||
m.CreateRef(ctx, pusher, repo, refFullName, refID)
|
m.CreateRef(ctx, pusher, repo, refFullName, refID)
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
|
|||||||
{{if or .Participants .Assignees .MentionableTeams}}
|
{{if or .Participants .Assignees .MentionableTeams}}
|
||||||
mentionValues: Array.from(new Map([
|
mentionValues: Array.from(new Map([
|
||||||
{{- range .Participants -}}
|
{{- range .Participants -}}
|
||||||
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink $.Context}}'}],
|
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink ctx}}'}],
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- range .Assignees -}}
|
{{- range .Assignees -}}
|
||||||
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink $.Context}}'}],
|
['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink ctx}}'}],
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- range .MentionableTeams -}}
|
{{- range .MentionableTeams -}}
|
||||||
['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}', name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
|
['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}', name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{$description := .DescriptionHTML $.Context}}
|
{{$description := .DescriptionHTML ctx}}
|
||||||
{{if $description}}
|
{{if $description}}
|
||||||
<div class="flex-item-body">{{$description}}</div>
|
<div class="flex-item-body">{{$description}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -99,17 +99,17 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="center aligned">
|
<td class="center aligned">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 0)}} checked{{end}} title="{{ctx.Locale.Tr "org.teams.none_access"}}">
|
<input type="radio" name="unit_{{$unit.Type.Value}}" value="0"{{if or ($unit.Type.UnitGlobalDisabled) (eq ($.Team.UnitAccessMode ctx $unit.Type) 0)}} checked{{end}} title="{{ctx.Locale.Tr "org.teams.none_access"}}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="center aligned">
|
<td class="center aligned">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.read_access"}}">
|
<input type="radio" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode ctx $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.read_access"}}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="center aligned">
|
<td class="center aligned">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.write_access"}}">
|
<input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode ctx $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{ctx.Locale.Tr "org.teams.write_access"}}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -121,7 +121,7 @@
|
|||||||
{{if lt $unit.MaxPerm 2}}
|
{{if lt $unit.MaxPerm 2}}
|
||||||
<div {{if $unit.Type.UnitGlobalDisabled}}class="field" data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{else}}class="field"{{end}}>
|
<div {{if $unit.Type.UnitGlobalDisabled}}class="field" data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{else}}class="field"{{end}}>
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
<input type="checkbox" name="unit_{{$unit.Type.Value}}" value="1"{{if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode ctx $unit.Type) 1)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}}>
|
||||||
<label>{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</label>
|
<label>{{ctx.Locale.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{ctx.Locale.Tr "org.team_unit_disabled"}}{{end}}</label>
|
||||||
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
|
<span class="help">{{ctx.Locale.Tr $unit.DescKey}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,11 +61,11 @@
|
|||||||
{{if (not $unit.Type.UnitGlobalDisabled)}}
|
{{if (not $unit.Type.UnitGlobalDisabled)}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{{ctx.Locale.Tr $unit.NameKey}}</strong></td>
|
<td><strong>{{ctx.Locale.Tr $unit.NameKey}}</strong></td>
|
||||||
<td>{{if eq ($.Team.UnitAccessMode $.Context $unit.Type) 0 -}}
|
<td>{{if eq ($.Team.UnitAccessMode ctx $unit.Type) 0 -}}
|
||||||
{{ctx.Locale.Tr "org.teams.none_access"}}
|
{{ctx.Locale.Tr "org.teams.none_access"}}
|
||||||
{{- else if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode $.Context $unit.Type) 1) -}}
|
{{- else if or (eq $.Team.ID 0) (eq ($.Team.UnitAccessMode ctx $unit.Type) 1) -}}
|
||||||
{{ctx.Locale.Tr "org.teams.read_access"}}
|
{{ctx.Locale.Tr "org.teams.read_access"}}
|
||||||
{{- else if eq ($.Team.UnitAccessMode $.Context $unit.Type) 2 -}}
|
{{- else if eq ($.Team.UnitAccessMode ctx $unit.Type) 2 -}}
|
||||||
{{ctx.Locale.Tr "org.teams.write_access"}}
|
{{ctx.Locale.Tr "org.teams.write_access"}}
|
||||||
{{- end}}</td>
|
{{- end}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -27,7 +27,7 @@ git-fetch-with-cli = true</code></pre></div>
|
|||||||
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
|
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
|
||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||||
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .PackageDescriptor.Metadata.Dependencies}}
|
{{if .PackageDescriptor.Metadata.Dependencies}}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
{{if .PackageDescriptor.Metadata.Description}}<p>{{.PackageDescriptor.Metadata.Description}}</p>{{end}}
|
{{if .PackageDescriptor.Metadata.Description}}<p>{{.PackageDescriptor.Metadata.Description}}</p>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.LongDescription}}{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.LongDescription}}{{end}}
|
{{if .PackageDescriptor.Metadata.LongDescription}}{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.LongDescription}}{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Comments}}
|
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Comments}}
|
||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||||
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.Comments}}<div class="ui attached segment">{{StringUtils.Join .PackageDescriptor.Metadata.Comments " "}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Comments}}<div class="ui attached segment">{{StringUtils.Join .PackageDescriptor.Metadata.Comments " "}}</div>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
{{if .PackageDescriptor.Metadata.Readme}}
|
{{if .PackageDescriptor.Metadata.Readme}}
|
||||||
<div class="markup markdown">
|
<div class="markup markdown">
|
||||||
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}
|
{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Readme}}
|
||||||
</div>
|
</div>
|
||||||
{{else if .PackageDescriptor.Metadata.Description}}
|
{{else if .PackageDescriptor.Metadata.Description}}
|
||||||
{{.PackageDescriptor.Metadata.Description}}
|
{{.PackageDescriptor.Metadata.Description}}
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes .PackageDescriptor.Metadata.Readme}}
|
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes .PackageDescriptor.Metadata.Readme}}
|
||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||||
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Description}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.ReleaseNotes}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.ReleaseNotes}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.ReleaseNotes}}<div class="ui attached segment">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.ReleaseNotes}}</div>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .PackageDescriptor.Metadata.Dependencies}}
|
{{if .PackageDescriptor.Metadata.Dependencies}}
|
||||||
|
@ -14,6 +14,6 @@
|
|||||||
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
|
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
|
||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4>
|
||||||
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
||||||
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<p>{{if .PackageDescriptor.Metadata.Summary}}{{.PackageDescriptor.Metadata.Summary}}{{end}}</p>
|
<p>{{if .PackageDescriptor.Metadata.Summary}}{{.PackageDescriptor.Metadata.Summary}}{{end}}</p>
|
||||||
{{if .PackageDescriptor.Metadata.LongDescription}}
|
{{if .PackageDescriptor.Metadata.LongDescription}}
|
||||||
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.LongDescription}}
|
{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.LongDescription}}
|
||||||
{{else if .PackageDescriptor.Metadata.Description}}
|
{{else if .PackageDescriptor.Metadata.Description}}
|
||||||
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}}
|
{{ctx.RenderUtils.MarkdownToHtml .PackageDescriptor.Metadata.Description}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
{{svg "octicon-check"}}
|
{{svg "octicon-check"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{RenderLabel $.Context ctx.Locale .}}
|
{{ctx.RenderUtils.RenderLabel .}}
|
||||||
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
<p class="tw-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
||||||
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
|
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
|
||||||
</div>
|
</div>
|
||||||
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
|
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="right aligned middle aligned overflow-visible">
|
<td class="right aligned middle aligned overflow-visible">
|
||||||
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
|
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
|
||||||
@ -102,7 +102,7 @@
|
|||||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
||||||
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
|
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
|
||||||
</div>
|
</div>
|
||||||
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
|
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
<td class="two wide ui">
|
<td class="two wide ui">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
|
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
|
||||||
<div class="tw-flex tw-mb-4 tw-gap-1">
|
<div class="tw-flex tw-mb-4 tw-gap-1">
|
||||||
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
|
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
|
||||||
{{if not $.PageIsWiki}}
|
{{if not $.PageIsWiki}}
|
||||||
<div class="commit-header-buttons">
|
<div class="commit-header-buttons">
|
||||||
<a class="ui primary tiny button" href="{{.SourcePath}}">
|
<a class="ui primary tiny button" href="{{.SourcePath}}">
|
||||||
@ -135,7 +135,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{if IsMultilineCommitMessage .Commit.Message}}
|
{{if IsMultilineCommitMessage .Commit.Message}}
|
||||||
<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</pre>
|
<pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeMetas ctx)}}</pre>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/commit_load_branches_and_tags" .}}
|
{{template "repo/commit_load_branches_and_tags" .}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,10 +59,10 @@
|
|||||||
<td class="message">
|
<td class="message">
|
||||||
<span class="message-wrapper">
|
<span class="message-wrapper">
|
||||||
{{if $.PageIsWiki}}
|
{{if $.PageIsWiki}}
|
||||||
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji $.Context}}</span>
|
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | ctx.RenderUtils.RenderEmoji}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
|
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
|
||||||
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.Context .Message $commitLink ($.Repository.ComposeMetas ctx)}}</span>
|
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeMetas ctx)}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
{{if IsMultilineCommitMessage .Message}}
|
{{if IsMultilineCommitMessage .Message}}
|
||||||
@ -70,7 +70,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
|
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
|
||||||
{{if IsMultilineCommitMessage .Message}}
|
{{if IsMultilineCommitMessage .Message}}
|
||||||
<pre class="commit-body tw-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
|
<pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeMetas ctx)}}</pre>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if $.CommitsTagsMap}}
|
{{if $.CommitsTagsMap}}
|
||||||
{{range (index $.CommitsTagsMap .ID.String)}}
|
{{range (index $.CommitsTagsMap .ID.String)}}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
{{$commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String)}}
|
{{$commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String)}}
|
||||||
|
|
||||||
<span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{.Summary}}">
|
<span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{.Summary}}">
|
||||||
{{- RenderCommitMessageLinkSubject $.root.Context .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}}
|
{{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{{if IsMultilineCommitMessage .Message}}
|
{{if IsMultilineCommitMessage .Message}}
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if IsMultilineCommitMessage .Message}}
|
{{if IsMultilineCommitMessage .Message}}
|
||||||
<pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}">
|
<pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}">
|
||||||
{{- RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}}
|
{{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}}
|
||||||
</pre>
|
</pre>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
|
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
|
||||||
{{template "repo/issue/view_content/context_menu" dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
|
{{template "repo/issue/view_content/context_menu" dict "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached segment comment-body">
|
<div class="ui attached segment comment-body">
|
||||||
|
@ -187,7 +187,7 @@
|
|||||||
<div class="ui segment flex-text-block tw-gap-4">
|
<div class="ui segment flex-text-block tw-gap-4">
|
||||||
{{template "shared/issueicon" .}}
|
{{template "shared/issueicon" .}}
|
||||||
<div class="issue-title tw-break-anywhere">
|
<div class="issue-title tw-break-anywhere">
|
||||||
{{RenderIssueTitle $.Context .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
|
{{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
|
||||||
<span class="index">#{{.PullRequest.Issue.Index}}</span>
|
<span class="index">#{{.PullRequest.Issue.Index}}</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
|
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="message tw-inline-block gt-ellipsis tw-mr-2">
|
<span class="message tw-inline-block gt-ellipsis tw-mr-2">
|
||||||
<span>{{RenderCommitMessage $.Context $commit.Subject ($.Repository.ComposeMetas ctx)}}</span>
|
<span>{{ctx.RenderUtils.RenderCommitMessage $commit.Subject ($.Repository.ComposeMetas ctx)}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="commit-refs tw-flex tw-items-center tw-mr-1">
|
<span class="commit-refs tw-flex tw-items-center tw-mr-1">
|
||||||
{{range $commit.Refs}}
|
{{range $commit.Refs}}
|
||||||
@ -37,7 +37,7 @@
|
|||||||
{{if eq $refGroup "pull"}}
|
{{if eq $refGroup "pull"}}
|
||||||
{{if or (not $.HidePRRefs) (SliceUtils.Contains $.SelectedBranches .Name)}}
|
{{if or (not $.HidePRRefs) (SliceUtils.Contains $.SelectedBranches .Name)}}
|
||||||
<!-- it's intended to use issues not pulls, if it's a pull you will get redirected -->
|
<!-- it's intended to use issues not pulls, if it's a pull you will get redirected -->
|
||||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/{{if $.Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypePullRequests}}pulls{{else}}issues{{end}}/{{.ShortName|PathEscape}}">
|
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/{{if $.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePullRequests}}pulls{{else}}issues{{end}}/{{.ShortName|PathEscape}}">
|
||||||
{{svg "octicon-git-pull-request"}} #{{.ShortName}}
|
{{svg "octicon-git-pull-request"}} #{{.ShortName}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -56,7 +56,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
<span class="author tw-flex tw-items-center tw-mr-2 tw-gap-[1px]">
|
<span class="author tw-flex tw-items-center tw-mr-2 tw-gap-1">
|
||||||
{{$userName := $commit.Commit.Author.Name}}
|
{{$userName := $commit.Commit.Author.Name}}
|
||||||
{{if $commit.User}}
|
{{if $commit.User}}
|
||||||
{{if and $commit.User.FullName DefaultShowFullName}}
|
{{if and $commit.User.FullName DefaultShowFullName}}
|
||||||
|
@ -177,7 +177,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$projectsUnit := .Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeProjects}}
|
{{$projectsUnit := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeProjects}}
|
||||||
{{if and (not ctx.Consts.RepoUnitTypeProjects.UnitGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
|
{{if and (not ctx.Consts.RepoUnitTypeProjects.UnitGlobalDisabled) (.Permission.CanRead ctx.Consts.RepoUnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
|
||||||
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
|
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
|
||||||
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.projects"}}
|
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.projects"}}
|
||||||
@ -203,7 +203,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalWiki}}
|
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeExternalWiki}}
|
||||||
<a class="item" href="{{(.Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}" target="_blank" rel="noopener noreferrer">
|
<a class="item" href="{{(.Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}" target="_blank" rel="noopener noreferrer">
|
||||||
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.wiki"}}
|
{{svg "octicon-link-external"}} {{ctx.Locale.Tr "repo.wiki"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user