From ce8de3533485eed0c56059d6334a5031a73eed67 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 11 May 2019 16:29:17 +0100 Subject: [PATCH] Remove local clones & make hooks run on merge/edit/upload (#6672) * Add options to git.Clone to make it more capable * Begin the process of removing the local copy and tidy up * Remove Wiki LocalCopy Checkouts * Remove the last LocalRepo helpers * Remove WithTemporaryFile * Enable push-hooks for these routes * Ensure tests cope with hooks Signed-off-by: Andrew Thornton * Remove Repository.LocalCopyPath() * Move temporary repo to use the standard temporary path * Fix the tests Signed-off-by: Andrew Thornton * Remove LocalWikiPath * Fix missing remove Signed-off-by: Andrew Thornton * Use AppURL for Oauth user link (#6894) * Use AppURL for Oauth user link Fix #6843 * Update oauth.go * Update oauth.go * internal/ssh: ignore env command totally (#6825) * ssh: ignore env command totally * Remove commented code Needed fix described in issue #6889 * Escape the commit message on issues update and title in telegram hook (#6901) * update sdk to latest (#6903) * improve description of branch protection (fix #6886) (#6906) The branch protection description text were not quite accurate. * Fix logging documentation (#6904) * ENABLE_MACARON_REDIRECT should be REDIRECT_MACARON_LOG * Allow DISABLE_ROUTER_LOG to be set in the [log] section * [skip ci] Updated translations via Crowdin * Move sdk structs to modules/structs (#6905) * move sdk structs to moduels/structs * fix tests * fix fmt * fix swagger * fix vendor --- contrib/pr/checkout.go | 1 - integrations/api_repo_file_content_test.go | 6 +- integrations/api_repo_file_create_test.go | 226 +++++------ integrations/api_repo_file_delete_test.go | 224 +++++------ integrations/api_repo_file_update_test.go | 290 +++++++------- integrations/editor_test.go | 146 +++---- integrations/integration_test.go | 1 - integrations/pull_create_test.go | 113 +++--- integrations/pull_merge_test.go | 161 ++++---- integrations/pull_status_test.go | 132 +++---- integrations/repo_activity_test.go | 93 ++--- integrations/repo_branch_test.go | 5 + .../repofiles_delete_test.go | 44 ++- integrations/repofiles_update_test.go | 365 ++++++++++++++++++ models/helper_directory.go | 45 +++ models/helper_environment.go | 36 ++ models/pull.go | 63 +-- models/repo.go | 63 +-- models/repo_branch.go | 177 +++------ models/repo_test.go | 21 - models/user.go | 11 - models/wiki.go | 231 +++++++---- models/wiki_test.go | 49 ++- modules/git/command.go | 48 ++- modules/git/repo.go | 24 +- modules/git/repo_branch.go | 6 +- modules/git/repo_index.go | 98 +++++ modules/git/repo_object.go | 27 ++ modules/git/repo_tree.go | 50 +++ modules/repofiles/temp_repo.go | 39 +- modules/repofiles/update_test.go | 357 ----------------- modules/setting/repository.go | 3 - routers/repo/branch.go | 6 - 33 files changed, 1698 insertions(+), 1463 deletions(-) rename modules/repofiles/delete_test.go => integrations/repofiles_delete_test.go (76%) create mode 100644 integrations/repofiles_update_test.go create mode 100644 models/helper_directory.go create mode 100644 models/helper_environment.go create mode 100644 modules/git/repo_index.go delete mode 100644 modules/repofiles/update_test.go diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index bc393da135ed..607a50318989 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -108,7 +108,6 @@ func runPR() { models.LoadFixtures() os.RemoveAll(setting.RepoRootPath) os.RemoveAll(models.LocalCopyPath()) - os.RemoveAll(models.LocalWikiPath()) com.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath) log.Printf("[PR] Setting up router\n") diff --git a/integrations/api_repo_file_content_test.go b/integrations/api_repo_file_content_test.go index 1f535ef3a07d..896d811083fb 100644 --- a/integrations/api_repo_file_content_test.go +++ b/integrations/api_repo_file_content_test.go @@ -6,6 +6,7 @@ package integrations import ( "net/http" + "net/url" "path/filepath" "testing" @@ -40,7 +41,10 @@ func getExpectedFileContentResponseForFileContents(branch string) *api.FileConte } func TestAPIGetFileContents(t *testing.T) { - prepareTestEnv(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 diff --git a/integrations/api_repo_file_create_test.go b/integrations/api_repo_file_create_test.go index a43855b06cdc..28097179a060 100644 --- a/integrations/api_repo_file_create_test.go +++ b/integrations/api_repo_file_create_test.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "fmt" "net/http" + "net/url" "path/filepath" "testing" @@ -91,125 +92,126 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon } func TestAPICreateFile(t *testing.T) { - prepareTestEnv(t) - 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 - fileID := 0 - - // 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) - - // Test creating a file in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { + onGiteaRun(t, func(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 + fileID := 0 + + // 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) + + // Test creating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + createFileOptions := getCreateFileOptions() + createFileOptions.BranchName = branch + fileID++ + treePath := fmt.Sprintf("new/file%d.txt", fileID) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp := session.MakeRequest(t, req, http.StatusCreated) + gitRepo, _ := git.OpenRepository(repo1.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) + expectedFileResponse := getExpectedFileResponseForCreate(commitID, treePath) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + } + + // Test creating a file in a new branch createFileOptions := getCreateFileOptions() - createFileOptions.BranchName = branch + createFileOptions.BranchName = repo1.DefaultBranch + createFileOptions.NewBranchName = "new_branch" fileID++ treePath := fmt.Sprintf("new/file%d.txt", fileID) url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) req := NewRequestWithJSON(t, "POST", url, &createFileOptions) resp := session.MakeRequest(t, req, http.StatusCreated) - gitRepo, _ := git.OpenRepository(repo1.RepoPath()) - commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) - expectedFileResponse := getExpectedFileResponseForCreate(commitID, treePath) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - } + expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) + + // Test trying to create a file that already exists, should fail + createFileOptions = getCreateFileOptions() + treePath = "README.md" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + resp = session.MakeRequest(t, req, http.StatusInternalServerError) + expectedAPIError := context.APIError{ + Message: "repository file already exists [path: " + treePath + "]", + URL: base.DocURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) - // Test creating a file in a new branch - createFileOptions := getCreateFileOptions() - createFileOptions.BranchName = repo1.DefaultBranch - createFileOptions.NewBranchName = "new_branch" - fileID++ - treePath := fmt.Sprintf("new/file%d.txt", fileID) - url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req := NewRequestWithJSON(t, "POST", url, &createFileOptions) - resp := session.MakeRequest(t, req, http.StatusCreated) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" - expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID) - expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) - - // Test trying to create a file that already exists, should fail - createFileOptions = getCreateFileOptions() - treePath = "README.md" - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - resp = session.MakeRequest(t, req, http.StatusInternalServerError) - expectedAPIError := context.APIError{ - Message: "repository file already exists [path: " + treePath + "]", - URL: base.DocURL, - } - var apiError context.APIError - DecodeJSON(t, resp, &apiError) - assert.Equal(t, expectedAPIError, apiError) - - // Test creating a file in repo1 by user4 who does not have write access - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "user3/repo3" where user2 is a collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "user3/repo3" with no user token - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) - req = NewRequestWithJSON(t, "POST", url, &createFileOptions) - session.MakeRequest(t, req, http.StatusForbidden) + // Tests a repo with no token given so will fail + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "user3/repo3" where user2 is a collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusCreated) + + // Test using org repo "user3/repo3" with no user token + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "POST", url, &createFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/integrations/api_repo_file_delete_test.go b/integrations/api_repo_file_delete_test.go index b619f9c43c26..57e2539e1918 100644 --- a/integrations/api_repo_file_delete_test.go +++ b/integrations/api_repo_file_delete_test.go @@ -7,6 +7,7 @@ package integrations import ( "fmt" "net/http" + "net/url" "testing" "code.gitea.io/gitea/models" @@ -37,34 +38,50 @@ func getDeleteFileOptions() *api.DeleteFileOptions { } func TestAPIDeleteFile(t *testing.T) { - prepareTestEnv(t) - 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 - fileID := 0 - - // 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) - - // Test deleting a file in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { + onGiteaRun(t, func(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 + fileID := 0 + + // 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) + + // Test deleting a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + fileID++ + treePath := fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions := getDeleteFileOptions() + deleteFileOptions.BranchName = branch + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.NotNil(t, fileResponse) + assert.Nil(t, fileResponse.Content) + } + + // Test deleting file and making the delete in a new branch fileID++ treePath := fmt.Sprintf("delete/file%d.txt", fileID) createFile(user2, repo1, treePath) deleteFileOptions := getDeleteFileOptions() - deleteFileOptions.BranchName = branch + deleteFileOptions.BranchName = repo1.DefaultBranch + deleteFileOptions.NewBranchName = "new_branch" url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) resp := session.MakeRequest(t, req, http.StatusOK) @@ -72,92 +89,77 @@ func TestAPIDeleteFile(t *testing.T) { DecodeJSON(t, resp, &fileResponse) assert.NotNil(t, fileResponse) assert.Nil(t, fileResponse.Content) - } - // Test deleting file and making the delete in a new branch - fileID++ - treePath := fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, treePath) - deleteFileOptions := getDeleteFileOptions() - deleteFileOptions.BranchName = repo1.DefaultBranch - deleteFileOptions.NewBranchName = "new_branch" - url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - resp := session.MakeRequest(t, req, http.StatusOK) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - assert.NotNil(t, fileResponse) - assert.Nil(t, fileResponse.Content) - - // Test deleting a file with the wrong SHA - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, treePath) - deleteFileOptions = getDeleteFileOptions() - correctSHA := deleteFileOptions.SHA - deleteFileOptions.SHA = "badsha" - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - resp = session.MakeRequest(t, req, http.StatusInternalServerError) - expectedAPIError := context.APIError{ - Message: "sha does not match [given: " + deleteFileOptions.SHA + ", expected: " + correctSHA + "]", - URL: base.DocURL, - } - var apiError context.APIError - DecodeJSON(t, resp, &apiError) - assert.Equal(t, expectedAPIError, apiError) - - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusOK) - - // Test using org repo "user3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusOK) - - // Test using org repo "user3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, treePath) - deleteFileOptions = getDeleteFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) - req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) - session.MakeRequest(t, req, http.StatusForbidden) + // Test deleting a file with the wrong SHA + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions = getDeleteFileOptions() + correctSHA := deleteFileOptions.SHA + deleteFileOptions.SHA = "badsha" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + resp = session.MakeRequest(t, req, http.StatusInternalServerError) + expectedAPIError := context.APIError{ + Message: "sha does not match [given: " + deleteFileOptions.SHA + ", expected: " + correctSHA + "]", + URL: base.DocURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo16, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user3, repo3, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user3, repo3, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(user2, repo1, treePath) + deleteFileOptions = getDeleteFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/integrations/api_repo_file_update_test.go b/integrations/api_repo_file_update_test.go index 71994564ffce..37438339bbd3 100644 --- a/integrations/api_repo_file_update_test.go +++ b/integrations/api_repo_file_update_test.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "fmt" "net/http" + "net/url" "path/filepath" "testing" @@ -79,156 +80,157 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon } func TestAPIUpdateFile(t *testing.T) { - prepareTestEnv(t) - 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 - fileID := 0 - - // 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) - - // Test updating a file in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { + onGiteaRun(t, func(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 + fileID := 0 + + // 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) + + // Test updating a file in repo1 which user2 owns, try both with branch and empty branch + for _, branch := range [...]string{ + "master", // Branch + "", // Empty branch + } { + fileID++ + treePath := fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = branch + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp := session.MakeRequest(t, req, http.StatusOK) + gitRepo, _ := git.OpenRepository(repo1.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) + expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + } + + // Test updating a file in a new branch + updateFileOptions := getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + updateFileOptions.NewBranchName = "new_branch" fileID++ treePath := fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo1, treePath) - updateFileOptions := getUpdateFileOptions() - updateFileOptions.BranchName = branch url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) resp := session.MakeRequest(t, req, http.StatusOK) - gitRepo, _ := git.OpenRepository(repo1.RepoPath()) - commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - } - - // Test updating a file in a new branch - updateFileOptions := getUpdateFileOptions() - updateFileOptions.BranchName = repo1.DefaultBranch - updateFileOptions.NewBranchName = "new_branch" - fileID++ - treePath := fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - resp := session.MakeRequest(t, req, http.StatusOK) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" - expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/update/file%d.txt", fileID) - expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) - - // Test updating a file and renaming it - updateFileOptions = getUpdateFileOptions() - updateFileOptions.BranchName = repo1.DefaultBranch - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions.FromPath = treePath - treePath = "rename/" + treePath - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - resp = session.MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &fileResponse) - expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" - expectedHTMLURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/master/rename/update/file%d.txt", fileID) - expectedDownloadURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) - - // Test updating a file with the wrong SHA - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - correctSHA := updateFileOptions.SHA - updateFileOptions.SHA = "badsha" - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - resp = session.MakeRequest(t, req, http.StatusInternalServerError) - expectedAPIError := context.APIError{ - Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", - URL: base.DocURL, - } - var apiError context.APIError - DecodeJSON(t, resp, &apiError) - assert.Equal(t, expectedAPIError, apiError) - - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusOK) - - // Test using org repo "user3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusOK) - - // Test using org repo "user3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) - req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) - session.MakeRequest(t, req, http.StatusForbidden) + expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/update/file%d.txt", fileID) + expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) + + // Test updating a file and renaming it + updateFileOptions = getUpdateFileOptions() + updateFileOptions.BranchName = repo1.DefaultBranch + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions.FromPath = treePath + treePath = "rename/" + treePath + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &fileResponse) + expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" + expectedHTMLURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/master/rename/update/file%d.txt", fileID) + expectedDownloadURL = fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) + assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) + assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) + assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) + + // Test updating a file with the wrong SHA + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + correctSHA := updateFileOptions.SHA + updateFileOptions.SHA = "badsha" + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + resp = session.MakeRequest(t, req, http.StatusInternalServerError) + expectedAPIError := context.APIError{ + Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", + URL: base.DocURL, + } + var apiError context.APIError + DecodeJSON(t, resp, &apiError) + assert.Equal(t, expectedAPIError, apiError) + + // Test creating a file in repo1 by user4 who does not have write access + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Tests a repo with no token given so will fail + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using access token for a private repo that the user of the token owns + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo16, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" where user2 is a collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusOK) + + // Test using org repo "user3/repo3" with no user token + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user3, repo3, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusNotFound) + + // Test using repo "user2/repo1" where user4 is a NOT collaborator + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + createFile(user2, repo1, treePath) + updateFileOptions = getUpdateFileOptions() + url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) + req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) + session.MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/integrations/editor_test.go b/integrations/editor_test.go index e2dd2e1dc4b8..8e6effe7ebd8 100644 --- a/integrations/editor_test.go +++ b/integrations/editor_test.go @@ -7,6 +7,7 @@ package integrations import ( "net/http" "net/http/httptest" + "net/url" "path" "testing" @@ -14,80 +15,79 @@ import ( ) func TestCreateFile(t *testing.T) { - prepareTestEnv(t) + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") - session := loginUser(t, "user2") + // Request editor page + req := NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp := session.MakeRequest(t, req, http.StatusOK) - // Request editor page - req := NewRequest(t, "GET", "/user2/repo1/_new/master/") - resp := session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - lastCommit := doc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) - // Save new file to master branch - req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ - "_csrf": doc.GetCSRF(), - "last_commit": lastCommit, - "tree_path": "test.txt", - "content": "Content", - "commit_choice": "direct", + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "test.txt", + "content": "Content", + "commit_choice": "direct", + }) + resp = session.MakeRequest(t, req, http.StatusFound) }) - resp = session.MakeRequest(t, req, http.StatusFound) } func TestCreateFileOnProtectedBranch(t *testing.T) { - prepareTestEnv(t) - - session := loginUser(t, "user2") - - csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") - // Change master branch to protected - req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ - "_csrf": csrf, - "protected": "on", - }) - resp := session.MakeRequest(t, req, http.StatusFound) - // Check if master branch has been locked successfully - flashCookie := session.GetCookie("macaron_flash") - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) - - // Request editor page - req = NewRequest(t, "GET", "/user2/repo1/_new/master/") - resp = session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - lastCommit := doc.GetInputValueByName("last_commit") - assert.NotEmpty(t, lastCommit) - - // Save new file to master branch - req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ - "_csrf": doc.GetCSRF(), - "last_commit": lastCommit, - "tree_path": "test.txt", - "content": "Content", - "commit_choice": "direct", - }) - - resp = session.MakeRequest(t, req, http.StatusOK) - // Check body for error message - assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch 'master'.") - - // remove the protected branch - csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") - // Change master branch to protected - req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ - "_csrf": csrf, - "protected": "off", + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + + csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") + // Change master branch to protected + req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ + "_csrf": csrf, + "protected": "on", + }) + resp := session.MakeRequest(t, req, http.StatusFound) + // Check if master branch has been locked successfully + flashCookie := session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + + // Request editor page + req = NewRequest(t, "GET", "/user2/repo1/_new/master/") + resp = session.MakeRequest(t, req, http.StatusOK) + + doc := NewHTMLParser(t, resp.Body) + lastCommit := doc.GetInputValueByName("last_commit") + assert.NotEmpty(t, lastCommit) + + // Save new file to master branch + req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ + "_csrf": doc.GetCSRF(), + "last_commit": lastCommit, + "tree_path": "test.txt", + "content": "Content", + "commit_choice": "direct", + }) + + resp = session.MakeRequest(t, req, http.StatusOK) + // Check body for error message + assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch 'master'.") + + // remove the protected branch + csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") + // Change master branch to protected + req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ + "_csrf": csrf, + "protected": "off", + }) + resp = session.MakeRequest(t, req, http.StatusFound) + // Check if master branch has been locked successfully + flashCookie = session.GetCookie("macaron_flash") + assert.NotNil(t, flashCookie) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value) }) - resp = session.MakeRequest(t, req, http.StatusFound) - // Check if master branch has been locked successfully - flashCookie = session.GetCookie("macaron_flash") - assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value) - } func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder { @@ -151,13 +151,15 @@ func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, bra } func TestEditFile(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user2") - testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") + }) } func TestEditFileToNewBranch(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user2") - testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") + }) } diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 566859518a10..93dacaf78af2 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -178,7 +178,6 @@ func prepareTestEnv(t testing.TB, skip ...int) { assert.NoError(t, models.LoadFixtures()) assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) assert.NoError(t, os.RemoveAll(models.LocalCopyPath())) - assert.NoError(t, os.RemoveAll(models.LocalWikiPath())) assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath)) diff --git a/integrations/pull_create_test.go b/integrations/pull_create_test.go index ed553bf162e9..8f39e8b02871 100644 --- a/integrations/pull_create_test.go +++ b/integrations/pull_create_test.go @@ -7,6 +7,7 @@ package integrations import ( "net/http" "net/http/httptest" + "net/url" "path" "strings" "testing" @@ -43,63 +44,65 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, titl } func TestPullCreate(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - - // check the redirected URL - url := resp.HeaderMap.Get("Location") - assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) - - // check .diff can be accessed and matches performed change - req := NewRequest(t, "GET", url+".diff") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) - assert.Regexp(t, "^diff", resp.Body) - assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one - - // check .patch can be accessed and matches performed change - req = NewRequest(t, "GET", url+".patch") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) - assert.Regexp(t, "diff", resp.Body) - assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) - assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + // check the redirected URL + url := resp.HeaderMap.Get("Location") + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + + // check .diff can be accessed and matches performed change + req := NewRequest(t, "GET", url+".diff") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) + assert.Regexp(t, "^diff", resp.Body) + assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one + + // check .patch can be accessed and matches performed change + req = NewRequest(t, "GET", url+".patch") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) + assert.Regexp(t, "diff", resp.Body) + assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) + assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one + }) } func TestPullCreate_TitleEscape(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "XSS PR") - - // check the redirected URL - url := resp.HeaderMap.Get("Location") - assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) - - // Edit title - req := NewRequest(t, "GET", url) - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url") - assert.True(t, exists, "The template has changed") - - req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{ - "_csrf": htmlDoc.GetCSRF(), - "title": "XSS PR", + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "XSS PR") + + // check the redirected URL + url := resp.HeaderMap.Get("Location") + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + + // Edit title + req := NewRequest(t, "GET", url) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url") + assert.True(t, exists, "The template has changed") + + req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "title": "XSS PR", + }) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", url) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html() + assert.NoError(t, err) + assert.Equal(t, "<i>XSS PR</i>", titleHTML) + titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html() + assert.NoError(t, err) + assert.Equal(t, "<u>XSS PR</u>", titleHTML) }) - session.MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "GET", url) - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html() - assert.NoError(t, err) - assert.Equal(t, "<i>XSS PR</i>", titleHTML) - titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html() - assert.NoError(t, err) - assert.Equal(t, "<u>XSS PR</u>", titleHTML) } diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go index f466515eb408..1f0ad27a0b5b 100644 --- a/integrations/pull_merge_test.go +++ b/integrations/pull_merge_test.go @@ -7,6 +7,7 @@ package integrations import ( "net/http" "net/http/httptest" + "net/url" "path" "strings" "testing" @@ -52,108 +53,118 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str } func TestPullMerge(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) + }) } func TestPullRebase(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebase) + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebase) + }) } func TestPullRebaseMerge(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + prepareTestEnv(t) + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebaseMerge) + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebaseMerge) + }) } func TestPullSquash(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") - - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + prepareTestEnv(t) + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) + }) } func TestPullCleanUpAfterMerge(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + prepareTestEnv(t) + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title") + resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title") - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) - // Check PR branch deletion - resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) - respJSON := struct { - Redirect string - }{} - DecodeJSON(t, resp, &respJSON) + // Check PR branch deletion + resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) + respJSON := struct { + Redirect string + }{} + DecodeJSON(t, resp, &respJSON) - assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") + assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") - elem = strings.Split(respJSON.Redirect, "/") - assert.EqualValues(t, "pulls", elem[3]) + elem = strings.Split(respJSON.Redirect, "/") + assert.EqualValues(t, "pulls", elem[3]) - // Check branch deletion result - req := NewRequest(t, "GET", respJSON.Redirect) - resp = session.MakeRequest(t, req, http.StatusOK) + // Check branch deletion result + req := NewRequest(t, "GET", respJSON.Redirect) + resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() + htmlDoc := NewHTMLParser(t, resp.Body) + resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg) + assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg) + }) } func TestCantMergeWorkInProgress(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - - resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title") - - req := NewRequest(t, "GET", resp.Header().Get("Location")) - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text()) - assert.NotEmpty(t, text, "Can't find WIP text") - - // remove from lang - expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]") - replacer := strings.NewReplacer("", "", "", "") - assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + prepareTestEnv(t) + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + + resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title") + + req := NewRequest(t, "GET", resp.Header().Get("Location")) + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text()) + assert.NotEmpty(t, text, "Can't find WIP text") + + // remove from lang + expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]") + replacer := strings.NewReplacer("", "", "", "") + assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text") + }) } diff --git a/integrations/pull_status_test.go b/integrations/pull_status_test.go index 2381444676eb..2a4d8e0b68fa 100644 --- a/integrations/pull_status_test.go +++ b/integrations/pull_status_test.go @@ -6,6 +6,7 @@ package integrations import ( "fmt" "net/http" + "net/url" "path" "testing" @@ -16,78 +17,79 @@ import ( ) func TestPullCreate_CommitStatus(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") - - url := path.Join("user1", "repo1", "compare", "master...status1") - req := NewRequestWithValues(t, "POST", url, - map[string]string{ - "_csrf": GetCSRF(t, session, url), - "title": "pull request from status1", - }, - ) - session.MakeRequest(t, req, http.StatusFound) - - req = NewRequest(t, "GET", "/user1/repo1/pulls") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - // Request repository commits page - req = NewRequest(t, "GET", "/user1/repo1/pulls/1/commits") - resp = session.MakeRequest(t, req, http.StatusOK) - doc = NewHTMLParser(t, resp.Body) - - // Get first commit URL - commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") - assert.True(t, exists) - assert.NotEmpty(t, commitURL) - - commitID := path.Base(commitURL) - - statusList := []models.CommitStatusState{ - models.CommitStatusPending, - models.CommitStatusError, - models.CommitStatusFailure, - models.CommitStatusWarning, - models.CommitStatusSuccess, - } - - statesIcons := map[models.CommitStatusState]string{ - models.CommitStatusPending: "circle icon yellow", - models.CommitStatusSuccess: "check icon green", - models.CommitStatusError: "warning icon red", - models.CommitStatusFailure: "remove icon red", - models.CommitStatusWarning: "warning sign icon yellow", - } - - // Update commit status, and check if icon is updated as well - for _, status := range statusList { - - // Call API to add status for commit - token := getTokenForLoggedInUser(t, session) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/user1/repo1/statuses/%s?token=%s", commitID, token), - api.CreateStatusOption{ - State: api.StatusState(status), - TargetURL: "http://test.ci/", - Description: "", - Context: "testci", + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") + + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", }, ) - session.MakeRequest(t, req, http.StatusCreated) + session.MakeRequest(t, req, http.StatusFound) - req = NewRequestf(t, "GET", "/user1/repo1/pulls/1/commits") + req = NewRequest(t, "GET", "/user1/repo1/pulls") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + // Request repository commits page + req = NewRequest(t, "GET", "/user1/repo1/pulls/1/commits") resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) - commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") + // Get first commit URL + commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") assert.True(t, exists) assert.NotEmpty(t, commitURL) - assert.EqualValues(t, commitID, path.Base(commitURL)) - cls, ok := doc.doc.Find("#commits-table tbody tr td.message i.commit-status").Last().Attr("class") - assert.True(t, ok) - assert.EqualValues(t, "commit-status "+statesIcons[status], cls) - } + commitID := path.Base(commitURL) + + statusList := []models.CommitStatusState{ + models.CommitStatusPending, + models.CommitStatusError, + models.CommitStatusFailure, + models.CommitStatusWarning, + models.CommitStatusSuccess, + } + + statesIcons := map[models.CommitStatusState]string{ + models.CommitStatusPending: "circle icon yellow", + models.CommitStatusSuccess: "check icon green", + models.CommitStatusError: "warning icon red", + models.CommitStatusFailure: "remove icon red", + models.CommitStatusWarning: "warning sign icon yellow", + } + + // Update commit status, and check if icon is updated as well + for _, status := range statusList { + + // Call API to add status for commit + token := getTokenForLoggedInUser(t, session) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/user1/repo1/statuses/%s?token=%s", commitID, token), + api.CreateStatusOption{ + State: api.StatusState(status), + TargetURL: "http://test.ci/", + Description: "", + Context: "testci", + }, + ) + session.MakeRequest(t, req, http.StatusCreated) + + req = NewRequestf(t, "GET", "/user1/repo1/pulls/1/commits") + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + + commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) + assert.EqualValues(t, commitID, path.Base(commitURL)) + + cls, ok := doc.doc.Find("#commits-table tbody tr td.message i.commit-status").Last().Attr("class") + assert.True(t, ok) + assert.EqualValues(t, "commit-status "+statesIcons[status], cls) + } + }) } diff --git a/integrations/repo_activity_test.go b/integrations/repo_activity_test.go index fcdbda2c8930..cec5c79c4d20 100644 --- a/integrations/repo_activity_test.go +++ b/integrations/repo_activity_test.go @@ -6,6 +6,7 @@ package integrations import ( "net/http" + "net/url" "strings" "testing" @@ -16,49 +17,51 @@ import ( ) func TestRepoActivity(t *testing.T) { - prepareTestEnv(t) - session := loginUser(t, "user1") - - // Create PRs (1 merged & 2 proposed) - testRepoFork(t, session, "user2", "repo1", "user1", "repo1") - testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") - resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") - elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) - testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) - - testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") - testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") - - testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") - testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") - - // Create issues (3 new issues) - testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") - testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2") - testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3") - - // Create releases (1 new release) - createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) - - // Open Activity page and check stats - req := NewRequest(t, "GET", "/user2/repo1/activity") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - - // Should be 1 published release - list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") - assert.Len(t, list.Nodes, 1) - - // Should be 1 merged pull request - list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") - assert.Len(t, list.Nodes, 1) - - // Should be 2 merged proposed pull requests - list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") - assert.Len(t, list.Nodes, 2) - - // Should be 3 new issues - list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") - assert.Len(t, list.Nodes, 3) + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + + session := loginUser(t, "user1") + + // Create PRs (1 merged & 2 proposed) + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) + + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") + testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") + + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") + testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") + + // Create issues (3 new issues) + testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") + testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2") + testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3") + + // Create releases (1 new release) + createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) + + // Open Activity page and check stats + req := NewRequest(t, "GET", "/user2/repo1/activity") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Should be 1 published release + list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") + assert.Len(t, list.Nodes, 1) + + // Should be 1 merged pull request + list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") + assert.Len(t, list.Nodes, 1) + + // Should be 2 merged proposed pull requests + list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") + assert.Len(t, list.Nodes, 2) + + // Should be 3 new issues + list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") + assert.Len(t, list.Nodes, 3) + }) } diff --git a/integrations/repo_branch_test.go b/integrations/repo_branch_test.go index 3101dc4c0feb..a5447cfb665b 100644 --- a/integrations/repo_branch_test.go +++ b/integrations/repo_branch_test.go @@ -6,6 +6,7 @@ package integrations import ( "net/http" + "net/url" "path" "strings" "testing" @@ -35,6 +36,10 @@ func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubU } func TestCreateBranch(t *testing.T) { + onGiteaRun(t, testCreateBranches) +} + +func testCreateBranches(t *testing.T, giteaURL *url.URL) { tests := []struct { OldRefSubURL string NewBranch string diff --git a/modules/repofiles/delete_test.go b/integrations/repofiles_delete_test.go similarity index 76% rename from modules/repofiles/delete_test.go rename to integrations/repofiles_delete_test.go index 9d034066f53d..c3bb18ec9c27 100644 --- a/modules/repofiles/delete_test.go +++ b/integrations/repofiles_delete_test.go @@ -2,20 +2,22 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repofiles +package integrations import ( + "net/url" "testing" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/repofiles" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) -func getDeleteRepoFileOptions(repo *models.Repository) *DeleteRepoFileOptions { - return &DeleteRepoFileOptions{ +func getDeleteRepoFileOptions(repo *models.Repository) *repofiles.DeleteRepoFileOptions { + return &repofiles.DeleteRepoFileOptions{ LastCommitID: "", OldBranch: repo.DefaultBranch, NewBranch: repo.DefaultBranch, @@ -27,15 +29,15 @@ func getDeleteRepoFileOptions(repo *models.Repository) *DeleteRepoFileOptions { } } -func getExpectedDeleteFileResponse() *api.FileResponse { +func getExpectedDeleteFileResponse(u *url.URL) *api.FileResponse { return &api.FileResponse{ Content: nil, Commit: &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", + URL: u.String() + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", }, - HTMLURL: "https://try.gitea.io/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", + HTMLURL: u.String() + "user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", Author: &api.CommitUser{ Identity: api.Identity{ Name: "user1", @@ -53,7 +55,7 @@ func getExpectedDeleteFileResponse() *api.FileResponse { Parents: []*api.CommitMeta{}, Message: "Initial commit\n", Tree: &api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/2a2f1d4670728a2e10049e345bd7a276468beab6", + URL: u.String() + "api/v1/repos/user2/repo1/git/trees/2a2f1d4670728a2e10049e345bd7a276468beab6", SHA: "2a2f1d4670728a2e10049e345bd7a276468beab6", }, }, @@ -67,6 +69,10 @@ func getExpectedDeleteFileResponse() *api.FileResponse { } func TestDeleteRepoFile(t *testing.T) { + onGiteaRun(t, testDeleteRepoFile) +} + +func testDeleteRepoFile(t *testing.T, u *url.URL) { // setup models.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") @@ -80,14 +86,14 @@ func TestDeleteRepoFile(t *testing.T) { opts := getDeleteRepoFileOptions(repo) t.Run("Delete README.md file", func(t *testing.T) { - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, err) - expectedFileResponse := getExpectedDeleteFileResponse() + expectedFileResponse := getExpectedDeleteFileResponse(u) assert.EqualValues(t, expectedFileResponse, fileResponse) }) t.Run("Verify README.md has been deleted", func(t *testing.T) { - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, fileResponse) expectedError := "repository file does not exist [path: " + opts.TreePath + "]" assert.EqualError(t, err, expectedError) @@ -96,6 +102,10 @@ func TestDeleteRepoFile(t *testing.T) { // Test opts with branch names removed, same results func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { + onGiteaRun(t, testDeleteRepoFileWithoutBranchNames) +} + +func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { // setup models.PrepareTestEnv(t) ctx := test.MockContext(t, "user2/repo1") @@ -111,9 +121,9 @@ func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { opts.NewBranch = "" t.Run("Delete README.md without Branch Name", func(t *testing.T) { - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, err) - expectedFileResponse := getExpectedDeleteFileResponse() + expectedFileResponse := getExpectedDeleteFileResponse(u) assert.EqualValues(t, expectedFileResponse, fileResponse) }) } @@ -133,7 +143,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { t.Run("Bad branch", func(t *testing.T) { opts := getDeleteRepoFileOptions(repo) opts.OldBranch = "bad_branch" - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Error(t, err) assert.Nil(t, fileResponse) expectedError := "branch does not exist [name: " + opts.OldBranch + "]" @@ -144,7 +154,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { opts := getDeleteRepoFileOptions(repo) origSHA := opts.SHA opts.SHA = "bad_sha" - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, fileResponse) assert.Error(t, err) expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" @@ -154,7 +164,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { t.Run("New branch already exists", func(t *testing.T) { opts := getDeleteRepoFileOptions(repo) opts.NewBranch = "develop" - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, fileResponse) assert.Error(t, err) expectedError := "branch already exists [name: " + opts.NewBranch + "]" @@ -164,7 +174,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { t.Run("TreePath is empty:", func(t *testing.T) { opts := getDeleteRepoFileOptions(repo) opts.TreePath = "" - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, fileResponse) assert.Error(t, err) expectedError := "path contains a malformed path component [path: ]" @@ -174,7 +184,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { t.Run("TreePath is a git directory:", func(t *testing.T) { opts := getDeleteRepoFileOptions(repo) opts.TreePath = ".git" - fileResponse, err := DeleteRepoFile(repo, doer, opts) + fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) assert.Nil(t, fileResponse) assert.Error(t, err) expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" diff --git a/integrations/repofiles_update_test.go b/integrations/repofiles_update_test.go new file mode 100644 index 000000000000..02a9bbeb168d --- /dev/null +++ b/integrations/repofiles_update_test.go @@ -0,0 +1,365 @@ +// 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/url" + "testing" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/repofiles" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func getCreateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFileOptions { + return &repofiles.UpdateRepoFileOptions{ + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + TreePath: "new/file.txt", + Message: "Creates new/file.txt", + Content: "This is a NEW file", + IsNewFile: true, + Author: nil, + Committer: nil, + } +} + +func getUpdateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFileOptions { + return &repofiles.UpdateRepoFileOptions{ + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + TreePath: "README.md", + Message: "Updates README.md", + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + Content: "This is UPDATED content for the README file", + IsNewFile: false, + Author: nil, + Committer: nil, + } +} + +func getExpectedFileResponseForRepofilesCreate(commitID string) *api.FileResponse { + return &api.FileResponse{ + Content: &api.FileContentResponse{ + Name: "file.txt", + Path: "new/file.txt", + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + 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", + 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", + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", + SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + }, + }, + Message: "Updates README.md\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc", + }, + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "unsigned", + Signature: "", + Payload: "", + }, + } +} + +func getExpectedFileResponseForRepofilesUpdate(commitID string) *api.FileResponse { + return &api.FileResponse{ + Content: &api.FileContentResponse{ + Name: "README.md", + Path: "README.md", + SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", + 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", + 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", + }, + }, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + Date: time.Now().UTC().Format(time.RFC3339), + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", + SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + }, + }, + Message: "Updates README.md\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc", + }, + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "unsigned", + Signature: "", + Payload: "", + }, + } +} + +func TestCreateOrUpdateRepoFileForCreate(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + 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 + doer := ctx.User + opts := getCreateRepoFileOptions(repo) + + // test + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + + // asserts + assert.Nil(t, err) + gitRepo, _ := git.OpenRepository(repo.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) + expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + }) +} + +func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + 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 + doer := ctx.User + opts := getUpdateRepoFileOptions(repo) + + // test + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + + // asserts + assert.Nil(t, err) + gitRepo, _ := git.OpenRepository(repo.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + }) +} + +func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + 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 + doer := ctx.User + opts := getUpdateRepoFileOptions(repo) + suffix := "_new" + opts.FromTreePath = "README.md" + opts.TreePath = "README.md" + suffix // new file name, README.md_new + + // test + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + + // asserts + assert.Nil(t, err) + gitRepo, _ := git.OpenRepository(repo.RepoPath()) + commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String()) + // assert that the old file no longer exists in the last commit of the branch + fromEntry, err := commit.GetTreeEntryByPath(opts.FromTreePath) + toEntry, err := commit.GetTreeEntryByPath(opts.TreePath) + assert.Nil(t, fromEntry) // Should no longer exist here + assert.NotNil(t, toEntry) // Should exist here + // 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.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.Commit.SHA, fileResponse.Commit.SHA) + assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + }) +} + +// Test opts with branch names removed, should get same results as above test +func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + 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 + doer := ctx.User + opts := getUpdateRepoFileOptions(repo) + opts.OldBranch = "" + opts.NewBranch = "" + + // test + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + + // asserts + assert.Nil(t, err) + gitRepo, _ := git.OpenRepository(repo.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) + expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commitID) + assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) + }) +} + +func TestCreateOrUpdateRepoFileErrors(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + 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 + doer := ctx.User + + t.Run("bad branch", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.OldBranch = "bad_branch" + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Error(t, err) + assert.Nil(t, fileResponse) + expectedError := "branch does not exist [name: " + opts.OldBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("bad SHA", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + origSHA := opts.SHA + opts.SHA = "bad_sha" + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("new branch already exists", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.NewBranch = "develop" + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "branch already exists [name: " + opts.NewBranch + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("treePath is empty:", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.TreePath = "" + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: ]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("treePath is a git directory:", func(t *testing.T) { + opts := getUpdateRepoFileOptions(repo) + opts.TreePath = ".git" + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) + + t.Run("create file that already exists", func(t *testing.T) { + opts := getCreateRepoFileOptions(repo) + opts.TreePath = "README.md" //already exists + fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts) + assert.Nil(t, fileResponse) + assert.Error(t, err) + expectedError := "repository file already exists [path: " + opts.TreePath + "]" + assert.EqualError(t, err, expectedError) + }) + }) +} diff --git a/models/helper_directory.go b/models/helper_directory.go new file mode 100644 index 000000000000..417402b41c32 --- /dev/null +++ b/models/helper_directory.go @@ -0,0 +1,45 @@ +// 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 models + +import ( + "fmt" + "os" + "path" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/Unknwon/com" +) + +// LocalCopyPath returns the local repository temporary copy path. +func LocalCopyPath() string { + if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { + return setting.Repository.Local.LocalCopyPath + } + return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) +} + +// CreateTemporaryPath creates a temporary path +func CreateTemporaryPath(prefix string) (string, error) { + timeStr := com.ToStr(time.Now().Nanosecond()) // SHOULD USE SOMETHING UNIQUE + basePath := path.Join(LocalCopyPath(), prefix+"-"+timeStr+".git") + if err := os.MkdirAll(filepath.Dir(basePath), os.ModePerm); err != nil { + log.Error("Unable to create temporary directory: %s (%v)", basePath, err) + return "", fmt.Errorf("Failed to create dir %s: %v", basePath, err) + } + return basePath, nil +} + +// RemoveTemporaryPath removes the temporary path +func RemoveTemporaryPath(basePath string) error { + if _, err := os.Stat(basePath); !os.IsNotExist(err) { + return os.RemoveAll(basePath) + } + return nil +} diff --git a/models/helper_environment.go b/models/helper_environment.go new file mode 100644 index 000000000000..283584cc5238 --- /dev/null +++ b/models/helper_environment.go @@ -0,0 +1,36 @@ +// 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 models + +import ( + "fmt" + "os" + "strings" +) + +// PushingEnvironment returns an os environment to allow hooks to work on push +func PushingEnvironment(doer *User, repo *Repository) []string { + isWiki := "false" + if strings.HasSuffix(repo.Name, ".wiki") { + isWiki = "true" + } + + sig := doer.NewGitSig() + + return append(os.Environ(), + "GIT_AUTHOR_NAME="+sig.Name, + "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_COMMITTER_NAME="+sig.Name, + "GIT_COMMITTER_EMAIL="+sig.Email, + EnvRepoName+"="+repo.Name, + EnvRepoUsername+"="+repo.OwnerName, + EnvRepoIsWiki+"="+isWiki, + EnvPusherName+"="+doer.Name, + EnvPusherID+"="+fmt.Sprintf("%d", doer.ID), + ProtectedBranchRepoID+"="+fmt.Sprintf("%d", repo.ID), + "SSH_ORIGINAL_COMMAND=gitea-internal", + ) + +} diff --git a/models/pull.go b/models/pull.go index 7382cbd126a6..6f37145d9beb 100644 --- a/models/pull.go +++ b/models/pull.go @@ -418,22 +418,21 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) }() - headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) - // Clone base repo. - tmpBasePath := path.Join(LocalCopyPath(), "merge-"+com.ToStr(time.Now().Nanosecond())+".git") - - if err := os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", tmpBasePath, err) + tmpBasePath, err := CreateTemporaryPath("merge") + if err != nil { + return err } + defer RemoveTemporaryPath(tmpBasePath) - defer os.RemoveAll(tmpBasePath) + headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) - var stderr string - if _, stderr, err = process.GetManager().ExecTimeout(5*time.Minute, - fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath), - "git", "clone", "-s", "--no-checkout", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil { - return fmt.Errorf("git clone: %s", stderr) + if err := git.Clone(baseGitRepo.Path, tmpBasePath, git.CloneRepoOptions{ + Shared: true, + NoCheckout: true, + Branch: pr.BaseBranch, + }); err != nil { + return fmt.Errorf("git clone: %v", err) } remoteRepoName := "head_repo" @@ -456,14 +455,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err) } - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath), "git", "remote", "add", remoteRepoName, headRepoPath); err != nil { return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Fetch head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath), "git", "fetch", remoteRepoName); err != nil { return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) @@ -487,14 +486,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle return fmt.Errorf("Writing sparse-checkout file to %s: %v", sparseCheckoutListPath, err) } - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git config): %s", tmpBasePath), "git", "config", "--local", "core.sparseCheckout", "true"); err != nil { return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr) } // Read base branch index - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git read-tree): %s", tmpBasePath), "git", "read-tree", "HEAD"); err != nil { return fmt.Errorf("git read-tree HEAD: %s", stderr) @@ -503,14 +502,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle // Merge commits. switch mergeStyle { case MergeStyleMerge: - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), "git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil { return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) } sig := doer.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -518,50 +517,50 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle } case MergeStyleRebase: // Checkout head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Rebase before merging - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "rebase", "-q", pr.BaseBranch); err != nil { return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Checkout base branch again - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", pr.BaseBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Merge fast forward - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "merge", "--ff-only", "-q", stagingBranch); err != nil { return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } case MergeStyleRebaseMerge: // Checkout head branch - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Rebase before merging - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), "git", "rebase", "-q", pr.BaseBranch); err != nil { return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } // Checkout base branch again - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), "git", "checkout", pr.BaseBranch); err != nil { return fmt.Errorf("git checkout: %s", stderr) } // Prepare merge with commit - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), "git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil { return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) @@ -569,7 +568,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle // Set custom message and author and create merge commit sig := doer.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git commit): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -578,13 +577,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle case MergeStyleSquash: // Merge with squash - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), "git", "merge", "-q", "--squash", trackingBranch); err != nil { return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } sig := pr.Issue.Poster.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDir(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message); err != nil { @@ -594,10 +593,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} } + env := PushingEnvironment(doer, pr.BaseRepo) + // Push back to upstream. - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + if _, stderr, err := process.GetManager().ExecDirEnv(-1, tmpBasePath, fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath), - "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { + env, "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil { return fmt.Errorf("git push: %s", stderr) } diff --git a/models/repo.go b/models/repo.go index 66c1bdbab198..2f87e2f514e2 100644 --- a/models/repo.go +++ b/models/repo.go @@ -518,7 +518,7 @@ func (repo *Repository) DeleteWiki() error { } func (repo *Repository) deleteWiki(e Engine) error { - wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} + wikiPaths := []string{repo.WikiPath()} for _, wikiPath := range wikiPaths { removeAllWithNotice(e, "Delete repository wiki", wikiPath) } @@ -749,56 +749,6 @@ func (repo *Repository) DescriptionHTML() template.HTML { return template.HTML(markup.Sanitize(string(desc))) } -// LocalCopyPath returns the local repository copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - -// LocalCopyPath returns the local repository copy path for the given repo. -func (repo *Repository) LocalCopyPath() string { - return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target branch by default, it is safe to assume subsequent -// operations are operating against target branch when caller has confidence for no race condition. -func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - Branch: branch, - }); err != nil { - return fmt.Errorf("git clone %s: %v", branch, err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if len(branch) > 0 { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: branch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", branch, err) - } - - if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { - return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) - } - } - } - return nil -} - -// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. -func (repo *Repository) UpdateLocalCopyBranch(branch string) error { - return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) -} - // PatchPath returns corresponding patch file path of repository by given issue ID. func (repo *Repository) PatchPath(index int64) (string, error) { return repo.patchPath(x, index) @@ -1583,12 +1533,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository directory: %v", err) } - removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) // Rename remote wiki repository to new path and delete local copy. wikiPath := WikiPath(owner.Name, repo.Name) if com.IsExist(wikiPath) { - removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } @@ -1633,20 +1581,11 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) return fmt.Errorf("rename repository directory: %v", err) } - localPath := repo.LocalCopyPath() - if com.IsExist(localPath) { - _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) - if err != nil { - return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) - } - } - wikiPath := repo.WikiPath() if com.IsExist(wikiPath) { if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { return fmt.Errorf("rename repository wiki: %v", err) } - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) } sess := x.NewSession() diff --git a/models/repo_branch.go b/models/repo_branch.go index 0958e2397453..08c881fc24c2 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -7,86 +7,11 @@ package models import ( "fmt" - "time" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" - - "github.com/Unknwon/com" + "code.gitea.io/gitea/modules/log" ) -// discardLocalRepoBranchChanges discards local commits/changes of -// given branch to make sure it is even to remote branch. -func discardLocalRepoBranchChanges(localPath, branch string) error { - if !com.IsExist(localPath) { - return nil - } - // No need to check if nothing in the repository. - if !git.IsBranchExist(localPath, branch) { - return nil - } - - refName := "origin/" + branch - if err := git.ResetHEAD(localPath, true, refName); err != nil { - return fmt.Errorf("git reset --hard %s: %v", refName, err) - } - return nil -} - -// DiscardLocalRepoBranchChanges discards the local repository branch changes -func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error { - return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch) -} - -// checkoutNewBranch checks out to a new branch from the a branch name. -func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, - Branch: newBranch, - OldBranch: oldBranch, - }); err != nil { - return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err) - } - return nil -} - -// CheckoutNewBranch checks out a new branch -func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error { - return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch) -} - -// deleteLocalBranch deletes a branch from a local repo cache -// First checks out default branch to avoid trying to delete the currently checked out branch -func deleteLocalBranch(localPath, defaultBranch, deleteBranch string) error { - if !com.IsExist(localPath) { - return nil - } - - if !git.IsBranchExist(localPath, deleteBranch) { - return nil - } - - // Must NOT have branch currently checked out - // Checkout default branch first - if err := git.Checkout(localPath, git.CheckoutOptions{ - Timeout: time.Duration(setting.Git.Timeout.Pull) * time.Second, - Branch: defaultBranch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", defaultBranch, err) - } - - cmd := git.NewCommand("branch") - cmd.AddArguments("-D") - cmd.AddArguments(deleteBranch) - _, err := cmd.RunInDir(localPath) - return err -} - -// DeleteLocalBranch deletes a branch from the local repo -func (repo *Repository) DeleteLocalBranch(branchName string) error { - return deleteLocalBranch(repo.LocalCopyPath(), repo.DefaultBranch, branchName) -} - // CanCreateBranch returns true if repository meets the requirements for creating new branches. func (repo *Repository) CanCreateBranch() bool { return !repo.IsMirror @@ -137,92 +62,86 @@ func (repo *Repository) CheckBranchName(name string) error { // CreateNewBranch creates a new repository branch func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - // Check if branch name can be used if err := repo.CheckBranchName(branchName); err != nil { return err } - localPath := repo.LocalCopyPath() - - if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil { - return fmt.Errorf("discardLocalRepoChanges: %v", err) - } else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil { - return fmt.Errorf("UpdateLocalCopyBranch: %v", err) + if !git.IsBranchExist(repo.RepoPath(), oldBranchName) { + return fmt.Errorf("OldBranch: %s does not exist. Cannot create new branch from this", oldBranchName) } - if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil { - return fmt.Errorf("CreateNewBranch: %v", err) + basePath, err := CreateTemporaryPath("branch-maker") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - if err = git.Push(localPath, git.PushOptions{ - Remote: "origin", - Branch: branchName, + if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, }); err != nil { - return fmt.Errorf("Push: %v", err) + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) } - return nil -} + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } -// updateLocalCopyToCommit pulls latest changes of given commit from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target commit by default, it is safe to assume subsequent -// operations are operating against target commit when caller has confidence for no race condition. -func updateLocalCopyToCommit(repoPath, localPath, commit string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - }); err != nil { - return fmt.Errorf("git clone: %v", err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if err := git.ResetHEAD(localPath, true, "HEAD"); err != nil { - return fmt.Errorf("git reset --hard HEAD: %v", err) - } + if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil { + log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) + return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err) } - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: commit, + + if err = git.Push(basePath, git.PushOptions{ + Remote: "origin", + Branch: branchName, + Env: PushingEnvironment(doer, repo), }); err != nil { - return fmt.Errorf("git checkout %s: %v", commit, err) + return fmt.Errorf("Push: %v", err) } - return nil -} -// updateLocalCopyToCommit makes sure local copy of repository is at given commit. -func (repo *Repository) updateLocalCopyToCommit(commit string) error { - return updateLocalCopyToCommit(repo.RepoPath(), repo.LocalCopyPath(), commit) + return nil } // CreateNewBranchFromCommit creates a new repository branch func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName string) (err error) { - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - // Check if branch name can be used if err := repo.CheckBranchName(branchName); err != nil { return err } + basePath, err := CreateTemporaryPath("branch-maker") + if err != nil { + return err + } + defer RemoveTemporaryPath(basePath) - localPath := repo.LocalCopyPath() + if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, + }); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } - if err = repo.updateLocalCopyToCommit(commit); err != nil { - return fmt.Errorf("UpdateLocalCopyBranch: %v", err) + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } - if err = repo.CheckoutNewBranch(commit, branchName); err != nil { - return fmt.Errorf("CheckoutNewBranch: %v", err) + if err = gitRepo.CreateBranch(branchName, commit); err != nil { + log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err) + return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, commit, err) } - if err = git.Push(localPath, git.PushOptions{ + if err = git.Push(basePath, git.PushOptions{ Remote: "origin", Branch: branchName, + Env: PushingEnvironment(doer, repo), }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/repo_test.go b/models/repo_test.go index a5b8cce9b993..eee399786802 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -5,11 +5,9 @@ package models import ( - "path" "testing" "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/setting" "github.com/Unknwon/com" "github.com/stretchr/testify/assert" @@ -138,25 +136,6 @@ func TestRepoAPIURL(t *testing.T) { assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) } -func TestRepoLocalCopyPath(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - repo, err := GetRepositoryByID(10) - assert.NoError(t, err) - assert.NotNil(t, repo) - - // test default - repoID := com.ToStr(repo.ID) - expected := path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath, repoID) - assert.Equal(t, expected, repo.LocalCopyPath()) - - // test absolute setting - tempPath := "/tmp/gitea/local-copy-path" - expected = path.Join(tempPath, repoID) - setting.Repository.Local.LocalCopyPath = tempPath - assert.Equal(t, expected, repo.LocalCopyPath()) -} - func TestTransferOwnership(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/user.go b/models/user.go index 0c445b5afd45..90ca189ef0e0 100644 --- a/models/user.go +++ b/models/user.go @@ -943,17 +943,6 @@ func ChangeUserName(u *User, newUserName string) (err error) { return fmt.Errorf("ChangeUsernameInPullRequests: %v", err) } - // Delete all local copies of repository wiki that user owns. - if err = x.BufferSize(setting.IterateBufferSize). - Where("owner_id=?", u.ID). - Iterate(new(Repository), func(idx int, bean interface{}) error { - repo := bean.(*Repository) - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - return nil - }); err != nil { - return fmt.Errorf("Delete repository wiki local copy: %v", err) - } - // Do not fail if directory does not exist if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { return fmt.Errorf("Rename user directory: %v", err) diff --git a/models/wiki.go b/models/wiki.go index 0f5cdc20bd1f..bcf97c076573 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -6,15 +6,13 @@ package models import ( "fmt" - "io/ioutil" "net/url" "os" - "path" "path/filepath" "strings" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/sync" "github.com/Unknwon/com" @@ -89,34 +87,6 @@ func (repo *Repository) InitWiki() error { return nil } -// LocalWikiPath returns the local wiki repository copy path. -func LocalWikiPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalWikiPath) { - return setting.Repository.Local.LocalWikiPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalWikiPath) -} - -// LocalWikiPath returns the path to the local wiki repository (?). -func (repo *Repository) LocalWikiPath() string { - return path.Join(LocalWikiPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date. -func (repo *Repository) updateLocalWiki() error { - // Don't pass branch name here because it fails to clone and - // checkout to a specific branch when wiki is an empty repository. - var branch = "" - if com.IsExist(repo.LocalWikiPath()) { - branch = "master" - } - return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), branch) -} - -func discardLocalWikiChanges(localPath string) error { - return discardLocalRepoBranchChanges(localPath, "master") -} - // nameAllowed checks if a wiki name is allowed func nameAllowed(name string) error { for _, reservedName := range reservedWikiNames { @@ -132,7 +102,6 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con if err = nameAllowed(newWikiName); err != nil { return err } - wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) @@ -140,54 +109,113 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con return fmt.Errorf("InitWiki: %v", err) } - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.updateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) + hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") + + basePath, err := CreateTemporaryPath("update-wiki") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - newWikiPath := path.Join(localPath, WikiNameToFilename(newWikiName)) + cloneOpts := git.CloneRepoOptions{ + Bare: true, + Shared: true, + } + + if hasMasterBranch { + cloneOpts.Branch = "master" + } - // If not a new file, show perform update not create. + if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } + + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) + } + + if hasMasterBranch { + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + } + + newWikiPath := WikiNameToFilename(newWikiName) if isNew { - if com.IsExist(newWikiPath) { - return ErrWikiAlreadyExist{newWikiPath} + filesInIndex, err := gitRepo.LsFiles(newWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + for _, file := range filesInIndex { + if file == newWikiPath { + return ErrWikiAlreadyExist{newWikiPath} + } } } else { - oldWikiPath := path.Join(localPath, WikiNameToFilename(oldWikiName)) - if err := os.Remove(oldWikiPath); err != nil { - return fmt.Errorf("Failed to remove %s: %v", oldWikiPath, err) + oldWikiPath := WikiNameToFilename(oldWikiName) + filesInIndex, err := gitRepo.LsFiles(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } + found := false + for _, file := range filesInIndex { + if file == oldWikiPath { + found = true + break + } + } + if found { + err := gitRepo.RemoveFilesFromIndex(oldWikiPath) + if err != nil { + log.Error("%v", err) + return err + } } } - // SECURITY: if new file is a symlink to non-exist critical file, - // attack content can be written to the target file (e.g. authorized_keys2) - // as a new page operation. - // So we want to make sure the symlink is removed before write anything. - // The new file we created will be in normal text format. - if err = os.RemoveAll(newWikiPath); err != nil { + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + objectHash, err := gitRepo.HashObject(strings.NewReader(content)) + if err != nil { + log.Error("%v", err) return err } - if err = ioutil.WriteFile(newWikiPath, []byte(content), 0666); err != nil { - return fmt.Errorf("WriteFile: %v", err) + if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { + log.Error("%v", err) + return err } - if len(message) == 0 { - message = "Update page '" + newWikiName + "'" + tree, err := gitRepo.WriteTree() + if err != nil { + log.Error("%v", err) + return err } - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, git.PushOptions{ + + commitTreeOpts := git.CommitTreeOpts{ + Message: message, + } + if hasMasterBranch { + commitTreeOpts.Parents = []string{"HEAD"} + } + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts) + if err != nil { + log.Error("%v", err) + return err + } + + if err := git.Push(basePath, git.PushOptions{ Remote: "origin", - Branch: "master", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: PushingEnvironment(doer, repo), }); err != nil { + log.Error("%v", err) return fmt.Errorf("Push: %v", err) } @@ -210,31 +238,74 @@ func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) - localPath := repo.LocalWikiPath() - if err = discardLocalWikiChanges(localPath); err != nil { - return fmt.Errorf("discardLocalWikiChanges: %v", err) - } else if err = repo.updateLocalWiki(); err != nil { - return fmt.Errorf("UpdateLocalWiki: %v", err) + if err = repo.InitWiki(); err != nil { + return fmt.Errorf("InitWiki: %v", err) + } + + basePath, err := CreateTemporaryPath("update-wiki") + if err != nil { + return err } + defer RemoveTemporaryPath(basePath) - filename := path.Join(localPath, WikiNameToFilename(wikiName)) + if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{ + Bare: true, + Shared: true, + Branch: "master", + }); err != nil { + log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err) + return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err) + } - if err := os.Remove(filename); err != nil { - return fmt.Errorf("Failed to remove %s: %v", filename, err) + gitRepo, err := git.OpenRepository(basePath) + if err != nil { + log.Error("Unable to open temporary repository: %s (%v)", basePath, err) + return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err) } + if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil { + log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err) + return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err) + } + + wikiPath := WikiNameToFilename(wikiName) + filesInIndex, err := gitRepo.LsFiles(wikiPath) + found := false + for _, file := range filesInIndex { + if file == wikiPath { + found = true + break + } + } + if found { + err := gitRepo.RemoveFilesFromIndex(wikiPath) + if err != nil { + return err + } + } else { + return os.ErrNotExist + } + + // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here + + tree, err := gitRepo.WriteTree() + if err != nil { + return err + } message := "Delete page '" + wikiName + "'" - if err = git.AddChanges(localPath, true); err != nil { - return fmt.Errorf("AddChanges: %v", err) - } else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ - Committer: doer.NewGitSig(), - Message: message, - }); err != nil { - return fmt.Errorf("CommitChanges: %v", err) - } else if err = git.Push(localPath, git.PushOptions{ + commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, git.CommitTreeOpts{ + Message: message, + Parents: []string{"HEAD"}, + }) + if err != nil { + return err + } + + if err := git.Push(basePath, git.PushOptions{ Remote: "origin", - Branch: "master", + Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), + Env: PushingEnvironment(doer, repo), }); err != nil { return fmt.Errorf("Push: %v", err) } diff --git a/models/wiki_test.go b/models/wiki_test.go index 5280b3ea01a2..991a3d95b903 100644 --- a/models/wiki_test.go +++ b/models/wiki_test.go @@ -5,13 +5,12 @@ package models import ( - "path" "path/filepath" "testing" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/stretchr/testify/assert" ) @@ -145,13 +144,6 @@ func TestRepository_InitWiki(t *testing.T) { assert.True(t, repo2.HasWiki()) } -func TestRepository_LocalWikiPath(t *testing.T) { - PrepareTestEnv(t) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - expected := filepath.Join(setting.AppDataPath, setting.Repository.Local.LocalWikiPath, "1") - assert.Equal(t, expected, repo.LocalWikiPath()) -} - func TestRepository_AddWikiPage(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) const wikiContent = "This is the wiki content" @@ -166,8 +158,15 @@ func TestRepository_AddWikiPage(t *testing.T) { t.Run("test wiki exist: "+wikiName, func(t *testing.T) { t.Parallel() assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg)) - expectedPath := path.Join(repo.LocalWikiPath(), WikiNameToFilename(wikiName)) - assert.True(t, com.IsExist(expectedPath)) + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename(wikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName) }) } @@ -200,11 +199,20 @@ func TestRepository_EditWikiPage(t *testing.T) { } { PrepareTestEnv(t) assert.NoError(t, repo.EditWikiPage(doer, "Home", newWikiName, newWikiContent, commitMsg)) - newPath := path.Join(repo.LocalWikiPath(), WikiNameToFilename(newWikiName)) - assert.True(t, com.IsExist(newPath)) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename(newWikiName) + entry, err := masterTree.GetTreeEntryByPath(wikiPath) + assert.NoError(t, err) + assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName) + if newWikiName != "Home" { - oldPath := path.Join(repo.LocalWikiPath(), "Home.md") - assert.False(t, com.IsExist(oldPath)) + _, err := masterTree.GetTreeEntryByPath("Home.md") + assert.Error(t, err) } } } @@ -214,6 +222,13 @@ func TestRepository_DeleteWikiPage(t *testing.T) { repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) assert.NoError(t, repo.DeleteWikiPage(doer, "Home")) - wikiPath := path.Join(repo.LocalWikiPath(), "Home.md") - assert.False(t, com.IsExist(wikiPath)) + + // Now need to show that the page has been added: + gitRepo, err := git.OpenRepository(repo.WikiPath()) + assert.NoError(t, err) + masterTree, err := gitRepo.GetTree("master") + assert.NoError(t, err) + wikiPath := WikiNameToFilename("Home") + _, err = masterTree.GetTreeEntryByPath(wikiPath) + assert.Error(t, err) } diff --git a/modules/git/command.go b/modules/git/command.go index 3602717702f1..d6221ce26827 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -52,9 +52,15 @@ func (c *Command) AddArguments(args ...string) *Command { return c } -// RunInDirTimeoutPipeline executes the command in given directory with given timeout, +// RunInDirTimeoutEnvPipeline executes the command in given directory with given timeout, // it pipes stdout and stderr to given io.Writer. -func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error { +func (c *Command) RunInDirTimeoutEnvPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer) error { + return c.RunInDirTimeoutEnvFullPipeline(env, timeout, dir, stdout, stderr, nil) +} + +// RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout, +// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. +func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error { if timeout == -1 { timeout = DefaultCommandExecutionTimeout } @@ -69,9 +75,11 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std defer cancel() cmd := exec.CommandContext(ctx, c.name, c.args...) + cmd.Env = env cmd.Dir = dir cmd.Stdout = stdout cmd.Stderr = stderr + cmd.Stdin = stdin if err := cmd.Start(); err != nil { return err } @@ -83,12 +91,30 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std return ctx.Err() } +// RunInDirTimeoutPipeline executes the command in given directory with given timeout, +// it pipes stdout and stderr to given io.Writer. +func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error { + return c.RunInDirTimeoutEnvPipeline(nil, timeout, dir, stdout, stderr) +} + +// RunInDirTimeoutFullPipeline executes the command in given directory with given timeout, +// it pipes stdout and stderr to given io.Writer, and stdin from the given io.Reader +func (c *Command) RunInDirTimeoutFullPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error { + return c.RunInDirTimeoutEnvFullPipeline(nil, timeout, dir, stdout, stderr, stdin) +} + // RunInDirTimeout executes the command in given directory with given timeout, // and returns stdout in []byte and error (combined with stderr). func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) { + return c.RunInDirTimeoutEnv(nil, timeout, dir) +} + +// RunInDirTimeoutEnv executes the command in given directory with given timeout, +// and returns stdout in []byte and error (combined with stderr). +func (c *Command) RunInDirTimeoutEnv(env []string, timeout time.Duration, dir string) ([]byte, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - if err := c.RunInDirTimeoutPipeline(timeout, dir, stdout, stderr); err != nil { + if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil { return nil, concatenateError(err, stderr.String()) } @@ -101,7 +127,13 @@ func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, er // RunInDirPipeline executes the command in given directory, // it pipes stdout and stderr to given io.Writer. func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error { - return c.RunInDirTimeoutPipeline(-1, dir, stdout, stderr) + return c.RunInDirFullPipeline(dir, stdout, stderr, nil) +} + +// RunInDirFullPipeline executes the command in given directory, +// it pipes stdout and stderr to given io.Writer. +func (c *Command) RunInDirFullPipeline(dir string, stdout, stderr io.Writer, stdin io.Reader) error { + return c.RunInDirTimeoutFullPipeline(-1, dir, stdout, stderr, stdin) } // RunInDirBytes executes the command in given directory @@ -113,7 +145,13 @@ func (c *Command) RunInDirBytes(dir string) ([]byte, error) { // RunInDir executes the command in given directory // and returns stdout in string and error (combined with stderr). func (c *Command) RunInDir(dir string) (string, error) { - stdout, err := c.RunInDirTimeout(-1, dir) + return c.RunInDirWithEnv(dir, nil) +} + +// RunInDirWithEnv executes the command in given directory +// and returns stdout in string and error (combined with stderr). +func (c *Command) RunInDirWithEnv(dir string, env []string) (string, error) { + stdout, err := c.RunInDirTimeoutEnv(env, -1, dir) if err != nil { return "", err } diff --git a/modules/git/repo.go b/modules/git/repo.go index 8355f8811f51..4be316413079 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -109,11 +109,13 @@ func OpenRepository(repoPath string) (*Repository, error) { // CloneRepoOptions options when clone a repository type CloneRepoOptions struct { - Timeout time.Duration - Mirror bool - Bare bool - Quiet bool - Branch string + Timeout time.Duration + Mirror bool + Bare bool + Quiet bool + Branch string + Shared bool + NoCheckout bool } // Clone clones original repository to target path. @@ -133,10 +135,17 @@ func Clone(from, to string, opts CloneRepoOptions) (err error) { if opts.Quiet { cmd.AddArguments("--quiet") } + if opts.Shared { + cmd.AddArguments("-s") + } + if opts.NoCheckout { + cmd.AddArguments("--no-checkout") + } + if len(opts.Branch) > 0 { cmd.AddArguments("-b", opts.Branch) } - cmd.AddArguments(from, to) + cmd.AddArguments("--", from, to) if opts.Timeout <= 0 { opts.Timeout = -1 @@ -181,6 +190,7 @@ type PushOptions struct { Remote string Branch string Force bool + Env []string } // Push pushs local commits to given remote branch. @@ -190,7 +200,7 @@ func Push(repoPath string, opts PushOptions) error { cmd.AddArguments("-f") } cmd.AddArguments(opts.Remote, opts.Branch) - _, err := cmd.RunInDir(repoPath) + _, err := cmd.RunInDirWithEnv(repoPath, opts.Env) return err } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 83689ee9dc03..116bdbee823c 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -17,7 +17,7 @@ const BranchPrefix = "refs/heads/" // IsReferenceExist returns true if given reference exists in the repository. func IsReferenceExist(repoPath, name string) bool { - _, err := NewCommand("show-ref", "--verify", name).RunInDir(repoPath) + _, err := NewCommand("show-ref", "--verify", "--", name).RunInDir(repoPath) return err == nil } @@ -145,9 +145,9 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro } // CreateBranch create a new branch -func (repo *Repository) CreateBranch(branch, newBranch string) error { +func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error { cmd := NewCommand("branch") - cmd.AddArguments(branch, newBranch) + cmd.AddArguments("--", branch, oldbranchOrCommit) _, err := cmd.RunInDir(repo.Path) diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go new file mode 100644 index 000000000000..4d26563a9101 --- /dev/null +++ b/modules/git/repo_index.go @@ -0,0 +1,98 @@ +// 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 git + +import ( + "bytes" + "strings" +) + +// ReadTreeToIndex reads a treeish to the index +func (repo *Repository) ReadTreeToIndex(treeish string) error { + if len(treeish) != 40 { + res, err := NewCommand("rev-parse", treeish).RunInDir(repo.Path) + if err != nil { + return err + } + if len(res) > 0 { + treeish = res[:len(res)-1] + } + } + id, err := NewIDFromString(treeish) + if err != nil { + return err + } + return repo.readTreeToIndex(id) +} + +func (repo *Repository) readTreeToIndex(id SHA1) error { + _, err := NewCommand("read-tree", id.String()).RunInDir(repo.Path) + if err != nil { + return err + } + return nil +} + +// EmptyIndex empties the index +func (repo *Repository) EmptyIndex() error { + _, err := NewCommand("read-tree", "--empty").RunInDir(repo.Path) + return err +} + +// LsFiles checks if the given filenames are in the index +func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { + cmd := NewCommand("ls-files", "-z", "--") + for _, arg := range filenames { + if arg != "" { + cmd.AddArguments(arg) + } + } + res, err := cmd.RunInDirBytes(repo.Path) + if err != nil { + return nil, err + } + filelist := make([]string, 0, len(filenames)) + for _, line := range bytes.Split(res, []byte{'\000'}) { + filelist = append(filelist, string(line)) + } + + return filelist, err +} + +// RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present. +func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { + cmd := NewCommand("update-index", "--remove", "-z", "--index-info") + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + buffer := new(bytes.Buffer) + for _, file := range filenames { + if file != "" { + buffer.WriteString("0 0000000000000000000000000000000000000000\t") + buffer.WriteString(file) + buffer.WriteByte('\000') + } + } + return cmd.RunInDirFullPipeline(repo.Path, stdout, stderr, bytes.NewReader(buffer.Bytes())) +} + +// AddObjectToIndex adds the provided object hash to the index at the provided filename +func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error { + cmd := NewCommand("update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename) + _, err := cmd.RunInDir(repo.Path) + return err +} + +// WriteTree writes the current index as a tree to the object db and returns its hash +func (repo *Repository) WriteTree() (*Tree, error) { + res, err := NewCommand("write-tree").RunInDir(repo.Path) + if err != nil { + return nil, err + } + id, err := NewIDFromString(strings.TrimSpace(res)) + if err != nil { + return nil, err + } + return NewTree(repo, id), nil +} diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go index 3be8400d2221..67060e30b0b4 100644 --- a/modules/git/repo_object.go +++ b/modules/git/repo_object.go @@ -4,6 +4,12 @@ package git +import ( + "bytes" + "io" + "strings" +) + // ObjectType git object type type ObjectType string @@ -17,3 +23,24 @@ const ( // ObjectTag tag object type ObjectTag ObjectType = "tag" ) + +// HashObject takes a reader and returns SHA1 hash for that reader +func (repo *Repository) HashObject(reader io.Reader) (SHA1, error) { + idStr, err := repo.hashObject(reader) + if err != nil { + return SHA1{}, err + } + return NewIDFromString(idStr) +} + +func (repo *Repository) hashObject(reader io.Reader) (string, error) { + cmd := NewCommand("hash-object", "-w", "--stdin") + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + err := cmd.RunInDirFullPipeline(repo.Path, stdout, stderr, reader) + + if err != nil { + return "", err + } + return strings.TrimSpace(stdout.String()), nil +} diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 8bb729174428..7d32d3685ccb 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -6,6 +6,11 @@ package git import ( + "fmt" + "os" + "strings" + "time" + "gopkg.in/src-d/go-git.v4/plumbing" ) @@ -47,3 +52,48 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { treeObject.ResolvedID = resolvedID return treeObject, nil } + +// CommitTreeOpts represents the possible options to CommitTree +type CommitTreeOpts struct { + Parents []string + Message string + KeyID string + NoGPGSign bool +} + +// CommitTree creates a commit from a given tree id for the user with provided message +func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { + commitTimeStr := time.Now().Format(time.UnixDate) + + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+sig.Name, + "GIT_AUTHOR_EMAIL="+sig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+sig.Name, + "GIT_COMMITTER_EMAIL="+sig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + cmd := NewCommand("commit-tree", tree.ID.String()) + + for _, parent := range opts.Parents { + cmd.AddArguments("-p", parent) + } + + cmd.AddArguments("-m", opts.Message) + + if opts.KeyID != "" { + cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID)) + } + + if opts.NoGPGSign { + cmd.AddArguments("--no-gpg-sign") + } + + res, err := cmd.RunInDirWithEnv(repo.Path, env) + + if err != nil { + return SHA1{}, err + } + return NewIDFromString(strings.TrimSpace(res)) +} diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go index 46e03f565023..ec35628676fc 100644 --- a/modules/repofiles/temp_repo.go +++ b/modules/repofiles/temp_repo.go @@ -11,17 +11,15 @@ import ( "io" "os" "os/exec" - "path" "regexp" "strings" "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" - - "github.com/Unknwon/com" ) // TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone @@ -33,13 +31,9 @@ type TemporaryUploadRepository struct { // NewTemporaryUploadRepository creates a new temporary upload repository func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) { - timeStr := com.ToStr(time.Now().Nanosecond()) // SHOULD USE SOMETHING UNIQUE - basePath := path.Join(models.LocalCopyPath(), "upload-"+timeStr+".git") - if err := os.MkdirAll(path.Dir(basePath), os.ModePerm); err != nil { - return nil, fmt.Errorf("failed to create dir %s: %v", basePath, err) - } - if repo.RepoPath() == "" { - return nil, fmt.Errorf("no path to repository on system") + basePath, err := models.CreateTemporaryPath("upload") + if err != nil { + return nil, err } t := &TemporaryUploadRepository{repo: repo, basePath: basePath} return t, nil @@ -47,8 +41,8 @@ func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepo // Close the repository cleaning up all files func (t *TemporaryUploadRepository) Close() { - if _, err := os.Stat(t.basePath); !os.IsNotExist(err) { - os.RemoveAll(t.basePath) + if err := models.RemoveTemporaryPath(t.basePath); err != nil { + log.Error("Failed to remove temporary path %s: %v", t.basePath, err) } } @@ -282,27 +276,8 @@ func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, t // Push the provided commitHash to the repository branch by the provided user func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, branch string) error { - isWiki := "false" - if strings.HasSuffix(t.repo.Name, ".wiki") { - isWiki = "true" - } - - sig := doer.NewGitSig() - - // FIXME: Should we add SSH_ORIGINAL_COMMAND to this // Because calls hooks we need to pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+sig.Name, - "GIT_AUTHOR_EMAIL="+sig.Email, - "GIT_COMMITTER_NAME="+sig.Name, - "GIT_COMMITTER_EMAIL="+sig.Email, - models.EnvRepoName+"="+t.repo.Name, - models.EnvRepoUsername+"="+t.repo.OwnerName, - models.EnvRepoIsWiki+"="+isWiki, - models.EnvPusherName+"="+doer.Name, - models.EnvPusherID+"="+fmt.Sprintf("%d", doer.ID), - models.ProtectedBranchRepoID+"="+fmt.Sprintf("%d", t.repo.ID), - ) + env := models.PushingEnvironment(doer, t.repo) if _, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute, t.basePath, diff --git a/modules/repofiles/update_test.go b/modules/repofiles/update_test.go deleted file mode 100644 index a3a0b0a4200b..000000000000 --- a/modules/repofiles/update_test.go +++ /dev/null @@ -1,357 +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 repofiles - -import ( - "testing" - "time" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/test" - - "github.com/stretchr/testify/assert" -) - -func getCreateRepoFileOptions(repo *models.Repository) *UpdateRepoFileOptions { - return &UpdateRepoFileOptions{ - OldBranch: repo.DefaultBranch, - NewBranch: repo.DefaultBranch, - TreePath: "new/file.txt", - Message: "Creates new/file.txt", - Content: "This is a NEW file", - IsNewFile: true, - Author: nil, - Committer: nil, - } -} - -func getUpdateRepoFileOptions(repo *models.Repository) *UpdateRepoFileOptions { - return &UpdateRepoFileOptions{ - OldBranch: repo.DefaultBranch, - NewBranch: repo.DefaultBranch, - TreePath: "README.md", - Message: "Updates README.md", - SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", - Content: "This is UPDATED content for the README file", - IsNewFile: false, - Author: nil, - Committer: nil, - } -} - -func getExpectedFileResponseForCreate(commitID string) *api.FileResponse { - return &api.FileResponse{ - Content: &api.FileContentResponse{ - Name: "file.txt", - Path: "new/file.txt", - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", - Size: 18, - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/new/file.txt", - HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/new/file.txt", - GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885", - DownloadURL: "https://try.gitea.io/user2/repo1/raw/branch/master/new/file.txt", - Type: "blob", - Links: &api.FileLinksResponse{ - Self: "https://try.gitea.io/api/v1/repos/user2/repo1/contents/new/file.txt", - GitURL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/103ff9234cefeee5ec5361d22b49fbb04d385885", - HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/new/file.txt", - }, - }, - Commit: &api.FileCommitResponse{ - CommitMeta: api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/" + commitID, - SHA: commitID, - }, - HTMLURL: "https://try.gitea.io/user2/repo1/commit/" + commitID, - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "User Two", - Email: "user2@", - }, - Date: time.Now().UTC().Format(time.RFC3339), - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "User Two", - Email: "user2@", - }, - Date: time.Now().UTC().Format(time.RFC3339), - }, - Parents: []*api.CommitMeta{ - { - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", - SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - }, - }, - Message: "Updates README.md\n", - Tree: &api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", - SHA: "f93e3a1a1525fb5b91020git dda86e44810c87a2d7bc", - }, - }, - Verification: &api.PayloadCommitVerification{ - Verified: false, - Reason: "unsigned", - Signature: "", - Payload: "", - }, - } -} - -func getExpectedFileResponseForUpdate(commitID string) *api.FileResponse { - return &api.FileResponse{ - Content: &api.FileContentResponse{ - Name: "README.md", - Path: "README.md", - SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", - Size: 43, - 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/dbf8d00e022e05b7e5cf7e535de857de57925647", - DownloadURL: "https://try.gitea.io/user2/repo1/raw/branch/master/README.md", - Type: "blob", - 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/dbf8d00e022e05b7e5cf7e535de857de57925647", - HTMLURL: "https://try.gitea.io/user2/repo1/blob/master/README.md", - }, - }, - Commit: &api.FileCommitResponse{ - CommitMeta: api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/" + commitID, - SHA: commitID, - }, - HTMLURL: "https://try.gitea.io/user2/repo1/commit/" + commitID, - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "User Two", - Email: "user2@", - }, - Date: time.Now().UTC().Format(time.RFC3339), - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "User Two", - Email: "user2@", - }, - Date: time.Now().UTC().Format(time.RFC3339), - }, - Parents: []*api.CommitMeta{ - { - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d", - SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - }, - }, - Message: "Updates README.md\n", - Tree: &api.CommitMeta{ - URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/f93e3a1a1525fb5b91020da86e44810c87a2d7bc", - SHA: "f93e3a1a1525fb5b91020da86e44810c87a2d7bc", - }, - }, - Verification: &api.PayloadCommitVerification{ - Verified: false, - Reason: "unsigned", - Signature: "", - Payload: "", - }, - } -} - -func TestCreateOrUpdateRepoFileForCreate(t *testing.T) { - // setup - 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 - doer := ctx.User - opts := getCreateRepoFileOptions(repo) - - // test - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - - // asserts - assert.Nil(t, err) - gitRepo, _ := git.OpenRepository(repo.RepoPath()) - commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) - expectedFileResponse := getExpectedFileResponseForCreate(commitID) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) -} - -func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) { - // setup - 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 - doer := ctx.User - opts := getUpdateRepoFileOptions(repo) - - // test - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - - // asserts - assert.Nil(t, err) - gitRepo, _ := git.OpenRepository(repo.RepoPath()) - commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) -} - -func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) { - // setup - 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 - doer := ctx.User - opts := getUpdateRepoFileOptions(repo) - suffix := "_new" - opts.FromTreePath = "README.md" - opts.TreePath = "README.md" + suffix // new file name, README.md_new - - // test - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - - // asserts - assert.Nil(t, err) - gitRepo, _ := git.OpenRepository(repo.RepoPath()) - commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) - expectedFileResponse := getExpectedFileResponseForUpdate(commit.ID.String()) - // assert that the old file no longer exists in the last commit of the branch - fromEntry, err := commit.GetTreeEntryByPath(opts.FromTreePath) - toEntry, err := commit.GetTreeEntryByPath(opts.TreePath) - assert.Nil(t, fromEntry) // Should no longer exist here - assert.NotNil(t, toEntry) // Should exist here - // 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.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.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) -} - -// Test opts with branch names removed, should get same results as above test -func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) { - // setup - 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 - doer := ctx.User - opts := getUpdateRepoFileOptions(repo) - opts.OldBranch = "" - opts.NewBranch = "" - - // test - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - - // asserts - assert.Nil(t, err) - gitRepo, _ := git.OpenRepository(repo.RepoPath()) - commitID, _ := gitRepo.GetBranchCommitID(repo.DefaultBranch) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) -} - -func TestCreateOrUpdateRepoFileErrors(t *testing.T) { - // setup - 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 - doer := ctx.User - - t.Run("bad branch", func(t *testing.T) { - opts := getUpdateRepoFileOptions(repo) - opts.OldBranch = "bad_branch" - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Error(t, err) - assert.Nil(t, fileResponse) - expectedError := "branch does not exist [name: " + opts.OldBranch + "]" - assert.EqualError(t, err, expectedError) - }) - - t.Run("bad SHA", func(t *testing.T) { - opts := getUpdateRepoFileOptions(repo) - origSHA := opts.SHA - opts.SHA = "bad_sha" - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Nil(t, fileResponse) - assert.Error(t, err) - expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" - assert.EqualError(t, err, expectedError) - }) - - t.Run("new branch already exists", func(t *testing.T) { - opts := getUpdateRepoFileOptions(repo) - opts.NewBranch = "develop" - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Nil(t, fileResponse) - assert.Error(t, err) - expectedError := "branch already exists [name: " + opts.NewBranch + "]" - assert.EqualError(t, err, expectedError) - }) - - t.Run("treePath is empty:", func(t *testing.T) { - opts := getUpdateRepoFileOptions(repo) - opts.TreePath = "" - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Nil(t, fileResponse) - assert.Error(t, err) - expectedError := "path contains a malformed path component [path: ]" - assert.EqualError(t, err, expectedError) - }) - - t.Run("treePath is a git directory:", func(t *testing.T) { - opts := getUpdateRepoFileOptions(repo) - opts.TreePath = ".git" - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Nil(t, fileResponse) - assert.Error(t, err) - expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" - assert.EqualError(t, err, expectedError) - }) - - t.Run("create file that already exists", func(t *testing.T) { - opts := getCreateRepoFileOptions(repo) - opts.TreePath = "README.md" //already exists - fileResponse, err := CreateOrUpdateRepoFile(repo, doer, opts) - assert.Nil(t, fileResponse) - assert.Error(t, err) - expectedError := "repository file already exists [path: " + opts.TreePath + "]" - assert.EqualError(t, err, expectedError) - }) -} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index f47661efdfd3..98e3d6e82624 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -53,7 +53,6 @@ var ( // Repository local settings Local struct { LocalCopyPath string - LocalWikiPath string } `ini:"-"` // Pull request settings @@ -105,10 +104,8 @@ var ( // Repository local settings Local: struct { LocalCopyPath string - LocalWikiPath string }{ LocalCopyPath: "tmp/local-repo", - LocalWikiPath: "tmp/local-wiki", }, // Pull request settings diff --git a/routers/repo/branch.go b/routers/repo/branch.go index 8b987f0a6029..ae87aa5b3a09 100644 --- a/routers/repo/branch.go +++ b/routers/repo/branch.go @@ -74,12 +74,6 @@ func DeleteBranchPost(ctx *context.Context) { return } - // Delete branch in local copy if it exists - if err := ctx.Repo.Repository.DeleteLocalBranch(branchName); err != nil { - ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName)) - return - } - ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName)) }