Merge branch 'rebase-forgejo-dependency' into wip-forgejo

This commit is contained in:
Earl Warren 2024-02-05 18:58:23 +01:00
commit 094c84ed6d
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
292 changed files with 8842 additions and 1269 deletions

View file

@ -8,6 +8,7 @@ import (
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@ -26,8 +27,6 @@ import (
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth"
"github.com/minio/sha256-simd"
)
const (

View file

@ -6,6 +6,7 @@ package maven
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/xml"
@ -26,8 +27,6 @@ import (
maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/minio/sha256-simd"
)
const (

View file

@ -6,7 +6,7 @@
//
// This documentation describes the Gitea API.
//
// Schemes: http, https
// Schemes: https, http
// BasePath: /api/v1
// Version: {{AppVer | JSEscape | Safe}}
// License: MIT http://opensource.org/licenses/MIT
@ -73,6 +73,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
@ -230,6 +231,39 @@ func repoAssignment() func(ctx *context.APIContext) {
}
}
// must be used within a group with a call to repoAssignment() to set ctx.Repo
func commentAssignment(idParam string) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(idParam))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.InternalServerError(err)
}
return
}
if err = comment.LoadIssue(ctx); err != nil {
ctx.InternalServerError(err)
return
}
if comment.Issue == nil || comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
comment.Issue.Repo = ctx.Repo.Repository
ctx.Comment = comment
}
}
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
@ -1104,6 +1138,18 @@ func Routes() *web.Route {
m.Get("/permission", repo.GetRepoPermissions)
})
}, reqToken())
if setting.Repository.EnableFlags {
m.Group("/flags", func() {
m.Combo("").Get(repo.ListFlags).
Put(bind(api.ReplaceFlagsOption{}), repo.ReplaceAllFlags).
Delete(repo.DeleteAllFlags)
m.Group("/{flag}", func() {
m.Combo("").Get(repo.HasFlag).
Put(repo.AddFlag).
Delete(repo.DeleteFlag)
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
}
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
m.Group("/teams", func() {
@ -1223,8 +1269,16 @@ func Routes() *web.Route {
Get(repo.GetPullReview).
Delete(reqToken(), repo.DeletePullReview).
Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
m.Combo("/comments").
Get(repo.GetPullReviewComments)
m.Group("/comments", func() {
m.Combo("").
Get(repo.GetPullReviewComments).
Post(reqToken(), bind(api.CreatePullReviewCommentOptions{}), repo.CreatePullReviewComment)
m.Group("/{comment}", func() {
m.Combo("").
Get(repo.GetPullReviewComment).
Delete(reqToken(), repo.DeletePullReviewComment)
}, commentAssignment("comment"))
})
m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
})
@ -1328,7 +1382,7 @@ func Routes() *web.Route {
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
}, mustEnableAttachments)
})
}, commentAssignment(":id"))
})
m.Group("/{index}", func() {
m.Combo("").Get(repo.GetIssue).

View file

@ -262,7 +262,9 @@ func GetArchive(ctx *context.APIContext) {
// ---
// summary: Get an archive of a repository
// produces:
// - application/json
// - application/octet-stream
// - application/zip
// - application/gzip
// parameters:
// - name: owner
// in: path
@ -342,7 +344,17 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
}
defer fr.Close()
contentType := ""
switch archiver.Type {
case git.ZIP:
contentType = "application/zip"
case git.TARGZ:
// Per RFC6713.
contentType = "application/gzip"
}
ctx.ServeContent(fr, &context.ServeHeaderOptions{
ContentType: contentType,
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})

View file

@ -0,0 +1,245 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"net/http"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
)
func ListFlags(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/flags repository repoListFlags
// ---
// summary: List a repository's flags
// 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/StringSlice"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
repoFlags, err := ctx.Repo.Repository.ListFlags(ctx)
if err != nil {
ctx.InternalServerError(err)
return
}
flags := make([]string, len(repoFlags))
for i := range repoFlags {
flags[i] = repoFlags[i].Name
}
ctx.SetTotalCountHeader(int64(len(repoFlags)))
ctx.JSON(http.StatusOK, flags)
}
func ReplaceAllFlags(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/flags repository repoReplaceAllFlags
// ---
// summary: Replace all flags of 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
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/ReplaceFlagsOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
flagsForm := web.GetForm(ctx).(*api.ReplaceFlagsOption)
if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, flagsForm.Flags); err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusNoContent)
}
func DeleteAllFlags(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/flags repository repoDeleteAllFlags
// ---
// summary: Remove all flags from 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:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, nil); err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusNoContent)
}
func HasFlag(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/flags/{flag} repository repoCheckFlag
// ---
// summary: Check if a repository has a given flag
// 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: flag
// in: path
// description: name of the flag
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
hasFlag := ctx.Repo.Repository.HasFlag(ctx, ctx.Params(":flag"))
if hasFlag {
ctx.Status(http.StatusNoContent)
} else {
ctx.NotFound()
}
}
func AddFlag(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/flags/{flag} repository repoAddFlag
// ---
// summary: Add a flag to 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
// - name: flag
// in: path
// description: name of the flag
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
flag := ctx.Params(":flag")
if ctx.Repo.Repository.HasFlag(ctx, flag) {
ctx.Status(http.StatusNoContent)
return
}
if err := ctx.Repo.Repository.AddFlag(ctx, flag); err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusNoContent)
}
func DeleteFlag(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/flags/{flag} repository repoDeleteFlag
// ---
// summary: Remove a flag from 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
// - name: flag
// in: path
// description: name of the flag
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
flag := ctx.Params(":flag")
if _, err := ctx.Repo.Repository.DeleteFlag(ctx, flag); err != nil {
ctx.InternalServerError(err)
return
}
ctx.Status(http.StatusNoContent)
}

View file

