diff --git a/models/git/refs.go b/models/git/refs.go new file mode 100644 index 0000000000000..ec2899627b14b --- /dev/null +++ b/models/git/refs.go @@ -0,0 +1,72 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "strings" + + "code.gitea.io/gitea/modules/git" +) + +// CheckReferenceEditability checks if the reference can be modified by the user or any user +func CheckReferenceEditability(refName, commitID string, repoID, userID int64) error { + refParts := strings.Split(refName, "/") + + // Must have at least 3 parts, e.g. refs/heads/new-branch + if len(refParts) <= 2 { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "reference name must contain at least three slash-separted components", + } + } + + // Must start with 'refs/' + if refParts[0] != "refs/" { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "reference must start with 'refs/'", + } + } + + // 'refs/pull/*' is not allowed + if refParts[1] == "pull" { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "refs/pull/* is read-only", + } + } + + if refParts[1] == "tags" { + // If the 2nd part is "tags" then we need ot make sure the user is allowed to + // modify this tag (not protected or is admin) + if protectedTags, err := GetProtectedTags(repoID); err == nil { + isAllowed, err := IsUserAllowedToControlTag(protectedTags, refName, userID) + if err != nil { + return err + } + if !isAllowed { + return git.ErrProtectedRefName{ + RefName: refName, + Message: "you're not authorized to change a protected tag", + } + } + } + } else if refParts[1] == "heads" { + // If the 2nd part is "heas" then we need to make sure the user is allowed to + // modify this branch (not protected or is admin) + isProtected, err := IsProtectedBranch(repoID, refName) + if err != nil { + return err + } + if !isProtected { + return git.ErrProtectedRefName{ + RefName: refName, + Message: "changes must be made through a pull request", + } + } + } + + return nil +} diff --git a/modules/convert/git_ref.go b/modules/convert/git_ref.go new file mode 100644 index 0000000000000..29d196ffbaf36 --- /dev/null +++ b/modules/convert/git_ref.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package convert + +import ( + "net/url" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" +) + +// ToGitRef converts a git.Reference to a api.Reference +func ToGitRef(repo *repo_model.Repository, ref *git.Reference) *api.Reference { + return &api.Reference{ + Ref: ref.Name, + URL: repo.APIURL() + "/git/" + util.PathEscapeSegments(ref.Name), + Object: &api.GitObject{ + SHA: ref.Object.String(), + Type: ref.Type, + URL: repo.APIURL() + "/git/" + url.PathEscape(ref.Type) + "s/" + url.PathEscape(ref.Object.String()), + }, + } +} diff --git a/modules/git/error.go b/modules/git/error.go index ebfea8e702c2e..2b8ce19140868 100644 --- a/modules/git/error.go +++ b/modules/git/error.go @@ -186,3 +186,69 @@ func IsErrMoreThanOne(err error) bool { func (err *ErrMoreThanOne) Error() string { return fmt.Sprintf("ErrMoreThanOne Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) } + +// ErrRefNotFound represents a "RefDoesMotExist" kind of error. +type ErrRefNotFound struct { + RefName string +} + +// IsErrRefNotFound checks if an error is a ErrRefNotFound. +func IsErrRefNotFound(err error) bool { + _, ok := err.(ErrRefNotFound) + return ok +} + +func (err ErrRefNotFound) Error() string { + return fmt.Sprintf("ref does not exist [ref_name: %s]", err.RefName) +} + +// ErrInvalidRefName represents a "InvalidRefName" kind of error. +type ErrInvalidRefName struct { + RefName string + Reason string +} + +// IsErrInvalidRefName checks if an error is a ErrInvalidRefName. +func IsErrInvalidRefName(err error) bool { + _, ok := err.(ErrInvalidRefName) + return ok +} + +func (err ErrInvalidRefName) Error() string { + return fmt.Sprintf("ref name is not valid: %s [ref_name: %s]", err.Reason, err.RefName) +} + +// ErrProtectedRefName represents a "ProtectedRefName" kind of error. +type ErrProtectedRefName struct { + RefName string + Message string +} + +// IsErrProtectedRefName checks if an error is a ErrProtectedRefName. +func IsErrProtectedRefName(err error) bool { + _, ok := err.(ErrProtectedRefName) + return ok +} + +func (err ErrProtectedRefName) Error() string { + str := fmt.Sprintf("ref name is protected [ref_name: %s]", err.RefName) + if err.Message != "" { + str = fmt.Sprintf("%s: %s", str, err.Message) + } + return str +} + +// ErrRefAlreadyExists represents an error that ref with such name already exists. +type ErrRefAlreadyExists struct { + RefName string +} + +// IsErrRefAlreadyExists checks if an error is an ErrRefAlreadyExists. +func IsErrRefAlreadyExists(err error) bool { + _, ok := err.(ErrRefAlreadyExists) + return ok +} + +func (err ErrRefAlreadyExists) Error() string { + return fmt.Sprintf("ref already exists [name: %s]", err.RefName) +} diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 397434e12f2a6..1ac1b269063c7 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -8,3 +8,18 @@ package git func (repo *Repository) GetRefs() ([]*Reference, error) { return repo.GetRefsFiltered("") } + +// GetReference gets the Reference object that a refName refers to +func (repo *Repository) GetReference(refName string) (*Reference, error) { + refs, err := repo.GetRefsFiltered(refName) + if err != nil { + return nil, err + } + var ref *Reference + for _, ref = range refs { + if ref.Name == refName { + return ref, nil + } + } + return nil, ErrRefNotFound{RefName: refName} +} diff --git a/modules/structs/repo.go b/modules/structs/repo.go index cf6601704edfb..6832c76e1afe7 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -242,6 +242,29 @@ type CreateBranchRepoOption struct { OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"` } +// CreateGitRefOption options when creating a git ref in a repository +// swagger:model +type CreateGitRefOption struct { + // The name of the reference. + // + // required: true + RefName string `json:"ref" binding:"Required;GitRefName;MaxSize(100)"` + + // The target commitish for this reference. + // + // required: true + Target string `json:"target" binding:"Required"` +} + +// UpdateGitRefOption options when updating a git ref in a repository +// swagger:model +type UpdateGitRefOption struct { + // The target commitish for the reference to be updated to. + // + // required: true + Target string `json:"target" binding:"Required"` +} + // TransferRepoOption options when transfer a repository's ownership // swagger:model type TransferRepoOption struct { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0d11674aa9971..d14f796f6c054 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1042,8 +1042,15 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/{sha}", repo.GetSingleCommit) m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch) }) - m.Get("/refs", repo.GetGitAllRefs) - m.Get("/refs/*", repo.GetGitRefs) + m.Group("/refs", func() { + m.Get("", repo.GetGitAllRefs) + m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateGitRefOption{}), repo.CreateGitRef) + m.Get("/*", repo.GetGitRefs) + m.Group("/*", func() { + m.Patch("", bind(api.UpdateGitRefOption{}), repo.UpdateGitRef) + m.Delete("", repo.DeleteGitRef) + }, reqToken(), reqRepoWriter(unit.TypeCode)) + }) m.Get("/trees/{sha}", repo.GetTree) m.Get("/blobs/{sha}", repo.GetBlob) m.Get("/tags/{sha}", repo.GetAnnotatedTag) diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go index 47f46df339ffc..3c20247f59110 100644 --- a/routers/api/v1/repo/git_ref.go +++ b/routers/api/v1/repo/git_ref.go @@ -5,13 +5,16 @@ package repo import ( + "fmt" "net/http" - "net/url" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/git" 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/gitref" ) // GetGitAllRefs get ref or an list all the refs of a repository @@ -89,15 +92,7 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) { apiRefs := make([]*api.Reference, len(refs)) for i := range refs { - apiRefs[i] = &api.Reference{ - Ref: refs[i].Name, - URL: ctx.Repo.Repository.APIURL() + "/git/" + util.PathEscapeSegments(refs[i].Name), - Object: &api.GitObject{ - SHA: refs[i].Object.String(), - Type: refs[i].Type, - URL: ctx.Repo.Repository.APIURL() + "/git/" + url.PathEscape(refs[i].Type) + "s/" + url.PathEscape(refs[i].Object.String()), - }, - } + apiRefs[i] = convert.ToGitRef(ctx.Repo.Repository, refs[i]) } // If single reference is found and it matches filter exactly return it as object if len(apiRefs) == 1 && apiRefs[0].Ref == filter { @@ -106,3 +101,200 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) { } ctx.JSON(http.StatusOK, &apiRefs) } + +// CreateGitRef creates a git ref for a repository that points to a target commitish +func CreateGitRef(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/git/refs repository repoCreateGitRef + // --- + // summary: Create a reference + // description: Creates a reference for your repository. You are unable to create new references for empty repositories, + // even if the commit SHA-1 hash used exists. Empty repositories are repositories without branches. + // 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: body + // in: body + // schema: + // "$ref": "#/definitions/CreateGitRefOption" + // responses: + // "201": + // "$ref": "#/responses/Reference" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // description: The git ref with the same name already exists. + // "422": + // description: Unable to form reference + + opt := web.GetForm(ctx).(*api.CreateGitRefOption) + + if ctx.Repo.GitRepo.IsReferenceExist(opt.RefName) { + ctx.Error(http.StatusConflict, "reference exists", fmt.Errorf("reference already exists: %s", opt.RefName)) + return + } + + commitID, err := ctx.Repo.GitRepo.GetRefCommitID(opt.Target) + if err != nil { + if git.IsErrNotExist(err) { + ctx.Error(http.StatusNotFound, "invalid target", fmt.Errorf("target does not exist: %s", opt.Target)) + return + } + ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err) + return + } + + ref, err := gitref.UpdateReferenceWithChecks(ctx, opt.RefName, commitID) + if err != nil { + if git.IsErrInvalidRefName(err) { + ctx.Error(http.StatusUnprocessableEntity, "invalid reference'", err) + } else if git.IsErrProtectedRefName(err) { + ctx.Error(http.StatusMethodNotAllowed, "protected reference", err) + } else if git.IsErrRefNotFound(err) { + ctx.Error(http.StatusUnprocessableEntity, "UpdateReferenceWithChecks", fmt.Errorf("unable to load reference [ref_name: %s]", opt.RefName)) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.JSON(http.StatusCreated, convert.ToGitRef(ctx.Repo.Repository, ref)) +} + +// UpdateGitRef updates a branch for a repository from a commit SHA +func UpdateGitRef(ctx *context.APIContext) { + // swagger:operation PATCH /repos/{owner}/{repo}/git/refs/{ref} repository repoUpdateGitRef + // --- + // summary: Update a reference + // description: + // 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: ref + // in: path + // description: name of the ref to update + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateGitRefOption" + // responses: + // "200": + // "$ref": "#/responses/Reference" + // "404": + // "$ref": "#/responses/notFound" + + refName := fmt.Sprintf("refs/%s", ctx.Params("*")) + opt := web.GetForm(ctx).(*api.UpdateGitRefOption) + + if ctx.Repo.GitRepo.IsReferenceExist(refName) { + ctx.Error(http.StatusConflict, "reference exists", fmt.Errorf("reference already exists: %s", refName)) + return + } + + commitID, err := ctx.Repo.GitRepo.GetRefCommitID(opt.Target) + if err != nil { + if git.IsErrNotExist(err) { + ctx.Error(http.StatusNotFound, "invalid target", fmt.Errorf("target does not exist: %s", opt.Target)) + return + } + ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err) + return + } + + ref, err := gitref.UpdateReferenceWithChecks(ctx, refName, commitID) + if err != nil { + if git.IsErrInvalidRefName(err) { + ctx.Error(http.StatusUnprocessableEntity, "invalid reference'", err) + } else if git.IsErrProtectedRefName(err) { + ctx.Error(http.StatusMethodNotAllowed, "protected reference", err) + } else if git.IsErrRefNotFound(err) { + ctx.Error(http.StatusUnprocessableEntity, "UpdateReferenceWithChecks", fmt.Errorf("unable to load reference [ref_name: %s]", refName)) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.JSON(http.StatusCreated, ref) +} + +// DeleteGitRef deletes a git ref for a repository that points to a target commitish +func DeleteGitRef(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/git/refs/{ref} repository repoDeleteGitRef + // --- + // summary: Delete a reference + // 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: ref + // in: path + // description: name of the ref to be deleted + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + // "404": + // "$ref": "#/responses/notFound" + // "405": + // "$ref": "#/responses/error" + // "409": + // "$ref": "#/responses/conflict" + + refName := fmt.Sprintf("refs/%s", ctx.Params("*")) + + if !ctx.Repo.GitRepo.IsReferenceExist(refName) { + ctx.Error(http.StatusNotFound, "git ref does not exist:", fmt.Errorf("reference does not exist: %s", refName)) + return + } + + err := gitref.RemoveReferenceWithChecks(ctx, refName) + if err != nil { + if git.IsErrInvalidRefName(err) { + ctx.Error(http.StatusUnprocessableEntity, "invalid reference'", err) + } else if git.IsErrProtectedRefName(err) { + ctx.Error(http.StatusMethodNotAllowed, "protected reference", err) + } else { + ctx.InternalServerError(err) + } + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index acc9696e1bef1..abb440ca4dc4e 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -213,7 +213,7 @@ func CreateRelease(ctx *context.APIContext) { } } else { if !rel.IsTag { - ctx.Error(http.StatusConflict, "GetRelease", "Release is has no Tag") + ctx.Error(http.StatusConflict, "GetRelease", "Release has no Tag") return } diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index e8cfc0706f5ca..e5534d770df2a 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -172,4 +172,10 @@ type swaggerParameterBodies struct { // in:body CreatePushMirrorOption api.CreatePushMirrorOption + + // in:body + CreateGitRefOption api.CreateGitRefOption + + // in:body + UpdateGitRefOption api.UpdateGitRefOption } diff --git a/services/gitref/gitref.go b/services/gitref/gitref.go new file mode 100644 index 0000000000000..787135c08969e --- /dev/null +++ b/services/gitref/gitref.go @@ -0,0 +1,124 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitref + +import ( + "fmt" + "strings" + + git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" +) + +// GetReference gets the Reference object that a refName refers to +func GetReference(gitRepo *git.Repository, refName string) (*git.Reference, error) { + refs, err := gitRepo.GetRefsFiltered(refName) + if err != nil { + return nil, err + } + var ref *git.Reference + for _, ref = range refs { + if ref.Name == refName { + return ref, nil + } + } + return nil, git.ErrRefNotFound{RefName: refName} +} + +// UpdateReferenceWithChecks creates or updates a reference, checking for format, permissions and special cases +func UpdateReferenceWithChecks(ctx *context.APIContext, refName, commitID string) (*git.Reference, error) { + err := CheckReferenceEditability(refName, commitID, ctx.Repo.Repository.ID, ctx.Doer.ID) + if err != nil { + return nil, err + } + + if err := ctx.Repo.GitRepo.SetReference(refName, commitID); err != nil { + message := err.Error() + prefix := fmt.Sprintf("exit status 128 - fatal: update_ref failed for ref '%s': ", refName) + if strings.HasPrefix(message, prefix) { + return nil, fmt.Errorf(strings.TrimRight(strings.TrimPrefix(message, prefix), "\n")) + } + return nil, err + } + + return ctx.Repo.GitRepo.GetReference(refName) +} + +// RemoveReferenceWithChecks deletes a reference, checking for format, permission and special cases +func RemoveReferenceWithChecks(ctx *context.APIContext, refName string) error { + err := CheckReferenceEditability(refName, "", ctx.Repo.Repository.ID, ctx.Doer.ID) + if err != nil { + return err + } + + return ctx.Repo.GitRepo.RemoveReference(refName) +} + +func CheckReferenceEditability(refName, commitID string, repoID, userID int64) error { + refParts := strings.Split(refName, "/") + + // Must have at least 3 parts, e.g. refs/heads/new-branch + if len(refParts) <= 2 { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "reference name must contain at least three slash-separted components", + } + } + + refPrefix := refParts[0] + refType := refParts[2] + refRest := strings.Join(refParts[2:], "/") + + // Must start with 'refs/' + if refPrefix != "refs" { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "reference must start with 'refs/'", + } + } + + // 'refs/pull/*' is not allowed + if refType == "pull" { + return git.ErrInvalidRefName{ + RefName: refName, + Reason: "refs/pull/* is read-only", + } + } + + if refType == "tags" { + // If the 2nd part is "tags" then we need ot make sure the user is allowed to + // modify this tag (not protected or is admin) + if protectedTags, err := git_model.GetProtectedTags(repoID); err == nil { + isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, refRest, userID) + if err != nil { + return err + } + if !isAllowed { + return git.ErrProtectedRefName{ + RefName: refName, + Message: "you're not authorized to change a protected tag", + } + } + } + } + + if refType == "heads" { + // If the 2nd part is "heas" then we need to make sure the user is allowed to + // modify this branch (not protected or is admin) + isProtected, err := git_model.IsProtectedBranch(repoID, refRest) + if err != nil { + return err + } + if !isProtected { + return git.ErrProtectedRefName{ + RefName: refName, + Message: "changes must be made through a pull request", + } + } + } + + return nil +} diff --git a/services/gitref/gitref_test.go b/services/gitref/gitref_test.go new file mode 100644 index 0000000000000..183c0cbecfd63 --- /dev/null +++ b/services/gitref/gitref_test.go @@ -0,0 +1,40 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitref + +import ( + "path/filepath" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m, &unittest.TestOptions{ + GiteaRootPath: filepath.Join("..", ".."), + }) +} + +func TestGitRef_Get(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repoPath := repo_model.RepoPath(user.Name, repo.Name) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repoPath) + assert.NoError(t, err) + defer gitRepo.Close() + + ref, err := GetReference(gitRepo, "refs/heads/master") + assert.NoError(t, err) + + assert.NotNil(t, ref) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 94fb67ab44568..f2eed87ce5056 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4105,6 +4105,57 @@ "$ref": "#/responses/notFound" } } + }, + "post": { + "description": "Creates a reference for your repository. You are unable to create new references for empty repositories, even if the commit SHA-1 hash used exists. Empty repositories are repositories without branches.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Create a reference", + "operationId": "repoCreateGitRef", + "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 + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateGitRefOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Reference" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "409": { + "description": "The git ref with the same name already exists." + }, + "422": { + "description": "Unable to form reference" + } + } } }, "/repos/{owner}/{repo}/git/refs/{ref}": { @@ -4148,6 +4199,107 @@ "$ref": "#/responses/notFound" } } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Delete a reference", + "operationId": "repoDeleteGitRef", + "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 ref to be deleted", + "name": "ref", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "405": { + "$ref": "#/responses/error" + }, + "409": { + "$ref": "#/responses/conflict" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Update a reference", + "operationId": "repoUpdateGitRef", + "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 ref to update", + "name": "ref", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateGitRefOption" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/Reference" + }, + "404": { + "$ref": "#/responses/notFound" + } + } } }, "/repos/{owner}/{repo}/git/tags/{sha}": { @@ -14435,6 +14587,27 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "CreateGitRefOption": { + "description": "CreateGitRefOption options when creating a git ref in a repository", + "type": "object", + "required": [ + "ref", + "target" + ], + "properties": { + "ref": { + "description": "The name of the reference.", + "type": "string", + "x-go-name": "RefName" + }, + "target": { + "description": "The target commitish for this reference.", + "type": "string", + "x-go-name": "Target" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "CreateHookOption": { "description": "CreateHookOption options when create a hook", "type": "object", @@ -18924,6 +19097,21 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpdateGitRefOption": { + "description": "UpdateGitRefOption options when updating a git ref in a repository", + "type": "object", + "required": [ + "target" + ], + "properties": { + "target": { + "description": "The target commitish for the reference to be updated to.", + "type": "string", + "x-go-name": "Target" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "User": { "description": "User represents a user", "type": "object", @@ -20126,7 +20314,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreatePushMirrorOption" + "$ref": "#/definitions/UpdateGitRefOption" } }, "redirect": {