mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +01:00 
			
		
		
		
	
							
								
								
									
										39
									
								
								integrations/api_repo_git_notes_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								integrations/api_repo_git_notes_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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.
										
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 3fa2f829675543ecfc16b2891aebe8bf0608a8f4 | ||||||
| @@ -10,19 +10,24 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
|  |  | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/object" | 	"github.com/go-git/go-git/v5/plumbing/object" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GetNote retrieves the git-notes data for a given commit. | // GetNote retrieves the git-notes data for a given commit. | ||||||
| func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { | 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) | 	notes, err := repo.GetCommit(NotesRef) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	remainingCommitID := commitID | 	remainingCommitID := commitID | ||||||
| 	path := "" | 	path := "" | ||||||
| 	currentTree := notes.Tree.gogitTree | 	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 | 	var file *object.File | ||||||
| 	for len(remainingCommitID) > 2 { | 	for len(remainingCommitID) > 2 { | ||||||
| 		file, err = currentTree.File(remainingCommitID) | 		file, err = currentTree.File(remainingCommitID) | ||||||
| @@ -39,6 +44,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) | |||||||
| 			if err == object.ErrDirectoryNotFound { | 			if err == object.ErrDirectoryNotFound { | ||||||
| 				return ErrNotExist{ID: remainingCommitID, RelPath: path} | 				return ErrNotExist{ID: remainingCommitID, RelPath: path} | ||||||
| 			} | 			} | ||||||
|  | 			log.Error("Unable to find git note corresponding to the commit %q. Error: %v", commitID, err) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -46,12 +52,14 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) | |||||||
| 	blob := file.Blob | 	blob := file.Blob | ||||||
| 	dataRc, err := blob.Reader() | 	dataRc, err := blob.Reader() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	defer dataRc.Close() | 	defer dataRc.Close() | ||||||
| 	d, err := ioutil.ReadAll(dataRc) | 	d, err := ioutil.ReadAll(dataRc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	note.Message = d | 	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}) | 	lastCommits, err := GetLastCommitForPaths(ctx, commitNode, "", []string{path}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to get the commit for the path %q. Error: %v", path, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	note.Commit = convertCommit(lastCommits[path]) | 	note.Commit = convertCommit(lastCommits[path]) | ||||||
|   | |||||||
| @@ -10,20 +10,26 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GetNote retrieves the git-notes data for a given commit. | // GetNote retrieves the git-notes data for a given commit. | ||||||
| func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error { | 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) | 	notes, err := repo.GetCommit(NotesRef) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to get commit from ref %q. Error: %v", NotesRef, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	path := "" | 	path := "" | ||||||
|  |  | ||||||
| 	tree := ¬es.Tree | 	tree := ¬es.Tree | ||||||
|  | 	log.Trace("Found tree with ID %q while searching for git note corresponding to the commit %q", tree.ID, commitID) | ||||||
|  |  | ||||||
| 	var entry *TreeEntry | 	var entry *TreeEntry | ||||||
|  | 	originalCommitID := commitID | ||||||
| 	for len(commitID) > 2 { | 	for len(commitID) > 2 { | ||||||
| 		entry, err = tree.GetTreeEntryByPath(commitID) | 		entry, err = tree.GetTreeEntryByPath(commitID) | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| @@ -36,12 +42,15 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) | |||||||
| 			commitID = commitID[2:] | 			commitID = commitID[2:] | ||||||
| 		} | 		} | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | 			log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err) | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dataRc, err := entry.Blob().DataAsync() | 	blob := entry.Blob() | ||||||
|  | 	dataRc, err := blob.DataAsync() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	closed := false | 	closed := false | ||||||
| @@ -52,6 +61,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) | |||||||
| 	}() | 	}() | ||||||
| 	d, err := ioutil.ReadAll(dataRc) | 	d, err := ioutil.ReadAll(dataRc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to read blob with ID %q. Error: %v", blob.ID, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	_ = dataRc.Close() | 	_ = 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}) | 	lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	note.Commit = lastCommits[path] | 	note.Commit = lastCommits[path] | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								modules/structs/repo_note.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								modules/structs/repo_note.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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"` | ||||||
|  | } | ||||||
| @@ -953,6 +953,7 @@ func Routes() *web.Route { | |||||||
| 					m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree) | 					m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree) | ||||||
| 					m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob) | 					m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob) | ||||||
| 					m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag) | 					m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetAnnotatedTag) | ||||||
|  | 					m.Get("/notes/{sha}", repo.GetNote) | ||||||
| 				}, reqRepoReader(models.UnitTypeCode)) | 				}, reqRepoReader(models.UnitTypeCode)) | ||||||
| 				m.Group("/contents", func() { | 				m.Group("/contents", func() { | ||||||
| 					m.Get("", repo.GetContentsList) | 					m.Get("", repo.GetContentsList) | ||||||
|   | |||||||
							
								
								
									
										82
									
								
								routers/api/v1/repo/notes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								routers/api/v1/repo/notes.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, ¬e) | ||||||
|  | 	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) | ||||||
|  | } | ||||||
| @@ -254,6 +254,13 @@ type swaggerCommitList struct { | |||||||
| 	Body []api.Commit `json:"body"` | 	Body []api.Commit `json:"body"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Note | ||||||
|  | // swagger:response Note | ||||||
|  | type swaggerNote struct { | ||||||
|  | 	// in: body | ||||||
|  | 	Body api.Note `json:"body"` | ||||||
|  | } | ||||||
|  |  | ||||||
| // EmptyRepository | // EmptyRepository | ||||||
| // swagger:response EmptyRepository | // swagger:response EmptyRepository | ||||||
| type swaggerEmptyRepository struct { | type swaggerEmptyRepository struct { | ||||||
|   | |||||||
| @@ -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": { |     "/repos/{owner}/{repo}/git/refs": { | ||||||
|       "get": { |       "get": { | ||||||
|         "produces": [ |         "produces": [ | ||||||
| @@ -15453,6 +15499,20 @@ | |||||||
|       }, |       }, | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "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": { |     "NotificationCount": { | ||||||
|       "description": "NotificationCount number of unread notifications", |       "description": "NotificationCount number of unread notifications", | ||||||
|       "type": "object", |       "type": "object", | ||||||
| @@ -17412,6 +17472,12 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "Note": { | ||||||
|  |       "description": "Note", | ||||||
|  |       "schema": { | ||||||
|  |         "$ref": "#/definitions/Note" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "NotificationCount": { |     "NotificationCount": { | ||||||
|       "description": "Number of unread notifications", |       "description": "Number of unread notifications", | ||||||
|       "schema": { |       "schema": { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user