@ -454,29 +454,7 @@ func GetIssueComment(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
}
return
}
if err = comment.LoadIssue(ctx); err != nil {
ctx.InternalServerError(err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
comment := ctx.Comment
if comment.Type != issues_model.CommentTypeComment {
ctx.Status(http.StatusNoContent)
@ -587,25 +565,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
}
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
return
}
comment := ctx.Comment
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Status(http.StatusForbidden)
@ -617,7 +577,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
err = comment.LoadIssue(ctx)
err := comment.LoadIssue(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
@ -668,7 +628,7 @@ func DeleteIssueComment(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
deleteIssueComment(ctx)
deleteIssueComment(ctx, issues_model.CommentTypeComment)
}
// DeleteIssueCommentDeprecated delete a comment from an issue
@ -707,39 +667,21 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
deleteIssueComment(ctx)
deleteIssueComment(ctx, issues_model.CommentTypeComment)
}
func deleteIssueComment(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
return
}
func deleteIssueComment(ctx *context.APIContext, commentType issues_model.CommentType) {
comment := ctx.Comment
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Status(http.StatusForbidden)
return
} else if comment.Type != issues_model.CommentTypeComment {
} else if comment.Type != commentType {
ctx.Status(http.StatusNoContent)
return
}
if err = issue_service.DeleteComment(ctx, ctx.Doer, comment); err != nil {
if err := issue_service.DeleteComment(ctx, ctx.Doer, comment); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
return
}

View file

@ -55,11 +55,8 @@ func GetIssueCommentAttachment(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/error"
comment := getIssueCommentSafe(ctx)
if comment == nil {
return
}
attachment := getIssueCommentAttachmentSafeRead(ctx, comment)
comment := ctx.Comment
attachment := getIssueCommentAttachmentSafeRead(ctx)
if attachment == nil {
return
}
@ -101,10 +98,7 @@ func ListIssueCommentAttachments(ctx *context.APIContext) {
// "$ref": "#/responses/AttachmentList"
// "404":
// "$ref": "#/responses/error"
comment := getIssueCommentSafe(ctx)
if comment == nil {
return
}
comment := ctx.Comment
if err := comment.LoadAttachments(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
@ -166,14 +160,12 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError"
// Check if comment exists and load comment
comment := getIssueCommentSafe(ctx)
if comment == nil {
if !canUserWriteIssueCommentAttachment(ctx) {
return
}
if !canUserWriteIssueCommentAttachment(ctx, comment) {
return
}
comment := ctx.Comment
updatedAt := ctx.Req.FormValue("updated_at")
if len(updatedAt) != 0 {
@ -341,42 +333,17 @@ func DeleteIssueCommentAttachment(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}
func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
return nil
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
return nil
}
if comment.Issue == nil || comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Error(http.StatusNotFound, "", "no matching issue comment found")
return nil
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
return nil
}
comment.Issue.Repo = ctx.Repo.Repository
return comment
}
func getIssueCommentAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment {
comment := getIssueCommentSafe(ctx)
if comment == nil {
if !canUserWriteIssueCommentAttachment(ctx) {
return nil
}
if !canUserWriteIssueCommentAttachment(ctx, comment) {
return nil
}
return getIssueCommentAttachmentSafeRead(ctx, comment)
return getIssueCommentAttachmentSafeRead(ctx)
}
func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues_model.Comment) bool {
func canUserWriteIssueCommentAttachment(ctx *context.APIContext) bool {
// ctx.Comment is assumed to be set in a safe way via a middleware
comment := ctx.Comment
canEditComment := ctx.IsSigned && (ctx.Doer.ID == comment.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin()) && ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)
if !canEditComment {
ctx.Error(http.StatusForbidden, "", "user should have permission to edit comment")
@ -386,7 +353,10 @@ func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues
return true
}
func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment {
func getIssueCommentAttachmentSafeRead(ctx *context.APIContext) *repo_model.Attachment {
// ctx.Comment is assumed to be set in a safe way via a middleware
comment := ctx.Comment
attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("attachment_id"))
if err != nil {
ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)

View file

@ -51,30 +51,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
}
return
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
return
}
comment := ctx.Comment
reactions, _, err := issues_model.FindCommentReactions(ctx, comment.IssueID, comment.ID)
if err != nil {
@ -188,30 +165,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
}
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
}
return
}
if err = comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
comment := ctx.Comment
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
@ -243,7 +197,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
})
} else {
// DeleteIssueCommentReaction part
err = issues_model.DeleteCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
err := issues_model.DeleteCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
return

View file

@ -208,6 +208,152 @@ func GetPullReviewComments(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, apiComments)
}
// GetPullReviewComment get a pull review comment
func GetPullReviewComment(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment} repository repoGetPullReviewComment
// ---
// summary: Get a pull review comment
// 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: index
// in: path
// description: index of the pull request
// type: integer
// format: int64
// required: true
// - name: id
// in: path
// description: id of the review
// type: integer
// format: int64
// required: true
// - name: comment
// in: path
// description: id of the comment
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/PullReviewComment"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
review, _, statusSet := prepareSingleReview(ctx)
if statusSet {
return
}
if err := ctx.Comment.LoadPoster(ctx); err != nil {
ctx.InternalServerError(err)
return
}
apiComment, err := convert.ToPullReviewComment(ctx, review, ctx.Comment, ctx.Doer)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, apiComment)
}
// CreatePullReviewComments add a new comment to a pull request review
func CreatePullReviewComment(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments repository repoCreatePullReviewComment
// ---
// summary: Add a new comment to a pull request review
// 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: index
// in: path
// description: index of the pull request
// type: integer
// format: int64
// required: true
// - name: id
// in: path
// description: id of the review
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/CreatePullReviewCommentOptions"
// responses:
// "200":
// "$ref": "#/responses/PullReviewComment"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
opts := web.GetForm(ctx).(*api.CreatePullReviewCommentOptions)
review, pr, statusSet := prepareSingleReview(ctx)
if statusSet {
return
}
if err := pr.Issue.LoadRepo(ctx); err != nil {
ctx.InternalServerError(err)
return
}
line := opts.NewLineNum
if opts.OldLineNum > 0 {
line = opts.OldLineNum * -1
}
comment, err := pull_service.CreateCodeCommentKnownReviewID(ctx,
ctx.Doer,
pr.Issue.Repo,
pr.Issue,
opts.Body,
opts.Path,
line,
review.ID,
)
if err != nil {
ctx.InternalServerError(err)
return
}
apiComment, err := convert.ToPullReviewComment(ctx, review, comment, ctx.Doer)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, apiComment)
}
// DeletePullReview delete a specific review from a pull request
func DeletePullReview(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoDeletePullReview
@ -868,6 +1014,53 @@ func UnDismissPullReview(ctx *context.APIContext) {
dismissReview(ctx, "", false, false)
}
// DeletePullReviewComment delete a pull review comment
func DeletePullReviewComment(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment} repository repoDeletePullReviewComment
// ---
// summary: Delete a pull review comment
// 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: index
// in: path
// description: index of the pull request
// type: integer
// format: int64
// required: true
// - name: id
// in: path
// description: id of the review
// type: integer
// format: int64
// required: true
// - name: comment
// in: path
// description: id of the comment
// type: integer
// format: int64
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
deleteIssueComment(ctx, issues_model.CommentTypeCode)
}
func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
if !ctx.Repo.IsAdmin() {
ctx.Error(http.StatusForbidden, "", "Must be repo admin")

View file

@ -34,6 +34,7 @@ import (
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/issue"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
)
// Search repositories via options
@ -740,6 +741,18 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
repo.DefaultBranch = *opts.DefaultBranch
}
// Wiki branch is updated if changed
if opts.WikiBranch != nil && repo.WikiBranch != *opts.WikiBranch {
if err := wiki_service.NormalizeWikiBranch(ctx, repo, *opts.WikiBranch); err != nil {
ctx.Error(http.StatusInternalServerError, "NormalizeWikiBranch", err)
return err
}
// While NormalizeWikiBranch updates the db, we need to update *this*
// instance of `repo`, so that the `UpdateRepository` below will not
// reset the branch back.
repo.WikiBranch = *opts.WikiBranch
}
if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err
@ -984,7 +997,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
}
if len(units)+len(deleteUnitTypes) > 0 {
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}

