mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-26 21:53:51 +03:00
Merge pull request '[gitea] week 2024-25 cherry pick (gitea/main -> forgejo)' (#4145) from earl-warren/wcp/2024-25 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4145 Reviewed-by: twenty-panda <twenty-panda@noreply.codeberg.org>
This commit is contained in:
commit
328b5d79d3
69 changed files with 1069 additions and 282 deletions
|
@ -87,7 +87,6 @@ code.gitea.io/gitea/models/repo
|
||||||
releaseSorter.Swap
|
releaseSorter.Swap
|
||||||
SortReleases
|
SortReleases
|
||||||
FindReposMapByIDs
|
FindReposMapByIDs
|
||||||
SearchOrderBy.String
|
|
||||||
IsErrTopicNotExist
|
IsErrTopicNotExist
|
||||||
ErrTopicNotExist.Error
|
ErrTopicNotExist.Error
|
||||||
ErrTopicNotExist.Unwrap
|
ErrTopicNotExist.Unwrap
|
||||||
|
|
|
@ -22,6 +22,7 @@ linters:
|
||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
|
- unparam
|
||||||
- wastedassign
|
- wastedassign
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715534503,
|
"lastModified": 1717974879,
|
||||||
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
"narHash": "sha256-GTO3C88+5DX171F/gVS3Qga/hOs/eRMxPFpiHq2t+D8=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
"rev": "c7b821ba2e1e635ba5a76d299af62821cbcb09f3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
go_1_22
|
go_1_22
|
||||||
|
gofumpt
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,6 @@ const (
|
||||||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
||||||
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
||||||
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
||||||
SearchOrderBySize SearchOrderBy = "size ASC"
|
|
||||||
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
|
|
||||||
SearchOrderByGitSize SearchOrderBy = "git_size ASC"
|
|
||||||
SearchOrderByGitSizeReverse SearchOrderBy = "git_size DESC"
|
|
||||||
SearchOrderByLFSSize SearchOrderBy = "lfs_size ASC"
|
|
||||||
SearchOrderByLFSSizeReverse SearchOrderBy = "lfs_size DESC"
|
|
||||||
SearchOrderByID SearchOrderBy = "id ASC"
|
SearchOrderByID SearchOrderBy = "id ASC"
|
||||||
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
||||||
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
||||||
|
|
|
@ -215,16 +215,15 @@ func fileTimestampToTime(timestamp int64) time.Time {
|
||||||
return time.UnixMicro(timestamp)
|
return time.UnixMicro(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) loadMetaByPath() (*dbfsMeta, error) {
|
func (f *file) loadMetaByPath() error {
|
||||||
var fileMeta dbfsMeta
|
var fileMeta dbfsMeta
|
||||||
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {
|
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
f.metaID = fileMeta.ID
|
f.metaID = fileMeta.ID
|
||||||
f.blockSize = fileMeta.BlockSize
|
f.blockSize = fileMeta.BlockSize
|
||||||
return &fileMeta, nil
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) open(flag int) (err error) {
|
func (f *file) open(flag int) (err error) {
|
||||||
|
@ -288,10 +287,7 @@ func (f *file) createEmpty() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = f.loadMetaByPath(); err != nil {
|
return f.loadMetaByPath()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) truncate() error {
|
func (f *file) truncate() error {
|
||||||
|
@ -368,8 +364,5 @@ func buildPath(path string) string {
|
||||||
func newDbFile(ctx context.Context, path string) (*file, error) {
|
func newDbFile(ctx context.Context, path string) (*file, error) {
|
||||||
path = buildPath(path)
|
path = buildPath(path)
|
||||||
f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize}
|
f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize}
|
||||||
if _, err := f.loadMetaByPath(); err != nil {
|
return f, f.loadMetaByPath()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,19 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
|
||||||
return tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProtectedTagByNamePattern gets protected tag by name_pattern
|
||||||
|
func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern string) (*ProtectedTag, error) {
|
||||||
|
tag := &ProtectedTag{NamePattern: pattern, RepoID: repoID}
|
||||||
|
has, err := db.GetEngine(ctx).Get(tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
||||||
// It returns true if the tag name is not protected or the user is allowed to control it.
|
// It returns true if the tag name is not protected or the user is allowed to control it.
|
||||||
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
||||||
|
|
|
@ -99,9 +99,9 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyLimit(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if opts.Paginator == nil || opts.Paginator.IsListAll() {
|
if opts.Paginator == nil || opts.Paginator.IsListAll() {
|
||||||
return sess
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
start := 0
|
start := 0
|
||||||
|
@ -109,11 +109,9 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||||
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
|
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
|
||||||
}
|
}
|
||||||
sess.Limit(opts.Paginator.PageSize, start)
|
sess.Limit(opts.Paginator.PageSize, start)
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.LabelIDs) > 0 {
|
if len(opts.LabelIDs) > 0 {
|
||||||
if opts.LabelIDs[0] == 0 {
|
if opts.LabelIDs[0] == 0 {
|
||||||
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
||||||
|
@ -136,11 +134,9 @@ func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session
|
||||||
if len(opts.ExcludedLabelNames) > 0 {
|
if len(opts.ExcludedLabelNames) > 0 {
|
||||||
sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames)))
|
sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID {
|
if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID {
|
||||||
sess.And("issue.milestone_id = 0")
|
sess.And("issue.milestone_id = 0")
|
||||||
} else if len(opts.MilestoneIDs) > 0 {
|
} else if len(opts.MilestoneIDs) > 0 {
|
||||||
|
@ -153,11 +149,9 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sess
|
||||||
From("milestone").
|
From("milestone").
|
||||||
Where(builder.In("name", opts.IncludeMilestones)))
|
Where(builder.In("name", opts.IncludeMilestones)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if opts.ProjectID > 0 { // specific project
|
if opts.ProjectID > 0 { // specific project
|
||||||
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
||||||
And("project_issue.project_id=?", opts.ProjectID)
|
And("project_issue.project_id=?", opts.ProjectID)
|
||||||
|
@ -166,10 +160,9 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sessio
|
||||||
}
|
}
|
||||||
// opts.ProjectID == 0 means all projects,
|
// opts.ProjectID == 0 means all projects,
|
||||||
// do not need to apply any condition
|
// do not need to apply any condition
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
// opts.ProjectColumnID == 0 means all project columns,
|
// opts.ProjectColumnID == 0 means all project columns,
|
||||||
// do not need to apply any condition
|
// do not need to apply any condition
|
||||||
if opts.ProjectColumnID > 0 {
|
if opts.ProjectColumnID > 0 {
|
||||||
|
@ -177,10 +170,9 @@ func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.
|
||||||
} else if opts.ProjectColumnID == db.NoConditionID {
|
} else if opts.ProjectColumnID == db.NoConditionID {
|
||||||
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
|
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
|
||||||
}
|
}
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.RepoIDs) == 1 {
|
if len(opts.RepoIDs) == 1 {
|
||||||
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
|
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
|
||||||
} else if len(opts.RepoIDs) > 1 {
|
} else if len(opts.RepoIDs) > 1 {
|
||||||
|
@ -195,10 +187,9 @@ func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session
|
||||||
if opts.RepoCond != nil {
|
if opts.RepoCond != nil {
|
||||||
sess.And(opts.RepoCond)
|
sess.And(opts.RepoCond)
|
||||||
}
|
}
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.IssueIDs) > 0 {
|
if len(opts.IssueIDs) > 0 {
|
||||||
sess.In("issue.id", opts.IssueIDs)
|
sess.In("issue.id", opts.IssueIDs)
|
||||||
}
|
}
|
||||||
|
@ -261,8 +252,6 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
||||||
if opts.User != nil {
|
if opts.User != nil {
|
||||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access
|
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access
|
||||||
|
@ -339,22 +328,22 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session {
|
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) {
|
||||||
return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||||
And("issue_assignees.assignee_id = ?", assigneeID)
|
And("issue_assignees.assignee_id = ?", assigneeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPosterCondition(sess *xorm.Session, posterID int64) *xorm.Session {
|
func applyPosterCondition(sess *xorm.Session, posterID int64) {
|
||||||
return sess.And("issue.poster_id=?", posterID)
|
sess.And("issue.poster_id=?", posterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) *xorm.Session {
|
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) {
|
||||||
return sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
|
sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
|
||||||
And("issue_user.is_mentioned = ?", true).
|
And("issue_user.is_mentioned = ?", true).
|
||||||
And("issue_user.uid = ?", mentionedID)
|
And("issue_user.uid = ?", mentionedID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) *xorm.Session {
|
func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) {
|
||||||
existInTeamQuery := builder.Select("team_user.team_id").
|
existInTeamQuery := builder.Select("team_user.team_id").
|
||||||
From("team_user").
|
From("team_user").
|
||||||
Where(builder.Eq{"team_user.uid": reviewRequestedID})
|
Where(builder.Eq{"team_user.uid": reviewRequestedID})
|
||||||
|
@ -375,11 +364,11 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
|
||||||
),
|
),
|
||||||
builder.In("review.id", maxReview),
|
builder.In("review.id", maxReview),
|
||||||
))
|
))
|
||||||
return sess.Where("issue.poster_id <> ?", reviewRequestedID).
|
sess.Where("issue.poster_id <> ?", reviewRequestedID).
|
||||||
And(builder.In("issue.id", subQuery))
|
And(builder.In("issue.id", subQuery))
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session {
|
func applyReviewedCondition(sess *xorm.Session, reviewedID int64) {
|
||||||
// Query for pull requests where you are a reviewer or commenter, excluding
|
// Query for pull requests where you are a reviewer or commenter, excluding
|
||||||
// any pull requests already returned by the review requested filter.
|
// any pull requests already returned by the review requested filter.
|
||||||
notPoster := builder.Neq{"issue.poster_id": reviewedID}
|
notPoster := builder.Neq{"issue.poster_id": reviewedID}
|
||||||
|
@ -406,11 +395,11 @@ func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session
|
||||||
builder.In("type", CommentTypeComment, CommentTypeCode, CommentTypeReview),
|
builder.In("type", CommentTypeComment, CommentTypeCode, CommentTypeReview),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
return sess.And(notPoster, builder.Or(reviewed, commented))
|
sess.And(notPoster, builder.Or(reviewed, commented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
|
func applySubscribedCondition(sess *xorm.Session, subscriberID int64) {
|
||||||
return sess.And(
|
sess.And(
|
||||||
builder.
|
builder.
|
||||||
NotIn("issue.id",
|
NotIn("issue.id",
|
||||||
builder.Select("issue_id").
|
builder.Select("issue_id").
|
||||||
|
|
|
@ -28,7 +28,7 @@ type PullRequestsOptions struct {
|
||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) {
|
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
||||||
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
|
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
|
||||||
|
|
||||||
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
|
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
|
||||||
|
@ -46,7 +46,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
||||||
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess, nil
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUnmergedPullRequestsByHeadInfoMax(ctx context.Context, repoID, olderThan int64, branch string) ([]*PullRequest, error) {
|
func GetUnmergedPullRequestsByHeadInfoMax(ctx context.Context, repoID, olderThan int64, branch string) ([]*PullRequest, error) {
|
||||||
|
@ -136,23 +136,15 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
|
||||||
opts.Page = 1
|
opts.Page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
countSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
|
countSession := listPullRequestStatement(ctx, baseRepoID, opts)
|
||||||
if err != nil {
|
|
||||||
log.Error("listPullRequestStatement: %v", err)
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
maxResults, err := countSession.Count(new(PullRequest))
|
maxResults, err := countSession.Count(new(PullRequest))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Count PRs: %v", err)
|
log.Error("Count PRs: %v", err)
|
||||||
return nil, maxResults, err
|
return nil, maxResults, err
|
||||||
}
|
}
|
||||||
|
|
||||||
findSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
|
findSession := listPullRequestStatement(ctx, baseRepoID, opts)
|
||||||
applySorts(findSession, opts.SortType, 0)
|
applySorts(findSession, opts.SortType, 0)
|
||||||
if err != nil {
|
|
||||||
log.Error("listPullRequestStatement: %v", err)
|
|
||||||
return nil, maxResults, err
|
|
||||||
}
|
|
||||||
findSession = db.SetSessionPagination(findSession, opts)
|
findSession = db.SetSessionPagination(findSession, opts)
|
||||||
prs := make([]*PullRequest, 0, opts.PageSize)
|
prs := make([]*PullRequest, 0, opts.PageSize)
|
||||||
return prs, maxResults, findSession.Find(&prs)
|
return prs, maxResults, findSession.Find(&prs)
|
||||||
|
@ -206,8 +198,10 @@ func (prs PullRequestList) LoadIssues(ctx context.Context) (IssueList, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load issues.
|
// Load issues which are not loaded
|
||||||
issueIDs := prs.GetIssueIDs()
|
issueIDs := container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
||||||
|
return pr.IssueID, pr.Issue == nil && pr.IssueID > 0
|
||||||
|
})
|
||||||
issues := make(map[int64]*Issue, len(issueIDs))
|
issues := make(map[int64]*Issue, len(issueIDs))
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
In("id", issueIDs).
|
In("id", issueIDs).
|
||||||
|
@ -243,10 +237,7 @@ func (prs PullRequestList) LoadIssues(ctx context.Context) (IssueList, error) {
|
||||||
// GetIssueIDs returns all issue ids
|
// GetIssueIDs returns all issue ids
|
||||||
func (prs PullRequestList) GetIssueIDs() []int64 {
|
func (prs PullRequestList) GetIssueIDs() []int64 {
|
||||||
return container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
return container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
||||||
if pr.Issue == nil {
|
return pr.IssueID, pr.IssueID > 0
|
||||||
return pr.IssueID, pr.IssueID > 0
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,31 +205,6 @@ type SearchRepoOptions struct {
|
||||||
OnlyShowRelevant bool
|
OnlyShowRelevant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrderBy is used to sort the result
|
|
||||||
type SearchOrderBy string
|
|
||||||
|
|
||||||
func (s SearchOrderBy) String() string {
|
|
||||||
return string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strings for sorting result
|
|
||||||
const (
|
|
||||||
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
|
|
||||||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
|
|
||||||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
|
|
||||||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
|
||||||
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
|
||||||
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
|
||||||
SearchOrderBySize SearchOrderBy = "size ASC"
|
|
||||||
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
|
|
||||||
SearchOrderByID SearchOrderBy = "id ASC"
|
|
||||||
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
|
||||||
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
|
||||||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
|
|
||||||
SearchOrderByForks SearchOrderBy = "num_forks ASC"
|
|
||||||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserOwnedRepoCond returns user ownered repositories
|
// UserOwnedRepoCond returns user ownered repositories
|
||||||
func UserOwnedRepoCond(userID int64) builder.Cond {
|
func UserOwnedRepoCond(userID int64) builder.Cond {
|
||||||
return builder.Eq{
|
return builder.Eq{
|
||||||
|
|
|
@ -5,20 +5,48 @@ package repo
|
||||||
|
|
||||||
import "code.gitea.io/gitea/models/db"
|
import "code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
// SearchOrderByMap represents all possible search order
|
// OrderByMap represents all possible search order
|
||||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
"asc": {
|
"asc": {
|
||||||
"alpha": "owner_name ASC, name ASC",
|
"alpha": "owner_name ASC, name ASC",
|
||||||
"created": db.SearchOrderByOldest,
|
"created": db.SearchOrderByOldest,
|
||||||
"updated": db.SearchOrderByLeastUpdated,
|
"updated": db.SearchOrderByLeastUpdated,
|
||||||
"size": db.SearchOrderBySize,
|
"size": "size ASC",
|
||||||
"id": db.SearchOrderByID,
|
"git_size": "git_size ASC",
|
||||||
|
"lfs_size": "lfs_size ASC",
|
||||||
|
"id": db.SearchOrderByID,
|
||||||
|
"stars": db.SearchOrderByStars,
|
||||||
|
"forks": db.SearchOrderByForks,
|
||||||
},
|
},
|
||||||
"desc": {
|
"desc": {
|
||||||
"alpha": "owner_name DESC, name DESC",
|
"alpha": "owner_name DESC, name DESC",
|
||||||
"created": db.SearchOrderByNewest,
|
"created": db.SearchOrderByNewest,
|
||||||
"updated": db.SearchOrderByRecentUpdated,
|
"updated": db.SearchOrderByRecentUpdated,
|
||||||
"size": db.SearchOrderBySizeReverse,
|
"size": "size DESC",
|
||||||
"id": db.SearchOrderByIDReverse,
|
"git_size": "git_size DESC",
|
||||||
|
"lfs_size": "lfs_size DESC",
|
||||||
|
"id": db.SearchOrderByIDReverse,
|
||||||
|
"stars": db.SearchOrderByStarsReverse,
|
||||||
|
"forks": db.SearchOrderByForksReverse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrderByFlatMap is similar to OrderByMap but use human language keywords
|
||||||
|
// to decide between asc and desc
|
||||||
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
|
"newest": OrderByMap["desc"]["created"],
|
||||||
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
|
"reversesize": OrderByMap["desc"]["size"],
|
||||||
|
"size": OrderByMap["asc"]["size"],
|
||||||
|
"reversegitsize": OrderByMap["desc"]["git_size"],
|
||||||
|
"gitsize": OrderByMap["asc"]["git_size"],
|
||||||
|
"reverselfssize": OrderByMap["desc"]["lfs_size"],
|
||||||
|
"lfssize": OrderByMap["asc"]["lfs_size"],
|
||||||
|
"moststars": OrderByMap["desc"]["stars"],
|
||||||
|
"feweststars": OrderByMap["asc"]["stars"],
|
||||||
|
"mostforks": OrderByMap["desc"]["forks"],
|
||||||
|
"fewestforks": OrderByMap["asc"]["forks"],
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func parseIntParam(value, param, algorithmName, config string, previousErr error
|
||||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) {
|
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam
|
||||||
parsed, err := strconv.ParseUint(value, 10, 64)
|
parsed, err := strconv.ParseUint(value, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
||||||
|
|
|
@ -42,20 +42,19 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadGitVersion returns current Git version from shell. Internal usage only.
|
// loadGitVersion returns current Git version from shell. Internal usage only.
|
||||||
func loadGitVersion() (*version.Version, error) {
|
func loadGitVersion() error {
|
||||||
// doesn't need RWMutex because it's executed by Init()
|
// doesn't need RWMutex because it's executed by Init()
|
||||||
if gitVersion != nil {
|
if gitVersion != nil {
|
||||||
return gitVersion, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return nil, runErr
|
return runErr
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Fields(stdout)
|
fields := strings.Fields(stdout)
|
||||||
if len(fields) < 3 {
|
if len(fields) < 3 {
|
||||||
return nil, fmt.Errorf("invalid git version output: %s", stdout)
|
return fmt.Errorf("invalid git version output: %s", stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionString string
|
var versionString string
|
||||||
|
@ -70,7 +69,7 @@ func loadGitVersion() (*version.Version, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
gitVersion, err = version.NewVersion(versionString)
|
gitVersion, err = version.NewVersion(versionString)
|
||||||
return gitVersion, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
||||||
|
@ -85,7 +84,7 @@ func SetExecutablePath(path string) error {
|
||||||
}
|
}
|
||||||
GitExecutable = absPath
|
GitExecutable = absPath
|
||||||
|
|
||||||
_, err = loadGitVersion()
|
err = loadGitVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to load git version: %w", err)
|
return fmt.Errorf("unable to load git version: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -312,7 +311,7 @@ func syncGitConfig() (err error) {
|
||||||
|
|
||||||
// CheckGitVersionAtLeast check git version is at least the constraint version
|
// CheckGitVersionAtLeast check git version is at least the constraint version
|
||||||
func CheckGitVersionAtLeast(atLeast string) error {
|
func CheckGitVersionAtLeast(atLeast string) error {
|
||||||
if _, err := loadGitVersion(); err != nil {
|
if err := loadGitVersion(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
atLeastVersion, err := version.NewVersion(atLeast)
|
atLeastVersion, err := version.NewVersion(atLeast)
|
||||||
|
@ -327,7 +326,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
|
||||||
|
|
||||||
// CheckGitVersionEqual checks if the git version is equal to the constraint version.
|
// CheckGitVersionEqual checks if the git version is equal to the constraint version.
|
||||||
func CheckGitVersionEqual(equal string) error {
|
func CheckGitVersionEqual(equal string) error {
|
||||||
if _, err := loadGitVersion(); err != nil {
|
if err := loadGitVersion(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
atLeastVersion, err := version.NewVersion(equal)
|
atLeastVersion, err := version.NewVersion(equal)
|
||||||
|
|
|
@ -34,13 +34,13 @@ type ObjectFormat interface {
|
||||||
ComputeHash(t ObjectType, content []byte) ObjectID
|
ComputeHash(t ObjectType, content []byte) ObjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeHash(dst []byte, hasher hash.Hash, t ObjectType, content []byte) []byte {
|
func computeHash(dst []byte, hasher hash.Hash, t ObjectType, content []byte) {
|
||||||
_, _ = hasher.Write(t.Bytes())
|
_, _ = hasher.Write(t.Bytes())
|
||||||
_, _ = hasher.Write([]byte(" "))
|
_, _ = hasher.Write([]byte(" "))
|
||||||
_, _ = hasher.Write([]byte(strconv.Itoa(len(content))))
|
_, _ = hasher.Write([]byte(strconv.Itoa(len(content))))
|
||||||
_, _ = hasher.Write([]byte{0})
|
_, _ = hasher.Write([]byte{0})
|
||||||
_, _ = hasher.Write(content)
|
_, _ = hasher.Write(content)
|
||||||
return hasher.Sum(dst)
|
hasher.Sum(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SHA1 Type */
|
/* SHA1 Type */
|
||||||
|
|
|
@ -38,6 +38,12 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
||||||
searchOpt.MilestoneIDs = opts.MilestoneIDs
|
searchOpt.MilestoneIDs = opts.MilestoneIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.ProjectID > 0 {
|
||||||
|
searchOpt.ProjectID = optional.Some(opts.ProjectID)
|
||||||
|
} else if opts.ProjectID == -1 { // FIXME: this is inconsistent from other places
|
||||||
|
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
|
||||||
|
}
|
||||||
|
|
||||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||||
convertID := func(id int64) optional.Option[int64] {
|
convertID := func(id int64) optional.Option[int64] {
|
||||||
if id > 0 {
|
if id > 0 {
|
||||||
|
@ -49,7 +55,6 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
searchOpt.ProjectID = convertID(opts.ProjectID)
|
|
||||||
searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
|
searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
|
||||||
searchOpt.PosterID = convertID(opts.PosterID)
|
searchOpt.PosterID = convertID(opts.PosterID)
|
||||||
searchOpt.AssigneeID = convertID(opts.AssigneeID)
|
searchOpt.AssigneeID = convertID(opts.AssigneeID)
|
||||||
|
|
|
@ -211,7 +211,7 @@ func createRequest(ctx context.Context, method, url string, headers map[string]s
|
||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
req.Header.Set(key, value)
|
req.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", MediaType)
|
req.Header.Set("Accept", AcceptHeader)
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
@ -251,6 +251,6 @@ func handleErrorResponse(resp *http.Response) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("ErrorResponse: %v", er)
|
log.Trace("ErrorResponse(%v): %v", resp.Status, er)
|
||||||
return errors.New(er.Message)
|
return errors.New(er.Message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ func TestHTTPClientDownload(t *testing.T) {
|
||||||
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
|
|
||||||
var batchRequest BatchRequest
|
var batchRequest BatchRequest
|
||||||
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
||||||
|
@ -263,7 +263,7 @@ func TestHTTPClientUpload(t *testing.T) {
|
||||||
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
|
|
||||||
var batchRequest BatchRequest
|
var batchRequest BatchRequest
|
||||||
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
const (
|
const (
|
||||||
// MediaType contains the media type for LFS server requests
|
// MediaType contains the media type for LFS server requests
|
||||||
MediaType = "application/vnd.git-lfs+json"
|
MediaType = "application/vnd.git-lfs+json"
|
||||||
|
// Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
|
||||||
|
AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BatchRequest contains multiple requests processed in one batch operation.
|
// BatchRequest contains multiple requests processed in one batch operation.
|
||||||
|
|
|
@ -37,6 +37,7 @@ func (a *BasicTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCl
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug("Download Request: %+v", req)
|
||||||
resp, err := performRequest(ctx, a.client, req)
|
resp, err := performRequest(ctx, a.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestBasicTransferAdapter(t *testing.T) {
|
||||||
p := Pointer{Oid: "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259", Size: 5}
|
p := Pointer{Oid: "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259", Size: 5}
|
||||||
|
|
||||||
roundTripHandler := func(req *http.Request) *http.Response {
|
roundTripHandler := func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
assert.Equal(t, "test-value", req.Header.Get("test-header"))
|
assert.Equal(t, "test-value", req.Header.Get("test-header"))
|
||||||
|
|
||||||
url := req.URL.String()
|
url := req.URL.String()
|
||||||
|
|
|
@ -48,7 +48,7 @@ var (
|
||||||
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
||||||
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
|
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
|
||||||
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
|
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
|
||||||
hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,](\s|$))`)
|
hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))`)
|
||||||
|
|
||||||
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
|
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
|
||||||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||||
|
|
|
@ -381,6 +381,7 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
||||||
"(abcdefabcdefabcdefabcdefabcdefabcdefabcd)",
|
"(abcdefabcdefabcdefabcdefabcdefabcdefabcd)",
|
||||||
"[abcdefabcdefabcdefabcdefabcdefabcdefabcd]",
|
"[abcdefabcdefabcdefabcdefabcdefabcdefabcd]",
|
||||||
"abcdefabcdefabcdefabcdefabcdefabcdefabcd.",
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcd.",
|
||||||
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcd:",
|
||||||
}
|
}
|
||||||
falseTestCases := []string{
|
falseTestCases := []string{
|
||||||
"test",
|
"test",
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type prefixedIDs struct {
|
type prefixedIDs struct {
|
||||||
|
@ -36,7 +36,7 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
||||||
if !bytes.HasPrefix(result, []byte("user-content-")) {
|
if !bytes.HasPrefix(result, []byte("user-content-")) {
|
||||||
result = append([]byte("user-content-"), result...)
|
result = append([]byte("user-content-"), result...)
|
||||||
}
|
}
|
||||||
if p.values.Add(util.BytesToReadOnlyString(result)) {
|
if p.values.Add(util.UnsafeBytesToString(result)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
|
@ -49,7 +49,7 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
||||||
|
|
||||||
// Put puts a given element id to the used ids table.
|
// Put puts a given element id to the used ids table.
|
||||||
func (p *prefixedIDs) Put(value []byte) {
|
func (p *prefixedIDs) Put(value []byte) {
|
||||||
p.values.Add(util.BytesToReadOnlyString(value))
|
p.values.Add(util.UnsafeBytesToString(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrefixedIDs() *prefixedIDs {
|
func newPrefixedIDs() *prefixedIDs {
|
||||||
|
|
|
@ -48,7 +48,7 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
|
func (g *ASTTransformer) transformCodeSpan(_ *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
|
||||||
colorContent := v.Text(reader.Source())
|
colorContent := v.Text(reader.Source())
|
||||||
if matchColor(strings.ToLower(string(colorContent))) {
|
if matchColor(strings.ToLower(string(colorContent))) {
|
||||||
v.AppendChild(v, NewColorPreview(colorContent))
|
v.AppendChild(v, NewColorPreview(colorContent))
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
|
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
|
||||||
|
@ -21,11 +21,11 @@ func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Headin
|
||||||
}
|
}
|
||||||
txt := v.Text(reader.Source())
|
txt := v.Text(reader.Source())
|
||||||
header := markup.Header{
|
header := markup.Header{
|
||||||
Text: util.BytesToReadOnlyString(txt),
|
Text: util.UnsafeBytesToString(txt),
|
||||||
Level: v.Level,
|
Level: v.Level,
|
||||||
}
|
}
|
||||||
if id, found := v.AttributeString("id"); found {
|
if id, found := v.AttributeString("id"); found {
|
||||||
header.ID = util.BytesToReadOnlyString(id.([]byte))
|
header.ID = util.UnsafeBytesToString(id.([]byte))
|
||||||
}
|
}
|
||||||
*tocList = append(*tocList, header)
|
*tocList = append(*tocList, header)
|
||||||
g.applyElementDir(v)
|
g.applyElementDir(v)
|
||||||
|
|
|
@ -6,6 +6,7 @@ package composer
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -36,10 +37,14 @@ type Package struct {
|
||||||
Metadata *Metadata
|
Metadata *Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://getcomposer.org/doc/04-schema.md
|
||||||
|
|
||||||
// Metadata represents the metadata of a Composer package
|
// Metadata represents the metadata of a Composer package
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
|
Readme string `json:"readme,omitempty"`
|
||||||
Keywords []string `json:"keywords,omitempty"`
|
Keywords []string `json:"keywords,omitempty"`
|
||||||
|
Comments Comments `json:"_comments,omitempty"`
|
||||||
Homepage string `json:"homepage,omitempty"`
|
Homepage string `json:"homepage,omitempty"`
|
||||||
License Licenses `json:"license,omitempty"`
|
License Licenses `json:"license,omitempty"`
|
||||||
Authors []Author `json:"authors,omitempty"`
|
Authors []Author `json:"authors,omitempty"`
|
||||||
|
@ -74,6 +79,28 @@ func (l *Licenses) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comments represents the comments of a Composer package
|
||||||
|
type Comments []string
|
||||||
|
|
||||||
|
// UnmarshalJSON reads from a string or array
|
||||||
|
func (c *Comments) UnmarshalJSON(data []byte) error {
|
||||||
|
switch data[0] {
|
||||||
|
case '"':
|
||||||
|
var value string
|
||||||
|
if err := json.Unmarshal(data, &value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = Comments{value}
|
||||||
|
case '[':
|
||||||
|
values := make([]string, 0, 5)
|
||||||
|
if err := json.Unmarshal(data, &values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = Comments(values)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Author represents an author
|
// Author represents an author
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
@ -101,14 +128,14 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return ParseComposerFile(f)
|
return ParseComposerFile(archive, path.Dir(file.Name), f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ErrMissingComposerFile
|
return nil, ErrMissingComposerFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
|
// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
|
||||||
func ParseComposerFile(r io.Reader) (*Package, error) {
|
func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Package, error) {
|
||||||
var cj struct {
|
var cj struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
@ -137,6 +164,19 @@ func ParseComposerFile(r io.Reader) (*Package, error) {
|
||||||
cj.Type = "library"
|
cj.Type = "library"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cj.Readme == "" {
|
||||||
|
cj.Readme = "README.md"
|
||||||
|
}
|
||||||
|
f, err := archive.Open(path.Join(pathPrefix, cj.Readme))
|
||||||
|
if err == nil {
|
||||||
|
// 10kb limit for readme content
|
||||||
|
buf, _ := io.ReadAll(io.LimitReader(f, 10*1024))
|
||||||
|
cj.Readme = string(buf)
|
||||||
|
_ = f.Close()
|
||||||
|
} else {
|
||||||
|
cj.Readme = ""
|
||||||
|
}
|
||||||
|
|
||||||
return &Package{
|
return &Package{
|
||||||
Name: cj.Name,
|
Name: cj.Name,
|
||||||
Version: cj.Version,
|
Version: cj.Version,
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
const (
|
const (
|
||||||
name = "gitea/composer-package"
|
name = "gitea/composer-package"
|
||||||
description = "Package Description"
|
description = "Package Description"
|
||||||
|
readme = "Package Readme"
|
||||||
|
comments = "Package Comment"
|
||||||
packageType = "composer-plugin"
|
packageType = "composer-plugin"
|
||||||
author = "Gitea Authors"
|
author = "Gitea Authors"
|
||||||
email = "no.reply@gitea.io"
|
email = "no.reply@gitea.io"
|
||||||
|
@ -41,7 +43,8 @@ const composerContent = `{
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2 || ^8.0"
|
"php": ">=7.2 || ^8.0"
|
||||||
}
|
},
|
||||||
|
"_comments": "` + comments + `"
|
||||||
}`
|
}`
|
||||||
|
|
||||||
func TestLicenseUnmarshal(t *testing.T) {
|
func TestLicenseUnmarshal(t *testing.T) {
|
||||||
|
@ -54,18 +57,30 @@ func TestLicenseUnmarshal(t *testing.T) {
|
||||||
assert.Equal(t, "MIT", l[0])
|
assert.Equal(t, "MIT", l[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommentsUnmarshal(t *testing.T) {
|
||||||
|
var c Comments
|
||||||
|
assert.NoError(t, json.NewDecoder(strings.NewReader(`["comment"]`)).Decode(&c))
|
||||||
|
assert.Len(t, c, 1)
|
||||||
|
assert.Equal(t, "comment", c[0])
|
||||||
|
assert.NoError(t, json.NewDecoder(strings.NewReader(`"comment"`)).Decode(&c))
|
||||||
|
assert.Len(t, c, 1)
|
||||||
|
assert.Equal(t, "comment", c[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePackage(t *testing.T) {
|
func TestParsePackage(t *testing.T) {
|
||||||
createArchive := func(name, content string) []byte {
|
createArchive := func(files map[string]string) []byte {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
archive := zip.NewWriter(&buf)
|
archive := zip.NewWriter(&buf)
|
||||||
w, _ := archive.Create(name)
|
for name, content := range files {
|
||||||
w.Write([]byte(content))
|
w, _ := archive.Create(name)
|
||||||
|
w.Write([]byte(content))
|
||||||
|
}
|
||||||
archive.Close()
|
archive.Close()
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("MissingComposerFile", func(t *testing.T) {
|
t.Run("MissingComposerFile", func(t *testing.T) {
|
||||||
data := createArchive("dummy.txt", "")
|
data := createArchive(map[string]string{"dummy.txt": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
|
@ -73,7 +88,7 @@ func TestParsePackage(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("MissingComposerFileInRoot", func(t *testing.T) {
|
t.Run("MissingComposerFileInRoot", func(t *testing.T) {
|
||||||
data := createArchive("sub/sub/composer.json", "")
|
data := createArchive(map[string]string{"sub/sub/composer.json": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
|
@ -81,43 +96,52 @@ func TestParsePackage(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidComposerFile", func(t *testing.T) {
|
t.Run("InvalidComposerFile", func(t *testing.T) {
|
||||||
data := createArchive("composer.json", "")
|
data := createArchive(map[string]string{"composer.json": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Valid", func(t *testing.T) {
|
t.Run("InvalidPackageName", func(t *testing.T) {
|
||||||
data := createArchive("composer.json", composerContent)
|
data := createArchive(map[string]string{"composer.json": "{}"})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, cp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseComposerFile(t *testing.T) {
|
|
||||||
t.Run("InvalidPackageName", func(t *testing.T) {
|
|
||||||
cp, err := ParseComposerFile(strings.NewReader(`{}`))
|
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.ErrorIs(t, err, ErrInvalidName)
|
assert.ErrorIs(t, err, ErrInvalidName)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidPackageVersion", func(t *testing.T) {
|
t.Run("InvalidPackageVersion", func(t *testing.T) {
|
||||||
cp, err := ParseComposerFile(strings.NewReader(`{"name": "gitea/composer-package", "version": "1.a.3"}`))
|
data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "version": "1.a.3"}`})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidReadmePath", func(t *testing.T) {
|
||||||
|
data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "readme": "sub/README.md"}`})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cp)
|
||||||
|
|
||||||
|
assert.Empty(t, cp.Metadata.Readme)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Valid", func(t *testing.T) {
|
t.Run("Valid", func(t *testing.T) {
|
||||||
cp, err := ParseComposerFile(strings.NewReader(composerContent))
|
data := createArchive(map[string]string{"composer.json": composerContent, "README.md": readme})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, cp)
|
assert.NotNil(t, cp)
|
||||||
|
|
||||||
assert.Equal(t, name, cp.Name)
|
assert.Equal(t, name, cp.Name)
|
||||||
assert.Empty(t, cp.Version)
|
assert.Empty(t, cp.Version)
|
||||||
assert.Equal(t, description, cp.Metadata.Description)
|
assert.Equal(t, description, cp.Metadata.Description)
|
||||||
|
assert.Equal(t, readme, cp.Metadata.Readme)
|
||||||
|
assert.Len(t, cp.Metadata.Comments, 1)
|
||||||
|
assert.Equal(t, comments, cp.Metadata.Comments[0])
|
||||||
assert.Len(t, cp.Metadata.Authors, 1)
|
assert.Len(t, cp.Metadata.Authors, 1)
|
||||||
assert.Equal(t, author, cp.Metadata.Authors[0].Name)
|
assert.Equal(t, author, cp.Metadata.Authors[0].Name)
|
||||||
assert.Equal(t, email, cp.Metadata.Authors[0].Email)
|
assert.Equal(t, email, cp.Metadata.Authors[0].Email)
|
||||||
|
|
|
@ -185,8 +185,6 @@ func ParseDescription(r io.Reader) (*Package, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setField(p *Package, data string) error {
|
func setField(p *Package, data string) error {
|
||||||
const listDelimiter = ", "
|
|
||||||
|
|
||||||
if data == "" {
|
if data == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -215,19 +213,19 @@ func setField(p *Package, data string) error {
|
||||||
case "Description":
|
case "Description":
|
||||||
p.Metadata.Description = value
|
p.Metadata.Description = value
|
||||||
case "URL":
|
case "URL":
|
||||||
p.Metadata.ProjectURL = splitAndTrim(value, listDelimiter)
|
p.Metadata.ProjectURL = splitAndTrim(value)
|
||||||
case "License":
|
case "License":
|
||||||
p.Metadata.License = value
|
p.Metadata.License = value
|
||||||
case "Author":
|
case "Author":
|
||||||
p.Metadata.Authors = splitAndTrim(authorReplacePattern.ReplaceAllString(value, ""), listDelimiter)
|
p.Metadata.Authors = splitAndTrim(authorReplacePattern.ReplaceAllString(value, ""))
|
||||||
case "Depends":
|
case "Depends":
|
||||||
p.Metadata.Depends = splitAndTrim(value, listDelimiter)
|
p.Metadata.Depends = splitAndTrim(value)
|
||||||
case "Imports":
|
case "Imports":
|
||||||
p.Metadata.Imports = splitAndTrim(value, listDelimiter)
|
p.Metadata.Imports = splitAndTrim(value)
|
||||||
case "Suggests":
|
case "Suggests":
|
||||||
p.Metadata.Suggests = splitAndTrim(value, listDelimiter)
|
p.Metadata.Suggests = splitAndTrim(value)
|
||||||
case "LinkingTo":
|
case "LinkingTo":
|
||||||
p.Metadata.LinkingTo = splitAndTrim(value, listDelimiter)
|
p.Metadata.LinkingTo = splitAndTrim(value)
|
||||||
case "NeedsCompilation":
|
case "NeedsCompilation":
|
||||||
p.Metadata.NeedsCompilation = value == "yes"
|
p.Metadata.NeedsCompilation = value == "yes"
|
||||||
}
|
}
|
||||||
|
@ -235,8 +233,8 @@ func setField(p *Package, data string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitAndTrim(s, sep string) []string {
|
func splitAndTrim(s string) []string {
|
||||||
items := strings.Split(s, sep)
|
items := strings.Split(s, ", ")
|
||||||
for i := range items {
|
for i := range items {
|
||||||
items[i] = strings.TrimSpace(items[i])
|
items[i] = strings.TrimSpace(items[i])
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/mdstripper"
|
"code.gitea.io/gitea/modules/markup/mdstripper"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -341,7 +340,7 @@ func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r := getCrossReference(util.StringToReadOnlyBytes(content), match[2], match[3], false, prOnly)
|
r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
|
||||||
|
|
||||||
// decodeEnvironmentKey decode the environment key to section and key
|
// decodeEnvironmentKey decode the environment key to section and key
|
||||||
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
|
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
|
||||||
func decodeEnvironmentKey(prefixRegexp *regexp.Regexp, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) {
|
func decodeEnvironmentKey(prefixRegexp *regexp.Regexp, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { //nolint:unparam
|
||||||
if strings.HasSuffix(envKey, suffixFile) {
|
if strings.HasSuffix(envKey, suffixFile) {
|
||||||
useFileValue = true
|
useFileValue = true
|
||||||
envKey = envKey[:len(envKey)-len(suffixFile)]
|
envKey = envKey[:len(envKey)-len(suffixFile)]
|
||||||
|
|
|
@ -122,7 +122,7 @@ const (
|
||||||
targetSecIsSec // target section is from the name seciont [name]
|
targetSecIsSec // target section is from the name seciont [name]
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) {
|
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) { //nolint:unparam
|
||||||
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
|
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsValidStorageType(StorageType(typ)) {
|
if !IsValidStorageType(StorageType(typ)) {
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// Tag represents a repository tag
|
// Tag represents a repository tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -46,3 +48,29 @@ type TagArchiveDownloadCount struct {
|
||||||
Zip int64 `json:"zip"`
|
Zip int64 `json:"zip"`
|
||||||
TarGz int64 `json:"tar_gz"`
|
TarGz int64 `json:"tar_gz"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtection represents a tag protection
|
||||||
|
type TagProtection struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Created time.Time `json:"created_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtectionOption options for creating a tag protection
|
||||||
|
type CreateTagProtectionOption struct {
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtectionOption options for editing a tag protection
|
||||||
|
type EditTagProtectionOption struct {
|
||||||
|
NamePattern *string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/system"
|
"code.gitea.io/gitea/models/system"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBStore can be used to store app state items in local filesystem
|
// DBStore can be used to store app state items in local filesystem
|
||||||
|
@ -24,7 +23,7 @@ func (f *DBStore) Get(ctx context.Context, item StateItem) error {
|
||||||
if content == "" {
|
if content == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(util.StringToReadOnlyBytes(content), item)
|
return json.Unmarshal(util.UnsafeStringToBytes(content), item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set saves the state item
|
// Set saves the state item
|
||||||
|
@ -33,5 +32,5 @@ func (f *DBStore) Set(ctx context.Context, item StateItem) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return system.SaveAppStateContent(ctx, item.Name(), util.BytesToReadOnlyString(b))
|
return system.SaveAppStateContent(ctx, item.Name(), util.UnsafeBytesToString(b))
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,7 @@ import (
|
||||||
// GenerateKeyPair generates a public and private keypair
|
// GenerateKeyPair generates a public and private keypair
|
||||||
func GenerateKeyPair(bits int) (string, string, error) {
|
func GenerateKeyPair(bits int) (string, string, error) {
|
||||||
priv, _ := rsa.GenerateKey(rand.Reader, bits)
|
priv, _ := rsa.GenerateKey(rand.Reader, bits)
|
||||||
privPem, err := pemBlockForPriv(priv)
|
privPem := pemBlockForPriv(priv)
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
pubPem, err := pemBlockForPub(&priv.PublicKey)
|
pubPem, err := pemBlockForPub(&priv.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
|
@ -26,12 +23,12 @@ func GenerateKeyPair(bits int) (string, string, error) {
|
||||||
return privPem, pubPem, nil
|
return privPem, pubPem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pemBlockForPriv(priv *rsa.PrivateKey) (string, error) {
|
func pemBlockForPriv(priv *rsa.PrivateKey) string {
|
||||||
privBytes := pem.EncodeToMemory(&pem.Block{
|
privBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
Type: "RSA PRIVATE KEY",
|
Type: "RSA PRIVATE KEY",
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||||
})
|
})
|
||||||
return string(privBytes), nil
|
return string(privBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
|
func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
|
||||||
|
|
|
@ -6,8 +6,6 @@ package util
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type sanitizedError struct {
|
type sanitizedError struct {
|
||||||
|
@ -33,7 +31,7 @@ var schemeSep = []byte("://")
|
||||||
|
|
||||||
// SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://") for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
|
// SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://") for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
|
||||||
func SanitizeCredentialURLs(s string) string {
|
func SanitizeCredentialURLs(s string) string {
|
||||||
bs := util.StringToReadOnlyBytes(s)
|
bs := UnsafeStringToBytes(s)
|
||||||
schemeSepPos := bytes.Index(bs, schemeSep)
|
schemeSepPos := bytes.Index(bs, schemeSep)
|
||||||
if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
|
if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
|
||||||
return s // fast return if there is no URL scheme or no userinfo
|
return s // fast return if there is no URL scheme or no userinfo
|
||||||
|
@ -70,5 +68,5 @@ func SanitizeCredentialURLs(s string) string {
|
||||||
schemeSepPos = bytes.Index(bs, schemeSep)
|
schemeSepPos = bytes.Index(bs, schemeSep)
|
||||||
}
|
}
|
||||||
out = append(out, bs...)
|
out = append(out, bs...)
|
||||||
return util.BytesToReadOnlyString(out)
|
return UnsafeBytesToString(out)
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,11 +87,11 @@ func ToSnakeCase(input string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
|
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
|
||||||
// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach
|
|
||||||
func UnsafeBytesToString(b []byte) string {
|
func UnsafeBytesToString(b []byte) string {
|
||||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsafeStringToBytes uses Go's unsafe package to convert a string to a byte slice.
|
||||||
func UnsafeStringToBytes(s string) []byte {
|
func UnsafeStringToBytes(s string) []byte {
|
||||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ func GetSiteCookie(req *http.Request, name string) string {
|
||||||
|
|
||||||
// SetSiteCookie returns given cookie value from request header.
|
// SetSiteCookie returns given cookie value from request header.
|
||||||
func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
||||||
|
// Previous versions would use a cookie path with a trailing /.
|
||||||
|
// These are more specific than cookies without a trailing /, so
|
||||||
|
// we need to delete these if they exist.
|
||||||
|
deleteLegacySiteCookie(resp, name)
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: url.QueryEscape(value),
|
Value: url.QueryEscape(value),
|
||||||
|
@ -46,10 +50,6 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
||||||
SameSite: setting.SessionConfig.SameSite,
|
SameSite: setting.SessionConfig.SameSite,
|
||||||
}
|
}
|
||||||
resp.Header().Add("Set-Cookie", cookie.String())
|
resp.Header().Add("Set-Cookie", cookie.String())
|
||||||
// Previous versions would use a cookie path with a trailing /.
|
|
||||||
// These are more specific than cookies without a trailing /, so
|
|
||||||
// we need to delete these if they exist.
|
|
||||||
deleteLegacySiteCookie(resp, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
|
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
|
||||||
|
|
3
release-notes/8.0.0/feat/4145.md
Normal file
3
release-notes/8.0.0/feat/4145.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/b60e3ac7b4aeeb9b8760f43eea9576c0e23309e9) allow downloading draft releases assets.
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/1fca15529ac8fefb60d86b0c1f4bec8dae9a8566) API endpoints for managing tag protection
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/4334c705b5f9388b16af23c7e75a69d027d07d5e) extract and display readme and comments for Composer packages
|
2
release-notes/8.0.0/fix/4145.md
Normal file
2
release-notes/8.0.0/fix/4145.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/364922c6e4f28264add9e2501a352c25ad6a0993) when a repository is adopted, its object format is not set in the database
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/e7f332a55d6a48a3f3b4f2bfa43d18455ac00acc) during a migration from bitbucket, LFS downloads fail
|
1
release-notes/8.0.0/perf/4145.md
Normal file
1
release-notes/8.0.0/perf/4145.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- [commit](https://codeberg.org/forgejo/forgejo/commit/358cd67c4f316f2d4f1d3be6dcb891dc04a2ff07) reduce memory usage for chunked artifact uploads to S3
|
|
@ -241,16 +241,12 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get upload file size
|
// get upload file size
|
||||||
fileRealTotalSize, contentLength, err := getUploadFileSize(ctx)
|
fileRealTotalSize, contentLength := getUploadFileSize(ctx)
|
||||||
if err != nil {
|
|
||||||
log.Error("Error get upload file size: %v", err)
|
|
||||||
ctx.Error(http.StatusInternalServerError, "Error get upload file size")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get artifact retention days
|
// get artifact retention days
|
||||||
expiredDays := setting.Actions.ArtifactRetentionDays
|
expiredDays := setting.Actions.ArtifactRetentionDays
|
||||||
if queryRetentionDays := ctx.Req.URL.Query().Get("retentionDays"); queryRetentionDays != "" {
|
if queryRetentionDays := ctx.Req.URL.Query().Get("retentionDays"); queryRetentionDays != "" {
|
||||||
|
var err error
|
||||||
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
|
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error parse retention days: %v", err)
|
log.Error("Error parse retention days: %v", err)
|
||||||
|
|
|
@ -39,7 +39,7 @@ func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext,
|
||||||
r = io.TeeReader(r, hasher)
|
r = io.TeeReader(r, hasher)
|
||||||
}
|
}
|
||||||
// save chunk to storage
|
// save chunk to storage
|
||||||
writtenSize, err := st.Save(storagePath, r, -1)
|
writtenSize, err := st.Save(storagePath, r, contentSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("save chunk to storage error: %v", err)
|
return -1, fmt.Errorf("save chunk to storage error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
|
||||||
|
|
||||||
// save merged file
|
// save merged file
|
||||||
storagePath := fmt.Sprintf("%d/%d/%d.%s", artifact.RunID%255, artifact.ID%255, time.Now().UnixNano(), extension)
|
storagePath := fmt.Sprintf("%d/%d/%d.%s", artifact.RunID%255, artifact.ID%255, time.Now().UnixNano(), extension)
|
||||||
written, err := st.Save(storagePath, mergedReader, -1)
|
written, err := st.Save(storagePath, mergedReader, artifact.FileCompressedSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save merged file error: %v", err)
|
return fmt.Errorf("save merged file error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
|
||||||
return task, runID, true
|
return task, runID, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) {
|
func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam
|
||||||
task := ctx.ActionTask
|
task := ctx.ActionTask
|
||||||
runID, err := strconv.ParseInt(rawRunID, 10, 64)
|
runID, err := strconv.ParseInt(rawRunID, 10, 64)
|
||||||
if err != nil || task.Job.RunID != runID {
|
if err != nil || task.Job.RunID != runID {
|
||||||
|
@ -84,11 +84,11 @@ func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) {
|
||||||
|
|
||||||
// getUploadFileSize returns the size of the file to be uploaded.
|
// getUploadFileSize returns the size of the file to be uploaded.
|
||||||
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
|
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
|
||||||
func getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
|
func getUploadFileSize(ctx *ArtifactContext) (int64, int64) {
|
||||||
contentLength := ctx.Req.ContentLength
|
contentLength := ctx.Req.ContentLength
|
||||||
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
|
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
|
||||||
if xTfsLength > 0 {
|
if xTfsLength > 0 {
|
||||||
return xTfsLength, contentLength, nil
|
return xTfsLength, contentLength
|
||||||
}
|
}
|
||||||
return contentLength, contentLength, nil
|
return contentLength, contentLength
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ var uploadVersionMutex sync.Mutex
|
||||||
|
|
||||||
// saveAsPackageBlob creates a package blob from an upload
|
// saveAsPackageBlob creates a package blob from an upload
|
||||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
|
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
|
||||||
pb := packages_service.NewPackageBlob(hsr)
|
pb := packages_service.NewPackageBlob(hsr)
|
||||||
|
|
||||||
exists := false
|
exists := false
|
||||||
|
|
|
@ -36,7 +36,7 @@ func apiError(ctx *context.Context, status int, obj any) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func xmlResponse(ctx *context.Context, status int, obj any) {
|
func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
|
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
|
||||||
ctx.Resp.WriteHeader(status)
|
ctx.Resp.WriteHeader(status)
|
||||||
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
|
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
|
||||||
|
|
|
@ -1112,6 +1112,15 @@ func Routes() *web.Route {
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
|
m.Group("/tag_protections", func() {
|
||||||
|
m.Combo("").Get(repo.ListTagProtection).
|
||||||
|
Post(bind(api.CreateTagProtectionOption{}), mustNotBeArchived, repo.CreateTagProtection)
|
||||||
|
m.Group("/{id}", func() {
|
||||||
|
m.Combo("").Get(repo.GetTagProtection).
|
||||||
|
Patch(bind(api.EditTagProtectionOption{}), mustNotBeArchived, repo.EditTagProtection).
|
||||||
|
Delete(repo.DeleteTagProtection)
|
||||||
|
})
|
||||||
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
||||||
|
|
|
@ -64,7 +64,7 @@ func CompareDiff(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
|
_, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
|
||||||
Base: infos[0],
|
Base: infos[0],
|
||||||
Head: infos[1],
|
Head: infos[1],
|
||||||
})
|
})
|
||||||
|
|
|
@ -406,7 +406,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get repo/branch information
|
// Get repo/branch information
|
||||||
_, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
|
headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1051,7 +1051,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||||
ctx.Status(http.StatusOK)
|
ctx.Status(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*user_model.User, *repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
|
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
|
||||||
baseRepo := ctx.Repo.Repository
|
baseRepo := ctx.Repo.Repository
|
||||||
|
|
||||||
// Get compared branches information
|
// Get compared branches information
|
||||||
|
@ -1084,14 +1084,14 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
} else {
|
} else {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||||
}
|
}
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
headBranch = headInfos[1]
|
headBranch = headInfos[1]
|
||||||
// The head repository can also point to the same repo
|
// The head repository can also point to the same repo
|
||||||
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
||||||
} else {
|
} else {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
||||||
|
@ -1099,7 +1099,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
// Check if base branch is valid.
|
// Check if base branch is valid.
|
||||||
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
||||||
ctx.NotFound("BaseNotExist")
|
ctx.NotFound("BaseNotExist")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if current user has fork of repository or in the same repository.
|
// Check if current user has fork of repository or in the same repository.
|
||||||
|
@ -1107,7 +1107,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
if headRepo == nil && !isSameRepo {
|
if headRepo == nil && !isSameRepo {
|
||||||
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
||||||
ctx.NotFound("GetForkedRepo")
|
ctx.NotFound("GetForkedRepo")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var headGitRepo *git.Repository
|
var headGitRepo *git.Repository
|
||||||
|
@ -1118,7 +1118,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
|
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,7 +1127,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
||||||
if log.IsTrace() {
|
if log.IsTrace() {
|
||||||
|
@ -1138,7 +1138,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
}
|
}
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// user should have permission to read headrepo's codes
|
// user should have permission to read headrepo's codes
|
||||||
|
@ -1146,7 +1146,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
if !permHead.CanRead(unit.TypeCode) {
|
if !permHead.CanRead(unit.TypeCode) {
|
||||||
if log.IsTrace() {
|
if log.IsTrace() {
|
||||||
|
@ -1157,24 +1157,24 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||||
}
|
}
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound("Can't read headRepo UnitTypeCode")
|
ctx.NotFound("Can't read headRepo UnitTypeCode")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if head branch is valid.
|
// Check if head branch is valid.
|
||||||
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
|
return headRepo, headGitRepo, compareInfo, baseBranch, headBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||||
|
|
|
@ -107,7 +107,7 @@ func Search(ctx *context.APIContext) {
|
||||||
// - name: sort
|
// - name: sort
|
||||||
// in: query
|
// in: query
|
||||||
// description: sort repos by attribute. Supported values are
|
// description: sort repos by attribute. Supported values are
|
||||||
// "alpha", "created", "updated", "size", and "id".
|
// "alpha", "created", "updated", "size", "git_size", "lfs_size", "stars", "forks" and "id".
|
||||||
// Default is "alpha"
|
// Default is "alpha"
|
||||||
// type: string
|
// type: string
|
||||||
// - name: order
|
// - name: order
|
||||||
|
@ -184,7 +184,7 @@ func Search(ctx *context.APIContext) {
|
||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,9 +7,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
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"
|
||||||
|
@ -314,3 +318,349 @@ func DeleteTag(ctx *context.APIContext) {
|
||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTagProtection lists tag protections for a repo
|
||||||
|
func ListTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections repository repoListTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: List tag protections for a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtectionList"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
pts, err := git_model.GetProtectedTags(ctx, repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTags", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiPts := make([]*api.TagProtection, len(pts))
|
||||||
|
for i := range pts {
|
||||||
|
apiPts[i] = convert.ToTagProtection(ctx, pts[i], repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiPts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagProtection gets a tag protection
|
||||||
|
func GetTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections/{id} repository repoGetTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Get a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the tag protect to get
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || repo.ID != pt.RepoID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtection creates a tag protection for a repo
|
||||||
|
func CreateTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/tag_protections repository repoCreateTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Create a tag protections for a repository
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
form := web.GetForm(ctx).(*api.CreateTagProtectionOption)
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
namePattern := strings.TrimSpace(form.NamePattern)
|
||||||
|
if namePattern == "" {
|
||||||
|
ctx.Error(http.StatusBadRequest, "name_pattern are empty", "name_pattern are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(form.WhitelistUsernames) == 0 && len(form.WhitelistTeams) == 0 {
|
||||||
|
ctx.Error(http.StatusBadRequest, "both whitelist_usernames and whitelist_teams are empty", "both whitelist_usernames and whitelist_teams are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err := git_model.GetProtectedTagByNamePattern(ctx, repo.ID, namePattern)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectTagOfRepo", err)
|
||||||
|
return
|
||||||
|
} else if pt != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "Create tag protection", "Tag protection already exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protectTag := &git_model.ProtectedTag{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
NamePattern: strings.TrimSpace(namePattern),
|
||||||
|
AllowlistUserIDs: whitelistUsers,
|
||||||
|
AllowlistTeamIDs: whitelistTeams,
|
||||||
|
}
|
||||||
|
if err := git_model.InsertProtectedTag(ctx, protectTag); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "InsertProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, protectTag.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtection edits a tag protection for a repo
|
||||||
|
func EditTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /repos/{owner}/{repo}/tag_protections/{id} repository repoEditTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Edit a tag protections for a repository. Only fields that are set will be changed
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
form := web.GetForm(ctx).(*api.EditTagProtectionOption)
|
||||||
|
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.NamePattern != nil {
|
||||||
|
pt.NamePattern = *form.NamePattern
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
if form.WhitelistTeams != nil {
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pt.AllowlistTeamIDs = whitelistTeams
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.WhitelistUsernames != nil {
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pt.AllowlistUserIDs = whitelistUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.UpdateProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", "New tag protection not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTagProtection
|
||||||
|
func DeleteTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/tag_protections/{id} repository repoDeleteTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Delete a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.DeleteProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
|
@ -184,6 +184,12 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
CreateTagOption api.CreateTagOption
|
CreateTagOption api.CreateTagOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
CreateTagProtectionOption api.CreateTagProtectionOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
EditTagProtectionOption api.EditTagProtectionOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateAccessTokenOption api.CreateAccessTokenOption
|
CreateAccessTokenOption api.CreateAccessTokenOption
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,20 @@ type swaggerResponseAnnotatedTag struct {
|
||||||
Body api.AnnotatedTag `json:"body"`
|
Body api.AnnotatedTag `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtectionList
|
||||||
|
// swagger:response TagProtectionList
|
||||||
|
type swaggerResponseTagProtectionList struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagProtection
|
||||||
|
// swagger:response TagProtection
|
||||||
|
type swaggerResponseTagProtection struct {
|
||||||
|
// in:body
|
||||||
|
Body api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// Reference
|
// Reference
|
||||||
// swagger:response Reference
|
// swagger:response Reference
|
||||||
type swaggerResponseReference struct {
|
type swaggerResponseReference struct {
|
||||||
|
|
|
@ -434,7 +434,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) {
|
func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) { //nolint:unparam
|
||||||
if !ctx.AssertCanWriteCode() {
|
if !ctx.AssertCanWriteCode() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -470,7 +470,7 @@ func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID string, refF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func preReceiveFor(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) {
|
func preReceiveFor(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) { //nolint:unparam
|
||||||
if !ctx.AssertCreatePullRequest() {
|
if !ctx.AssertCreatePullRequest() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ func ChangeConfig(ctx *context.Context) {
|
||||||
value := ctx.FormString("value")
|
value := ctx.FormString("value")
|
||||||
cfg := setting.Config()
|
cfg := setting.Config()
|
||||||
|
|
||||||
marshalBool := func(v string) (string, error) {
|
marshalBool := func(v string) (string, error) { //nolint:unparam
|
||||||
if b, _ := strconv.ParseBool(v); b {
|
if b, _ := strconv.ParseBool(v); b {
|
||||||
return "true", nil
|
return "true", nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package explore
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -57,47 +58,18 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||||
orderBy db.SearchOrderBy
|
orderBy db.SearchOrderBy
|
||||||
)
|
)
|
||||||
|
|
||||||
sortOrder := ctx.FormString("sort")
|
sortOrder := strings.ToLower(ctx.FormString("sort"))
|
||||||
if sortOrder == "" {
|
if sortOrder == "" {
|
||||||
sortOrder = setting.UI.ExploreDefaultSort
|
sortOrder = setting.UI.ExploreDefaultSort
|
||||||
}
|
}
|
||||||
ctx.Data["SortType"] = sortOrder
|
|
||||||
|
|
||||||
switch sortOrder {
|
if order, ok := repo_model.OrderByFlatMap[sortOrder]; ok {
|
||||||
case "newest":
|
orderBy = order
|
||||||
orderBy = db.SearchOrderByNewest
|
} else {
|
||||||
case "oldest":
|
sortOrder = "recentupdate"
|
||||||
orderBy = db.SearchOrderByOldest
|
|
||||||
case "leastupdate":
|
|
||||||
orderBy = db.SearchOrderByLeastUpdated
|
|
||||||
case "reversealphabetically":
|
|
||||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
|
||||||
case "alphabetically":
|
|
||||||
orderBy = db.SearchOrderByAlphabetically
|
|
||||||
case "reversesize":
|
|
||||||
orderBy = db.SearchOrderBySizeReverse
|
|
||||||
case "size":
|
|
||||||
orderBy = db.SearchOrderBySize
|
|
||||||
case "reversegitsize":
|
|
||||||
orderBy = db.SearchOrderByGitSizeReverse
|
|
||||||
case "gitsize":
|
|
||||||
orderBy = db.SearchOrderByGitSize
|
|
||||||
case "reverselfssize":
|
|
||||||
orderBy = db.SearchOrderByLFSSizeReverse
|
|
||||||
case "lfssize":
|
|
||||||
orderBy = db.SearchOrderByLFSSize
|
|
||||||
case "moststars":
|
|
||||||
orderBy = db.SearchOrderByStarsReverse
|
|
||||||
case "feweststars":
|
|
||||||
orderBy = db.SearchOrderByStars
|
|
||||||
case "mostforks":
|
|
||||||
orderBy = db.SearchOrderByForksReverse
|
|
||||||
case "fewestforks":
|
|
||||||
orderBy = db.SearchOrderByForks
|
|
||||||
default:
|
|
||||||
ctx.Data["SortType"] = "recentupdate"
|
|
||||||
orderBy = db.SearchOrderByRecentUpdated
|
orderBy = db.SearchOrderByRecentUpdated
|
||||||
}
|
}
|
||||||
|
ctx.Data["SortType"] = sortOrder
|
||||||
|
|
||||||
keyword := ctx.FormTrim("q")
|
keyword := ctx.FormTrim("q")
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ func redirectToBadge(ctx *context_module.Context, label, text, color string) {
|
||||||
ctx.Redirect(getBadgeURL(ctx, label, text, color))
|
ctx.Redirect(getBadgeURL(ctx, label, text, color))
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorBadge(ctx *context_module.Context, label, text string) {
|
func errorBadge(ctx *context_module.Context, label, text string) { //nolint:unparam
|
||||||
ctx.Redirect(getBadgeURL(ctx, label, text, "crimson"))
|
ctx.Redirect(getBadgeURL(ctx, label, text, "crimson"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -415,8 +415,9 @@ func RedirectDownload(ctx *context.Context) {
|
||||||
tagNames := []string{vTag}
|
tagNames := []string{vTag}
|
||||||
curRepo := ctx.Repo.Repository
|
curRepo := ctx.Repo.Repository
|
||||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||||
RepoID: curRepo.ID,
|
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
|
||||||
TagNames: tagNames,
|
RepoID: curRepo.ID,
|
||||||
|
TagNames: tagNames,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RedirectDownload", err)
|
ctx.ServerError("RedirectDownload", err)
|
||||||
|
@ -628,7 +629,7 @@ func SearchRepo(ctx *context.Context) {
|
||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -474,7 +474,7 @@ func handleSchedules(
|
||||||
detectedWorkflows []*actions_module.DetectedWorkflow,
|
detectedWorkflows []*actions_module.DetectedWorkflow,
|
||||||
commit *git.Commit,
|
commit *git.Commit,
|
||||||
input *notifyInput,
|
input *notifyInput,
|
||||||
ref string,
|
_ string,
|
||||||
) error {
|
) error {
|
||||||
branch, err := commit.GetBranchName()
|
branch, err := commit.GetBranchName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -411,6 +411,32 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
||||||
|
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
||||||
|
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetRepoReaders: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistUsernames := getWhitelistEntities(readers, pt.AllowlistUserIDs)
|
||||||
|
|
||||||
|
teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistTeams := getWhitelistEntities(teamReaders, pt.AllowlistTeamIDs)
|
||||||
|
|
||||||
|
return &api.TagProtection{
|
||||||
|
ID: pt.ID,
|
||||||
|
NamePattern: pt.NamePattern,
|
||||||
|
WhitelistUsernames: whitelistUsernames,
|
||||||
|
WhitelistTeams: whitelistTeams,
|
||||||
|
Created: pt.CreatedUnix.AsTime(),
|
||||||
|
Updated: pt.UpdatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
||||||
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
||||||
return &api.TopicResponse{
|
return &api.TopicResponse{
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (o *repository) GetRepositoryPushURL() string {
|
||||||
return o.getURL()
|
return o.getURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRepository(ctx context.Context) generic.NodeDriverInterface {
|
func newRepository(_ context.Context) generic.NodeDriverInterface {
|
||||||
r := &repository{
|
r := &repository{
|
||||||
f: &f3.Repository{},
|
f: &f3.Repository{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"code.forgejo.org/f3/gof3/v3/options"
|
"code.forgejo.org/f3/gof3/v3/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestOptions(t *testing.T) options.Interface {
|
func newTestOptions(_ *testing.T) options.Interface {
|
||||||
o := options.GetFactory(driver_options.Name)().(*driver_options.Options)
|
o := options.GetFactory(driver_options.Name)().(*driver_options.Options)
|
||||||
o.SetLogger(util.NewF3Logger(nil, forgejo_log.GetLogger(forgejo_log.DEFAULT)))
|
o.SetLogger(util.NewF3Logger(nil, forgejo_log.GetLogger(forgejo_log.DEFAULT)))
|
||||||
return o
|
return o
|
||||||
|
|
|
@ -477,7 +477,7 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
|
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
|
||||||
verifyHeader["Accept"] = lfs_module.MediaType
|
verifyHeader["Accept"] = lfs_module.AcceptHeader
|
||||||
|
|
||||||
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
|
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func MailNewUser(ctx context.Context, u *user_model.User) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []string) {
|
func mailNewUser(_ context.Context, u *user_model.User, lang string, tos []string) {
|
||||||
locale := translation.NewLocale(lang)
|
locale := translation.NewLocale(lang)
|
||||||
|
|
||||||
manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(u.ID, 10)
|
manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(u.ID, 10)
|
||||||
|
|
|
@ -252,7 +252,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
||||||
}
|
}
|
||||||
|
|
||||||
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
||||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) {
|
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { //nolint:unparam
|
||||||
// Clone base repo.
|
// Clone base repo.
|
||||||
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -84,7 +84,7 @@ func MaybePromoteRemoteUser(ctx context.Context, source *auth_model.Source, logi
|
||||||
return true, reason, nil
|
return true, reason, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRemoteUserToPromote(ctx context.Context, source *auth_model.Source, loginName, email string) (*user_model.User, Reason, error) {
|
func getRemoteUserToPromote(ctx context.Context, source *auth_model.Source, loginName, email string) (*user_model.User, Reason, error) { //nolint:unparam
|
||||||
if !source.IsOAuth2() {
|
if !source.IsOAuth2() {
|
||||||
return nil, NewReason(log.DEBUG, ReasonNotAuth2, "source %v is not OAuth2", source), nil
|
return nil, NewReason(log.DEBUG, ReasonNotAuth2, "source %v is not OAuth2", source), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .PackageDescriptor.Metadata.Description}}
|
{{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>
|
||||||
<div class="ui attached segment">
|
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
|
||||||
{{.PackageDescriptor.Metadata.Description}}
|
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
|
||||||
</div>
|
{{if .PackageDescriptor.Metadata.Comments}}<div class="ui attached segment">{{StringUtils.Join .PackageDescriptor.Metadata.Comments " "}}</div>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if or .PackageDescriptor.Metadata.Require .PackageDescriptor.Metadata.RequireDev}}
|
{{if or .PackageDescriptor.Metadata.Require .PackageDescriptor.Metadata.RequireDev}}
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if or .PackageDescriptor.Metadata.Keywords}}
|
{{if .PackageDescriptor.Metadata.Keywords}}
|
||||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4>
|
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
{{range .PackageDescriptor.Metadata.Keywords}}
|
{{range .PackageDescriptor.Metadata.Keywords}}
|
||||||
|
|
334
templates/swagger/v1_json.tmpl
generated
334
templates/swagger/v1_json.tmpl
generated
|
@ -3616,7 +3616,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "sort repos by attribute. Supported values are \"alpha\", \"created\", \"updated\", \"size\", and \"id\". Default is \"alpha\"",
|
"description": "sort repos by attribute. Supported values are \"alpha\", \"created\", \"updated\", \"size\", \"git_size\", \"lfs_size\", \"stars\", \"forks\" and \"id\". Default is \"alpha\"",
|
||||||
"name": "sort",
|
"name": "sort",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
@ -14184,6 +14184,233 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/tag_protections": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List tag protections for a repository",
|
||||||
|
"operationId": "repoListTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtectionList"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Create a tag protections for a repository",
|
||||||
|
"operationId": "repoCreateTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/CreateTagProtectionOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"423": {
|
||||||
|
"$ref": "#/responses/repoArchivedError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/tag_protections/{id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get a specific tag protection for the repository",
|
||||||
|
"operationId": "repoGetTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of the tag protect to get",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Delete a specific tag protection for the repository",
|
||||||
|
"operationId": "repoDeleteTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of protected tag",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Edit a tag protections for a repository. Only fields that are set will be changed",
|
||||||
|
"operationId": "repoEditTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of protected tag",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditTagProtectionOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"423": {
|
||||||
|
"$ref": "#/responses/repoArchivedError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/tags": {
|
"/repos/{owner}/{repo}/tags": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -20331,6 +20558,31 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"CreateTagProtectionOption": {
|
||||||
|
"description": "CreateTagProtectionOption options for creating a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"CreateTeamOption": {
|
"CreateTeamOption": {
|
||||||
"description": "CreateTeamOption options for creating a team",
|
"description": "CreateTeamOption options for creating a team",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -21282,6 +21534,31 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"EditTagProtectionOption": {
|
||||||
|
"description": "EditTagProtectionOption options for editing a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"EditTeamOption": {
|
"EditTeamOption": {
|
||||||
"description": "EditTeamOption options for editing a team",
|
"description": "EditTeamOption options for editing a team",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -24507,6 +24784,46 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"TagProtection": {
|
||||||
|
"description": "TagProtection represents a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Created"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Updated"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Team": {
|
"Team": {
|
||||||
"description": "Team represents a team in an organization",
|
"description": "Team represents a team in an organization",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -26121,6 +26438,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"TagProtection": {
|
||||||
|
"description": "TagProtection",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/TagProtection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TagProtectionList": {
|
||||||
|
"description": "TagProtectionList",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/TagProtection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"TasksList": {
|
"TasksList": {
|
||||||
"description": "TasksList",
|
"description": "TasksList",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
|
@ -105,7 +105,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
session := loginUser(t, test.user.Name)
|
session := loginUser(t, test.user.Name)
|
||||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path})
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path})
|
||||||
req.Header.Set("Accept", lfs.MediaType)
|
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||||
req.Header.Set("Content-Type", lfs.MediaType)
|
req.Header.Set("Content-Type", lfs.MediaType)
|
||||||
resp := session.MakeRequest(t, req, test.httpResult)
|
resp := session.MakeRequest(t, req, test.httpResult)
|
||||||
if len(test.addTime) > 0 {
|
if len(test.addTime) > 0 {
|
||||||
|
@ -123,7 +123,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
for _, test := range resultsTests {
|
for _, test := range resultsTests {
|
||||||
session := loginUser(t, test.user.Name)
|
session := loginUser(t, test.user.Name)
|
||||||
req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName())
|
req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName())
|
||||||
req.Header.Set("Accept", lfs.MediaType)
|
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
var lfsLocks api.LFSLockList
|
var lfsLocks api.LFSLockList
|
||||||
DecodeJSON(t, resp, &lfsLocks)
|
DecodeJSON(t, resp, &lfsLocks)
|
||||||
|
@ -135,7 +135,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{})
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{})
|
||||||
req.Header.Set("Accept", lfs.MediaType)
|
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||||
req.Header.Set("Content-Type", lfs.MediaType)
|
req.Header.Set("Content-Type", lfs.MediaType)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
var lfsLocksVerify api.LFSLockListVerify
|
var lfsLocksVerify api.LFSLockListVerify
|
||||||
|
@ -159,7 +159,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
for _, test := range deleteTests {
|
for _, test := range deleteTests {
|
||||||
session := loginUser(t, test.user.Name)
|
session := loginUser(t, test.user.Name)
|
||||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{})
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{})
|
||||||
req.Header.Set("Accept", lfs.MediaType)
|
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||||
req.Header.Set("Content-Type", lfs.MediaType)
|
req.Header.Set("Content-Type", lfs.MediaType)
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
var lfsLockRep api.LFSLockResponse
|
var lfsLockRep api.LFSLockResponse
|
||||||
|
@ -172,7 +172,7 @@ func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
for _, test := range resultsTests {
|
for _, test := range resultsTests {
|
||||||
session := loginUser(t, test.user.Name)
|
session := loginUser(t, test.user.Name)
|
||||||
req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName())
|
req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName())
|
||||||
req.Header.Set("Accept", lfs.MediaType)
|
req.Header.Set("Accept", lfs.AcceptHeader)
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
var lfsLocks api.LFSLockList
|
var lfsLocks api.LFSLockList
|
||||||
DecodeJSON(t, resp, &lfsLocks)
|
DecodeJSON(t, resp, &lfsLocks)
|
||||||
|
|
|
@ -85,7 +85,7 @@ func TestAPILFSBatch(t *testing.T) {
|
||||||
|
|
||||||
newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper {
|
newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper {
|
||||||
return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br).
|
return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br).
|
||||||
SetHeader("Accept", lfs.MediaType).
|
SetHeader("Accept", lfs.AcceptHeader).
|
||||||
SetHeader("Content-Type", lfs.MediaType)
|
SetHeader("Content-Type", lfs.MediaType)
|
||||||
}
|
}
|
||||||
decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse {
|
decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse {
|
||||||
|
@ -448,7 +448,7 @@ func TestAPILFSVerify(t *testing.T) {
|
||||||
|
|
||||||
newRequest := func(t testing.TB, p *lfs.Pointer) *RequestWrapper {
|
newRequest := func(t testing.TB, p *lfs.Pointer) *RequestWrapper {
|
||||||
return NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p).
|
return NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p).
|
||||||
SetHeader("Accept", lfs.MediaType).
|
SetHeader("Accept", lfs.AcceptHeader).
|
||||||
SetHeader("Content-Type", lfs.MediaType)
|
SetHeader("Content-Type", lfs.MediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue