Skip to content

Commit 0d8c2a8

Browse files
committed
Feature #11835 - Adds Git Refs API
1 parent a08b484 commit 0d8c2a8

File tree

12 files changed

+775
-15
lines changed

12 files changed

+775
-15
lines changed

models/git/refs.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package git
6+
7+
import (
8+
"strings"
9+
10+
"code.gitea.io/gitea/modules/git"
11+
)
12+
13+
// CheckReferenceEditability checks if the reference can be modified by the user or any user
14+
func CheckReferenceEditability(refName, commitID string, repoID, userID int64) error {
15+
refParts := strings.Split(refName, "/")
16+
17+
// Must have at least 3 parts, e.g. refs/heads/new-branch
18+
if len(refParts) <= 2 {
19+
return git.ErrInvalidRefName{
20+
RefName: refName,
21+
Reason: "reference name must contain at least three slash-separted components",
22+
}
23+
}
24+
25+
// Must start with 'refs/'
26+
if refParts[0] != "refs/" {
27+
return git.ErrInvalidRefName{
28+
RefName: refName,
29+
Reason: "reference must start with 'refs/'",
30+
}
31+
}
32+
33+
// 'refs/pull/*' is not allowed
34+
if refParts[1] == "pull" {
35+
return git.ErrInvalidRefName{
36+
RefName: refName,
37+
Reason: "refs/pull/* is read-only",
38+
}
39+
}
40+
41+
if refParts[1] == "tags" {
42+
// If the 2nd part is "tags" then we need ot make sure the user is allowed to
43+
// modify this tag (not protected or is admin)
44+
if protectedTags, err := GetProtectedTags(repoID); err == nil {
45+
isAllowed, err := IsUserAllowedToControlTag(protectedTags, refName, userID)
46+
if err != nil {
47+
return err
48+
}
49+
if !isAllowed {
50+
return git.ErrProtectedRefName{
51+
RefName: refName,
52+
Message: "you're not authorized to change a protected tag",
53+
}
54+
}
55+
}
56+
} else if refParts[1] == "heads" {
57+
// If the 2nd part is "heas" then we need to make sure the user is allowed to
58+
// modify this branch (not protected or is admin)
59+
isProtected, err := IsProtectedBranch(repoID, refName)
60+
if err != nil {
61+
return err
62+
}
63+
if !isProtected {
64+
return git.ErrProtectedRefName{
65+
RefName: refName,
66+
Message: "changes must be made through a pull request",
67+
}
68+
}
69+
}
70+
71+
return nil
72+
}

modules/convert/git_ref.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package convert
6+
7+
import (
8+
"net/url"
9+
10+
repo_model "code.gitea.io/gitea/models/repo"
11+
"code.gitea.io/gitea/modules/git"
12+
api "code.gitea.io/gitea/modules/structs"
13+
"code.gitea.io/gitea/modules/util"
14+
)
15+
16+
// ToGitRef converts a git.Reference to a api.Reference
17+
func ToGitRef(repo *repo_model.Repository, ref *git.Reference) *api.Reference {
18+
return &api.Reference{
19+
Ref: ref.Name,
20+
URL: repo.APIURL() + "/git/" + util.PathEscapeSegments(ref.Name),
21+
Object: &api.GitObject{
22+
SHA: ref.Object.String(),
23+
Type: ref.Type,
24+
URL: repo.APIURL() + "/git/" + url.PathEscape(ref.Type) + "s/" + url.PathEscape(ref.Object.String()),
25+
},
26+
}
27+
}

