| @@ -1,117 +0,0 @@ | |||||
| // 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" | |||||
| "net/url" | |||||
| "path/filepath" | |||||
| "testing" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func getExpectedFileContentResponseForFileContents(branch string) *api.FileContentResponse { | |||||
| treePath := "README.md" | |||||
| sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" | |||||
| return &api.FileContentResponse{ | |||||
| Name: filepath.Base(treePath), | |||||
| Path: treePath, | |||||
| SHA: sha, | |||||
| Size: 30, | |||||
| URL: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/" + branch + "/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| DownloadURL: setting.AppURL + "user2/repo1/raw/branch/" + branch + "/" + treePath, | |||||
| Type: "blob", | |||||
| Links: &api.FileLinksResponse{ | |||||
| Self: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/" + branch + "/" + treePath, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func TestAPIGetFileContents(t *testing.T) { | |||||
| onGiteaRun(t, testAPIGetFileContents) | |||||
| } | |||||
| func testAPIGetFileContents(t *testing.T, u *url.URL) { | |||||
| user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 | |||||
| user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org | |||||
| user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos | |||||
| repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo | |||||
| repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo | |||||
| repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | |||||
| treePath := "README.md" | |||||
| // Get user2's token | |||||
| session := loginUser(t, user2.Name) | |||||
| token2 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Get user4's token | |||||
| session = loginUser(t, user4.Name) | |||||
| token4 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Make a second master branch in repo1 | |||||
| repo1.CreateNewBranch(user2, repo1.DefaultBranch, "master2") | |||||
| // ref is default branch | |||||
| branch := repo1.DefaultBranch | |||||
| req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, branch) | |||||
| resp := session.MakeRequest(t, req, http.StatusOK) | |||||
| var fileContentResponse api.FileContentResponse | |||||
| DecodeJSON(t, resp, &fileContentResponse) | |||||
| assert.NotNil(t, fileContentResponse) | |||||
| expectedFileContentResponse := getExpectedFileContentResponseForFileContents(branch) | |||||
| assert.EqualValues(t, *expectedFileContentResponse, fileContentResponse) | |||||
| // No ref | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &fileContentResponse) | |||||
| assert.NotNil(t, fileContentResponse) | |||||
| expectedFileContentResponse = getExpectedFileContentResponseForFileContents(repo1.DefaultBranch) | |||||
| assert.EqualValues(t, *expectedFileContentResponse, fileContentResponse) | |||||
| // ref is master2 | |||||
| branch = "master2" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, branch) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &fileContentResponse) | |||||
| assert.NotNil(t, fileContentResponse) | |||||
| expectedFileContentResponse = getExpectedFileContentResponseForFileContents("master2") | |||||
| assert.EqualValues(t, *expectedFileContentResponse, fileContentResponse) | |||||
| // Test file contents a file with the wrong branch | |||||
| branch = "badbranch" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, branch) | |||||
| resp = session.MakeRequest(t, req, http.StatusInternalServerError) | |||||
| expectedAPIError := context.APIError{ | |||||
| Message: "object does not exist [id: " + branch + ", rel_path: ]", | |||||
| URL: setting.API.SwaggerURL, | |||||
| } | |||||
| var apiError context.APIError | |||||
| DecodeJSON(t, resp, &apiError) | |||||
| assert.Equal(t, expectedAPIError, apiError) | |||||
| // Test accessing private branch with user token that does not have access - should fail | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | |||||
| session.MakeRequest(t, req, http.StatusNotFound) | |||||
| // Test access private branch of owner of token | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md?token=%s", user2.Name, repo16.Name, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| // Test access of org user3 private repo file by owner user2 | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| } | |||||
| @@ -44,21 +44,29 @@ func getCreateFileOptions() api.CreateFileOptions { | |||||
| func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileResponse { | func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileResponse { | ||||
| sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | ||||
| encoding := "base64" | |||||
| content := "VGhpcyBpcyBuZXcgdGV4dA==" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath | |||||
| return &api.FileResponse{ | return &api.FileResponse{ | ||||
| Content: &api.FileContentResponse{ | |||||
| Content: &api.ContentsResponse{ | |||||
| Name: filepath.Base(treePath), | Name: filepath.Base(treePath), | ||||
| Path: treePath, | Path: treePath, | ||||
| SHA: sha, | SHA: sha, | ||||
| Size: 16, | Size: 16, | ||||
| URL: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| DownloadURL: setting.AppURL + "user2/repo1/raw/branch/master/" + treePath, | |||||
| Type: "blob", | |||||
| Type: "file", | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/" + treePath, | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | }, | ||||
| }, | }, | ||||
| Commit: &api.FileCommitResponse{ | Commit: &api.FileCommitResponse{ | ||||
| @@ -145,11 +153,11 @@ func TestAPICreateFile(t *testing.T) { | |||||
| var fileResponse api.FileResponse | var fileResponse api.FileResponse | ||||
| DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
| expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | ||||
| expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/blob/new_branch/new/file%d.txt", fileID) | |||||
| expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) | |||||
| expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) | expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) | ||||
| assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | ||||
| assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | |||||
| assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) | |||||
| assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) | assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) | ||||
| // Test creating a file without a message | // Test creating a file without a message | ||||
| @@ -47,21 +47,29 @@ func getUpdateFileOptions() *api.UpdateFileOptions { | |||||
| func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileResponse { | func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileResponse { | ||||
| sha := "08bd14b2e2852529157324de9c226b3364e76136" | sha := "08bd14b2e2852529157324de9c226b3364e76136" | ||||
| encoding := "base64" | |||||
| content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath | |||||
| return &api.FileResponse{ | return &api.FileResponse{ | ||||
| Content: &api.FileContentResponse{ | |||||
| Content: &api.ContentsResponse{ | |||||
| Name: filepath.Base(treePath), | Name: filepath.Base(treePath), | ||||
| Path: treePath, | Path: treePath, | ||||
| SHA: sha, | SHA: sha, | ||||
| Type: "file", | |||||
| Size: 20, | Size: 20, | ||||
| URL: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| DownloadURL: setting.AppURL + "user2/repo1/raw/branch/master/" + treePath, | |||||
| Type: "blob", | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath, | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha, | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/" + treePath, | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | }, | ||||
| }, | }, | ||||
| Commit: &api.FileCommitResponse{ | Commit: &api.FileCommitResponse{ | ||||
| @@ -150,11 +158,11 @@ func TestAPIUpdateFile(t *testing.T) { | |||||
| var fileResponse api.FileResponse | var fileResponse api.FileResponse | ||||
| DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
| expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" | expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" | ||||
| expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/blob/new_branch/update/file%d.txt", fileID) | |||||
| expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) | |||||
| expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) | expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) | ||||
| assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | ||||
| assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | |||||
| assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) | |||||
| assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) | assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) | ||||
| // Test updating a file and renaming it | // Test updating a file and renaming it | ||||
| @@ -170,11 +178,11 @@ func TestAPIUpdateFile(t *testing.T) { | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | resp = session.MakeRequest(t, req, http.StatusOK) | ||||
| DecodeJSON(t, resp, &fileResponse) | DecodeJSON(t, resp, &fileResponse) | ||||
| expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" | expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" | ||||
| expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/blob/master/rename/update/file%d.txt", fileID) | |||||
| expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) | |||||
| expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) | expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) | ||||
| assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | ||||
| assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | |||||
| assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) | |||||
| assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) | |||||
| // Test updating a file without a message | // Test updating a file without a message | ||||
| updateFileOptions = getUpdateFileOptions() | updateFileOptions = getUpdateFileOptions() | ||||
| @@ -0,0 +1,156 @@ | |||||
| // 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" | |||||
| "net/url" | |||||
| "path/filepath" | |||||
| "testing" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/git" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func getExpectedContentsListResponseForContents(ref, refType string) []*api.ContentsResponse { | |||||
| treePath := "README.md" | |||||
| sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=" + ref | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/" + refType + "/" + ref + "/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath | |||||
| return []*api.ContentsResponse{ | |||||
| { | |||||
| Name: filepath.Base(treePath), | |||||
| Path: treePath, | |||||
| SHA: sha, | |||||
| Type: "file", | |||||
| Size: 30, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func TestAPIGetContentsList(t *testing.T) { | |||||
| onGiteaRun(t, testAPIGetContentsList) | |||||
| } | |||||
| func testAPIGetContentsList(t *testing.T, u *url.URL) { | |||||
| /*** SETUP ***/ | |||||
| user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 | |||||
| user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org | |||||
| user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos | |||||
| repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo | |||||
| repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo | |||||
| repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | |||||
| treePath := "" // root dir | |||||
| // Get user2's token | |||||
| session := loginUser(t, user2.Name) | |||||
| token2 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Get user4's token | |||||
| session = loginUser(t, user4.Name) | |||||
| token4 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Make a new branch in repo1 | |||||
| newBranch := "test_branch" | |||||
| repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) | |||||
| // Get the commit ID of the default branch | |||||
| gitRepo, _ := git.OpenRepository(repo1.RepoPath()) | |||||
| commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) | |||||
| // Make a new tag in repo1 | |||||
| newTag := "test_tag" | |||||
| gitRepo.CreateTag(newTag, commitID) | |||||
| /*** END SETUP ***/ | |||||
| // ref is default ref | |||||
| ref := repo1.DefaultBranch | |||||
| refType := "branch" | |||||
| req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp := session.MakeRequest(t, req, http.StatusOK) | |||||
| var contentsListResponse []*api.ContentsResponse | |||||
| DecodeJSON(t, resp, &contentsListResponse) | |||||
| assert.NotNil(t, contentsListResponse) | |||||
| expectedContentsListResponse := getExpectedContentsListResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) | |||||
| // No ref | |||||
| refType = "branch" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsListResponse) | |||||
| assert.NotNil(t, contentsListResponse) | |||||
| expectedContentsListResponse = getExpectedContentsListResponseForContents(repo1.DefaultBranch, refType) | |||||
| assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) | |||||
| // ref is the branch we created above in setup | |||||
| ref = newBranch | |||||
| refType = "branch" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsListResponse) | |||||
| assert.NotNil(t, contentsListResponse) | |||||
| expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) | |||||
| // ref is the new tag we created above in setup | |||||
| ref = newTag | |||||
| refType = "tag" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsListResponse) | |||||
| assert.NotNil(t, contentsListResponse) | |||||
| expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) | |||||
| // ref is a commit | |||||
| ref = commitID | |||||
| refType = "commit" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsListResponse) | |||||
| assert.NotNil(t, contentsListResponse) | |||||
| expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) | |||||
| // Test file contents a file with a bad ref | |||||
| ref = "badref" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusInternalServerError) | |||||
| expectedAPIError := context.APIError{ | |||||
| Message: "object does not exist [id: " + ref + ", rel_path: ]", | |||||
| URL: setting.API.SwaggerURL, | |||||
| } | |||||
| var apiError context.APIError | |||||
| DecodeJSON(t, resp, &apiError) | |||||
| assert.Equal(t, expectedAPIError, apiError) | |||||
| // Test accessing private ref with user token that does not have access - should fail | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | |||||
| session.MakeRequest(t, req, http.StatusNotFound) | |||||
| // Test access private ref of owner of token | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md?token=%s", user2.Name, repo16.Name, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| // Test access of org user3 private repo file by owner user2 | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| } | |||||
| @@ -0,0 +1,157 @@ | |||||
| // 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" | |||||
| "net/url" | |||||
| "testing" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/git" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "github.com/stretchr/testify/assert" | |||||
| ) | |||||
| func getExpectedContentsResponseForContents(ref, refType string) *api.ContentsResponse { | |||||
| treePath := "README.md" | |||||
| sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" | |||||
| encoding := "base64" | |||||
| content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=" + ref | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/" + refType + "/" + ref + "/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath | |||||
| return &api.ContentsResponse{ | |||||
| Name: treePath, | |||||
| Path: treePath, | |||||
| SHA: sha, | |||||
| Type: "file", | |||||
| Size: 30, | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func TestAPIGetContents(t *testing.T) { | |||||
| onGiteaRun(t, testAPIGetContents) | |||||
| } | |||||
| func testAPIGetContents(t *testing.T, u *url.URL) { | |||||
| /*** SETUP ***/ | |||||
| user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 | |||||
| user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org | |||||
| user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos | |||||
| repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo | |||||
| repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo | |||||
| repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | |||||
| treePath := "README.md" | |||||
| // Get user2's token | |||||
| session := loginUser(t, user2.Name) | |||||
| token2 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Get user4's token | |||||
| session = loginUser(t, user4.Name) | |||||
| token4 := getTokenForLoggedInUser(t, session) | |||||
| session = emptyTestSession(t) | |||||
| // Make a new branch in repo1 | |||||
| newBranch := "test_branch" | |||||
| repo1.CreateNewBranch(user2, repo1.DefaultBranch, newBranch) | |||||
| // Get the commit ID of the default branch | |||||
| gitRepo, _ := git.OpenRepository(repo1.RepoPath()) | |||||
| commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) | |||||
| // Make a new tag in repo1 | |||||
| newTag := "test_tag" | |||||
| gitRepo.CreateTag(newTag, commitID) | |||||
| /*** END SETUP ***/ | |||||
| // ref is default ref | |||||
| ref := repo1.DefaultBranch | |||||
| refType := "branch" | |||||
| req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp := session.MakeRequest(t, req, http.StatusOK) | |||||
| var contentsResponse api.ContentsResponse | |||||
| DecodeJSON(t, resp, &contentsResponse) | |||||
| assert.NotNil(t, contentsResponse) | |||||
| expectedContentsResponse := getExpectedContentsResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, *expectedContentsResponse, contentsResponse) | |||||
| // No ref | |||||
| refType = "branch" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsResponse) | |||||
| assert.NotNil(t, contentsResponse) | |||||
| expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType) | |||||
| assert.EqualValues(t, *expectedContentsResponse, contentsResponse) | |||||
| // ref is the branch we created above in setup | |||||
| ref = newBranch | |||||
| refType = "branch" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsResponse) | |||||
| assert.NotNil(t, contentsResponse) | |||||
| expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, *expectedContentsResponse, contentsResponse) | |||||
| // ref is the new tag we created above in setup | |||||
| ref = newTag | |||||
| refType = "tag" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsResponse) | |||||
| assert.NotNil(t, contentsResponse) | |||||
| expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, *expectedContentsResponse, contentsResponse) | |||||
| // ref is a commit | |||||
| ref = commitID | |||||
| refType = "commit" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusOK) | |||||
| DecodeJSON(t, resp, &contentsResponse) | |||||
| assert.NotNil(t, contentsResponse) | |||||
| expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType) | |||||
| assert.EqualValues(t, *expectedContentsResponse, contentsResponse) | |||||
| // Test file contents a file with a bad ref | |||||
| ref = "badref" | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?ref=%s", user2.Name, repo1.Name, treePath, ref) | |||||
| resp = session.MakeRequest(t, req, http.StatusInternalServerError) | |||||
| expectedAPIError := context.APIError{ | |||||
| Message: "object does not exist [id: " + ref + ", rel_path: ]", | |||||
| URL: setting.API.SwaggerURL, | |||||
| } | |||||
| var apiError context.APIError | |||||
| DecodeJSON(t, resp, &apiError) | |||||
| assert.Equal(t, expectedAPIError, apiError) | |||||
| // Test accessing private ref with user token that does not have access - should fail | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | |||||
| session.MakeRequest(t, req, http.StatusNotFound) | |||||
| // Test access private ref of owner of token | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/readme.md?token=%s", user2.Name, repo16.Name, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| // Test access of org user3 private repo file by owner user2 | |||||
| req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | |||||
| session.MakeRequest(t, req, http.StatusOK) | |||||
| } | |||||
| @@ -6,6 +6,7 @@ package integrations | |||||
| import ( | import ( | ||||
| "net/url" | "net/url" | ||||
| "path/filepath" | |||||
| "testing" | "testing" | ||||
| "time" | "time" | ||||
| @@ -47,21 +48,30 @@ func getUpdateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFile | |||||
| } | } | ||||
| func getExpectedFileResponseForRepofilesCreate(commitID string) *api.FileResponse { | func getExpectedFileResponseForRepofilesCreate(commitID string) *api.FileResponse { | ||||
| treePath := "new/file.txt" | |||||
| encoding := "base64" | |||||
| content := "VGhpcyBpcyBhIE5FVyBmaWxl" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885" | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath | |||||
| return &api.FileResponse{ | return &api.FileResponse{ | ||||
| Content: &api.FileContentResponse{ | |||||
| Name: "file.txt", | |||||
| Path: "new/file.txt", | |||||
| Content: &api.ContentsResponse{ | |||||
| Name: filepath.Base(treePath), | |||||
| Path: treePath, | |||||
| SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", | SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", | ||||
| Type: "file", | |||||
| Size: 18, | Size: 18, | ||||
| URL: setting.AppURL + "api/v1/repos/user2/repo1/contents/new/file.txt", | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/new/file.txt", | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885", | |||||
| DownloadURL: setting.AppURL + "user2/repo1/raw/branch/master/new/file.txt", | |||||
| Type: "blob", | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: setting.AppURL + "api/v1/repos/user2/repo1/contents/new/file.txt", | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885", | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/new/file.txt", | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | }, | ||||
| }, | }, | ||||
| Commit: &api.FileCommitResponse{ | Commit: &api.FileCommitResponse{ | ||||
| @@ -105,22 +115,30 @@ func getExpectedFileResponseForRepofilesCreate(commitID string) *api.FileRespons | |||||
| } | } | ||||
| } | } | ||||
| func getExpectedFileResponseForRepofilesUpdate(commitID string) *api.FileResponse { | |||||
| func getExpectedFileResponseForRepofilesUpdate(commitID, filename string) *api.FileResponse { | |||||
| encoding := "base64" | |||||
| content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ==" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master" | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + filename | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647" | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename | |||||
| return &api.FileResponse{ | return &api.FileResponse{ | ||||
| Content: &api.FileContentResponse{ | |||||
| Name: "README.md", | |||||
| Path: "README.md", | |||||
| Content: &api.ContentsResponse{ | |||||
| Name: filename, | |||||
| Path: filename, | |||||
| SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", | SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", | ||||
| Type: "file", | |||||
| Size: 43, | Size: 43, | ||||
| URL: setting.AppURL + "api/v1/repos/user2/repo1/contents/README.md", | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/README.md", | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647", | |||||
| DownloadURL: setting.AppURL + "user2/repo1/raw/branch/master/README.md", | |||||
| Type: "blob", | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: setting.AppURL + "api/v1/repos/user2/repo1/contents/README.md", | |||||
| GitURL: setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/dbf8d00e022e05b7e5cf7e535de857de57925647", | |||||
| HTMLURL: setting.AppURL + "user2/repo1/blob/master/README.md", | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | }, | ||||
| }, | }, | ||||
| Commit: &api.FileCommitResponse{ | Commit: &api.FileCommitResponse{ | ||||
| @@ -213,7 +231,7 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { | |||||
| assert.Nil(t, err) | assert.Nil(t, err) | ||||
| gitRepo, _ := git.OpenRepository(repo.RepoPath()) | gitRepo, _ := git.OpenRepository(repo.RepoPath()) | ||||
| commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) | commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) | ||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID) | |||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) | |||||
| assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | ||||
| assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | ||||
| assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | ||||
| @@ -234,9 +252,8 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { | |||||
| repo := ctx.Repo.Repository | repo := ctx.Repo.Repository | ||||
| doer := ctx.User | doer := ctx.User | ||||
| opts := getUpdateRepoFileOptions(repo) | opts := getUpdateRepoFileOptions(repo) | ||||
| suffix := "_new" | |||||
| opts.FromTreePath = "README.md" | opts.FromTreePath = "README.md" | ||||
| opts.TreePath = "README.md" + suffix // new file name, README.md_new | |||||
| opts.TreePath = "README_new.md" // new file name, README_new.md | |||||
| // test | // test | ||||
| fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) | fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) | ||||
| @@ -245,7 +262,7 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { | |||||
| assert.Nil(t, err) | assert.Nil(t, err) | ||||
| gitRepo, _ := git.OpenRepository(repo.RepoPath()) | gitRepo, _ := git.OpenRepository(repo.RepoPath()) | ||||
| commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) | commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) | ||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String()) | |||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.TreePath) | |||||
| // assert that the old file no longer exists in the last commit of the branch | // assert that the old file no longer exists in the last commit of the branch | ||||
| fromEntry, err := commit.GetTreeEntryByPath(opts.FromTreePath) | fromEntry, err := commit.GetTreeEntryByPath(opts.FromTreePath) | ||||
| toEntry, err := commit.GetTreeEntryByPath(opts.TreePath) | toEntry, err := commit.GetTreeEntryByPath(opts.TreePath) | ||||
| @@ -253,9 +270,9 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { | |||||
| assert.NotNil(t, toEntry) // Should exist here | assert.NotNil(t, toEntry) // Should exist here | ||||
| // assert SHA has remained the same but paths use the new file name | // assert SHA has remained the same but paths use the new file name | ||||
| assert.EqualValues(t, expectedFileResponse.Content.SHA, fileResponse.Content.SHA) | assert.EqualValues(t, expectedFileResponse.Content.SHA, fileResponse.Content.SHA) | ||||
| assert.EqualValues(t, expectedFileResponse.Content.Name+suffix, fileResponse.Content.Name) | |||||
| assert.EqualValues(t, expectedFileResponse.Content.Path+suffix, fileResponse.Content.Path) | |||||
| assert.EqualValues(t, expectedFileResponse.Content.URL+suffix, fileResponse.Content.URL) | |||||
| assert.EqualValues(t, expectedFileResponse.Content.Name, fileResponse.Content.Name) | |||||
| assert.EqualValues(t, expectedFileResponse.Content.Path, fileResponse.Content.Path) | |||||
| assert.EqualValues(t, expectedFileResponse.Content.URL, fileResponse.Content.URL) | |||||
| assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | ||||
| assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | ||||
| }) | }) | ||||
| @@ -284,7 +301,7 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { | |||||
| assert.Nil(t, err) | assert.Nil(t, err) | ||||
| gitRepo, _ := git.OpenRepository(repo.RepoPath()) | gitRepo, _ := git.OpenRepository(repo.RepoPath()) | ||||
| commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) | commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) | ||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID) | |||||
| expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID, opts.TreePath) | |||||
| assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | ||||
| }) | }) | ||||
| } | } | ||||
| @@ -37,6 +37,19 @@ func (b *Blob) Name() string { | |||||
| return b.name | return b.name | ||||
| } | } | ||||
| // GetBlobContent Gets the content of the blob as raw text | |||||
| func (b *Blob) GetBlobContent() (string, error) { | |||||
| dataRc, err := b.DataAsync() | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| defer dataRc.Close() | |||||
| buf := make([]byte, 1024) | |||||
| n, _ := dataRc.Read(buf) | |||||
| buf = buf[:n] | |||||
| return string(buf), nil | |||||
| } | |||||
| // GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string | // GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string | ||||
| func (b *Blob) GetBlobContentBase64() (string, error) { | func (b *Blob) GetBlobContentBase64() (string, error) { | ||||
| dataRc, err := b.DataAsync() | dataRc, err := b.DataAsync() | ||||
| @@ -1,4 +1,5 @@ | |||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 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 | // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||
| @@ -22,6 +23,8 @@ const ( | |||||
| ObjectBlob ObjectType = "blob" | ObjectBlob ObjectType = "blob" | ||||
| // ObjectTag tag object type | // ObjectTag tag object type | ||||
| ObjectTag ObjectType = "tag" | ObjectTag ObjectType = "tag" | ||||
| // ObjectBranch branch object type | |||||
| ObjectBranch ObjectType = "branch" | |||||
| ) | ) | ||||
| // HashObject takes a reader and returns SHA1 hash for that reader | // HashObject takes a reader and returns SHA1 hash for that reader | ||||
| @@ -44,3 +47,17 @@ func (repo *Repository) hashObject(reader io.Reader) (string, error) { | |||||
| } | } | ||||
| return strings.TrimSpace(stdout.String()), nil | return strings.TrimSpace(stdout.String()), nil | ||||
| } | } | ||||
| // GetRefType gets the type of the ref based on the string | |||||
| func (repo *Repository) GetRefType(ref string) ObjectType { | |||||
| if repo.IsTagExist(ref) { | |||||
| return ObjectTag | |||||
| } else if repo.IsBranchExist(ref) { | |||||
| return ObjectBranch | |||||
| } else if repo.IsCommitExist(ref) { | |||||
| return ObjectCommit | |||||
| } else if _, err := repo.GetBlob(ref); err == nil { | |||||
| return ObjectBlob | |||||
| } | |||||
| return ObjectType("invalid") | |||||
| } | |||||
| @@ -5,26 +5,52 @@ | |||||
| package repofiles | package repofiles | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "net/url" | "net/url" | ||||
| "path" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
| api "code.gitea.io/gitea/modules/structs" | api "code.gitea.io/gitea/modules/structs" | ||||
| ) | ) | ||||
| // GetFileContents gets the meta data on a file's contents | |||||
| func GetFileContents(repo *models.Repository, treePath, ref string) (*api.FileContentResponse, error) { | |||||
| // ContentType repo content type | |||||
| type ContentType string | |||||
| // The string representations of different content types | |||||
| const ( | |||||
| // ContentTypeRegular regular content type (file) | |||||
| ContentTypeRegular ContentType = "file" | |||||
| // ContentTypeDir dir content type (dir) | |||||
| ContentTypeDir ContentType = "dir" | |||||
| // ContentLink link content type (symlink) | |||||
| ContentTypeLink ContentType = "symlink" | |||||
| // ContentTag submodule content type (submodule) | |||||
| ContentTypeSubmodule ContentType = "submodule" | |||||
| ) | |||||
| // String gets the string of ContentType | |||||
| func (ct *ContentType) String() string { | |||||
| return string(*ct) | |||||
| } | |||||
| // GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree | |||||
| // directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag | |||||
| func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface{}, error) { | |||||
| if ref == "" { | if ref == "" { | ||||
| ref = repo.DefaultBranch | ref = repo.DefaultBranch | ||||
| } | } | ||||
| origRef := ref | |||||
| // Check that the path given in opts.treePath is valid (not a git path) | // Check that the path given in opts.treePath is valid (not a git path) | ||||
| treePath = CleanUploadFileName(treePath) | |||||
| if treePath == "" { | |||||
| cleanTreePath := CleanUploadFileName(treePath) | |||||
| if cleanTreePath == "" && treePath != "" { | |||||
| return nil, models.ErrFilenameInvalid{ | return nil, models.ErrFilenameInvalid{ | ||||
| Path: treePath, | Path: treePath, | ||||
| } | } | ||||
| } | } | ||||
| treePath = cleanTreePath | |||||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | gitRepo, err := git.OpenRepository(repo.RepoPath()) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -42,32 +68,145 @@ func GetFileContents(repo *models.Repository, treePath, ref string) (*api.FileCo | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| urlRef := ref | |||||
| if _, err := gitRepo.GetBranchCommit(ref); err == nil { | |||||
| urlRef = "branch/" + ref | |||||
| if entry.Type() != "tree" { | |||||
| return GetContents(repo, treePath, origRef, false) | |||||
| } | |||||
| // We are in a directory, so we return a list of FileContentResponse objects | |||||
| var fileList []*api.ContentsResponse | |||||
| gitTree, err := commit.SubTree(treePath) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| entries, err := gitTree.ListEntries() | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| for _, e := range entries { | |||||
| subTreePath := path.Join(treePath, e.Name()) | |||||
| fileContentResponse, err := GetContents(repo, subTreePath, origRef, true) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| fileList = append(fileList, fileContentResponse) | |||||
| } | |||||
| return fileList, nil | |||||
| } | |||||
| // GetContents gets the meta data on a file's contents. Ref can be a branch, commit or tag | |||||
| func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*api.ContentsResponse, error) { | |||||
| if ref == "" { | |||||
| ref = repo.DefaultBranch | |||||
| } | } | ||||
| origRef := ref | |||||
| selfURL, _ := url.Parse(repo.APIURL() + "/contents/" + treePath) | |||||
| gitURL, _ := url.Parse(repo.APIURL() + "/git/blobs/" + entry.ID.String()) | |||||
| downloadURL, _ := url.Parse(repo.HTMLURL() + "/raw/" + urlRef + "/" + treePath) | |||||
| htmlURL, _ := url.Parse(repo.HTMLURL() + "/blob/" + ref + "/" + treePath) | |||||
| // Check that the path given in opts.treePath is valid (not a git path) | |||||
| cleanTreePath := CleanUploadFileName(treePath) | |||||
| if cleanTreePath == "" && treePath != "" { | |||||
| return nil, models.ErrFilenameInvalid{ | |||||
| Path: treePath, | |||||
| } | |||||
| } | |||||
| treePath = cleanTreePath | |||||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| fileContent := &api.FileContentResponse{ | |||||
| Name: entry.Name(), | |||||
| Path: treePath, | |||||
| SHA: entry.ID.String(), | |||||
| Size: entry.Size(), | |||||
| URL: selfURL.String(), | |||||
| HTMLURL: htmlURL.String(), | |||||
| GitURL: gitURL.String(), | |||||
| DownloadURL: downloadURL.String(), | |||||
| Type: entry.Type(), | |||||
| // Get the commit object for the ref | |||||
| commit, err := gitRepo.GetCommit(ref) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| commitID := commit.ID.String() | |||||
| if len(ref) >= 4 && strings.HasPrefix(commitID, ref) { | |||||
| ref = commit.ID.String() | |||||
| } | |||||
| entry, err := commit.GetTreeEntryByPath(treePath) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| refType := gitRepo.GetRefType(ref) | |||||
| if refType == "invalid" { | |||||
| return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref) | |||||
| } | |||||
| selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| selfURLString := selfURL.String() | |||||
| // All content types have these fields in populated | |||||
| contentsResponse := &api.ContentsResponse{ | |||||
| Name: entry.Name(), | |||||
| Path: treePath, | |||||
| SHA: entry.ID.String(), | |||||
| Size: entry.Size(), | |||||
| URL: &selfURLString, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: selfURL.String(), | |||||
| GitURL: gitURL.String(), | |||||
| HTMLURL: htmlURL.String(), | |||||
| Self: &selfURLString, | |||||
| }, | }, | ||||
| } | } | ||||
| return fileContent, nil | |||||
| // Now populate the rest of the ContentsResponse based on entry type | |||||
| if entry.IsRegular() { | |||||
| contentsResponse.Type = string(ContentTypeRegular) | |||||
| if blobResponse, err := GetBlobBySHA(repo, entry.ID.String()); err != nil { | |||||
| return nil, err | |||||
| } else if !forList { | |||||
| // We don't show the content if we are getting a list of FileContentResponses | |||||
| contentsResponse.Encoding = &blobResponse.Encoding | |||||
| contentsResponse.Content = &blobResponse.Content | |||||
| } | |||||
| } else if entry.IsDir() { | |||||
| contentsResponse.Type = string(ContentTypeDir) | |||||
| } else if entry.IsLink() { | |||||
| contentsResponse.Type = string(ContentTypeLink) | |||||
| // The target of a symlink file is the content of the file | |||||
| targetFromContent, err := entry.Blob().GetBlobContent() | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| contentsResponse.Target = &targetFromContent | |||||
| } else if entry.IsSubModule() { | |||||
| contentsResponse.Type = string(ContentTypeSubmodule) | |||||
| submodule, err := commit.GetSubModule(treePath) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| contentsResponse.SubmoduleGitURL = &submodule.URL | |||||
| } | |||||
| // Handle links | |||||
| if entry.IsRegular() || entry.IsLink() { | |||||
| downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| downloadURLString := downloadURL.String() | |||||
| contentsResponse.DownloadURL = &downloadURLString | |||||
| } | |||||
| if !entry.IsSubModule() { | |||||
| htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| htmlURLString := htmlURL.String() | |||||
| contentsResponse.HTMLURL = &htmlURLString | |||||
| contentsResponse.Links.HTMLURL = &htmlURLString | |||||
| gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String())) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| gitURLString := gitURL.String() | |||||
| contentsResponse.GitURL = &gitURLString | |||||
| contentsResponse.Links.GitURL = &gitURLString | |||||
| } | |||||
| return contentsResponse, nil | |||||
| } | } | ||||
| @@ -9,7 +9,7 @@ import ( | |||||
| "testing" | "testing" | ||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/structs" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "code.gitea.io/gitea/modules/test" | "code.gitea.io/gitea/modules/test" | ||||
| "github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
| @@ -19,7 +19,36 @@ func TestMain(m *testing.M) { | |||||
| models.MainTest(m, filepath.Join("..", "..")) | models.MainTest(m, filepath.Join("..", "..")) | ||||
| } | } | ||||
| func TestGetFileContents(t *testing.T) { | |||||
| func getExpectedReadmeContentsResponse() *api.ContentsResponse { | |||||
| treePath := "README.md" | |||||
| sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" | |||||
| encoding := "base64" | |||||
| content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x" | |||||
| selfURL := "https://try.gitea.io/api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" | |||||
| htmlURL := "https://try.gitea.io/user2/repo1/src/branch/master/" + treePath | |||||
| gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath | |||||
| return &api.ContentsResponse{ | |||||
| Name: treePath, | |||||
| Path: treePath, | |||||
| SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| Type: "file", | |||||
| Size: 30, | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | |||||
| } | |||||
| } | |||||
| func TestGetContents(t *testing.T) { | |||||
| models.PrepareTestEnv(t) | models.PrepareTestEnv(t) | ||||
| ctx := test.MockContext(t, "user2/repo1") | ctx := test.MockContext(t, "user2/repo1") | ||||
| ctx.SetParams(":id", "1") | ctx.SetParams(":id", "1") | ||||
| @@ -30,37 +59,110 @@ func TestGetFileContents(t *testing.T) { | |||||
| treePath := "README.md" | treePath := "README.md" | ||||
| ref := ctx.Repo.Repository.DefaultBranch | ref := ctx.Repo.Repository.DefaultBranch | ||||
| expectedFileContentResponse := &structs.FileContentResponse{ | |||||
| Name: treePath, | |||||
| Path: treePath, | |||||
| SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| Size: 30, | |||||
| URL: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/README.md", | |||||
| HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/README.md", | |||||
| GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| DownloadURL: "https://try.gitea.io/user2/repo1/raw/branch/master/README.md", | |||||
| Type: "blob", | |||||
| Links: &structs.FileLinksResponse{ | |||||
| Self: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/README.md", | |||||
| GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/README.md", | |||||
| }, | |||||
| expectedContentsResponse := getExpectedReadmeContentsResponse() | |||||
| t.Run("Get README.md contents with GetContents()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, ref, false) | |||||
| assert.EqualValues(t, expectedContentsResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | |||||
| }) | |||||
| t.Run("Get REAMDE.md contents with ref as empty string (should then use the repo's default branch) with GetContents()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, "", false) | |||||
| assert.EqualValues(t, expectedContentsResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | |||||
| }) | |||||
| } | |||||
| func TestGetContentsOrListForDir(t *testing.T) { | |||||
| models.PrepareTestEnv(t) | |||||
| ctx := test.MockContext(t, "user2/repo1") | |||||
| ctx.SetParams(":id", "1") | |||||
| test.LoadRepo(t, ctx, 1) | |||||
| test.LoadRepoCommit(t, ctx) | |||||
| test.LoadUser(t, ctx, 2) | |||||
| test.LoadGitRepo(t, ctx) | |||||
| treePath := "" // root dir | |||||
| ref := ctx.Repo.Repository.DefaultBranch | |||||
| readmeContentsResponse := getExpectedReadmeContentsResponse() | |||||
| // because will be in a list, doesn't have encoding and content | |||||
| readmeContentsResponse.Encoding = nil | |||||
| readmeContentsResponse.Content = nil | |||||
| expectedContentsListResponse := []*api.ContentsResponse{ | |||||
| readmeContentsResponse, | |||||
| } | } | ||||
| t.Run("Get README.md contents", func(t *testing.T) { | |||||
| fileContentResponse, err := GetFileContents(ctx.Repo.Repository, treePath, ref) | |||||
| assert.EqualValues(t, expectedFileContentResponse, fileContentResponse) | |||||
| t.Run("Get root dir contents with GetContentsOrList()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref) | |||||
| assert.EqualValues(t, expectedContentsListResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | |||||
| }) | |||||
| t.Run("Get root dir contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "") | |||||
| assert.EqualValues(t, expectedContentsListResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | |||||
| }) | |||||
| } | |||||
| func TestGetContentsOrListForFile(t *testing.T) { | |||||
| models.PrepareTestEnv(t) | |||||
| ctx := test.MockContext(t, "user2/repo1") | |||||
| ctx.SetParams(":id", "1") | |||||
| test.LoadRepo(t, ctx, 1) | |||||
| test.LoadRepoCommit(t, ctx) | |||||
| test.LoadUser(t, ctx, 2) | |||||
| test.LoadGitRepo(t, ctx) | |||||
| treePath := "README.md" | |||||
| ref := ctx.Repo.Repository.DefaultBranch | |||||
| expectedContentsResponse := getExpectedReadmeContentsResponse() | |||||
| t.Run("Get README.md contents with GetContentsOrList()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref) | |||||
| assert.EqualValues(t, expectedContentsResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | assert.Nil(t, err) | ||||
| }) | }) | ||||
| t.Run("Get REAMDE.md contents with ref as empty string (should then use the repo's default branch)", func(t *testing.T) { | |||||
| fileContentResponse, err := GetFileContents(ctx.Repo.Repository, treePath, "") | |||||
| assert.EqualValues(t, expectedFileContentResponse, fileContentResponse) | |||||
| t.Run("Get REAMDE.md contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) { | |||||
| fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "") | |||||
| assert.EqualValues(t, expectedContentsResponse, fileContentResponse) | |||||
| assert.Nil(t, err) | assert.Nil(t, err) | ||||
| }) | }) | ||||
| } | } | ||||
| func TestGetFileContentsErrors(t *testing.T) { | |||||
| func TestGetContentsErrors(t *testing.T) { | |||||
| models.PrepareTestEnv(t) | |||||
| ctx := test.MockContext(t, "user2/repo1") | |||||
| ctx.SetParams(":id", "1") | |||||
| test.LoadRepo(t, ctx, 1) | |||||
| test.LoadRepoCommit(t, ctx) | |||||
| test.LoadUser(t, ctx, 2) | |||||
| test.LoadGitRepo(t, ctx) | |||||
| repo := ctx.Repo.Repository | |||||
| treePath := "README.md" | |||||
| ref := repo.DefaultBranch | |||||
| t.Run("bad treePath", func(t *testing.T) { | |||||
| badTreePath := "bad/tree.md" | |||||
| fileContentResponse, err := GetContents(repo, badTreePath, ref, false) | |||||
| assert.Error(t, err) | |||||
| assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]") | |||||
| assert.Nil(t, fileContentResponse) | |||||
| }) | |||||
| t.Run("bad ref", func(t *testing.T) { | |||||
| badRef := "bad_ref" | |||||
| fileContentResponse, err := GetContents(repo, treePath, badRef, false) | |||||
| assert.Error(t, err) | |||||
| assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]") | |||||
| assert.Nil(t, fileContentResponse) | |||||
| }) | |||||
| } | |||||
| func TestGetContentsOrListErrors(t *testing.T) { | |||||
| models.PrepareTestEnv(t) | models.PrepareTestEnv(t) | ||||
| ctx := test.MockContext(t, "user2/repo1") | ctx := test.MockContext(t, "user2/repo1") | ||||
| ctx.SetParams(":id", "1") | ctx.SetParams(":id", "1") | ||||
| @@ -74,7 +176,7 @@ func TestGetFileContentsErrors(t *testing.T) { | |||||
| t.Run("bad treePath", func(t *testing.T) { | t.Run("bad treePath", func(t *testing.T) { | ||||
| badTreePath := "bad/tree.md" | badTreePath := "bad/tree.md" | ||||
| fileContentResponse, err := GetFileContents(repo, badTreePath, ref) | |||||
| fileContentResponse, err := GetContentsOrList(repo, badTreePath, ref) | |||||
| assert.Error(t, err) | assert.Error(t, err) | ||||
| assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]") | assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]") | ||||
| assert.Nil(t, fileContentResponse) | assert.Nil(t, fileContentResponse) | ||||
| @@ -82,7 +184,7 @@ func TestGetFileContentsErrors(t *testing.T) { | |||||
| t.Run("bad ref", func(t *testing.T) { | t.Run("bad ref", func(t *testing.T) { | ||||
| badRef := "bad_ref" | badRef := "bad_ref" | ||||
| fileContentResponse, err := GetFileContents(repo, treePath, badRef) | |||||
| fileContentResponse, err := GetContentsOrList(repo, treePath, badRef) | |||||
| assert.Error(t, err) | assert.Error(t, err) | ||||
| assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]") | assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]") | ||||
| assert.Nil(t, fileContentResponse) | assert.Nil(t, fileContentResponse) | ||||
| @@ -17,8 +17,8 @@ import ( | |||||
| // GetFileResponseFromCommit Constructs a FileResponse from a Commit object | // GetFileResponseFromCommit Constructs a FileResponse from a Commit object | ||||
| func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) { | func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) { | ||||
| fileContents, _ := GetFileContents(repo, treeName, branch) // ok if fails, then will be nil | |||||
| fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil | |||||
| fileContents, _ := GetContents(repo, treeName, branch, false) // ok if fails, then will be nil | |||||
| fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil | |||||
| verification := GetPayloadCommitVerification(commit) | verification := GetPayloadCommitVerification(commit) | ||||
| fileResponse := &api.FileResponse{ | fileResponse := &api.FileResponse{ | ||||
| Content: fileContents, | Content: fileContents, | ||||
| @@ -5,6 +5,7 @@ | |||||
| package repofiles | package repofiles | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "testing" | "testing" | ||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| @@ -16,21 +17,31 @@ import ( | |||||
| ) | ) | ||||
| func getExpectedFileResponse() *api.FileResponse { | func getExpectedFileResponse() *api.FileResponse { | ||||
| treePath := "README.md" | |||||
| sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f" | |||||
| encoding := "base64" | |||||
| content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x" | |||||
| selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" | |||||
| htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath | |||||
| gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha | |||||
| downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath | |||||
| return &api.FileResponse{ | return &api.FileResponse{ | ||||
| Content: &api.FileContentResponse{ | |||||
| Name: "README.md", | |||||
| Path: "README.md", | |||||
| SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| Content: &api.ContentsResponse{ | |||||
| Name: treePath, | |||||
| Path: treePath, | |||||
| SHA: sha, | |||||
| Type: "file", | |||||
| Size: 30, | Size: 30, | ||||
| URL: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/README.md", | |||||
| HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/README.md", | |||||
| GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| DownloadURL: "https://try.gitea.io/user2/repo1/raw/branch/master/README.md", | |||||
| Type: "blob", | |||||
| Encoding: &encoding, | |||||
| Content: &content, | |||||
| URL: &selfURL, | |||||
| HTMLURL: &htmlURL, | |||||
| GitURL: &gitURL, | |||||
| DownloadURL: &downloadURL, | |||||
| Links: &api.FileLinksResponse{ | Links: &api.FileLinksResponse{ | ||||
| Self: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/README.md", | |||||
| GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f", | |||||
| HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/README.md", | |||||
| Self: &selfURL, | |||||
| GitURL: &gitURL, | |||||
| HTMLURL: &htmlURL, | |||||
| }, | }, | ||||
| }, | }, | ||||
| Commit: &api.FileCommitResponse{ | Commit: &api.FileCommitResponse{ | ||||
| @@ -49,23 +49,32 @@ type UpdateFileOptions struct { | |||||
| // FileLinksResponse contains the links for a repo's file | // FileLinksResponse contains the links for a repo's file | ||||
| type FileLinksResponse struct { | type FileLinksResponse struct { | ||||
| Self string `json:"url"` | |||||
| GitURL string `json:"git_url"` | |||||
| HTMLURL string `json:"html_url"` | |||||
| Self *string `json:"self"` | |||||
| GitURL *string `json:"git"` | |||||
| HTMLURL *string `json:"html"` | |||||
| } | } | ||||
| // FileContentResponse contains information about a repo's file stats and content | |||||
| type FileContentResponse struct { | |||||
| Name string `json:"name"` | |||||
| Path string `json:"path"` | |||||
| SHA string `json:"sha"` | |||||
| Size int64 `json:"size"` | |||||
| URL string `json:"url"` | |||||
| HTMLURL string `json:"html_url"` | |||||
| GitURL string `json:"git_url"` | |||||
| DownloadURL string `json:"download_url"` | |||||
| Type string `json:"type"` | |||||
| Links *FileLinksResponse `json:"_links"` | |||||
| // ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content | |||||
| type ContentsResponse struct { | |||||
| Name string `json:"name"` | |||||
| Path string `json:"path"` | |||||
| SHA string `json:"sha"` | |||||
| // `type` will be `file`, `dir`, `symlink`, or `submodule` | |||||
| Type string `json:"type"` | |||||
| Size int64 `json:"size"` | |||||
| // `encoding` is populated when `type` is `file`, otherwise null | |||||
| Encoding *string `json:"encoding"` | |||||
| // `content` is populated when `type` is `file`, otherwise null | |||||
| Content *string `json:"content"` | |||||
| // `target` is populated when `type` is `symlink`, otherwise null | |||||
| Target *string `json:"target"` | |||||
| URL *string `json:"url"` | |||||
| HTMLURL *string `json:"html_url"` | |||||
| GitURL *string `json:"git_url"` | |||||
| DownloadURL *string `json:"download_url"` | |||||
| // `submodule_git_url` is populated when `type` is `submodule`, otherwise null | |||||
| SubmoduleGitURL *string `json:"submodule_git_url"` | |||||
| Links *FileLinksResponse `json:"_links"` | |||||
| } | } | ||||
| // FileCommitResponse contains information generated from a Git commit for a repo's file. | // FileCommitResponse contains information generated from a Git commit for a repo's file. | ||||
| @@ -81,7 +90,7 @@ type FileCommitResponse struct { | |||||
| // FileResponse contains information about a repo's file | // FileResponse contains information about a repo's file | ||||
| type FileResponse struct { | type FileResponse struct { | ||||
| Content *FileContentResponse `json:"content"` | |||||
| Content *ContentsResponse `json:"content"` | |||||
| Commit *FileCommitResponse `json:"commit"` | Commit *FileCommitResponse `json:"commit"` | ||||
| Verification *PayloadCommitVerification `json:"verification"` | Verification *PayloadCommitVerification `json:"verification"` | ||||
| } | } | ||||
| @@ -766,7 +766,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Get("/tags/:sha", context.RepoRef(), repo.GetTag) | m.Get("/tags/:sha", context.RepoRef(), repo.GetTag) | ||||
| }, reqRepoReader(models.UnitTypeCode)) | }, reqRepoReader(models.UnitTypeCode)) | ||||
| m.Group("/contents", func() { | m.Group("/contents", func() { | ||||
| m.Get("/*", repo.GetFileContents) | |||||
| m.Get("", repo.GetContentsList) | |||||
| m.Get("/*", repo.GetContents) | |||||
| m.Group("/*", func() { | m.Group("/*", func() { | ||||
| m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile) | m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile) | ||||
| m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile) | m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile) | ||||
| @@ -366,11 +366,11 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) { | |||||
| } | } | ||||
| } | } | ||||
| // GetFileContents Get the contents of a fle in a repository | |||||
| func GetFileContents(ctx *context.APIContext) { | |||||
| // swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetFileContents | |||||
| // GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir | |||||
| func GetContents(ctx *context.APIContext) { | |||||
| // swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents | |||||
| // --- | // --- | ||||
| // summary: Gets the contents of a file or directory in a repository | |||||
| // summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir | |||||
| // produces: | // produces: | ||||
| // - application/json | // - application/json | ||||
| // parameters: | // parameters: | ||||
| @@ -386,20 +386,20 @@ func GetFileContents(ctx *context.APIContext) { | |||||
| // required: true | // required: true | ||||
| // - name: filepath | // - name: filepath | ||||
| // in: path | // in: path | ||||
| // description: path of the file to delete | |||||
| // description: path of the dir, file, symlink or submodule in the repo | |||||
| // type: string | // type: string | ||||
| // required: true | // required: true | ||||
| // - name: ref | // - name: ref | ||||
| // in: query | // in: query | ||||
| // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)" | // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)" | ||||
| // required: false | |||||
| // type: string | // type: string | ||||
| // required: false | |||||
| // responses: | // responses: | ||||
| // "200": | // "200": | ||||
| // "$ref": "#/responses/FileContentResponse" | |||||
| // "$ref": "#/responses/ContentsResponse" | |||||
| if !CanReadFiles(ctx.Repo) { | if !CanReadFiles(ctx.Repo) { | ||||
| ctx.Error(http.StatusInternalServerError, "GetFileContents", models.ErrUserDoesNotHaveAccessToRepo{ | |||||
| ctx.Error(http.StatusInternalServerError, "GetContentsOrList", models.ErrUserDoesNotHaveAccessToRepo{ | |||||
| UserID: ctx.User.ID, | UserID: ctx.User.ID, | ||||
| RepoName: ctx.Repo.Repository.LowerName, | RepoName: ctx.Repo.Repository.LowerName, | ||||
| }) | }) | ||||
| @@ -409,9 +409,40 @@ func GetFileContents(ctx *context.APIContext) { | |||||
| treePath := ctx.Params("*") | treePath := ctx.Params("*") | ||||
| ref := ctx.QueryTrim("ref") | ref := ctx.QueryTrim("ref") | ||||
| if fileContents, err := repofiles.GetFileContents(ctx.Repo.Repository, treePath, ref); err != nil { | |||||
| ctx.Error(http.StatusInternalServerError, "GetFileContents", err) | |||||
| if fileList, err := repofiles.GetContentsOrList(ctx.Repo.Repository, treePath, ref); err != nil { | |||||
| ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err) | |||||
| } else { | } else { | ||||
| ctx.JSON(http.StatusOK, fileContents) | |||||
| ctx.JSON(http.StatusOK, fileList) | |||||
| } | } | ||||
| } | } | ||||
| // GetContentsList Get the metadata of all the entries of the root dir | |||||
| func GetContentsList(ctx *context.APIContext) { | |||||
| // swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList | |||||
| // --- | |||||
| // summary: Gets the metadata of all the entries of the root dir | |||||
| // 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: ref | |||||
| // in: query | |||||
| // description: "The name of the commit/branch/tag. Default the repository’s default branch (usually master)" | |||||
| // type: string | |||||
| // required: false | |||||
| // responses: | |||||
| // "200": | |||||
| // "$ref": "#/responses/ContentsListResponse" | |||||
| // same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface | |||||
| GetContents(ctx) | |||||
| } | |||||
| @@ -197,11 +197,18 @@ type swaggerFileResponse struct { | |||||
| Body api.FileResponse `json:"body"` | Body api.FileResponse `json:"body"` | ||||
| } | } | ||||
| // FileContentResponse | |||||
| // swagger:response FileContentResponse | |||||
| type swaggerFileContentResponse struct { | |||||
| // ContentsResponse | |||||
| // swagger:response ContentsResponse | |||||
| type swaggerContentsResponse struct { | |||||
| //in: body | //in: body | ||||
| Body api.FileContentResponse `json:"body"` | |||||
| Body api.ContentsResponse `json:"body"` | |||||
| } | |||||
| // ContentsListResponse | |||||
| // swagger:response ContentsListResponse | |||||
| type swaggerContentsListResponse struct { | |||||
| // in:body | |||||
| Body []api.ContentsResponse `json:"body"` | |||||
| } | } | ||||
| // FileDeleteResponse | // FileDeleteResponse | ||||
| @@ -1570,6 +1570,45 @@ | |||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "/repos/{owner}/{repo}/contents": { | |||||
| "get": { | |||||
| "produces": [ | |||||
| "application/json" | |||||
| ], | |||||
| "tags": [ | |||||
| "repository" | |||||
| ], | |||||
| "summary": "Gets the metadata of all the entries of the root dir", | |||||
| "operationId": "repoGetContentsList", | |||||
| "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 name of the commit/branch/tag. Default the repository’s default branch (usually master)", | |||||
| "name": "ref", | |||||
| "in": "query" | |||||
| } | |||||
| ], | |||||
| "responses": { | |||||
| "200": { | |||||
| "$ref": "#/responses/ContentsListResponse" | |||||
| } | |||||
| } | |||||
| } | |||||
| }, | |||||
| "/repos/{owner}/{repo}/contents/{filepath}": { | "/repos/{owner}/{repo}/contents/{filepath}": { | ||||
| "get": { | "get": { | ||||
| "produces": [ | "produces": [ | ||||
| @@ -1578,8 +1617,8 @@ | |||||
| "tags": [ | "tags": [ | ||||
| "repository" | "repository" | ||||
| ], | ], | ||||
| "summary": "Gets the contents of a file or directory in a repository", | |||||
| "operationId": "repoGetFileContents", | |||||
| "summary": "Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir", | |||||
| "operationId": "repoGetContents", | |||||
| "parameters": [ | "parameters": [ | ||||
| { | { | ||||
| "type": "string", | "type": "string", | ||||
| @@ -1597,7 +1636,7 @@ | |||||
| }, | }, | ||||
| { | { | ||||
| "type": "string", | "type": "string", | ||||
| "description": "path of the file to delete", | |||||
| "description": "path of the dir, file, symlink or submodule in the repo", | |||||
| "name": "filepath", | "name": "filepath", | ||||
| "in": "path", | "in": "path", | ||||
| "required": true | "required": true | ||||
| @@ -1611,7 +1650,7 @@ | |||||
| ], | ], | ||||
| "responses": { | "responses": { | ||||
| "200": { | "200": { | ||||
| "$ref": "#/responses/FileContentResponse" | |||||
| "$ref": "#/responses/ContentsResponse" | |||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| @@ -7017,6 +7056,74 @@ | |||||
| }, | }, | ||||
| "x-go-package": "code.gitea.io/gitea/modules/structs" | "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
| }, | }, | ||||
| "ContentsResponse": { | |||||
| "description": "ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content", | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "_links": { | |||||
| "$ref": "#/definitions/FileLinksResponse" | |||||
| }, | |||||
| "content": { | |||||
| "description": "`content` is populated when `type` is `file`, otherwise null", | |||||
| "type": "string", | |||||
| "x-go-name": "Content" | |||||
| }, | |||||
| "download_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "DownloadURL" | |||||
| }, | |||||
| "encoding": { | |||||
| "description": "`encoding` is populated when `type` is `file`, otherwise null", | |||||
| "type": "string", | |||||
| "x-go-name": "Encoding" | |||||
| }, | |||||
| "git_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "GitURL" | |||||
| }, | |||||
| "html_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "HTMLURL" | |||||
| }, | |||||
| "name": { | |||||
| "type": "string", | |||||
| "x-go-name": "Name" | |||||
| }, | |||||
| "path": { | |||||
| "type": "string", | |||||
| "x-go-name": "Path" | |||||
| }, | |||||
| "sha": { | |||||
| "type": "string", | |||||
| "x-go-name": "SHA" | |||||
| }, | |||||
| "size": { | |||||
| "type": "integer", | |||||
| "format": "int64", | |||||
| "x-go-name": "Size" | |||||
| }, | |||||
| "submodule_git_url": { | |||||
| "description": "`submodule_git_url` is populated when `type` is `submodule`, otherwise null", | |||||
| "type": "string", | |||||
| "x-go-name": "SubmoduleGitURL" | |||||
| }, | |||||
| "target": { | |||||
| "description": "`target` is populated when `type` is `symlink`, otherwise null", | |||||
| "type": "string", | |||||
| "x-go-name": "Target" | |||||
| }, | |||||
| "type": { | |||||
| "description": "`type` will be `file`, `dir`, `symlink`, or `submodule`", | |||||
| "type": "string", | |||||
| "x-go-name": "Type" | |||||
| }, | |||||
| "url": { | |||||
| "type": "string", | |||||
| "x-go-name": "URL" | |||||
| } | |||||
| }, | |||||
| "x-go-package": "code.gitea.io/gitea/modules/structs" | |||||
| }, | |||||
| "CreateEmailOption": { | "CreateEmailOption": { | ||||
| "description": "CreateEmailOption options when creating email addresses", | "description": "CreateEmailOption options when creating email addresses", | ||||
| "type": "object", | "type": "object", | ||||
| @@ -8179,53 +8286,6 @@ | |||||
| }, | }, | ||||
| "x-go-package": "code.gitea.io/gitea/modules/structs" | "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
| }, | }, | ||||
| "FileContentResponse": { | |||||
| "description": "FileContentResponse contains information about a repo's file stats and content", | |||||
| "type": "object", | |||||
| "properties": { | |||||
| "_links": { | |||||
| "$ref": "#/definitions/FileLinksResponse" | |||||
| }, | |||||
| "download_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "DownloadURL" | |||||
| }, | |||||
| "git_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "GitURL" | |||||
| }, | |||||
| "html_url": { | |||||
| "type": "string", | |||||
| "x-go-name": "HTMLURL" | |||||
| }, | |||||
| "name": { | |||||
| "type": "string", | |||||
| "x-go-name": "Name" | |||||
| }, | |||||
| "path": { | |||||
| "type": "string", | |||||
| "x-go-name": "Path" | |||||
| }, | |||||
| "sha": { | |||||
| "type": "string", | |||||
| "x-go-name": "SHA" | |||||
| }, | |||||
| "size": { | |||||
| "type": "integer", | |||||
| "format": "int64", | |||||
| "x-go-name": "Size" | |||||
| }, | |||||
| "type": { | |||||
| "type": "string", | |||||
| "x-go-name": "Type" | |||||
| }, | |||||
| "url": { | |||||
| "type": "string", | |||||
| "x-go-name": "URL" | |||||
| } | |||||
| }, | |||||
| "x-go-package": "code.gitea.io/gitea/modules/structs" | |||||
| }, | |||||
| "FileDeleteResponse": { | "FileDeleteResponse": { | ||||
| "description": "FileDeleteResponse contains information about a repo's file that was deleted", | "description": "FileDeleteResponse contains information about a repo's file that was deleted", | ||||
| "type": "object", | "type": "object", | ||||
| @@ -8247,15 +8307,15 @@ | |||||
| "description": "FileLinksResponse contains the links for a repo's file", | "description": "FileLinksResponse contains the links for a repo's file", | ||||
| "type": "object", | "type": "object", | ||||
| "properties": { | "properties": { | ||||
| "git_url": { | |||||
| "git": { | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "GitURL" | "x-go-name": "GitURL" | ||||
| }, | }, | ||||
| "html_url": { | |||||
| "html": { | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "HTMLURL" | "x-go-name": "HTMLURL" | ||||
| }, | }, | ||||
| "url": { | |||||
| "self": { | |||||
| "type": "string", | "type": "string", | ||||
| "x-go-name": "Self" | "x-go-name": "Self" | ||||
| } | } | ||||
| @@ -8270,7 +8330,7 @@ | |||||
| "$ref": "#/definitions/FileCommitResponse" | "$ref": "#/definitions/FileCommitResponse" | ||||
| }, | }, | ||||
| "content": { | "content": { | ||||
| "$ref": "#/definitions/FileContentResponse" | |||||
| "$ref": "#/definitions/ContentsResponse" | |||||
| }, | }, | ||||
| "verification": { | "verification": { | ||||
| "$ref": "#/definitions/PayloadCommitVerification" | "$ref": "#/definitions/PayloadCommitVerification" | ||||
| @@ -9898,6 +9958,21 @@ | |||||
| "$ref": "#/definitions/Commit" | "$ref": "#/definitions/Commit" | ||||
| } | } | ||||
| }, | }, | ||||
| "ContentsListResponse": { | |||||
| "description": "ContentsListResponse", | |||||
| "schema": { | |||||
| "type": "array", | |||||
| "items": { | |||||
| "$ref": "#/definitions/ContentsResponse" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "ContentsResponse": { | |||||
| "description": "ContentsResponse", | |||||
| "schema": { | |||||
| "$ref": "#/definitions/ContentsResponse" | |||||
| } | |||||
| }, | |||||
| "DeployKey": { | "DeployKey": { | ||||
| "description": "DeployKey", | "description": "DeployKey", | ||||
| "schema": { | "schema": { | ||||
| @@ -9922,12 +9997,6 @@ | |||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "FileContentResponse": { | |||||
| "description": "FileContentResponse", | |||||
| "schema": { | |||||
| "$ref": "#/definitions/FileContentResponse" | |||||
| } | |||||
| }, | |||||
| "FileDeleteResponse": { | "FileDeleteResponse": { | ||||
| "description": "FileDeleteResponse", | "description": "FileDeleteResponse", | ||||
| "schema": { | "schema": { | ||||