View file

@ -176,7 +176,7 @@ func CreateTag(ctx *context.APIContext) {
// schema:
// "$ref": "#/definitions/CreateTagOption"
// responses:
// "200":
// "201":
// "$ref": "#/responses/Tag"
// "404":
// "$ref": "#/responses/notFound"

View file

@ -203,7 +203,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
}
return &api.WikiPage{
WikiPageMetaData: wiki_service.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
WikiPageMetaData: convert.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
ContentBase64: content,
CommitCount: commitsCount,
Sidebar: sidebarContent,
@ -333,7 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
return
}
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
pages = append(pages, convert.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
}
ctx.SetTotalCountHeader(int64(len(entries)))

View file

@ -12,7 +12,7 @@ import (
"code.gitea.io/gitea/modules/util"
)
// RegistrationToken is response related to registeration token
// RegistrationToken is a string used to register a runner with a server
// swagger:response RegistrationToken
type RegistrationToken struct {
Token string `json:"token"`

View file

@ -17,6 +17,9 @@ type swaggerParameterBodies struct {
// in:body
AddCollaboratorOption api.AddCollaboratorOption
// in:body
ReplaceFlagsOption api.ReplaceFlagsOption
// in:body
CreateEmailOption api.CreateEmailOption
// in:body
@ -158,6 +161,9 @@ type swaggerParameterBodies struct {
// in:body
CreatePullReviewComment api.CreatePullReviewComment
// in:body
CreatePullReviewCommentOptions api.CreatePullReviewCommentOptions
// in:body
SubmitPullReviewOptions api.SubmitPullReviewOptions

View file

@ -244,7 +244,7 @@ func CreateOauth2Application(ctx *context.APIContext) {
// ListOauth2Applications list all the Oauth2 application
func ListOauth2Applications(ctx *context.APIContext) {
// swagger:operation GET /user/applications/oauth2 user userGetOauth2Application
// swagger:operation GET /user/applications/oauth2 user userGetOAuth2Applications
// ---
// summary: List the authenticated user's oauth2 applications
// produces:

View file

@ -358,6 +358,12 @@ func SubmitInstall(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
return
}
if len(form.AdminPasswd) < setting.MinPasswordLength {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplInstall, form)
return
}
}
// Init the engine with migration
@ -407,7 +413,7 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("true")
cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath)
var lfsJwtSecret string
if _, lfsJwtSecret, err = generate.NewJwtSecretBase64(); err != nil {
if _, lfsJwtSecret, err = generate.NewJwtSecret(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form)
return
}

View file

@ -33,6 +33,7 @@ import (
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
notify_service "code.gitea.io/gitea/services/notify"
user_service "code.gitea.io/gitea/services/user"
"github.com/markbates/goth"
@ -606,6 +607,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
}
}
notify_service.NewUserSignUp(ctx, u)
// update external user information
if gothUser != nil {
if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
@ -651,13 +653,22 @@ func Activate(ctx *context.Context) {
}
// Resend confirmation email.
if setting.Service.RegisterEmailConfirm {
if ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName) {
var cacheKey string
if ctx.Cache.IsExist("MailChangedJustNow_" + ctx.Doer.LowerName) {
cacheKey = "MailChangedLimit_"
if err := ctx.Cache.Delete("MailChangedJustNow_" + ctx.Doer.LowerName); err != nil {
log.Error("Delete cache(MailChangedJustNow) fail: %v", err)
}
} else {
cacheKey = "MailResendLimit_"
}
if ctx.Cache.IsExist(cacheKey + ctx.Doer.LowerName) {
ctx.Data["ResendLimited"] = true
} else {
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
if err := ctx.Cache.Put(cacheKey+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
@ -691,6 +702,43 @@ func Activate(ctx *context.Context) {
func ActivatePost(ctx *context.Context) {
code := ctx.FormString("code")
if len(code) == 0 {
email := ctx.FormString("email")
if len(email) > 0 {
ctx.Data["IsActivatePage"] = true
if ctx.Doer == nil || ctx.Doer.IsActive {
ctx.NotFound("invalid user", nil)
return
}
// Change the primary email
if setting.Service.RegisterEmailConfirm {
if ctx.Cache.IsExist("MailChangeLimit_" + ctx.Doer.LowerName) {
ctx.Data["ResendLimited"] = true
} else {
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
err := user_service.ReplaceInactivePrimaryEmail(ctx, ctx.Doer.Email, &user_model.EmailAddress{
UID: ctx.Doer.ID,
Email: email,
})
if err != nil {
ctx.Data["IsActivatePage"] = false
log.Error("Couldn't replace inactive primary email of user %d: %v", ctx.Doer.ID, err)
ctx.RenderWithErr(ctx.Tr("auth.change_unconfirmed_email_error", err), TplActivate, nil)
return
}
if err := ctx.Cache.Put("MailChangeLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailChangeLimit) fail: %v", err)
}
if err := ctx.Cache.Put("MailChangedJustNow_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailChangedJustNow) fail: %v", err)
}
// Confirmation mail will be re-sent after the redirect to `/user/activate` below.
}
} else {
ctx.Data["ServiceNotEnabled"] = true
}
}
ctx.Redirect(setting.AppSubURL + "/user/activate")
return
}

View file

@ -952,10 +952,16 @@ func SignInOAuthCallback(ctx *context.Context) {
return
} else if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration {
// create new user with details from oauth2 provider
var missingFields []string
if gothUser.UserID == "" {
missingFields = append(missingFields, "sub")
log.Error("OAuth2 Provider %s returned empty or missing field: UserID", authSource.Name)
if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
}
err = fmt.Errorf("OAuth2 Provider %s returned empty or missing field: UserID", authSource.Name)
ctx.ServerError("CreateUser", err)
return
}
var missingFields []string
if gothUser.Email == "" {
missingFields = append(missingFields, "email")
}
@ -963,12 +969,10 @@ func SignInOAuthCallback(ctx *context.Context) {
missingFields = append(missingFields, "nickname")
}
if len(missingFields) > 0 {
log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
}
err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
ctx.ServerError("CreateUser", err)
// we don't have enough information to create an account automatically,
// so we prompt the user for the remaining bits
log.Trace("OAuth2 Provider %s returned empty or missing fields: %s, prompting the user for them", authSource.Name, missingFields)
showLinkingLogin(ctx, gothUser)
return
}
uname, err := getUserName(&gothUser)

View file

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/gorilla/feeds"
"github.com/jaytaylor/html2text"
)
func toBranchLink(ctx *context.Context, act *activities_model.Action) string {
@ -240,8 +241,15 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
content = desc
}
// It's a common practice for feed generators to use plain text titles.
// See https://codeberg.org/forgejo/forgejo/pulls/1595
plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true})
if err != nil {
return nil, err
}
items = append(items, &feeds.Item{
Title: title,
Title: plainTitle,
Link: link,
Description: desc,
IsPermaLink: "false",

View file

@ -8,11 +8,12 @@ import (
)
// RenderBranchFeed render format for branch or file
func RenderBranchFeed(ctx *context.Context) {
_, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req)
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
} else {
ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
func RenderBranchFeed(feedType string) func(ctx *context.Context) {
return func(ctx *context.Context) {
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, feedType)
} else {
ShowFileFeed(ctx, ctx.Repo.Repository, feedType)
}
}
}

View file

@ -46,6 +46,20 @@ func View(ctx *context_module.Context) {
ctx.HTML(http.StatusOK, tplViewActions)
}
func ViewLatest(ctx *context_module.Context) {
run, err := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.NotFound("GetLatestRun", err)
return
}
err = run.LoadAttributes(ctx)
if err != nil {
ctx.ServerError("LoadAttributes", err)
return
}
ctx.Redirect(run.HTMLURL(), http.StatusTemporaryRedirect)
}
type ViewRequest struct {
LogCursors []struct {
Step int `json:"step"`

View file

@ -0,0 +1,165 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package badges
import (
"fmt"
"net/url"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
)
func getBadgeURL(ctx *context_module.Context, label, text, color string) string {
sb := &strings.Builder{}
_ = setting.Badges.GeneratorURLTemplateTemplate.Execute(sb, map[string]string{
"label": url.PathEscape(label),
"text": url.PathEscape(text),
"color": url.PathEscape(color),
})
badgeURL := sb.String()
q := ctx.Req.URL.Query()
// Remove any `branch` or `event` query parameters. They're used by the
// workflow badge route, and do not need forwarding to the badge generator.
delete(q, "branch")
delete(q, "event")
if len(q) > 0 {
return fmt.Sprintf("%s?%s", badgeURL, q.Encode())
}
return badgeURL
}
func redirectToBadge(ctx *context_module.Context, label, text, color string) {
ctx.Redirect(getBadgeURL(ctx, label, text, color))
}
func errorBadge(ctx *context_module.Context, label, text string) {
ctx.Redirect(getBadgeURL(ctx, label, text, "crimson"))
}
func GetWorkflowBadge(ctx *context_module.Context) {
branch := ctx.Req.URL.Query().Get("branch")
if branch == "" {
branch = ctx.Repo.Repository.DefaultBranch
}
branch = fmt.Sprintf("refs/heads/%s", branch)
event := ctx.Req.URL.Query().Get("event")
workflowFile := ctx.Params("workflow_name")
run, err := actions_model.GetLatestRunForBranchAndWorkflow(ctx, ctx.Repo.Repository.ID, branch, workflowFile, event)
if err != nil {
errorBadge(ctx, workflowFile, "Not found")
return
}
var color string
switch run.Status {
case actions_model.StatusUnknown:
color = "lightgrey"
case actions_model.StatusWaiting:
color = "lightgrey"
case actions_model.StatusRunning:
color = "gold"
case actions_model.StatusSuccess:
color = "brightgreen"
case actions_model.StatusFailure:
color = "crimson"
case actions_model.StatusCancelled:
color = "orange"
case actions_model.StatusSkipped:
color = "blue"
case actions_model.StatusBlocked:
color = "yellow"
default:
color = "lightgrey"
}
redirectToBadge(ctx, workflowFile, run.Status.String(), color)
}
func getIssueOrPullBadge(ctx *context_module.Context, label, variant string, num int) {
var text string
if len(variant) > 0 {
text = fmt.Sprintf("%d %s", num, variant)
} else {
text = fmt.Sprintf("%d", num)
}
redirectToBadge(ctx, label, text, "blue")
}
func getIssueBadge(ctx *context_module.Context, variant string, num int) {
if !ctx.Repo.CanRead(unit.TypeIssues) &&
!ctx.Repo.CanRead(unit.TypeExternalTracker) {
errorBadge(ctx, "issues", "Not found")
return
}
_, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker)
if err == nil {
errorBadge(ctx, "issues", "Not found")
return
}
getIssueOrPullBadge(ctx, "issues", variant, num)
}
func getPullBadge(ctx *context_module.Context, variant string, num int) {
if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(unit.TypePullRequests) {
errorBadge(ctx, "pulls", "Not found")
return
}
getIssueOrPullBadge(ctx, "pulls", variant, num)
}
func GetOpenIssuesBadge(ctx *context_module.Context) {
getIssueBadge(ctx, "open", ctx.Repo.Repository.NumOpenIssues)
}
func GetClosedIssuesBadge(ctx *context_module.Context) {
getIssueBadge(ctx, "closed", ctx.Repo.Repository.NumClosedIssues)
}
func GetTotalIssuesBadge(ctx *context_module.Context) {
getIssueBadge(ctx, "", ctx.Repo.Repository.NumIssues)
}
func GetOpenPullsBadge(ctx *context_module.Context) {
getPullBadge(ctx, "open", ctx.Repo.Repository.NumOpenPulls)
}
func GetClosedPullsBadge(ctx *context_module.Context) {
getPullBadge(ctx, "closed", ctx.Repo.Repository.NumClosedPulls)
}
func GetTotalPullsBadge(ctx *context_module.Context) {
getPullBadge(ctx, "", ctx.Repo.Repository.NumPulls)
}
func GetStarsBadge(ctx *context_module.Context) {
redirectToBadge(ctx, "stars", fmt.Sprintf("%d", ctx.Repo.Repository.NumStars), "blue")
}
func GetLatestReleaseBadge(ctx *context_module.Context) {
release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
errorBadge(ctx, "release", "Not found")
return
}
ctx.ServerError("GetLatestReleaseByRepoID", err)
}
if err := release.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadAttributes", err)
return
}
redirectToBadge(ctx, "release", release.TagName, "blue")
}

