Skip to content

Commit 2d25b7d

Browse files
authored
Add an api endpoint to fetch git notes (#15373) (#16649)
close #15373
1 parent c4d70a0 commit 2d25b7d

File tree

12 files changed

+228
-1
lines changed

12 files changed

+228
-1
lines changed
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2021 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 integrations
6+
7+
import (
8+
"net/http"
9+
"net/url"
10+
"testing"
11+
12+
"code.gitea.io/gitea/models"
13+
api "code.gitea.io/gitea/modules/structs"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestAPIReposGitNotes(t *testing.T) {
18+
onGiteaRun(t, func(*testing.T, *url.URL) {
19+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
20+
// Login as User2.
21+
session := loginUser(t, user.Name)
22+
token := getTokenForLoggedInUser(t, session)
23+
24+
// check invalid requests
25+
req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345?token=%s", user.Name, token)
26+
session.MakeRequest(t, req, http.StatusNotFound)
27+
28+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..?token=%s", user.Name, token)
29+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
30+
31+
// check valid request
32+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d?token=%s", user.Name, token)
33+
resp := session.MakeRequest(t, req, http.StatusOK)
34+
35+
var apiData api.Note
36+
DecodeJSON(t, resp, &apiData)
37+
assert.Equal(t, "This is a test note\n", apiData.Message)
38+
})
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3fa2f829675543ecfc16b2891aebe8bf0608a8f4

modules/git/notes_gogit.go

+9
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@ import (
1010
"context"
1111
"io/ioutil"
1212

13+
"code.gitea.io/gitea/modules/log"
14+
1315
"github.com/go-git/go-git/v5/plumbing/object"
1416
)
1517

1618
// GetNote retrieves the git-notes data for a given commit.
1719
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
20+
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
1821
notes, err := repo.GetCommit(NotesRef)
1922
if err != nil {
23+
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
2024
return err
2125
}
2226

2327
remainingCommitID := commitID
2428
path := ""
2529
currentTree := notes.Tree.gogitTree
30+
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", currentTree.Entries[0].Name, commitID)
2631
var file *object.File
2732
for len(remainingCommitID) > 2 {
2833
file, err = currentTree.File(remainingCommitID)
@@ -39,19 +44,22 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
3944
if err == object.ErrDirectoryNotFound {
4045
return ErrNotExist{ID: remainingCommitID, RelPath: path}
4146
}
47+
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", commitID, err)
4248
return err
4349
}
4450
}
4551

4652
blob := file.Blob
4753
dataRc, err := blob.Reader()
4854
if err != nil {
55+
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
4956
return err
5057
}
5158

5259
defer dataRc.Close()
5360
d, err := ioutil.ReadAll(dataRc)
5461
if err != nil {
62+
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
5563
return err
5664
}
5765
note.Message = d
@@ -68,6 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
6876

6977
lastCommits, err := GetLastCommitForPaths(ctx, commitNode, "", []string{path})
7078
if err != nil {
79+
log.Error("Unable to get the commit for the path %q. Error: %v", path, err)
7180
return err
7281
}
7382
note.Commit = convertCommit(lastCommits[path])

modules/git/notes_nogogit.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,26 @@ import (
1010
"context"
1111
"io/ioutil"
1212
"strings"
13+
14+
"code.gitea.io/gitea/modules/log"
1315
)
1416

1517
// GetNote retrieves the git-notes data for a given commit.
1618
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
19+
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
1720
notes, err := repo.GetCommit(NotesRef)
1821
if err != nil {
22+
log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err)
1923
return err
2024
}
2125

2226
path := ""
2327

2428
tree := &notes.Tree
29+
log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID)
2530

2631
var entry *TreeEntry
32+
originalCommitID := commitID
2733
for len(commitID) > 2 {
2834
entry, err = tree.GetTreeEntryByPath(commitID)
2935
if err == nil {
@@ -36,12 +42,15 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
3642
commitID = commitID[2:]
3743
}
3844
if err != nil {
45+
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
3946
return err
4047
}
4148
}
4249

43-
dataRc, err := entry.Blob().DataAsync()
50+
blob := entry.Blob()
51+
dataRc, err := blob.DataAsync()
4452
if err != nil {
53+
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
4554
return err
4655
}
4756
closed := false
@@ -52,6 +61,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
5261
}()
5362
d, err := ioutil.ReadAll(dataRc)
5463
if err != nil {
64+
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
5565
return err
5666
}
5767
_ = dataRc.Close()
@@ -66,6 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
6676

6777
lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path})
6878
if err != nil {
79+
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
6980
return err
7081
}
7182
note.Commit = lastCommits[path]

modules/structs/repo_note.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2021 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 structs
6+
7+
// Note contains information related to a git note
8+
type Note struct {
9+
Message string `json:"message"`
10+
Commit *Commit `json:"commit"`
11+
}

routers/api/v1/api.go

