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