From 83fa7a55ddee7c994275ffddd44ed23b36292f6a Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Tue, 17 Oct 2017 15:40:41 -0500 Subject: [PATCH 01/10] WIP --- routers/api/v1/api.go | 3 +++ routers/api/v1/repo/repo.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c5f01d91d8c50..af1234c931921 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -444,6 +444,9 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/:username/:reponame", func() { m.Combo("").Get(repo.Get).Delete(reqToken(), repo.Delete) + // TODO: Expand this to make the files available as an endpoint; note that + // GitHub uses `/repos/:owner/:repo/git/trees/:sha` as documented at + // https://developer.github.com/v3/git/trees/ m.Group("/hooks", func() { m.Combo("").Get(repo.ListHooks). Post(bind(api.CreateHookOption{}), repo.CreateHook) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index bf6346eebdfca..4d335993f7468 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -602,3 +602,7 @@ func TopicSearch(ctx *context.Context) { "topics": topics, }) } + +// TODO: Since this is where the repo details are, it would seem to be the right place to add +// listing of files -- see https://developer.github.com/v3/git/trees/ for what the GitHub API +// looks like From c01391fe72a48baffba230c87228a3d59c4e3b6d Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Wed, 18 Oct 2017 22:42:47 -0500 Subject: [PATCH 02/10] Initial stab at repo listing --- routers/api/v1/repo/repo.go | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 4d335993f7468..6262ab232c94b 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -9,8 +9,12 @@ import ( "net/http" "strings" + api "code.gitea.io/sdk/gitea" + "code.gitea.io/git" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -606,3 +610,80 @@ func TopicSearch(ctx *context.Context) { // TODO: Since this is where the repo details are, it would seem to be the right place to add // listing of files -- see https://developer.github.com/v3/git/trees/ for what the GitHub API // looks like +// List contents of one repository +func ListContentsAtSHA(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/git/trees/{sha} repository repoListAtSHA + // + // Produces: + // - application/json + // + // Responses: + // 200: RepoFiles + // 403: forbidden + // 500: error + + log.Warn(" -- loaded with context: %s, %s, %s, %s, %s", + ctx, ctx.Repo, ctx.Repo.Repository, + ctx.Repo.GitRepo, ctx.Params("sha")) + + tree, err := ctx.Repo.GitRepo.GetTree(ctx.Repo.Commit.ID.String()) + if err != nil { + ctx.NotFoundOrServerError("Repo.Repository.RepoPath", git.IsErrNotExist, err) + return + } + + entries, err := tree.ListEntries() + if err != nil { + ctx.Handle(500, "ListEntries", err) + return + } + entries.CustomSort(base.NaturalSortLess) + + // entriesInfo, err := entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath) + // if err != nil { + // ctx.Handle(500, "GetCommitsInfo", err) + // return + // } + + var names []string + for i := range entries { + names = append(names, entries[i].Name()) + } + ctx.JSON(200, names) + + //TODO: Looks lke we'll need a custom type RepoListing or something to match the GitHub + // sample output, and some subtype of TreeEntry for each entry in the tree. We can then + // populate it from the ListeEntries(), with some sort of solution for the `recusrvie=1` case + + // TODO: Make output match the GitHub sample output: + // { + // "sha": "9fb037999f264ba9a7fc6274d15fa3ae2ab98312", + // "url": "https://api.github.com/repos/octocat/Hello-World/trees/9fb037999f264ba9a7fc6274d15fa3ae2ab98312", + // "tree": [ + // { + // "path": "file.rb", + // "mode": "100644", + // "type": "blob", + // "size": 30, + // "sha": "44b4fc6d56897b048c772eb4087f854f46256132", + // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/44b4fc6d56897b048c772eb4087f854f46256132" + // }, + // { + // "path": "subdir", + // "mode": "040000", + // "type": "tree", + // "sha": "f484d249c660418515fb01c2b9662073663c242e", + // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/f484d249c660418515fb01c2b9662073663c242e" + // }, + // { + // "path": "exec_file", + // "mode": "100755", + // "type": "blob", + // "size": 75, + // "sha": "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/45b983be36b73c0788dc9cbcb76cbb80fc7bb057" + // } + // ], + // "truncated": false + // } +} From 3ac0edb75232af0979c101a484bd7dd3d5c55d8d Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Wed, 18 Oct 2017 22:43:03 -0500 Subject: [PATCH 03/10] Add repo listing path --- routers/api/v1/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index af1234c931921..80ecbcae56a43 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -576,6 +576,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/statuses", repo.GetCommitStatusesByRef) }) + m.Get("/git/tree/:sha", context.RepoRef(), repo.ListContentsAtSHA) }, repoAssignment()) }) From 21cefc88e409bfdfbc650f26af5559193cdacfb9 Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Wed, 18 Oct 2017 22:43:21 -0500 Subject: [PATCH 04/10] Baby integration tests --- integrations/api_repo_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index da748942f6435..300225158a887 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -313,3 +313,16 @@ func TestAPIOrgRepoCreate(t *testing.T) { session.MakeRequest(t, req, testCase.expectedStatus) } } + +func TestAPIListRepoEntries(t *testing.T) { + prepareTestEnv(t) + + // TODO: Make this actually work!!! + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/tree/thing/thing") + resp := MakeRequest(t, req, http.StatusOK) + + var repo api.Repository + DecodeJSON(t, resp, &repo) + assert.EqualValues(t, 1, repo.ID) + assert.EqualValues(t, "repo1", repo.Name) +} From ea55e323b74d06033fcd7aa9eea48123acba8a77 Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Thu, 19 Oct 2017 12:15:04 -0500 Subject: [PATCH 05/10] More consistent routing --- routers/api/v1/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 80ecbcae56a43..a86cc0011888a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -576,7 +576,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/statuses", repo.GetCommitStatusesByRef) }) - m.Get("/git/tree/:sha", context.RepoRef(), repo.ListContentsAtSHA) + m.Get("/git/trees/*", context.RepoRef(), repo.ListContentsAtSHA) }, repoAssignment()) }) From 8532318ad05092108e9acbb22da232bcbac5a297 Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Thu, 19 Oct 2017 12:15:20 -0500 Subject: [PATCH 06/10] First cut at organized functions and types --- routers/api/v1/repo/repo.go | 112 +++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 45 deletions(-) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 6262ab232c94b..6180972f7b942 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "strings" + "path/filepath" api "code.gitea.io/sdk/gitea" "code.gitea.io/git" @@ -618,43 +619,18 @@ func ListContentsAtSHA(ctx *context.APIContext) { // - application/json // // Responses: - // 200: RepoFiles + // 200: RepoTreeListing // 403: forbidden // 500: error - log.Warn(" -- loaded with context: %s, %s, %s, %s, %s", - ctx, ctx.Repo, ctx.Repo.Repository, - ctx.Repo.GitRepo, ctx.Params("sha")) - - tree, err := ctx.Repo.GitRepo.GetTree(ctx.Repo.Commit.ID.String()) - if err != nil { - ctx.NotFoundOrServerError("Repo.Repository.RepoPath", git.IsErrNotExist, err) - return - } - - entries, err := tree.ListEntries() + rawLink := filepath.Join(ctx.Repo.RepoLink, "/raw/", ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) + listing, err := repoTreeListing(ctx.Repo.GitRepo, ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath, rawLink) if err != nil { - ctx.Handle(500, "ListEntries", err) + ctx.Handle(500, "RepoTreeListing", err) return } - entries.CustomSort(base.NaturalSortLess) - - // entriesInfo, err := entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath) - // if err != nil { - // ctx.Handle(500, "GetCommitsInfo", err) - // return - // } - - var names []string - for i := range entries { - names = append(names, entries[i].Name()) - } - ctx.JSON(200, names) - - //TODO: Looks lke we'll need a custom type RepoListing or something to match the GitHub - // sample output, and some subtype of TreeEntry for each entry in the tree. We can then - // populate it from the ListeEntries(), with some sort of solution for the `recusrvie=1` case + ctx.JSON(200, listing) // TODO: Make output match the GitHub sample output: // { // "sha": "9fb037999f264ba9a7fc6274d15fa3ae2ab98312", @@ -668,22 +644,68 @@ func ListContentsAtSHA(ctx *context.APIContext) { // "sha": "44b4fc6d56897b048c772eb4087f854f46256132", // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/44b4fc6d56897b048c772eb4087f854f46256132" // }, - // { - // "path": "subdir", - // "mode": "040000", - // "type": "tree", - // "sha": "f484d249c660418515fb01c2b9662073663c242e", - // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/f484d249c660418515fb01c2b9662073663c242e" - // }, - // { - // "path": "exec_file", - // "mode": "100755", - // "type": "blob", - // "size": 75, - // "sha": "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - // "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/45b983be36b73c0788dc9cbcb76cbb80fc7bb057" - // } // ], // "truncated": false // } } + +// RepoFile represents a file blob contained in the repository +type RepoFile struct { + Path string `json:"path"` + Mode git.EntryMode `json:"mode"` + Type git.ObjectType `json:"type"` + Size int64 `json:"size"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// RepoTreeListing represents a tree (or subtree) listing in the repository +type RepoTreeListing struct { + SHA string `json:"sha"` + Path string `json:"path"` + Tree []*RepoFile `json:"tree"` +} + +func repoFile(e *git.TreeEntry, rawLink string) *RepoFile { + filePath := filepath.Join(e.GetSubJumpablePathName(), e.Name()) + return &RepoFile{ + Path: filePath, + // Mode: e.mode, // TODO: Not exported by `git.TreeEntry` + Type: e.Type, + // Size: e.Size(), // TODO: Expensive! + SHA: e.ID.String(), + URL: filepath.Join(rawLink, filePath), + } +} + +func repoTreeListing(r *git.Repository, commit, treePath, rawLink string, recursive bool) (*RepoTreeListing, error) { + tree, err := r.GetTree(commit) + if err != nil { + return nil, err + } + tree, err = tree.SubTree(treePath) + if err != nil { + return nil, err + } + + var entries []*RepoFile + treeEntries, err := tree.ListEntries() + if err != nil { + return nil, err + } + treeEntries.CustomSort(base.NaturalSortLess) + for i := range treeEntries { + entry := treeEntries[i] + if entry.IsDir() && recursive { + // TODO: + } else { + entries = append(entries, repoFile(treeEntries[i], rawLink)) + } + } + + return &RepoTreeListing{ + SHA: tree.ID.String(), + Path: treePath, + Tree: entries, + }, nil +} From 33c8939228f858b6780522116216c2aa65c6ba34 Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Thu, 19 Oct 2017 15:00:06 -0500 Subject: [PATCH 07/10] Add stub tests, support recursion --- integrations/api_repo_test.go | 13 ------ integrations/api_tree_test.go | 81 +++++++++++++++++++++++++++++++++++ routers/api/v1/repo/repo.go | 41 ++++++++++++------ routers/repo/repo_test.go | 10 +++++ 4 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 integrations/api_tree_test.go create mode 100644 routers/repo/repo_test.go diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 300225158a887..da748942f6435 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -313,16 +313,3 @@ func TestAPIOrgRepoCreate(t *testing.T) { session.MakeRequest(t, req, testCase.expectedStatus) } } - -func TestAPIListRepoEntries(t *testing.T) { - prepareTestEnv(t) - - // TODO: Make this actually work!!! - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/tree/thing/thing") - resp := MakeRequest(t, req, http.StatusOK) - - var repo api.Repository - DecodeJSON(t, resp, &repo) - assert.EqualValues(t, 1, repo.ID) - assert.EqualValues(t, "repo1", repo.Name) -} diff --git a/integrations/api_tree_test.go b/integrations/api_tree_test.go new file mode 100644 index 0000000000000..c3c5a905bfebf --- /dev/null +++ b/integrations/api_tree_test.go @@ -0,0 +1,81 @@ +// Copyright 2017 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 ( + "bytes" + "fmt" + "net/http" + "testing" + + api "code.gitea.io/sdk/gitea" + + "github.com/stretchr/testify/assert" +) + +func testAPIGetTree(t *testing.T, treePath string, exists bool, entries []*api.TreeEntry) { + prepareTestEnv(t) + + session := loginUser(t, "user2") + req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/git/trees/%s", treePath) + resp := session.MakeRequest(t, req, NoExpectedStatus) + if !exists { + assert.EqualValues(t, http.StatusNotFound, resp.HeaderCode) + return + } + assert.EqualValues(t, http.StatusOK, resp.HeaderCode) + fmt.Print(bytes.NewBuffer(resp.Body)) + var trees []*api.TreeEntry + DecodeJSON(t, resp, &trees) + + if assert.EqualValues(t, len(entries), len(trees)) { + for i, tree := range trees { + assert.EqualValues(t, entries[i], tree) + } + } +} + +func TestAPIGetTree(t *testing.T) { + for _, test := range []struct { + TreePath string + Exists bool + Listing *api.RepoTreeListing + // Entries []*api.TreeEntry + }{ + {"master", true, []*api.TreeEntry{ + &api.TreeEntry{ + Name: "README.md", + ID: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + Type: "blob", + // Size: 30, + }, + }}, + {"master/doesnotexist", false, []*api.TreeEntry{}}, + {"feature/1", true, []*api.TreeEntry{ + &api.TreeEntry{ + Name: "README.md", + ID: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + Type: "blob", + // Size: 30, + }, + }}, + {"feature/1/doesnotexist", false, []*api.TreeEntry{}}, + } { + testAPIGetTree(t, test.TreePath, test.Exists, test.Entries) + } +} + +// func TestAPIListRepoEntries(t *testing.T) { +// prepareTestEnv(t) + +// // TODO: Make this actually work!!! +// req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/tree/thing/thing") +// resp := MakeRequest(t, req, http.StatusOK) + +// var repo api.Repository +// DecodeJSON(t, resp, &repo) +// assert.EqualValues(t, 1, repo.ID) +// assert.EqualValues(t, "repo1", repo.Name) +// } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 6180972f7b942..f629d6e626a9f 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -624,7 +624,17 @@ func ListContentsAtSHA(ctx *context.APIContext) { // 500: error rawLink := filepath.Join(ctx.Repo.RepoLink, "/raw/", ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) - listing, err := repoTreeListing(ctx.Repo.GitRepo, ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath, rawLink) + tree, err := ctx.Repo.GitRepo.GetTree(ctx.Repo.Commit.ID.String()) + if err != nil { + if models.IsErrRepoNotExist(err) { + ctx.Status(404) + } else { + ctx.Handle(500, "GetRepo", err) + } + return + } + + listing, err := treeListing(tree, ctx.Repo.TreePath, rawLink, ctx.QueryBool("recursive")) if err != nil { ctx.Handle(500, "RepoTreeListing", err) return @@ -652,9 +662,9 @@ func ListContentsAtSHA(ctx *context.APIContext) { // RepoFile represents a file blob contained in the repository type RepoFile struct { Path string `json:"path"` - Mode git.EntryMode `json:"mode"` + // Mode git.EntryMode `json:"mode"` Type git.ObjectType `json:"type"` - Size int64 `json:"size"` + Size int64 `json:"size"` // TODO: Do we include this? It's expensive... SHA string `json:"sha"` URL string `json:"url"` } @@ -666,8 +676,13 @@ type RepoTreeListing struct { Tree []*RepoFile `json:"tree"` } -func repoFile(e *git.TreeEntry, rawLink string) *RepoFile { - filePath := filepath.Join(e.GetSubJumpablePathName(), e.Name()) +func repoFile(e *git.TreeEntry, parentPath string, rawLink string) *RepoFile { + var filePath string + if parentPath != "" { + filePath = filepath.Join(parentPath, e.Name()) + } else { + filePath = e.Name() + } return &RepoFile{ Path: filePath, // Mode: e.mode, // TODO: Not exported by `git.TreeEntry` @@ -678,12 +693,8 @@ func repoFile(e *git.TreeEntry, rawLink string) *RepoFile { } } -func repoTreeListing(r *git.Repository, commit, treePath, rawLink string, recursive bool) (*RepoTreeListing, error) { - tree, err := r.GetTree(commit) - if err != nil { - return nil, err - } - tree, err = tree.SubTree(treePath) +func treeListing(t *git.Tree, treePath, rawLink string, recursive bool) (*RepoTreeListing, error) { + tree, err := t.SubTree(treePath) if err != nil { return nil, err } @@ -697,9 +708,13 @@ func repoTreeListing(r *git.Repository, commit, treePath, rawLink string, recurs for i := range treeEntries { entry := treeEntries[i] if entry.IsDir() && recursive { - // TODO: + subListing, err := treeListing(t, filepath.Join(treePath, entry.Name()), rawLink, recursive) + if err != nil { + return nil, err + } + entries = append(entries, subListing.Tree...) } else { - entries = append(entries, repoFile(treeEntries[i], rawLink)) + entries = append(entries, repoFile(treeEntries[i], treePath, rawLink)) } } diff --git a/routers/repo/repo_test.go b/routers/repo/repo_test.go new file mode 100644 index 0000000000000..4e1869186684d --- /dev/null +++ b/routers/repo/repo_test.go @@ -0,0 +1,10 @@ +// Copyright, whatnot +package repo + +func TestTreeListing() { + +} + +func TestRepoFile() { + +} From 262d452b83708c9d8377cffd731798f4ee57dc3e Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Fri, 20 Oct 2017 09:01:35 -0500 Subject: [PATCH 08/10] More groping around trying to find where to put tests --- integrations/api_tree_test.go | 12 ++++++++++++ models/repo_tree.go | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 models/repo_tree.go diff --git a/integrations/api_tree_test.go b/integrations/api_tree_test.go index c3c5a905bfebf..af75025414129 100644 --- a/integrations/api_tree_test.go +++ b/integrations/api_tree_test.go @@ -79,3 +79,15 @@ func TestAPIGetTree(t *testing.T) { // assert.EqualValues(t, 1, repo.ID) // assert.EqualValues(t, "repo1", repo.Name) // } + +// func TestVersion(t *testing.T) { +// prepareTestEnv(t) + +// setting.AppVer = "test-version-1" +// req := NewRequest(t, "GET", "/api/v1/version") +// resp := MakeRequest(t, req, http.StatusOK) + +// var version gitea.ServerVersion +// DecodeJSON(t, resp, &version) +// assert.Equal(t, setting.AppVer, string(version.Version)) +// } diff --git a/models/repo_tree.go b/models/repo_tree.go new file mode 100644 index 0000000000000..b36a5cedfb2c9 --- /dev/null +++ b/models/repo_tree.go @@ -0,0 +1,8 @@ +// TODO: Create TreeListing and RepoFile and other model objects here, and then +// open the necessary issues in the gitea repo to discuss designing the API endpoint + +// With those model objects here, we can write tests for them and for TreeListing? Or maybe +// they should go elsewhere? + +// Then we can write fuller integration tests, with model objects that we're expecting and asserting +// against From 351a3419132719de80b017460d74d0a195dc7e0b Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Mon, 23 Oct 2017 12:48:51 -0400 Subject: [PATCH 09/10] Move data structures into `models` --- models/repo_tree.go | 79 +++++++++++++++++++++++++++++++++++-- routers/api/v1/repo/repo.go | 68 +------------------------------ 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/models/repo_tree.go b/models/repo_tree.go index b36a5cedfb2c9..8e6e690e93731 100644 --- a/models/repo_tree.go +++ b/models/repo_tree.go @@ -1,8 +1,79 @@ -// TODO: Create TreeListing and RepoFile and other model objects here, and then -// open the necessary issues in the gitea repo to discuss designing the API endpoint - -// With those model objects here, we can write tests for them and for TreeListing? Or maybe +// TODO: +// With model objects here, we can write tests for them and for TreeListing? Or maybe // they should go elsewhere? // Then we can write fuller integration tests, with model objects that we're expecting and asserting // against +import ( + "path/filepath" + + "code.gitea.io/git" +) + +// RepoFile represents a file blob contained in the repository +type RepoFile struct { + Path string `json:"path"` + // Mode git.EntryMode `json:"mode"` // TODO: Do we include this? It'll require exporting the mode as public in the `git` module... + Type git.ObjectType `json:"type"` + // Size int64 `json:"size"` // TODO: Do we include this? It's expensive... + SHA string `json:"sha"` + URL string `json:"url"` +} + +// RepoTreeListing represents a tree (or subtree) listing in the repository +type RepoTreeListing struct { + SHA string `json:"sha"` + Path string `json:"path"` + Tree []*RepoFile `json:"tree"` +} + +// NewRepoFile creates a new RepoFile from a Git tree entry and some metadata. +func NewRepoFile(e *git.TreeEntry, parentPath string, rawLink string) *RepoFile { + var filePath string + if parentPath != "" { + filePath = filepath.Join(parentPath, e.Name()) + } else { + filePath = e.Name() + } + return &RepoFile{ + Path: filePath, + // Mode: e.mode, // TODO: Not exported by `git.TreeEntry` + Type: e.Type, + // Size: e.Size(), // TODO: Expensive! + SHA: e.ID.String(), + URL: filepath.Join(rawLink, filePath), + } +} + +// NewRepoTreeListing creates a new RepoTreeListing from a Git tree and some metadata +func NewRepoTreeListing(t *git.Tree, treePath, rawLink string, recursive bool) (*RepoTreeListing, error) { + tree, err := t.SubTree(treePath) + if err != nil { + return nil, err + } + + var entries []*RepoFile + treeEntries, err := tree.ListEntries() + if err != nil { + return nil, err + } + treeEntries.CustomSort(base.NaturalSortLess) + for i := range treeEntries { + entry := treeEntries[i] + if entry.IsDir() && recursive { + subListing, err := treeListing(t, filepath.Join(treePath, entry.Name()), rawLink, recursive) + if err != nil { + return nil, err + } + entries = append(entries, subListing.Tree...) + } else { + entries = append(entries, models.NewRepoFile(treeEntries[i], treePath, rawLink)) + } + } + + return &RepoTreeListing{ + SHA: tree.ID.String(), + Path: treePath, + Tree: entries, + }, nil +} diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index f629d6e626a9f..8a07d41430fd7 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -634,7 +634,7 @@ func ListContentsAtSHA(ctx *context.APIContext) { return } - listing, err := treeListing(tree, ctx.Repo.TreePath, rawLink, ctx.QueryBool("recursive")) + listing, err := models.NewRepoTreeListing(tree, ctx.Repo.TreePath, rawLink, ctx.QueryBool("recursive")) if err != nil { ctx.Handle(500, "RepoTreeListing", err) return @@ -658,69 +658,3 @@ func ListContentsAtSHA(ctx *context.APIContext) { // "truncated": false // } } - -// RepoFile represents a file blob contained in the repository -type RepoFile struct { - Path string `json:"path"` - // Mode git.EntryMode `json:"mode"` - Type git.ObjectType `json:"type"` - Size int64 `json:"size"` // TODO: Do we include this? It's expensive... - SHA string `json:"sha"` - URL string `json:"url"` -} - -// RepoTreeListing represents a tree (or subtree) listing in the repository -type RepoTreeListing struct { - SHA string `json:"sha"` - Path string `json:"path"` - Tree []*RepoFile `json:"tree"` -} - -func repoFile(e *git.TreeEntry, parentPath string, rawLink string) *RepoFile { - var filePath string - if parentPath != "" { - filePath = filepath.Join(parentPath, e.Name()) - } else { - filePath = e.Name() - } - return &RepoFile{ - Path: filePath, - // Mode: e.mode, // TODO: Not exported by `git.TreeEntry` - Type: e.Type, - // Size: e.Size(), // TODO: Expensive! - SHA: e.ID.String(), - URL: filepath.Join(rawLink, filePath), - } -} - -func treeListing(t *git.Tree, treePath, rawLink string, recursive bool) (*RepoTreeListing, error) { - tree, err := t.SubTree(treePath) - if err != nil { - return nil, err - } - - var entries []*RepoFile - treeEntries, err := tree.ListEntries() - if err != nil { - return nil, err - } - treeEntries.CustomSort(base.NaturalSortLess) - for i := range treeEntries { - entry := treeEntries[i] - if entry.IsDir() && recursive { - subListing, err := treeListing(t, filepath.Join(treePath, entry.Name()), rawLink, recursive) - if err != nil { - return nil, err - } - entries = append(entries, subListing.Tree...) - } else { - entries = append(entries, repoFile(treeEntries[i], treePath, rawLink)) - } - } - - return &RepoTreeListing{ - SHA: tree.ID.String(), - Path: treePath, - Tree: entries, - }, nil -} From 4ebbfd8a8a6eb4169d411137feb756749377444c Mon Sep 17 00:00:00 2001 From: Rami Chowdhury Date: Sun, 25 Nov 2018 00:56:55 -0500 Subject: [PATCH 10/10] Remove redundant TODO comment --- routers/api/v1/repo/repo.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 8a07d41430fd7..ae141a5445603 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -608,9 +608,6 @@ func TopicSearch(ctx *context.Context) { }) } -// TODO: Since this is where the repo details are, it would seem to be the right place to add -// listing of files -- see https://developer.github.com/v3/git/trees/ for what the GitHub API -// looks like // List contents of one repository func ListContentsAtSHA(ctx *context.APIContext) { // swagger:route GET /repos/{username}/{reponame}/git/trees/{sha} repository repoListAtSHA