mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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 <art27@cantab.net> * Remove Repository.LocalCopyPath() * Move temporary repo to use the standard temporary path * Fix the tests Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove LocalWikiPath * Fix missing remove Signed-off-by: Andrew Thornton <art27@cantab.net> * 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
This commit is contained in:
		| @@ -108,7 +108,6 @@ func runPR() { | |||||||
| 	models.LoadFixtures() | 	models.LoadFixtures() | ||||||
| 	os.RemoveAll(setting.RepoRootPath) | 	os.RemoveAll(setting.RepoRootPath) | ||||||
| 	os.RemoveAll(models.LocalCopyPath()) | 	os.RemoveAll(models.LocalCopyPath()) | ||||||
| 	os.RemoveAll(models.LocalWikiPath()) |  | ||||||
| 	com.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath) | 	com.CopyDir(path.Join(curDir, "integrations/gitea-repositories-meta"), setting.RepoRootPath) | ||||||
|  |  | ||||||
| 	log.Printf("[PR] Setting up router\n") | 	log.Printf("[PR] Setting up router\n") | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package integrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -40,7 +41,10 @@ func getExpectedFileContentResponseForFileContents(branch string) *api.FileConte | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestAPIGetFileContents(t *testing.T) { | 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 | 	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 | 	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 | 	user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User)               // owner of neither repos | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -91,125 +92,126 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestAPICreateFile(t *testing.T) { | func TestAPICreateFile(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)               // owner of the repo1 & repo16 | 		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 | 		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 | 		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 | 		repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)   // public repo | ||||||
| 	repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*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 | 		repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | ||||||
| 	fileID := 0 | 		fileID := 0 | ||||||
|  |  | ||||||
| 	// Get user2's token | 		// Get user2's token | ||||||
| 	session := loginUser(t, user2.Name) | 		session := loginUser(t, user2.Name) | ||||||
| 	token2 := getTokenForLoggedInUser(t, session) | 		token2 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
| 	// Get user4's token | 		// Get user4's token | ||||||
| 	session = loginUser(t, user4.Name) | 		session = loginUser(t, user4.Name) | ||||||
| 	token4 := getTokenForLoggedInUser(t, session) | 		token4 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
|  |  | ||||||
| 	// Test creating a file in repo1 which user2 owns, try both with branch and empty branch | 		// Test creating a file in repo1 which user2 owns, try both with branch and empty branch | ||||||
| 	for _, branch := range [...]string{ | 		for _, branch := range [...]string{ | ||||||
| 		"master", // Branch | 			"master", // Branch | ||||||
| 		"",       // Empty 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 := getCreateFileOptions() | ||||||
| 		createFileOptions.BranchName = branch | 		createFileOptions.BranchName = repo1.DefaultBranch | ||||||
|  | 		createFileOptions.NewBranchName = "new_branch" | ||||||
| 		fileID++ | 		fileID++ | ||||||
| 		treePath := fmt.Sprintf("new/file%d.txt", 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) | 		url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 		req := NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req := NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 		resp := session.MakeRequest(t, req, http.StatusCreated) | 		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 | 		var fileResponse api.FileResponse | ||||||
| 		DecodeJSON(t, resp, &fileResponse) | 		DecodeJSON(t, resp, &fileResponse) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | 		expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | 		expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | 		expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) | 		assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) | 		assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||||
| 	} | 		assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | ||||||
|  |  | ||||||
| 	// Test creating a file in a new branch | 		// Test trying to create a file that already exists, should fail | ||||||
| 	createFileOptions := getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	createFileOptions.BranchName = repo1.DefaultBranch | 		treePath = "README.md" | ||||||
| 	createFileOptions.NewBranchName = "new_branch" | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 	fileID++ | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	treePath := fmt.Sprintf("new/file%d.txt", fileID) | 		resp = session.MakeRequest(t, req, http.StatusInternalServerError) | ||||||
| 	url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		expectedAPIError := context.APIError{ | ||||||
| 	req := NewRequestWithJSON(t, "POST", url, &createFileOptions) | 			Message: "repository file already exists [path: " + treePath + "]", | ||||||
| 	resp := session.MakeRequest(t, req, http.StatusCreated) | 			URL:     base.DocURL, | ||||||
| 	var fileResponse api.FileResponse | 		} | ||||||
| 	DecodeJSON(t, resp, &fileResponse) | 		var apiError context.APIError | ||||||
| 	expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" | 		DecodeJSON(t, resp, &apiError) | ||||||
| 	expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID) | 		assert.Equal(t, expectedAPIError, apiError) | ||||||
| 	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 | 		// Test creating a file in repo1 by user4 who does not have write access | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	treePath = "README.md" | 		fileID++ | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		treePath = fmt.Sprintf("new/file%d.txt", fileID) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusInternalServerError) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	expectedAPIError := context.APIError{ | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
| 		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 | 		// Tests a repo with no token given so will fail | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("new/file%d.txt", 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) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Tests a repo with no token given so will fail | 		// Test using access token for a private repo that the user of the token owns | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("new/file%d.txt", fileID) | 		treePath = fmt.Sprintf("new/file%d.txt", fileID) | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  |  | ||||||
| 	// Test using access token for a private repo that the user of the token owns | 		// Test using org repo "user3/repo3" where user2 is a collaborator | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("new/file%d.txt", 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) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusCreated) | 		session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" where user2 is a collaborator | 		// Test using org repo "user3/repo3" with no user token | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("new/file%d.txt", 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) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusCreated) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" with no user token | 		// Test using repo "user2/repo1" where user4 is a NOT collaborator | ||||||
| 	createFileOptions = getCreateFileOptions() | 		createFileOptions = getCreateFileOptions() | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("new/file%d.txt", fileID) | 		treePath = fmt.Sprintf("new/file%d.txt", fileID) | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) | ||||||
| 	req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | 		req = NewRequestWithJSON(t, "POST", url, &createFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusForbidden) | ||||||
|  | 	}) | ||||||
| 	// 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) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package integrations | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| @@ -37,34 +38,50 @@ func getDeleteFileOptions() *api.DeleteFileOptions { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestAPIDeleteFile(t *testing.T) { | func TestAPIDeleteFile(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)               // owner of the repo1 & repo16 | 		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 | 		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 | 		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 | 		repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)   // public repo | ||||||
| 	repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*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 | 		repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | ||||||
| 	fileID := 0 | 		fileID := 0 | ||||||
|  |  | ||||||
| 	// Get user2's token | 		// Get user2's token | ||||||
| 	session := loginUser(t, user2.Name) | 		session := loginUser(t, user2.Name) | ||||||
| 	token2 := getTokenForLoggedInUser(t, session) | 		token2 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
| 	// Get user4's token | 		// Get user4's token | ||||||
| 	session = loginUser(t, user4.Name) | 		session = loginUser(t, user4.Name) | ||||||
| 	token4 := getTokenForLoggedInUser(t, session) | 		token4 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
|  |  | ||||||
| 	// Test deleting a file in repo1 which user2 owns, try both with branch and empty branch | 		// Test deleting a file in repo1 which user2 owns, try both with branch and empty branch | ||||||
| 	for _, branch := range [...]string{ | 		for _, branch := range [...]string{ | ||||||
| 		"master", // Branch | 			"master", // Branch | ||||||
| 		"",       // Empty 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++ | 		fileID++ | ||||||
| 		treePath := fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath := fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 		createFile(user2, repo1, treePath) | 		createFile(user2, repo1, treePath) | ||||||
| 		deleteFileOptions := getDeleteFileOptions() | 		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) | 		url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 		req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 		resp := session.MakeRequest(t, req, http.StatusOK) | 		resp := session.MakeRequest(t, req, http.StatusOK) | ||||||
| @@ -72,92 +89,77 @@ func TestAPIDeleteFile(t *testing.T) { | |||||||
| 		DecodeJSON(t, resp, &fileResponse) | 		DecodeJSON(t, resp, &fileResponse) | ||||||
| 		assert.NotNil(t, fileResponse) | 		assert.NotNil(t, fileResponse) | ||||||
| 		assert.Nil(t, fileResponse.Content) | 		assert.Nil(t, fileResponse.Content) | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Test deleting file and making the delete in a new branch | 		// Test deleting a file with the wrong SHA | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath := fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo1, treePath) | 		createFile(user2, repo1, treePath) | ||||||
| 	deleteFileOptions := getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	deleteFileOptions.BranchName = repo1.DefaultBranch | 		correctSHA := deleteFileOptions.SHA | ||||||
| 	deleteFileOptions.NewBranchName = "new_branch" | 		deleteFileOptions.SHA = "badsha" | ||||||
| 	url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 	req := NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	resp := session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusInternalServerError) | ||||||
| 	var fileResponse api.FileResponse | 		expectedAPIError := context.APIError{ | ||||||
| 	DecodeJSON(t, resp, &fileResponse) | 			Message: "sha does not match [given: " + deleteFileOptions.SHA + ", expected: " + correctSHA + "]", | ||||||
| 	assert.NotNil(t, fileResponse) | 			URL:     base.DocURL, | ||||||
| 	assert.Nil(t, fileResponse.Content) | 		} | ||||||
|  | 		var apiError context.APIError | ||||||
|  | 		DecodeJSON(t, resp, &apiError) | ||||||
|  | 		assert.Equal(t, expectedAPIError, apiError) | ||||||
|  |  | ||||||
| 	// Test deleting a file with the wrong SHA | 		// Test creating a file in repo1 by user4 who does not have write access | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo1, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	correctSHA := deleteFileOptions.SHA | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | ||||||
| 	deleteFileOptions.SHA = "badsha" | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
| 	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 | 		// Tests a repo with no token given so will fail | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Tests a repo with no token given so will fail | 		// Test using access token for a private repo that the user of the token owns | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusOK) | ||||||
|  |  | ||||||
| 	// Test using access token for a private repo that the user of the token owns | 		// Test using org repo "user3/repo3" where user2 is a collaborator | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user3, repo3, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusOK) | 		session.MakeRequest(t, req, http.StatusOK) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" where user2 is a collaborator | 		// Test using org repo "user3/repo3" with no user token | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user3, repo3, treePath) | 		createFile(user3, repo3, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusOK) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" with no user token | 		// Test using repo "user2/repo1" where user4 is a NOT collaborator | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("delete/file%d.txt", fileID) | 		treePath = fmt.Sprintf("delete/file%d.txt", fileID) | ||||||
| 	createFile(user3, repo3, treePath) | 		createFile(user2, repo1, treePath) | ||||||
| 	deleteFileOptions = getDeleteFileOptions() | 		deleteFileOptions = getDeleteFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) | ||||||
| 	req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | 		req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusForbidden) | ||||||
|  | 	}) | ||||||
| 	// 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) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -79,156 +80,157 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestAPIUpdateFile(t *testing.T) { | func TestAPIUpdateFile(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)               // owner of the repo1 & repo16 | 		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 | 		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 | 		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 | 		repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)   // public repo | ||||||
| 	repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*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 | 		repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo | ||||||
| 	fileID := 0 | 		fileID := 0 | ||||||
|  |  | ||||||
| 	// Get user2's token | 		// Get user2's token | ||||||
| 	session := loginUser(t, user2.Name) | 		session := loginUser(t, user2.Name) | ||||||
| 	token2 := getTokenForLoggedInUser(t, session) | 		token2 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
| 	// Get user4's token | 		// Get user4's token | ||||||
| 	session = loginUser(t, user4.Name) | 		session = loginUser(t, user4.Name) | ||||||
| 	token4 := getTokenForLoggedInUser(t, session) | 		token4 := getTokenForLoggedInUser(t, session) | ||||||
| 	session = emptyTestSession(t) | 		session = emptyTestSession(t) | ||||||
|  |  | ||||||
| 	// Test updating a file in repo1 which user2 owns, try both with branch and empty branch | 		// Test updating a file in repo1 which user2 owns, try both with branch and empty branch | ||||||
| 	for _, branch := range [...]string{ | 		for _, branch := range [...]string{ | ||||||
| 		"master", // Branch | 			"master", // Branch | ||||||
| 		"",       // Empty 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++ | 		fileID++ | ||||||
| 		treePath := fmt.Sprintf("update/file%d.txt", fileID) | 		treePath := fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 		createFile(user2, repo1, treePath) | 		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) | 		url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 		req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 		resp := session.MakeRequest(t, req, http.StatusOK) | 		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 | 		var fileResponse api.FileResponse | ||||||
| 		DecodeJSON(t, resp, &fileResponse) | 		DecodeJSON(t, resp, &fileResponse) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) | 		expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) | 		expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/update/file%d.txt", fileID) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) | 		expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) | 		assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | ||||||
| 		assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) | 		assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||||
| 	} | 		assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | ||||||
|  |  | ||||||
| 	// Test updating a file in a new branch | 		// Test updating a file and renaming it | ||||||
| 	updateFileOptions := getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	updateFileOptions.BranchName = repo1.DefaultBranch | 		updateFileOptions.BranchName = repo1.DefaultBranch | ||||||
| 	updateFileOptions.NewBranchName = "new_branch" | 		fileID++ | ||||||
| 	fileID++ | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	treePath := fmt.Sprintf("update/file%d.txt", fileID) | 		createFile(user2, repo1, treePath) | ||||||
| 	createFile(user2, repo1, treePath) | 		updateFileOptions.FromPath = treePath | ||||||
| 	url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		treePath = "rename/" + treePath | ||||||
| 	req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 	resp := session.MakeRequest(t, req, http.StatusOK) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	var fileResponse api.FileResponse | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 	DecodeJSON(t, resp, &fileResponse) | 		DecodeJSON(t, resp, &fileResponse) | ||||||
| 	expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" | 		expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" | ||||||
| 	expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/update/file%d.txt", fileID) | 		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/new_branch/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, expectedSHA, fileResponse.Content.SHA) | ||||||
| 	assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | 		assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | ||||||
| 	assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | 		assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) | ||||||
|  |  | ||||||
| 	// Test updating a file and renaming it | 		// Test updating a file with the wrong SHA | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		fileID++ | ||||||
| 	updateFileOptions.BranchName = repo1.DefaultBranch | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	fileID++ | 		createFile(user2, repo1, treePath) | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	createFile(user2, repo1, treePath) | 		correctSHA := updateFileOptions.SHA | ||||||
| 	updateFileOptions.FromPath = treePath | 		updateFileOptions.SHA = "badsha" | ||||||
| 	treePath = "rename/" + treePath | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		resp = session.MakeRequest(t, req, http.StatusInternalServerError) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		expectedAPIError := context.APIError{ | ||||||
| 	DecodeJSON(t, resp, &fileResponse) | 			Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", | ||||||
| 	expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" | 			URL:     base.DocURL, | ||||||
| 	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) | 		var apiError context.APIError | ||||||
| 	assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) | 		DecodeJSON(t, resp, &apiError) | ||||||
| 	assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL) | 		assert.Equal(t, expectedAPIError, apiError) | ||||||
| 	assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) |  | ||||||
|  |  | ||||||
| 	// Test updating a file with the wrong SHA | 		// Test creating a file in repo1 by user4 who does not have write access | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo1, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	correctSHA := updateFileOptions.SHA | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | ||||||
| 	updateFileOptions.SHA = "badsha" | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
| 	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 | 		// Tests a repo with no token given so will fail | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Tests a repo with no token given so will fail | 		// Test using access token for a private repo that the user of the token owns | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user2, repo16, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusOK) | ||||||
|  |  | ||||||
| 	// Test using access token for a private repo that the user of the token owns | 		// Test using org repo "user3/repo3" where user2 is a collaborator | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user2, repo16, treePath) | 		createFile(user3, repo3, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusOK) | 		session.MakeRequest(t, req, http.StatusOK) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" where user2 is a collaborator | 		// Test using org repo "user3/repo3" with no user token | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user3, repo3, treePath) | 		createFile(user3, repo3, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusOK) | 		session.MakeRequest(t, req, http.StatusNotFound) | ||||||
|  |  | ||||||
| 	// Test using org repo "user3/repo3" with no user token | 		// Test using repo "user2/repo1" where user4 is a NOT collaborator | ||||||
| 	fileID++ | 		fileID++ | ||||||
| 	treePath = fmt.Sprintf("update/file%d.txt", fileID) | 		treePath = fmt.Sprintf("update/file%d.txt", fileID) | ||||||
| 	createFile(user3, repo3, treePath) | 		createFile(user2, repo1, treePath) | ||||||
| 	updateFileOptions = getUpdateFileOptions() | 		updateFileOptions = getUpdateFileOptions() | ||||||
| 	url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath) | 		url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4) | ||||||
| 	req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | 		req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) | ||||||
| 	session.MakeRequest(t, req, http.StatusNotFound) | 		session.MakeRequest(t, req, http.StatusForbidden) | ||||||
|  | 	}) | ||||||
| 	// 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) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package integrations | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -14,80 +15,79 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestCreateFile(t *testing.T) { | 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 | 		doc := NewHTMLParser(t, resp.Body) | ||||||
| 	req := NewRequest(t, "GET", "/user2/repo1/_new/master/") | 		lastCommit := doc.GetInputValueByName("last_commit") | ||||||
| 	resp := session.MakeRequest(t, req, http.StatusOK) | 		assert.NotEmpty(t, lastCommit) | ||||||
|  |  | ||||||
| 	doc := NewHTMLParser(t, resp.Body) | 		// Save new file to master branch | ||||||
| 	lastCommit := doc.GetInputValueByName("last_commit") | 		req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ | ||||||
| 	assert.NotEmpty(t, lastCommit) | 			"_csrf":         doc.GetCSRF(), | ||||||
|  | 			"last_commit":   lastCommit, | ||||||
| 	// Save new file to master branch | 			"tree_path":     "test.txt", | ||||||
| 	req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ | 			"content":       "Content", | ||||||
| 		"_csrf":         doc.GetCSRF(), | 			"commit_choice": "direct", | ||||||
| 		"last_commit":   lastCommit, | 		}) | ||||||
| 		"tree_path":     "test.txt", | 		resp = session.MakeRequest(t, req, http.StatusFound) | ||||||
| 		"content":       "Content", |  | ||||||
| 		"commit_choice": "direct", |  | ||||||
| 	}) | 	}) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusFound) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestCreateFileOnProtectedBranch(t *testing.T) { | func TestCreateFileOnProtectedBranch(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
|  | 		session := loginUser(t, "user2") | ||||||
|  |  | ||||||
| 	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) | ||||||
|  |  | ||||||
| 	csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") | 		// Request editor page | ||||||
| 	// Change master branch to protected | 		req = NewRequest(t, "GET", "/user2/repo1/_new/master/") | ||||||
| 	req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 		"_csrf":     csrf, |  | ||||||
| 		"protected": "on", | 		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%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) |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder { | 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) { | func TestEditFile(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	session := loginUser(t, "user2") | 		session := loginUser(t, "user2") | ||||||
| 	testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") | 		testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n") | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestEditFileToNewBranch(t *testing.T) { | func TestEditFileToNewBranch(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	session := loginUser(t, "user2") | 		session := loginUser(t, "user2") | ||||||
| 	testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") | 		testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -178,7 +178,6 @@ func prepareTestEnv(t testing.TB, skip ...int) { | |||||||
| 	assert.NoError(t, models.LoadFixtures()) | 	assert.NoError(t, models.LoadFixtures()) | ||||||
| 	assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) | 	assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) | ||||||
| 	assert.NoError(t, os.RemoveAll(models.LocalCopyPath())) | 	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"), | 	assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), | ||||||
| 		setting.RepoRootPath)) | 		setting.RepoRootPath)) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package integrations | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -43,63 +44,65 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, titl | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullCreate(t *testing.T) { | func TestPullCreate(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		session := loginUser(t, "user1") | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		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") | 		resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") | ||||||
|  |  | ||||||
| 	// check the redirected URL | 		// check the redirected URL | ||||||
| 	url := resp.HeaderMap.Get("Location") | 		url := resp.HeaderMap.Get("Location") | ||||||
| 	assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) | 		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) | ||||||
|  |  | ||||||
| 	// check .diff can be accessed and matches performed change | 		// check .diff can be accessed and matches performed change | ||||||
| 	req := NewRequest(t, "GET", url+".diff") | 		req := NewRequest(t, "GET", url+".diff") | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 	assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) | 		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) | ||||||
| 	assert.Regexp(t, "^diff", resp.Body) | 		assert.Regexp(t, "^diff", resp.Body) | ||||||
| 	assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one | 		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one | ||||||
|  |  | ||||||
| 	// check .patch can be accessed and matches performed change | 		// check .patch can be accessed and matches performed change | ||||||
| 	req = NewRequest(t, "GET", url+".patch") | 		req = NewRequest(t, "GET", url+".patch") | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 	assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) | 		assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) | ||||||
| 	assert.Regexp(t, "diff", resp.Body) | 		assert.Regexp(t, "diff", resp.Body) | ||||||
| 	assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) | 		assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) | ||||||
| 	assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one | 		assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullCreate_TitleEscape(t *testing.T) { | func TestPullCreate_TitleEscape(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		session := loginUser(t, "user1") | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		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", "<i>XSS PR</i>") | 		resp := testPullCreate(t, session, "user1", "repo1", "master", "<i>XSS PR</i>") | ||||||
|  |  | ||||||
| 	// check the redirected URL | 		// check the redirected URL | ||||||
| 	url := resp.HeaderMap.Get("Location") | 		url := resp.HeaderMap.Get("Location") | ||||||
| 	assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) | 		assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) | ||||||
|  |  | ||||||
| 	// Edit title | 		// Edit title | ||||||
| 	req := NewRequest(t, "GET", url) | 		req := NewRequest(t, "GET", url) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 	htmlDoc := NewHTMLParser(t, resp.Body) | 		htmlDoc := NewHTMLParser(t, resp.Body) | ||||||
| 	editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url") | 		editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url") | ||||||
| 	assert.True(t, exists, "The template has changed") | 		assert.True(t, exists, "The template has changed") | ||||||
|  |  | ||||||
| 	req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{ | 		req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{ | ||||||
| 		"_csrf": htmlDoc.GetCSRF(), | 			"_csrf": htmlDoc.GetCSRF(), | ||||||
| 		"title": "<u>XSS PR</u>", | 			"title": "<u>XSS PR</u>", | ||||||
|  | 		}) | ||||||
|  | 		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) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package integrations | |||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -52,108 +53,118 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullMerge(t *testing.T) { | func TestPullMerge(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		session := loginUser(t, "user1") | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		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") | 		resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") | ||||||
|  |  | ||||||
| 	elem := strings.Split(test.RedirectURL(resp), "/") | 		elem := strings.Split(test.RedirectURL(resp), "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
| 	testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) | 		testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullRebase(t *testing.T) { | func TestPullRebase(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		session := loginUser(t, "user1") | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		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") | 		resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") | ||||||
|  |  | ||||||
| 	elem := strings.Split(test.RedirectURL(resp), "/") | 		elem := strings.Split(test.RedirectURL(resp), "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
| 	testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebase) | 		testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebase) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullRebaseMerge(t *testing.T) { | func TestPullRebaseMerge(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		prepareTestEnv(t) | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		session := loginUser(t, "user1") | ||||||
| 	testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") | 		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), "/") | 		elem := strings.Split(test.RedirectURL(resp), "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
| 	testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebaseMerge) | 		testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebaseMerge) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullSquash(t *testing.T) { | func TestPullSquash(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		prepareTestEnv(t) | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		session := loginUser(t, "user1") | ||||||
| 	testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") | 		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") | ||||||
|  | 		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), "/") | 		elem := strings.Split(test.RedirectURL(resp), "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
| 	testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) | 		testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestPullCleanUpAfterMerge(t *testing.T) { | func TestPullCleanUpAfterMerge(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		prepareTestEnv(t) | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		session := loginUser(t, "user1") | ||||||
| 	testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") | 		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), "/") | 		elem := strings.Split(test.RedirectURL(resp), "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
| 	testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) | 		testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) | ||||||
|  |  | ||||||
| 	// Check PR branch deletion | 		// Check PR branch deletion | ||||||
| 	resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) | 		resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) | ||||||
| 	respJSON := struct { | 		respJSON := struct { | ||||||
| 		Redirect string | 			Redirect string | ||||||
| 	}{} | 		}{} | ||||||
| 	DecodeJSON(t, resp, &respJSON) | 		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, "/") | 		elem = strings.Split(respJSON.Redirect, "/") | ||||||
| 	assert.EqualValues(t, "pulls", elem[3]) | 		assert.EqualValues(t, "pulls", elem[3]) | ||||||
|  |  | ||||||
| 	// Check branch deletion result | 		// Check branch deletion result | ||||||
| 	req := NewRequest(t, "GET", respJSON.Redirect) | 		req := NewRequest(t, "GET", respJSON.Redirect) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
|  |  | ||||||
| 	htmlDoc := NewHTMLParser(t, resp.Body) | 		htmlDoc := NewHTMLParser(t, resp.Body) | ||||||
| 	resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() | 		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) { | func TestCantMergeWorkInProgress(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		prepareTestEnv(t) | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		session := loginUser(t, "user1") | ||||||
| 	testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") | 		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") | 		resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title") | ||||||
|  |  | ||||||
| 	req := NewRequest(t, "GET", resp.Header().Get("Location")) | 		req := NewRequest(t, "GET", resp.Header().Get("Location")) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 	htmlDoc := NewHTMLParser(t, resp.Body) | 		htmlDoc := NewHTMLParser(t, resp.Body) | ||||||
| 	text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text()) | 		text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text()) | ||||||
| 	assert.NotEmpty(t, text, "Can't find WIP text") | 		assert.NotEmpty(t, text, "Can't find WIP text") | ||||||
|  |  | ||||||
| 	// remove <strong /> from lang | 		// remove <strong /> from lang | ||||||
| 	expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]") | 		expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]") | ||||||
| 	replacer := strings.NewReplacer("<strong>", "", "</strong>", "") | 		replacer := strings.NewReplacer("<strong>", "", "</strong>", "") | ||||||
| 	assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text") | 		assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text") | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package integrations | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -16,78 +17,79 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestPullCreate_CommitStatus(t *testing.T) { | func TestPullCreate_CommitStatus(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, u *url.URL) { | ||||||
| 	session := loginUser(t, "user1") | 		session := loginUser(t, "user1") | ||||||
| 	testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | 		testRepoFork(t, session, "user2", "repo1", "user1", "repo1") | ||||||
| 	testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") | 		testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") | ||||||
|  |  | ||||||
| 	url := path.Join("user1", "repo1", "compare", "master...status1") | 		url := path.Join("user1", "repo1", "compare", "master...status1") | ||||||
| 	req := NewRequestWithValues(t, "POST", url, | 		req := NewRequestWithValues(t, "POST", url, | ||||||
| 		map[string]string{ | 			map[string]string{ | ||||||
| 			"_csrf": GetCSRF(t, session, url), | 				"_csrf": GetCSRF(t, session, url), | ||||||
| 			"title": "pull request from status1", | 				"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", |  | ||||||
| 			}, | 			}, | ||||||
| 		) | 		) | ||||||
| 		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) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
| 		doc = NewHTMLParser(t, resp.Body) | 		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.True(t, exists) | ||||||
| 		assert.NotEmpty(t, commitURL) | 		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") | 		commitID := path.Base(commitURL) | ||||||
| 		assert.True(t, ok) |  | ||||||
| 		assert.EqualValues(t, "commit-status "+statesIcons[status], cls) | 		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) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package integrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| @@ -16,49 +17,51 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestRepoActivity(t *testing.T) { | func TestRepoActivity(t *testing.T) { | ||||||
| 	prepareTestEnv(t) | 	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | ||||||
| 	session := loginUser(t, "user1") |  | ||||||
|  |  | ||||||
| 	// Create PRs (1 merged & 2 proposed) | 		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") |  | ||||||
| 	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") | 		// Create PRs (1 merged & 2 proposed) | ||||||
| 	testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") | 		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/much_better_readme", "README.md", "Hello, World (Edited More)\n") | 		testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") | ||||||
| 	testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") | 		testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") | ||||||
|  |  | ||||||
| 	// Create issues (3 new issues) | 		testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") | ||||||
| 	testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") | 		testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") | ||||||
| 	testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2") |  | ||||||
| 	testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3") |  | ||||||
|  |  | ||||||
| 	// Create releases (1 new release) | 		// Create issues (3 new issues) | ||||||
| 	createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) | 		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") | ||||||
|  |  | ||||||
| 	// Open Activity page and check stats | 		// Create releases (1 new release) | ||||||
| 	req := NewRequest(t, "GET", "/user2/repo1/activity") | 		createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) | ||||||
| 	resp = session.MakeRequest(t, req, http.StatusOK) |  | ||||||
| 	htmlDoc := NewHTMLParser(t, resp.Body) |  | ||||||
|  |  | ||||||
| 	// Should be 1 published release | 		// Open Activity page and check stats | ||||||
| 	list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") | 		req := NewRequest(t, "GET", "/user2/repo1/activity") | ||||||
| 	assert.Len(t, list.Nodes, 1) | 		resp = session.MakeRequest(t, req, http.StatusOK) | ||||||
|  | 		htmlDoc := NewHTMLParser(t, resp.Body) | ||||||
|  |  | ||||||
| 	// Should be 1 merged pull request | 		// Should be 1 published release | ||||||
| 	list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") | 		list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") | ||||||
| 	assert.Len(t, list.Nodes, 1) | 		assert.Len(t, list.Nodes, 1) | ||||||
|  |  | ||||||
| 	// Should be 2 merged proposed pull requests | 		// Should be 1 merged pull request | ||||||
| 	list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") | 		list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") | ||||||
| 	assert.Len(t, list.Nodes, 2) | 		assert.Len(t, list.Nodes, 1) | ||||||
|  |  | ||||||
| 	// Should be 3 new issues | 		// Should be 2 merged proposed pull requests | ||||||
| 	list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") | 		list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") | ||||||
| 	assert.Len(t, list.Nodes, 3) | 		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) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package integrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -35,6 +36,10 @@ func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubU | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestCreateBranch(t *testing.T) { | func TestCreateBranch(t *testing.T) { | ||||||
|  | 	onGiteaRun(t, testCreateBranches) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func testCreateBranches(t *testing.T, giteaURL *url.URL) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		OldRefSubURL   string | 		OldRefSubURL   string | ||||||
| 		NewBranch      string | 		NewBranch      string | ||||||
|   | |||||||
| @@ -2,20 +2,22 @@ | |||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
| 
 | 
 | ||||||
| package repofiles | package integrations | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"net/url" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/repofiles" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func getDeleteRepoFileOptions(repo *models.Repository) *DeleteRepoFileOptions { | func getDeleteRepoFileOptions(repo *models.Repository) *repofiles.DeleteRepoFileOptions { | ||||||
| 	return &DeleteRepoFileOptions{ | 	return &repofiles.DeleteRepoFileOptions{ | ||||||
| 		LastCommitID: "", | 		LastCommitID: "", | ||||||
| 		OldBranch:    repo.DefaultBranch, | 		OldBranch:    repo.DefaultBranch, | ||||||
| 		NewBranch:    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{ | 	return &api.FileResponse{ | ||||||
| 		Content: nil, | 		Content: nil, | ||||||
| 		Commit: &api.FileCommitResponse{ | 		Commit: &api.FileCommitResponse{ | ||||||
| 			CommitMeta: api.CommitMeta{ | 			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", | 				SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", | ||||||
| 			}, | 			}, | ||||||
| 			HTMLURL: "https://try.gitea.io/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", | 			HTMLURL: u.String() + "user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", | ||||||
| 			Author: &api.CommitUser{ | 			Author: &api.CommitUser{ | ||||||
| 				Identity: api.Identity{ | 				Identity: api.Identity{ | ||||||
| 					Name:  "user1", | 					Name:  "user1", | ||||||
| @@ -53,7 +55,7 @@ func getExpectedDeleteFileResponse() *api.FileResponse { | |||||||
| 			Parents: []*api.CommitMeta{}, | 			Parents: []*api.CommitMeta{}, | ||||||
| 			Message: "Initial commit\n", | 			Message: "Initial commit\n", | ||||||
| 			Tree: &api.CommitMeta{ | 			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", | 				SHA: "2a2f1d4670728a2e10049e345bd7a276468beab6", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| @@ -67,6 +69,10 @@ func getExpectedDeleteFileResponse() *api.FileResponse { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestDeleteRepoFile(t *testing.T) { | func TestDeleteRepoFile(t *testing.T) { | ||||||
|  | 	onGiteaRun(t, testDeleteRepoFile) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testDeleteRepoFile(t *testing.T, u *url.URL) { | ||||||
| 	// setup | 	// setup | ||||||
| 	models.PrepareTestEnv(t) | 	models.PrepareTestEnv(t) | ||||||
| 	ctx := test.MockContext(t, "user2/repo1") | 	ctx := test.MockContext(t, "user2/repo1") | ||||||
| @@ -80,14 +86,14 @@ func TestDeleteRepoFile(t *testing.T) { | |||||||
| 	opts := getDeleteRepoFileOptions(repo) | 	opts := getDeleteRepoFileOptions(repo) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Delete README.md file", func(t *testing.T) { | 	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) | 		assert.Nil(t, err) | ||||||
| 		expectedFileResponse := getExpectedDeleteFileResponse() | 		expectedFileResponse := getExpectedDeleteFileResponse(u) | ||||||
| 		assert.EqualValues(t, expectedFileResponse, fileResponse) | 		assert.EqualValues(t, expectedFileResponse, fileResponse) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Verify README.md has been deleted", func(t *testing.T) { | 	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) | 		assert.Nil(t, fileResponse) | ||||||
| 		expectedError := "repository file does not exist [path: " + opts.TreePath + "]" | 		expectedError := "repository file does not exist [path: " + opts.TreePath + "]" | ||||||
| 		assert.EqualError(t, err, expectedError) | 		assert.EqualError(t, err, expectedError) | ||||||
| @@ -96,6 +102,10 @@ func TestDeleteRepoFile(t *testing.T) { | |||||||
| 
 | 
 | ||||||
| // Test opts with branch names removed, same results | // Test opts with branch names removed, same results | ||||||
| func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { | func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { | ||||||
|  | 	onGiteaRun(t, testDeleteRepoFileWithoutBranchNames) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) { | ||||||
| 	// setup | 	// setup | ||||||
| 	models.PrepareTestEnv(t) | 	models.PrepareTestEnv(t) | ||||||
| 	ctx := test.MockContext(t, "user2/repo1") | 	ctx := test.MockContext(t, "user2/repo1") | ||||||
| @@ -111,9 +121,9 @@ func TestDeleteRepoFileWithoutBranchNames(t *testing.T) { | |||||||
| 	opts.NewBranch = "" | 	opts.NewBranch = "" | ||||||
| 
 | 
 | ||||||
| 	t.Run("Delete README.md without Branch Name", func(t *testing.T) { | 	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) | 		assert.Nil(t, err) | ||||||
| 		expectedFileResponse := getExpectedDeleteFileResponse() | 		expectedFileResponse := getExpectedDeleteFileResponse(u) | ||||||
| 		assert.EqualValues(t, expectedFileResponse, fileResponse) | 		assert.EqualValues(t, expectedFileResponse, fileResponse) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @@ -133,7 +143,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { | |||||||
| 	t.Run("Bad branch", func(t *testing.T) { | 	t.Run("Bad branch", func(t *testing.T) { | ||||||
| 		opts := getDeleteRepoFileOptions(repo) | 		opts := getDeleteRepoFileOptions(repo) | ||||||
| 		opts.OldBranch = "bad_branch" | 		opts.OldBranch = "bad_branch" | ||||||
| 		fileResponse, err := DeleteRepoFile(repo, doer, opts) | 		fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		assert.Nil(t, fileResponse) | 		assert.Nil(t, fileResponse) | ||||||
| 		expectedError := "branch does not exist [name: " + opts.OldBranch + "]" | 		expectedError := "branch does not exist [name: " + opts.OldBranch + "]" | ||||||
| @@ -144,7 +154,7 @@ func TestDeleteRepoFileErrors(t *testing.T) { | |||||||
| 		opts := getDeleteRepoFileOptions(repo) | 		opts := getDeleteRepoFileOptions(repo) | ||||||
| 		origSHA := opts.SHA | 		origSHA := opts.SHA | ||||||
| 		opts.SHA = "bad_sha" | 		opts.SHA = "bad_sha" | ||||||
| 		fileResponse, err := DeleteRepoFile(repo, doer, opts) | 		fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) | ||||||
| 		assert.Nil(t, fileResponse) | 		assert.Nil(t, fileResponse) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]" | 		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) { | 	t.Run("New branch already exists", func(t *testing.T) { | ||||||
| 		opts := getDeleteRepoFileOptions(repo) | 		opts := getDeleteRepoFileOptions(repo) | ||||||
| 		opts.NewBranch = "develop" | 		opts.NewBranch = "develop" | ||||||
| 		fileResponse, err := DeleteRepoFile(repo, doer, opts) | 		fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) | ||||||
| 		assert.Nil(t, fileResponse) | 		assert.Nil(t, fileResponse) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		expectedError := "branch already exists [name: " + opts.NewBranch + "]" | 		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) { | 	t.Run("TreePath is empty:", func(t *testing.T) { | ||||||
| 		opts := getDeleteRepoFileOptions(repo) | 		opts := getDeleteRepoFileOptions(repo) | ||||||
| 		opts.TreePath = "" | 		opts.TreePath = "" | ||||||
| 		fileResponse, err := DeleteRepoFile(repo, doer, opts) | 		fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) | ||||||
| 		assert.Nil(t, fileResponse) | 		assert.Nil(t, fileResponse) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		expectedError := "path contains a malformed path component [path: ]" | 		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) { | 	t.Run("TreePath is a git directory:", func(t *testing.T) { | ||||||
| 		opts := getDeleteRepoFileOptions(repo) | 		opts := getDeleteRepoFileOptions(repo) | ||||||
| 		opts.TreePath = ".git" | 		opts.TreePath = ".git" | ||||||
| 		fileResponse, err := DeleteRepoFile(repo, doer, opts) | 		fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts) | ||||||
| 		assert.Nil(t, fileResponse) | 		assert.Nil(t, fileResponse) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" | 		expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]" | ||||||
							
								
								
									
										365
									
								
								integrations/repofiles_update_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								integrations/repofiles_update_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								models/helper_directory.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								models/helper_directory.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								models/helper_environment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								models/helper_environment.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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", | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -418,22 +418,21 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
| 		go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) | 		go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
|  | 	// Clone base repo. | ||||||
|  | 	tmpBasePath, err := CreateTemporaryPath("merge") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer RemoveTemporaryPath(tmpBasePath) | ||||||
|  |  | ||||||
| 	headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | 	headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | ||||||
|  |  | ||||||
| 	// Clone base repo. | 	if err := git.Clone(baseGitRepo.Path, tmpBasePath, git.CloneRepoOptions{ | ||||||
| 	tmpBasePath := path.Join(LocalCopyPath(), "merge-"+com.ToStr(time.Now().Nanosecond())+".git") | 		Shared:     true, | ||||||
|  | 		NoCheckout: true, | ||||||
| 	if err := os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm); err != nil { | 		Branch:     pr.BaseBranch, | ||||||
| 		return fmt.Errorf("Failed to create dir %s: %v", tmpBasePath, err) | 	}); err != nil { | ||||||
| 	} | 		return fmt.Errorf("git clone: %v", err) | ||||||
|  |  | ||||||
| 	defer os.RemoveAll(tmpBasePath) |  | ||||||
|  |  | ||||||
| 	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) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	remoteRepoName := "head_repo" | 	remoteRepoName := "head_repo" | ||||||
| @@ -456,14 +455,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
| 	if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { | 	if err := addCacheRepo(tmpBasePath, headRepoPath); err != nil { | ||||||
| 		return fmt.Errorf("addCacheRepo [%s -> %s]: %v", headRepoPath, tmpBasePath, err) | 		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), | 		fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath), | ||||||
| 		"git", "remote", "add", remoteRepoName, headRepoPath); err != nil { | 		"git", "remote", "add", remoteRepoName, headRepoPath); err != nil { | ||||||
| 		return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 		return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Fetch head branch | 	// 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), | 		fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath), | ||||||
| 		"git", "fetch", remoteRepoName); err != nil { | 		"git", "fetch", remoteRepoName); err != nil { | ||||||
| 		return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 		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) | 		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), | 		fmt.Sprintf("PullRequest.Merge (git config): %s", tmpBasePath), | ||||||
| 		"git", "config", "--local", "core.sparseCheckout", "true"); err != nil { | 		"git", "config", "--local", "core.sparseCheckout", "true"); err != nil { | ||||||
| 		return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr) | 		return fmt.Errorf("git config [core.sparsecheckout -> true]: %v", stderr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Read base branch index | 	// 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), | 		fmt.Sprintf("PullRequest.Merge (git read-tree): %s", tmpBasePath), | ||||||
| 		"git", "read-tree", "HEAD"); err != nil { | 		"git", "read-tree", "HEAD"); err != nil { | ||||||
| 		return fmt.Errorf("git read-tree HEAD: %s", stderr) | 		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. | 	// Merge commits. | ||||||
| 	switch mergeStyle { | 	switch mergeStyle { | ||||||
| 	case MergeStyleMerge: | 	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), | 			fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), | ||||||
| 			"git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil { | 			"git", "merge", "--no-ff", "--no-commit", trackingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) | 			return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		sig := doer.NewGitSig() | 		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), | 			fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), | ||||||
| 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | ||||||
| 			"-m", message); err != nil { | 			"-m", message); err != nil { | ||||||
| @@ -518,50 +517,50 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
| 		} | 		} | ||||||
| 	case MergeStyleRebase: | 	case MergeStyleRebase: | ||||||
| 		// Checkout head branch | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), | ||||||
| 			"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { | 			"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git checkout: %s", stderr) | 			return fmt.Errorf("git checkout: %s", stderr) | ||||||
| 		} | 		} | ||||||
| 		// Rebase before merging | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), | ||||||
| 			"git", "rebase", "-q", pr.BaseBranch); err != nil { | 			"git", "rebase", "-q", pr.BaseBranch); err != nil { | ||||||
| 			return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 			return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | ||||||
| 		} | 		} | ||||||
| 		// Checkout base branch again | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), | ||||||
| 			"git", "checkout", pr.BaseBranch); err != nil { | 			"git", "checkout", pr.BaseBranch); err != nil { | ||||||
| 			return fmt.Errorf("git checkout: %s", stderr) | 			return fmt.Errorf("git checkout: %s", stderr) | ||||||
| 		} | 		} | ||||||
| 		// Merge fast forward | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), | ||||||
| 			"git", "merge", "--ff-only", "-q", stagingBranch); err != nil { | 			"git", "merge", "--ff-only", "-q", stagingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 			return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | ||||||
| 		} | 		} | ||||||
| 	case MergeStyleRebaseMerge: | 	case MergeStyleRebaseMerge: | ||||||
| 		// Checkout head branch | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), | ||||||
| 			"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { | 			"git", "checkout", "-b", stagingBranch, trackingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git checkout: %s", stderr) | 			return fmt.Errorf("git checkout: %s", stderr) | ||||||
| 		} | 		} | ||||||
| 		// Rebase before merging | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), | ||||||
| 			"git", "rebase", "-q", pr.BaseBranch); err != nil { | 			"git", "rebase", "-q", pr.BaseBranch); err != nil { | ||||||
| 			return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 			return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | ||||||
| 		} | 		} | ||||||
| 		// Checkout base branch again | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), | ||||||
| 			"git", "checkout", pr.BaseBranch); err != nil { | 			"git", "checkout", pr.BaseBranch); err != nil { | ||||||
| 			return fmt.Errorf("git checkout: %s", stderr) | 			return fmt.Errorf("git checkout: %s", stderr) | ||||||
| 		} | 		} | ||||||
| 		// Prepare merge with commit | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), | ||||||
| 			"git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil { | 			"git", "merge", "--no-ff", "--no-commit", "-q", stagingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git merge --no-ff [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 			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 | 		// Set custom message and author and create merge commit | ||||||
| 		sig := doer.NewGitSig() | 		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), | 			fmt.Sprintf("PullRequest.Merge (git commit): %s", tmpBasePath), | ||||||
| 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | ||||||
| 			"-m", message); err != nil { | 			"-m", message); err != nil { | ||||||
| @@ -578,13 +577,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
|  |  | ||||||
| 	case MergeStyleSquash: | 	case MergeStyleSquash: | ||||||
| 		// Merge with squash | 		// 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), | 			fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), | ||||||
| 			"git", "merge", "-q", "--squash", trackingBranch); err != nil { | 			"git", "merge", "-q", "--squash", trackingBranch); err != nil { | ||||||
| 			return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | 			return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) | ||||||
| 		} | 		} | ||||||
| 		sig := pr.Issue.Poster.NewGitSig() | 		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), | 			fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), | ||||||
| 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | 			"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), | ||||||
| 			"-m", message); err != nil { | 			"-m", message); err != nil { | ||||||
| @@ -594,10 +593,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
| 		return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} | 		return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	env := PushingEnvironment(doer, pr.BaseRepo) | ||||||
|  |  | ||||||
| 	// Push back to upstream. | 	// 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), | 		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) | 		return fmt.Errorf("git push: %s", stderr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -518,7 +518,7 @@ func (repo *Repository) DeleteWiki() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (repo *Repository) deleteWiki(e Engine) error { | func (repo *Repository) deleteWiki(e Engine) error { | ||||||
| 	wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} | 	wikiPaths := []string{repo.WikiPath()} | ||||||
| 	for _, wikiPath := range wikiPaths { | 	for _, wikiPath := range wikiPaths { | ||||||
| 		removeAllWithNotice(e, "Delete repository wiki", wikiPath) | 		removeAllWithNotice(e, "Delete repository wiki", wikiPath) | ||||||
| 	} | 	} | ||||||
| @@ -749,56 +749,6 @@ func (repo *Repository) DescriptionHTML() template.HTML { | |||||||
| 	return template.HTML(markup.Sanitize(string(desc))) | 	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. | // PatchPath returns corresponding patch file path of repository by given issue ID. | ||||||
| func (repo *Repository) PatchPath(index int64) (string, error) { | func (repo *Repository) PatchPath(index int64) (string, error) { | ||||||
| 	return repo.patchPath(x, index) | 	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 { | 	if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { | ||||||
| 		return fmt.Errorf("rename repository directory: %v", err) | 		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. | 	// Rename remote wiki repository to new path and delete local copy. | ||||||
| 	wikiPath := WikiPath(owner.Name, repo.Name) | 	wikiPath := WikiPath(owner.Name, repo.Name) | ||||||
| 	if com.IsExist(wikiPath) { | 	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 { | 		if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { | ||||||
| 			return fmt.Errorf("rename repository wiki: %v", err) | 			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) | 		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() | 	wikiPath := repo.WikiPath() | ||||||
| 	if com.IsExist(wikiPath) { | 	if com.IsExist(wikiPath) { | ||||||
| 		if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { | 		if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { | ||||||
| 			return fmt.Errorf("rename repository wiki: %v", err) | 			return fmt.Errorf("rename repository wiki: %v", err) | ||||||
| 		} | 		} | ||||||
| 		RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	sess := x.NewSession() | 	sess := x.NewSession() | ||||||
|   | |||||||
| @@ -7,86 +7,11 @@ package models | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // 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. | // CanCreateBranch returns true if repository meets the requirements for creating new branches. | ||||||
| func (repo *Repository) CanCreateBranch() bool { | func (repo *Repository) CanCreateBranch() bool { | ||||||
| 	return !repo.IsMirror | 	return !repo.IsMirror | ||||||
| @@ -137,29 +62,44 @@ func (repo *Repository) CheckBranchName(name string) error { | |||||||
|  |  | ||||||
| // CreateNewBranch creates a new repository branch | // CreateNewBranch creates a new repository branch | ||||||
| func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) { | 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 | 	// Check if branch name can be used | ||||||
| 	if err := repo.CheckBranchName(branchName); err != nil { | 	if err := repo.CheckBranchName(branchName); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	localPath := repo.LocalCopyPath() | 	if !git.IsBranchExist(repo.RepoPath(), oldBranchName) { | ||||||
|  | 		return fmt.Errorf("OldBranch: %s does not exist. Cannot create new branch from this", oldBranchName) | ||||||
| 	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 err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil { | 	basePath, err := CreateTemporaryPath("branch-maker") | ||||||
| 		return fmt.Errorf("CreateNewBranch: %v", err) | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer RemoveTemporaryPath(basePath) | ||||||
|  |  | ||||||
|  | 	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 = git.Push(localPath, git.PushOptions{ | 	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.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.Push(basePath, git.PushOptions{ | ||||||
| 		Remote: "origin", | 		Remote: "origin", | ||||||
| 		Branch: branchName, | 		Branch: branchName, | ||||||
|  | 		Env:    PushingEnvironment(doer, repo), | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		return fmt.Errorf("Push: %v", err) | 		return fmt.Errorf("Push: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -167,62 +107,41 @@ func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName st | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // 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 := git.Checkout(localPath, git.CheckoutOptions{ |  | ||||||
| 		Branch: commit, |  | ||||||
| 	}); err != nil { |  | ||||||
| 		return fmt.Errorf("git checkout %s: %v", commit, 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) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CreateNewBranchFromCommit creates a new repository branch | // CreateNewBranchFromCommit creates a new repository branch | ||||||
| func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName string) (err error) { | 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 | 	// Check if branch name can be used | ||||||
| 	if err := repo.CheckBranchName(branchName); err != nil { | 	if err := repo.CheckBranchName(branchName); err != nil { | ||||||
| 		return err | 		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, | ||||||
| 	if err = repo.updateLocalCopyToCommit(commit); err != nil { | 		Shared: true, | ||||||
| 		return fmt.Errorf("UpdateLocalCopyBranch: %v", err) | 	}); 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.CheckoutNewBranch(commit, branchName); err != nil { | 	gitRepo, err := git.OpenRepository(basePath) | ||||||
| 		return fmt.Errorf("CheckoutNewBranch: %v", err) | 	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 = git.Push(localPath, git.PushOptions{ | 	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(basePath, git.PushOptions{ | ||||||
| 		Remote: "origin", | 		Remote: "origin", | ||||||
| 		Branch: branchName, | 		Branch: branchName, | ||||||
|  | 		Env:    PushingEnvironment(doer, repo), | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		return fmt.Errorf("Push: %v", err) | 		return fmt.Errorf("Push: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,11 +5,9 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"path" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/markup" | 	"code.gitea.io/gitea/modules/markup" | ||||||
| 	"code.gitea.io/gitea/modules/setting" |  | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" | 	"github.com/Unknwon/com" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"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()) | 	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) { | func TestTransferOwnership(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -943,17 +943,6 @@ func ChangeUserName(u *User, newUserName string) (err error) { | |||||||
| 		return fmt.Errorf("ChangeUsernameInPullRequests: %v", err) | 		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 | 	// Do not fail if directory does not exist | ||||||
| 	if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { | 	if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { | ||||||
| 		return fmt.Errorf("Rename user directory: %v", err) | 		return fmt.Errorf("Rename user directory: %v", err) | ||||||
|   | |||||||
							
								
								
									
										231
									
								
								models/wiki.go
									
									
									
									
									
								
							
							
						
						
									
										231
									
								
								models/wiki.go
									
									
									
									
									
								
							| @@ -6,15 +6,13 @@ package models | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/sync" | 	"code.gitea.io/gitea/modules/sync" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" | 	"github.com/Unknwon/com" | ||||||
| @@ -89,34 +87,6 @@ func (repo *Repository) InitWiki() error { | |||||||
| 	return nil | 	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 | // nameAllowed checks if a wiki name is allowed | ||||||
| func nameAllowed(name string) error { | func nameAllowed(name string) error { | ||||||
| 	for _, reservedName := range reservedWikiNames { | 	for _, reservedName := range reservedWikiNames { | ||||||
| @@ -132,7 +102,6 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con | |||||||
| 	if err = nameAllowed(newWikiName); err != nil { | 	if err = nameAllowed(newWikiName); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) | 	wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) | ||||||
| 	defer wikiWorkingPool.CheckOut(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) | 		return fmt.Errorf("InitWiki: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	localPath := repo.LocalWikiPath() | 	hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master") | ||||||
| 	if err = discardLocalWikiChanges(localPath); err != nil { |  | ||||||
| 		return fmt.Errorf("discardLocalWikiChanges: %v", err) | 	basePath, err := CreateTemporaryPath("update-wiki") | ||||||
| 	} else if err = repo.updateLocalWiki(); err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("UpdateLocalWiki: %v", err) | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer RemoveTemporaryPath(basePath) | ||||||
|  |  | ||||||
|  | 	cloneOpts := git.CloneRepoOptions{ | ||||||
|  | 		Bare:   true, | ||||||
|  | 		Shared: true, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	newWikiPath := path.Join(localPath, WikiNameToFilename(newWikiName)) | 	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 isNew { | ||||||
| 		if com.IsExist(newWikiPath) { | 		filesInIndex, err := gitRepo.LsFiles(newWikiPath) | ||||||
| 			return ErrWikiAlreadyExist{newWikiPath} | 		if err != nil { | ||||||
|  | 			log.Error("%v", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		for _, file := range filesInIndex { | ||||||
|  | 			if file == newWikiPath { | ||||||
|  | 				return ErrWikiAlreadyExist{newWikiPath} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		oldWikiPath := path.Join(localPath, WikiNameToFilename(oldWikiName)) | 		oldWikiPath := WikiNameToFilename(oldWikiName) | ||||||
| 		if err := os.Remove(oldWikiPath); err != nil { | 		filesInIndex, err := gitRepo.LsFiles(oldWikiPath) | ||||||
| 			return fmt.Errorf("Failed to remove %s: %v", oldWikiPath, err) | 		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, | 	// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here | ||||||
| 	// attack content can be written to the target file (e.g. authorized_keys2) |  | ||||||
| 	// as a new page operation. | 	objectHash, err := gitRepo.HashObject(strings.NewReader(content)) | ||||||
| 	// So we want to make sure the symlink is removed before write anything. | 	if err != nil { | ||||||
| 	// The new file we created will be in normal text format. | 		log.Error("%v", err) | ||||||
| 	if err = os.RemoveAll(newWikiPath); err != nil { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = ioutil.WriteFile(newWikiPath, []byte(content), 0666); err != nil { | 	if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil { | ||||||
| 		return fmt.Errorf("WriteFile: %v", err) | 		log.Error("%v", err) | ||||||
|  | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(message) == 0 { | 	tree, err := gitRepo.WriteTree() | ||||||
| 		message = "Update page '" + newWikiName + "'" | 	if err != nil { | ||||||
|  | 		log.Error("%v", err) | ||||||
|  | 		return err | ||||||
| 	} | 	} | ||||||
| 	if err = git.AddChanges(localPath, true); err != nil { |  | ||||||
| 		return fmt.Errorf("AddChanges: %v", err) | 	commitTreeOpts := git.CommitTreeOpts{ | ||||||
| 	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ | 		Message: message, | ||||||
| 		Committer: doer.NewGitSig(), | 	} | ||||||
| 		Message:   message, | 	if hasMasterBranch { | ||||||
| 	}); err != nil { | 		commitTreeOpts.Parents = []string{"HEAD"} | ||||||
| 		return fmt.Errorf("CommitChanges: %v", err) | 	} | ||||||
| 	} else if err = git.Push(localPath, git.PushOptions{ | 	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", | 		Remote: "origin", | ||||||
| 		Branch: "master", | 		Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), | ||||||
|  | 		Env:    PushingEnvironment(doer, repo), | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
|  | 		log.Error("%v", err) | ||||||
| 		return fmt.Errorf("Push: %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)) | 	wikiWorkingPool.CheckIn(com.ToStr(repo.ID)) | ||||||
| 	defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) | 	defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID)) | ||||||
|  |  | ||||||
| 	localPath := repo.LocalWikiPath() | 	if err = repo.InitWiki(); err != nil { | ||||||
| 	if err = discardLocalWikiChanges(localPath); err != nil { | 		return fmt.Errorf("InitWiki: %v", err) | ||||||
| 		return fmt.Errorf("discardLocalWikiChanges: %v", err) |  | ||||||
| 	} else if err = repo.updateLocalWiki(); err != nil { |  | ||||||
| 		return fmt.Errorf("UpdateLocalWiki: %v", err) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	filename := path.Join(localPath, WikiNameToFilename(wikiName)) | 	basePath, err := CreateTemporaryPath("update-wiki") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer RemoveTemporaryPath(basePath) | ||||||
|  |  | ||||||
| 	if err := os.Remove(filename); err != nil { | 	if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{ | ||||||
| 		return fmt.Errorf("Failed to remove %s: %v", filename, err) | 		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) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	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 + "'" | 	message := "Delete page '" + wikiName + "'" | ||||||
|  |  | ||||||
| 	if err = git.AddChanges(localPath, true); err != nil { | 	commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, git.CommitTreeOpts{ | ||||||
| 		return fmt.Errorf("AddChanges: %v", err) | 		Message: message, | ||||||
| 	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{ | 		Parents: []string{"HEAD"}, | ||||||
| 		Committer: doer.NewGitSig(), | 	}) | ||||||
| 		Message:   message, | 	if err != nil { | ||||||
| 	}); err != nil { | 		return err | ||||||
| 		return fmt.Errorf("CommitChanges: %v", err) | 	} | ||||||
| 	} else if err = git.Push(localPath, git.PushOptions{ |  | ||||||
|  | 	if err := git.Push(basePath, git.PushOptions{ | ||||||
| 		Remote: "origin", | 		Remote: "origin", | ||||||
| 		Branch: "master", | 		Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), | ||||||
|  | 		Env:    PushingEnvironment(doer, repo), | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		return fmt.Errorf("Push: %v", err) | 		return fmt.Errorf("Push: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,13 +5,12 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -145,13 +144,6 @@ func TestRepository_InitWiki(t *testing.T) { | |||||||
| 	assert.True(t, repo2.HasWiki()) | 	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) { | func TestRepository_AddWikiPage(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
| 	const wikiContent = "This is the wiki content" | 	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.Run("test wiki exist: "+wikiName, func(t *testing.T) { | ||||||
| 			t.Parallel() | 			t.Parallel() | ||||||
| 			assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg)) | 			assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg)) | ||||||
| 			expectedPath := path.Join(repo.LocalWikiPath(), WikiNameToFilename(wikiName)) | 			// Now need to show that the page has been added: | ||||||
| 			assert.True(t, com.IsExist(expectedPath)) | 			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) | 		PrepareTestEnv(t) | ||||||
| 		assert.NoError(t, repo.EditWikiPage(doer, "Home", newWikiName, newWikiContent, commitMsg)) | 		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" { | 		if newWikiName != "Home" { | ||||||
| 			oldPath := path.Join(repo.LocalWikiPath(), "Home.md") | 			_, err := masterTree.GetTreeEntryByPath("Home.md") | ||||||
| 			assert.False(t, com.IsExist(oldPath)) | 			assert.Error(t, err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -214,6 +222,13 @@ func TestRepository_DeleteWikiPage(t *testing.T) { | |||||||
| 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | ||||||
| 	doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | 	doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
| 	assert.NoError(t, repo.DeleteWikiPage(doer, "Home")) | 	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) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -52,9 +52,15 @@ func (c *Command) AddArguments(args ...string) *Command { | |||||||
| 	return c | 	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. | // 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 { | 	if timeout == -1 { | ||||||
| 		timeout = DefaultCommandExecutionTimeout | 		timeout = DefaultCommandExecutionTimeout | ||||||
| 	} | 	} | ||||||
| @@ -69,9 +75,11 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std | |||||||
| 	defer cancel() | 	defer cancel() | ||||||
|  |  | ||||||
| 	cmd := exec.CommandContext(ctx, c.name, c.args...) | 	cmd := exec.CommandContext(ctx, c.name, c.args...) | ||||||
|  | 	cmd.Env = env | ||||||
| 	cmd.Dir = dir | 	cmd.Dir = dir | ||||||
| 	cmd.Stdout = stdout | 	cmd.Stdout = stdout | ||||||
| 	cmd.Stderr = stderr | 	cmd.Stderr = stderr | ||||||
|  | 	cmd.Stdin = stdin | ||||||
| 	if err := cmd.Start(); err != nil { | 	if err := cmd.Start(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -83,12 +91,30 @@ func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, std | |||||||
| 	return ctx.Err() | 	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, | // RunInDirTimeout executes the command in given directory with given timeout, | ||||||
| // and returns stdout in []byte and error (combined with stderr). | // and returns stdout in []byte and error (combined with stderr). | ||||||
| func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) { | 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) | 	stdout := new(bytes.Buffer) | ||||||
| 	stderr := 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()) | 		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, | // RunInDirPipeline executes the command in given directory, | ||||||
| // it pipes stdout and stderr to given io.Writer. | // it pipes stdout and stderr to given io.Writer. | ||||||
| func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error { | 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 | // 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 | // RunInDir executes the command in given directory | ||||||
| // and returns stdout in string and error (combined with stderr). | // and returns stdout in string and error (combined with stderr). | ||||||
| func (c *Command) RunInDir(dir string) (string, error) { | 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 { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -109,11 +109,13 @@ func OpenRepository(repoPath string) (*Repository, error) { | |||||||
|  |  | ||||||
| // CloneRepoOptions options when clone a repository | // CloneRepoOptions options when clone a repository | ||||||
| type CloneRepoOptions struct { | type CloneRepoOptions struct { | ||||||
| 	Timeout time.Duration | 	Timeout    time.Duration | ||||||
| 	Mirror  bool | 	Mirror     bool | ||||||
| 	Bare    bool | 	Bare       bool | ||||||
| 	Quiet   bool | 	Quiet      bool | ||||||
| 	Branch  string | 	Branch     string | ||||||
|  | 	Shared     bool | ||||||
|  | 	NoCheckout bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // Clone clones original repository to target path. | // Clone clones original repository to target path. | ||||||
| @@ -133,10 +135,17 @@ func Clone(from, to string, opts CloneRepoOptions) (err error) { | |||||||
| 	if opts.Quiet { | 	if opts.Quiet { | ||||||
| 		cmd.AddArguments("--quiet") | 		cmd.AddArguments("--quiet") | ||||||
| 	} | 	} | ||||||
|  | 	if opts.Shared { | ||||||
|  | 		cmd.AddArguments("-s") | ||||||
|  | 	} | ||||||
|  | 	if opts.NoCheckout { | ||||||
|  | 		cmd.AddArguments("--no-checkout") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(opts.Branch) > 0 { | 	if len(opts.Branch) > 0 { | ||||||
| 		cmd.AddArguments("-b", opts.Branch) | 		cmd.AddArguments("-b", opts.Branch) | ||||||
| 	} | 	} | ||||||
| 	cmd.AddArguments(from, to) | 	cmd.AddArguments("--", from, to) | ||||||
|  |  | ||||||
| 	if opts.Timeout <= 0 { | 	if opts.Timeout <= 0 { | ||||||
| 		opts.Timeout = -1 | 		opts.Timeout = -1 | ||||||
| @@ -181,6 +190,7 @@ type PushOptions struct { | |||||||
| 	Remote string | 	Remote string | ||||||
| 	Branch string | 	Branch string | ||||||
| 	Force  bool | 	Force  bool | ||||||
|  | 	Env    []string | ||||||
| } | } | ||||||
|  |  | ||||||
| // Push pushs local commits to given remote branch. | // Push pushs local commits to given remote branch. | ||||||
| @@ -190,7 +200,7 @@ func Push(repoPath string, opts PushOptions) error { | |||||||
| 		cmd.AddArguments("-f") | 		cmd.AddArguments("-f") | ||||||
| 	} | 	} | ||||||
| 	cmd.AddArguments(opts.Remote, opts.Branch) | 	cmd.AddArguments(opts.Remote, opts.Branch) | ||||||
| 	_, err := cmd.RunInDir(repoPath) | 	_, err := cmd.RunInDirWithEnv(repoPath, opts.Env) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ const BranchPrefix = "refs/heads/" | |||||||
|  |  | ||||||
| // IsReferenceExist returns true if given reference exists in the repository. | // IsReferenceExist returns true if given reference exists in the repository. | ||||||
| func IsReferenceExist(repoPath, name string) bool { | 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 | 	return err == nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -145,9 +145,9 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro | |||||||
| } | } | ||||||
|  |  | ||||||
| // CreateBranch create a new branch | // 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 := NewCommand("branch") | ||||||
| 	cmd.AddArguments(branch, newBranch) | 	cmd.AddArguments("--", branch, oldbranchOrCommit) | ||||||
|  |  | ||||||
| 	_, err := cmd.RunInDir(repo.Path) | 	_, err := cmd.RunInDir(repo.Path) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								modules/git/repo_index.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								modules/git/repo_index.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
| @@ -4,6 +4,12 @@ | |||||||
|  |  | ||||||
| package git | package git | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
| // ObjectType git object type | // ObjectType git object type | ||||||
| type ObjectType string | type ObjectType string | ||||||
|  |  | ||||||
| @@ -17,3 +23,24 @@ const ( | |||||||
| 	// ObjectTag tag object type | 	// ObjectTag tag object type | ||||||
| 	ObjectTag ObjectType = "tag" | 	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 | ||||||
|  | } | ||||||
|   | |||||||
| @@ -6,6 +6,11 @@ | |||||||
| package git | package git | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"gopkg.in/src-d/go-git.v4/plumbing" | 	"gopkg.in/src-d/go-git.v4/plumbing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -47,3 +52,48 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { | |||||||
| 	treeObject.ResolvedID = resolvedID | 	treeObject.ResolvedID = resolvedID | ||||||
| 	return treeObject, nil | 	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)) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -11,17 +11,15 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path" |  | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/process" | 	"code.gitea.io/gitea/modules/process" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone | // 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 | // NewTemporaryUploadRepository creates a new temporary upload repository | ||||||
| func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) { | func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) { | ||||||
| 	timeStr := com.ToStr(time.Now().Nanosecond()) // SHOULD USE SOMETHING UNIQUE | 	basePath, err := models.CreateTemporaryPath("upload") | ||||||
| 	basePath := path.Join(models.LocalCopyPath(), "upload-"+timeStr+".git") | 	if err != nil { | ||||||
| 	if err := os.MkdirAll(path.Dir(basePath), os.ModePerm); err != nil { | 		return nil, err | ||||||
| 		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") |  | ||||||
| 	} | 	} | ||||||
| 	t := &TemporaryUploadRepository{repo: repo, basePath: basePath} | 	t := &TemporaryUploadRepository{repo: repo, basePath: basePath} | ||||||
| 	return t, nil | 	return t, nil | ||||||
| @@ -47,8 +41,8 @@ func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepo | |||||||
|  |  | ||||||
| // Close the repository cleaning up all files | // Close the repository cleaning up all files | ||||||
| func (t *TemporaryUploadRepository) Close() { | func (t *TemporaryUploadRepository) Close() { | ||||||
| 	if _, err := os.Stat(t.basePath); !os.IsNotExist(err) { | 	if err := models.RemoveTemporaryPath(t.basePath); err != nil { | ||||||
| 		os.RemoveAll(t.basePath) | 		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 | // Push the provided commitHash to the repository branch by the provided user | ||||||
| func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, branch string) error { | 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 | 	// Because calls hooks we need to pass in the environment | ||||||
| 	env := append(os.Environ(), | 	env := models.PushingEnvironment(doer, t.repo) | ||||||
| 		"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), |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	if _, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute, | 	if _, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute, | ||||||
| 		t.basePath, | 		t.basePath, | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| @@ -53,7 +53,6 @@ var ( | |||||||
| 		// Repository local settings | 		// Repository local settings | ||||||
| 		Local struct { | 		Local struct { | ||||||
| 			LocalCopyPath string | 			LocalCopyPath string | ||||||
| 			LocalWikiPath string |  | ||||||
| 		} `ini:"-"` | 		} `ini:"-"` | ||||||
|  |  | ||||||
| 		// Pull request settings | 		// Pull request settings | ||||||
| @@ -105,10 +104,8 @@ var ( | |||||||
| 		// Repository local settings | 		// Repository local settings | ||||||
| 		Local: struct { | 		Local: struct { | ||||||
| 			LocalCopyPath string | 			LocalCopyPath string | ||||||
| 			LocalWikiPath string |  | ||||||
| 		}{ | 		}{ | ||||||
| 			LocalCopyPath: "tmp/local-repo", | 			LocalCopyPath: "tmp/local-repo", | ||||||
| 			LocalWikiPath: "tmp/local-wiki", |  | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		// Pull request settings | 		// Pull request settings | ||||||
|   | |||||||
| @@ -74,12 +74,6 @@ func DeleteBranchPost(ctx *context.Context) { | |||||||
| 		return | 		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)) | 	ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName)) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user