diff --git a/internal/route/api/v1/api.go b/internal/route/api/v1/api.go index 6f5ebdb55..afc50191b 100644 --- a/internal/route/api/v1/api.go +++ b/internal/route/api/v1/api.go @@ -271,6 +271,7 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqRepoAdmin()) m.Get("/raw/*", context.RepoRef(), repo2.GetRawFile) + m.Get("/contents/*", context.RepoRef(), repo2.GetContents) m.Get("/archive/*", repo2.GetArchive) m.Group("/git/trees", func() { m.Get("/:sha", context.RepoRef(), repo2.GetRepoGitTree) diff --git a/internal/route/api/v1/repo/contents.go b/internal/route/api/v1/repo/contents.go new file mode 100644 index 000000000..7f7ec18e4 --- /dev/null +++ b/internal/route/api/v1/repo/contents.go @@ -0,0 +1,203 @@ +// Copyright 2020 The Gogs 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 ( + "encoding/base64" + "fmt" + "io/ioutil" + + "github.com/gogs/git-module" + + "gogs.io/gogs/internal/context" +) + +type repoContent struct { + Type string `json:"type"` + Target string `json:"target,omitempty"` + SubmoduleGitURL string `json:"submodule_git_url,omitempty"` + Encoding string `json:"encoding,omitempty"` + Size int64 `json:"size"` + Name string `json:"name"` + Path string `json:"path"` + Content string `json:"content,omitempty"` + Sha string `json:"sha"` + URL string `json:"url"` + GitURL string `json:"git_url"` + HTMLURL string `json:"html_url"` + DownloadURL string `json:"download_url"` + Links Links `json:"_links"` +} + +type Links struct { + Git string `json:"git"` + Self string `json:"self"` + HTML string `json:"html"` +} + +func GetContents(c *context.APIContext) { + treeEntry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath) + if err != nil { + c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err) + return + } + username := c.Params(":username") + reponame := c.Params(":reponame") + + // TODO: figure out the best way to do this + // :base-url/:username/:project/raw/:refs/:path + templateDownloadURL := "%s/%s/%s/raw/%s" + // :base-url/repos/:username/:project/contents/:path + templateSelfLink := "%s/repos/%s/%s/contents/%s" + // :baseurl/repos/:username/:project/git/trees/:sha + templateGitURLLink := "%s/repos/%s/%s/trees/%s" + // :baseurl/repos/:username/:project/tree/:sha + templateHTMLLLink := "%s/repos/%s/%s/tree/%s" + + gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, treeEntry.ID.String()) + htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, treeEntry.ID.String()) + selfURL := fmt.Sprintf(templateSelfLink, c.BaseURL, username, reponame, c.Repo.TreePath) + + // TODO(unknwon): Make a treeEntryToRepoContent helper. + contents := &repoContent{ + Size: treeEntry.Size(), + Name: treeEntry.Name(), + Path: c.Repo.TreePath, + Sha: treeEntry.ID.String(), + URL: selfURL, + GitURL: gitURL, + HTMLURL: htmlURL, + DownloadURL: fmt.Sprintf(templateDownloadURL, c.BaseURL, username, reponame, c.Repo.TreePath), + Links: Links{ + Git: gitURL, + Self: selfURL, + HTML: htmlURL, + }, + } + + // A tree entry can only be one of the following types: + // 1. Tree (directory) + // 2. SubModule + // 3. SymLink + // 4. Blob (file) + if treeEntry.IsSubModule() { + // TODO(unknwon): submoduleURL is not set as current git-module doesn't handle it properly + contents.Type = "submodule" + c.JSONSuccess(contents) + return + + } else if treeEntry.IsLink() { + contents.Type = "symlink" + blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) + if err != nil { + c.ServerError("GetBlobByPath", err) + return + } + b, err := blob.Data() + if err != nil { + c.ServerError("Data", err) + return + } + buf, err := ioutil.ReadAll(b) + if err != nil { + c.ServerError("ReadAll", err) + return + } + contents.Target = string(buf) + c.JSONSuccess(contents) + return + + } else if treeEntry.Type == "blob" { + blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) + if err != nil { + c.ServerError("GetBlobByPath", err) + return + } + b, err := blob.Data() + if err != nil { + c.ServerError("Data", err) + return + } + buf, err := ioutil.ReadAll(b) + if err != nil { + c.ServerError("ReadAll", err) + return + } + contents.Content = base64.StdEncoding.EncodeToString(buf) + contents.Type = "file" + c.JSONSuccess(contents) + return + } + + // treeEntry is a directory + dirTree, err := c.Repo.GitRepo.GetTree(treeEntry.ID.String()) + if err != nil { + c.NotFoundOrServerError("GetTree", git.IsErrNotExist, err) + return + } + + entries, err := dirTree.ListEntries() + if err != nil { + c.NotFoundOrServerError("ListEntries", git.IsErrNotExist, err) + return + } + + if len(entries) == 0 { + c.JSONSuccess([]string{}) + return + } + + var results = make([]*repoContent, 0, len(entries)) + for _, entry := range entries { + gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, entry.ID.String()) + htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, entry.ID.String()) + selfURL := fmt.Sprintf(templateSelfLink, c.BaseURL, username, reponame, c.Repo.TreePath) + var contentType string + if entry.IsDir() { + contentType = "dir" + } else if entry.IsSubModule() { + // TODO(unknwon): submoduleURL is not set as current git-module doesn't handle it properly + contentType = "submodule" + } else if entry.IsLink() { + contentType = "symlink" + blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath) + if err != nil { + c.ServerError("GetBlobByPath", err) + return + } + b, err := blob.Data() + if err != nil { + c.ServerError("Data", err) + return + } + buf, err := ioutil.ReadAll(b) + if err != nil { + c.ServerError("ReadAll", err) + return + } + contents.Target = string(buf) + } else { + contentType = "file" + } + + results = append(results, &repoContent{ + Type: contentType, + Size: entry.Size(), + Name: entry.Name(), + Path: c.Repo.TreePath, + Sha: entry.ID.String(), + URL: selfURL, + GitURL: gitURL, + HTMLURL: htmlURL, + DownloadURL: fmt.Sprintf(templateDownloadURL, c.BaseURL, username, reponame, c.Repo.TreePath), + Links: Links{ + Git: gitURL, + Self: selfURL, + HTML: htmlURL, + }, + }) + } + c.JSONSuccess(results) +}