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
release/v1.9
zeripath 5 years ago committed by techknowlogick
parent 34eee25bd4
commit ce8de35334

@ -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 trying to create a file that already exists, should fail
createFileOptions = getCreateFileOptions()
treePath = "README.md"
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
resp = session.MakeRequest(t, req, http.StatusInternalServerError)
expectedAPIError := context.APIError{
Message: "repository file already exists [path: " + treePath + "]",
URL: base.DocURL,
}
var apiError context.APIError
DecodeJSON(t, resp, &apiError)
assert.Equal(t, expectedAPIError, apiError)
// Test creating a file in repo1 by user4 who does not have write access
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusNotFound)
// Test creating a file in a new branch // Tests a repo with no token given so will fail
createFileOptions := getCreateFileOptions() createFileOptions = getCreateFileOptions()
createFileOptions.BranchName = repo1.DefaultBranch fileID++
createFileOptions.NewBranchName = "new_branch" treePath = fmt.Sprintf("new/file%d.txt", fileID)
fileID++ url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
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, repo1.Name, treePath, token2) session.MakeRequest(t, req, http.StatusNotFound)
req := NewRequestWithJSON(t, "POST", url, &createFileOptions)
resp := session.MakeRequest(t, req, http.StatusCreated) // Test using access token for a private repo that the user of the token owns
var fileResponse api.FileResponse createFileOptions = getCreateFileOptions()
DecodeJSON(t, resp, &fileResponse) fileID++
expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" treePath = fmt.Sprintf("new/file%d.txt", fileID)
expectedHTMLURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/blob/new_branch/new/file%d.txt", fileID) url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
expectedDownloadURL := fmt.Sprintf("http://localhost:"+setting.HTTPPort+"/user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) session.MakeRequest(t, req, http.StatusCreated)
assert.EqualValues(t, expectedHTMLURL, fileResponse.Content.HTMLURL)
assert.EqualValues(t, expectedDownloadURL, fileResponse.Content.DownloadURL) // Test using org repo "user3/repo3" where user2 is a collaborator
createFileOptions = getCreateFileOptions()
// Test trying to create a file that already exists, should fail fileID++
createFileOptions = getCreateFileOptions() treePath = fmt.Sprintf("new/file%d.txt", fileID)
treePath = "README.md" 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?token=%s", user2.Name, repo1.Name, treePath, token2) req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions) session.MakeRequest(t, req, http.StatusCreated)
resp = session.MakeRequest(t, req, http.StatusInternalServerError)
expectedAPIError := context.APIError{ // Test using org repo "user3/repo3" with no user token
Message: "repository file already exists [path: " + treePath + "]", createFileOptions = getCreateFileOptions()
URL: base.DocURL, fileID++
} treePath = fmt.Sprintf("new/file%d.txt", fileID)
var apiError context.APIError url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
DecodeJSON(t, resp, &apiError) req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
assert.Equal(t, expectedAPIError, apiError) session.MakeRequest(t, req, http.StatusNotFound)
// Test creating a file in repo1 by user4 who does not have write access // 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?token=%s", user2.Name, repo16.Name, treePath, token4) 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)
})
// Tests a repo with no token given so will fail
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusNotFound)
// Test using access token for a private repo that the user of the token owns
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusCreated)
// Test using org repo "user3/repo3" where user2 is a collaborator
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusCreated)
// Test using org repo "user3/repo3" with no user token
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusNotFound)
// Test using repo "user2/repo1" where user4 is a NOT collaborator
createFileOptions = getCreateFileOptions()
fileID++
treePath = fmt.Sprintf("new/file%d.txt", fileID)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
session.MakeRequest(t, req, http.StatusForbidden)
} }