View file

@ -8,7 +8,6 @@ import (
gotemplate "html/template"
"net/http"
"net/url"
"strconv"
"strings"
user_model "code.gitea.io/gitea/models/user"
@ -39,32 +38,15 @@ type blameRow struct {
// RefBlame render blame page
func RefBlame(ctx *context.Context) {
fileName := ctx.Repo.TreePath
if len(fileName) == 0 {
ctx.NotFound("Blame FileName", nil)
if ctx.Repo.TreePath == "" {
ctx.NotFound("No file specified", nil)
return
}
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
}
var treeNames []string
paths := make([]string, 0, 5)
if len(ctx.Repo.TreePath) > 0 {
treeNames = strings.Split(ctx.Repo.TreePath, "/")
for i := range treeNames {
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
}
ctx.Data["HasParentPath"] = true
if len(paths)-2 >= 0 {
ctx.Data["ParentPath"] = "/" + paths[len(paths)-1]
}
treeNames := strings.Split(ctx.Repo.TreePath, "/")
for i := range treeNames {
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
}
// Get current entry user currently looking at.
@ -73,47 +55,35 @@ func RefBlame(ctx *context.Context) {
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
return
}
blob := entry.Blob()
ctx.Data["Paths"] = paths
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
ctx.Data["PageIsViewCode"] = true
ctx.Data["IsBlame"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
ctx.Data["Paths"] = paths
ctx.Data["TreeNames"] = treeNames
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
ctx.Data["NumLinesSet"] = true
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
if err != nil {
ctx.NotFound("GetBlobLineCount", err)
ctx.ServerError("GetBlobLineCount", err)
return
}
bypassBlameIgnore, _ := strconv.ParseBool(ctx.FormString("bypass-blame-ignore"))
result, err := performBlame(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Commit, fileName, bypassBlameIgnore)
result, err := performBlame(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormBool("bypass-blame-ignore"))
if err != nil {
ctx.NotFound("CreateBlameReader", err)
ctx.ServerError("performBlame", err)
return
}
ctx.Data["UsesIgnoreRevs"] = result.UsesIgnoreRevs
ctx.Data["FaultyIgnoreRevsFile"] = result.FaultyIgnoreRevsFile
// Get Topics of this repo
renderRepoTopics(ctx)
if ctx.Written() {
return
}
commitNames := processBlameParts(ctx, result.Parts)
if ctx.Written() {
return
@ -130,12 +100,13 @@ type blameResult struct {
FaultyIgnoreRevsFile bool
}
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
func performBlame(ctx *context.Context, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
repoPath := ctx.Repo.Repository.RepoPath()
objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
if err != nil {
ctx.NotFound("CreateBlameReader", err)
return nil, err
}
blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
if err != nil {
return nil, err

View file

@ -244,6 +244,22 @@ func FileHistory(ctx *context.Context) {
ctx.ServerError("CommitsByFileAndRange", err)
return
}
oldestCommit := commits[len(commits)-1]
renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
if err != nil {
ctx.ServerError("GetCommitFileRenames", err)
return
}
for _, renames := range renamedFiles {
if renames[1] == fileName {
ctx.Data["OldFilename"] = renames[0]
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
break
}
}
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
ctx.Data["Username"] = ctx.Repo.Owner.Name

View file

@ -14,6 +14,7 @@ import (
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
@ -99,6 +100,27 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) {
return treeNames, treePaths
}
// getSelectableEmailAddresses returns which emails can be used by the user as
// email for a Git commiter.
func getSelectableEmailAddresses(ctx *context.Context) ([]*user_model.ActivatedEmailAddress, error) {
// Retrieve emails that the user could use for commiter identity.
commitEmails, err := user_model.GetActivatedEmailAddresses(ctx, ctx.Doer.ID)
if err != nil {
return nil, fmt.Errorf("GetActivatedEmailAddresses: %w", err)
}
// Allow for the placeholder mail to be used. Use -1 as ID to identify
// this entry to be the placerholder mail of the user.
placeholderMail := &user_model.ActivatedEmailAddress{ID: -1, Email: ctx.Doer.GetPlaceholderEmail()}
if ctx.Doer.KeepEmailPrivate {
commitEmails = append([]*user_model.ActivatedEmailAddress{placeholderMail}, commitEmails...)
} else {
commitEmails = append(commitEmails, placeholderMail)
}
return commitEmails, nil
}
func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["PageIsEdit"] = true
ctx.Data["IsNewFile"] = isNewFile
@ -177,6 +199,12 @@ func editFile(ctx *context.Context, isNewFile bool) {
treeNames = append(treeNames, fileName)
}
commitEmails, err := getSelectableEmailAddresses(ctx)
if err != nil {
ctx.ServerError("getSelectableEmailAddresses", err)
return
}
ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
@ -192,6 +220,8 @@ func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath)
ctx.Data["CommitMails"] = commitEmails
ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail()
ctx.HTML(http.StatusOK, tplEditFile)
}
@ -227,6 +257,12 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
branchName = form.NewBranchName
}
commitEmails, err := getSelectableEmailAddresses(ctx)
if err != nil {
ctx.ServerError("getSelectableEmailAddresses", err)
return
}
ctx.Data["PageIsEdit"] = true
ctx.Data["PageHasPosted"] = true
ctx.Data["IsNewFile"] = isNewFile
@ -243,6 +279,8 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath)
ctx.Data["CommitMails"] = commitEmails
ctx.Data["DefaultCommitMail"] = ctx.Doer.GetEmail()
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplEditFile)
@ -277,6 +315,30 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
operation = "create"
}
gitIdentity := &files_service.IdentityOptions{
Name: ctx.Doer.Name,
}
// -1 is defined as placeholder email.
if form.CommitMailID == -1 {
gitIdentity.Email = ctx.Doer.GetPlaceholderEmail()
} else {
// Check if the given email is activated.
email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, form.CommitMailID)
if err != nil {
ctx.ServerError("GetEmailAddressByID", err)
return
}
if email == nil || !email.IsActivated {
ctx.Data["Err_CommitMailID"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, &form)
return
}
gitIdentity.Email = email.Email
}
if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
@ -290,7 +352,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")),
},
},
Signoff: form.Signoff,
Signoff: form.Signoff,
Author: gitIdentity,
Committer: gitIdentity,
}); err != nil {
// This is where we handle all the errors thrown by files_service.ChangeRepoFiles
if git.IsErrNotExist(err) {

View file

@ -0,0 +1,49 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package flags
import (
"net/http"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
const (
tplRepoFlags base.TplName = "repo/flags"
)
func Manage(ctx *context.Context) {
ctx.Data["IsRepoFlagsPage"] = true
ctx.Data["Title"] = ctx.Tr("repo.admin.manage_flags")
flags := map[string]bool{}
for _, f := range setting.Repository.SettableFlags {
flags[f] = false
}
repoFlags, _ := ctx.Repo.Repository.ListFlags(ctx)
for _, f := range repoFlags {
flags[f.Name] = true
}
ctx.Data["Flags"] = flags
ctx.HTML(http.StatusOK, tplRepoFlags)
}
func ManagePost(ctx *context.Context) {
newFlags := ctx.FormStrings("flags")
err := ctx.Repo.Repository.ReplaceAllFlags(ctx, newFlags)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.admin.failed_to_replace_flags"))
log.Error("Error replacing repository flags for repo %d: %v", ctx.Repo.Repository.ID, err)
} else {
ctx.Flash.Success(ctx.Tr("repo.admin.flags_replaced"))
}
ctx.Redirect(ctx.Repo.Repository.HTMLURL() + "/flags")
}

View file

@ -2504,7 +2504,8 @@ func UpdatePullReviewRequest(ctx *context.Context) {
func SearchIssues(ctx *context.Context) {
before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, err.Error())
log.Error("GetQueryBeforeSince: %v", err)
ctx.Error(http.StatusUnprocessableEntity, "invalid before or since")
return
}
@ -2541,10 +2542,11 @@ func SearchIssues(ctx *context.Context) {
if ctx.FormString("owner") != "" {
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
if err != nil {
log.Error("GetUserByName: %v", err)
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusBadRequest, "Owner not found", err.Error())
} else {
ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error())
ctx.Error(http.StatusInternalServerError)
}
return
}
@ -2555,15 +2557,16 @@ func SearchIssues(ctx *context.Context) {
}
if ctx.FormString("team") != "" {
if ctx.FormString("owner") == "" {
ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team")
ctx.Error(http.StatusBadRequest, "Owner organisation is required for filtering on team")
return
}
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
if err != nil {
log.Error("GetTeam: %v", err)
if organization.IsErrTeamNotExist(err) {
ctx.Error(http.StatusBadRequest, "Team not found", err.Error())
ctx.Error(http.StatusBadRequest)
} else {
ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error())
ctx.Error(http.StatusInternalServerError)
}
return
}
@ -2576,7 +2579,8 @@ func SearchIssues(ctx *context.Context) {
}
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err.Error())
log.Error("SearchRepositoryIDs: %v", err)
ctx.Error(http.StatusInternalServerError)
return
}
if len(repoIDs) == 0 {
@ -2610,7 +2614,8 @@ func SearchIssues(ctx *context.Context) {
}
includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelIDsByNames", err.Error())
log.Error("GetLabelIDsByNames: %v", err)
ctx.Error(http.StatusInternalServerError)
return
}
}
@ -2624,7 +2629,8 @@ func SearchIssues(ctx *context.Context) {
}
includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetMilestoneIDsByNames", err.Error())
log.Error("GetMilestoneIDsByNames: %v", err)
ctx.Error(http.StatusInternalServerError)
return
}
}
@ -2691,12 +2697,14 @@ func SearchIssues(ctx *context.Context) {
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
if err != nil {
ctx.Error(http.StatusInternalServerError, "SearchIssues", err.Error())
log.Error("SearchIssues: %v", err)
ctx.Error(http.StatusInternalServerError)
return
}
issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err.Error())
log.Error("GetIssuesByIDs: %v", err)
ctx.Error(http.StatusInternalServerError)
return
}

