Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions integrations/api_repo_git_notes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2021 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 integrations

import (
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)

func TestAPIReposGitNotes(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
// Login as User2.
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session)

// check invalid requests
req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345?token=%s", user.Name, token)
session.MakeRequest(t, req, http.StatusNotFound)

req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..?token=%s", user.Name, token)
session.MakeRequest(t, req, http.StatusUnprocessableEntity)

// check valid request
req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d?token=%s", user.Name, token)
resp := session.MakeRequest(t, req, http.StatusOK)

var apiData api.Note
DecodeJSON(t, resp, &apiData)
assert.Equal(t, "This is a test note\n", apiData.Message)
})
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3fa2f829675543ecfc16b2891aebe8bf0608a8f4
9 changes: 9 additions & 0 deletions modules/git/notes_gogit.go
Original file line number Diff line number Diff line change
@@ -10,19 +10,24 @@ import (
"context"
"io/ioutil"

"code.gitea.io/gitea/modules/log"

"github.com/go-git/go-git/v5/plumbing/object"
)

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

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

blob := file.Blob
dataRc, err := blob.Reader()
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}

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

lastCommits, err := GetLastCommitForPaths(ctx, commitNode, "", []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", path, err)
return err
}
note.Commit = convertCommit(lastCommits[path])
13 changes: 12 additions & 1 deletion modules/git/notes_nogogit.go
Original file line number Diff line number Diff line change
@@ -10,20 +10,26 @@ import (
"context"
"io/ioutil"
"strings"

"code.gitea.io/gitea/modules/log"
)

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

path := ""

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

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

dataRc, err := entry.Blob().DataAsync()
blob := entry.Blob()
dataRc, err := blob.DataAsync()
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
closed := false
@@ -52,6 +61,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
}()
d, err := ioutil.ReadAll(dataRc)
if err != nil {
log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err)
return err
}
_ = dataRc.Close()
@@ -66,6 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)

lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
return err
}
note.Commit = lastCommits[path]
11 changes: 11 additions & 0 deletions modules/structs/repo_note.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2021 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 structs

// Note contains information related to a git note
type Note struct {
Message string `json:"message"`
Commit *Commit `json:"commit"`
}
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
@@ -953,6 +953,7 @@ func Routes() *web.Route {
m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree)
m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob)
m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag)
m.Get("/notes/{sha}", repo.GetNote)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
82 changes: 82 additions & 0 deletions routers/api/v1/repo/notes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2021 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 repo

import (
"fmt"
"net/http"

"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/validation"
)

// GetNote Get a note corresponding to a single commit from a repository
func GetNote(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/git/notes/{sha} repository repoGetNote
// ---
// summary: Get a note corresponding to a single commit from a repository
// 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: sha
// in: path
// description: a git ref or commit sha
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Note"
// "422":
// "$ref": "#/responses/validationError"
// "404":
// "$ref": "#/responses/notFound"

sha := ctx.Params(":sha")
if (validation.GitRefNamePatternInvalid.MatchString(sha) || !validation.CheckGitRefAdditionalRulesValid(sha)) && !git.SHAPattern.MatchString(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return
}
getNote(ctx, sha)
}

func getNote(ctx *context.APIContext, identifier string) {
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
defer gitRepo.Close()
var note git.Note
err = git.GetNote(ctx, gitRepo, identifier, &note)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(identifier)
return
}
ctx.Error(http.StatusInternalServerError, "GetNote", err)
return
}

cmt, err := convert.ToCommit(ctx.Repo.Repository, note.Commit, nil)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToCommit", err)
return
}
apiNote := api.Note{Message: string(note.Message), Commit: cmt}
ctx.JSON(http.StatusOK, apiNote)
}
7 changes: 7 additions & 0 deletions routers/api/v1/swagger/repo.go
Original file line number Diff line number Diff line change
@@ -254,6 +254,13 @@ type swaggerCommitList struct {
Body []api.Commit `json:"body"`
}

// Note
// swagger:response Note
type swaggerNote struct {
// in: body
Body api.Note `json:"body"`
}

// EmptyRepository
// swagger:response EmptyRepository
type swaggerEmptyRepository struct {
66 changes: 66 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
@@ -3569,6 +3569,52 @@
}
}
},
"/repos/{owner}/{repo}/git/notes/{sha}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get a note corresponding to a single commit from a repository",
"operationId": "repoGetNote",
"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": "a git ref or commit sha",
"name": "sha",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/Note"
},
"404": {
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
},
"/repos/{owner}/{repo}/git/refs": {
"get": {
"produces": [
@@ -15453,6 +15499,20 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Note": {
"description": "Note contains information related to a git note",
"type": "object",
"properties": {
"commit": {
"$ref": "#/definitions/Commit"
},
"message": {
"type": "string",
"x-go-name": "Message"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"NotificationCount": {
"description": "NotificationCount number of unread notifications",
"type": "object",
@@ -17412,6 +17472,12 @@
}
}
},
"Note": {
"description": "Note",
"schema": {
"$ref": "#/definitions/Note"
}
},
"NotificationCount": {
"description": "Number of unread notifications",
"schema": {