@ -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
// Test deleting a file with the wrong SHA DecodeJSON(t, resp, &apiError)
fileID++ assert.Equal(t, expectedAPIError, apiError)
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user2, repo1, treePath) // Test creating a file in repo1 by user4 who does not have write access
deleteFileOptions = getDeleteFileOptions() fileID++
correctSHA := deleteFileOptions.SHA treePath = fmt.Sprintf("delete/file%d.txt", fileID)
deleteFileOptions.SHA = "badsha" createFile(user2, repo16, treePath)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2) deleteFileOptions = getDeleteFileOptions()
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) 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, "DELETE", url, &deleteFileOptions)
expectedAPIError := context.APIError{ session.MakeRequest(t, req, http.StatusNotFound)
Message: "sha does not match [given: " + deleteFileOptions.SHA + ", expected: " + correctSHA + "]",
URL: base.DocURL, // Tests a repo with no token given so will fail
} fileID++
var apiError context.APIError treePath = fmt.Sprintf("delete/file%d.txt", fileID)
DecodeJSON(t, resp, &apiError) createFile(user2, repo16, treePath)
assert.Equal(t, expectedAPIError, apiError) deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
// Test creating a file in repo1 by user4 who does not have write access req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
fileID++ session.MakeRequest(t, req, http.StatusNotFound)
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user2, repo16, treePath) // Test using access token for a private repo that the user of the token owns
deleteFileOptions = getDeleteFileOptions() fileID++
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) treePath = fmt.Sprintf("delete/file%d.txt", fileID)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) createFile(user2, repo16, treePath)
session.MakeRequest(t, req, http.StatusNotFound) deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
// Tests a repo with no token given so will fail req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
fileID++ session.MakeRequest(t, req, http.StatusOK)
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user2, repo16, treePath) // Test using org repo "user3/repo3" where user2 is a collaborator
deleteFileOptions = getDeleteFileOptions() fileID++
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath) treePath = fmt.Sprintf("delete/file%d.txt", fileID)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) createFile(user3, repo3, treePath)
session.MakeRequest(t, req, http.StatusNotFound) deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2)
// Test using access token for a private repo that the user of the token owns req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
fileID++ session.MakeRequest(t, req, http.StatusOK)
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user2, repo16, treePath) // Test using org repo "user3/repo3" with no user token
deleteFileOptions = getDeleteFileOptions() fileID++
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2) treePath = fmt.Sprintf("delete/file%d.txt", fileID)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) createFile(user3, repo3, treePath)
session.MakeRequest(t, req, http.StatusOK) deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
// Test using org repo "user3/repo3" where user2 is a collaborator req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
fileID++ session.MakeRequest(t, req, http.StatusNotFound)
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user3, repo3, treePath) // Test using repo "user2/repo1" where user4 is a NOT collaborator
deleteFileOptions = getDeleteFileOptions() fileID++
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2) treePath = fmt.Sprintf("delete/file%d.txt", fileID)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) createFile(user2, repo1, treePath)
session.MakeRequest(t, req, http.StatusOK) deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
// Test using org repo "user3/repo3" with no user token req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
fileID++ session.MakeRequest(t, req, http.StatusForbidden)
treePath = fmt.Sprintf("delete/file%d.txt", fileID) })
createFile(user3, repo3, treePath)
deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
session.MakeRequest(t, req, http.StatusNotFound)
// Test using repo "user2/repo1" where user4 is a NOT collaborator
fileID++
treePath = fmt.Sprintf("delete/file%d.txt", fileID)
createFile(user2, repo1, treePath)
deleteFileOptions = getDeleteFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions)
session.MakeRequest(t, req, http.StatusForbidden)
} }