View file

@ -94,6 +94,8 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue
// CanWrite means the doer can manage the issue/PR list
if ctx.Repo.IsOwner() || ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
canSoftDelete = true
} else if ctx.Doer == nil {
canSoftDelete = false
} else {
// for read-only users, they could still post issues or comments,
// they should be able to delete the history related to their own issue/comment, a case is:

View file

@ -967,6 +967,18 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
return
}
// determine if the user viewing the pull request can edit the head branch
if ctx.Doer != nil && pull.HeadRepo != nil && !pull.HasMerged {
headRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
ctx.Data["HeadBranchIsEditable"] = pull.HeadRepo.CanEnableEditor() && issues_model.CanMaintainerWriteToBranch(ctx, headRepoPerm, pull.HeadBranch, ctx.Doer)
ctx.Data["SourceRepoLink"] = pull.HeadRepo.Link()
ctx.Data["HeadBranch"] = pull.HeadBranch
}
if ctx.IsSigned && ctx.Doer != nil {
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, issue, ctx.Doer); err != nil {
ctx.ServerError("CanMarkConversation", err)

View file

@ -153,19 +153,12 @@ func UpdateResolveConversation(ctx *context.Context) {
}
func renderConversation(ctx *context.Context, comment *issues_model.Comment, origin string) {
ctx.Data["PageIsPullFiles"] = origin == "diff"
comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, ctx.Data["ShowOutdatedComments"].(bool))
comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, true)
if err != nil {
ctx.ServerError("FetchCodeCommentsByLine", err)
return
}
if len(comments) == 0 {
// if the comments are empty (deleted, outdated, etc), it doesn't need to render anything, just return an empty body to replace "conversation-holder" on the page
ctx.Resp.WriteHeader(http.StatusOK)
return
}
ctx.Data["PageIsPullFiles"] = (origin == "diff")
ctx.Data["comments"] = comments
if ctx.Data["CanMarkConversation"], err = issues_model.CanMarkConversation(ctx, comment.Issue, ctx.Doer); err != nil {
ctx.ServerError("CanMarkConversation", err)
@ -186,8 +179,6 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori
ctx.HTML(http.StatusOK, tplDiffConversation)
} else if origin == "timeline" {
ctx.HTML(http.StatusOK, tplTimelineConversation)
} else {
ctx.Error(http.StatusBadRequest, "Unknown origin: "+origin)
}
}

