Merge remote-tracking branch 'upstream/main' into feat/api-project-boards

This commit is contained in:
dineshsalunke
2024-01-17 19:55:54 +05:30
874 changed files with 10728 additions and 7872 deletions

View File

@@ -63,7 +63,6 @@ package actions
import (
"crypto/md5"
"errors"
"fmt"
"net/http"
"strconv"
@@ -258,8 +257,11 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
return
}
// update artifact size if zero
if artifact.FileSize == 0 || artifact.FileCompressedSize == 0 {
// update artifact size if zero or not match, over write artifact size
if artifact.FileSize == 0 ||
artifact.FileCompressedSize == 0 ||
artifact.FileSize != fileRealTotalSize ||
artifact.FileCompressedSize != chunksTotalSize {
artifact.FileSize = fileRealTotalSize
artifact.FileCompressedSize = chunksTotalSize
artifact.ContentEncoding = ctx.Req.Header.Get("Content-Encoding")
@@ -268,6 +270,8 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
ctx.Error(http.StatusInternalServerError, "Error update artifact")
return
}
log.Debug("[artifact] update artifact size, artifact_id: %d, size: %d, compressed size: %d",
artifact.ID, artifact.FileSize, artifact.FileCompressedSize)
}
ctx.JSON(http.StatusOK, map[string]string{
@@ -423,15 +427,15 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
}
artifactID := ctx.ParamsInt64("artifact_id")
artifact, err := actions.GetArtifactByID(ctx, artifactID)
if errors.Is(err, util.ErrNotExist) {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusNotFound, err.Error())
return
} else if err != nil {
artifact, exist, err := db.GetByID[actions.ActionArtifact](ctx, artifactID)
if err != nil {
log.Error("Error getting artifact: %v", err)
ctx.Error(http.StatusInternalServerError, err.Error())
return
} else if !exist {
log.Error("artifact with ID %d does not exist", artifactID)
ctx.Error(http.StatusNotFound, fmt.Sprintf("artifact with ID %d does not exist", artifactID))
return
}
if artifact.RunID != runID {
log.Error("Error dismatch runID and artifactID, task: %v, artifact: %v", runID, artifactID)

View File

@@ -26,10 +26,11 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
// build chunk store path
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
@@ -58,6 +59,7 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
}
type chunkFileItem struct {
RunID int64
ArtifactID int64
Start int64
End int64
@@ -67,9 +69,12 @@ type chunkFileItem struct {
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem
if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
item := chunkFileItem{Path: path}
if _, err := fmt.Sscanf(path, filepath.Join(storageDir, "%d-%d-%d.chunk"), &item.ArtifactID, &item.Start, &item.End); err != nil {
if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error {
baseName := filepath.Base(fpath)
// when read chunks from storage, it only contains storage dir and basename,
// no matter the subdirectory setting in storage config
item := chunkFileItem{Path: storageDir + "/" + baseName}
if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)
@@ -181,7 +186,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
}()
// save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
log.Debug("[artifact] merge chunks to artifact: %d, %s, old:%s", artifact.ID, storagePath, artifact.StoragePath)
// if artifact is already uploaded, delete the old file
if artifact.StoragePath != "" {
if err := st.Delete(artifact.StoragePath); err != nil {
log.Warn("Error deleting old artifact: %s, %v", artifact.StoragePath, err)
}
}
artifact.StoragePath = storagePath
artifact.Status = int64(actions.ArtifactStatusUploadConfirmed)
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {

View File

@@ -94,6 +94,12 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
variables := map[string]string{}
// Global
globalVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{})
if err != nil {
log.Error("find global variables: %v", err)
}
// Org / User level
ownerVariables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
if err != nil {
@@ -106,8 +112,8 @@ func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
}
// Level precedence: Repo > Org / User
for _, v := range append(ownerVariables, repoVariables...) {
// Level precedence: Repo > Org / User > Global
for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) {
variables[v.Name] = v.Data
}

View File

@@ -512,16 +512,7 @@ func CommonRoutes() *web.Route {
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/rpm", func() {
r.Get(".repo", rpm.GetRepositoryConfig)
r.Get("/repository.key", rpm.GetRepositoryKey)
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
r.Group("/package/{name}/{version}/{architecture}", func() {
r.Get("", rpm.DownloadPackageFile)
r.Delete("", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
})
r.Get("/repodata/{filename}", rpm.GetRepositoryFile)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -586,6 +577,82 @@ func CommonRoutes() *web.Route {
return r
}
// Support for uploading rpm packages with arbitrary depth paths
func RpmRoutes(r *web.Route) func() {
var (
groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`)
groupUpload = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`)
groupRpm = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`)
)
return func() {
r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) {
path := ctx.Params("*")
isHead := ctx.Req.Method == "HEAD"
isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
isPut := ctx.Req.Method == "PUT"
isDelete := ctx.Req.Method == "DELETE"
if path == "/repository.key" && isGetHead {
rpm.GetRepositoryKey(ctx)
return
}
// get repo
m := groupRepoInfo.FindStringSubmatch(path)
if len(m) == 2 && isGetHead {
ctx.SetParams("group", strings.Trim(m[1], "/"))
rpm.GetRepositoryConfig(ctx)
return
}
// get meta
m = groupMetadata.FindStringSubmatch(path)
if len(m) == 3 && isGetHead {
ctx.SetParams("group", strings.Trim(m[1], "/"))
ctx.SetParams("filename", m[2])
if isHead {
rpm.CheckRepositoryFileExistence(ctx)
} else {
rpm.GetRepositoryFile(ctx)
}
return
}
// upload
m = groupUpload.FindStringSubmatch(path)
if len(m) == 2 && isPut {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
ctx.SetParams("group", strings.Trim(m[1], "/"))
rpm.UploadPackageFile(ctx)
return
}
// rpm down/delete
m = groupRpm.FindStringSubmatch(path)
if len(m) == 6 {
ctx.SetParams("group", strings.Trim(m[1], "/"))
ctx.SetParams("name", m[2])
ctx.SetParams("version", m[3])
ctx.SetParams("architecture", m[4])
if isGetHead {
rpm.DownloadPackageFile(ctx)
return
} else if isDelete {
reqPackageAccess(perm.AccessModeWrite)(ctx)
if ctx.Written() {
return
}
rpm.DeletePackageFile(ctx)
}
}
// default
ctx.Status(http.StatusNotFound)
})
}
}
// ContainerRoutes provides endpoints that implement the OCI API to serve containers
// These have to be mounted on `/v2/...` to comply with the OCI spec:
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
@@ -600,7 +667,10 @@ func ContainerRoutes() *web.Route {
})
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Get("/token", container.Authenticate)
r.Group("/token", func() {
r.Get("", container.Authenticate)
r.Post("", container.AuthenticateNotImplemented)
})
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {

View File

@@ -156,6 +156,17 @@ func Authenticate(ctx *context.Context) {
})
}
// https://distribution.github.io/distribution/spec/auth/oauth/
func AuthenticateNotImplemented(ctx *context.Context) {
// This optional endpoint can be used to authenticate a client.
// It must implement the specification described in:
// https://datatracker.ietf.org/doc/html/rfc6749
// https://distribution.github.io/distribution/spec/auth/oauth/
// Purpose of this stub is to respond with 404 Not Found instead of 405 Method Not Allowed.
ctx.Status(http.StatusNotFound)
}
// https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n")

View File

@@ -33,11 +33,14 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://dnf.readthedocs.io/en/latest/conf_ref.html
func GetRepositoryConfig(ctx *context.Context) {
group := ctx.Params("group")
if group != "" {
group = fmt.Sprintf("/%s", group)
}
url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`]
name=`+ctx.Package.Owner.Name+` - `+setting.AppName+`
baseurl=`+url+`
ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`]
name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+`
baseurl=`+url+group+`/
enabled=1
gpgcheck=1
gpgkey=`+url+`/repository.key`)
@@ -57,6 +60,30 @@ func GetRepositoryKey(ctx *context.Context) {
})
}
func CheckRepositoryFileExistence(ctx *context.Context) {
pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), ctx.Params("group"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Status(http.StatusNotFound)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
ctx.Status(http.StatusOK)
}
// Gets a pre-generated repository metadata file
func GetRepositoryFile(ctx *context.Context) {
pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
@@ -69,7 +96,8 @@ func GetRepositoryFile(ctx *context.Context) {
ctx,
pv,
&packages_service.PackageFileInfo{
Filename: ctx.Params("filename"),
Filename: ctx.Params("filename"),
CompositeKey: ctx.Params("group"),
},
)
if err != nil {
@@ -121,7 +149,7 @@ func UploadPackageFile(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err)
return
}
group := ctx.Params("group")
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
ctx,
&packages_service.PackageCreationInfo{
@@ -129,14 +157,15 @@ func UploadPackageFile(ctx *context.Context) {
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: pck.Name,
Version: pck.Version,
Version: strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"),
},
Creator: ctx.Doer,
Metadata: pck.VersionMetadata,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
CompositeKey: group,
},
Creator: ctx.Doer,
Data: buf,
@@ -158,7 +187,7 @@ func UploadPackageFile(ctx *context.Context) {
return
}
if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil {
if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
@@ -167,19 +196,20 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
group := ctx.Params("group")
name := ctx.Params("name")
version := ctx.Params("version")
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: name,
Version: version,
Version: strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
},
&packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
CompositeKey: group,
},
)
if err != nil {
@@ -195,14 +225,19 @@ func DownloadPackageFile(ctx *context.Context) {
}
func DeletePackageFile(webctx *context.Context) {
group := webctx.Params("group")
name := webctx.Params("name")
version := webctx.Params("version")
architecture := webctx.Params("architecture")
var pd *packages_model.PackageDescriptor
err := db.WithTx(webctx, func(ctx stdctx.Context) error {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version)
pv, err := packages_model.GetVersionByNameAndVersion(ctx,
webctx.Package.Owner.ID,
packages_model.TypeRpm,
name,
strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
)
if err != nil {
return err
}
@@ -211,7 +246,7 @@ func DeletePackageFile(webctx *context.Context) {
ctx,
pv.ID,
fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture),
packages_model.EmptyFileKey,
group,
)
if err != nil {
return err
@@ -251,7 +286,7 @@ func DeletePackageFile(webctx *context.Context) {
notify_service.PackageDelete(webctx, webctx.Doer, pd)
}
if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil {
if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
apiError(webctx, http.StatusInternalServerError, err)
return
}

View File

@@ -0,0 +1,26 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
// GetRegistrationToken returns the token to register global runners
func GetRegistrationToken(ctx *context.APIContext) {
// swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken
// ---
// summary: Get an global actions runner registration token
// produces:
// - application/json
// parameters:
// responses:
// "200":
// "$ref": "#/responses/RegistrationToken"
shared.GetRegistrationToken(ctx, 0, 0)
}

View File

@@ -93,18 +93,28 @@ func CreateUser(ctx *context.APIContext) {
if ctx.Written() {
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
if u.LoginType == auth.Plain {
if len(form.Password) < setting.MinPasswordLength {
err := errors.New("PasswordIsRequired")
ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
@@ -173,6 +183,8 @@ func EditUser(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/User"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
@@ -254,6 +266,10 @@ func EditUser(ctx *context.APIContext) {
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
}
if form.Admin != nil {
if !*form.Admin && user_model.IsLastAdminUser(ctx, ctx.ContextUser) {
ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
return
}
ctx.ContextUser.IsAdmin = *form.Admin
}
if form.AllowGitHook != nil {
@@ -331,7 +347,8 @@ func DeleteUser(ctx *context.APIContext) {
if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) ||
models.IsErrUserOwnPackages(err) {
models.IsErrUserOwnPackages(err) ||
models.IsErrDeleteLastAdminUser(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "DeleteUser", err)

View File

@@ -35,10 +35,12 @@
// type: apiKey
// name: token
// in: query
// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AccessToken:
// type: apiKey
// name: access_token
// in: query
// description: This authentication option is deprecated for removal in Gitea 1.23. Please use AuthorizationHeaderToken instead.
// AuthorizationHeaderToken:
// type: apiKey
// name: Authorization
@@ -867,6 +869,31 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
}
}
func individualPermsChecker(ctx *context.APIContext) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// check for and warn against deprecated authentication options
func checkDeprecatedAuthMethods(ctx *context.APIContext) {
if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
}
}
// Routes registers all v1 APIs routes to web application.
func Routes() *web.Route {
m := web.NewRoute()
@@ -874,9 +901,7 @@ func Routes() *web.Route {
m.Use(securityHeaders())
if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
AllowedOrigins: setting.CORSConfig.AllowDomain,
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedOrigins: setting.CORSConfig.AllowDomain,
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: append(
@@ -887,6 +912,8 @@ func Routes() *web.Route {
}
m.Use(context.APIContexter())
m.Use(checkDeprecatedAuthMethods)
// Get user from session if logged in.
m.Use(apiAuth(buildAuthGroup()))
@@ -974,7 +1001,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds)
}, context_service.UserAssignmentAPI())
}, context_service.UserAssignmentAPI(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope)
@@ -1007,11 +1034,17 @@ func Routes() *web.Route {
Post(bind(api.CreateEmailOption{}), user.AddEmail).
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
// create or update a user's actions secrets
m.Group("/actions/secrets", func() {
m.Combo("/{secretname}").
Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
Delete(user.DeleteSecret)
// manage user-level actions features
m.Group("/actions", func() {
m.Group("/secrets", func() {
m.Combo("/{secretname}").
Put(bind(api.CreateOrUpdateSecretOption{}), user.CreateOrUpdateSecret).
Delete(user.DeleteSecret)
})
m.Group("/runners", func() {
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
})
})
m.Get("/followers", user.ListMyFollowers)
@@ -1128,10 +1161,16 @@ func Routes() *web.Route {
m.Post("/accept", repo.AcceptTransfer)
m.Post("/reject", repo.RejectTransfer)
}, reqToken())
m.Group("/actions/secrets", func() {
m.Combo("/{secretname}").
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
m.Group("/actions", func() {
m.Group("/secrets", func() {
m.Combo("/{secretname}").
Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret).
Delete(reqToken(), reqOwner(), repo.DeleteSecret)
})
m.Group("/runners", func() {
m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken)
})
})
m.Group("/hooks/git", func() {
m.Combo("").Get(repo.ListGitHooks)
@@ -1280,9 +1319,9 @@ func Routes() *web.Route {
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
m.Put("", reqToken(), user.Watch)
m.Delete("", reqToken(), user.Unwatch)
})
m.Put("", user.Watch)
m.Delete("", user.Unwatch)
}, reqToken())
m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
@@ -1469,8 +1508,8 @@ func Routes() *web.Route {
m.Group("/{username}/{reponame}", func() {
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Get("/pinned", repo.ListPinnedIssues)
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments)
m.Group("/{id}", func() {
@@ -1636,11 +1675,17 @@ func Routes() *web.Route {
m.Combo("/{username}").Get(reqToken(), org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
})
m.Group("/actions/secrets", func() {
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
m.Combo("/{secretname}").
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
m.Group("/actions", func() {
m.Group("/secrets", func() {
m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets)
m.Combo("/{secretname}").
Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret).
Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret)
})
m.Group("/runners", func() {
m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken)
})
})
m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers)
@@ -1744,6 +1789,9 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), admin.EditHook).
Delete(admin.DeleteHook)
})
m.Group("/runners", func() {
m.Get("/registration-token", admin.GetRegistrationToken)
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
m.Group("/projects", func() {

View File

@@ -29,10 +29,9 @@ func NodeInfo(ctx *context.APIContext) {
nodeInfoUsage := structs.NodeInfoUsage{}
if setting.Federation.ShareUserStatistics {
cached := false
if setting.CacheService.Enabled {
nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
}
var cached bool
nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
if !cached {
usersTotal := int(user_model.CountUsers(ctx, nil))
now := time.Now()
@@ -53,11 +52,10 @@ func NodeInfo(ctx *context.APIContext) {
LocalPosts: int(allIssues),
LocalComments: int(allComments),
}
if setting.CacheService.Enabled {
if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
ctx.InternalServerError(err)
return
}
if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
ctx.InternalServerError(err)
return
}
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
// GetRegistrationToken returns the token to register org runners
func GetRegistrationToken(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
// ---
// summary: Get an organization's actions runner registration token
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/RegistrationToken"
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
}

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
@@ -137,7 +138,7 @@ func DeleteBranch(ctx *context.APIContext) {
}
// check whether branches of this repository has been synced
totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
})
@@ -251,12 +252,11 @@ func CreateBranch(ctx *context.APIContext) {
}
}
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
}
if models.IsErrTagAlreadyExists(err) {
} else if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")
@@ -342,7 +342,7 @@ func ListBranches(ctx *context.APIContext) {
IsDeletedBranch: util.OptionalBoolFalse,
}
var err error
totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
@@ -361,7 +361,7 @@ func ListBranches(ctx *context.APIContext) {
return
}
branches, err := git_model.FindBranches(ctx, branchOpts)
branches, err := db.Find[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@@ -615,6 +615,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
DismissStaleApprovals: form.DismissStaleApprovals,
IgnoreStaleApprovals: form.IgnoreStaleApprovals,
RequireSignedCommits: form.RequireSignedCommits,
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
@@ -786,6 +787,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
}
if form.IgnoreStaleApprovals != nil {
protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
}
if form.RequireSignedCommits != nil {
protectBranch.RequireSignedCommits = *form.RequireSignedCommits
}

View File

@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
@@ -53,7 +54,9 @@ func ListCollaborators(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
count, err := repo_model.CountCollaborators(ctx, ctx.Repo.Repository.ID)
count, err := db.Count[repo_model.Collaboration](ctx, repo_model.FindCollaborationOptions{
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
ctx.InternalServerError(err)
return

View File

@@ -462,6 +462,24 @@ func ListIssues(ctx *context.APIContext) {
isPull = util.OptionalBoolNone
}
if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(isPull.IsTrue()) {
ctx.NotFound()
return
}
if isPull == util.OptionalBoolNone {
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
if !canReadIssues && !canReadPulls {
ctx.NotFound()
return
} else if !canReadIssues {
isPull = util.OptionalBoolTrue
} else if !canReadPulls {
isPull = util.OptionalBoolFalse
}
}
// FIXME: we should be more efficient here
createdByID := getUserIDForFilter(ctx, "created_by")
if ctx.Written() {
@@ -593,6 +611,10 @@ func GetIssue(ctx *context.APIContext) {
}
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
}

View File

@@ -12,9 +12,11 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
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/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@@ -71,6 +73,11 @@ func ListIssueComments(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
issue.Repo = ctx.Repo.Repository
opts := &issues_model.FindCommentsOptions{
@@ -271,12 +278,27 @@ func ListRepoIssueComments(ctx *context.APIContext) {
return
}
var isPull util.OptionalBool
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
if canReadIssue && canReadPull {
isPull = util.OptionalBoolNone
} else if canReadIssue {
isPull = util.OptionalBoolFalse
} else if canReadPull {
isPull = util.OptionalBoolTrue
} else {
ctx.NotFound()
return
}
opts := &issues_model.FindCommentsOptions{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
Type: issues_model.CommentTypeComment,
Since: since,
Before: before,
IsPull: isPull,
}
comments, err := issues_model.FindComments(ctx, opts)
@@ -367,6 +389,11 @@ func CreateIssueComment(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
return
@@ -436,6 +463,11 @@ func GetIssueComment(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
if comment.Type != issues_model.CommentTypeComment {
ctx.Status(http.StatusNoContent)
return
@@ -555,7 +587,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
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
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Status(http.StatusForbidden)
return
}
@@ -658,7 +700,17 @@ func deleteIssueComment(ctx *context.APIContext) {
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
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
}
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 {

View File

@@ -329,6 +329,10 @@ func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
return nil
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
return nil
}
comment.Issue.Repo = ctx.Repo.Repository
return comment

View File

@@ -102,23 +102,24 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
var lastRepoID int64
var lastPerm access_model.Permission
repoPerms := make(map[int64]access_model.Permission)
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for _, blocker := range blockersInfo {
// Get the permissions for this repository
perm := lastPerm
if lastRepoID != blocker.Repository.ID {
if blocker.Repository.ID == ctx.Repo.Repository.ID {
perm = ctx.Repo.Permission
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
// If the repo ID exists in the map, return the exist permissions
// else get the permission and add it to the map
var perm access_model.Permission
existPerm, ok := repoPerms[blocker.RepoID]
if ok {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
lastRepoID = blocker.Repository.ID
repoPerms[blocker.RepoID] = perm
}
// check permission
@@ -345,29 +346,31 @@ func GetIssueBlocks(ctx *context.APIContext) {
return
}
var lastRepoID int64
var lastPerm access_model.Permission
var issues []*issues_model.Issue
repoPerms := make(map[int64]access_model.Permission)
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for i, depMeta := range deps {
if i < skip || i >= max {
continue
}
// Get the permissions for this repository
perm := lastPerm
if lastRepoID != depMeta.Repository.ID {
if depMeta.Repository.ID == ctx.Repo.Repository.ID {
perm = ctx.Repo.Permission
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
// If the repo ID exists in the map, return the exist permissions
// else get the permission and add it to the map
var perm access_model.Permission
existPerm, ok := repoPerms[depMeta.RepoID]
if ok {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
}
lastRepoID = depMeta.Repository.ID
repoPerms[depMeta.RepoID] = perm
}
if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {

View File

@@ -61,6 +61,12 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
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) {
@@ -190,9 +196,19 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
return
}
err = comment.LoadIssue(ctx)
if err != nil {
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
}
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {

View File

@@ -153,6 +153,12 @@ func GetDeployKey(ctx *context.APIContext) {
return
}
// this check make it more consistent
if key.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if err = key.GetContent(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "GetContent", err)
return

View File

@@ -9,10 +9,12 @@ import (
"strconv"
"time"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@@ -58,14 +60,21 @@ func ListMilestones(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
milestones, total, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
state := api.StateType(ctx.FormString("state"))
var isClosed util.OptionalBool
switch state {
case api.StateClosed, api.StateOpen:
isClosed = util.OptionalBoolOf(state == api.StateClosed)
}
milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.FormString("state")),
IsClosed: isClosed,
Name: ctx.FormString("name"),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetMilestones", err)
ctx.Error(http.StatusInternalServerError, "db.FindAndCount[issues_model.Milestone]", err)
return
}

View File

@@ -227,11 +227,18 @@ func GetPushMirrorByName(ctx *context.APIContext) {
mirrorName := ctx.Params(":name")
// Get push mirror of a specific repo by remoteName
pushMirror, err := repo_model.GetPushMirror(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: mirrorName})
pushMirror, exist, err := db.Get[repo_model.PushMirror](ctx, repo_model.PushMirrorOptions{
RepoID: ctx.Repo.Repository.ID,
RemoteName: mirrorName,
}.ToConds())
if err != nil {
ctx.Error(http.StatusNotFound, "GetPushMirrors", err)
ctx.Error(http.StatusInternalServerError, "GetPushMirrors", err)
return
} else if !exist {
ctx.Error(http.StatusNotFound, "GetPushMirrors", nil)
return
}
m, err := convert.ToPushMirror(ctx, pushMirror)
if err != nil {
ctx.ServerError("GetPushMirrorByRemoteName", err)
@@ -368,7 +375,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
RemoteAddress: remoteAddress,
}
if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil {
if err = db.Insert(ctx, pushMirror); err != nil {
ctx.ServerError("InsertPushMirror", err)
return
}

View File

@@ -66,7 +66,7 @@ func getNote(ctx *context.APIContext, identifier string) {
return
}
commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier)
commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(err)
@@ -77,7 +77,7 @@ func getNote(ctx *context.APIContext, identifier string) {
}
var note git.Note
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitSHA.String(), &note); err != nil {
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitID.String(), &note); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(identifier)
return

View File

@@ -913,6 +913,10 @@ func MergePullRequest(ctx *context.APIContext) {
}
defer headRepo.Close()
}
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
return
}
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
switch {
case git.IsErrBranchNotExist(err):
@@ -1329,17 +1333,16 @@ func GetPullRequestCommits(ctx *context.APIContext) {
userCache := make(map[string]*user_model.User)
start, end := listOptions.GetStartEnd()
start, limit := listOptions.GetSkipTake()
if end > totalNumberOfCommits {
end = totalNumberOfCommits
}
limit = min(limit, totalNumberOfCommits-start)
limit = max(limit, 0)
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files")
apiCommits := make([]*api.Commit, 0, end-start)
for i := start; i < end; i++ {
apiCommits := make([]*api.Commit, 0, limit)
for i := start; i < start+limit; i++ {
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
convert.ToCommitOptions{
Stat: true,
@@ -1477,19 +1480,14 @@ func GetPullRequestFiles(ctx *context.APIContext) {
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
start, end := listOptions.GetStartEnd()
start, limit := listOptions.GetSkipTake()
if end > totalNumberOfFiles {
end = totalNumberOfFiles
}
limit = min(limit, totalNumberOfFiles-start)
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
limit = max(limit, 0)
apiFiles := make([]*api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles := make([]*api.ChangedFile, 0, limit)
for i := start; i < start+limit; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
@@ -49,13 +50,12 @@ func GetRelease(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
id := ctx.ParamsInt64(":id")
release, err := repo_model.GetReleaseByID(ctx, id)
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
ctx.NotFound()
return
}
@@ -134,11 +134,6 @@ func ListReleases(ctx *context.APIContext) {
// in: query
// description: filter (exclude / include) pre-releases
// type: boolean
// - name: per_page
// in: query
// description: page size of results, deprecated - use limit
// type: integer
// deprecated: true
// - name: page
// in: query
// description: page number of results to return (1-based)
@@ -153,9 +148,6 @@ func ListReleases(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
if listOptions.PageSize == 0 && ctx.FormInt("per_page") != 0 {
listOptions.PageSize = ctx.FormInt("per_page")
}
opts := repo_model.FindReleasesOptions{
ListOptions: listOptions,
@@ -163,9 +155,10 @@ func ListReleases(ctx *context.APIContext) {
IncludeTags: false,
IsDraft: ctx.FormOptionalBool("draft"),
IsPreRelease: ctx.FormOptionalBool("pre-release"),
RepoID: ctx.Repo.Repository.ID,
}
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return
@@ -179,7 +172,7 @@ func ListReleases(ctx *context.APIContext) {
rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
}
filteredCount, err := repo_model.CountReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
filteredCount, err := db.Count[repo_model.Release](ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
@@ -315,13 +308,12 @@ func EditRelease(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditReleaseOption)
id := ctx.ParamsInt64(":id")
rel, err := repo_model.GetReleaseByID(ctx, id)
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
ctx.NotFound()
return
}
@@ -393,17 +385,16 @@ func DeleteRelease(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
id := ctx.ParamsInt64(":id")
rel, err := repo_model.GetReleaseByID(ctx, id)
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
ctx.NotFound()
return
}
if err := release_service.DeleteReleaseByID(ctx, id, ctx.Doer, false); err != nil {
if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View File

@@ -17,6 +17,23 @@ import (
"code.gitea.io/gitea/services/convert"
)
func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound()
return false
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return false
}
if release.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return false
}
return true
}
// GetReleaseAttachment gets a single attachment of the release
func GetReleaseAttachment(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
@@ -54,6 +71,10 @@ func GetReleaseAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
@@ -176,13 +197,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
@@ -203,7 +218,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: release.RepoID,
RepoID: ctx.Repo.Repository.ID,
ReleaseID: releaseID,
})
if err != nil {
@@ -264,6 +279,10 @@ func EditReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
@@ -328,6 +347,10 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {

View File

@@ -112,7 +112,7 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
return
}
if err = releaseservice.DeleteReleaseByID(ctx, release.ID, ctx.Doer, false); err != nil {
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View File

@@ -242,17 +242,18 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
}
repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{
Name: opt.Name,
Description: opt.Description,
IssueLabels: opt.IssueLabels,
Gitignores: opt.Gitignores,
License: opt.License,
Readme: opt.Readme,
IsPrivate: opt.Private,
AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch,
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template,
Name: opt.Name,
Description: opt.Description,
IssueLabels: opt.IssueLabels,
Gitignores: opt.Gitignores,
License: opt.License,
Readme: opt.Readme,
IsPrivate: opt.Private,
AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch,
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template,
ObjectFormatName: git.Sha1ObjectFormat.Name(),
})
if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {
@@ -982,7 +983,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
}
if len(units)+len(deleteUnitTypes) > 0 {
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}

View File

@@ -0,0 +1,34 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
)
// GetRegistrationToken returns the token to register repo runners
func GetRegistrationToken(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken
// ---
// summary: Get a repository's actions runner registration token
// 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/RegistrationToken"
shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
@@ -194,8 +195,10 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
listOptions := utils.GetListOptions(ctx)
statuses, maxResults, err := git_model.GetCommitStatuses(ctx, repo, sha, &git_model.CommitStatusOptions{
statuses, maxResults, err := db.FindAndCount[git_model.CommitStatus](ctx, &git_model.CommitStatusOptions{
ListOptions: listOptions,
RepoID: repo.ID,
SHA: sha,
SortType: ctx.FormTrim("sort"),
State: ctx.FormTrim("state"),
})

View File

@@ -272,7 +272,7 @@ func DeleteTag(ctx *context.APIContext) {
return
}
if err = releaseservice.DeleteReleaseByID(ctx, tag.ID, ctx.Doer, true); err != nil {
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View File

@@ -0,0 +1,32 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package shared
import (
"errors"
"net/http"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
)
// RegistrationToken is response related to registeration token
// swagger:response RegistrationToken
type RegistrationToken struct {
Token string `json:"token"`
}
func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
token, err := actions_model.GetLatestRunnerToken(ctx, ownerID, repoID)
if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
token, err = actions_model.NewRunnerToken(ctx, ownerID, repoID)
}
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
}

View File

@@ -342,6 +342,10 @@ func GetOauth2Application(ctx *context.APIContext) {
}
return
}
if app.UID != ctx.Doer.ID {
ctx.NotFound()
return
}
app.ClientSecret = ""

View File

@@ -18,23 +18,25 @@ import (
)
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
keys, err := asymkey_model.ListGPGKeys(ctx, uid, listOptions)
keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: listOptions,
OwnerID: uid,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return
}
if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return
}
apiKeys := make([]*api.GPGKey, len(keys))
for i := range keys {
apiKeys[i] = convert.ToGPGKey(keys[i])
}
total, err := asymkey_model.CountUserGPGKeys(ctx, uid)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &apiKeys)
}
@@ -112,7 +114,7 @@ func GetGPGKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
key, err := asymkey_model.GetGPGKeyByID(ctx, ctx.ParamsInt64(":id"))
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.ParamsInt64(":id"))
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()
@@ -121,6 +123,10 @@ func GetGPGKey(ctx *context.APIContext) {
}
return
}
if err := key.LoadSubKeys(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
return
}
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
}
@@ -198,7 +204,10 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
}
key, err := asymkey_model.GetGPGKeysByKeyID(ctx, form.KeyID)
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
KeyID: form.KeyID,
IncludeSubKeys: true,
})
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()
@@ -207,7 +216,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
}
return
}
ctx.JSON(http.StatusOK, convert.ToGPGKey(key[0]))
ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
}
// swagger:parameters userCurrentPostGPGKey

View File

@@ -62,6 +62,11 @@ func GetHook(ctx *context.APIContext) {
return
}
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
ctx.NotFound()
return
}
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
if err != nil {
ctx.InternalServerError(err)

View File

@@ -0,0 +1,26 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared"
)
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
// GetRegistrationToken returns the token to register user runners
func GetRegistrationToken(ctx *context.APIContext) {
// swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken
// ---
// summary: Get an user's actions runner registration token
// produces:
// - application/json
// parameters:
// responses:
// "200":
// "$ref": "#/responses/RegistrationToken"
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
}

View File

@@ -69,27 +69,28 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
return "", "", nil
}
// ConvertToSHA1 returns a full-length SHA1 from a potential ID string
func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.SHA1, error) {
if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) {
sha1, err := git.NewIDFromString(commitID)
// ConvertToObjectID returns a full-length SHA1 from a potential ID string
func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
objectFormat, _ := repo.GitRepo.GetObjectFormat()
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
sha, err := git.NewIDFromString(commitID)
if err == nil {
return sha1, nil
return sha, nil
}
}
gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath())
if err != nil {
return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
return objectFormat.EmptyObjectID(), fmt.Errorf("RepositoryFromContextOrOpen: %w", err)
}
defer closer.Close()
return gitRepo.ConvertToSHA1(commitID)
return gitRepo.ConvertToGitID(commitID)
}
// MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1
func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string {
sha, err := ConvertToSHA1(ctx, repo, commitID)
sha, err := ConvertToObjectID(ctx, repo, commitID)
if err != nil {
return commitID
}

View File

@@ -6,6 +6,7 @@ package utils
import (
"fmt"
"net/http"
"strconv"
"strings"
"code.gitea.io/gitea/models/db"
@@ -157,6 +158,7 @@ func pullHook(events []string, event string) bool {
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
var isSystemWebhook bool
if !checkCreateHookOption(ctx, form) {
return nil, false
}
@@ -164,13 +166,22 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
if len(form.Events) == 0 {
form.Events = []string{"push"}
}
if form.Config["is_system_webhook"] != "" {
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "", "Invalid is_system_webhook value")
return nil, false
}
isSystemWebhook = sw
}
w := &webhook.Webhook{
OwnerID: ownerID,
RepoID: repoID,
URL: form.Config["url"],
ContentType: webhook.ToHookContentType(form.Config["content_type"]),
Secret: form.Config["secret"],
HTTPMethod: "POST",
OwnerID: ownerID,
RepoID: repoID,
URL: form.Config["url"],
ContentType: webhook.ToHookContentType(form.Config["content_type"]),
Secret: form.Config["secret"],
HTTPMethod: "POST",
IsSystemWebhook: isSystemWebhook,
HookEvent: &webhook_module.HookEvent{
ChooseEvents: true,
HookEvents: webhook_module.HookEvents{

View File

@@ -37,7 +37,6 @@ func InitDBEngine(ctx context.Context) (err error) {
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second))
time.Sleep(setting.Database.DBConnectBackoff)
}
db.HasEngine = true
config.SetDynGetter(system_model.NewDatabaseDynKeyGetter())
return nil
}

View File

@@ -32,8 +32,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
case "markdown":
// Raw markdown
if err := markdown.RenderRaw(&markup.RenderContext{
Ctx: ctx,
URLPrefix: urlPrefix,
Ctx: ctx,
Links: markup.Links{
Base: urlPrefix,
},
}, strings.NewReader(text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
}
@@ -75,8 +77,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
}
if err := markup.Render(&markup.RenderContext{
Ctx: ctx,
URLPrefix: urlPrefix,
Ctx: ctx,
Links: markup.Links{
Base: urlPrefix,
},
Metas: meta,
IsWiki: wiki,
Type: markupType,

View File

@@ -118,7 +118,7 @@ func InitWebInstalled(ctx context.Context) {
mustInit(storage.Init)
mailer.NewContext(ctx)
mustInit(cache.NewContext)
mustInit(cache.Init)
mustInit(feed_service.Init)
mustInit(uinotification.Init)
mustInitCtx(ctx, archiver.Init)

View File

@@ -159,8 +159,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
}
// If we've pushed a branch (and not deleted it)
if newCommitID != git.EmptySHA && refFullName.IsBranch() {
if git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
if repo == nil {
repo = loadRepository(ctx, ownerName, repoName)

View File

@@ -145,8 +145,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo
objectFormat, _ := gitRepo.GetObjectFormat()
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
@@ -174,7 +175,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
// First of all we need to enforce absolutely:
//
// 1. Detect and prevent deletion of the branch
if newCommitID == git.EmptySHA {
if newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from deletion", branchName),
@@ -183,7 +184,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
// 2. Disallow force pushes to protected branches
if git.EmptySHA != oldCommitID {
if oldCommitID != objectFormat.EmptyObjectID().String() {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env})
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)

View File

@@ -29,7 +29,8 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []
}()
var command *git.Command
if oldCommitID == git.EmptySHA {
objectFormat, _ := repo.GetObjectFormat()
if oldCommitID == objectFormat.EmptyObjectID().String() {
// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all":
// List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits
// So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
@@ -82,7 +83,8 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
hash := git.MustIDFromString(sha)
commitID := git.MustIDFromString(sha)
return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha).
Run(&git.RunOpts{
@@ -91,7 +93,7 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
Stdout: stdoutWriter,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
commit, err := git.CommitFromReader(repo, hash, stdoutReader)
commit, err := git.CommitFromReader(repo, commitID, stdoutReader)
if err != nil {
return err
}

View File

@@ -22,14 +22,17 @@ func TestVerifyCommits(t *testing.T) {
defer gitRepo.Close()
assert.NoError(t, err)
objectFormat, err := gitRepo.GetObjectFormat()
assert.NoError(t, err)
testCases := []struct {
base, head string
verified bool
}{
{"72920278f2f999e3005801e5d5b8ab8139d3641c", "d766f2917716d45be24bfa968b8409544941be32", true},
{git.EmptySHA, "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
{objectFormat.EmptyObjectID().String(), "93eac826f6188f34646cea81bf426aa5ba7d3bfe", true}, // New branch with verified commit
{"9779d17a04f1e2640583d35703c62460b2d86e0a", "72920278f2f999e3005801e5d5b8ab8139d3641c", false},
{git.EmptySHA, "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
{objectFormat.EmptyObjectID().String(), "9ce3f779ae33f31fce17fac3c512047b75d7498b", false}, // New branch with unverified commit
}
for _, tc := range testCases {

View File

@@ -12,6 +12,7 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
@@ -27,6 +28,7 @@ import (
const (
tplDashboard base.TplName = "admin/dashboard"
tplSelfCheck base.TplName = "admin/self_check"
tplCron base.TplName = "admin/cron"
tplQueue base.TplName = "admin/queue"
tplStacktrace base.TplName = "admin/stacktrace"
@@ -172,6 +174,33 @@ func DashboardPost(ctx *context.Context) {
}
}
func SelfCheck(ctx *context.Context) {
ctx.Data["PageIsAdminSelfCheck"] = true
r, err := db.CheckCollationsDefaultEngine()
if err != nil {
ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true)
}
if r != nil {
ctx.Data["DatabaseType"] = setting.Database.Type
ctx.Data["DatabaseCheckResult"] = r
hasProblem := false
if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) {
ctx.Data["DatabaseCheckCollationMismatch"] = true
hasProblem = true
}
if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
ctx.Data["DatabaseCheckCollationCaseInsensitive"] = true
hasProblem = true
}
ctx.Data["DatabaseCheckInconsistentCollationColumns"] = r.InconsistentCollationColumns
hasProblem = hasProblem || len(r.InconsistentCollationColumns) > 0
ctx.Data["DatabaseCheckHasProblems"] = hasProblem
}
ctx.HTML(http.StatusOK, tplSelfCheck)
}
func CronTasks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
ctx.Data["PageIsAdminMonitorCron"] = true

View File

@@ -58,4 +58,11 @@ func MonitorDiagnosis(ctx *context.Context) {
return
}
_ = pprof.Lookup("goroutine").WriteTo(f, 1)
f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "heap.dat", Method: zip.Deflate, Modified: time.Now()})
if err != nil {
ctx.ServerError("Failed to create zip file", err)
return
}
_ = pprof.Lookup("heap").WriteTo(f, 0)
}

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"strconv"
"code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -55,7 +56,7 @@ func DeleteNotices(ctx *context.Context) {
}
}
if err := system_model.DeleteNoticesByIDs(ctx, ids); err != nil {
if err := db.DeleteByIDs[system_model.Notice](ctx, ids...); err != nil {
ctx.Flash.Error("DeleteNoticesByIDs: " + err.Error())
ctx.Status(http.StatusInternalServerError)
} else {

View File

@@ -436,6 +436,12 @@ func EditUserPost(ctx *context.Context) {
}
// Check whether user is the last admin
if !form.Admin && user_model.IsLastAdminUser(ctx, u) {
ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
return
}
u.LoginName = form.LoginName
u.FullName = form.FullName
emailChanged := !strings.EqualFold(u.Email, form.Email)
@@ -503,7 +509,10 @@ func DeleteUser(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
default:
ctx.ServerError("DeleteUser", err)
}

View File

@@ -45,10 +45,6 @@ const (
// autoSignIn reads cookie and try to auto-login.
func autoSignIn(ctx *context.Context) (bool, error) {
if !db.HasEngine {
return false, nil
}
isSucceed := false
defer func() {
if !isSucceed {
@@ -145,7 +141,11 @@ func CheckAutoLogin(ctx *context.Context) bool {
if isSucceed {
middleware.DeleteRedirectToCookie(ctx.Resp)
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL)
if setting.LandingPageURL == setting.LandingPageLogin {
nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page
}
ctx.RedirectToFirst(redirectTo, nextRedirectTo)
return true
}
@@ -368,14 +368,14 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
return setting.AppSubURL + "/"
}
func getUserName(gothUser *goth.User) string {
func getUserName(gothUser *goth.User) (string, error) {
switch setting.OAuth2Client.Username {
case setting.OAuth2UsernameEmail:
return strings.Split(gothUser.Email, "@")[0]
return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
case setting.OAuth2UsernameNickname:
return gothUser.NickName
return user_model.NormalizeUserName(gothUser.NickName)
default: // OAuth2UsernameUserid
return gothUser.UserID
return gothUser.UserID, nil
}
}
@@ -622,10 +622,8 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
ctx.HTML(http.StatusOK, TplActivate)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
return false
}
@@ -645,16 +643,14 @@ func Activate(ctx *context.Context) {
}
// Resend confirmation email.
if setting.Service.RegisterEmailConfirm {
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName) {
if ctx.Cache.IsExist("MailResendLimit_" + 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 setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
} else {
@@ -789,7 +785,7 @@ func ActivateEmail(ctx *context.Context) {
if u, err := user_model.GetUserByID(ctx, email.UID); err != nil {
log.Warn("GetUserByID: %d", email.UID)
} else if setting.CacheService.Enabled {
} else {
// Allow user to validate more emails
_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
}

View File

@@ -55,7 +55,11 @@ func LinkAccount(ctx *context.Context) {
}
gu, _ := gothUser.(goth.User)
uname := getUserName(&gu)
uname, err := getUserName(&gu)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
email := gu.Email
ctx.Data["user_name"] = uname
ctx.Data["email"] = email

View File

@@ -970,8 +970,13 @@ func SignInOAuthCallback(ctx *context.Context) {
ctx.ServerError("CreateUser", err)
return
}
uname, err := getUserName(&gothUser)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
u = &user_model.User{
Name: getUserName(&gothUser),
Name: uname,
FullName: gothUser.Name,
Email: gothUser.Email,
LoginType: auth.OAuth2,

View File

@@ -79,7 +79,7 @@ func ForgotPasswdPost(ctx *context.Context) {
return
}
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+u.LowerName) {
if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
ctx.Data["ResendLimited"] = true
ctx.HTML(http.StatusOK, tplForgotPassword)
return
@@ -87,10 +87,8 @@ func ForgotPasswdPost(ctx *context.Context) {
mailer.SendResetPasswordMail(u)
if setting.CacheService.Enabled {
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)

View File

@@ -51,9 +51,11 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
// If rendering fails, the original markdown text is returned
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
markdownCtx := &markup.RenderContext{
Ctx: ctx,
URLPrefix: act.GetRepoLink(ctx),
Type: markdown.MarkupName,
Ctx: ctx,
Links: markup.Links{
Base: act.GetRepoLink(ctx),
},
Type: markdown.MarkupName,
Metas: map[string]string{
"user": act.GetRepoUserName(ctx),
"repo": act.GetRepoName(ctx),
@@ -199,7 +201,6 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act)
repoLink := act.GetRepoAbsoluteLink(ctx)
for _, commit := range push.Commits {
if len(desc) != 0 {
@@ -208,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
commit.Sha1,
templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
templates.RenderCommitMessage(ctx, commit.Message, nil),
)
}
@@ -288,9 +289,11 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
link := &feeds.Link{Href: rel.HTMLURL()}
content, err = markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: rel.Repo.Link(),
Metas: rel.Repo.ComposeMetas(ctx),
Ctx: ctx,
Links: markup.Links{
Base: rel.Repo.Link(),
},
Metas: rel.Repo.ComposeMetas(ctx),
}, rel.Note)
if err != nil {

View File

@@ -42,8 +42,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
}
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: ctx.ContextUser.HTMLURL(),
Ctx: ctx,
Links: markup.Links{
Base: ctx.ContextUser.HTMLURL(),
},
Metas: map[string]string{
"user": ctx.ContextUser.GetDisplayName(),
},

View File

@@ -6,6 +6,7 @@ package feed
import (
"time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
@@ -14,8 +15,9 @@ import (
// shows tags and/or releases on the repo as RSS / Atom feed
func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) {
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeTags: !isReleasesOnly,
RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)

View File

@@ -28,16 +28,16 @@ func requireSignIn(ctx *context.Context) {
func gitHTTPRouters(m *web.Route) {
m.Group("", func() {
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
m.GetOptions("/info/refs", repo.GetInfoRefs)
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
m.Methods("GET,OPTIONS", "/info/refs", repo.GetInfoRefs)
m.Methods("GET,OPTIONS", "/HEAD", repo.GetTextFile("HEAD"))
m.Methods("GET,OPTIONS", "/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks)
m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
}

View File

@@ -121,10 +121,6 @@ func checkDatabase(ctx context.Context, checks checks) status {
// cache checks gitea cache status
func checkCache(checks checks) status {
if !setting.CacheService.Enabled {
return pass
}
st := componentStatus{}
if err := cache.GetCache().Ping(); err != nil {
st.Status = fail

View File

@@ -33,10 +33,6 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
func DummyBadRequest(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
if ok, _ := util.IsExist(robotsTxt); !ok {

View File

@@ -5,6 +5,7 @@ package org
import (
"net/http"
"path"
"strings"
"code.gitea.io/gitea/models/db"
@@ -18,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
)
@@ -46,10 +48,8 @@ func Home(ctx *context.Context) {
ctx.Data["Title"] = org.DisplayName()
if len(org.Description) != 0 {
desc, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: ctx.Repo.RepoLink,
Metas: map[string]string{"mode": "document"},
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Metas: map[string]string{"mode": "document"},
}, org.Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -157,14 +157,14 @@ func Home(ctx *context.Context) {
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
defer profileClose()
prepareOrgProfileReadme(ctx, profileGitRepo, profileReadmeBlob)
prepareOrgProfileReadme(ctx, profileGitRepo, profileDbRepo, profileReadmeBlob)
ctx.HTML(http.StatusOK, tplOrgHome)
}
func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileReadme *git.Blob) {
func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
if profileGitRepo == nil || profileReadme == nil {
return
}
@@ -175,7 +175,13 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Metas: map[string]string{"mode": "document"},
Links: markup.Links{
// Pass repo link to markdown render for the full link of media elements.
// The profile of default branch would be shown.
Base: profileDbRepo.Link(),
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
},
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {

View File

@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/convert"
@@ -77,6 +78,7 @@ func List(ctx *context.Context) {
// Get all runner labels
runners, err := db.Find[actions_model.ActionRunner](ctx, actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
IsOnline: util.OptionalBoolTrue,
WithAvailable: true,
})
if err != nil {
@@ -113,7 +115,7 @@ func List(ctx *context.Context) {
continue
}
if !allRunnerLabels.Contains(ro) {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro)
break
}
}
@@ -200,6 +202,7 @@ func List(ctx *context.Context) {
pager.AddParamString("actor", fmt.Sprint(actorID))
pager.AddParamString("status", fmt.Sprint(status))
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
ctx.HTML(http.StatusOK, tplListActions)
}

View File

@@ -114,24 +114,29 @@ func RefBlame(ctx *context.Context) {
return
}
commitNames, previousCommits := processBlameParts(ctx, result.Parts)
commitNames := processBlameParts(ctx, result.Parts)
if ctx.Written() {
return
}
renderBlame(ctx, result.Parts, commitNames, previousCommits)
renderBlame(ctx, result.Parts, commitNames)
ctx.HTML(http.StatusOK, tplRepoHome)
}
type blameResult struct {
Parts []git.BlamePart
Parts []*git.BlamePart
UsesIgnoreRevs bool
FaultyIgnoreRevsFile bool
}
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
blameReader, err := git.CreateBlameReader(ctx, repoPath, commit, file, bypassBlameIgnore)
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
}
@@ -147,7 +152,7 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
if len(r.Parts) == 0 && r.UsesIgnoreRevs {
// try again without ignored revs
blameReader, err = git.CreateBlameReader(ctx, repoPath, commit, file, true)
blameReader, err = git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, true)
if err != nil {
return nil, err
}
@@ -170,7 +175,9 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
func fillBlameResult(br *git.BlameReader, r *blameResult) error {
r.UsesIgnoreRevs = br.UsesIgnoreRevs()
r.Parts = make([]git.BlamePart, 0, 5)
previousHelper := make(map[string]*git.BlamePart)
r.Parts = make([]*git.BlamePart, 0, 5)
for {
blamePart, err := br.NextPart()
if err != nil {
@@ -179,18 +186,25 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
if blamePart == nil {
break
}
r.Parts = append(r.Parts, *blamePart)
if prev, ok := previousHelper[blamePart.Sha]; ok {
if blamePart.PreviousSha == "" {
blamePart.PreviousSha = prev.PreviousSha
blamePart.PreviousPath = prev.PreviousPath
}
} else {
previousHelper[blamePart.Sha] = blamePart
}
r.Parts = append(r.Parts, blamePart)
}
return nil
}
func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]*user_model.UserCommit, map[string]string) {
func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[string]*user_model.UserCommit {
// store commit data by SHA to look up avatar info etc
commitNames := make(map[string]*user_model.UserCommit)
// previousCommits contains links from SHA to parent SHA,
// if parent also contains the current TreePath.
previousCommits := make(map[string]string)
// and as blameParts can reference the same commits multiple
// times, we cache the lookup work locally
commits := make([]*git.Commit, 0, len(blameParts))
@@ -214,29 +228,11 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[st
} else {
ctx.ServerError("Repo.GitRepo.GetCommit", err)
}
return nil, nil
return nil
}
commitCache[sha] = commit
}
// find parent commit
if commit.ParentCount() > 0 {
psha := commit.Parents[0]
previousCommit, ok := commitCache[psha.String()]
if !ok {
previousCommit, _ = commit.Parent(0)
if previousCommit != nil {
commitCache[psha.String()] = previousCommit
}
}
// only store parent commit ONCE, if it has the file
if previousCommit != nil {
if haz1, _ := previousCommit.HasFile(ctx.Repo.TreePath); haz1 {
previousCommits[commit.ID.String()] = previousCommit.ID.String()
}
}
}
commits = append(commits, commit)
}
@@ -245,10 +241,10 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[st
commitNames[c.ID.String()] = c
}
return commitNames, previousCommits
return commitNames
}
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit, previousCommits map[string]string) {
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink
language := ""
@@ -295,7 +291,6 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
}
commit := commitNames[part.Sha]
previousSha := previousCommits[part.Sha]
if index == 0 {
// Count commit number
commitCnt++
@@ -313,8 +308,8 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
br.Avatar = gotemplate.HTML(avatar)
br.RepoLink = repoLink
br.PartSha = part.Sha
br.PreviousSha = previousSha
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(previousSha), util.PathEscapeSegments(ctx.Repo.TreePath))
br.PreviousSha = part.PreviousSha
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath))
br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha))
br.CommitMessage = commit.CommitMessage
br.CommitSince = commitSince
@@ -332,8 +327,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
lexerName = lexerNameForLine
}
br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
br.Code = gotemplate.HTML(line)
br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus)
}

View File

@@ -147,11 +147,18 @@ func RestoreBranchPost(ctx *context.Context) {
return
}
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
if err != nil {
log.Error("RestoreBranch: CreateBranch: %w", err)
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
return
}
// Don't return error below this
if err := repo_service.PushUpdate(
&repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: git.EmptySHA,
OldCommitID: objectFormat.EmptyObjectID().String(),
NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
@@ -191,9 +198,9 @@ func CreateBranch(ctx *context.Context) {
}
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
} else if ctx.Repo.IsViewBranch {
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName)
} else {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName)
}
if err != nil {
if models.IsErrProtectedTagName(err) {

View File

@@ -7,7 +7,9 @@ package repo
import (
"errors"
"fmt"
"html/template"
"net/http"
"path"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -21,7 +23,9 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitgraph"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
git_service "code.gitea.io/gitea/services/repository"
)
@@ -294,7 +298,7 @@ func Diff(ctx *context.Context) {
}
return
}
if len(commitID) != git.SHAFullLength {
if len(commitID) != commit.ID.Type().FullLength() {
commitID = commit.ID.String()
}
@@ -370,9 +374,21 @@ func Diff(ctx *context.Context) {
note := &git.Note{}
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
if err == nil {
ctx.Data["Note"] = string(charset.ToUTF8WithFallback(note.Message))
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message))))
if err != nil {
ctx.ServerError("RenderCommitMessage", err)
return
}
}
ctx.Data["BranchName"], err = commit.GetBranchName()

View File

@@ -310,13 +310,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
ci.BaseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = ci.BaseBranch
baseIsCommit = true
} else if ci.BaseBranch == git.EmptySHA {
} else if ci.BaseBranch == objectFormat.EmptyObjectID().String() {
if isSameRepo {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
} else {
@@ -844,6 +845,7 @@ func CompareDiff(ctx *context.Context) {
}
}
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanWrite(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")

View File

@@ -329,7 +329,7 @@ func dummyInfoRefs(ctx *context.Context) {
}
}()
if err := git.InitRepository(ctx, tmpDir, true); err != nil {
if err := git.InitRepository(ctx, tmpDir, true, git.Sha1ObjectFormat.Name()); err != nil {
log.Error("Failed to init bare repo for git-receive-pack cache: %v", err)
return
}

View File

@@ -237,10 +237,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
}
isShowClosed := ctx.FormString("state") == "closed"
// if open issues are zero and close don't, use closed as default
var isShowClosed util.OptionalBool
switch ctx.FormString("state") {
case "closed":
isShowClosed = util.OptionalBoolTrue
case "all":
isShowClosed = util.OptionalBoolNone
default:
isShowClosed = util.OptionalBoolFalse
}
// if there are closed issues and no open issues, default to showing all issues
if len(ctx.FormString("state")) == 0 && issueStats.OpenCount == 0 && issueStats.ClosedCount != 0 {
isShowClosed = true
isShowClosed = util.OptionalBoolNone
}
if repo.IsTimetrackerEnabled(ctx) {
@@ -260,10 +268,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
var total int
if !isShowClosed {
total = int(issueStats.OpenCount)
} else {
switch isShowClosed {
case util.OptionalBoolTrue:
total = int(issueStats.ClosedCount)
case util.OptionalBoolNone:
total = int(issueStats.OpenCount + issueStats.ClosedCount)
default:
total = int(issueStats.OpenCount)
}
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
@@ -282,7 +293,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ReviewedID: reviewedID,
MilestoneIDs: mileIDs,
ProjectID: projectID,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsClosed: isShowClosed,
IsPull: isPullOption,
LabelIDs: labelIDs,
SortType: sortType,
@@ -428,6 +439,9 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["OpenCount"] = issueStats.OpenCount
ctx.Data["ClosedCount"] = issueStats.ClosedCount
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived)
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
mentionedID, projectID, assigneeID, posterID, archived)
@@ -442,11 +456,13 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
ctx.Data["ProjectID"] = projectID
ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["Keyword"] = keyword
if isShowClosed {
switch isShowClosed {
case util.OptionalBoolTrue:
ctx.Data["State"] = "closed"
} else {
case util.OptionalBoolNone:
ctx.Data["State"] = "all"
default:
ctx.Data["State"] = "open"
}
ctx.Data["ShowArchivedLabels"] = archived
@@ -510,9 +526,8 @@ func Issues(ctx *context.Context) {
func renderMilestones(ctx *context.Context) {
// Get milestones
milestones, _, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: ctx.Repo.Repository.ID,
State: api.StateAll,
})
if err != nil {
ctx.ServerError("GetAllRepoMilestones", err)
@@ -534,17 +549,17 @@ func renderMilestones(ctx *context.Context) {
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) {
var err error
ctx.Data["OpenMilestones"], _, err = issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
RepoID: repo.ID,
State: api.StateOpen,
ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
IsClosed: util.OptionalBoolFalse,
})
if err != nil {
ctx.ServerError("GetMilestones", err)
return
}
ctx.Data["ClosedMilestones"], _, err = issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
RepoID: repo.ID,
State: api.StateClosed,
ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
RepoID: repo.ID,
IsClosed: util.OptionalBoolTrue,
})
if err != nil {
ctx.ServerError("GetMilestones", err)
@@ -1319,7 +1334,7 @@ func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *use
return roleDescriptor, err
} else if hasMergedPR {
roleDescriptor.RoleInRepo = issues_model.RoleRepoContributor
} else {
} else if issue.IsPull {
// only display first time contributor in the first opening pull request
roleDescriptor.RoleInRepo = issues_model.RoleRepoFirstTimeContributor
}
@@ -1437,12 +1452,13 @@ func ViewIssue(ctx *context.Context) {
}
}
ctx.Data["IssueWatch"] = iw
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1602,10 +1618,12 @@ func ViewIssue(ctx *context.Context) {
}
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1679,10 +1697,12 @@ func ViewIssue(ctx *context.Context) {
}
} else if comment.Type.HasContentSupport() {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1963,7 +1983,7 @@ func ViewIssue(ctx *context.Context) {
return
}
ctx.Data["BlockingDependencies"], ctx.Data["BlockingByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
if ctx.Written() {
return
}
@@ -2024,38 +2044,34 @@ func ViewIssue(ctx *context.Context) {
// checkBlockedByIssues return canRead and notPermitted
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
var (
lastRepoID int64
lastPerm access_model.Permission
)
for i, blocker := range blockers {
repoPerms := make(map[int64]access_model.Permission)
repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for _, blocker := range blockers {
// Get the permissions for this repository
perm := lastPerm
if lastRepoID != blocker.Repository.ID {
if blocker.Repository.ID == ctx.Repo.Repository.ID {
perm = ctx.Repo.Permission
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil
}
// If the repo ID exists in the map, return the exist permissions
// else get the permission and add it to the map
var perm access_model.Permission
existPerm, ok := repoPerms[blocker.RepoID]
if ok {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil
}
lastRepoID = blocker.Repository.ID
repoPerms[blocker.RepoID] = perm
}
// check permission
if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
blockers[len(notPermitted)], blockers[i] = blocker, blockers[len(notPermitted)]
notPermitted = blockers[:len(notPermitted)+1]
if perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
canRead = append(canRead, blocker)
} else {
notPermitted = append(notPermitted, blocker)
}
}
blockers = blockers[len(notPermitted):]
sortDependencyInfo(blockers)
sortDependencyInfo(canRead)
sortDependencyInfo(notPermitted)
return blockers, notPermitted
return canRead, notPermitted
}
func sortDependencyInfo(blockers []*issues_model.DependencyInfo) {
@@ -2239,10 +2255,12 @@ func UpdateIssueContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -3106,6 +3124,11 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Error(http.StatusForbidden)
return
@@ -3143,10 +3166,12 @@ func UpdateCommentContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -3172,6 +3197,11 @@ func DeleteComment(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Error(http.StatusForbidden)
return
@@ -3298,6 +3328,11 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull)) {
if log.IsTrace() {
if ctx.IsSigned {
@@ -3441,6 +3476,21 @@ func GetCommentAttachments(ctx *context.Context) {
return
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.NotFoundOrServerError("LoadIssue", issues_model.IsErrIssueNotExist, err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound("CanReadIssuesOrPulls", issues_model.ErrCommentNotExist{})
return
}
if !comment.Type.HasAttachmentSupport() {
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
return

View File

@@ -122,7 +122,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
}
historyID := ctx.FormInt64("history_id")
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, historyID)
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, issue.ID, historyID)
if err != nil {
ctx.JSON(http.StatusNotFound, map[string]any{
"message": "Can not find the content history",
@@ -193,15 +193,29 @@ func SoftDeleteContentHistory(ctx *context.Context) {
var comment *issues_model.Comment
var history *issues_model.ContentHistory
var err error
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
log.Error("can not get issue content history %v. err=%v", historyID, err)
return
}
if history.IssueID != issue.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if commentID != 0 {
if history.CommentID != commentID {
ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{})
return
}
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
}
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
log.Error("can not get issue content history %v. err=%v", historyID, err)
return
if comment.IssueID != issue.ID {
ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{})
return
}
}
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)

View File

@@ -90,6 +90,12 @@ func IssuePinMove(ctx *context.Context) {
return
}
if issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
log.Error("Issue does not belong to this repository")
return
}
err = issue.MovePin(ctx, form.Position)
if err != nil {
ctx.Status(http.StatusInternalServerError)

View File

@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -46,18 +45,13 @@ func Milestones(ctx *context.Context) {
page = 1
}
state := structs.StateOpen
if isShowClosed {
state = structs.StateClosed
}
miles, total, err := issues_model.GetMilestones(ctx, issues_model.GetMilestonesOption{
miles, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoID: ctx.Repo.Repository.ID,
State: state,
IsClosed: util.OptionalBoolOf(isShowClosed),
SortType: sortType,
Name: keyword,
})
@@ -80,17 +74,19 @@ func Milestones(ctx *context.Context) {
url.QueryEscape(keyword), url.QueryEscape(sortType))
if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) {
if err := miles.LoadTotalTrackedTimes(ctx); err != nil {
if err := issues_model.MilestoneList(miles).LoadTotalTrackedTimes(ctx); err != nil {
ctx.ServerError("LoadTotalTrackedTimes", err)
return
}
}
for _, m := range miles {
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, m.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -281,10 +277,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
}
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, milestone.Content)
if err != nil {
ctx.ServerError("RenderString", err)

View File

@@ -90,10 +90,12 @@ func Projects(ctx *context.Context) {
for i := range projects {
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, projects[i].Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -357,10 +359,12 @@ func ViewProject(ctx *context.Context) {
ctx.Data["LinkedPRs"] = linkedPrsMap
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, project.Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -468,7 +472,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)

View File

@@ -653,7 +653,15 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
if pb != nil && pb.EnableStatusCheck {
ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pb.StatusCheckContexts {
if gp, err := glob.Compile(c); err == nil && gp.Match(context) {
if c == context {
return true
}
if gp, err := glob.Compile(c); err != nil {
// All newly created status_check_contexts are checked to ensure they are valid glob expressions before being stored in the database.
// But some old status_check_context created before glob was introduced may be invalid glob expressions.
// So log the error here for debugging.
log.Error("compile glob %q: %v", c, err)
} else if gp.Match(context) {
return true
}
}
@@ -1141,30 +1149,28 @@ func MergePullRequest(ctx *context.Context) {
switch {
case errors.Is(err, pull_service.ErrIsClosed):
if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
} else {
ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
}
case errors.Is(err, pull_service.ErrUserNotAllowedToMerge):
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
case errors.Is(err, pull_service.ErrHasMerged):
ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
case errors.Is(err, pull_service.ErrIsWorkInProgress):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
case errors.Is(err, pull_service.ErrNotMergableState):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case models.IsErrDisallowedToMerge(err):
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case asymkey_service.IsErrWontSign(err):
ctx.Flash.Error(err.Error()) // has no translation ...
ctx.JSONError(err.Error()) // has no translation ...
case errors.Is(err, pull_service.ErrDependenciesLeft):
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
default:
ctx.ServerError("WebCheck", err)
return
}
ctx.Redirect(issue.Link())
return
}
@@ -1172,18 +1178,18 @@ func MergePullRequest(ctx *context.Context) {
if manuallyMerged {
if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
switch {
case models.IsErrInvalidMergeStyle(err):
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
case strings.Contains(err.Error(), "Wrong commit ID"):
ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
default:
ctx.ServerError("MergedManually", err)
return
}
return
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
return
}
@@ -1213,15 +1219,14 @@ func MergePullRequest(ctx *context.Context) {
} else if scheduled {
// nothing more to do ...
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
return
}
}
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(issue.Link())
ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1234,7 +1239,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
ctx.Flash.Error(flashError)
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1278,7 +1283,7 @@ func MergePullRequest(ctx *context.Context) {
}
ctx.Flash.Error(flashError)
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
} else {
ctx.ServerError("Merge", err)
}
@@ -1287,7 +1292,7 @@ func MergePullRequest(ctx *context.Context) {
log.Trace("Pull request merged: %d", pr.ID)
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
ctx.ServerError("CreateOrStopIssueStopwatch", err)
ctx.ServerError("stopTimerIfAvailable", err)
return
}
@@ -1301,7 +1306,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
if exist {
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
return
}
@@ -1319,7 +1324,7 @@ func MergePullRequest(ctx *context.Context) {
deleteBranch(ctx, pr, headRepo)
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
}
// CancelAutoMergePullRequest cancels a scheduled pr
@@ -1379,7 +1384,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() {
return
}
@@ -1457,6 +1462,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
if projectID > 0 {
if !ctx.Repo.CanWrite(unit.TypeProjects) {
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
return
}
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
}
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
ctx.JSONRedirect(pullIssue.Link())
}
@@ -1571,6 +1587,12 @@ func CleanUpPullRequest(ctx *context.Context) {
func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) {
fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
return
}
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil {
switch {
case git.IsErrBranchNotExist(err):

View File

@@ -95,9 +95,10 @@ func Releases(ctx *context.Context) {
ListOptions: listOptions,
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess,
RepoID: ctx.Repo.Repository.ID,
}
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
return
@@ -135,10 +136,12 @@ func Releases(ctx *context.Context) {
}
r.Note, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, r.Note)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -174,6 +177,7 @@ func TagsList(ctx *context.Context) {
// Disable the showCreateNewBranch form in the dropdown on this page.
ctx.Data["CanCreateBranch"] = false
ctx.Data["HideBranchesInDropdown"] = true
ctx.Data["CanCreateRelease"] = ctx.Repo.CanWrite(unit.TypeReleases) && !ctx.Repo.Repository.IsArchived
listOptions := db.ListOptions{
Page: ctx.FormInt("page"),
@@ -193,9 +197,10 @@ func TagsList(ctx *context.Context) {
IncludeDrafts: true,
IncludeTags: true,
HasSha1: util.OptionalBoolTrue,
RepoID: ctx.Repo.Repository.ID,
}
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
return
@@ -284,10 +289,12 @@ func SingleRelease(ctx *context.Context) {
}
}
release.Note, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, release.Note)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -616,7 +623,27 @@ func DeleteTag(ctx *context.Context) {
}
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil {
redirect := func() {
if isDelTag {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
return
}
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
}
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound("GetReleaseForRepoByID", err)
} else {
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
redirect()
}
return
}
if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
} else {
@@ -630,10 +657,5 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
}
}
if isDelTag {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
return
}
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
redirect()
}

View File

@@ -6,6 +6,7 @@ package repo
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
@@ -74,8 +75,9 @@ func TestCalReleaseNumCommitsBehind(t *testing.T) {
contexttest.LoadGitRepo(t, ctx)
t.Cleanup(func() { ctx.Repo.GitRepo.Close() })
releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
RepoID: ctx.Repo.Repository.ID,
})
assert.NoError(t, err)

View File

@@ -57,16 +57,15 @@ func RenderFile(ctx *context.Context) {
return
}
treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
if ctx.Repo.TreePath != "" {
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
}
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
err = markup.Render(&markup.RenderContext{
Ctx: ctx,
RelativePath: ctx.Repo.TreePath,
URLPrefix: path.Dir(treeLink),
Ctx: ctx,
RelativePath: ctx.Repo.TreePath,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath),
},
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
InStandalonePage: true,

View File

@@ -159,6 +159,7 @@ func Create(ctx *context.Context) {
ctx.Data["private"] = getRepoPrivate(ctx)
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
ctx.Data["default_branch"] = setting.Repository.DefaultBranch
ctx.Data["hash_type"] = "sha1"
ctxUser := checkContextUser(ctx, ctx.FormInt64("org"))
if ctx.Written() {
@@ -277,17 +278,18 @@ func CreatePost(ctx *context.Context) {
}
} else {
repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
Name: form.RepoName,
Description: form.Description,
Gitignores: form.Gitignores,
IssueLabels: form.IssueLabels,
License: form.License,
Readme: form.Readme,
IsPrivate: form.Private || setting.Repository.ForcePrivate,
DefaultBranch: form.DefaultBranch,
AutoInit: form.AutoInit,
IsTemplate: form.Template,
TrustModel: repo_model.ToTrustModel(form.TrustModel),
Name: form.RepoName,
Description: form.Description,
Gitignores: form.Gitignores,
IssueLabels: form.IssueLabels,
License: form.License,
Readme: form.Readme,
IsPrivate: form.Private || setting.Repository.ForcePrivate,
DefaultBranch: form.DefaultBranch,
AutoInit: form.AutoInit,
IsTemplate: form.Template,
TrustModel: repo_model.DefaultTrustModel,
ObjectFormatName: form.ObjectFormatName,
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
@@ -377,7 +379,10 @@ func RedirectDownload(ctx *context.Context) {
)
tagNames := []string{vTag}
curRepo := ctx.Repo.Repository
releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames)
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: curRepo.ID,
TagNames: tagNames,
})
if err != nil {
ctx.ServerError("RedirectDownload", err)
return
@@ -606,7 +611,7 @@ func SearchRepo(ctx *context.Context) {
}
// call the database O(1) times to get the commit statuses for all repos
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{})
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatusForPairs: %v", err)
return

View File

@@ -6,13 +6,12 @@ package setting
import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
git_model "code.gitea.io/gitea/models/git"
"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"
notify_service "code.gitea.io/gitea/services/notify"
repo_service "code.gitea.io/gitea/services/repository"
)
// SetDefaultBranchPost set default branch
@@ -35,23 +34,14 @@ func SetDefaultBranchPost(ctx *context.Context) {
}
branch := ctx.FormString("branch")
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 {
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)
return
}
notify_service.ChangeDefaultBranch(ctx, repo)
return
}
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)

View File

@@ -388,20 +388,21 @@ func LFSFileFind(ctx *context.Context) {
sha := ctx.FormString("sha")
ctx.Data["Title"] = oid
ctx.Data["PageIsSettingsLFS"] = true
var hash git.SHA1
objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
var objectID git.ObjectID
if len(sha) == 0 {
pointer := lfs.Pointer{Oid: oid, Size: size}
hash = git.ComputeBlobHash([]byte(pointer.StringContent()))
sha = hash.String()
objectID = git.ComputeBlobHash(objectFormat, []byte(pointer.StringContent()))
sha = objectID.String()
} else {
hash = git.MustIDFromString(sha)
objectID = git.MustIDFromString(sha)
}
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
ctx.Data["Oid"] = oid
ctx.Data["Size"] = size
ctx.Data["SHA"] = sha
results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, hash)
results, err := pipeline.FindLFSFile(ctx.Repo.GitRepo, objectID)
if err != nil && err != io.EOF {
log.Error("Failure in FindLFSFile: %v", err)
ctx.ServerError("LFSFind: FindLFSFile.", err)

View File

@@ -228,6 +228,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns

View File

@@ -418,7 +418,7 @@ func SettingsPost(ctx *context.Context) {
Interval: interval,
RemoteAddress: remoteAddress,
}
if err := repo_model.InsertPushMirror(ctx, m); err != nil {
if err := db.Insert(ctx, m); err != nil {
ctx.ServerError("InsertPushMirror", err)
return
}
@@ -594,7 +594,7 @@ func SettingsPost(ctx *context.Context) {
return
}
if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.ServerError("UpdateRepositoryUnits", err)
return
}

View File

@@ -15,9 +15,10 @@ import (
)
const (
tplRepoVariables base.TplName = "repo/settings/actions"
tplOrgVariables base.TplName = "org/settings/actions"
tplUserVariables base.TplName = "user/settings/actions"
tplRepoVariables base.TplName = "repo/settings/actions"
tplOrgVariables base.TplName = "org/settings/actions"
tplUserVariables base.TplName = "user/settings/actions"
tplAdminVariables base.TplName = "admin/actions"
)
type variablesCtx struct {
@@ -26,6 +27,7 @@ type variablesCtx struct {
IsRepo bool
IsOrg bool
IsUser bool
IsGlobal bool
VariablesTemplate base.TplName
RedirectLink string
}
@@ -33,6 +35,7 @@ type variablesCtx struct {
func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsRepoSettings"] == true {
return &variablesCtx{
OwnerID: 0,
RepoID: ctx.Repo.Repository.ID,
IsRepo: true,
VariablesTemplate: tplRepoVariables,
@@ -48,6 +51,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
}
return &variablesCtx{
OwnerID: ctx.ContextUser.ID,
RepoID: 0,
IsOrg: true,
VariablesTemplate: tplOrgVariables,
RedirectLink: ctx.Org.OrgLink + "/settings/actions/variables",
@@ -57,12 +61,23 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
if ctx.Data["PageIsUserSettings"] == true {
return &variablesCtx{
OwnerID: ctx.Doer.ID,
RepoID: 0,
IsUser: true,
VariablesTemplate: tplUserVariables,
RedirectLink: setting.AppSubURL + "/user/settings/actions/variables",
}, nil
}
if ctx.Data["PageIsAdmin"] == true {
return &variablesCtx{
OwnerID: 0,
RepoID: 0,
IsGlobal: true,
VariablesTemplate: tplAdminVariables,
RedirectLink: setting.AppSubURL + "/admin/actions/variables",
}, nil
}
return nil, errors.New("unable to set Variables context")
}

View File

@@ -655,8 +655,14 @@ func TestWebhook(ctx *context.Context) {
commit := ctx.Repo.Commit
if commit == nil {
ghost := user_model.NewGhostUser()
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
if err != nil {
ctx.Flash.Error("GetObjectFormatOfRepo: " + err.Error())
ctx.Status(http.StatusInternalServerError)
return
}
commit = &git.Commit{
ID: git.MustIDFromString(git.EmptySHA),
ID: objectFormat.EmptyObjectID(),
Author: ghost.NewGitSig(),
Committer: ghost.NewGitSig(),
CommitMessage: "This is a fake commit",

View File

@@ -9,6 +9,7 @@ import (
gocontext "context"
"encoding/base64"
"fmt"
"html/template"
"image"
"io"
"net/http"
@@ -157,7 +158,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
return "", readmeFile, nil
}
func renderDirectory(ctx *context.Context, treeLink string) {
func renderDirectory(ctx *context.Context) {
entries := renderDirectoryFiles(ctx, 1*time.Second)
if ctx.Written() {
return
@@ -174,7 +175,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
return
}
renderReadmeFile(ctx, subfolder, readmeFile, treeLink)
renderReadmeFile(ctx, subfolder, readmeFile)
}
// localizedExtensions prepends the provided language code with and without a
@@ -258,7 +259,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
}
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry, readmeTreelink string) {
func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
target := readmeFile
if readmeFile != nil && readmeFile.IsLink() {
target, _ = readmeFile.FollowLinks()
@@ -311,25 +312,28 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
Ctx: ctx,
RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
URLPrefix: path.Join(readmeTreelink, subfolder),
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Join(ctx.Repo.TreePath, subfolder),
},
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], _ = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
ctx.Data["FileContent"] = buf.String()
}
} else {
ctx.Data["IsPlainText"] = true
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], err = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
if err != nil {
log.Error("Read failed: %v", err)
delete(ctx.Data, "IsMarkup")
}
}
ctx.Data["FileContent"] = buf.String()
if ctx.Data["IsMarkup"] != true {
ctx.Data["IsPlainText"] = true
content, err := io.ReadAll(rd)
if err != nil {
log.Error("Read readme content failed: %v", err)
}
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
}
if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
@@ -337,7 +341,36 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}
}
func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
// Show latest commit info of repository in table header,
// or of directory if not in root directory.
ctx.Data["LatestCommit"] = latestCommit
if latestCommit != nil {
verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit)
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID)
}, nil); err != nil {
ctx.ServerError("CalculateTrustStatus", err)
return false
}
ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{ListAll: true})
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(statuses)
ctx.Data["LatestCommitStatuses"] = statuses
}
return true
}
func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["IsViewFile"] = true
ctx.Data["HideRepoInfo"] = true
blob := entry.Blob()
@@ -351,7 +384,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileName"] = blob.Name()
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("GetCommitByPath", err)
return
}
if !loadLatestCommitData(ctx, commit) {
return
}
if ctx.Repo.TreePath == ".editorconfig" {
_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
@@ -479,9 +522,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
Ctx: ctx,
Type: markupType,
RelativePath: ctx.Repo.TreePath,
URLPrefix: path.Dir(treeLink),
Metas: metas,
GitRepo: ctx.Repo.GitRepo,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath),
},
Metas: metas,
GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
ctx.ServerError("Render", err)
@@ -493,7 +540,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
buf, _ := io.ReadAll(rd)
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
// empty: 0 lines; "a": 1 line, 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
// Gitea uses the definition (like most modern editors):
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
@@ -585,9 +632,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
Ctx: ctx,
RelativePath: ctx.Repo.TreePath,
URLPrefix: path.Dir(treeLink),
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
Links: markup.Links{
Base: ctx.Repo.RepoLink,
BranchPath: ctx.Repo.BranchNameSubURL(),
TreePath: path.Dir(ctx.Repo.TreePath),
},
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
ctx.ServerError("Render", err)
@@ -620,7 +671,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
}
}
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) {
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
markupRd, markupWr := io.Pipe()
defer markupWr.Close()
done := make(chan struct{})
@@ -628,7 +679,7 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
sb := &strings.Builder{}
// We allow NBSP here this is rendered
escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP)
output = sb.String()
output = template.HTML(sb.String())
close(done)
}()
err = markup.Render(renderCtx, input, markupWr)
@@ -711,21 +762,14 @@ func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {
}
for _, entry := range allEntries {
if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
ctx.Data["CitiationExist"] = true
// Read Citation file contents
blob := entry.Blob()
dataRc, err := blob.DataAsync()
if err != nil {
ctx.ServerError("DataAsync", err)
return
if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("checkCitationFile: GetBlobContent: %v", err)
} else {
ctx.Data["CitiationExist"] = true
ctx.PageData["citationFileContent"] = content
break
}
defer dataRc.Close()
ctx.PageData["citationFileContent"], err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
if err != nil {
ctx.ServerError("GetBlobContent", err)
return
}
break
}
}
}
@@ -841,29 +885,8 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
return nil
}
// Show latest commit info of repository in table header,
// or of directory if not in root directory.
ctx.Data["LatestCommit"] = latestCommit
if latestCommit != nil {
verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit)
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID)
}, nil); err != nil {
ctx.ServerError("CalculateTrustStatus", err)
return nil
}
ctx.Data["LatestCommitVerification"] = verification
ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit)
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptions{ListAll: true})
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(statuses)
ctx.Data["LatestCommitStatuses"] = statuses
if !loadLatestCommitData(ctx, latestCommit) {
return nil
}
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
@@ -952,14 +975,6 @@ func renderCode(ctx *context.Context) {
}
ctx.Data["Title"] = title
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)
}
// Get Topics of this repo
renderRepoTopics(ctx)
if ctx.Written() {
@@ -984,9 +999,9 @@ func renderCode(ctx *context.Context) {
}
if entry.IsDir() {
renderDirectory(ctx, treeLink)
renderDirectory(ctx)
} else {
renderFile(ctx, entry, treeLink, rawLink)
renderFile(ctx, entry)
}
if ctx.Written() {
return
@@ -1027,6 +1042,12 @@ func renderCode(ctx *context.Context) {
}
ctx.Data["Paths"] = paths
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
treeLink := branchLink
if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
}
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink

View File

@@ -238,10 +238,12 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
}
rctx := &markup.RenderContext{
Ctx: ctx,
URLPrefix: ctx.Repo.RepoLink,
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
IsWiki: true,
Ctx: ctx,
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
Links: markup.Links{
Base: ctx.Repo.RepoLink,
},
IsWiki: true,
}
buf := &strings.Builder{}

View File

@@ -47,10 +47,8 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: map[string]string{"mode": "document"},
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -87,7 +85,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
}
}
func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {
func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {
profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ".profile")
if err == nil {
perm, err := access_model.GetUserRepoPermission(ctx, profileDbRepo, doer)
@@ -105,7 +103,7 @@ func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profile
} else if !repo_model.IsErrRepoNotExist(err) {
log.Error("FindUserProfileReadme failed to GetRepositoryByName: %v", err)
}
return profileGitRepo, profileReadmeBlob, func() {
return profileDbRepo, profileGitRepo, profileReadmeBlob, func() {
if profileGitRepo != nil {
_ = profileGitRepo.Close()
}
@@ -115,7 +113,7 @@ func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profile
func RenderUserHeader(ctx *context.Context) {
prepareContextForCommonProfile(ctx)
_, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer)
_, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer)
defer profileClose()
ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil
}

View File

@@ -26,7 +26,6 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/context"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
@@ -213,13 +212,26 @@ func Milestones(ctx *context.Context) {
}
}
counts, err := issues_model.CountMilestonesByRepoCondAndKw(ctx, userRepoCond, keyword, isShowClosed)
counts, err := issues_model.CountMilestonesMap(ctx, issues_model.FindMilestoneOptions{
RepoCond: userRepoCond,
Name: keyword,
IsClosed: util.OptionalBoolOf(isShowClosed),
})
if err != nil {
ctx.ServerError("CountMilestonesByRepoIDs", err)
return
}
milestones, err := issues_model.SearchMilestones(ctx, repoCond, page, isShowClosed, sortType, keyword)
milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
},
RepoCond: repoCond,
IsClosed: util.OptionalBoolOf(isShowClosed),
SortType: sortType,
Name: keyword,
})
if err != nil {
ctx.ServerError("SearchMilestones", err)
return
@@ -246,9 +258,11 @@ func Milestones(ctx *context.Context) {
}
milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: milestones[i].Repo.Link(),
Metas: milestones[i].Repo.ComposeMetas(ctx),
Ctx: ctx,
Links: markup.Links{
Base: milestones[i].Repo.Link(),
},
Metas: milestones[i].Repo.ComposeMetas(ctx),
Ctx: ctx,
}, milestones[i].Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -335,7 +349,6 @@ func Pulls(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("pull_requests")
ctx.Data["PageIsPulls"] = true
ctx.Data["SingleRepoAction"] = "pull"
buildIssueOverview(ctx, unit.TypePullRequests)
}
@@ -349,7 +362,6 @@ func Issues(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("issues")
ctx.Data["PageIsIssues"] = true
ctx.Data["SingleRepoAction"] = "issue"
buildIssueOverview(ctx, unit.TypeIssues)
}
@@ -475,6 +487,13 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
opts.RepoIDs = []int64{0}
}
}
if ctx.Doer.ID == ctxUser.ID && filterMode != issues_model.FilterModeYourRepositories {
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
// because the doer may create issues or be mentioned in any public repo.
// So we need search issues in all public repos.
opts.AllPublic = true
}
switch filterMode {
case issues_model.FilterModeAll:
@@ -499,14 +518,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
isShowClosed := ctx.FormString("state") == "closed"
opts.IsClosed = util.OptionalBoolOf(isShowClosed)
// Filter repos and count issues in them. Count will be used later.
// USING NON-FINAL STATE OF opts FOR A QUERY.
issueCountByRepo, err := issue_indexer.CountIssuesByRepo(ctx, issue_indexer.ToSearchOptions(keyword, opts))
if err != nil {
ctx.ServerError("CountIssuesByRepo", err)
return
}
// Make sure page number is at least 1. Will be posted to ctx.Data.
page := ctx.FormInt("page")
if page <= 1 {
@@ -531,17 +542,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
}
opts.LabelIDs = labelIDs
// Parse ctx.FormString("repos") and remember matched repo IDs for later.
// Gets set when clicking filters on the issues overview page.
selectedRepoIDs := getRepoIDs(ctx.FormString("repos"))
// Remove repo IDs that are not accessible to the user.
selectedRepoIDs = slices.DeleteFunc(selectedRepoIDs, func(v int64) bool {
return !accessibleRepos.Contains(v)
})
if len(selectedRepoIDs) > 0 {
opts.RepoIDs = selectedRepoIDs
}
// ------------------------------
// Get issues as defined by opts.
// ------------------------------
@@ -562,41 +562,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
}
}
// ----------------------------------
// Add repository pointers to Issues.
// ----------------------------------
// Remove repositories that should not be shown,
// which are repositories that have no issues and are not selected by the user.
selectedRepos := container.SetOf(selectedRepoIDs...)
for k, v := range issueCountByRepo {
if v == 0 && !selectedRepos.Contains(k) {
delete(issueCountByRepo, k)
}
}
// showReposMap maps repository IDs to their Repository pointers.
showReposMap, err := loadRepoByIDs(ctx, ctxUser, issueCountByRepo, unitType)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
ctx.NotFound("GetRepositoryByID", err)
return
}
ctx.ServerError("loadRepoByIDs", err)
return
}
// a RepositoryList
showRepos := repo_model.RepositoryListOfMap(showReposMap)
sort.Sort(showRepos)
// maps pull request IDs to their CommitStatus. Will be posted to ctx.Data.
for _, issue := range issues {
if issue.Repo == nil {
issue.Repo = showReposMap[issue.RepoID]
}
}
commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(ctx, issues)
if err != nil {
ctx.ServerError("GetIssuesLastCommitStatus", err)
@@ -606,7 +571,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
// -------------------------------
// Fill stats to post to ctx.Data.
// -------------------------------
issueStats, err := getUserIssueStats(ctx, filterMode, issue_indexer.ToSearchOptions(keyword, opts), ctx.Doer.ID)
issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts))
if err != nil {
ctx.ServerError("getUserIssueStats", err)
return
@@ -619,25 +584,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
} else {
shownIssues = int(issueStats.ClosedCount)
}
if len(opts.RepoIDs) != 0 {
shownIssues = 0
for _, repoID := range opts.RepoIDs {
shownIssues += int(issueCountByRepo[repoID])
}
}
var allIssueCount int64
for _, issueCount := range issueCountByRepo {
allIssueCount += issueCount
}
ctx.Data["TotalIssueCount"] = allIssueCount
if len(opts.RepoIDs) == 1 {
repo := showReposMap[opts.RepoIDs[0]]
if repo != nil {
ctx.Data["SingleRepoLink"] = repo.Link()
}
}
ctx.Data["IsShowClosed"] = isShowClosed
@@ -674,12 +620,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
}
ctx.Data["CommitLastStatus"] = lastStatus
ctx.Data["CommitStatuses"] = commitStatuses
ctx.Data["Repos"] = showRepos
ctx.Data["Counts"] = issueCountByRepo
ctx.Data["IssueStats"] = issueStats
ctx.Data["ViewType"] = viewType
ctx.Data["SortType"] = sortType
ctx.Data["RepoIDs"] = selectedRepoIDs
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["SelectLabels"] = selectedLabels
@@ -689,15 +632,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
ctx.Data["State"] = "open"
}
// Convert []int64 to string
reposParam, _ := json.Marshal(opts.RepoIDs)
ctx.Data["ReposParam"] = string(reposParam)
pager := context.NewPagination(shownIssues, setting.UI.IssuePagingNum, page, 5)
pager.AddParam(ctx, "q", "Keyword")
pager.AddParam(ctx, "type", "ViewType")
pager.AddParam(ctx, "repos", "ReposParam")
pager.AddParam(ctx, "sort", "SortType")
pager.AddParam(ctx, "state", "State")
pager.AddParam(ctx, "labels", "SelectLabels")
@@ -708,55 +645,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
ctx.HTML(http.StatusOK, tplIssues)
}
func getRepoIDs(reposQuery string) []int64 {
if len(reposQuery) == 0 || reposQuery == "[]" {
return []int64{}
}
if !issueReposQueryPattern.MatchString(reposQuery) {
log.Warn("issueReposQueryPattern does not match query: %q", reposQuery)
return []int64{}
}
var repoIDs []int64
// remove "[" and "]" from string
reposQuery = reposQuery[1 : len(reposQuery)-1]
// for each ID (delimiter ",") add to int to repoIDs
for _, rID := range strings.Split(reposQuery, ",") {
// Ensure nonempty string entries
if rID != "" && rID != "0" {
rIDint64, err := strconv.ParseInt(rID, 10, 64)
if err == nil {
repoIDs = append(repoIDs, rIDint64)
}
}
}
return repoIDs
}
func loadRepoByIDs(ctx *context.Context, ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) {
totalRes := make(map[int64]*repo_model.Repository, len(issueCountByRepo))
repoIDs := make([]int64, 0, 500)
for id := range issueCountByRepo {
if id <= 0 {
continue
}
repoIDs = append(repoIDs, id)
if len(repoIDs) == 500 {
if err := repo_model.FindReposMapByIDs(ctx, repoIDs, totalRes); err != nil {
return nil, err
}
repoIDs = repoIDs[:0]
}
}
if len(repoIDs) > 0 {
if err := repo_model.FindReposMapByIDs(ctx, repoIDs, totalRes); err != nil {
return nil, err
}
}
return totalRes, nil
}
// ShowSSHKeys output all the ssh keys of user by uid
func ShowSSHKeys(ctx *context.Context) {
keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
@@ -777,7 +665,10 @@ func ShowSSHKeys(ctx *context.Context) {
// ShowGPGKeys output all the public GPG keys of user by uid
func ShowGPGKeys(ctx *context.Context) {
keys, err := asymkey_model.ListGPGKeys(ctx, ctx.ContextUser.ID, db.ListOptions{})
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.ContextUser.ID,
})
if err != nil {
ctx.ServerError("ListGPGKeys", err)
return
@@ -870,8 +761,15 @@ func UsernameSubRoute(ctx *context.Context) {
}
}
func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions, doerID int64) (*issues_model.IssueStats, error) {
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (*issues_model.IssueStats, error) {
doerID := ctx.Doer.ID
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
// because the doer may create issues or be mentioned in any public repo.
// So we need search issues in all public repos.
o.AllPublic = doerID == ctxUser.ID
o.AssigneeID = nil
o.PosterID = nil
o.MentionID = nil
@@ -887,7 +785,10 @@ func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer
{
openClosedOpts := opts.Copy()
switch filterMode {
case issues_model.FilterModeAll, issues_model.FilterModeYourRepositories:
case issues_model.FilterModeAll:
// no-op
case issues_model.FilterModeYourRepositories:
openClosedOpts.AllPublic = false
case issues_model.FilterModeAssign:
openClosedOpts.AssigneeID = &doerID
case issues_model.FilterModeCreate:
@@ -911,7 +812,7 @@ func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer
}
}
ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts)
ret.YourRepositoriesCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AllPublic = false }))
if err != nil {
return nil, err
}

View File

@@ -45,9 +45,7 @@ func TestArchivedIssues(t *testing.T) {
// Assert: One Issue (ID 30) from one Repo (ID 50) is retrieved, while nothing from archived Repo 51 is retrieved
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, map[int64]int64{50: 1}, ctx.Data["Counts"])
assert.Len(t, ctx.Data["Issues"], 1)
assert.Len(t, ctx.Data["Repos"], 1)
}
func TestIssues(t *testing.T) {
@@ -60,10 +58,8 @@ func TestIssues(t *testing.T) {
Issues(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"])
assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
assert.Len(t, ctx.Data["Issues"], 1)
assert.Len(t, ctx.Data["Repos"], 2)
}
func TestPulls(t *testing.T) {

View File

@@ -7,6 +7,7 @@ package user
import (
"fmt"
"net/http"
"path"
"strings"
activities_model "code.gitea.io/gitea/models/activities"
@@ -64,17 +65,17 @@ func userProfile(ctx *context.Context) {
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
}
profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
defer profileClose()
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
prepareUserProfileTabData(ctx, showPrivate, profileGitRepo, profileReadmeBlob)
prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileGitRepo, profileReadmeBlob)
// call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing
shared_user.PrepareContextForProfileBigAvatar(ctx)
ctx.HTML(http.StatusOK, tplProfile)
}
func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGitRepo *git.Repository, profileReadme *git.Blob) {
func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadme *git.Blob) {
// if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
// if there is not a profile readme, the overview tab should be treated as the repositories tab
tab := ctx.FormString("tab")
@@ -236,7 +237,16 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGi
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Metas: map[string]string{"mode": "document"},
Links: markup.Links{
// Give the repo link to the markdown render for the full link of media element.
// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
// Eg. /Tom/.profile/media/branch/main
// The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
// https://docs.gitea.com/usage/profile-readme
Base: profileDbRepo.Link(),
BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
},
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {

View File

@@ -105,7 +105,7 @@ func EmailPost(ctx *context.Context) {
// Send activation Email
if ctx.FormString("_method") == "SENDACTIVATION" {
var address string
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName) {
if ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName) {
log.Error("Send activation: activation still pending")
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
@@ -141,11 +141,10 @@ func EmailPost(ctx *context.Context) {
}
address = email.Email
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
@@ -204,11 +203,10 @@ func EmailPost(ctx *context.Context) {
// Send confirmation email
if setting.Service.RegisterEmailConfirm {
mailer.SendActivateEmailMail(ctx.Doer, email)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
if err := ctx.Cache.Put("MailResendLimit_"+ctx.Doer.LowerName, ctx.Doer.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
} else {
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
@@ -246,6 +244,13 @@ func DeleteAccount(ctx *context.Context) {
return
}
// admin should not delete themself
if ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}
if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
switch {
case models.IsErrUserOwnRepos(err):
@@ -257,6 +262,9 @@ func DeleteAccount(ctx *context.Context) {
case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
case models.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
default:
ctx.ServerError("DeleteUser", err)
}
@@ -276,7 +284,7 @@ func loadAccountData(ctx *context.Context) {
user_model.EmailAddress
CanBePrimary bool
}
pendingActivation := setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName)
pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.Doer.LowerName)
emails := make([]*UserEmail, len(emlist))
for i, em := range emlist {
var email UserEmail

View File

@@ -277,18 +277,29 @@ func loadKeysData(ctx *context.Context) {
}
ctx.Data["ExternalKeys"] = externalKeys
gpgkeys, err := asymkey_model.ListGPGKeys(ctx, ctx.Doer.ID, db.ListOptions{})
gpgkeys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.Doer.ID,
})
if err != nil {
ctx.ServerError("ListGPGKeys", err)
return
}
if err := asymkey_model.GPGKeyList(gpgkeys).LoadSubKeys(ctx); err != nil {
ctx.ServerError("LoadSubKeys", err)
return
}
ctx.Data["GPGKeys"] = gpgkeys
tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1)
// generate a new aes cipher using the csrfToken
ctx.Data["TokenToSign"] = tokenToSign
principals, err := asymkey_model.ListPrincipalKeys(ctx, ctx.Doer.ID, db.ListOptions{})
principals, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
ListOptions: db.ListOptionsAll,
OwnerID: ctx.Doer.ID,
KeyTypes: []asymkey_model.KeyType{asymkey_model.KeyTypePrincipal},
})
if err != nil {
ctx.ServerError("ListPrincipalKeys", err)
return

View File

@@ -14,6 +14,7 @@ import (
"path/filepath"
"strings"
"code.gitea.io/gitea/models/avatars"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
@@ -130,7 +131,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser *
ctxUser.UseCustomAvatar = form.Source == forms.AvatarLocal
if len(form.Gravatar) > 0 {
if form.Avatar != nil {
ctxUser.Avatar = base.EncodeMD5(form.Gravatar)
ctxUser.Avatar = avatars.HashEmail(form.Gravatar)
} else {
ctxUser.Avatar = ""
}

View File

@@ -60,13 +60,12 @@ const (
GzipMinSize = 1400
)
// CorsHandler return a http handler who set CORS options if enabled by config
func CorsHandler() func(next http.Handler) http.Handler {
// 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
if setting.CORSConfig.Enabled {
return cors.Handler(cors.Options{
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
AllowedOrigins: setting.CORSConfig.AllowDomain,
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
corsHandler = cors.Handler(cors.Options{
AllowedOrigins: setting.CORSConfig.AllowDomain,
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: setting.CORSConfig.Headers,
@@ -75,7 +74,23 @@ func CorsHandler() func(next http.Handler) http.Handler {
}
return func(next http.Handler) http.Handler {
return next
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
if corsHandler != nil && r.Header.Get("Access-Control-Request-Method") != "" {
corsHandler(next).ServeHTTP(w, r)
} else {
// it should explicitly deny OPTIONS requests if CORS handler is not executed, to avoid the next GET/POST handler being incorrectly called by the OPTIONS request
w.WriteHeader(http.StatusMethodNotAllowed)
}
return
}
// for non-OPTIONS requests, call the CORS handler to add some related headers like "Vary"
if corsHandler != nil {
corsHandler(next).ServeHTTP(w, r)
} else {
next.ServeHTTP(w, r)
}
})
}
}
@@ -218,7 +233,7 @@ func Routes() *web.Route {
routes := web.NewRoute()
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.FileHandlerFunc())
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc())
routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
@@ -417,7 +432,7 @@ func registerRoutes(m *web.Route) {
m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo_setting.PackagistHooksEditPost)
}
addSettingVariablesRoutes := func() {
addSettingsVariablesRoutes := func() {
m.Group("/variables", func() {
m.Get("", repo_setting.Variables)
m.Post("/new", web.Bind(forms.EditVariableForm{}), repo_setting.VariableCreate)
@@ -458,8 +473,8 @@ func registerRoutes(m *web.Route) {
m.Get("/change-password", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
})
m.Any("/*", CorsHandler(), public.FileHandlerFunc())
}, CorsHandler())
m.Methods("GET, HEAD", "/*", public.FileHandlerFunc())
}, optionsCorsHandler())
m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
@@ -532,12 +547,11 @@ func registerRoutes(m *web.Route) {
// TODO manage redirection
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
}, ignSignInAndCsrf, reqSignIn)
m.Get("/login/oauth/userinfo", ignSignInAndCsrf, auth.InfoOAuth)
m.Options("/login/oauth/access_token", CorsHandler(), misc.DummyBadRequest)
m.Post("/login/oauth/access_token", CorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
m.Get("/login/oauth/keys", ignSignInAndCsrf, auth.OIDCKeys)
m.Options("/login/oauth/introspect", CorsHandler(), misc.DummyBadRequest)
m.Post("/login/oauth/introspect", CorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
m.Methods("GET, OPTIONS", "/login/oauth/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
m.Methods("POST, OPTIONS", "/login/oauth/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
m.Methods("GET, OPTIONS", "/login/oauth/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys)
m.Methods("POST, OPTIONS", "/login/oauth/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
m.Group("/user/settings", func() {
m.Get("", user_setting.Profile)
@@ -616,7 +630,7 @@ func registerRoutes(m *web.Route) {
m.Get("", user_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingVariablesRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
m.Get("/organization", user_setting.Organization)
@@ -664,6 +678,8 @@ func registerRoutes(m *web.Route) {
m.Get("", admin.Dashboard)
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
m.Get("/self_check", admin.SelfCheck)
m.Group("/config", func() {
m.Get("", admin.Config)
m.Post("", admin.ChangeConfig)
@@ -761,13 +777,14 @@ func registerRoutes(m *web.Route) {
m.Group("/actions", func() {
m.Get("", admin.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsVariablesRoutes()
})
}, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled))
// ***** END: Admin *****
m.Group("", func() {
m.Get("/{username}", user.UsernameSubRoute)
m.Get("/attachments/{uuid}", repo.GetAttachment)
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
}, ignSignIn)
m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action)
@@ -796,6 +813,24 @@ func registerRoutes(m *web.Route) {
}
}
individualPermsChecker := func(ctx *context.Context) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == structs.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == structs.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// ***** START: Organization *****
m.Group("/org", func() {
m.Group("/{org}", func() {
@@ -885,7 +920,7 @@ func registerRoutes(m *web.Route) {
m.Get("", org_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingVariablesRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
m.Methods("GET,POST", "/delete", org.SettingsDelete)
@@ -976,11 +1011,11 @@ func registerRoutes(m *web.Route) {
return
}
})
})
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker)
m.Group("", func() {
m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
m.Group("/{username}/{reponame}", func() {
@@ -1064,7 +1099,7 @@ func registerRoutes(m *web.Route) {
m.Get("", repo_setting.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingVariablesRoutes()
addSettingsVariablesRoutes()
}, actions.MustEnableActions)
// the follow handler must be under "settings", otherwise this incomplete repo can't be accessed
m.Group("/migrate", func() {