@ -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 creating a file in repo1 by user4 who does not have write access
// Test updating a file with the wrong SHA fileID++
fileID++ treePath = fmt.Sprintf("update/file%d.txt", fileID)
treePath = fmt.Sprintf("update/file%d.txt", fileID) createFile(user2, repo16, treePath)
createFile(user2, repo1, treePath) updateFileOptions = getUpdateFileOptions()
updateFileOptions = getUpdateFileOptions() url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4)
correctSHA := updateFileOptions.SHA req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
updateFileOptions.SHA = "badsha" session.MakeRequest(t, req, http.StatusNotFound)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) // Tests a repo with no token given so will fail
resp = session.MakeRequest(t, req, http.StatusInternalServerError) fileID++
expectedAPIError := context.APIError{ treePath = fmt.Sprintf("update/file%d.txt", fileID)
Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]", createFile(user2, repo16, treePath)
URL: base.DocURL, updateFileOptions = getUpdateFileOptions()
} url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
var apiError context.APIError req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
DecodeJSON(t, resp, &apiError) session.MakeRequest(t, req, http.StatusNotFound)
assert.Equal(t, expectedAPIError, apiError)
// Test using access token for a private repo that the user of the token owns
// 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, 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, token2)
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4) 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" where user2 is a collaborator
// 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(user3, repo3, treePath)
createFile(user2, repo16, 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", user2.Name, repo16.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 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(user3, repo3, treePath)
createFile(user2, repo16, 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, 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 repo "user2/repo1" where user4 is a NOT collaborator
// 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, repo1, treePath)
createFile(user3, repo3, treePath) updateFileOptions = getUpdateFileOptions()
updateFileOptions = getUpdateFileOptions() url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
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.StatusForbidden)
session.MakeRequest(t, req, http.StatusOK) })
// Test using org repo "user3/repo3" with no user token
fileID++
treePath = fmt.Sprintf("update/file%d.txt", fileID)
createFile(user3, repo3, treePath)
updateFileOptions = getUpdateFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
session.MakeRequest(t, req, http.StatusNotFound)
// Test using repo "user2/repo1" where user4 is a NOT collaborator
fileID++
treePath = fmt.Sprintf("update/file%d.txt", fileID)
createFile(user2, repo1, treePath)
updateFileOptions = getUpdateFileOptions()
url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
session.MakeRequest(t, req, http.StatusForbidden)
} }