+1
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ func Routes() *web.Route {
953953
m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree)
954954
m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob)
955955
m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag)
956+
m.Get("/notes/{sha}", repo.GetNote)
956957
}, reqRepoReader(models.UnitTypeCode))
957958
m.Group("/contents", func() {
958959
m.Get("", repo.GetContentsList)

routers/api/v1/repo/notes.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2021 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 repo
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
11+
"code.gitea.io/gitea/modules/context"
12+
"code.gitea.io/gitea/modules/convert"
13+
"code.gitea.io/gitea/modules/git"
14+
api "code.gitea.io/gitea/modules/structs"
15+
"code.gitea.io/gitea/modules/validation"
16+
)
17+
18+
// GetNote Get a note corresponding to a single commit from a repository
19+
func GetNote(ctx *context.APIContext) {
20+
// swagger:operation GET /repos/{owner}/{repo}/git/notes/{sha} repository repoGetNote
21+
// ---
22+
// summary: Get a note corresponding to a single commit from a repository
23+
// produces:
24+
// - application/json
25+
// parameters:
26+
// - name: owner
27+
// in: path
28+
// description: owner of the repo
29+
// type: string
30+
// required: true
31+
// - name: repo
32+
// in: path
33+
// description: name of the repo
34+
// type: string
35+
// required: true
36+
// - name: sha
37+
// in: path
38+
// description: a git ref or commit sha
39+
// type: string
40+
// required: true
41+
// responses:
42+
// "200":
43+
// "$ref": "#/responses/Note"
44+
// "422":
45+
// "$ref": "#/responses/validationError"
46+
// "404":
47+
// "$ref": "#/responses/notFound"
48+
49+
sha := ctx.Params(":sha")
50+
if (validation.GitRefNamePatternInvalid.MatchString(sha) || !validation.CheckGitRefAdditionalRulesValid(sha)) && !git.SHAPattern.MatchString(sha) {
51+
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
52+
return
53+
}
54+
getNote(ctx, sha)
55+
}
56+
57+
func getNote(ctx *context.APIContext, identifier string) {
58+
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
59+
if err != nil {
60+
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
61+
return
62+
}
63+
defer gitRepo.Close()
64+
var note git.Note
65+
err = git.GetNote(ctx, gitRepo, identifier, &note)
66+
if err != nil {
67+
if git.IsErrNotExist(err) {
68+
ctx.NotFound(identifier)
69+
return
70+
}
71+
ctx.Error(http.StatusInternalServerError, "GetNote", err)
72+
return
73+
}
74+
75+
cmt, err := convert.ToCommit(ctx.Repo.Repository, note.Commit, nil)
76+
if err != nil {
77+
ctx.Error(http.StatusInternalServerError, "ToCommit", err)
78+
return
79+
}
80+
apiNote := api.Note{Message: string(note.Message), Commit: cmt}
81+
ctx.JSON(http.StatusOK, apiNote)
82+
}

routers/api/v1/swagger/repo.go

+7
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@ type swaggerCommitList struct {
254254
Body []api.Commit `json:"body"`
255255
}
256256

257+
// Note
258+
// swagger:response Note
259+
type swaggerNote struct {
260+
// in: body
261+
Body api.Note `json:"body"`
262+
}
263+
257264
// EmptyRepository
258265
// swagger:response EmptyRepository
259266
type swaggerEmptyRepository struct {

templates/swagger/v1_json.tmpl

+66
Original file line numberDiff line numberDiff line change
@@ -3569,6 +3569,52 @@
35693569
}
35703570
}
35713571
},
3572+
"/repos/{owner}/{repo}/git/notes/{sha}": {
3573+
"get": {
3574+
"produces": [
3575+
"application/json"
3576+
],
3577+
"tags": [
3578+
"repository"
3579+
],
3580+
"summary": "Get a note corresponding to a single commit from a repository",
3581+
"operationId": "repoGetNote",
3582+
"parameters": [
3583+
{
3584+
"type": "string",
3585+
"description": "owner of the repo",
3586+
"name": "owner",
3587+
"in": "path",
3588+
"required": true
3589+
},
3590+
{
3591+
"type": "string",
3592+
"description": "name of the repo",
3593+
"name": "repo",
3594+
"in": "path",
3595+
"required": true
3596+
},
3597+
{
3598+
"type": "string",
3599+
"description": "a git ref or commit sha",
3600+
"name": "sha",
3601+
"in": "path",
3602+
"required": true
3603+
}
3604+
],
3605+
"responses": {
3606+
"200": {
3607+
"$ref": "#/responses/Note"
3608+
},
3609+
"404": {
3610+
"$ref": "#/responses/notFound"
3611+
},
3612+
"422": {
3613+
"$ref": "#/responses/validationError"
3614+
}
3615+
}
3616+
}
3617+
},
35723618
"/repos/{owner}/{repo}/git/refs": {
35733619
"get": {
35743620
"produces": [
@@ -15453,6 +15499,20 @@
1545315499
},
1545415500
"x-go-package": "code.gitea.io/gitea/modules/structs"
1545515501
},
15502+
"Note": {
15503+
"description": "Note contains information related to a git note",
15504+
"type": "object",
15505+
"properties": {
15506+
"commit": {
15507+
"$ref": "#/definitions/Commit"
15508+
},
15509+
"message": {
15510+
"type": "string",
15511+
"x-go-name": "Message"
15512+
}
15513+
},
15514+
"x-go-package": "code.gitea.io/gitea/modules/structs"
15515+
},
1545615516
"NotificationCount": {
1545715517
"description": "NotificationCount number of unread notifications",
1545815518
"type": "object",
@@ -17412,6 +17472,12 @@
1741217472
}
1741317473
}
1741417474
},
17475+
"Note": {
17476+
"description": "Note",
17477+
"schema": {
17478+
"$ref": "#/definitions/Note"
17479+
}
17480+
},
1741517481
"NotificationCount": {
1741617482
"description": "Number of unread notifications",
1741717483
"schema": {

0 commit comments

Comments
 (0)