* 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 vendorfor-closed-social
@ -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", | |||||
) | |||||
} |
@ -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 | |||||
} |
@ -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) | |||||
}) | |||||
} |