@ -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)
lastCommit := doc.GetInputValueByName("last_commit")
assert.NotEmpty(t, lastCommit)
// Save new file to master branch // Save new file to master branch
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
"_csrf": doc.GetCSRF(), "_csrf": doc.GetCSRF(),
"last_commit": lastCommit, "last_commit": lastCommit,
"tree_path": "test.txt", "tree_path": "test.txt",
"content": "Content", "content": "Content",
"commit_choice": "direct", "commit_choice": "direct",
})
resp = session.MakeRequest(t, req, http.StatusFound)
}) })
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")
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches") // Change master branch to protected
// Change master branch to protected req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ "_csrf": csrf,
"_csrf": csrf, "protected": "on",
"protected": "on", })
}) resp := session.MakeRequest(t, req, http.StatusFound)
resp := session.MakeRequest(t, req, http.StatusFound) // Check if master branch has been locked successfully
// Check if master branch has been locked successfully flashCookie := session.GetCookie("macaron_flash")
flashCookie := session.GetCookie("macaron_flash") assert.NotNil(t, flashCookie)
assert.NotNil(t, flashCookie) assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
// Request editor page
// Request editor page req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
req = NewRequest(t, "GET", "/user2/repo1/_new/master/") resp = session.MakeRequest(t, req, http.StatusOK)
resp = session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
doc := NewHTMLParser(t, resp.Body) lastCommit := doc.GetInputValueByName("last_commit")
lastCommit := doc.GetInputValueByName("last_commit") assert.NotEmpty(t, lastCommit)
assert.NotEmpty(t, lastCommit)
// Save new file to master branch
// Save new file to master branch req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{ "_csrf": doc.GetCSRF(),
"_csrf": doc.GetCSRF(), "last_commit": lastCommit,
"last_commit": lastCommit, "tree_path": "test.txt",
"tree_path": "test.txt", "content": "Content",
"content": "Content", "commit_choice": "direct",
"commit_choice": "direct", })
})
resp = session.MakeRequest(t, req, http.StatusOK)
resp = session.MakeRequest(t, req, http.StatusOK) // Check body for error message
// Check body for error message assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch &#39;master&#39;.")
assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch &#39;master&#39;.")
// remove the protected branch
// remove the protected branch csrf = GetCSRF(t, session, "/user2/repo1/settings/branches")
csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") // Change master branch to protected
// Change master branch to protected req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{ "_csrf": csrf,
"_csrf": csrf, "protected": "off",
"protected": "off", })
resp = session.MakeRequest(t, req, http.StatusFound)
// Check if master branch has been locked successfully
flashCookie = session.GetCookie("macaron_flash")
assert.NotNil(t, flashCookie)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value)
}) })
resp = session.MakeRequest(t, req, http.StatusFound)
// Check if master branch has been locked successfully
flashCookie = session.GetCookie("macaron_flash")
assert.NotNil(t, flashCookie)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value)
} }
func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder { 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, "&lt;i&gt;XSS PR&lt;/i&gt;", titleHTML)
titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;u&gt;XSS PR&lt;/u&gt;", 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, "&lt;i&gt;XSS PR&lt;/i&gt;", titleHTML)
titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;u&gt;XSS PR&lt;/u&gt;", 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), "/")
assert.EqualValues(t, "pulls", elem[3]) elem := strings.Split(test.RedirectURL(resp), "/")
testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) assert.EqualValues(t, "pulls", elem[3])
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"))
resp = session.MakeRequest(t, req, http.StatusOK) req := NewRequest(t, "GET", resp.Header().Get("Location"))
htmlDoc := NewHTMLParser(t, resp.Body) resp = session.MakeRequest(t, req, http.StatusOK)
text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text()) htmlDoc := NewHTMLParser(t, resp.Body)
assert.NotEmpty(t, text, "Can't find WIP text") text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text())
assert.NotEmpty(t, text, "Can't find WIP text")
// remove <strong /> from lang
expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]") // remove <strong /> from lang
replacer := strings.NewReplacer("<strong>", "", "</strong>", "") expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]")
assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text") replacer := strings.NewReplacer("<strong>", "", "</strong>", "")
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")
session := loginUser(t, "user1")
// Create PRs (1 merged & 2 proposed)
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") // Create PRs (1 merged & 2 proposed)
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
elem := strings.Split(test.RedirectURL(resp), "/") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
assert.EqualValues(t, "pulls", elem[3]) elem := strings.Split(test.RedirectURL(resp), "/")
testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge)
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n")
testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n")
testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n")
testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n")
testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title")
// Create issues (3 new issues)
testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") // Create issues (3 new issues)
testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2") testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1")
testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3") testNewIssue(t, session, "user2", "repo1", "Issue 2", "Description 2")
testNewIssue(t, session, "user2", "repo1", "Issue 3", "Description 3")
// Create releases (1 new release)
createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false) // Create releases (1 new release)
createNewRelease(t, session, "/user2/repo1", "v1.0.0", "v1.0.0", false, false)
// Open Activity page and check stats
req := NewRequest(t, "GET", "/user2/repo1/activity") // Open Activity page and check stats
resp = session.MakeRequest(t, req, http.StatusOK) req := NewRequest(t, "GET", "/user2/repo1/activity")
htmlDoc := NewHTMLParser(t, resp.Body) resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
// Should be 1 published release
list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") // Should be 1 published release
assert.Len(t, list.Nodes, 1) list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc")
assert.Len(t, list.Nodes, 1)
// Should be 1 merged pull request
list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") // Should be 1 merged pull request
assert.Len(t, list.Nodes, 1) list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc")
assert.Len(t, list.Nodes, 1)
// Should be 2 merged proposed pull requests
list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") // Should be 2 merged proposed pull requests
assert.Len(t, list.Nodes, 2) list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc")
assert.Len(t, list.Nodes, 2)
// Should be 3 new issues
list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") // Should be 3 new issues
assert.Len(t, list.Nodes, 3) 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 + "]"

@ -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)
})
})
}

@ -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
}