View file

@ -397,7 +397,11 @@ func NewReleasePost(ctx *context.Context) {
return
}
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
// form.Target can be a branch name or a full commitID.
if !ctx.Repo.GitRepo.IsBranchExist(form.Target) &&
len(form.Target) == objectFormat.FullLength() && !ctx.Repo.GitRepo.IsCommitExist(form.Target) {
ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form)
return
}

View file

@ -6,12 +6,13 @@ package setting
import (
"net/http"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web/repo"
repo_service "code.gitea.io/gitea/services/repository"
notify_service "code.gitea.io/gitea/services/notify"
)
// SetDefaultBranchPost set default branch
@ -34,14 +35,23 @@ func SetDefaultBranchPost(ctx *context.Context) {
}
branch := ctx.FormString("branch")
if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
switch {
case git_model.IsErrBranchNotExist(err):
ctx.Status(http.StatusNotFound)
default:
ctx.ServerError("SetDefaultBranch", err)
}
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
ctx.Status(http.StatusNotFound)
return
} else if repo.DefaultBranch != branch {
repo.DefaultBranch = branch
if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
if !git.IsErrUnsupportedVersion(err) {
ctx.ServerError("SetDefaultBranch", err)
return
}
}
if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
ctx.ServerError("SetDefaultBranch", err)
return
}
notify_service.ChangeDefaultBranch(ctx, repo)
}
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)