modules/git/error.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,69 @@ func IsErrMoreThanOne(err error) bool {
176176
func (err *ErrMoreThanOne) Error() string {
177177
return fmt.Sprintf("ErrMoreThanOne Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
178178
}
179+
180+
// ErrRefNotFound represents a "RefDoesMotExist" kind of error.
181+
type ErrRefNotFound struct {
182+
RefName string
183+
}
184+
185+
// IsErrRefNotFound checks if an error is a ErrRefNotFound.
186+
func IsErrRefNotFound(err error) bool {
187+
_, ok := err.(ErrRefNotFound)
188+
return ok
189+
}
190+
191+
func (err ErrRefNotFound) Error() string {
192+
return fmt.Sprintf("ref does not exist [ref_name: %s]", err.RefName)
193+
}
194+
195+
// ErrInvalidRefName represents a "InvalidRefName" kind of error.
196+
type ErrInvalidRefName struct {
197+
RefName string
198+
Reason string
199+
}
200+
201+
// IsErrInvalidRefName checks if an error is a ErrInvalidRefName.
202+
func IsErrInvalidRefName(err error) bool {
203+
_, ok := err.(ErrInvalidRefName)
204+
return ok
205+
}
206+
207+
func (err ErrInvalidRefName) Error() string {
208+
return fmt.Sprintf("ref name is not valid: %s [ref_name: %s]", err.Reason, err.RefName)
209+
}
210+
211+
// ErrProtectedRefName represents a "ProtectedRefName" kind of error.
212+
type ErrProtectedRefName struct {
213+
RefName string
214+
Message string
215+
}
216+
217+
// IsErrProtectedRefName checks if an error is a ErrProtectedRefName.
218+
func IsErrProtectedRefName(err error) bool {
219+
_, ok := err.(ErrProtectedRefName)
220+
return ok
221+
}
222+
223+
func (err ErrProtectedRefName) Error() string {
224+
str := fmt.Sprintf("ref name is protected [ref_name: %s]", err.RefName)
225+
if err.Message != "" {
226+
str = fmt.Sprintf("%s: %s", str, err.Message)
227+
}
228+
return str
229+
}
230+
231+
// ErrRefAlreadyExists represents an error that ref with such name already exists.
232+
type ErrRefAlreadyExists struct {
233+
RefName string
234+
}
235+
236+
// IsErrRefAlreadyExists checks if an error is an ErrRefAlreadyExists.
237+
func IsErrRefAlreadyExists(err error) bool {
238+
_, ok := err.(ErrRefAlreadyExists)
239+
return ok
240+
}
241+
242+
func (err ErrRefAlreadyExists) Error() string {
243+
return fmt.Sprintf("ref already exists [name: %s]", err.RefName)
244+
}

modules/git/repo_ref.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,18 @@ package git
88
func (repo *Repository) GetRefs() ([]*Reference, error) {
99
return repo.GetRefsFiltered("")
1010
}
11+
12+
// GetReference gets the Reference object that a refName refers to
13+
func (repo *Repository) GetReference(refName string) (*Reference, error) {
14+
refs, err := repo.GetRefsFiltered(refName)
15+
if err != nil {
16+
return nil, err
17+
}
18+
var ref *Reference
19+
for _, ref = range refs {
20+
if ref.Name == refName {
21+
return ref, nil
22+
}
23+
}
24+
return nil, ErrRefNotFound{RefName: refName}
25+
}

modules/structs/repo.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,29 @@ type CreateBranchRepoOption struct {
240240
OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"`
241241
}
242242

243+
// CreateGitRefOption options when creating a git ref in a repository
244+
// swagger:model
245+
type CreateGitRefOption struct {
246+
// The name of the reference.
247+
//
248+
// required: true
249+
RefName string `json:"ref" binding:"Required;GitRefName;MaxSize(100)"`
250+
251+
// The target commitish for this reference.
252+
//
253+
// required: true
254+
Target string `json:"target" binding:"Required"`
255+
}
256+
257+
// UpdateGitRefOption options when updating a git ref in a repository
258+
// swagger:model
259+
type UpdateGitRefOption struct {
260+
// The target commitish for the reference to be updated to.
261+
//
262+
// required: true
263+
Target string `json:"target" binding:"Required"`
264+
}
265+
243266
// TransferRepoOption options when transfer a repository's ownership
244267
// swagger:model
245268
type TransferRepoOption struct {

routers/api/v1/api.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,8 +1042,15 @@ func Routes(ctx gocontext.Context) *web.Route {
10421042
m.Get("/{sha}", repo.GetSingleCommit)
10431043
m.Get("/{sha}.{diffType:diff|patch}", repo.DownloadCommitDiffOrPatch)
10441044
})
1045-
m.Get("/refs", repo.GetGitAllRefs)
1046-
m.Get("/refs/*", repo.GetGitRefs)
1045+
m.Group("/refs", func() {
1046+
m.Get("", repo.GetGitAllRefs)
1047+
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateGitRefOption{}), repo.CreateGitRef)
1048+
m.Get("/*", repo.GetGitRefs)
1049+
m.Group("/*", func() {
1050+
m.Patch("", bind(api.UpdateGitRefOption{}), repo.UpdateGitRef)
1051+
m.Delete("", repo.DeleteGitRef)
1052+
}, reqToken(), reqRepoWriter(unit.TypeCode))
1053+
})
10471054
m.Get("/trees/{sha}", repo.GetTree)
10481055
m.Get("/blobs/{sha}", repo.GetBlob)
10491056
m.Get("/tags/{sha}", repo.GetAnnotatedTag)

0 commit comments

Comments
 (0)