@ -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)
}() }()
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
// Clone base repo. // Clone base repo.
tmpBasePath := path.Join(LocalCopyPath(), "merge-"+com.ToStr(time.Now().Nanosecond())+".git") tmpBasePath, err := CreateTemporaryPath("merge")
if err != nil {
if err := os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm); err != nil { return err
return fmt.Errorf("Failed to create dir %s: %v", tmpBasePath, err)
} }
defer RemoveTemporaryPath(tmpBasePath)
defer os.RemoveAll(tmpBasePath) headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
var stderr string if err := git.Clone(baseGitRepo.Path, tmpBasePath, git.CloneRepoOptions{
if _, stderr, err = process.GetManager().ExecTimeout(5*time.Minute, Shared: true,
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath), NoCheckout: true,
"git", "clone", "-s", "--no-checkout", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil { Branch: pr.BaseBranch,
return fmt.Errorf("git clone: %s", stderr) }); err != nil {
return fmt.Errorf("git clone: %v", err)
} }
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,92 +62,86 @@ 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.Push(localPath, git.PushOptions{ if err := git.Clone(repo.RepoPath(), basePath, git.CloneRepoOptions{
Remote: "origin", Bare: true,
Branch: branchName, Shared: true,
}); err != nil { }); err != nil {
return fmt.Errorf("Push: %v", err) log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
} }
return nil gitRepo, err := git.OpenRepository(basePath)
} if err != nil {
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
}
// updateLocalCopyToCommit pulls latest changes of given commit from repoPath to localPath. if err = gitRepo.CreateBranch(branchName, oldBranchName); err != nil {
// It creates a new clone if local copy does not exist. log.Error("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err)
// This function checks out target commit by default, it is safe to assume subsequent return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, oldBranchName, err)
// 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, if err = git.Push(basePath, git.PushOptions{
Remote: "origin",
Branch: branchName,
Env: PushingEnvironment(doer, repo),
}); err != nil { }); err != nil {
return fmt.Errorf("git checkout %s: %v", commit, err) return fmt.Errorf("Push: %v", err)
} }
return nil
}
// updateLocalCopyToCommit makes sure local copy of repository is at given commit. return nil
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,
Shared: true,
}); err != nil {
log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
}
if err = repo.updateLocalCopyToCommit(commit); err != nil { gitRepo, err := git.OpenRepository(basePath)
return fmt.Errorf("UpdateLocalCopyBranch: %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 = repo.CheckoutNewBranch(commit, branchName); err != nil { if err = gitRepo.CreateBranch(branchName, commit); err != nil {
return fmt.Errorf("CheckoutNewBranch: %v", err) log.Error("Unable to create branch: %s from %s. (%v)", branchName, commit, err)
return fmt.Errorf("Unable to create branch: %s from %s. (%v)", branchName, commit, err)
} }
if err = git.Push(localPath, git.PushOptions{ if err = git.Push(basePath, git.PushOptions{
Remote: "origin", 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)

@ -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)
newWikiPath := path.Join(localPath, WikiNameToFilename(newWikiName)) cloneOpts := git.CloneRepoOptions{
Bare: true,
Shared: true,
}
if hasMasterBranch {
cloneOpts.Branch = "master"
}
// If not a new file, show perform update not create. if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil {
log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
}
gitRepo, err := git.OpenRepository(basePath)
if err != nil {
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
}
if hasMasterBranch {
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)
return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
}
}
newWikiPath := WikiNameToFilename(newWikiName)
if isNew { if 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) basePath, err := CreateTemporaryPath("update-wiki")
if err != nil {
return err
} }
defer RemoveTemporaryPath(basePath)
filename := path.Join(localPath, WikiNameToFilename(wikiName)) if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{
Bare: true,
Shared: true,
Branch: "master",
}); err != nil {
log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
}
if err := os.Remove(filename); err != nil { gitRepo, err := git.OpenRepository(basePath)
return fmt.Errorf("Failed to remove %s: %v", filename, 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 := 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)

@ -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))
} }

Loading…
Cancel
Save