View file

@ -474,10 +474,17 @@ func SettingsPost(ctx *context.Context) {
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
var wikiPermissions repo_model.UnitAccessMode
if form.GloballyWriteableWiki {
wikiPermissions = repo_model.UnitAccessModeWrite
} else {
wikiPermissions = repo_model.UnitAccessModeRead
}
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
Type: unit_model.TypeWiki,
Config: new(repo_model.UnitConfig),
RepoID: repo.ID,
Type: unit_model.TypeWiki,
Config: new(repo_model.UnitConfig),
DefaultPermissions: wikiPermissions,
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
} else {
@ -595,7 +602,7 @@ func SettingsPost(ctx *context.Context) {
return
}
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.ServerError("UpdateRepositoryUnits", err)
return
}
@ -868,6 +875,27 @@ func SettingsPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
case "rename-wiki-branch":
if !ctx.Repo.IsOwner() {
ctx.Error(http.StatusNotFound)
return
}
if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return
}
if err := wiki_service.NormalizeWikiBranch(ctx, repo, setting.Repository.DefaultBranch); err != nil {
log.Error("Normalize Wiki branch: %v", err.Error())
ctx.Flash.Error(ctx.Tr("repo.settings.wiki_branch_rename_failure"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
return
}
log.Trace("Repository wiki normalized: %s#%s", repo.FullName(), setting.Repository.DefaultBranch)
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_branch_rename_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
case "archive":
if !ctx.Repo.IsOwner() {
ctx.Error(http.StatusForbidden)

View file

@ -166,7 +166,7 @@ func renderDirectory(ctx *context.Context) {
if ctx.Repo.TreePath != "" {
ctx.Data["HideRepoInfo"] = true
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
}
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
@ -381,7 +381,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
}
defer dataRc.Close()
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefName)
ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileName"] = blob.Name()
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
@ -747,7 +747,7 @@ func checkHomeCodeViewable(ctx *context.Context) {
}
unit, ok := unit_model.Units[repoUnit.Type]
if ok && (firstUnit == nil || !firstUnit.IsLessThan(unit)) {
if ok && (firstUnit == nil || !firstUnit.IsLessThan(unit)) && repoUnit.Type.CanBeDefault() {
firstUnit = &unit
}
}
@ -794,12 +794,19 @@ func Home(ctx *context.Context) {
if setting.Other.EnableFeed {
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
if isFeed {
switch {
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
if ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType) {
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath == "":
return
}
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound("MustBeNotEmpty", nil)
return
}
if ctx.Repo.TreePath == "" {
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
case ctx.Repo.TreePath != "":
} else {
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
}
return
@ -1028,20 +1035,79 @@ func renderCode(ctx *context.Context) {
return
}
showRecentlyPushedNewBranches := true
if ctx.Repo.Repository.IsMirror ||
!ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypePullRequests) {
showRecentlyPushedNewBranches = false
// If the repo is a mirror, don't display recently pushed branches.
if ctx.Repo.Repository.IsMirror {
goto PostRecentBranchCheck
}
if showRecentlyPushedNewBranches {
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch)
if err != nil {
ctx.ServerError("GetRecentlyPushedBranches", err)
return
// If pull requests aren't enabled for either the current repo, or its
// base, don't display recently pushed branches.
if !(ctx.Repo.Repository.AllowsPulls(ctx) ||
(ctx.Repo.Repository.BaseRepo != nil && ctx.Repo.Repository.BaseRepo.AllowsPulls(ctx))) {
goto PostRecentBranchCheck
}
// Find recently pushed new branches to *this* repo.
branches, err := git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch)
if err != nil {
ctx.ServerError("FindRecentlyPushedBranches", err)
return
}
// If this is not a fork, check if the signed in user has a fork, and
// check branches there.
if !ctx.Repo.Repository.IsFork {
repo := repo_model.GetForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
if repo != nil {
baseBranches, err := git_model.FindRecentlyPushedNewBranches(ctx, repo.ID, ctx.Doer.ID, repo.DefaultBranch)
if err != nil {
ctx.ServerError("FindRecentlyPushedBranches", err)
return
}
branches = append(branches, baseBranches...)
}
}
// Filter out branches that have no relation to the default branch of
// the repository.
var filteredBranches []*git_model.Branch
for _, branch := range branches {
repo, err := branch.GetRepo(ctx)
if err != nil {
continue
}
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
continue
}
defer gitRepo.Close()
head, err := gitRepo.GetCommit(branch.CommitID)
if err != nil {
continue
}
defaultBranch, err := gitRepo.GetDefaultBranch()
if err != nil {
continue
}
defaultBranchHead, err := gitRepo.GetCommit(defaultBranch)
if err != nil {
continue
}
hasMergeBase, err := head.HasPreviousCommit(defaultBranchHead.ID)
if err != nil {
continue
}
if hasMergeBase {
filteredBranches = append(filteredBranches, branch)
}
}
ctx.Data["RecentlyPushedNewBranches"] = filteredBranches
}
PostRecentBranchCheck:
var treeNames []string
paths := make([]string, 0, 5)
if len(ctx.Repo.TreePath) > 0 {

View file

@ -99,7 +99,7 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
return nil, nil, err
}
commit, err := wikiRepo.GetBranchCommit(wiki_service.DefaultBranch)
commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.GetWikiBranchName())
if err != nil {
return wikiRepo, nil, err
}
@ -316,7 +316,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.GetWikiBranchName(), pageFilename)
ctx.Data["CommitCount"] = commitsCount
return wikiRepo, entry
@ -368,7 +368,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
ctx.Data["footerContent"] = ""
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename)
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.GetWikiBranchName(), pageFilename)
ctx.Data["CommitCount"] = commitsCount
// get page
@ -380,7 +380,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: wiki_service.DefaultBranch,
Revision: ctx.Repo.Repository.GetWikiBranchName(),
File: pageFilename,
Page: page,
})

View file

@ -98,7 +98,7 @@ func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profile
if commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch); err != nil {
log.Error("FindUserProfileReadme failed to GetBranchCommit: %v", err)
} else {
profileReadmeBlob, _ = commit.GetBlobByPath("README.md")
profileReadmeBlob, _ = commit.GetBlobByFoldedPath("README.md")
}
}
}

