summaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorMaxim Slipenko <maxim@slipenko.com>2024-12-19 11:45:59 +0100
committerMaxim Slipenko <maxim@slipenko.com>2024-12-19 11:45:59 +0100
commitbbcd7d6fae7b806316efaae1505f0b9d9c40779c (patch)
tree0658f25815a2634aff4485780cd3f4c3ff9968a8 /routers
parentfix: do not rewrite when there are no keys (diff)
parentUpdate dependency katex to v0.16.18 (forgejo) (#6319) (diff)
downloadforgejo-bbcd7d6fae7b806316efaae1505f0b9d9c40779c.tar.xz
forgejo-bbcd7d6fae7b806316efaae1505f0b9d9c40779c.zip
Merge remote-tracking branch 'upstream/forgejo' into feat/add-oidc-ssh-keys
Diffstat (limited to 'routers')
-rw-r--r--routers/api/actions/runner/main_test.go16
-rw-r--r--routers/api/actions/runner/utils.go60
-rw-r--r--routers/api/actions/runner/utils_test.go29
-rw-r--r--routers/api/packages/api.go1
-rw-r--r--routers/api/v1/admin/user.go38
-rw-r--r--routers/api/v1/api.go1
-rw-r--r--routers/api/v1/repo/branch.go71
-rw-r--r--routers/api/v1/swagger/options.go2
-rw-r--r--routers/web/auth/auth.go6
-rw-r--r--routers/web/auth/oauth.go64
-rw-r--r--routers/web/repo/actions/actions.go34
-rw-r--r--routers/web/repo/actions/view.go18
12 files changed, 287 insertions, 53 deletions
diff --git a/routers/api/actions/runner/main_test.go b/routers/api/actions/runner/main_test.go
new file mode 100644
index 0000000000..bed63c166e
--- /dev/null
+++ b/routers/api/actions/runner/main_test.go
@@ -0,0 +1,16 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package runner
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+
+ _ "code.gitea.io/gitea/models/forgefed"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m)
+}
diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go
index ff6ec5bd54..539be8d889 100644
--- a/routers/api/actions/runner/utils.go
+++ b/routers/api/actions/runner/utils.go
@@ -162,28 +162,56 @@ func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[str
return nil, fmt.Errorf("FindRunJobs: %w", err)
}
- ret := make(map[string]*runnerv1.TaskNeed, len(needs))
+ jobIDJobs := make(map[string][]*actions_model.ActionRunJob)
for _, job := range jobs {
- if !needs.Contains(job.JobID) {
- continue
- }
- if job.TaskID == 0 || !job.Status.IsDone() {
- // it shouldn't happen, or the job has been rerun
+ jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job)
+ }
+
+ ret := make(map[string]*runnerv1.TaskNeed, len(needs))
+ for jobID, jobsWithSameID := range jobIDJobs {
+ if !needs.Contains(jobID) {
continue
}
- outputs := make(map[string]string)
- got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID)
- if err != nil {
- return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
+ var jobOutputs map[string]string
+ for _, job := range jobsWithSameID {
+ if job.TaskID == 0 || !job.Status.IsDone() {
+ // it shouldn't happen, or the job has been rerun
+ continue
+ }
+ got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID)
+ if err != nil {
+ return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
+ }
+ outputs := make(map[string]string, len(got))
+ for _, v := range got {
+ outputs[v.OutputKey] = v.OutputValue
+ }
+ if len(jobOutputs) == 0 {
+ jobOutputs = outputs
+ } else {
+ jobOutputs = mergeTwoOutputs(outputs, jobOutputs)
+ }
}
- for _, v := range got {
- outputs[v.OutputKey] = v.OutputValue
- }
- ret[job.JobID] = &runnerv1.TaskNeed{
- Outputs: outputs,
- Result: runnerv1.Result(job.Status),
+ ret[jobID] = &runnerv1.TaskNeed{
+ Outputs: jobOutputs,
+ Result: runnerv1.Result(actions_model.AggregateJobStatus(jobsWithSameID)),
}
}
return ret, nil
}
+
+// mergeTwoOutputs merges two outputs from two different ActionRunJobs
+// Values with the same output name may be overridden. The user should ensure the output names are unique.
+// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job
+func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
+ ret := make(map[string]string, len(o1))
+ for k1, v1 := range o1 {
+ if len(v1) > 0 {
+ ret[k1] = v1
+ } else {
+ ret[k1] = o2[k1]
+ }
+ }
+ return ret
+}
diff --git a/routers/api/actions/runner/utils_test.go b/routers/api/actions/runner/utils_test.go
new file mode 100644
index 0000000000..c8a0a28d65
--- /dev/null
+++ b/routers/api/actions/runner/utils_test.go
@@ -0,0 +1,29 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package runner
+
+import (
+ "context"
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_findTaskNeeds(t *testing.T) {
+ require.NoError(t, unittest.PrepareTestDatabase())
+
+ task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 51})
+
+ ret, err := findTaskNeeds(context.Background(), task)
+ require.NoError(t, err)
+ assert.Len(t, ret, 1)
+ assert.Contains(t, ret, "job1")
+ assert.Len(t, ret["job1"].Outputs, 2)
+ assert.Equal(t, "abc", ret["job1"].Outputs["output_a"])
+ assert.Equal(t, "bbb", ret["job1"].Outputs["output_b"])
+}
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 781bfc7f90..e216a0c02b 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -337,6 +337,7 @@ func CommonRoutes() *web.Route {
r.Get("/PACKAGES", cran.EnumerateSourcePackages)
r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
r.Get("/{filename}", cran.DownloadSourcePackageFile)
+ r.Get("/Archive/{packagename}/{filename}", cran.DownloadSourcePackageFile)
})
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cran.UploadSourcePackageFile)
})
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index b17200381a..acab883107 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -415,6 +416,11 @@ func SearchUsers(ctx *context.APIContext) {
// in: query
// description: user's login name to search for
// type: string
+ // - name: sort
+ // in: query
+ // description: sort order of results
+ // type: string
+ // enum: [oldest, newest, alphabetically, reversealphabetically, recentupdate, leastupdate]
// - name: page
// in: query
// description: page number of results to return (1-based)
@@ -431,12 +437,40 @@ func SearchUsers(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
+ sort := ctx.FormString("sort")
+ var orderBy db.SearchOrderBy
+
+ switch sort {
+ case "oldest":
+ orderBy = db.SearchOrderByOldest
+ case "newest":
+ orderBy = db.SearchOrderByNewest
+ case "alphabetically":
+ orderBy = db.SearchOrderByAlphabetically
+ case "reversealphabetically":
+ orderBy = db.SearchOrderByAlphabeticallyReverse
+ case "recentupdate":
+ orderBy = db.SearchOrderByRecentUpdated
+ case "leastupdate":
+ orderBy = db.SearchOrderByLeastUpdated
+ default:
+ orderBy = db.SearchOrderByAlphabetically
+ }
+
+ intSource, err := strconv.ParseInt(ctx.FormString("source_id"), 10, 64)
+ var sourceID optional.Option[int64]
+ if ctx.FormString("source_id") == "" || err != nil {
+ sourceID = optional.None[int64]()
+ } else {
+ sourceID = optional.Some(intSource)
+ }
+
users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
LoginName: ctx.FormTrim("login_name"),
- SourceID: ctx.FormInt64("source_id"),
- OrderBy: db.SearchOrderByAlphabetically,
+ SourceID: sourceID,
+ OrderBy: orderBy,
ListOptions: listOptions,
})
if err != nil {
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index c028a5a32d..4928c9ff58 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1153,6 +1153,7 @@ func Routes() *web.Route {
m.Get("/*", repo.GetBranch)
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.CreateBranch)
+ m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index e3e6efa781..3ca97f7770 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -393,6 +393,77 @@ func ListBranches(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, apiBranches)
}
+// UpdateBranch updates a repository's branch.
+func UpdateBranch(ctx *context.APIContext) {
+ // swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
+ // ---
+ // summary: Update a branch
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // - name: branch
+ // in: path
+ // description: name of the branch
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/UpdateBranchRepoOption"
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
+
+ oldName := ctx.Params("*")
+ repo := ctx.Repo.Repository
+
+ if repo.IsEmpty {
+ ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
+ return
+ }
+
+ if repo.IsMirror {
+ ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
+ return
+ }
+
+ msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
+ return
+ }
+ if msg == "target_exist" {
+ ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
+ return
+ }
+ if msg == "from_not_exist" {
+ ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
// GetBranchProtection gets a branch protection
func GetBranchProtection(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index 1dccf92d82..432e42d4e7 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -101,6 +101,8 @@ type swaggerParameterBodies struct {
// in:body
EditRepoOption api.EditRepoOption
// in:body
+ UpdateBranchRepoOption api.UpdateBranchRepoOption
+ // in:body
TransferRepoOption api.TransferRepoOption
// in:body
CreateForkOption api.CreateForkOption
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 71d7b8ca11..ccab47a9a2 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -599,8 +599,10 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
notify_service.NewUserSignUp(ctx, u)
// update external user information
if gothUser != nil {
- if err := externalaccount.EnsureLinkExternalToUser(ctx, u, *gothUser); err != nil {
- log.Error("EnsureLinkExternalToUser failed: %v", err)
+ if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
+ if !errors.Is(err, util.ErrNotExist) {
+ log.Error("UpdateExternalUser failed: %v", err)
+ }
}
}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index e329729dcd..fbdd47479a 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -1205,39 +1205,9 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
groups := getClaimedGroups(oauth2Source, &gothUser)
- opts := &user_service.UpdateOptions{}
-
- // Reactivate user if they are deactivated
- if !u.IsActive {
- opts.IsActive = optional.Some(true)
- }
-
- // Update GroupClaims
- opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
-
- if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
- if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
- ctx.ServerError("SyncGroupsToTeams", err)
- return
- }
- }
-
- if err := externalaccount.EnsureLinkExternalToUser(ctx, u, gothUser); err != nil {
- ctx.ServerError("EnsureLinkExternalToUser", err)
- return
- }
-
// If this user is enrolled in 2FA and this source doesn't override it,
// we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
if !needs2FA {
- // Register last login
- opts.SetLastLogin = true
-
- if err := user_service.UpdateUser(ctx, u, opts); err != nil {
- ctx.ServerError("UpdateUser", err)
- return
- }
-
if err := updateSession(ctx, nil, map[string]any{
"uid": u.ID,
}); err != nil {
@@ -1248,6 +1218,29 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
// Clear whatever CSRF cookie has right now, force to generate a new one
ctx.Csrf.DeleteCookie(ctx)
+ opts := &user_service.UpdateOptions{
+ SetLastLogin: true,
+ }
+ opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
+ if err := user_service.UpdateUser(ctx, u, opts); err != nil {
+ ctx.ServerError("UpdateUser", err)
+ return
+ }
+
+ if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
+ if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
+ ctx.ServerError("SyncGroupsToTeams", err)
+ return
+ }
+ }
+
+ // update external user information
+ if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
+ if !errors.Is(err, util.ErrNotExist) {
+ log.Error("UpdateExternalUser failed: %v", err)
+ }
+ }
+
if err := resetLocale(ctx, u); err != nil {
ctx.ServerError("resetLocale", err)
return
@@ -1263,13 +1256,22 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
return
}
- if opts.IsActive.Has() || opts.IsAdmin.Has() || opts.IsRestricted.Has() {
+ opts := &user_service.UpdateOptions{}
+ opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
+ if opts.IsAdmin.Has() || opts.IsRestricted.Has() {
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
ctx.ServerError("UpdateUser", err)
return
}
}
+ if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
+ if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
+ ctx.ServerError("SyncGroupsToTeams", err)
+ return
+ }
+ }
+
if err := updateSession(ctx, nil, map[string]any{
// User needs to use 2FA, save data and redirect to 2FA page.
"twofaUid": u.ID,
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index ff3b16159b..283d476df1 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -12,11 +12,13 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -222,6 +224,10 @@ func List(ctx *context.Context) {
return
}
+ if err := loadIsRefDeleted(ctx, runs); err != nil {
+ log.Error("LoadIsRefDeleted", err)
+ }
+
ctx.Data["Runs"] = runs
ctx.Data["Repo"] = ctx.Repo
@@ -245,3 +251,31 @@ func List(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplListActions)
}
+
+// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
+// TODO: move this function to models/actions/run_list.go but now it will result in a circular import.
+func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error {
+ branches := make(container.Set[string], len(runs))
+ for _, run := range runs {
+ refName := git.RefName(run.Ref)
+ if refName.IsBranch() {
+ branches.Add(refName.ShortName())
+ }
+ }
+ if len(branches) == 0 {
+ return nil
+ }
+
+ branchInfos, err := git_model.GetBranches(ctx, ctx.Repo.Repository.ID, branches.Values(), false)
+ if err != nil {
+ return err
+ }
+ branchSet := git_model.BranchesToNamesSet(branchInfos)
+ for _, run := range runs {
+ refName := git.RefName(run.Ref)
+ if refName.IsBranch() && !branchSet.Contains(run.Ref) {
+ run.IsRefDeleted = true
+ }
+ }
+ return nil
+}
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index cab37acdd1..dea31bb1c4 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -20,10 +20,13 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/templates"
@@ -157,8 +160,9 @@ type ViewUser struct {
}
type ViewBranch struct {
- Name string `json:"name"`
- Link string `json:"link"`
+ Name string `json:"name"`
+ Link string `json:"link"`
+ IsDeleted bool `json:"isDeleted"`
}
type ViewJobStep struct {
@@ -227,6 +231,16 @@ func ViewPost(ctx *context_module.Context) {
Name: run.PrettyRef(),
Link: run.RefLink(),
}
+ refName := git.RefName(run.Ref)
+ if refName.IsBranch() {
+ b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, refName.ShortName())
+ if err != nil && !git_model.IsErrBranchNotExist(err) {
+ log.Error("GetBranch: %v", err)
+ } else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) {
+ branch.IsDeleted = true
+ }
+ }
+
resp.State.Run.Commit = ViewCommit{
LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),