Merge branch 'main' into allow-force-push-protected-branches

This commit is contained in:
Henry Goodman
2024-01-11 18:59:27 +11:00
committed by GitHub
480 changed files with 4706 additions and 3755 deletions
+6 -7
View File
@@ -63,7 +63,6 @@ package actions
import (
"crypto/md5"
"errors"
"fmt"
"net/http"
"strconv"
@@ -423,15 +422,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)
+9 -4
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)
+8 -2
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
}
+26
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)
}
+36 -17
View File
@@ -822,9 +822,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([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
@@ -950,11 +948,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)
@@ -1054,10 +1058,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)
@@ -1424,11 +1434,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)
@@ -1520,6 +1536,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("/topics", func() {
+31
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)
}
+10 -3
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
}
+34
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)
}
+32
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})
}
+26
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)
}
+17 -6
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{
-1
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
}
+29
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
+7
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)
}
+2 -1
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 {
+9 -9
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
}
}
+5 -1
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
+6 -1
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,
+11 -11
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())
}
-4
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 {
+11 -6
View File
@@ -18,6 +18,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"
)
@@ -157,14 +158,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
}
@@ -172,10 +173,14 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
// Pass URLPrefix to markdown render for the full link of media elements.
// The profile of default branch would be shown.
prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
GitRepo: profileGitRepo,
URLPrefix: prefix,
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
+17 -5
View File
@@ -125,7 +125,7 @@ func RefBlame(ctx *context.Context) {
}
type blameResult struct {
Parts []git.BlamePart
Parts []*git.BlamePart
UsesIgnoreRevs bool
FaultyIgnoreRevsFile bool
}
@@ -175,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 {
@@ -184,13 +186,23 @@ 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 {
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)
// and as blameParts can reference the same commits multiple
@@ -232,7 +244,7 @@ func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[str
return commitNames
}
func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink
language := ""
+9 -1
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
}
}
+1 -1
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
}
+18 -3
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")
}
+3 -3
View File
@@ -87,7 +87,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 +105,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 +115,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
}
+14 -6
View File
@@ -64,17 +64,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")
@@ -233,10 +233,18 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileGi
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
// Give the URLPrefix 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
prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
if profileContent, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
GitRepo: profileGitRepo,
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
GitRepo: profileGitRepo,
URLPrefix: prefix,
Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
+2 -1
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 = ""
}
+38 -21
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)
@@ -903,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)
@@ -1082,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() {