mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-22 22:33:33 +01:00
Merge pull request '[gitea] week 2024-51 cherry pick (gitea/main -> forgejo)' (#6271) from earl-warren/wcp/2024-51 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6271 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
380e266440
28 changed files with 526 additions and 32 deletions
|
@ -37,6 +37,7 @@ type ActionRun struct {
|
|||
TriggerUser *user_model.User `xorm:"-"`
|
||||
ScheduleID int64
|
||||
Ref string `xorm:"index"` // the commit/tag/… that caused the run
|
||||
IsRefDeleted bool `xorm:"-"`
|
||||
CommitSHA string
|
||||
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
|
||||
NeedApproval bool // may need approval if it's a fork pull request
|
||||
|
|
|
@ -137,7 +137,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
run.Status = aggregateJobStatus(jobs)
|
||||
run.Status = AggregateJobStatus(jobs)
|
||||
if run.Started.IsZero() && run.Status.IsRunning() {
|
||||
run.Started = timeutil.TimeStampNow()
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
|||
return affected, nil
|
||||
}
|
||||
|
||||
func aggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
func AggregateJobStatus(jobs []*ActionRunJob) Status {
|
||||
allDone := true
|
||||
allWaiting := true
|
||||
hasFailure := false
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
package db // it's not db_test, because this file is for testing the private type halfCommitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -80,7 +80,7 @@ func Test_halfCommitter(t *testing.T) {
|
|||
testWithCommitter(mockCommitter, func(committer Committer) error {
|
||||
defer committer.Close()
|
||||
if true {
|
||||
return fmt.Errorf("error")
|
||||
return errors.New("error")
|
||||
}
|
||||
return committer.Commit()
|
||||
})
|
||||
|
@ -94,7 +94,7 @@ func Test_halfCommitter(t *testing.T) {
|
|||
testWithCommitter(mockCommitter, func(committer Committer) error {
|
||||
committer.Close()
|
||||
committer.Commit()
|
||||
return fmt.Errorf("error")
|
||||
return errors.New("error")
|
||||
})
|
||||
|
||||
mockCommitter.Assert(t)
|
||||
|
|
|
@ -413,6 +413,25 @@
|
|||
},
|
||||
"total_commits": 0
|
||||
}
|
||||
-
|
||||
id: 793
|
||||
title: "job output"
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
workflow_id: "test.yaml"
|
||||
index: 189
|
||||
trigger_user_id: 1
|
||||
ref: "refs/heads/master"
|
||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||
event: "push"
|
||||
is_fork_pull_request: 0
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
created: 1683636108
|
||||
updated: 1683636626
|
||||
need_approval: 0
|
||||
approved_by: 0
|
||||
-
|
||||
id: 891
|
||||
title: "update actions"
|
||||
|
|
|
@ -26,6 +26,49 @@
|
|||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 194
|
||||
run_id: 793
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job1 (1)
|
||||
attempt: 1
|
||||
job_id: job1
|
||||
task_id: 49
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 195
|
||||
run_id: 793
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job1 (2)
|
||||
attempt: 1
|
||||
job_id: job1
|
||||
task_id: 50
|
||||
status: 1
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 196
|
||||
run_id: 793
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
name: job2
|
||||
attempt: 1
|
||||
job_id: job2
|
||||
needs: [job1]
|
||||
task_id: 51
|
||||
status: 5
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
-
|
||||
id: 292
|
||||
run_id: 891
|
||||
|
|
|
@ -57,3 +57,63 @@
|
|||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 49
|
||||
job_id: 194
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 1 # success
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784220
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 50
|
||||
job_id: 195
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 1 # success
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784221
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
-
|
||||
id: 51
|
||||
job_id: 196
|
||||
attempt: 1
|
||||
runner_id: 1
|
||||
status: 6 # running
|
||||
started: 1683636528
|
||||
stopped: 1683636626
|
||||
repo_id: 4
|
||||
owner_id: 1
|
||||
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||
is_fork_pull_request: 0
|
||||
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
|
||||
token_salt: ffffffffff
|
||||
token_last_eight: ffffffff
|
||||
log_filename: artifact-test2/2f/47.log
|
||||
log_in_storage: 1
|
||||
log_length: 707
|
||||
log_size: 90179
|
||||
log_expired: 0
|
||||
|
|
20
models/fixtures/action_task_output.yml
Normal file
20
models/fixtures/action_task_output.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
-
|
||||
id: 1
|
||||
task_id: 49
|
||||
output_key: output_a
|
||||
output_value: abc
|
||||
-
|
||||
id: 2
|
||||
task_id: 49
|
||||
output_key: output_b
|
||||
output_value: ''
|
||||
-
|
||||
id: 3
|
||||
task_id: 50
|
||||
output_key: output_a
|
||||
output_value: ''
|
||||
-
|
||||
id: 4
|
||||
task_id: 50
|
||||
output_key: output_b
|
||||
output_value: bbb
|
|
@ -11,6 +11,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
@ -162,9 +163,22 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
|||
return &branch, nil
|
||||
}
|
||||
|
||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string) ([]*Branch, error) {
|
||||
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
|
||||
branches := make([]*Branch, 0, len(branchNames))
|
||||
return branches, db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames).Find(&branches)
|
||||
|
||||
sess := db.GetEngine(ctx).Where("repo_id=?", repoID).In("name", branchNames)
|
||||
if !includeDeleted {
|
||||
sess.And("is_deleted=?", false)
|
||||
}
|
||||
return branches, sess.Find(&branches)
|
||||
}
|
||||
|
||||
func BranchesToNamesSet(branches []*Branch) container.Set[string] {
|
||||
names := make(container.Set[string], len(branches))
|
||||
for _, branch := range branches {
|
||||
names.Add(branch.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func AddBranches(ctx context.Context, branches []*Branch) error {
|
||||
|
|
|
@ -72,7 +72,10 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin
|
|||
|
||||
url := fmt.Sprintf("%s/objects/batch", c.endpoint)
|
||||
|
||||
request := &BatchRequest{operation, c.transferNames(), nil, objects}
|
||||
// `ref` is an "optional object describing the server ref that the objects belong to"
|
||||
// but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones.
|
||||
// https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37
|
||||
request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects}
|
||||
payload := new(bytes.Buffer)
|
||||
err := json.NewEncoder(payload).Encode(request)
|
||||
if err != nil {
|
||||
|
@ -236,6 +239,7 @@ func createRequest(ctx context.Context, method, url string, headers map[string]s
|
|||
req.Header.Set(key, value)
|
||||
}
|
||||
req.Header.Set("Accept", AcceptHeader)
|
||||
req.Header.Set("User-Agent", UserAgentHeader)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
|
|
@ -14,8 +14,12 @@ import (
|
|||
const (
|
||||
// MediaType contains the media type for LFS server requests
|
||||
MediaType = "application/vnd.git-lfs+json"
|
||||
// Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
|
||||
// AcceptHeader Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
|
||||
AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
|
||||
// UserAgentHeader Add User-Agent for gitea's self-implemented lfs client,
|
||||
// and the version is consistent with the latest version of git lfs can be avoided incompatibilities.
|
||||
// Some lfs servers will check this
|
||||
UserAgentHeader = "git-lfs/3.6.0 (Forgejo)"
|
||||
)
|
||||
|
||||
// BatchRequest contains multiple requests processed in one batch operation.
|
||||
|
|
|
@ -290,6 +290,16 @@ type CreateBranchRepoOption struct {
|
|||
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
|
||||
}
|
||||
|
||||
// UpdateBranchRepoOption options when updating a branch in a repository
|
||||
// swagger:model
|
||||
type UpdateBranchRepoOption struct {
|
||||
// New branch name
|
||||
//
|
||||
// required: true
|
||||
// unique: true
|
||||
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
|
||||
}
|
||||
|
||||
// TransferRepoOption options when transfer a repository's ownership
|
||||
// swagger:model
|
||||
type TransferRepoOption struct {
|
||||
|
|
4
release-notes/6271.md
Normal file
4
release-notes/6271.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/96a7f0a3f065c5db8fdf352c93c8367e24d259de) Fix missing outputs for jobs with matrix
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/2b5c69c451a684b20119e2521dc23734c7869241) Detect whether action view branch was deleted
|
||||
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/b0d6a7f07bff836190a8e87fe5645d5557893e32) Implement update branch API
|
||||
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/bf934c96c92d643678ac7a18697b6563bc9d20a5) Add standard-compliant route to serve outdated R packages
|
16
routers/api/actions/runner/main_test.go
Normal file
16
routers/api/actions/runner/main_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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) {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
outputs := make(map[string]string)
|
||||
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
|
||||
}
|
||||
ret[job.JobID] = &runnerv1.TaskNeed{
|
||||
Outputs: outputs,
|
||||
Result: runnerv1.Result(job.Status),
|
||||
if len(jobOutputs) == 0 {
|
||||
jobOutputs = outputs
|
||||
} else {
|
||||
jobOutputs = mergeTwoOutputs(outputs, jobOutputs)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
29
routers/api/actions/runner/utils_test.go
Normal file
29
routers/api/actions/runner/utils_test.go
Normal file
|
@ -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"])
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -159,6 +162,7 @@ type ViewUser struct {
|
|||
type ViewBranch struct {
|
||||
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"),
|
||||
|
|
|
@ -254,7 +254,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
|
|||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
branches, err := git_model.GetBranches(ctx, repoID, branchNames)
|
||||
branches, err := git_model.GetBranches(ctx, repoID, branchNames, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git_model.GetBranches: %v", err)
|
||||
}
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
{{if .RefLink}}
|
||||
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}">{{.PrettyRef}}</a>
|
||||
{{if .IsRefDeleted}}
|
||||
<span class="ui label run-list-ref gt-ellipsis tw-line-through" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</span>
|
||||
{{else}}
|
||||
<span class="ui label run-list-ref gt-ellipsis">{{.PrettyRef}}</span>
|
||||
<a class="ui label run-list-ref gt-ellipsis" href="{{.RefLink}}" data-tooltip-content="{{.PrettyRef}}">{{.PrettyRef}}</a>
|
||||
{{end}}
|
||||
<div class="run-list-item-right">
|
||||
<div class="run-list-meta">{{svg "octicon-calendar" 16}}{{DateUtils.TimeSince .Updated}}</div>
|
||||
|
|
73
templates/swagger/v1_json.tmpl
generated
73
templates/swagger/v1_json.tmpl
generated
|
@ -5820,6 +5820,63 @@
|
|||
"$ref": "#/responses/repoArchivedError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Update a branch",
|
||||
"operationId": "repoUpdateBranch",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the branch",
|
||||
"name": "branch",
|
||||
"in": "path",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/collaborators": {
|
||||
|
@ -27302,6 +27359,22 @@
|
|||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"UpdateBranchRepoOption": {
|
||||
"description": "UpdateBranchRepoOption options when updating a branch in a repository",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "New branch name",
|
||||
"type": "string",
|
||||
"uniqueItems": true,
|
||||
"x-go-name": "Name"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"UpdateFileOptions": {
|
||||
"description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
|
||||
"type": "object",
|
||||
|
|
|
@ -5,6 +5,7 @@ package integration
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
|
@ -187,6 +188,37 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
|
|||
return resp.Result().StatusCode == status
|
||||
}
|
||||
|
||||
func TestAPIUpdateBranch(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
t.Run("UpdateBranchWithEmptyRepo", func(t *testing.T) {
|
||||
testAPIUpdateBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound)
|
||||
})
|
||||
t.Run("UpdateBranchWithSameBranchNames", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity)
|
||||
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||
})
|
||||
t.Run("UpdateBranchThatAlreadyExists", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity)
|
||||
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||
})
|
||||
t.Run("UpdateBranchWithNonExistentBranch", func(t *testing.T) {
|
||||
resp := testAPIUpdateBranch(t, "user2", "repo1", "i-dont-exist", "new-branch-name", http.StatusNotFound)
|
||||
assert.Contains(t, resp.Body.String(), "Branch doesn't exist.")
|
||||
})
|
||||
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
|
||||
testAPIUpdateBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testAPIUpdateBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
||||
token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.UpdateBranchRepoOption{
|
||||
Name: to,
|
||||
}).AddTokenAuth(token)
|
||||
return MakeRequest(t, req, expectedHTTPStatus)
|
||||
}
|
||||
|
||||
func TestAPIBranchProtection(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
|
|
@ -116,6 +116,14 @@ func TestPackageCran(t *testing.T) {
|
|||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("DownloadArchived", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/src/contrib/Archive/%s/%s_%s.tar.gz", url, packageName, packageName, packageVersion)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("Enumerate", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
|
|
|
@ -166,6 +166,11 @@ func TestMain(m *testing.M) {
|
|||
os.Unsetenv("GIT_COMMITTER_EMAIL")
|
||||
os.Unsetenv("GIT_COMMITTER_DATE")
|
||||
|
||||
// Avoid loading the default system config. On MacOS, this config
|
||||
// sets the osxkeychain credential helper, which will cause tests
|
||||
// to freeze with a dialog.
|
||||
os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
|
||||
|
||||
err := unittest.InitFixtures(
|
||||
unittest.FixturesOptions{
|
||||
Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
|
||||
|
|
|
@ -444,7 +444,8 @@ export function initRepositoryActionView() {
|
|||
{{ run.commit.localePushedBy }}
|
||||
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
|
||||
<span class="ui label tw-max-w-full" v-if="run.commit.shortSHA">
|
||||
<a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
|
||||
<span v-if="run.commit.branch.isDeleted" class="gt-ellipsis tw-line-through" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</span>
|
||||
<a v-else class="gt-ellipsis" :href="run.commit.branch.link" :data-tooltip-content="run.commit.branch.name">{{ run.commit.branch.name }}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="action-summary">
|
||||
|
|
Loading…
Reference in a new issue