View file

@ -715,12 +715,15 @@ func UsernameSubRoute(ctx *context.Context) {
reloadParam := func(suffix string) (success bool) {
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
context_service.UserAssignmentWeb()(ctx)
if ctx.Written() {
return false
}
// check view permissions
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name))
return false
}
return !ctx.Written()
return true
}
switch {
case strings.HasSuffix(username, ".png"):

View file

@ -37,6 +37,8 @@ import (
org_setting "code.gitea.io/gitea/routers/web/org/setting"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/routers/web/repo/actions"
"code.gitea.io/gitea/routers/web/repo/badges"
repo_flags "code.gitea.io/gitea/routers/web/repo/flags"
repo_setting "code.gitea.io/gitea/routers/web/repo/setting"
"code.gitea.io/gitea/routers/web/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
@ -49,17 +51,12 @@ import (
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
"gitea.com/go-chi/captcha"
"github.com/NYTimes/gziphandler"
chi_middleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/klauspost/compress/gzhttp"
"github.com/prometheus/client_golang/prometheus"
)
const (
// GzipMinSize represents min size to compress for the body size of response
GzipMinSize = 1400
)
// optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests.
func optionsCorsHandler() func(next http.Handler) http.Handler {
var corsHandler func(next http.Handler) http.Handler
@ -245,11 +242,11 @@ func Routes() *web.Route {
var mid []any
if setting.EnableGzip {
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
wrapper, err := gzhttp.NewWrapper(gzhttp.RandomJitter(32, 0, false))
if err != nil {
log.Fatal("GzipHandlerWithOpts failed: %v", err)
log.Fatal("gzhttp.NewWrapper failed: %v", err)
}
mid = append(mid, h)
mid = append(mid, wrapper)
}
if setting.Service.EnableCaptcha {
@ -686,7 +683,9 @@ func registerRoutes(m *web.Route) {
m.Get("", admin.Dashboard)
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
m.Get("/self_check", admin.SelfCheck)
if setting.Database.Type.IsMySQL() || setting.Database.Type.IsMSSQL() {
m.Get("/self_check", admin.SelfCheck)
}
m.Group("/config", func() {
m.Get("", admin.Config)
@ -1249,7 +1248,7 @@ func registerRoutes(m *web.Route) {
Post(web.Bind(forms.UploadRepoFileForm{}), repo.UploadFilePost)
m.Combo("/_diffpatch/*").Get(repo.NewDiffPatch).
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
m.Combo("/_cherrypick/{sha:([a-f0-9]{7,64})}/*").Get(repo.CherryPick).
m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick).
Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
}, repo.MustBeEditable)
m.Group("", func() {
@ -1334,6 +1333,24 @@ func registerRoutes(m *web.Route) {
m.Get("/packages", repo.Packages)
}
if setting.Badges.Enabled {
m.Group("/badges", func() {
m.Get("/workflows/{workflow_name}/badge.svg", badges.GetWorkflowBadge)
m.Group("/issues", func() {
m.Get(".svg", badges.GetTotalIssuesBadge)
m.Get("/open.svg", badges.GetOpenIssuesBadge)
m.Get("/closed.svg", badges.GetClosedIssuesBadge)
})
m.Group("/pulls", func() {
m.Get(".svg", badges.GetTotalPullsBadge)
m.Get("/open.svg", badges.GetOpenPullsBadge)
m.Get("/closed.svg", badges.GetClosedPullsBadge)
})
m.Get("/stars.svg", badges.GetStarsBadge)
m.Get("/release.svg", badges.GetLatestReleaseBadge)
})
}
m.Group("/projects", func() {
m.Get("", repo.Projects)
m.Get("/{id}", repo.ViewProject)
@ -1365,23 +1382,28 @@ func registerRoutes(m *web.Route) {
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
m.Group("/runs/{run}", func() {
m.Combo("").
Get(actions.View).
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
m.Group("/jobs/{job}", func() {
m.Group("/runs", func() {
m.Get("/latest", actions.ViewLatest)
m.Group("/{run}", func() {
m.Combo("").
Get(actions.View).
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
m.Group("/jobs/{job}", func() {
m.Combo("").
Get(actions.View).
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
m.Get("/logs", actions.Logs)
})
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
m.Post("/artifacts", actions.ArtifactsView)
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
m.Get("/logs", actions.Logs)
})
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
m.Post("/artifacts", actions.ArtifactsView)
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
})
m.Get("/workflows/{workflow_name}/badge.svg", badges.GetWorkflowBadge)
}, reqRepoActionsReader, actions.MustEnableActions)
m.Group("/wiki", func() {
@ -1391,8 +1413,8 @@ func registerRoutes(m *web.Route) {
m.Combo("/*").
Get(repo.Wiki).
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff)
m.Get("/commit/{sha:[a-f0-9]{4,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{4,64}}.{ext:patch|diff}", repo.RawDiff)
}, repo.MustEnableWiki, func(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink()
@ -1452,7 +1474,7 @@ func registerRoutes(m *web.Route) {
m.Group("/commits", func() {
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
})
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
@ -1461,8 +1483,8 @@ func registerRoutes(m *web.Route) {
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
m.Group("/files", func() {
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
m.Get("/{shaFrom:[a-f0-9]{4,40}}..{shaTo:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
m.Group("/reviews", func() {
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
@ -1512,13 +1534,13 @@ func registerRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/graph", repo.Graph)
m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:([a-f0-9]{7,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
m.Get("/commit/{sha:([a-f0-9]{4,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:([a-f0-9]{4,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
m.Get("/cherry-pick/{sha:([a-f0-9]{4,64})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed)
m.Get("/rss/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("rss"))
m.Get("/atom/branch/*", repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefBranch), feedEnabled, feed.RenderBranchFeed("atom"))
m.Group("/src", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
@ -1531,7 +1553,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/forks", repo.Forks)
}, context.RepoRef(), reqRepoCodeReader)
m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
m.Get("/commit/{sha:([a-f0-9]{4,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
}, ignSignIn, context.RepoAssignment, context.UnitTypes())
m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit)
@ -1568,6 +1590,13 @@ func registerRoutes(m *web.Route) {
gitHTTPRouters(m)
})
})
if setting.Repository.EnableFlags {
m.Group("/{username}/{reponame}/flags", func() {
m.Get("", repo_flags.Manage)
m.Post("", repo_flags.ManagePost)
}, adminReq, context.RepoAssignment, context.UnitTypes())
}
// ***** END: Repository *****
m.Group("/notifications", func() {