summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gopkg.lock4
-rw-r--r--integrations/api_repo_git_commits_test.go32
-rw-r--r--routers/api/v1/api.go3
-rw-r--r--routers/api/v1/repo/commits.go119
-rw-r--r--routers/api/v1/swagger/repo.go7
-rw-r--r--templates/swagger/v1_json.tmpl142
-rw-r--r--vendor/code.gitea.io/git/commit.go52
-rw-r--r--vendor/code.gitea.io/git/repo_commit.go3
-rw-r--r--vendor/code.gitea.io/git/submodule.go16
-rw-r--r--vendor/code.gitea.io/git/tree.go22
10 files changed, 389 insertions, 11 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 1727b91afa..65cdf7efa3 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -3,11 +3,11 @@
[[projects]]
branch = "master"
- digest = "1:ab875622908a804a327a95a1701002b150806a3c5406df51ec231eac16d3a1ca"
+ digest = "1:8a6c3c311918c0f08fa2899feae2c938a9bf22b51378e3720d63b80aca4e80aa"
name = "code.gitea.io/git"
packages = ["."]
pruneopts = "NUT"
- revision = "389d3c803e12a30dffcbb54a15c2242521bc4333"
+ revision = "d04f81a6f8979be39da165fc034447a805071b97"
[[projects]]
branch = "master"
diff --git a/integrations/api_repo_git_commits_test.go b/integrations/api_repo_git_commits_test.go
new file mode 100644
index 0000000000..587e9de5b2
--- /dev/null
+++ b/integrations/api_repo_git_commits_test.go
@@ -0,0 +1,32 @@
+// 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 integrations
+
+import (
+ "net/http"
+ "testing"
+
+ "code.gitea.io/gitea/models"
+)
+
+func TestAPIReposGitCommits(t *testing.T) {
+ prepareTestEnv(t)
+ user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+ // Login as User2.
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session)
+
+ for _, ref := range [...]string{
+ "commits/master", // Branch
+ "commits/v1.1", // Tag
+ } {
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s?token="+token, user.Name, ref)
+ session.MakeRequest(t, req, http.StatusOK)
+ }
+
+ // Test getting non-existent refs
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/unknown?token="+token, user.Name)
+ session.MakeRequest(t, req, http.StatusNotFound)
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 7e7bf6a50b..55f5c66290 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -617,6 +617,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/statuses", repo.GetCommitStatusesByRef)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/git", func() {
+ m.Group("/commits", func() {
+ m.Get("/:sha", repo.GetSingleCommit)
+ })
m.Get("/refs", repo.GetGitAllRefs)
m.Get("/refs/*", repo.GetGitRefs)
m.Combo("/trees/:sha", context.RepoRef()).Get(repo.GetTree)
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
new file mode 100644
index 0000000000..a4cf5037d7
--- /dev/null
+++ b/routers/api/v1/repo/commits.go
@@ -0,0 +1,119 @@
+// Copyright 2018 The Gogs Authors. All rights reserved.
+// 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 repo
+
+import (
+ "time"
+
+ "code.gitea.io/git"
+ api "code.gitea.io/sdk/gitea"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// GetSingleCommit get a commit via
+func GetSingleCommit(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/git/commits/{sha} repository repoGetSingleCommit
+ // ---
+ // summary: Get 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: the commit hash
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Commit"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
+ if err != nil {
+ ctx.ServerError("OpenRepository", err)
+ return
+ }
+ commit, err := gitRepo.GetCommit(ctx.Params(":sha"))
+ if err != nil {
+ ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
+ return
+ }
+
+ // Retrieve author and committer information
+ var apiAuthor, apiCommitter *api.User
+ author, err := models.GetUserByEmail(commit.Author.Email)
+ if err != nil && !models.IsErrUserNotExist(err) {
+ ctx.ServerError("Get user by author email", err)
+ return
+ } else if err == nil {
+ apiAuthor = author.APIFormat()
+ }
+ // Save one query if the author is also the committer
+ if commit.Committer.Email == commit.Author.Email {
+ apiCommitter = apiAuthor
+ } else {
+ committer, err := models.GetUserByEmail(commit.Committer.Email)
+ if err != nil && !models.IsErrUserNotExist(err) {
+ ctx.ServerError("Get user by committer email", err)
+ return
+ } else if err == nil {
+ apiCommitter = committer.APIFormat()
+ }
+ }
+
+ // Retrieve parent(s) of the commit
+ apiParents := make([]*api.CommitMeta, commit.ParentCount())
+ for i := 0; i < commit.ParentCount(); i++ {
+ sha, _ := commit.ParentID(i)
+ apiParents[i] = &api.CommitMeta{
+ URL: ctx.Repo.Repository.APIURL() + "/git/commits/" + sha.String(),
+ SHA: sha.String(),
+ }
+ }
+
+ ctx.JSON(200, &api.Commit{
+ CommitMeta: &api.CommitMeta{
+ URL: setting.AppURL + ctx.Link[1:],
+ SHA: commit.ID.String(),
+ },
+ HTMLURL: ctx.Repo.Repository.HTMLURL() + "/commits/" + commit.ID.String(),
+ RepoCommit: &api.RepoCommit{
+ URL: setting.AppURL + ctx.Link[1:],
+ Author: &api.CommitUser{
+ Name: commit.Author.Name,
+ Email: commit.Author.Email,
+ Date: commit.Author.When.Format(time.RFC3339),
+ },
+ Committer: &api.CommitUser{
+ Name: commit.Committer.Name,
+ Email: commit.Committer.Email,
+ Date: commit.Committer.When.Format(time.RFC3339),
+ },
+ Message: commit.Summary(),
+ Tree: &api.CommitMeta{
+ URL: ctx.Repo.Repository.APIURL() + "/trees/" + commit.ID.String(),
+ SHA: commit.ID.String(),
+ },
+ },
+ Author: apiAuthor,
+ Committer: apiCommitter,
+ Parents: apiParents,
+ })
+}
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index 0c9f95f962..5b930e295e 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -140,3 +140,10 @@ type swaggerGitTreeResponse struct {
//in: body
Body api.GitTreeResponse `json:"body"`
}
+
+// Commit
+// swagger:response Commit
+type swaggerCommit struct {
+ //in: body
+ Body api.Commit `json:"body"`
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 801bab51f6..0ce6b805f7 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -1622,6 +1622,49 @@
}
}
},
+ "/repos/{owner}/{repo}/git/commits/{sha}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Get a single commit from a repository",
+ "operationId": "repoGetSingleCommit",
+ "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": "the commit hash",
+ "name": "sha",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/Commit"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/git/refs": {
"get": {
"produces": [
@@ -6174,6 +6217,75 @@
},
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
},
+ "Commit": {
+ "type": "object",
+ "title": "Commit contains information generated from a Git commit.",
+ "properties": {
+ "author": {
+ "$ref": "#/definitions/User"
+ },
+ "commit": {
+ "$ref": "#/definitions/RepoCommit"
+ },
+ "committer": {
+ "$ref": "#/definitions/User"
+ },
+ "html_url": {
+ "type": "string",
+ "x-go-name": "HTMLURL"
+ },
+ "parents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/CommitMeta"
+ },
+ "x-go-name": "Parents"
+ },
+ "sha": {
+ "type": "string",
+ "x-go-name": "SHA"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
+ },
+ "CommitMeta": {
+ "type": "object",
+ "title": "CommitMeta contains meta information of a commit in terms of API.",
+ "properties": {
+ "sha": {
+ "type": "string",
+ "x-go-name": "SHA"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
+ },
+ "CommitUser": {
+ "type": "object",
+ "title": "CommitUser contains information of a user in the context of a commit.",
+ "properties": {
+ "date": {
+ "type": "string",
+ "x-go-name": "Date"
+ },
+ "email": {
+ "type": "string",
+ "x-go-name": "Email"
+ },
+ "name": {
+ "type": "string",
+ "x-go-name": "Name"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
+ },
"CreateEmailOption": {
"description": "CreateEmailOption options when creating email addresses",
"type": "object",
@@ -7952,6 +8064,30 @@
},
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
},
+ "RepoCommit": {
+ "type": "object",
+ "title": "RepoCommit contains information of a commit in the context of a repository.",
+ "properties": {
+ "author": {
+ "$ref": "#/definitions/CommitUser"
+ },
+ "committer": {
+ "$ref": "#/definitions/CommitUser"
+ },
+ "message": {
+ "type": "string",
+ "x-go-name": "Message"
+ },
+ "tree": {
+ "$ref": "#/definitions/CommitMeta"
+ },
+ "url": {
+ "type": "string",
+ "x-go-name": "URL"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
+ },
"Repository": {
"description": "Repository represents a repository",
"type": "object",
@@ -8382,6 +8518,12 @@
}
}
},
+ "Commit": {
+ "description": "Commit",
+ "schema": {
+ "$ref": "#/definitions/Commit"
+ }
+ },
"DeployKey": {
"description": "DeployKey",
"schema": {
diff --git a/vendor/code.gitea.io/git/commit.go b/vendor/code.gitea.io/git/commit.go
index 5e8c91d303..227df09b7d 100644
--- a/vendor/code.gitea.io/git/commit.go
+++ b/vendor/code.gitea.io/git/commit.go
@@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
+// Copyright 2018 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.
@@ -9,6 +10,7 @@ import (
"bytes"
"container/list"
"fmt"
+ "io"
"net/http"
"strconv"
"strings"
@@ -279,6 +281,56 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
return nil, nil
}
+// CommitFileStatus represents status of files in a commit.
+type CommitFileStatus struct {
+ Added []string
+ Removed []string
+ Modified []string
+}
+
+// NewCommitFileStatus creates a CommitFileStatus
+func NewCommitFileStatus() *CommitFileStatus {
+ return &CommitFileStatus{
+ []string{}, []string{}, []string{},
+ }
+}
+
+// GetCommitFileStatus returns file status of commit in given repository.
+func GetCommitFileStatus(repoPath, commitID string) (*CommitFileStatus, error) {
+ stdout, w := io.Pipe()
+ done := make(chan struct{})
+ fileStatus := NewCommitFileStatus()
+ go func() {
+ scanner := bufio.NewScanner(stdout)
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) < 2 {
+ continue
+ }
+
+ switch fields[0][0] {
+ case 'A':
+ fileStatus.Added = append(fileStatus.Added, fields[1])
+ case 'D':
+ fileStatus.Removed = append(fileStatus.Removed, fields[1])
+ case 'M':
+ fileStatus.Modified = append(fileStatus.Modified, fields[1])
+ }
+ }
+ done <- struct{}{}
+ }()
+
+ stderr := new(bytes.Buffer)
+ err := NewCommand("show", "--name-status", "--pretty=format:''", commitID).RunInDirPipeline(repoPath, w, stderr)
+ w.Close() // Close writer to exit parsing goroutine
+ if err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+
+ <-done
+ return fileStatus, nil
+}
+
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(repoPath, shortID string) (string, error) {
if len(shortID) >= 40 {
diff --git a/vendor/code.gitea.io/git/repo_commit.go b/vendor/code.gitea.io/git/repo_commit.go
index d5cab8f873..484568585f 100644
--- a/vendor/code.gitea.io/git/repo_commit.go
+++ b/vendor/code.gitea.io/git/repo_commit.go
@@ -140,6 +140,9 @@ func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
var err error
commitID, err = NewCommand("rev-parse", commitID).RunInDir(repo.Path)
if err != nil {
+ if strings.Contains(err.Error(), "unknown revision or path") {
+ return nil, ErrNotExist{commitID, ""}
+ }
return nil, err
}
}
diff --git a/vendor/code.gitea.io/git/submodule.go b/vendor/code.gitea.io/git/submodule.go
index a0fe7b4a56..294df3986a 100644
--- a/vendor/code.gitea.io/git/submodule.go
+++ b/vendor/code.gitea.io/git/submodule.go
@@ -29,13 +29,12 @@ func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
}
}
-// RefURL guesses and returns reference URL.
-func (sf *SubModuleFile) RefURL(urlPrefix string, parentPath string) string {
- if sf.refURL == "" {
+func getRefURL(refURL, urlPrefix, parentPath string) string {
+ if refURL == "" {
return ""
}
- url := strings.TrimSuffix(sf.refURL, ".git")
+ url := strings.TrimSuffix(refURL, ".git")
// git://xxx/user/repo
if strings.HasPrefix(url, "git://") {
@@ -67,12 +66,21 @@ func (sf *SubModuleFile) RefURL(urlPrefix string, parentPath string) string {
if strings.Contains(urlPrefix, url[i+1:j]) {
return urlPrefix + url[j+1:]
}
+ if strings.HasPrefix(url, "ssh://") || strings.HasPrefix(url, "git+ssh://") {
+ k := strings.Index(url[j+1:], "/")
+ return "http://" + url[i+1:j] + "/" + url[j+1:][k+1:]
+ }
return "http://" + url[i+1:j] + "/" + url[j+1:]
}
return url
}
+// RefURL guesses and returns reference URL.
+func (sf *SubModuleFile) RefURL(urlPrefix string, parentPath string) string {
+ return getRefURL(sf.refURL, urlPrefix, parentPath)
+}
+
// RefID returns reference ID.
func (sf *SubModuleFile) RefID() string {
return sf.refID
diff --git a/vendor/code.gitea.io/git/tree.go b/vendor/code.gitea.io/git/tree.go
index b67bf55840..b65fe19409 100644
--- a/vendor/code.gitea.io/git/tree.go
+++ b/vendor/code.gitea.io/git/tree.go
@@ -18,6 +18,9 @@ type Tree struct {
entries Entries
entriesParsed bool
+
+ entriesRecursive Entries
+ entriesRecursiveParsed bool
}
// NewTree create a new tree according the repository and commit id
@@ -67,20 +70,29 @@ func (t *Tree) ListEntries() (Entries, error) {
if err != nil {
return nil, err
}
+
t.entries, err = parseTreeEntries(stdout, t)
+ if err == nil {
+ t.entriesParsed = true
+ }
+
return t.entries, err
}
// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
func (t *Tree) ListEntriesRecursive() (Entries, error) {
- if t.entriesParsed {
- return t.entries, nil
+ if t.entriesRecursiveParsed {
+ return t.entriesRecursive, nil
}
stdout, err := NewCommand("ls-tree", "-t", "-r", t.ID.String()).RunInDirBytes(t.repo.Path)
-
if err != nil {
return nil, err
}
- t.entries, err = parseTreeEntries(stdout, t)
- return t.entries, err
+
+ t.entriesRecursive, err = parseTreeEntries(stdout, t)
+ if err == nil {
+ t.entriesRecursiveParsed = true
+ }
+
+ return t.entriesRecursive, err
}