import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
func createFileInBranch(user *models.User, repo *models.Repository, treePath, branchName, content string) (*api.FileResponse, error) {
- opts := &repofiles.UpdateRepoFileOptions{
+ opts := &files_service.UpdateRepoFileOptions{
OldBranch: branchName,
TreePath: treePath,
Content: content,
Author: nil,
Committer: nil,
}
- return repofiles.CreateOrUpdateRepoFile(repo, user, opts)
+ return files_service.CreateOrUpdateRepoFile(repo, user, opts)
}
func createFile(user *models.User, repo *models.Repository, treePath string) (*api.FileResponse, error) {
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/repofiles"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
+ files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
)
assert.NotEmpty(t, headRepo)
//create a commit on base Repo
- _, err = repofiles.CreateOrUpdateRepoFile(baseRepo, actor, &repofiles.UpdateRepoFileOptions{
+ _, err = files_service.CreateOrUpdateRepoFile(baseRepo, actor, &files_service.UpdateRepoFileOptions{
TreePath: "File_A",
Message: "Add File A",
Content: "File A",
IsNewFile: true,
OldBranch: "master",
NewBranch: "master",
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: actor.Name,
Email: actor.Email,
},
- Committer: &repofiles.IdentityOptions{
+ Committer: &files_service.IdentityOptions{
Name: actor.Name,
Email: actor.Email,
},
- Dates: &repofiles.CommitDateOptions{
+ Dates: &files_service.CommitDateOptions{
Author: time.Now(),
Committer: time.Now(),
},
assert.NoError(t, err)
//create a commit on head Repo
- _, err = repofiles.CreateOrUpdateRepoFile(headRepo, actor, &repofiles.UpdateRepoFileOptions{
+ _, err = files_service.CreateOrUpdateRepoFile(headRepo, actor, &files_service.UpdateRepoFileOptions{
TreePath: "File_B",
Message: "Add File on PR branch",
Content: "File B",
IsNewFile: true,
OldBranch: "master",
NewBranch: "newBranch",
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: actor.Name,
Email: actor.Email,
},
- Committer: &repofiles.IdentityOptions{
+ Committer: &files_service.IdentityOptions{
Name: actor.Name,
Email: actor.Email,
},
- Dates: &repofiles.CommitDateOptions{
+ Dates: &files_service.CommitDateOptions{
Author: time.Now(),
Committer: time.Now(),
},
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
+ files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
)
-func getDeleteRepoFileOptions(repo *models.Repository) *repofiles.DeleteRepoFileOptions {
- return &repofiles.DeleteRepoFileOptions{
+func getDeleteRepoFileOptions(repo *models.Repository) *files_service.DeleteRepoFileOptions {
+ return &files_service.DeleteRepoFileOptions{
LastCommitID: "",
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
TreePath: "README.md",
Message: "Deletes README.md",
SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: "Bob Smith",
Email: "bob@smith.com",
},
opts := getDeleteRepoFileOptions(repo)
t.Run("Delete README.md file", func(t *testing.T) {
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.NoError(t, err)
expectedFileResponse := getExpectedDeleteFileResponse(u)
assert.NotNil(t, fileResponse)
})
t.Run("Verify README.md has been deleted", func(t *testing.T) {
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
expectedError := "repository file does not exist [path: " + opts.TreePath + "]"
assert.EqualError(t, err, expectedError)
opts.NewBranch = ""
t.Run("Delete README.md without Branch Name", func(t *testing.T) {
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.NoError(t, err)
expectedFileResponse := getExpectedDeleteFileResponse(u)
assert.NotNil(t, fileResponse)
t.Run("Bad branch", func(t *testing.T) {
opts := getDeleteRepoFileOptions(repo)
opts.OldBranch = "bad_branch"
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Error(t, err)
assert.Nil(t, fileResponse)
expectedError := "branch does not exist [name: " + opts.OldBranch + "]"
opts := getDeleteRepoFileOptions(repo)
origSHA := opts.SHA
opts.SHA = "bad_sha"
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]"
t.Run("New branch already exists", func(t *testing.T) {
opts := getDeleteRepoFileOptions(repo)
opts.NewBranch = "develop"
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "branch already exists [name: " + opts.NewBranch + "]"
t.Run("TreePath is empty:", func(t *testing.T) {
opts := getDeleteRepoFileOptions(repo)
opts.TreePath = ""
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "path contains a malformed path component [path: ]"
t.Run("TreePath is a git directory:", func(t *testing.T) {
opts := getDeleteRepoFileOptions(repo)
opts.TreePath = ".git"
- fileResponse, err := repofiles.DeleteRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.DeleteRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]"
"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"
+ files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert"
)
-func getCreateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFileOptions {
- return &repofiles.UpdateRepoFileOptions{
+func getCreateRepoFileOptions(repo *models.Repository) *files_service.UpdateRepoFileOptions {
+ return &files_service.UpdateRepoFileOptions{
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
TreePath: "new/file.txt",
}
}
-func getUpdateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFileOptions {
- return &repofiles.UpdateRepoFileOptions{
+func getUpdateRepoFileOptions(repo *models.Repository) *files_service.UpdateRepoFileOptions {
+ return &files_service.UpdateRepoFileOptions{
OldBranch: repo.DefaultBranch,
NewBranch: repo.DefaultBranch,
TreePath: "README.md",
opts := getCreateRepoFileOptions(repo)
// test
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
// asserts
assert.NoError(t, err)
opts := getUpdateRepoFileOptions(repo)
// test
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
// asserts
assert.NoError(t, err)
opts.TreePath = "README_new.md" // new file name, README_new.md
// test
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
// asserts
assert.NoError(t, err)
opts.NewBranch = ""
// test
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
// asserts
assert.NoError(t, err)
t.Run("bad branch", func(t *testing.T) {
opts := getUpdateRepoFileOptions(repo)
opts.OldBranch = "bad_branch"
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Error(t, err)
assert.Nil(t, fileResponse)
expectedError := "branch does not exist [name: " + opts.OldBranch + "]"
opts := getUpdateRepoFileOptions(repo)
origSHA := opts.SHA
opts.SHA = "bad_sha"
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "sha does not match [given: " + opts.SHA + ", expected: " + origSHA + "]"
t.Run("new branch already exists", func(t *testing.T) {
opts := getUpdateRepoFileOptions(repo)
opts.NewBranch = "develop"
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "branch already exists [name: " + opts.NewBranch + "]"
t.Run("treePath is empty:", func(t *testing.T) {
opts := getUpdateRepoFileOptions(repo)
opts.TreePath = ""
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "path contains a malformed path component [path: ]"
t.Run("treePath is a git directory:", func(t *testing.T) {
opts := getUpdateRepoFileOptions(repo)
opts.TreePath = ".git"
- fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "path contains a malformed path component [path: " + opts.TreePath + "]"
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)
+ fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
assert.Nil(t, fileResponse)
assert.Error(t, err)
expectedError := "repository file already exists [path: " + opts.TreePath + "]"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
)
},
}
- baseBranch, err = repo_module.GetBranch(pr.BaseRepo, pr.BaseBranch)
+ gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
+ if err != nil {
+ log.Error("OpenRepository[%s]: %v", pr.BaseRepo.RepoPath(), err)
+ return nil
+ }
+ defer gitRepo.Close()
+
+ baseBranch, err = gitRepo.GetBranch(pr.BaseBranch)
if err != nil && !git.IsErrBranchNotExist(err) {
log.Error("GetBranch[%s]: %v", pr.BaseBranch, err)
return nil
+++ /dev/null
-// 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 (
- "net/url"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
-)
-
-// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
-func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, error) {
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- defer gitRepo.Close()
- gitBlob, err := gitRepo.GetBlob(sha)
- if err != nil {
- return nil, err
- }
- content := ""
- if gitBlob.Size() <= setting.API.DefaultMaxBlobSize {
- content, err = gitBlob.GetBlobContentBase64()
- if err != nil {
- return nil, err
- }
- }
- return &api.GitBlobResponse{
- SHA: gitBlob.ID.String(),
- URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
- Size: gitBlob.Size(),
- Encoding: "base64",
- Content: content,
- }, nil
-}
+++ /dev/null
-// 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"
-
- "code.gitea.io/gitea/models/unittest"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/test"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetBlobBySHA(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx := test.MockContext(t, "user2/repo1")
- test.LoadRepo(t, ctx, 1)
- test.LoadRepoCommit(t, ctx)
- test.LoadUser(t, ctx, 2)
- test.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
- ctx.SetParams(":id", "1")
- ctx.SetParams(":sha", sha)
-
- gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha"))
- expectedGBR := &api.GitBlobResponse{
- Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
- Encoding: "base64",
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
- SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
- Size: 180,
- }
- assert.NoError(t, err)
- assert.Equal(t, expectedGBR, gbr)
-}
+++ /dev/null
-// 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 (
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
-)
-
-// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
-func CountDivergingCommits(repo *models.Repository, branch string) (*git.DivergeObject, error) {
- divergence, err := git.GetDivergingCommits(repo.RepoPath(), repo.DefaultBranch, branch)
- if err != nil {
- return nil, err
- }
- return &divergence, nil
-}
+++ /dev/null
-// 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 (
- "fmt"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
-)
-
-// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
-// NOTE: All text-values will be trimmed from whitespaces.
-// Requires: Repo, Creator, SHA
-func CreateCommitStatus(repo *models.Repository, creator *models.User, sha string, status *models.CommitStatus) error {
- repoPath := repo.RepoPath()
-
- // confirm that commit is exist
- gitRepo, err := git.OpenRepository(repoPath)
- if err != nil {
- return fmt.Errorf("OpenRepository[%s]: %v", repoPath, err)
- }
- if _, err := gitRepo.GetCommit(sha); err != nil {
- gitRepo.Close()
- return fmt.Errorf("GetCommit[%s]: %v", sha, err)
- }
- gitRepo.Close()
-
- if err := models.NewCommitStatus(models.NewCommitStatusOptions{
- Repo: repo,
- Creator: creator,
- SHA: sha,
- CommitStatus: status,
- }); err != nil {
- return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
- }
-
- return nil
-}
+++ /dev/null
-// 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 (
- "fmt"
- "net/url"
- "path"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- api "code.gitea.io/gitea/modules/structs"
-)
-
-// ContentType repo content type
-type ContentType string
-
-// The string representations of different content types
-const (
- // ContentTypeRegular regular content type (file)
- ContentTypeRegular ContentType = "file"
- // ContentTypeDir dir content type (dir)
- ContentTypeDir ContentType = "dir"
- // ContentLink link content type (symlink)
- ContentTypeLink ContentType = "symlink"
- // ContentTag submodule content type (submodule)
- ContentTypeSubmodule ContentType = "submodule"
-)
-
-// String gets the string of ContentType
-func (ct *ContentType) String() string {
- return string(*ct)
-}
-
-// GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree
-// directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag
-func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface{}, error) {
- if repo.IsEmpty {
- return make([]interface{}, 0), nil
- }
- if ref == "" {
- ref = repo.DefaultBranch
- }
- origRef := ref
-
- // Check that the path given in opts.treePath is valid (not a git path)
- cleanTreePath := CleanUploadFileName(treePath)
- if cleanTreePath == "" && treePath != "" {
- return nil, models.ErrFilenameInvalid{
- Path: treePath,
- }
- }
- treePath = cleanTreePath
-
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- defer gitRepo.Close()
-
- // Get the commit object for the ref
- commit, err := gitRepo.GetCommit(ref)
- if err != nil {
- return nil, err
- }
-
- entry, err := commit.GetTreeEntryByPath(treePath)
- if err != nil {
- return nil, err
- }
-
- if entry.Type() != "tree" {
- return GetContents(repo, treePath, origRef, false)
- }
-
- // We are in a directory, so we return a list of FileContentResponse objects
- var fileList []*api.ContentsResponse
-
- gitTree, err := commit.SubTree(treePath)
- if err != nil {
- return nil, err
- }
- entries, err := gitTree.ListEntries()
- if err != nil {
- return nil, err
- }
- for _, e := range entries {
- subTreePath := path.Join(treePath, e.Name())
- fileContentResponse, err := GetContents(repo, subTreePath, origRef, true)
- if err != nil {
- return nil, err
- }
- fileList = append(fileList, fileContentResponse)
- }
- return fileList, nil
-}
-
-// GetContents gets the meta data on a file's contents. Ref can be a branch, commit or tag
-func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*api.ContentsResponse, error) {
- if ref == "" {
- ref = repo.DefaultBranch
- }
- origRef := ref
-
- // Check that the path given in opts.treePath is valid (not a git path)
- cleanTreePath := CleanUploadFileName(treePath)
- if cleanTreePath == "" && treePath != "" {
- return nil, models.ErrFilenameInvalid{
- Path: treePath,
- }
- }
- treePath = cleanTreePath
-
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- defer gitRepo.Close()
-
- // Get the commit object for the ref
- commit, err := gitRepo.GetCommit(ref)
- if err != nil {
- return nil, err
- }
- commitID := commit.ID.String()
- if len(ref) >= 4 && strings.HasPrefix(commitID, ref) {
- ref = commit.ID.String()
- }
-
- entry, err := commit.GetTreeEntryByPath(treePath)
- if err != nil {
- return nil, err
- }
-
- refType := gitRepo.GetRefType(ref)
- if refType == "invalid" {
- return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref)
- }
-
- selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef))
- if err != nil {
- return nil, err
- }
- selfURLString := selfURL.String()
-
- // All content types have these fields in populated
- contentsResponse := &api.ContentsResponse{
- Name: entry.Name(),
- Path: treePath,
- SHA: entry.ID.String(),
- Size: entry.Size(),
- URL: &selfURLString,
- Links: &api.FileLinksResponse{
- Self: &selfURLString,
- },
- }
-
- // Now populate the rest of the ContentsResponse based on entry type
- if entry.IsRegular() || entry.IsExecutable() {
- contentsResponse.Type = string(ContentTypeRegular)
- if blobResponse, err := GetBlobBySHA(repo, entry.ID.String()); err != nil {
- return nil, err
- } else if !forList {
- // We don't show the content if we are getting a list of FileContentResponses
- contentsResponse.Encoding = &blobResponse.Encoding
- contentsResponse.Content = &blobResponse.Content
- }
- } else if entry.IsDir() {
- contentsResponse.Type = string(ContentTypeDir)
- } else if entry.IsLink() {
- contentsResponse.Type = string(ContentTypeLink)
- // The target of a symlink file is the content of the file
- targetFromContent, err := entry.Blob().GetBlobContent()
- if err != nil {
- return nil, err
- }
- contentsResponse.Target = &targetFromContent
- } else if entry.IsSubModule() {
- contentsResponse.Type = string(ContentTypeSubmodule)
- submodule, err := commit.GetSubModule(treePath)
- if err != nil {
- return nil, err
- }
- contentsResponse.SubmoduleGitURL = &submodule.URL
- }
- // Handle links
- if entry.IsRegular() || entry.IsLink() {
- downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
- if err != nil {
- return nil, err
- }
- downloadURLString := downloadURL.String()
- contentsResponse.DownloadURL = &downloadURLString
- }
- if !entry.IsSubModule() {
- htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
- if err != nil {
- return nil, err
- }
- htmlURLString := htmlURL.String()
- contentsResponse.HTMLURL = &htmlURLString
- contentsResponse.Links.HTMLURL = &htmlURLString
-
- gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String()))
- if err != nil {
- return nil, err
- }
- gitURLString := gitURL.String()
- contentsResponse.GitURL = &gitURLString
- contentsResponse.Links.GitURL = &gitURLString
- }
-
- return contentsResponse, nil
-}
+++ /dev/null
-// 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 (
- "path/filepath"
- "testing"
-
- "code.gitea.io/gitea/models/unittest"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/test"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestMain(m *testing.M) {
- unittest.MainTest(m, filepath.Join("..", ".."))
-}
-
-func getExpectedReadmeContentsResponse() *api.ContentsResponse {
- treePath := "README.md"
- sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
- encoding := "base64"
- content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
- selfURL := "https://try.gitea.io/api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
- htmlURL := "https://try.gitea.io/user2/repo1/src/branch/master/" + treePath
- gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha
- downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath
- return &api.ContentsResponse{
- Name: treePath,
- Path: treePath,
- SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- Type: "file",
- Size: 30,
- Encoding: &encoding,
- Content: &content,
- URL: &selfURL,
- HTMLURL: &htmlURL,
- GitURL: &gitURL,
- DownloadURL: &downloadURL,
- Links: &api.FileLinksResponse{
- Self: &selfURL,
- GitURL: &gitURL,
- HTMLURL: &htmlURL,
- },
- }
-}
-
-func TestGetContents(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "README.md"
- ref := ctx.Repo.Repository.DefaultBranch
-
- expectedContentsResponse := getExpectedReadmeContentsResponse()
-
- t.Run("Get README.md contents with GetContents()", func(t *testing.T) {
- fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, ref, false)
- assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-
- t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContents()", func(t *testing.T) {
- fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, "", false)
- assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-}
-
-func TestGetContentsOrListForDir(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "" // root dir
- ref := ctx.Repo.Repository.DefaultBranch
-
- readmeContentsResponse := getExpectedReadmeContentsResponse()
- // because will be in a list, doesn't have encoding and content
- readmeContentsResponse.Encoding = nil
- readmeContentsResponse.Content = nil
-
- expectedContentsListResponse := []*api.ContentsResponse{
- readmeContentsResponse,
- }
-
- t.Run("Get root dir contents with GetContentsOrList()", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref)
- assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-
- t.Run("Get root dir contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "")
- assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-}
-
-func TestGetContentsOrListForFile(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- treePath := "README.md"
- ref := ctx.Repo.Repository.DefaultBranch
-
- expectedContentsResponse := getExpectedReadmeContentsResponse()
-
- t.Run("Get README.md contents with GetContentsOrList()", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref)
- assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-
- t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) {
- fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "")
- assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
- assert.NoError(t, err)
- })
-}
-
-func TestGetContentsErrors(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- treePath := "README.md"
- ref := repo.DefaultBranch
-
- t.Run("bad treePath", func(t *testing.T) {
- badTreePath := "bad/tree.md"
- fileContentResponse, err := GetContents(repo, badTreePath, ref, false)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
- assert.Nil(t, fileContentResponse)
- })
-
- t.Run("bad ref", func(t *testing.T) {
- badRef := "bad_ref"
- fileContentResponse, err := GetContents(repo, treePath, badRef, false)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
- assert.Nil(t, fileContentResponse)
- })
-}
-
-func TestGetContentsOrListErrors(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- treePath := "README.md"
- ref := repo.DefaultBranch
-
- t.Run("bad treePath", func(t *testing.T) {
- badTreePath := "bad/tree.md"
- fileContentResponse, err := GetContentsOrList(repo, badTreePath, ref)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
- assert.Nil(t, fileContentResponse)
- })
-
- t.Run("bad ref", func(t *testing.T) {
- badRef := "bad_ref"
- fileContentResponse, err := GetContentsOrList(repo, treePath, badRef)
- assert.Error(t, err)
- assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
- assert.Nil(t, fileContentResponse)
- })
-}
-
-func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx := test.MockContext(t, "user2/repo15")
- ctx.SetParams(":id", "15")
- test.LoadRepo(t, ctx, 15)
- test.LoadUser(t, ctx, 2)
- test.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
-
- t.Run("empty repo", func(t *testing.T) {
- contents, err := GetContentsOrList(repo, "", "")
- assert.NoError(t, err)
- assert.Empty(t, contents)
- })
-}
+++ /dev/null
-// 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 (
- "fmt"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- repo_module "code.gitea.io/gitea/modules/repository"
- api "code.gitea.io/gitea/modules/structs"
-)
-
-// DeleteRepoFileOptions holds the repository delete file options
-type DeleteRepoFileOptions struct {
- LastCommitID string
- OldBranch string
- NewBranch string
- TreePath string
- Message string
- SHA string
- Author *IdentityOptions
- Committer *IdentityOptions
- Dates *CommitDateOptions
- Signoff bool
-}
-
-// DeleteRepoFile deletes a file in the given repository
-func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepoFileOptions) (*api.FileResponse, error) {
- // If no branch name is set, assume the repo's default branch
- if opts.OldBranch == "" {
- opts.OldBranch = repo.DefaultBranch
- }
- if opts.NewBranch == "" {
- opts.NewBranch = opts.OldBranch
- }
-
- // oldBranch must exist for this operation
- if _, err := repo_module.GetBranch(repo, opts.OldBranch); err != nil {
- return nil, err
- }
-
- // A NewBranch can be specified for the file to be created/updated in a new branch.
- // Check to make sure the branch does not already exist, otherwise we can't proceed.
- // If we aren't branching to a new branch, make sure user can commit to the given branch
- if opts.NewBranch != opts.OldBranch {
- newBranch, err := repo_module.GetBranch(repo, opts.NewBranch)
- if err != nil && !git.IsErrBranchNotExist(err) {
- return nil, err
- }
- if newBranch != nil {
- return nil, models.ErrBranchAlreadyExists{
- BranchName: opts.NewBranch,
- }
- }
- } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
- return nil, err
- }
-
- // Check that the path given in opts.treeName is valid (not a git path)
- treePath := CleanUploadFileName(opts.TreePath)
- if treePath == "" {
- return nil, models.ErrFilenameInvalid{
- Path: opts.TreePath,
- }
- }
-
- message := strings.TrimSpace(opts.Message)
-
- author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
-
- t, err := NewTemporaryUploadRepository(repo)
- if err != nil {
- return nil, err
- }
- defer t.Close()
- if err := t.Clone(opts.OldBranch); err != nil {
- return nil, err
- }
- if err := t.SetDefaultIndex(); err != nil {
- return nil, err
- }
-
- // Get the commit of the original branch
- commit, err := t.GetBranchCommit(opts.OldBranch)
- if err != nil {
- return nil, err // Couldn't get a commit for the branch
- }
-
- // Assigned LastCommitID in opts if it hasn't been set
- if opts.LastCommitID == "" {
- opts.LastCommitID = commit.ID.String()
- } else {
- lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
- if err != nil {
- return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
- }
- opts.LastCommitID = lastCommitID.String()
- }
-
- // Get the files in the index
- filesInIndex, err := t.LsFiles(opts.TreePath)
- if err != nil {
- return nil, fmt.Errorf("DeleteRepoFile: %v", err)
- }
-
- // Find the file we want to delete in the index
- inFilelist := false
- for _, file := range filesInIndex {
- if file == opts.TreePath {
- inFilelist = true
- break
- }
- }
- if !inFilelist {
- return nil, models.ErrRepoFileDoesNotExist{
- Path: opts.TreePath,
- }
- }
-
- // Get the entry of treePath and check if the SHA given is the same as the file
- entry, err := commit.GetTreeEntryByPath(treePath)
- if err != nil {
- return nil, err
- }
- if opts.SHA != "" {
- // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
- if opts.SHA != entry.ID.String() {
- return nil, models.ErrSHADoesNotMatch{
- Path: treePath,
- GivenSHA: opts.SHA,
- CurrentSHA: entry.ID.String(),
- }
- }
- } else if opts.LastCommitID != "" {
- // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
- // an error, but only if we aren't creating a new branch.
- if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
- // CommitIDs don't match, but we don't want to throw a ErrCommitIDDoesNotMatch unless
- // this specific file has been edited since opts.LastCommitID
- if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
- return nil, err
- } else if changed {
- return nil, models.ErrCommitIDDoesNotMatch{
- GivenCommitID: opts.LastCommitID,
- CurrentCommitID: opts.LastCommitID,
- }
- }
- // The file wasn't modified, so we are good to delete it
- }
- } else {
- // When deleting a file, a lastCommitID or SHA needs to be given to make sure other commits haven't been
- // made. We throw an error if one wasn't provided.
- return nil, models.ErrSHAOrCommitIDNotProvided{}
- }
-
- // Remove the file from the index
- if err := t.RemoveFilesFromIndex(opts.TreePath); err != nil {
- return nil, err
- }
-
- // Now write the tree
- treeHash, err := t.WriteTree()
- if err != nil {
- return nil, err
- }
-
- // Now commit the tree
- var commitHash string
- if opts.Dates != nil {
- commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
- } else {
- commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
- }
- if err != nil {
- return nil, err
- }
-
- // Then push this tree to NewBranch
- if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
- return nil, err
- }
-
- commit, err = t.GetCommit(commitHash)
- if err != nil {
- return nil, err
- }
-
- file, err := GetFileResponseFromCommit(repo, commit, opts.NewBranch, treePath)
- if err != nil {
- return nil, err
- }
- return file, nil
-}
+++ /dev/null
-// 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 (
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/services/gitdiff"
-)
-
-// GetDiffPreview produces and returns diff result of a file which is not yet committed.
-func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*gitdiff.Diff, error) {
- if branch == "" {
- branch = repo.DefaultBranch
- }
- t, err := NewTemporaryUploadRepository(repo)
- if err != nil {
- return nil, err
- }
- defer t.Close()
- if err := t.Clone(branch); err != nil {
- return nil, err
- }
- if err := t.SetDefaultIndex(); err != nil {
- return nil, err
- }
-
- // Add the object to the database
- objectHash, err := t.HashObject(strings.NewReader(content))
- if err != nil {
- return nil, err
- }
-
- // Add the object to the index
- if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
- return nil, err
- }
- return t.DiffIndex()
-}
+++ /dev/null
-// 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"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/json"
- "code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/services/gitdiff"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetDiffPreview(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- branch := ctx.Repo.Repository.DefaultBranch
- treePath := "README.md"
- content := "# repo1\n\nDescription for repo1\nthis is a new line"
-
- expectedDiff := &gitdiff.Diff{
- TotalAddition: 2,
- TotalDeletion: 1,
- Files: []*gitdiff.DiffFile{
- {
- Name: "README.md",
- OldName: "README.md",
- Index: 1,
- Addition: 2,
- Deletion: 1,
- Type: 2,
- IsCreated: false,
- IsDeleted: false,
- IsBin: false,
- IsLFSFile: false,
- IsRenamed: false,
- IsSubmodule: false,
- Sections: []*gitdiff.DiffSection{
- {
- FileName: "README.md",
- Name: "",
- Lines: []*gitdiff.DiffLine{
- {
- LeftIdx: 0,
- RightIdx: 0,
- Type: 4,
- Content: "@@ -1,3 +1,4 @@",
- Comments: nil,
- SectionInfo: &gitdiff.DiffLineSectionInfo{
- Path: "README.md",
- LastLeftIdx: 0,
- LastRightIdx: 0,
- LeftIdx: 1,
- RightIdx: 1,
- LeftHunkSize: 3,
- RightHunkSize: 4,
- },
- },
- {
- LeftIdx: 1,
- RightIdx: 1,
- Type: 1,
- Content: " # repo1",
- Comments: nil,
- },
- {
- LeftIdx: 2,
- RightIdx: 2,
- Type: 1,
- Content: " ",
- Comments: nil,
- },
- {
- LeftIdx: 3,
- RightIdx: 0,
- Match: 4,
- Type: 3,
- Content: "-Description for repo1",
- Comments: nil,
- },
- {
- LeftIdx: 0,
- RightIdx: 3,
- Match: 3,
- Type: 2,
- Content: "+Description for repo1",
- Comments: nil,
- },
- {
- LeftIdx: 0,
- RightIdx: 4,
- Match: -1,
- Type: 2,
- Content: "+this is a new line",
- Comments: nil,
- },
- },
- },
- },
- IsIncomplete: false,
- },
- },
- IsIncomplete: false,
- }
- expectedDiff.NumFiles = len(expectedDiff.Files)
-
- t.Run("with given branch", func(t *testing.T) {
- diff, err := GetDiffPreview(ctx.Repo.Repository, branch, treePath, content)
- assert.NoError(t, err)
- expectedBs, err := json.Marshal(expectedDiff)
- assert.NoError(t, err)
- bs, err := json.Marshal(diff)
- assert.NoError(t, err)
- assert.EqualValues(t, expectedBs, bs)
- })
-
- t.Run("empty branch, same results", func(t *testing.T) {
- diff, err := GetDiffPreview(ctx.Repo.Repository, "", treePath, content)
- assert.NoError(t, err)
- expectedBs, err := json.Marshal(expectedDiff)
- assert.NoError(t, err)
- bs, err := json.Marshal(diff)
- assert.NoError(t, err)
- assert.EqualValues(t, expectedBs, bs)
- })
-}
-
-func TestGetDiffPreviewErrors(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- branch := ctx.Repo.Repository.DefaultBranch
- treePath := "README.md"
- content := "# repo1\n\nDescription for repo1\nthis is a new line"
-
- t.Run("empty repo", func(t *testing.T) {
- diff, err := GetDiffPreview(&models.Repository{}, branch, treePath, content)
- assert.Nil(t, diff)
- assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]")
- })
-
- t.Run("bad branch", func(t *testing.T) {
- badBranch := "bad_branch"
- diff, err := GetDiffPreview(ctx.Repo.Repository, badBranch, treePath, content)
- assert.Nil(t, diff)
- assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]")
- })
-
- t.Run("empty treePath", func(t *testing.T) {
- diff, err := GetDiffPreview(ctx.Repo.Repository, branch, "", content)
- assert.Nil(t, diff)
- assert.EqualError(t, err, "path is invalid [path: ]")
- })
-}
+++ /dev/null
-// 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 (
- "fmt"
- "net/url"
- "strings"
- "time"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- api "code.gitea.io/gitea/modules/structs"
-)
-
-// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
-func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) {
- fileContents, _ := GetContents(repo, treeName, branch, false) // ok if fails, then will be nil
- fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
- verification := GetPayloadCommitVerification(commit)
- fileResponse := &api.FileResponse{
- Content: fileContents,
- Commit: fileCommitResponse,
- Verification: verification,
- }
- return fileResponse, nil
-}
-
-// GetFileCommitResponse Constructs a FileCommitResponse from a Commit object
-func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.FileCommitResponse, error) {
- if repo == nil {
- return nil, fmt.Errorf("repo cannot be nil")
- }
- if commit == nil {
- return nil, fmt.Errorf("commit cannot be nil")
- }
- commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()))
- commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String()))
- parents := make([]*api.CommitMeta, commit.ParentCount())
- for i := 0; i <= commit.ParentCount(); i++ {
- if parent, err := commit.Parent(i); err == nil && parent != nil {
- parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(parent.ID.String()))
- parents[i] = &api.CommitMeta{
- SHA: parent.ID.String(),
- URL: parentCommitURL.String(),
- }
- }
- }
- commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()))
- fileCommit := &api.FileCommitResponse{
- CommitMeta: api.CommitMeta{
- SHA: commit.ID.String(),
- URL: commitURL.String(),
- },
- HTMLURL: commitHTMLURL.String(),
- Author: &api.CommitUser{
- Identity: api.Identity{
- Name: commit.Author.Name,
- Email: commit.Author.Email,
- },
- Date: commit.Author.When.UTC().Format(time.RFC3339),
- },
- Committer: &api.CommitUser{
- Identity: api.Identity{
- Name: commit.Committer.Name,
- Email: commit.Committer.Email,
- },
- Date: commit.Committer.When.UTC().Format(time.RFC3339),
- },
- Message: commit.Message(),
- Tree: &api.CommitMeta{
- URL: commitTreeURL.String(),
- SHA: commit.Tree.ID.String(),
- },
- Parents: parents,
- }
- return fileCommit, nil
-}
-
-// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
-func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (authorUser, committerUser *models.User) {
- // Committer and author are optional. If they are not the doer (not same email address)
- // then we use bogus User objects for them to store their FullName and Email.
- // If only one of the two are provided, we set both of them to it.
- // If neither are provided, both are the doer.
- if committer != nil && committer.Email != "" {
- if doer != nil && strings.EqualFold(doer.Email, committer.Email) {
- committerUser = doer // the committer is the doer, so will use their user object
- if committer.Name != "" {
- committerUser.FullName = committer.Name
- }
- } else {
- committerUser = &models.User{
- FullName: committer.Name,
- Email: committer.Email,
- }
- }
- }
- if author != nil && author.Email != "" {
- if doer != nil && strings.EqualFold(doer.Email, author.Email) {
- authorUser = doer // the author is the doer, so will use their user object
- if authorUser.Name != "" {
- authorUser.FullName = author.Name
- }
- } else {
- authorUser = &models.User{
- FullName: author.Name,
- Email: author.Email,
- }
- }
- }
- if authorUser == nil {
- if committerUser != nil {
- authorUser = committerUser // No valid author was given so use the committer
- } else if doer != nil {
- authorUser = doer // No valid author was given and no valid committer so use the doer
- }
- }
- if committerUser == nil {
- committerUser = authorUser // No valid committer so use the author as the committer (was set to a valid user above)
- }
- return authorUser, committerUser
-}
+++ /dev/null
-// 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"
-
- "code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/git"
- "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 getExpectedFileResponse() *api.FileResponse {
- treePath := "README.md"
- sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
- encoding := "base64"
- content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
- selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
- htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
- gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha
- downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
- return &api.FileResponse{
- Content: &api.ContentsResponse{
- Name: treePath,
- Path: treePath,
- SHA: sha,
- Type: "file",
- Size: 30,
- Encoding: &encoding,
- Content: &content,
- URL: &selfURL,
- HTMLURL: &htmlURL,
- GitURL: &gitURL,
- DownloadURL: &downloadURL,
- Links: &api.FileLinksResponse{
- Self: &selfURL,
- GitURL: &gitURL,
- HTMLURL: &htmlURL,
- },
- },
- Commit: &api.FileCommitResponse{
- CommitMeta: api.CommitMeta{
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
- SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
- },
- HTMLURL: "https://try.gitea.io/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
- Author: &api.CommitUser{
- Identity: api.Identity{
- Name: "user1",
- Email: "address1@example.com",
- },
- Date: "2017-03-19T20:47:59Z",
- },
- Committer: &api.CommitUser{
- Identity: api.Identity{
- Name: "Ethan Koenig",
- Email: "ethantkoenig@gmail.com",
- },
- Date: "2017-03-19T20:47:59Z",
- },
- Parents: []*api.CommitMeta{},
- Message: "Initial commit\n",
- Tree: &api.CommitMeta{
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/2a2f1d4670728a2e10049e345bd7a276468beab6",
- SHA: "2a2f1d4670728a2e10049e345bd7a276468beab6",
- },
- },
- Verification: &api.PayloadCommitVerification{
- Verified: false,
- Reason: "gpg.error.not_signed_commit",
- Signature: "",
- Payload: "",
- },
- }
-}
-
-func TestGetFileResponseFromCommit(t *testing.T) {
- unittest.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)
- defer ctx.Repo.GitRepo.Close()
-
- repo := ctx.Repo.Repository
- branch := repo.DefaultBranch
- treePath := "README.md"
- gitRepo, _ := git.OpenRepository(repo.RepoPath())
- defer gitRepo.Close()
- commit, _ := gitRepo.GetBranchCommit(branch)
- expectedFileResponse := getExpectedFileResponse()
-
- fileResponse, err := GetFileResponseFromCommit(repo, commit, branch, treePath)
- assert.NoError(t, err)
- assert.EqualValues(t, expectedFileResponse, fileResponse)
-}
+++ /dev/null
-// 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
-
-package repofiles
-
-import (
- "path"
- "strings"
-)
-
-// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
-func CleanUploadFileName(name string) string {
- // Rebase the filename
- name = strings.Trim(path.Clean("/"+name), " /")
- // Git disallows any filenames to have a .git directory in them.
- for _, part := range strings.Split(name, "/") {
- if strings.ToLower(part) == ".git" {
- return ""
- }
- }
- return name
-}
+++ /dev/null
-// 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"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestCleanUploadFileName(t *testing.T) {
- t.Run("Clean regular file", func(t *testing.T) {
- name := "this/is/test"
- cleanName := CleanUploadFileName(name)
- expectedCleanName := name
- assert.EqualValues(t, expectedCleanName, cleanName)
- })
-
- t.Run("Clean a .git path", func(t *testing.T) {
- name := "this/is/test/.git"
- cleanName := CleanUploadFileName(name)
- expectedCleanName := ""
- assert.EqualValues(t, expectedCleanName, cleanName)
- })
-}
+++ /dev/null
-// 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 (
- "bytes"
- "context"
- "fmt"
- "io"
- "os"
- "regexp"
- "strings"
- "time"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/services/gitdiff"
-)
-
-// TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone
-type TemporaryUploadRepository struct {
- repo *models.Repository
- gitRepo *git.Repository
- basePath string
-}
-
-// NewTemporaryUploadRepository creates a new temporary upload repository
-func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) {
- basePath, err := models.CreateTemporaryPath("upload")
- if err != nil {
- return nil, err
- }
- t := &TemporaryUploadRepository{repo: repo, basePath: basePath}
- return t, nil
-}
-
-// Close the repository cleaning up all files
-func (t *TemporaryUploadRepository) Close() {
- defer t.gitRepo.Close()
- if err := models.RemoveTemporaryPath(t.basePath); err != nil {
- log.Error("Failed to remove temporary path %s: %v", t.basePath, err)
- }
-}
-
-// Clone the base repository to our path and set branch as the HEAD
-func (t *TemporaryUploadRepository) Clone(branch string) error {
- if _, err := git.NewCommand("clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath).Run(); err != nil {
- stderr := err.Error()
- if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
- return git.ErrBranchNotExist{
- Name: branch,
- }
- } else if matched, _ := regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
- return models.ErrRepoNotExist{
- ID: t.repo.ID,
- UID: t.repo.OwnerID,
- OwnerName: t.repo.OwnerName,
- Name: t.repo.Name,
- }
- } else {
- return fmt.Errorf("Clone: %v %s", err, stderr)
- }
- }
- gitRepo, err := git.OpenRepository(t.basePath)
- if err != nil {
- return err
- }
- t.gitRepo = gitRepo
- return nil
-}
-
-// SetDefaultIndex sets the git index to our HEAD
-func (t *TemporaryUploadRepository) SetDefaultIndex() error {
- if _, err := git.NewCommand("read-tree", "HEAD").RunInDir(t.basePath); err != nil {
- return fmt.Errorf("SetDefaultIndex: %v", err)
- }
- return nil
-}
-
-// LsFiles checks if the given filename arguments are in the index
-func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, error) {
- stdOut := new(bytes.Buffer)
- stdErr := new(bytes.Buffer)
-
- cmdArgs := []string{"ls-files", "-z", "--"}
- for _, arg := range filenames {
- if arg != "" {
- cmdArgs = append(cmdArgs, arg)
- }
- }
-
- if err := git.NewCommand(cmdArgs...).RunInDirPipeline(t.basePath, stdOut, stdErr); err != nil {
- log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
- err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
- return nil, err
- }
-
- filelist := make([]string, len(filenames))
- for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
- filelist = append(filelist, string(line))
- }
-
- return filelist, nil
-}
-
-// RemoveFilesFromIndex removes the given files from the index
-func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error {
- stdOut := new(bytes.Buffer)
- stdErr := new(bytes.Buffer)
- stdIn := new(bytes.Buffer)
- for _, file := range filenames {
- if file != "" {
- stdIn.WriteString("0 0000000000000000000000000000000000000000\t")
- stdIn.WriteString(file)
- stdIn.WriteByte('\000')
- }
- }
-
- if err := git.NewCommand("update-index", "--remove", "-z", "--index-info").RunInDirFullPipeline(t.basePath, stdOut, stdErr, stdIn); err != nil {
- log.Error("Unable to update-index for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
- return fmt.Errorf("Unable to update-index for temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
- }
- return nil
-}
-
-// HashObject writes the provided content to the object db and returns its hash
-func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error) {
- stdOut := new(bytes.Buffer)
- stdErr := new(bytes.Buffer)
-
- if err := git.NewCommand("hash-object", "-w", "--stdin").RunInDirFullPipeline(t.basePath, stdOut, stdErr, content); err != nil {
- log.Error("Unable to hash-object to temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
- return "", fmt.Errorf("Unable to hash-object to temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
- }
-
- return strings.TrimSpace(stdOut.String()), nil
-}
-
-// AddObjectToIndex adds the provided object hash to the index with the provided mode and path
-func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPath string) error {
- if _, err := git.NewCommand("update-index", "--add", "--replace", "--cacheinfo", mode, objectHash, objectPath).RunInDir(t.basePath); err != nil {
- stderr := err.Error()
- if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched {
- return models.ErrFilePathInvalid{
- Message: objectPath,
- Path: objectPath,
- }
- }
- log.Error("Unable to add object to index: %s %s %s in temporary repo %s(%s) Error: %v", mode, objectHash, objectPath, t.repo.FullName(), t.basePath, err)
- return fmt.Errorf("Unable to add object to index at %s in temporary repo %s Error: %v", objectPath, t.repo.FullName(), err)
- }
- return nil
-}
-
-// WriteTree writes the current index as a tree to the object db and returns its hash
-func (t *TemporaryUploadRepository) WriteTree() (string, error) {
- stdout, err := git.NewCommand("write-tree").RunInDir(t.basePath)
- if err != nil {
- log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err)
- return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %v", t.repo.FullName(), err)
- }
- return strings.TrimSpace(stdout), nil
-}
-
-// GetLastCommit gets the last commit ID SHA of the repo
-func (t *TemporaryUploadRepository) GetLastCommit() (string, error) {
- return t.GetLastCommitByRef("HEAD")
-}
-
-// GetLastCommitByRef gets the last commit ID SHA of the repo by ref
-func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, error) {
- if ref == "" {
- ref = "HEAD"
- }
- stdout, err := git.NewCommand("rev-parse", ref).RunInDir(t.basePath)
- if err != nil {
- log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err)
- return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %v", ref, t.repo.FullName(), err)
- }
- return strings.TrimSpace(stdout), nil
-}
-
-// CommitTree creates a commit from a given tree for the user with provided message
-func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, treeHash string, message string, signoff bool) (string, error) {
- return t.CommitTreeWithDate(author, committer, treeHash, message, signoff, time.Now(), time.Now())
-}
-
-// CommitTreeWithDate creates a commit from a given tree for the user with provided message
-func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models.User, treeHash string, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
- authorSig := author.NewGitSig()
- committerSig := committer.NewGitSig()
-
- err := git.LoadGitVersion()
- if err != nil {
- return "", fmt.Errorf("Unable to get git version: %v", err)
- }
-
- // Because this may call hooks we should pass in the environment
- env := append(os.Environ(),
- "GIT_AUTHOR_NAME="+authorSig.Name,
- "GIT_AUTHOR_EMAIL="+authorSig.Email,
- "GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
- "GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
- )
-
- messageBytes := new(bytes.Buffer)
- _, _ = messageBytes.WriteString(message)
- _, _ = messageBytes.WriteString("\n")
-
- args := []string{"commit-tree", treeHash, "-p", "HEAD"}
-
- // Determine if we should sign
- if git.CheckGitVersionAtLeast("1.7.9") == nil {
- sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
- if sign {
- args = append(args, "-S"+keyID)
- if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
- if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
- // Add trailers
- _, _ = messageBytes.WriteString("\n")
- _, _ = messageBytes.WriteString("Co-authored-by: ")
- _, _ = messageBytes.WriteString(committerSig.String())
- _, _ = messageBytes.WriteString("\n")
- _, _ = messageBytes.WriteString("Co-committed-by: ")
- _, _ = messageBytes.WriteString(committerSig.String())
- _, _ = messageBytes.WriteString("\n")
- }
- committerSig = signer
- }
- } else if git.CheckGitVersionAtLeast("2.0.0") == nil {
- args = append(args, "--no-gpg-sign")
- }
- }
-
- if signoff {
- // Signed-off-by
- _, _ = messageBytes.WriteString("\n")
- _, _ = messageBytes.WriteString("Signed-off-by: ")
- _, _ = messageBytes.WriteString(committerSig.String())
- }
-
- env = append(env,
- "GIT_COMMITTER_NAME="+committerSig.Name,
- "GIT_COMMITTER_EMAIL="+committerSig.Email,
- )
-
- stdout := new(bytes.Buffer)
- stderr := new(bytes.Buffer)
- if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil {
- log.Error("Unable to commit-tree in temporary repo: %s (%s) Error: %v\nStdout: %s\nStderr: %s",
- t.repo.FullName(), t.basePath, err, stdout, stderr)
- return "", fmt.Errorf("Unable to commit-tree in temporary repo: %s Error: %v\nStdout: %s\nStderr: %s",
- t.repo.FullName(), err, stdout, stderr)
- }
- return strings.TrimSpace(stdout.String()), nil
-}
-
-// Push the provided commitHash to the repository branch by the provided user
-func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, branch string) error {
- // Because calls hooks we need to pass in the environment
- env := models.PushingEnvironment(doer, t.repo)
- if err := git.Push(t.basePath, git.PushOptions{
- Remote: t.repo.RepoPath(),
- Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch),
- Env: env,
- }); err != nil {
- if git.IsErrPushOutOfDate(err) {
- return err
- } else if git.IsErrPushRejected(err) {
- rejectErr := err.(*git.ErrPushRejected)
- log.Info("Unable to push back to repo from temporary repo due to rejection: %s (%s)\nStdout: %s\nStderr: %s\nError: %v",
- t.repo.FullName(), t.basePath, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
- return err
- }
- log.Error("Unable to push back to repo from temporary repo: %s (%s)\nError: %v",
- t.repo.FullName(), t.basePath, err)
- return fmt.Errorf("Unable to push back to repo from temporary repo: %s (%s) Error: %v",
- t.repo.FullName(), t.basePath, err)
- }
- return nil
-}
-
-// DiffIndex returns a Diff of the current index to the head
-func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
- stdoutReader, stdoutWriter, err := os.Pipe()
- if err != nil {
- log.Error("Unable to open stdout pipe: %v", err)
- return nil, fmt.Errorf("Unable to open stdout pipe: %v", err)
- }
- defer func() {
- _ = stdoutReader.Close()
- _ = stdoutWriter.Close()
- }()
- stderr := new(bytes.Buffer)
- var diff *gitdiff.Diff
- var finalErr error
-
- if err := git.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
- RunInDirTimeoutEnvFullPipelineFunc(nil, 30*time.Second, t.basePath, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error {
- _ = stdoutWriter.Close()
- diff, finalErr = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
- if finalErr != nil {
- log.Error("ParsePatch: %v", finalErr)
- cancel()
- }
- _ = stdoutReader.Close()
- return finalErr
- }); err != nil {
- if finalErr != nil {
- log.Error("Unable to ParsePatch in temporary repo %s (%s). Error: %v", t.repo.FullName(), t.basePath, finalErr)
- return nil, finalErr
- }
- log.Error("Unable to run diff-index pipeline in temporary repo %s (%s). Error: %v\nStderr: %s",
- t.repo.FullName(), t.basePath, err, stderr)
- return nil, fmt.Errorf("Unable to run diff-index pipeline in temporary repo %s. Error: %v\nStderr: %s",
- t.repo.FullName(), err, stderr)
- }
-
- diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.basePath, "--cached", "HEAD")
- if err != nil {
- return nil, err
- }
-
- return diff, nil
-}
-
-// GetBranchCommit Gets the commit object of the given branch
-func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, error) {
- if t.gitRepo == nil {
- return nil, fmt.Errorf("repository has not been cloned")
- }
- return t.gitRepo.GetBranchCommit(branch)
-}
-
-// GetCommit Gets the commit object of the given commit ID
-func (t *TemporaryUploadRepository) GetCommit(commitID string) (*git.Commit, error) {
- if t.gitRepo == nil {
- return nil, fmt.Errorf("repository has not been cloned")
- }
- return t.gitRepo.GetCommit(commitID)
-}
+++ /dev/null
-// 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 (
- "fmt"
- "net/url"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/setting"
- api "code.gitea.io/gitea/modules/structs"
-)
-
-// GetTreeBySHA get the GitTreeResponse of a repository using a sha hash.
-func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recursive bool) (*api.GitTreeResponse, error) {
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- defer gitRepo.Close()
- gitTree, err := gitRepo.GetTree(sha)
- if err != nil || gitTree == nil {
- return nil, models.ErrSHANotFound{
- SHA: sha,
- }
- }
- tree := new(api.GitTreeResponse)
- tree.SHA = gitTree.ResolvedID.String()
- tree.URL = repo.APIURL() + "/git/trees/" + url.PathEscape(tree.SHA)
- var entries git.Entries
- if recursive {
- entries, err = gitTree.ListEntriesRecursive()
- } else {
- entries, err = gitTree.ListEntries()
- }
- if err != nil {
- return nil, err
- }
- apiURL := repo.APIURL()
- apiURLLen := len(apiURL)
-
- // 51 is len(sha1) + len("/git/blobs/"). 40 + 11.
- blobURL := make([]byte, apiURLLen+51)
- copy(blobURL, apiURL)
- copy(blobURL[apiURLLen:], "/git/blobs/")
-
- // 51 is len(sha1) + len("/git/trees/"). 40 + 11.
- treeURL := make([]byte, apiURLLen+51)
- copy(treeURL, apiURL)
- copy(treeURL[apiURLLen:], "/git/trees/")
-
- // 40 is the size of the sha1 hash in hexadecimal format.
- copyPos := len(treeURL) - 40
-
- if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
- perPage = setting.API.DefaultGitTreesPerPage
- }
- if page <= 0 {
- page = 1
- }
- tree.Page = page
- tree.TotalCount = len(entries)
- rangeStart := perPage * (page - 1)
- if rangeStart >= len(entries) {
- return tree, nil
- }
- var rangeEnd int
- if len(entries) > perPage {
- tree.Truncated = true
- }
- if rangeStart+perPage < len(entries) {
- rangeEnd = rangeStart + perPage
- } else {
- rangeEnd = len(entries)
- }
- tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
- for e := rangeStart; e < rangeEnd; e++ {
- i := e - rangeStart
-
- tree.Entries[i].Path = entries[e].Name()
- tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode())
- tree.Entries[i].Type = entries[e].Type()
- tree.Entries[i].Size = entries[e].Size()
- tree.Entries[i].SHA = entries[e].ID.String()
-
- if entries[e].IsDir() {
- copy(treeURL[copyPos:], entries[e].ID.String())
- tree.Entries[i].URL = string(treeURL)
- } else {
- copy(blobURL[copyPos:], entries[e].ID.String())
- tree.Entries[i].URL = string(blobURL)
- }
- }
- return tree, nil
-}
+++ /dev/null
-// 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"
-
- "code.gitea.io/gitea/models/unittest"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/test"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetTreeBySHA(t *testing.T) {
- unittest.PrepareTestEnv(t)
- ctx := test.MockContext(t, "user2/repo1")
- test.LoadRepo(t, ctx, 1)
- test.LoadRepoCommit(t, ctx)
- test.LoadUser(t, ctx, 2)
- test.LoadGitRepo(t, ctx)
- defer ctx.Repo.GitRepo.Close()
-
- sha := ctx.Repo.Repository.DefaultBranch
- page := 1
- perPage := 10
- ctx.SetParams(":id", "1")
- ctx.SetParams(":sha", sha)
-
- tree, err := GetTreeBySHA(ctx.Repo.Repository, ctx.Params(":sha"), page, perPage, true)
- assert.NoError(t, err)
- expectedTree := &api.GitTreeResponse{
- SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/65f1bf27bc3bf70f64657658635e66094edbcb4d",
- Entries: []api.GitEntry{
- {
- Path: "README.md",
- Mode: "100644",
- Type: "blob",
- Size: 30,
- SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
- },
- },
- Truncated: false,
- Page: 1,
- TotalCount: 1,
- }
-
- assert.EqualValues(t, expectedTree, tree)
-}
+++ /dev/null
-// 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 (
- "bytes"
- "fmt"
- "path"
- "strings"
- "time"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/charset"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/lfs"
- "code.gitea.io/gitea/modules/log"
- repo_module "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
-
- stdcharset "golang.org/x/net/html/charset"
- "golang.org/x/text/transform"
-)
-
-// IdentityOptions for a person's identity like an author or committer
-type IdentityOptions struct {
- Name string
- Email string
-}
-
-// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
-type CommitDateOptions struct {
- Author time.Time
- Committer time.Time
-}
-
-// UpdateRepoFileOptions holds the repository file update options
-type UpdateRepoFileOptions struct {
- LastCommitID string
- OldBranch string
- NewBranch string
- TreePath string
- FromTreePath string
- Message string
- Content string
- SHA string
- IsNewFile bool
- Author *IdentityOptions
- Committer *IdentityOptions
- Dates *CommitDateOptions
- Signoff bool
-}
-
-func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string, bool) {
- reader, err := entry.Blob().DataAsync()
- if err != nil {
- // return default
- return "UTF-8", false
- }
- defer reader.Close()
- buf := make([]byte, 1024)
- n, err := util.ReadAtMost(reader, buf)
- if err != nil {
- // return default
- return "UTF-8", false
- }
- buf = buf[:n]
-
- if setting.LFS.StartServer {
- pointer, _ := lfs.ReadPointerFromBuffer(buf)
- if pointer.IsValid() {
- meta, err := repo.GetLFSMetaObjectByOid(pointer.Oid)
- if err != nil && err != models.ErrLFSObjectNotExist {
- // return default
- return "UTF-8", false
- }
- if meta != nil {
- dataRc, err := lfs.ReadMetaObject(pointer)
- if err != nil {
- // return default
- return "UTF-8", false
- }
- defer dataRc.Close()
- buf = make([]byte, 1024)
- n, err = util.ReadAtMost(dataRc, buf)
- if err != nil {
- // return default
- return "UTF-8", false
- }
- buf = buf[:n]
- }
- }
- }
-
- encoding, err := charset.DetectEncoding(buf)
- if err != nil {
- // just default to utf-8 and no bom
- return "UTF-8", false
- }
- if encoding == "UTF-8" {
- return encoding, bytes.Equal(buf[0:3], charset.UTF8BOM)
- }
- charsetEncoding, _ := stdcharset.Lookup(encoding)
- if charsetEncoding == nil {
- return "UTF-8", false
- }
-
- result, n, err := transform.String(charsetEncoding.NewDecoder(), string(buf))
- if err != nil {
- // return default
- return "UTF-8", false
- }
-
- if n > 2 {
- return encoding, bytes.Equal([]byte(result)[0:3], charset.UTF8BOM)
- }
-
- return encoding, false
-}
-
-// CreateOrUpdateRepoFile adds or updates a file in the given repository
-func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *UpdateRepoFileOptions) (*structs.FileResponse, error) {
- // If no branch name is set, assume default branch
- if opts.OldBranch == "" {
- opts.OldBranch = repo.DefaultBranch
- }
- if opts.NewBranch == "" {
- opts.NewBranch = opts.OldBranch
- }
-
- // oldBranch must exist for this operation
- if _, err := repo_module.GetBranch(repo, opts.OldBranch); err != nil {
- return nil, err
- }
-
- // A NewBranch can be specified for the file to be created/updated in a new branch.
- // Check to make sure the branch does not already exist, otherwise we can't proceed.
- // If we aren't branching to a new branch, make sure user can commit to the given branch
- if opts.NewBranch != opts.OldBranch {
- existingBranch, err := repo_module.GetBranch(repo, opts.NewBranch)
- if existingBranch != nil {
- return nil, models.ErrBranchAlreadyExists{
- BranchName: opts.NewBranch,
- }
- }
- if err != nil && !git.IsErrBranchNotExist(err) {
- return nil, err
- }
- } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
- return nil, err
- }
-
- // If FromTreePath is not set, set it to the opts.TreePath
- if opts.TreePath != "" && opts.FromTreePath == "" {
- opts.FromTreePath = opts.TreePath
- }
-
- // Check that the path given in opts.treePath is valid (not a git path)
- treePath := CleanUploadFileName(opts.TreePath)
- if treePath == "" {
- return nil, models.ErrFilenameInvalid{
- Path: opts.TreePath,
- }
- }
- // If there is a fromTreePath (we are copying it), also clean it up
- fromTreePath := CleanUploadFileName(opts.FromTreePath)
- if fromTreePath == "" && opts.FromTreePath != "" {
- return nil, models.ErrFilenameInvalid{
- Path: opts.FromTreePath,
- }
- }
-
- message := strings.TrimSpace(opts.Message)
-
- author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
-
- t, err := NewTemporaryUploadRepository(repo)
- if err != nil {
- log.Error("%v", err)
- }
- defer t.Close()
- if err := t.Clone(opts.OldBranch); err != nil {
- return nil, err
- }
- if err := t.SetDefaultIndex(); err != nil {
- return nil, err
- }
-
- // Get the commit of the original branch
- commit, err := t.GetBranchCommit(opts.OldBranch)
- if err != nil {
- return nil, err // Couldn't get a commit for the branch
- }
-
- // Assigned LastCommitID in opts if it hasn't been set
- if opts.LastCommitID == "" {
- opts.LastCommitID = commit.ID.String()
- } else {
- lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
- if err != nil {
- return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
- }
- opts.LastCommitID = lastCommitID.String()
-
- }
-
- encoding := "UTF-8"
- bom := false
- executable := false
-
- if !opts.IsNewFile {
- fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
- if err != nil {
- return nil, err
- }
- if opts.SHA != "" {
- // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
- if opts.SHA != fromEntry.ID.String() {
- return nil, models.ErrSHADoesNotMatch{
- Path: treePath,
- GivenSHA: opts.SHA,
- CurrentSHA: fromEntry.ID.String(),
- }
- }
- } else if opts.LastCommitID != "" {
- // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
- // an error, but only if we aren't creating a new branch.
- if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
- if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
- return nil, err
- } else if changed {
- return nil, models.ErrCommitIDDoesNotMatch{
- GivenCommitID: opts.LastCommitID,
- CurrentCommitID: opts.LastCommitID,
- }
- }
- // The file wasn't modified, so we are good to delete it
- }
- } else {
- // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
- // haven't been made. We throw an error if one wasn't provided.
- return nil, models.ErrSHAOrCommitIDNotProvided{}
- }
- encoding, bom = detectEncodingAndBOM(fromEntry, repo)
- executable = fromEntry.IsExecutable()
- }
-
- // For the path where this file will be created/updated, we need to make
- // sure no parts of the path are existing files or links except for the last
- // item in the path which is the file name, and that shouldn't exist IF it is
- // a new file OR is being moved to a new path.
- treePathParts := strings.Split(treePath, "/")
- subTreePath := ""
- for index, part := range treePathParts {
- subTreePath = path.Join(subTreePath, part)
- entry, err := commit.GetTreeEntryByPath(subTreePath)
- if err != nil {
- if git.IsErrNotExist(err) {
- // Means there is no item with that name, so we're good
- break
- }
- return nil, err
- }
- if index < len(treePathParts)-1 {
- if !entry.IsDir() {
- return nil, models.ErrFilePathInvalid{
- Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
- Path: subTreePath,
- Name: part,
- Type: git.EntryModeBlob,
- }
- }
- } else if entry.IsLink() {
- return nil, models.ErrFilePathInvalid{
- Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
- Path: subTreePath,
- Name: part,
- Type: git.EntryModeSymlink,
- }
- } else if entry.IsDir() {
- return nil, models.ErrFilePathInvalid{
- Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
- Path: subTreePath,
- Name: part,
- Type: git.EntryModeTree,
- }
- } else if fromTreePath != treePath || opts.IsNewFile {
- // The entry shouldn't exist if we are creating new file or moving to a new path
- return nil, models.ErrRepoFileAlreadyExists{
- Path: treePath,
- }
- }
-
- }
-
- // Get the two paths (might be the same if not moving) from the index if they exist
- filesInIndex, err := t.LsFiles(opts.TreePath, opts.FromTreePath)
- if err != nil {
- return nil, fmt.Errorf("UpdateRepoFile: %v", err)
- }
- // If is a new file (not updating) then the given path shouldn't exist
- if opts.IsNewFile {
- for _, file := range filesInIndex {
- if file == opts.TreePath {
- return nil, models.ErrRepoFileAlreadyExists{
- Path: opts.TreePath,
- }
- }
- }
- }
-
- // Remove the old path from the tree
- if fromTreePath != treePath && len(filesInIndex) > 0 {
- for _, file := range filesInIndex {
- if file == fromTreePath {
- if err := t.RemoveFilesFromIndex(opts.FromTreePath); err != nil {
- return nil, err
- }
- }
- }
- }
-
- content := opts.Content
- if bom {
- content = string(charset.UTF8BOM) + content
- }
- if encoding != "UTF-8" {
- charsetEncoding, _ := stdcharset.Lookup(encoding)
- if charsetEncoding != nil {
- result, _, err := transform.String(charsetEncoding.NewEncoder(), content)
- if err != nil {
- // Look if we can't encode back in to the original we should just stick with utf-8
- log.Error("Error re-encoding %s (%s) as %s - will stay as UTF-8: %v", opts.TreePath, opts.FromTreePath, encoding, err)
- result = content
- }
- content = result
- } else {
- log.Error("Unknown encoding: %s", encoding)
- }
- }
- // Reset the opts.Content to our adjusted content to ensure that LFS gets the correct content
- opts.Content = content
- var lfsMetaObject *models.LFSMetaObject
-
- if setting.LFS.StartServer {
- // Check there is no way this can return multiple infos
- filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
- Attributes: []string{"filter"},
- Filenames: []string{treePath},
- })
- if err != nil {
- return nil, err
- }
-
- if filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" {
- // OK so we are supposed to LFS this data!
- pointer, err := lfs.GeneratePointer(strings.NewReader(opts.Content))
- if err != nil {
- return nil, err
- }
- lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: repo.ID}
- content = pointer.StringContent()
- }
- }
- // Add the object to the database
- objectHash, err := t.HashObject(strings.NewReader(content))
- if err != nil {
- return nil, err
- }
-
- // Add the object to the index
- if executable {
- if err := t.AddObjectToIndex("100755", objectHash, treePath); err != nil {
- return nil, err
- }
- } else {
- if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
- return nil, err
- }
- }
-
- // Now write the tree
- treeHash, err := t.WriteTree()
- if err != nil {
- return nil, err
- }
-
- // Now commit the tree
- var commitHash string
- if opts.Dates != nil {
- commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
- } else {
- commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
- }
- if err != nil {
- return nil, err
- }
-
- if lfsMetaObject != nil {
- // We have an LFS object - create it
- lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
- if err != nil {
- return nil, err
- }
- contentStore := lfs.NewContentStore()
- exist, err := contentStore.Exists(lfsMetaObject.Pointer)
- if err != nil {
- return nil, err
- }
- if !exist {
- if err := contentStore.Put(lfsMetaObject.Pointer, strings.NewReader(opts.Content)); err != nil {
- if _, err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
- return nil, fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
- }
- return nil, err
- }
- }
- }
-
- // Then push this tree to NewBranch
- if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
- log.Error("%T %v", err, err)
- return nil, err
- }
-
- commit, err = t.GetCommit(commitHash)
- if err != nil {
- return nil, err
- }
-
- file, err := GetFileResponseFromCommit(repo, commit, opts.NewBranch, treePath)
- if err != nil {
- return nil, err
- }
- return file, nil
-}
-
-// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
-func VerifyBranchProtection(repo *models.Repository, doer *models.User, branchName string, treePath string) error {
- protectedBranch, err := repo.GetBranchProtection(branchName)
- if err != nil {
- return err
- }
- if protectedBranch != nil {
- isUnprotectedFile := false
- glob := protectedBranch.GetUnprotectedFilePatterns()
- if len(glob) != 0 {
- isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath)
- }
- if !protectedBranch.CanUserPush(doer.ID) && !isUnprotectedFile {
- return models.ErrUserCannotCommit{
- UserName: doer.LowerName,
- }
- }
- if protectedBranch.RequireSignedCommits {
- _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), branchName)
- if err != nil {
- if !models.IsErrWontSign(err) {
- return err
- }
- return models.ErrUserCannotCommit{
- UserName: doer.LowerName,
- }
- }
- }
- patterns := protectedBranch.GetProtectedFilePatterns()
- for _, pat := range patterns {
- if pat.Match(strings.ToLower(treePath)) {
- return models.ErrFilePathProtected{
- Path: treePath,
- }
- }
- }
- }
- return nil
-}
+++ /dev/null
-// 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 (
- "fmt"
- "os"
- "path"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/lfs"
- "code.gitea.io/gitea/modules/setting"
-)
-
-// UploadRepoFileOptions contains the uploaded repository file options
-type UploadRepoFileOptions struct {
- LastCommitID string
- OldBranch string
- NewBranch string
- TreePath string
- Message string
- Files []string // In UUID format.
- Signoff bool
-}
-
-type uploadInfo struct {
- upload *models.Upload
- lfsMetaObject *models.LFSMetaObject
-}
-
-func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
- for _, info := range *infos {
- if info.lfsMetaObject == nil {
- continue
- }
- if !info.lfsMetaObject.Existing {
- if _, err := t.repo.RemoveLFSMetaObjectByOid(info.lfsMetaObject.Oid); err != nil {
- original = fmt.Errorf("%v, %v", original, err)
- }
- }
- }
- return original
-}
-
-// UploadRepoFiles uploads files to the given repository
-func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRepoFileOptions) error {
- if len(opts.Files) == 0 {
- return nil
- }
-
- uploads, err := models.GetUploadsByUUIDs(opts.Files)
- if err != nil {
- return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err)
- }
-
- names := make([]string, len(uploads))
- infos := make([]uploadInfo, len(uploads))
- for i, upload := range uploads {
- // Check file is not lfs locked, will return nil if lock setting not enabled
- filepath := path.Join(opts.TreePath, upload.Name)
- lfsLock, err := repo.GetTreePathLock(filepath)
- if err != nil {
- return err
- }
- if lfsLock != nil && lfsLock.OwnerID != doer.ID {
- return models.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: lfsLock.Owner.Name}
- }
-
- names[i] = upload.Name
- infos[i] = uploadInfo{upload: upload}
- }
-
- t, err := NewTemporaryUploadRepository(repo)
- if err != nil {
- return err
- }
- defer t.Close()
- if err := t.Clone(opts.OldBranch); err != nil {
- return err
- }
- if err := t.SetDefaultIndex(); err != nil {
- return err
- }
-
- var filename2attribute2info map[string]map[string]string
- if setting.LFS.StartServer {
- filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
- Attributes: []string{"filter"},
- Filenames: names,
- })
- if err != nil {
- return err
- }
- }
-
- // Copy uploaded files into repository.
- for i := range infos {
- if err := copyUploadedLFSFileIntoRepository(&infos[i], filename2attribute2info, t, opts.TreePath); err != nil {
- return err
- }
- }
-
- // Now write the tree
- treeHash, err := t.WriteTree()
- if err != nil {
- return err
- }
-
- // make author and committer the doer
- author := doer
- committer := doer
-
- // Now commit the tree
- commitHash, err := t.CommitTree(author, committer, treeHash, opts.Message, opts.Signoff)
- if err != nil {
- return err
- }
-
- // Now deal with LFS objects
- for i := range infos {
- if infos[i].lfsMetaObject == nil {
- continue
- }
- infos[i].lfsMetaObject, err = models.NewLFSMetaObject(infos[i].lfsMetaObject)
- if err != nil {
- // OK Now we need to cleanup
- return cleanUpAfterFailure(&infos, t, err)
- }
- // Don't move the files yet - we need to ensure that
- // everything can be inserted first
- }
-
- // OK now we can insert the data into the store - there's no way to clean up the store
- // once it's in there, it's in there.
- contentStore := lfs.NewContentStore()
- for _, info := range infos {
- if err := uploadToLFSContentStore(info, contentStore); err != nil {
- return cleanUpAfterFailure(&infos, t, err)
- }
- }
-
- // Then push this tree to NewBranch
- if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
- return err
- }
-
- return models.DeleteUploads(uploads...)
-}
-
-func copyUploadedLFSFileIntoRepository(info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error {
- file, err := os.Open(info.upload.LocalPath())
- if err != nil {
- return err
- }
- defer file.Close()
-
- var objectHash string
- if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" {
- // Handle LFS
- // FIXME: Inefficient! this should probably happen in models.Upload
- pointer, err := lfs.GeneratePointer(file)
- if err != nil {
- return err
- }
-
- info.lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
-
- if objectHash, err = t.HashObject(strings.NewReader(pointer.StringContent())); err != nil {
- return err
- }
- } else if objectHash, err = t.HashObject(file); err != nil {
- return err
- }
-
- // Add the object to the index
- return t.AddObjectToIndex("100644", objectHash, path.Join(treePath, info.upload.Name))
-}
-
-func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) error {
- if info.lfsMetaObject == nil {
- return nil
- }
- exist, err := contentStore.Exists(info.lfsMetaObject.Pointer)
- if err != nil {
- return err
- }
- if !exist {
- file, err := os.Open(info.upload.LocalPath())
- if err != nil {
- return err
- }
-
- defer file.Close()
- // FIXME: Put regenerates the hash and copies the file over.
- // I guess this strictly ensures the soundness of the store but this is inefficient.
- if err := contentStore.Put(info.lfsMetaObject.Pointer, file); err != nil {
- // OK Now we need to cleanup
- // Can't clean up the store, once uploaded there they're there.
- return err
- }
- }
- return nil
-}
+++ /dev/null
-// 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 (
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/structs"
-)
-
-// GetPayloadCommitVerification returns the verification information of a commit
-func GetPayloadCommitVerification(commit *git.Commit) *structs.PayloadCommitVerification {
- verification := &structs.PayloadCommitVerification{}
- commitVerification := models.ParseCommitWithSignature(commit)
- if commit.Signature != nil {
- verification.Signature = commit.Signature.Signature
- verification.Payload = commit.Signature.Payload
- }
- if commitVerification.SigningUser != nil {
- verification.Signer = &structs.PayloadUser{
- Name: commitVerification.SigningUser.Name,
- Email: commitVerification.SigningUser.Email,
- }
- }
- verification.Verified = commitVerification.Verified
- verification.Reason = commitVerification.Reason
- if verification.Reason == "" && !verification.Verified {
- verification.Reason = "gpg.error.not_signed_commit"
- }
- return verification
-}
+++ /dev/null
-// Copyright 2020 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 repository
-
-import (
- "fmt"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/git"
-)
-
-// GetBranch returns a branch by its name
-func GetBranch(repo *models.Repository, branch string) (*git.Branch, error) {
- if len(branch) == 0 {
- return nil, fmt.Errorf("GetBranch: empty string for branch")
- }
- gitRepo, err := git.OpenRepository(repo.RepoPath())
- if err != nil {
- return nil, err
- }
- defer gitRepo.Close()
-
- return gitRepo.GetBranch(branch)
-}
+++ /dev/null
-// Copyright 2020 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 repository
-
-import (
- "context"
- "fmt"
- "strings"
- "time"
-
- "code.gitea.io/gitea/models"
- admin_model "code.gitea.io/gitea/models/admin"
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
-
- "xorm.io/builder"
-)
-
-// GitFsck calls 'git fsck' to check repository health.
-func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
- log.Trace("Doing: GitFsck")
-
- if err := db.Iterate(
- db.DefaultContext,
- new(models.Repository),
- builder.Expr("id>0 AND is_fsck_enabled=?", true),
- func(idx int, bean interface{}) error {
- repo := bean.(*models.Repository)
- select {
- case <-ctx.Done():
- return db.ErrCancelledf("before fsck of %s", repo.FullName())
- default:
- }
- log.Trace("Running health check on repository %v", repo)
- repoPath := repo.RepoPath()
- if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
- log.Warn("Failed to health check repository (%v): %v", repo, err)
- if err = admin_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
- log.Error("CreateRepositoryNotice: %v", err)
- }
- }
- return nil
- },
- ); err != nil {
- log.Trace("Error: GitFsck: %v", err)
- return err
- }
-
- log.Trace("Finished: GitFsck")
- return nil
-}
-
-// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
-func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) error {
- log.Trace("Doing: GitGcRepos")
- args = append([]string{"gc"}, args...)
-
- if err := db.Iterate(
- db.DefaultContext,
- new(models.Repository),
- builder.Gt{"id": 0},
- func(idx int, bean interface{}) error {
- repo := bean.(*models.Repository)
- select {
- case <-ctx.Done():
- return db.ErrCancelledf("before GC of %s", repo.FullName())
- default:
- }
- log.Trace("Running git gc on %v", repo)
- command := git.NewCommandContext(ctx, args...).
- SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
- var stdout string
- var err error
- if timeout > 0 {
- var stdoutBytes []byte
- stdoutBytes, err = command.RunInDirTimeout(
- timeout,
- repo.RepoPath())
- stdout = string(stdoutBytes)
- } else {
- stdout, err = command.RunInDir(repo.RepoPath())
- }
-
- if err != nil {
- log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
- desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
- log.Error("CreateRepositoryNotice: %v", err)
- }
- return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
- }
-
- // Now update the size of the repository
- if err := repo.UpdateSize(db.DefaultContext); err != nil {
- log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
- desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
- log.Error("CreateRepositoryNotice: %v", err)
- }
- return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
- }
-
- return nil
- },
- ); err != nil {
- return err
- }
-
- log.Trace("Finished: GitGcRepos")
- return nil
-}
-
-func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) {
- repos := make([]*models.Repository, 0, 10)
- if err := db.Iterate(
- db.DefaultContext,
- new(models.Repository),
- builder.Gt{"id": 0},
- func(idx int, bean interface{}) error {
- repo := bean.(*models.Repository)
- select {
- case <-ctx.Done():
- return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
- default:
- }
- isDir, err := util.IsDir(repo.RepoPath())
- if err != nil {
- return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err)
- }
- if !isDir {
- repos = append(repos, repo)
- }
- return nil
- },
- ); err != nil {
- if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
- return nil, err
- }
- if err2 := admin_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
- log.Error("CreateRepositoryNotice: %v", err2)
- }
- return nil, err
- }
- return repos, nil
-}
-
-// DeleteMissingRepositories deletes all repository records that lost Git files.
-func DeleteMissingRepositories(ctx context.Context, doer *models.User) error {
- repos, err := gatherMissingRepoRecords(ctx)
- if err != nil {
- return err
- }
-
- if len(repos) == 0 {
- return nil
- }
-
- for _, repo := range repos {
- select {
- case <-ctx.Done():
- return db.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
- default:
- }
- log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
- if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
- log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
- if err2 := admin_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
- log.Error("CreateRepositoryNotice: %v", err)
- }
- }
- }
- return nil
-}
-
-// ReinitMissingRepositories reinitializes all repository records that lost Git files.
-func ReinitMissingRepositories(ctx context.Context) error {
- repos, err := gatherMissingRepoRecords(ctx)
- if err != nil {
- return err
- }
-
- if len(repos) == 0 {
- return nil
- }
-
- for _, repo := range repos {
- select {
- case <-ctx.Done():
- return db.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
- default:
- }
- log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
- if err := git.InitRepository(repo.RepoPath(), true); err != nil {
- log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
- if err2 := admin_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
- log.Error("CreateRepositoryNotice: %v", err2)
- }
- }
- }
- return nil
-}
"net/http"
"code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/repofiles"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
// GetBlob get the blob of a repository file.
ctx.Error(http.StatusBadRequest, "", "sha not provided")
return
}
- if blob, err := repofiles.GetBlobBySHA(ctx.Repo.Repository, sha); err != nil {
+ if blob, err := files_service.GetBlobBySHA(ctx.Repo.Repository, sha); err != nil {
ctx.Error(http.StatusBadRequest, "", err)
} else {
ctx.JSON(http.StatusOK, blob)
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
- repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
branchName := ctx.Params("*")
- branch, err := repo_module.GetBranch(ctx.Repo.Repository, branchName)
+ branch, err := repo_service.GetBranch(ctx.Repo.Repository, branchName)
if err != nil {
if git.IsErrBranchNotExist(err) {
ctx.NotFound(err)
return
}
- branch, err := repo_module.GetBranch(ctx.Repo.Repository, opt.BranchName)
+ branch, err := repo_service.GetBranch(ctx.Repo.Repository, opt.BranchName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
return
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/routers/web/repo"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
// GetRawFile get a file by path on a repository
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
- opts := &repofiles.UpdateRepoFileOptions{
+ opts := &files_service.UpdateRepoFileOptions{
Content: apiOpts.Content,
IsNewFile: true,
Message: apiOpts.Message,
TreePath: ctx.Params("*"),
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
- Committer: &repofiles.IdentityOptions{
+ Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
- Dates: &repofiles.CommitDateOptions{
+ Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
- opts := &repofiles.UpdateRepoFileOptions{
+ opts := &files_service.UpdateRepoFileOptions{
Content: apiOpts.Content,
SHA: apiOpts.SHA,
IsNewFile: false,
TreePath: ctx.Params("*"),
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
- Committer: &repofiles.IdentityOptions{
+ Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
- Dates: &repofiles.CommitDateOptions{
+ Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
}
// Called from both CreateFile or UpdateFile to handle both
-func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileOptions) (*api.FileResponse, error) {
+func createOrUpdateFile(ctx *context.APIContext, opts *files_service.UpdateRepoFileOptions) (*api.FileResponse, error) {
if !canWriteFiles(ctx.Repo) {
return nil, models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID,
}
opts.Content = string(content)
- return repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, opts)
+ return files_service.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, opts)
}
// DeleteFile Delete a fle in a repository
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
- opts := &repofiles.DeleteRepoFileOptions{
+ opts := &files_service.DeleteRepoFileOptions{
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
SHA: apiOpts.SHA,
TreePath: ctx.Params("*"),
- Committer: &repofiles.IdentityOptions{
+ Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
- Author: &repofiles.IdentityOptions{
+ Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
- Dates: &repofiles.CommitDateOptions{
+ Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
opts.Message = ctx.Tr("repo.editor.delete", opts.TreePath)
}
- if fileResponse, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
+ if fileResponse, err := files_service.DeleteRepoFile(ctx.Repo.Repository, ctx.User, opts); err != nil {
if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteFile", err)
return
treePath := ctx.Params("*")
ref := ctx.FormTrim("ref")
- if fileList, err := repofiles.GetContentsOrList(ctx.Repo.Repository, treePath, ref); err != nil {
+ if fileList, err := files_service.GetContentsOrList(ctx.Repo.Repository, treePath, ref); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetContentsOrList", err)
return
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
- "code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
// NewCommitStatus creates a new CommitStatus
Description: form.Description,
Context: form.Context,
}
- if err := repofiles.CreateCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil {
+ if err := files_service.CreateCommitStatus(ctx.Repo.Repository, ctx.User, sha, status); err != nil {
ctx.Error(http.StatusInternalServerError, "CreateCommitStatus", err)
return
}
"net/http"
"code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/repofiles"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
// GetTree get the tree of a repository.
ctx.Error(http.StatusBadRequest, "", "sha not provided")
return
}
- if tree, err := repofiles.GetTreeBySHA(ctx.Repo.Repository, sha, ctx.FormInt("page"), ctx.FormInt("per_page"), ctx.FormBool("recursive")); err != nil {
+ if tree, err := files_service.GetTreeBySHA(ctx.Repo.Repository, sha, ctx.FormInt("page"), ctx.FormInt("per_page"), ctx.FormBool("recursive")); err != nil {
ctx.Error(http.StatusBadRequest, "", err.Error())
} else {
ctx.JSON(http.StatusOK, tree)
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/repofiles"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
const (
// loadBranches loads branches from the repository limited by page & pageSize.
// NOTE: May write to context on error.
func loadBranches(ctx *context.Context, skip, limit int) ([]*Branch, int) {
- defaultBranch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
+ defaultBranch, err := repo_service.GetBranch(ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch)
if err != nil {
log.Error("loadBranches: get default branch: %v", err)
ctx.ServerError("GetDefaultBranch", err)
}
}
- divergence, divergenceError := repofiles.CountDivergingCommits(ctx.Repo.Repository, git.BranchPrefix+branchName)
+ divergence, divergenceError := files_service.CountDivergingCommits(ctx.Repo.Repository, git.BranchPrefix+branchName)
if divergenceError != nil {
ctx.ServerError("CountDivergingCommits", divergenceError)
return nil
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/repofiles"
- repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/forms"
+ repo_service "code.gitea.io/gitea/services/repository"
+ files_service "code.gitea.io/gitea/services/repository/files"
)
const (
message += "\n\n" + form.CommitMessage
}
- if _, err := repofiles.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.UpdateRepoFileOptions{
+ if _, err := files_service.CreateOrUpdateRepoFile(ctx.Repo.Repository, ctx.User, &files_service.UpdateRepoFileOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
IsNewFile: isNewFile,
Signoff: form.Signoff,
}); err != nil {
- // This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile
+ // This is where we handle all the errors thrown by files_service.CreateOrUpdateRepoFile
if git.IsErrNotExist(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
} else if models.IsErrLFSFileLocked(err) {
return
}
- diff, err := repofiles.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
+ diff, err := files_service.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDiffPreview: "+err.Error())
return
message += "\n\n" + form.CommitMessage
}
- if _, err := repofiles.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.DeleteRepoFileOptions{
+ if _, err := files_service.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &files_service.DeleteRepoFileOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
}
if oldBranchName != branchName {
- if _, err := repo_module.GetBranch(ctx.Repo.Repository, branchName); err == nil {
+ if _, err := repo_service.GetBranch(ctx.Repo.Repository, branchName); err == nil {
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form)
return
message += "\n\n" + form.CommitMessage
}
- if err := repofiles.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &repofiles.UploadRepoFileOptions{
+ if err := files_service.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &files_service.UploadRepoFileOptions{
LastCommitID: ctx.Repo.CommitID,
OldBranch: oldBranchName,
NewBranch: branchName,
prefix := ctx.User.LowerName + "-patch-"
for i := 1; i <= 1000; i++ {
branchName := fmt.Sprintf("%s%d", prefix, i)
- if _, err := repo_module.GetBranch(ctx.Repo.Repository, branchName); err != nil {
+ if _, err := repo_service.GetBranch(ctx.Repo.Repository, branchName); err != nil {
if git.IsErrBranchNotExist(err) {
return branchName
}
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/webhook"
- repository_service "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror"
+ repository_service "code.gitea.io/gitea/services/repository"
)
func registerUpdateMirrorTask() {
"time"
"code.gitea.io/gitea/models"
- repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
repo_service "code.gitea.io/gitea/services/repository"
Args: setting.Git.GCArgs,
}, func(ctx context.Context, _ *models.User, config Config) error {
rhcConfig := config.(*RepoHealthCheckConfig)
- return repo_module.GitGcRepos(ctx, rhcConfig.Timeout, rhcConfig.Args...)
+ return repo_service.GitGcRepos(ctx, rhcConfig.Timeout, rhcConfig.Args...)
})
}
RunAtStart: false,
Schedule: "@every 72h",
}, func(ctx context.Context, _ *models.User, _ Config) error {
- return repo_module.ReinitMissingRepositories(ctx)
+ return repo_service.ReinitMissingRepositories(ctx)
})
}
RunAtStart: false,
Schedule: "@every 72h",
}, func(ctx context.Context, user *models.User, _ Config) error {
- return repo_module.DeleteMissingRepositories(ctx, user)
+ return repo_service.DeleteMissingRepositories(ctx, user)
})
}
return nil
}
+// GetBranch returns a branch by its name
+func GetBranch(repo *models.Repository, branch string) (*git.Branch, error) {
+ if len(branch) == 0 {
+ return nil, fmt.Errorf("GetBranch: empty string for branch")
+ }
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+
+ return gitRepo.GetBranch(branch)
+}
+
// GetBranches returns branches from the repository, skipping skip initial branches and
// returning at most limit branches, or all branches if limit is 0.
func GetBranches(repo *models.Repository, skip, limit int) ([]*git.Branch, int, error) {
--- /dev/null
+// Copyright 2020 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 repository
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ admin_model "code.gitea.io/gitea/models/admin"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
+
+ "xorm.io/builder"
+)
+
+// GitFsck calls 'git fsck' to check repository health.
+func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
+ log.Trace("Doing: GitFsck")
+
+ if err := db.Iterate(
+ db.DefaultContext,
+ new(models.Repository),
+ builder.Expr("id>0 AND is_fsck_enabled=?", true),
+ func(idx int, bean interface{}) error {
+ repo := bean.(*models.Repository)
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("before fsck of %s", repo.FullName())
+ default:
+ }
+ log.Trace("Running health check on repository %v", repo)
+ repoPath := repo.RepoPath()
+ if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
+ log.Warn("Failed to health check repository (%v): %v", repo, err)
+ if err = admin_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
+ log.Error("CreateRepositoryNotice: %v", err)
+ }
+ }
+ return nil
+ },
+ ); err != nil {
+ log.Trace("Error: GitFsck: %v", err)
+ return err
+ }
+
+ log.Trace("Finished: GitFsck")
+ return nil
+}
+
+// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
+func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) error {
+ log.Trace("Doing: GitGcRepos")
+ args = append([]string{"gc"}, args...)
+
+ if err := db.Iterate(
+ db.DefaultContext,
+ new(models.Repository),
+ builder.Gt{"id": 0},
+ func(idx int, bean interface{}) error {
+ repo := bean.(*models.Repository)
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("before GC of %s", repo.FullName())
+ default:
+ }
+ log.Trace("Running git gc on %v", repo)
+ command := git.NewCommandContext(ctx, args...).
+ SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
+ var stdout string
+ var err error
+ if timeout > 0 {
+ var stdoutBytes []byte
+ stdoutBytes, err = command.RunInDirTimeout(
+ timeout,
+ repo.RepoPath())
+ stdout = string(stdoutBytes)
+ } else {
+ stdout, err = command.RunInDir(repo.RepoPath())
+ }
+
+ if err != nil {
+ log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
+ desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
+ if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ log.Error("CreateRepositoryNotice: %v", err)
+ }
+ return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
+ }
+
+ // Now update the size of the repository
+ if err := repo.UpdateSize(db.DefaultContext); err != nil {
+ log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
+ desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
+ if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ log.Error("CreateRepositoryNotice: %v", err)
+ }
+ return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
+ }
+
+ return nil
+ },
+ ); err != nil {
+ return err
+ }
+
+ log.Trace("Finished: GitGcRepos")
+ return nil
+}
+
+func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) {
+ repos := make([]*models.Repository, 0, 10)
+ if err := db.Iterate(
+ db.DefaultContext,
+ new(models.Repository),
+ builder.Gt{"id": 0},
+ func(idx int, bean interface{}) error {
+ repo := bean.(*models.Repository)
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
+ default:
+ }
+ isDir, err := util.IsDir(repo.RepoPath())
+ if err != nil {
+ return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err)
+ }
+ if !isDir {
+ repos = append(repos, repo)
+ }
+ return nil
+ },
+ ); err != nil {
+ if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
+ return nil, err
+ }
+ if err2 := admin_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
+ log.Error("CreateRepositoryNotice: %v", err2)
+ }
+ return nil, err
+ }
+ return repos, nil
+}
+
+// DeleteMissingRepositories deletes all repository records that lost Git files.
+func DeleteMissingRepositories(ctx context.Context, doer *models.User) error {
+ repos, err := gatherMissingRepoRecords(ctx)
+ if err != nil {
+ return err
+ }
+
+ if len(repos) == 0 {
+ return nil
+ }
+
+ for _, repo := range repos {
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
+ default:
+ }
+ log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
+ if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
+ log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
+ if err2 := admin_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
+ log.Error("CreateRepositoryNotice: %v", err)
+ }
+ }
+ }
+ return nil
+}
+
+// ReinitMissingRepositories reinitializes all repository records that lost Git files.
+func ReinitMissingRepositories(ctx context.Context) error {
+ repos, err := gatherMissingRepoRecords(ctx)
+ if err != nil {
+ return err
+ }
+
+ if len(repos) == 0 {
+ return nil
+ }
+
+ for _, repo := range repos {
+ select {
+ case <-ctx.Done():
+ return db.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
+ default:
+ }
+ log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
+ if err := git.InitRepository(repo.RepoPath(), true); err != nil {
+ log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
+ if err2 := admin_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
+ log.Error("CreateRepositoryNotice: %v", err2)
+ }
+ }
+ }
+ return nil
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/structs"
+)
+
+// CreateCommitStatus creates a new CommitStatus given a bunch of parameters
+// NOTE: All text-values will be trimmed from whitespaces.
+// Requires: Repo, Creator, SHA
+func CreateCommitStatus(repo *models.Repository, creator *models.User, sha string, status *models.CommitStatus) error {
+ repoPath := repo.RepoPath()
+
+ // confirm that commit is exist
+ gitRepo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ return fmt.Errorf("OpenRepository[%s]: %v", repoPath, err)
+ }
+ if _, err := gitRepo.GetCommit(sha); err != nil {
+ gitRepo.Close()
+ return fmt.Errorf("GetCommit[%s]: %v", sha, err)
+ }
+ gitRepo.Close()
+
+ if err := models.NewCommitStatus(models.NewCommitStatusOptions{
+ Repo: repo,
+ Creator: creator,
+ SHA: sha,
+ CommitStatus: status,
+ }); err != nil {
+ return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
+ }
+
+ return nil
+}
+
+// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
+func CountDivergingCommits(repo *models.Repository, branch string) (*git.DivergeObject, error) {
+ divergence, err := git.GetDivergingCommits(repo.RepoPath(), repo.DefaultBranch, branch)
+ if err != nil {
+ return nil, err
+ }
+ return &divergence, nil
+}
+
+// GetPayloadCommitVerification returns the verification information of a commit
+func GetPayloadCommitVerification(commit *git.Commit) *structs.PayloadCommitVerification {
+ verification := &structs.PayloadCommitVerification{}
+ commitVerification := models.ParseCommitWithSignature(commit)
+ if commit.Signature != nil {
+ verification.Signature = commit.Signature.Signature
+ verification.Payload = commit.Signature.Payload
+ }
+ if commitVerification.SigningUser != nil {
+ verification.Signer = &structs.PayloadUser{
+ Name: commitVerification.SigningUser.Name,
+ Email: commitVerification.SigningUser.Email,
+ }
+ }
+ verification.Verified = commitVerification.Verified
+ verification.Reason = commitVerification.Reason
+ if verification.Reason == "" && !verification.Verified {
+ verification.Reason = "gpg.error.not_signed_commit"
+ }
+ return verification
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+ "net/url"
+ "path"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+// ContentType repo content type
+type ContentType string
+
+// The string representations of different content types
+const (
+ // ContentTypeRegular regular content type (file)
+ ContentTypeRegular ContentType = "file"
+ // ContentTypeDir dir content type (dir)
+ ContentTypeDir ContentType = "dir"
+ // ContentLink link content type (symlink)
+ ContentTypeLink ContentType = "symlink"
+ // ContentTag submodule content type (submodule)
+ ContentTypeSubmodule ContentType = "submodule"
+)
+
+// String gets the string of ContentType
+func (ct *ContentType) String() string {
+ return string(*ct)
+}
+
+// GetContentsOrList gets the meta data of a file's contents (*ContentsResponse) if treePath not a tree
+// directory, otherwise a listing of file contents ([]*ContentsResponse). Ref can be a branch, commit or tag
+func GetContentsOrList(repo *models.Repository, treePath, ref string) (interface{}, error) {
+ if repo.IsEmpty {
+ return make([]interface{}, 0), nil
+ }
+ if ref == "" {
+ ref = repo.DefaultBranch
+ }
+ origRef := ref
+
+ // Check that the path given in opts.treePath is valid (not a git path)
+ cleanTreePath := CleanUploadFileName(treePath)
+ if cleanTreePath == "" && treePath != "" {
+ return nil, models.ErrFilenameInvalid{
+ Path: treePath,
+ }
+ }
+ treePath = cleanTreePath
+
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+
+ // Get the commit object for the ref
+ commit, err := gitRepo.GetCommit(ref)
+ if err != nil {
+ return nil, err
+ }
+
+ entry, err := commit.GetTreeEntryByPath(treePath)
+ if err != nil {
+ return nil, err
+ }
+
+ if entry.Type() != "tree" {
+ return GetContents(repo, treePath, origRef, false)
+ }
+
+ // We are in a directory, so we return a list of FileContentResponse objects
+ var fileList []*api.ContentsResponse
+
+ gitTree, err := commit.SubTree(treePath)
+ if err != nil {
+ return nil, err
+ }
+ entries, err := gitTree.ListEntries()
+ if err != nil {
+ return nil, err
+ }
+ for _, e := range entries {
+ subTreePath := path.Join(treePath, e.Name())
+ fileContentResponse, err := GetContents(repo, subTreePath, origRef, true)
+ if err != nil {
+ return nil, err
+ }
+ fileList = append(fileList, fileContentResponse)
+ }
+ return fileList, nil
+}
+
+// GetContents gets the meta data on a file's contents. Ref can be a branch, commit or tag
+func GetContents(repo *models.Repository, treePath, ref string, forList bool) (*api.ContentsResponse, error) {
+ if ref == "" {
+ ref = repo.DefaultBranch
+ }
+ origRef := ref
+
+ // Check that the path given in opts.treePath is valid (not a git path)
+ cleanTreePath := CleanUploadFileName(treePath)
+ if cleanTreePath == "" && treePath != "" {
+ return nil, models.ErrFilenameInvalid{
+ Path: treePath,
+ }
+ }
+ treePath = cleanTreePath
+
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+
+ // Get the commit object for the ref
+ commit, err := gitRepo.GetCommit(ref)
+ if err != nil {
+ return nil, err
+ }
+ commitID := commit.ID.String()
+ if len(ref) >= 4 && strings.HasPrefix(commitID, ref) {
+ ref = commit.ID.String()
+ }
+
+ entry, err := commit.GetTreeEntryByPath(treePath)
+ if err != nil {
+ return nil, err
+ }
+
+ refType := gitRepo.GetRefType(ref)
+ if refType == "invalid" {
+ return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref)
+ }
+
+ selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef))
+ if err != nil {
+ return nil, err
+ }
+ selfURLString := selfURL.String()
+
+ // All content types have these fields in populated
+ contentsResponse := &api.ContentsResponse{
+ Name: entry.Name(),
+ Path: treePath,
+ SHA: entry.ID.String(),
+ Size: entry.Size(),
+ URL: &selfURLString,
+ Links: &api.FileLinksResponse{
+ Self: &selfURLString,
+ },
+ }
+
+ // Now populate the rest of the ContentsResponse based on entry type
+ if entry.IsRegular() || entry.IsExecutable() {
+ contentsResponse.Type = string(ContentTypeRegular)
+ if blobResponse, err := GetBlobBySHA(repo, entry.ID.String()); err != nil {
+ return nil, err
+ } else if !forList {
+ // We don't show the content if we are getting a list of FileContentResponses
+ contentsResponse.Encoding = &blobResponse.Encoding
+ contentsResponse.Content = &blobResponse.Content
+ }
+ } else if entry.IsDir() {
+ contentsResponse.Type = string(ContentTypeDir)
+ } else if entry.IsLink() {
+ contentsResponse.Type = string(ContentTypeLink)
+ // The target of a symlink file is the content of the file
+ targetFromContent, err := entry.Blob().GetBlobContent()
+ if err != nil {
+ return nil, err
+ }
+ contentsResponse.Target = &targetFromContent
+ } else if entry.IsSubModule() {
+ contentsResponse.Type = string(ContentTypeSubmodule)
+ submodule, err := commit.GetSubModule(treePath)
+ if err != nil {
+ return nil, err
+ }
+ contentsResponse.SubmoduleGitURL = &submodule.URL
+ }
+ // Handle links
+ if entry.IsRegular() || entry.IsLink() {
+ downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
+ if err != nil {
+ return nil, err
+ }
+ downloadURLString := downloadURL.String()
+ contentsResponse.DownloadURL = &downloadURLString
+ }
+ if !entry.IsSubModule() {
+ htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath))
+ if err != nil {
+ return nil, err
+ }
+ htmlURLString := htmlURL.String()
+ contentsResponse.HTMLURL = &htmlURLString
+ contentsResponse.Links.HTMLURL = &htmlURLString
+
+ gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String()))
+ if err != nil {
+ return nil, err
+ }
+ gitURLString := gitURL.String()
+ contentsResponse.GitURL = &gitURLString
+ contentsResponse.Links.GitURL = &gitURLString
+ }
+
+ return contentsResponse, nil
+}
+
+// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash.
+func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, error) {
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+ gitBlob, err := gitRepo.GetBlob(sha)
+ if err != nil {
+ return nil, err
+ }
+ content := ""
+ if gitBlob.Size() <= setting.API.DefaultMaxBlobSize {
+ content, err = gitBlob.GetBlobContentBase64()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &api.GitBlobResponse{
+ SHA: gitBlob.ID.String(),
+ URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()),
+ Size: gitBlob.Size(),
+ Encoding: "base64",
+ Content: content,
+ }, nil
+}
--- /dev/null
+// 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 files
+
+import (
+ "path/filepath"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, filepath.Join("..", "..", ".."))
+}
+
+func getExpectedReadmeContentsResponse() *api.ContentsResponse {
+ treePath := "README.md"
+ sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
+ encoding := "base64"
+ content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
+ selfURL := "https://try.gitea.io/api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
+ htmlURL := "https://try.gitea.io/user2/repo1/src/branch/master/" + treePath
+ gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha
+ downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath
+ return &api.ContentsResponse{
+ Name: treePath,
+ Path: treePath,
+ SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
+ Type: "file",
+ Size: 30,
+ Encoding: &encoding,
+ Content: &content,
+ URL: &selfURL,
+ HTMLURL: &htmlURL,
+ GitURL: &gitURL,
+ DownloadURL: &downloadURL,
+ Links: &api.FileLinksResponse{
+ Self: &selfURL,
+ GitURL: &gitURL,
+ HTMLURL: &htmlURL,
+ },
+ }
+}
+
+func TestGetContents(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ treePath := "README.md"
+ ref := ctx.Repo.Repository.DefaultBranch
+
+ expectedContentsResponse := getExpectedReadmeContentsResponse()
+
+ t.Run("Get README.md contents with GetContents()", func(t *testing.T) {
+ fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, ref, false)
+ assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContents()", func(t *testing.T) {
+ fileContentResponse, err := GetContents(ctx.Repo.Repository, treePath, "", false)
+ assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+}
+
+func TestGetContentsOrListForDir(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ treePath := "" // root dir
+ ref := ctx.Repo.Repository.DefaultBranch
+
+ readmeContentsResponse := getExpectedReadmeContentsResponse()
+ // because will be in a list, doesn't have encoding and content
+ readmeContentsResponse.Encoding = nil
+ readmeContentsResponse.Content = nil
+
+ expectedContentsListResponse := []*api.ContentsResponse{
+ readmeContentsResponse,
+ }
+
+ t.Run("Get root dir contents with GetContentsOrList()", func(t *testing.T) {
+ fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref)
+ assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Get root dir contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) {
+ fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "")
+ assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+}
+
+func TestGetContentsOrListForFile(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ treePath := "README.md"
+ ref := ctx.Repo.Repository.DefaultBranch
+
+ expectedContentsResponse := getExpectedReadmeContentsResponse()
+
+ t.Run("Get README.md contents with GetContentsOrList()", func(t *testing.T) {
+ fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, ref)
+ assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList()", func(t *testing.T) {
+ fileContentResponse, err := GetContentsOrList(ctx.Repo.Repository, treePath, "")
+ assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
+ assert.NoError(t, err)
+ })
+}
+
+func TestGetContentsErrors(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ repo := ctx.Repo.Repository
+ treePath := "README.md"
+ ref := repo.DefaultBranch
+
+ t.Run("bad treePath", func(t *testing.T) {
+ badTreePath := "bad/tree.md"
+ fileContentResponse, err := GetContents(repo, badTreePath, ref, false)
+ assert.Error(t, err)
+ assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
+ assert.Nil(t, fileContentResponse)
+ })
+
+ t.Run("bad ref", func(t *testing.T) {
+ badRef := "bad_ref"
+ fileContentResponse, err := GetContents(repo, treePath, badRef, false)
+ assert.Error(t, err)
+ assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
+ assert.Nil(t, fileContentResponse)
+ })
+}
+
+func TestGetContentsOrListErrors(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ repo := ctx.Repo.Repository
+ treePath := "README.md"
+ ref := repo.DefaultBranch
+
+ t.Run("bad treePath", func(t *testing.T) {
+ badTreePath := "bad/tree.md"
+ fileContentResponse, err := GetContentsOrList(repo, badTreePath, ref)
+ assert.Error(t, err)
+ assert.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
+ assert.Nil(t, fileContentResponse)
+ })
+
+ t.Run("bad ref", func(t *testing.T) {
+ badRef := "bad_ref"
+ fileContentResponse, err := GetContentsOrList(repo, treePath, badRef)
+ assert.Error(t, err)
+ assert.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
+ assert.Nil(t, fileContentResponse)
+ })
+}
+
+func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo15")
+ ctx.SetParams(":id", "15")
+ test.LoadRepo(t, ctx, 15)
+ test.LoadUser(t, ctx, 2)
+ test.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+
+ repo := ctx.Repo.Repository
+
+ t.Run("empty repo", func(t *testing.T) {
+ contents, err := GetContentsOrList(repo, "", "")
+ assert.NoError(t, err)
+ assert.Empty(t, contents)
+ })
+}
+
+func TestGetBlobBySHA(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1")
+ test.LoadRepo(t, ctx, 1)
+ test.LoadRepoCommit(t, ctx)
+ test.LoadUser(t, ctx, 2)
+ test.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+
+ sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
+ ctx.SetParams(":id", "1")
+ ctx.SetParams(":sha", sha)
+
+ gbr, err := GetBlobBySHA(ctx.Repo.Repository, ctx.Params(":sha"))
+ expectedGBR := &api.GitBlobResponse{
+ Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
+ Encoding: "base64",
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ Size: 180,
+ }
+ assert.NoError(t, err)
+ assert.Equal(t, expectedGBR, gbr)
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ api "code.gitea.io/gitea/modules/structs"
+ repo_service "code.gitea.io/gitea/services/repository"
+)
+
+// DeleteRepoFileOptions holds the repository delete file options
+type DeleteRepoFileOptions struct {
+ LastCommitID string
+ OldBranch string
+ NewBranch string
+ TreePath string
+ Message string
+ SHA string
+ Author *IdentityOptions
+ Committer *IdentityOptions
+ Dates *CommitDateOptions
+ Signoff bool
+}
+
+// DeleteRepoFile deletes a file in the given repository
+func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepoFileOptions) (*api.FileResponse, error) {
+ // If no branch name is set, assume the repo's default branch
+ if opts.OldBranch == "" {
+ opts.OldBranch = repo.DefaultBranch
+ }
+ if opts.NewBranch == "" {
+ opts.NewBranch = opts.OldBranch
+ }
+
+ // oldBranch must exist for this operation
+ if _, err := repo_service.GetBranch(repo, opts.OldBranch); err != nil {
+ return nil, err
+ }
+
+ // A NewBranch can be specified for the file to be created/updated in a new branch.
+ // Check to make sure the branch does not already exist, otherwise we can't proceed.
+ // If we aren't branching to a new branch, make sure user can commit to the given branch
+ if opts.NewBranch != opts.OldBranch {
+ newBranch, err := repo_service.GetBranch(repo, opts.NewBranch)
+ if err != nil && !git.IsErrBranchNotExist(err) {
+ return nil, err
+ }
+ if newBranch != nil {
+ return nil, models.ErrBranchAlreadyExists{
+ BranchName: opts.NewBranch,
+ }
+ }
+ } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
+ return nil, err
+ }
+
+ // Check that the path given in opts.treeName is valid (not a git path)
+ treePath := CleanUploadFileName(opts.TreePath)
+ if treePath == "" {
+ return nil, models.ErrFilenameInvalid{
+ Path: opts.TreePath,
+ }
+ }
+
+ message := strings.TrimSpace(opts.Message)
+
+ author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
+
+ t, err := NewTemporaryUploadRepository(repo)
+ if err != nil {
+ return nil, err
+ }
+ defer t.Close()
+ if err := t.Clone(opts.OldBranch); err != nil {
+ return nil, err
+ }
+ if err := t.SetDefaultIndex(); err != nil {
+ return nil, err
+ }
+
+ // Get the commit of the original branch
+ commit, err := t.GetBranchCommit(opts.OldBranch)
+ if err != nil {
+ return nil, err // Couldn't get a commit for the branch
+ }
+
+ // Assigned LastCommitID in opts if it hasn't been set
+ if opts.LastCommitID == "" {
+ opts.LastCommitID = commit.ID.String()
+ } else {
+ lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
+ if err != nil {
+ return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
+ }
+ opts.LastCommitID = lastCommitID.String()
+ }
+
+ // Get the files in the index
+ filesInIndex, err := t.LsFiles(opts.TreePath)
+ if err != nil {
+ return nil, fmt.Errorf("DeleteRepoFile: %v", err)
+ }
+
+ // Find the file we want to delete in the index
+ inFilelist := false
+ for _, file := range filesInIndex {
+ if file == opts.TreePath {
+ inFilelist = true
+ break
+ }
+ }
+ if !inFilelist {
+ return nil, models.ErrRepoFileDoesNotExist{
+ Path: opts.TreePath,
+ }
+ }
+
+ // Get the entry of treePath and check if the SHA given is the same as the file
+ entry, err := commit.GetTreeEntryByPath(treePath)
+ if err != nil {
+ return nil, err
+ }
+ if opts.SHA != "" {
+ // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
+ if opts.SHA != entry.ID.String() {
+ return nil, models.ErrSHADoesNotMatch{
+ Path: treePath,
+ GivenSHA: opts.SHA,
+ CurrentSHA: entry.ID.String(),
+ }
+ }
+ } else if opts.LastCommitID != "" {
+ // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
+ // an error, but only if we aren't creating a new branch.
+ if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
+ // CommitIDs don't match, but we don't want to throw a ErrCommitIDDoesNotMatch unless
+ // this specific file has been edited since opts.LastCommitID
+ if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
+ return nil, err
+ } else if changed {
+ return nil, models.ErrCommitIDDoesNotMatch{
+ GivenCommitID: opts.LastCommitID,
+ CurrentCommitID: opts.LastCommitID,
+ }
+ }
+ // The file wasn't modified, so we are good to delete it
+ }
+ } else {
+ // When deleting a file, a lastCommitID or SHA needs to be given to make sure other commits haven't been
+ // made. We throw an error if one wasn't provided.
+ return nil, models.ErrSHAOrCommitIDNotProvided{}
+ }
+
+ // Remove the file from the index
+ if err := t.RemoveFilesFromIndex(opts.TreePath); err != nil {
+ return nil, err
+ }
+
+ // Now write the tree
+ treeHash, err := t.WriteTree()
+ if err != nil {
+ return nil, err
+ }
+
+ // Now commit the tree
+ var commitHash string
+ if opts.Dates != nil {
+ commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
+ } else {
+ commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Then push this tree to NewBranch
+ if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
+ return nil, err
+ }
+
+ commit, err = t.GetCommit(commitHash)
+ if err != nil {
+ return nil, err
+ }
+
+ file, err := GetFileResponseFromCommit(repo, commit, opts.NewBranch, treePath)
+ if err != nil {
+ return nil, err
+ }
+ return file, nil
+}
--- /dev/null
+// 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 files
+
+import (
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/services/gitdiff"
+)
+
+// GetDiffPreview produces and returns diff result of a file which is not yet committed.
+func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*gitdiff.Diff, error) {
+ if branch == "" {
+ branch = repo.DefaultBranch
+ }
+ t, err := NewTemporaryUploadRepository(repo)
+ if err != nil {
+ return nil, err
+ }
+ defer t.Close()
+ if err := t.Clone(branch); err != nil {
+ return nil, err
+ }
+ if err := t.SetDefaultIndex(); err != nil {
+ return nil, err
+ }
+
+ // Add the object to the database
+ objectHash, err := t.HashObject(strings.NewReader(content))
+ if err != nil {
+ return nil, err
+ }
+
+ // Add the object to the index
+ if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
+ return nil, err
+ }
+ return t.DiffIndex()
+}
--- /dev/null
+// 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 files
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/services/gitdiff"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetDiffPreview(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ branch := ctx.Repo.Repository.DefaultBranch
+ treePath := "README.md"
+ content := "# repo1\n\nDescription for repo1\nthis is a new line"
+
+ expectedDiff := &gitdiff.Diff{
+ TotalAddition: 2,
+ TotalDeletion: 1,
+ Files: []*gitdiff.DiffFile{
+ {
+ Name: "README.md",
+ OldName: "README.md",
+ Index: 1,
+ Addition: 2,
+ Deletion: 1,
+ Type: 2,
+ IsCreated: false,
+ IsDeleted: false,
+ IsBin: false,
+ IsLFSFile: false,
+ IsRenamed: false,
+ IsSubmodule: false,
+ Sections: []*gitdiff.DiffSection{
+ {
+ FileName: "README.md",
+ Name: "",
+ Lines: []*gitdiff.DiffLine{
+ {
+ LeftIdx: 0,
+ RightIdx: 0,
+ Type: 4,
+ Content: "@@ -1,3 +1,4 @@",
+ Comments: nil,
+ SectionInfo: &gitdiff.DiffLineSectionInfo{
+ Path: "README.md",
+ LastLeftIdx: 0,
+ LastRightIdx: 0,
+ LeftIdx: 1,
+ RightIdx: 1,
+ LeftHunkSize: 3,
+ RightHunkSize: 4,
+ },
+ },
+ {
+ LeftIdx: 1,
+ RightIdx: 1,
+ Type: 1,
+ Content: " # repo1",
+ Comments: nil,
+ },
+ {
+ LeftIdx: 2,
+ RightIdx: 2,
+ Type: 1,
+ Content: " ",
+ Comments: nil,
+ },
+ {
+ LeftIdx: 3,
+ RightIdx: 0,
+ Match: 4,
+ Type: 3,
+ Content: "-Description for repo1",
+ Comments: nil,
+ },
+ {
+ LeftIdx: 0,
+ RightIdx: 3,
+ Match: 3,
+ Type: 2,
+ Content: "+Description for repo1",
+ Comments: nil,
+ },
+ {
+ LeftIdx: 0,
+ RightIdx: 4,
+ Match: -1,
+ Type: 2,
+ Content: "+this is a new line",
+ Comments: nil,
+ },
+ },
+ },
+ },
+ IsIncomplete: false,
+ },
+ },
+ IsIncomplete: false,
+ }
+ expectedDiff.NumFiles = len(expectedDiff.Files)
+
+ t.Run("with given branch", func(t *testing.T) {
+ diff, err := GetDiffPreview(ctx.Repo.Repository, branch, treePath, content)
+ assert.NoError(t, err)
+ expectedBs, err := json.Marshal(expectedDiff)
+ assert.NoError(t, err)
+ bs, err := json.Marshal(diff)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expectedBs, bs)
+ })
+
+ t.Run("empty branch, same results", func(t *testing.T) {
+ diff, err := GetDiffPreview(ctx.Repo.Repository, "", treePath, content)
+ assert.NoError(t, err)
+ expectedBs, err := json.Marshal(expectedDiff)
+ assert.NoError(t, err)
+ bs, err := json.Marshal(diff)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expectedBs, bs)
+ })
+}
+
+func TestGetDiffPreviewErrors(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ branch := ctx.Repo.Repository.DefaultBranch
+ treePath := "README.md"
+ content := "# repo1\n\nDescription for repo1\nthis is a new line"
+
+ t.Run("empty repo", func(t *testing.T) {
+ diff, err := GetDiffPreview(&models.Repository{}, branch, treePath, content)
+ assert.Nil(t, diff)
+ assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]")
+ })
+
+ t.Run("bad branch", func(t *testing.T) {
+ badBranch := "bad_branch"
+ diff, err := GetDiffPreview(ctx.Repo.Repository, badBranch, treePath, content)
+ assert.Nil(t, diff)
+ assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]")
+ })
+
+ t.Run("empty treePath", func(t *testing.T) {
+ diff, err := GetDiffPreview(ctx.Repo.Repository, branch, "", content)
+ assert.Nil(t, diff)
+ assert.EqualError(t, err, "path is invalid [path: ]")
+ })
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+ "net/url"
+ "path"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
+func GetFileResponseFromCommit(repo *models.Repository, commit *git.Commit, branch, treeName string) (*api.FileResponse, error) {
+ fileContents, _ := GetContents(repo, treeName, branch, false) // ok if fails, then will be nil
+ fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
+ verification := GetPayloadCommitVerification(commit)
+ fileResponse := &api.FileResponse{
+ Content: fileContents,
+ Commit: fileCommitResponse,
+ Verification: verification,
+ }
+ return fileResponse, nil
+}
+
+// GetFileCommitResponse Constructs a FileCommitResponse from a Commit object
+func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.FileCommitResponse, error) {
+ if repo == nil {
+ return nil, fmt.Errorf("repo cannot be nil")
+ }
+ if commit == nil {
+ return nil, fmt.Errorf("commit cannot be nil")
+ }
+ commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()))
+ commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String()))
+ parents := make([]*api.CommitMeta, commit.ParentCount())
+ for i := 0; i <= commit.ParentCount(); i++ {
+ if parent, err := commit.Parent(i); err == nil && parent != nil {
+ parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(parent.ID.String()))
+ parents[i] = &api.CommitMeta{
+ SHA: parent.ID.String(),
+ URL: parentCommitURL.String(),
+ }
+ }
+ }
+ commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()))
+ fileCommit := &api.FileCommitResponse{
+ CommitMeta: api.CommitMeta{
+ SHA: commit.ID.String(),
+ URL: commitURL.String(),
+ },
+ HTMLURL: commitHTMLURL.String(),
+ Author: &api.CommitUser{
+ Identity: api.Identity{
+ Name: commit.Author.Name,
+ Email: commit.Author.Email,
+ },
+ Date: commit.Author.When.UTC().Format(time.RFC3339),
+ },
+ Committer: &api.CommitUser{
+ Identity: api.Identity{
+ Name: commit.Committer.Name,
+ Email: commit.Committer.Email,
+ },
+ Date: commit.Committer.When.UTC().Format(time.RFC3339),
+ },
+ Message: commit.Message(),
+ Tree: &api.CommitMeta{
+ URL: commitTreeURL.String(),
+ SHA: commit.Tree.ID.String(),
+ },
+ Parents: parents,
+ }
+ return fileCommit, nil
+}
+
+// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
+func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (authorUser, committerUser *models.User) {
+ // Committer and author are optional. If they are not the doer (not same email address)
+ // then we use bogus User objects for them to store their FullName and Email.
+ // If only one of the two are provided, we set both of them to it.
+ // If neither are provided, both are the doer.
+ if committer != nil && committer.Email != "" {
+ if doer != nil && strings.EqualFold(doer.Email, committer.Email) {
+ committerUser = doer // the committer is the doer, so will use their user object
+ if committer.Name != "" {
+ committerUser.FullName = committer.Name
+ }
+ } else {
+ committerUser = &models.User{
+ FullName: committer.Name,
+ Email: committer.Email,
+ }
+ }
+ }
+ if author != nil && author.Email != "" {
+ if doer != nil && strings.EqualFold(doer.Email, author.Email) {
+ authorUser = doer // the author is the doer, so will use their user object
+ if authorUser.Name != "" {
+ authorUser.FullName = author.Name
+ }
+ } else {
+ authorUser = &models.User{
+ FullName: author.Name,
+ Email: author.Email,
+ }
+ }
+ }
+ if authorUser == nil {
+ if committerUser != nil {
+ authorUser = committerUser // No valid author was given so use the committer
+ } else if doer != nil {
+ authorUser = doer // No valid author was given and no valid committer so use the doer
+ }
+ }
+ if committerUser == nil {
+ committerUser = authorUser // No valid committer so use the author as the committer (was set to a valid user above)
+ }
+ return authorUser, committerUser
+}
+
+// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
+func CleanUploadFileName(name string) string {
+ // Rebase the filename
+ name = strings.Trim(path.Clean("/"+name), " /")
+ // Git disallows any filenames to have a .git directory in them.
+ for _, part := range strings.Split(name, "/") {
+ if strings.ToLower(part) == ".git" {
+ return ""
+ }
+ }
+ return name
+}
--- /dev/null
+// 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 files
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/git"
+ "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 TestCleanUploadFileName(t *testing.T) {
+ t.Run("Clean regular file", func(t *testing.T) {
+ name := "this/is/test"
+ cleanName := CleanUploadFileName(name)
+ expectedCleanName := name
+ assert.EqualValues(t, expectedCleanName, cleanName)
+ })
+
+ t.Run("Clean a .git path", func(t *testing.T) {
+ name := "this/is/test/.git"
+ cleanName := CleanUploadFileName(name)
+ expectedCleanName := ""
+ assert.EqualValues(t, expectedCleanName, cleanName)
+ })
+}
+
+func getExpectedFileResponse() *api.FileResponse {
+ treePath := "README.md"
+ sha := "4b4851ad51df6a7d9f25c979345979eaeb5b349f"
+ encoding := "base64"
+ content := "IyByZXBvMQoKRGVzY3JpcHRpb24gZm9yIHJlcG8x"
+ selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
+ htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
+ gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha
+ downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
+ return &api.FileResponse{
+ Content: &api.ContentsResponse{
+ Name: treePath,
+ Path: treePath,
+ SHA: sha,
+ Type: "file",
+ Size: 30,
+ Encoding: &encoding,
+ Content: &content,
+ URL: &selfURL,
+ HTMLURL: &htmlURL,
+ GitURL: &gitURL,
+ DownloadURL: &downloadURL,
+ Links: &api.FileLinksResponse{
+ Self: &selfURL,
+ GitURL: &gitURL,
+ HTMLURL: &htmlURL,
+ },
+ },
+ Commit: &api.FileCommitResponse{
+ CommitMeta: api.CommitMeta{
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/commits/65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ },
+ HTMLURL: "https://try.gitea.io/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ Author: &api.CommitUser{
+ Identity: api.Identity{
+ Name: "user1",
+ Email: "address1@example.com",
+ },
+ Date: "2017-03-19T20:47:59Z",
+ },
+ Committer: &api.CommitUser{
+ Identity: api.Identity{
+ Name: "Ethan Koenig",
+ Email: "ethantkoenig@gmail.com",
+ },
+ Date: "2017-03-19T20:47:59Z",
+ },
+ Parents: []*api.CommitMeta{},
+ Message: "Initial commit\n",
+ Tree: &api.CommitMeta{
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/2a2f1d4670728a2e10049e345bd7a276468beab6",
+ SHA: "2a2f1d4670728a2e10049e345bd7a276468beab6",
+ },
+ },
+ Verification: &api.PayloadCommitVerification{
+ Verified: false,
+ Reason: "gpg.error.not_signed_commit",
+ Signature: "",
+ Payload: "",
+ },
+ }
+}
+
+func TestGetFileResponseFromCommit(t *testing.T) {
+ unittest.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)
+ defer ctx.Repo.GitRepo.Close()
+
+ repo := ctx.Repo.Repository
+ branch := repo.DefaultBranch
+ treePath := "README.md"
+ gitRepo, _ := git.OpenRepository(repo.RepoPath())
+ defer gitRepo.Close()
+ commit, _ := gitRepo.GetBranchCommit(branch)
+ expectedFileResponse := getExpectedFileResponse()
+
+ fileResponse, err := GetFileResponseFromCommit(repo, commit, branch, treePath)
+ assert.NoError(t, err)
+ assert.EqualValues(t, expectedFileResponse, fileResponse)
+}
--- /dev/null
+// 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 files
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "regexp"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/gitdiff"
+)
+
+// TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone
+type TemporaryUploadRepository struct {
+ repo *models.Repository
+ gitRepo *git.Repository
+ basePath string
+}
+
+// NewTemporaryUploadRepository creates a new temporary upload repository
+func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) {
+ basePath, err := models.CreateTemporaryPath("upload")
+ if err != nil {
+ return nil, err
+ }
+ t := &TemporaryUploadRepository{repo: repo, basePath: basePath}
+ return t, nil
+}
+
+// Close the repository cleaning up all files
+func (t *TemporaryUploadRepository) Close() {
+ defer t.gitRepo.Close()
+ if err := models.RemoveTemporaryPath(t.basePath); err != nil {
+ log.Error("Failed to remove temporary path %s: %v", t.basePath, err)
+ }
+}
+
+// Clone the base repository to our path and set branch as the HEAD
+func (t *TemporaryUploadRepository) Clone(branch string) error {
+ if _, err := git.NewCommand("clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath).Run(); err != nil {
+ stderr := err.Error()
+ if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched {
+ return git.ErrBranchNotExist{
+ Name: branch,
+ }
+ } else if matched, _ := regexp.MatchString(".* repository .* does not exist.*", stderr); matched {
+ return models.ErrRepoNotExist{
+ ID: t.repo.ID,
+ UID: t.repo.OwnerID,
+ OwnerName: t.repo.OwnerName,
+ Name: t.repo.Name,
+ }
+ } else {
+ return fmt.Errorf("Clone: %v %s", err, stderr)
+ }
+ }
+ gitRepo, err := git.OpenRepository(t.basePath)
+ if err != nil {
+ return err
+ }
+ t.gitRepo = gitRepo
+ return nil
+}
+
+// SetDefaultIndex sets the git index to our HEAD
+func (t *TemporaryUploadRepository) SetDefaultIndex() error {
+ if _, err := git.NewCommand("read-tree", "HEAD").RunInDir(t.basePath); err != nil {
+ return fmt.Errorf("SetDefaultIndex: %v", err)
+ }
+ return nil
+}
+
+// LsFiles checks if the given filename arguments are in the index
+func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, error) {
+ stdOut := new(bytes.Buffer)
+ stdErr := new(bytes.Buffer)
+
+ cmdArgs := []string{"ls-files", "-z", "--"}
+ for _, arg := range filenames {
+ if arg != "" {
+ cmdArgs = append(cmdArgs, arg)
+ }
+ }
+
+ if err := git.NewCommand(cmdArgs...).RunInDirPipeline(t.basePath, stdOut, stdErr); err != nil {
+ log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
+ err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
+ return nil, err
+ }
+
+ filelist := make([]string, len(filenames))
+ for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
+ filelist = append(filelist, string(line))
+ }
+
+ return filelist, nil
+}
+
+// RemoveFilesFromIndex removes the given files from the index
+func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error {
+ stdOut := new(bytes.Buffer)
+ stdErr := new(bytes.Buffer)
+ stdIn := new(bytes.Buffer)
+ for _, file := range filenames {
+ if file != "" {
+ stdIn.WriteString("0 0000000000000000000000000000000000000000\t")
+ stdIn.WriteString(file)
+ stdIn.WriteByte('\000')
+ }
+ }
+
+ if err := git.NewCommand("update-index", "--remove", "-z", "--index-info").RunInDirFullPipeline(t.basePath, stdOut, stdErr, stdIn); err != nil {
+ log.Error("Unable to update-index for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
+ return fmt.Errorf("Unable to update-index for temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
+ }
+ return nil
+}
+
+// HashObject writes the provided content to the object db and returns its hash
+func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error) {
+ stdOut := new(bytes.Buffer)
+ stdErr := new(bytes.Buffer)
+
+ if err := git.NewCommand("hash-object", "-w", "--stdin").RunInDirFullPipeline(t.basePath, stdOut, stdErr, content); err != nil {
+ log.Error("Unable to hash-object to temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
+ return "", fmt.Errorf("Unable to hash-object to temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
+ }
+
+ return strings.TrimSpace(stdOut.String()), nil
+}
+
+// AddObjectToIndex adds the provided object hash to the index with the provided mode and path
+func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPath string) error {
+ if _, err := git.NewCommand("update-index", "--add", "--replace", "--cacheinfo", mode, objectHash, objectPath).RunInDir(t.basePath); err != nil {
+ stderr := err.Error()
+ if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched {
+ return models.ErrFilePathInvalid{
+ Message: objectPath,
+ Path: objectPath,
+ }
+ }
+ log.Error("Unable to add object to index: %s %s %s in temporary repo %s(%s) Error: %v", mode, objectHash, objectPath, t.repo.FullName(), t.basePath, err)
+ return fmt.Errorf("Unable to add object to index at %s in temporary repo %s Error: %v", objectPath, t.repo.FullName(), err)
+ }
+ return nil
+}
+
+// WriteTree writes the current index as a tree to the object db and returns its hash
+func (t *TemporaryUploadRepository) WriteTree() (string, error) {
+ stdout, err := git.NewCommand("write-tree").RunInDir(t.basePath)
+ if err != nil {
+ log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err)
+ return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %v", t.repo.FullName(), err)
+ }
+ return strings.TrimSpace(stdout), nil
+}
+
+// GetLastCommit gets the last commit ID SHA of the repo
+func (t *TemporaryUploadRepository) GetLastCommit() (string, error) {
+ return t.GetLastCommitByRef("HEAD")
+}
+
+// GetLastCommitByRef gets the last commit ID SHA of the repo by ref
+func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, error) {
+ if ref == "" {
+ ref = "HEAD"
+ }
+ stdout, err := git.NewCommand("rev-parse", ref).RunInDir(t.basePath)
+ if err != nil {
+ log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err)
+ return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %v", ref, t.repo.FullName(), err)
+ }
+ return strings.TrimSpace(stdout), nil
+}
+
+// CommitTree creates a commit from a given tree for the user with provided message
+func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, treeHash string, message string, signoff bool) (string, error) {
+ return t.CommitTreeWithDate(author, committer, treeHash, message, signoff, time.Now(), time.Now())
+}
+
+// CommitTreeWithDate creates a commit from a given tree for the user with provided message
+func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models.User, treeHash string, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
+ authorSig := author.NewGitSig()
+ committerSig := committer.NewGitSig()
+
+ err := git.LoadGitVersion()
+ if err != nil {
+ return "", fmt.Errorf("Unable to get git version: %v", err)
+ }
+
+ // Because this may call hooks we should pass in the environment
+ env := append(os.Environ(),
+ "GIT_AUTHOR_NAME="+authorSig.Name,
+ "GIT_AUTHOR_EMAIL="+authorSig.Email,
+ "GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
+ "GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
+ )
+
+ messageBytes := new(bytes.Buffer)
+ _, _ = messageBytes.WriteString(message)
+ _, _ = messageBytes.WriteString("\n")
+
+ args := []string{"commit-tree", treeHash, "-p", "HEAD"}
+
+ // Determine if we should sign
+ if git.CheckGitVersionAtLeast("1.7.9") == nil {
+ sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
+ if sign {
+ args = append(args, "-S"+keyID)
+ if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
+ if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
+ // Add trailers
+ _, _ = messageBytes.WriteString("\n")
+ _, _ = messageBytes.WriteString("Co-authored-by: ")
+ _, _ = messageBytes.WriteString(committerSig.String())
+ _, _ = messageBytes.WriteString("\n")
+ _, _ = messageBytes.WriteString("Co-committed-by: ")
+ _, _ = messageBytes.WriteString(committerSig.String())
+ _, _ = messageBytes.WriteString("\n")
+ }
+ committerSig = signer
+ }
+ } else if git.CheckGitVersionAtLeast("2.0.0") == nil {
+ args = append(args, "--no-gpg-sign")
+ }
+ }
+
+ if signoff {
+ // Signed-off-by
+ _, _ = messageBytes.WriteString("\n")
+ _, _ = messageBytes.WriteString("Signed-off-by: ")
+ _, _ = messageBytes.WriteString(committerSig.String())
+ }
+
+ env = append(env,
+ "GIT_COMMITTER_NAME="+committerSig.Name,
+ "GIT_COMMITTER_EMAIL="+committerSig.Email,
+ )
+
+ stdout := new(bytes.Buffer)
+ stderr := new(bytes.Buffer)
+ if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil {
+ log.Error("Unable to commit-tree in temporary repo: %s (%s) Error: %v\nStdout: %s\nStderr: %s",
+ t.repo.FullName(), t.basePath, err, stdout, stderr)
+ return "", fmt.Errorf("Unable to commit-tree in temporary repo: %s Error: %v\nStdout: %s\nStderr: %s",
+ t.repo.FullName(), err, stdout, stderr)
+ }
+ return strings.TrimSpace(stdout.String()), nil
+}
+
+// Push the provided commitHash to the repository branch by the provided user
+func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, branch string) error {
+ // Because calls hooks we need to pass in the environment
+ env := models.PushingEnvironment(doer, t.repo)
+ if err := git.Push(t.basePath, git.PushOptions{
+ Remote: t.repo.RepoPath(),
+ Branch: strings.TrimSpace(commitHash) + ":refs/heads/" + strings.TrimSpace(branch),
+ Env: env,
+ }); err != nil {
+ if git.IsErrPushOutOfDate(err) {
+ return err
+ } else if git.IsErrPushRejected(err) {
+ rejectErr := err.(*git.ErrPushRejected)
+ log.Info("Unable to push back to repo from temporary repo due to rejection: %s (%s)\nStdout: %s\nStderr: %s\nError: %v",
+ t.repo.FullName(), t.basePath, rejectErr.StdOut, rejectErr.StdErr, rejectErr.Err)
+ return err
+ }
+ log.Error("Unable to push back to repo from temporary repo: %s (%s)\nError: %v",
+ t.repo.FullName(), t.basePath, err)
+ return fmt.Errorf("Unable to push back to repo from temporary repo: %s (%s) Error: %v",
+ t.repo.FullName(), t.basePath, err)
+ }
+ return nil
+}
+
+// DiffIndex returns a Diff of the current index to the head
+func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
+ stdoutReader, stdoutWriter, err := os.Pipe()
+ if err != nil {
+ log.Error("Unable to open stdout pipe: %v", err)
+ return nil, fmt.Errorf("Unable to open stdout pipe: %v", err)
+ }
+ defer func() {
+ _ = stdoutReader.Close()
+ _ = stdoutWriter.Close()
+ }()
+ stderr := new(bytes.Buffer)
+ var diff *gitdiff.Diff
+ var finalErr error
+
+ if err := git.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
+ RunInDirTimeoutEnvFullPipelineFunc(nil, 30*time.Second, t.basePath, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error {
+ _ = stdoutWriter.Close()
+ diff, finalErr = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
+ if finalErr != nil {
+ log.Error("ParsePatch: %v", finalErr)
+ cancel()
+ }
+ _ = stdoutReader.Close()
+ return finalErr
+ }); err != nil {
+ if finalErr != nil {
+ log.Error("Unable to ParsePatch in temporary repo %s (%s). Error: %v", t.repo.FullName(), t.basePath, finalErr)
+ return nil, finalErr
+ }
+ log.Error("Unable to run diff-index pipeline in temporary repo %s (%s). Error: %v\nStderr: %s",
+ t.repo.FullName(), t.basePath, err, stderr)
+ return nil, fmt.Errorf("Unable to run diff-index pipeline in temporary repo %s. Error: %v\nStderr: %s",
+ t.repo.FullName(), err, stderr)
+ }
+
+ diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.basePath, "--cached", "HEAD")
+ if err != nil {
+ return nil, err
+ }
+
+ return diff, nil
+}
+
+// GetBranchCommit Gets the commit object of the given branch
+func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, error) {
+ if t.gitRepo == nil {
+ return nil, fmt.Errorf("repository has not been cloned")
+ }
+ return t.gitRepo.GetBranchCommit(branch)
+}
+
+// GetCommit Gets the commit object of the given commit ID
+func (t *TemporaryUploadRepository) GetCommit(commitID string) (*git.Commit, error) {
+ if t.gitRepo == nil {
+ return nil, fmt.Errorf("repository has not been cloned")
+ }
+ return t.gitRepo.GetCommit(commitID)
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+ "net/url"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+// GetTreeBySHA get the GitTreeResponse of a repository using a sha hash.
+func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recursive bool) (*api.GitTreeResponse, error) {
+ gitRepo, err := git.OpenRepository(repo.RepoPath())
+ if err != nil {
+ return nil, err
+ }
+ defer gitRepo.Close()
+ gitTree, err := gitRepo.GetTree(sha)
+ if err != nil || gitTree == nil {
+ return nil, models.ErrSHANotFound{
+ SHA: sha,
+ }
+ }
+ tree := new(api.GitTreeResponse)
+ tree.SHA = gitTree.ResolvedID.String()
+ tree.URL = repo.APIURL() + "/git/trees/" + url.PathEscape(tree.SHA)
+ var entries git.Entries
+ if recursive {
+ entries, err = gitTree.ListEntriesRecursive()
+ } else {
+ entries, err = gitTree.ListEntries()
+ }
+ if err != nil {
+ return nil, err
+ }
+ apiURL := repo.APIURL()
+ apiURLLen := len(apiURL)
+
+ // 51 is len(sha1) + len("/git/blobs/"). 40 + 11.
+ blobURL := make([]byte, apiURLLen+51)
+ copy(blobURL, apiURL)
+ copy(blobURL[apiURLLen:], "/git/blobs/")
+
+ // 51 is len(sha1) + len("/git/trees/"). 40 + 11.
+ treeURL := make([]byte, apiURLLen+51)
+ copy(treeURL, apiURL)
+ copy(treeURL[apiURLLen:], "/git/trees/")
+
+ // 40 is the size of the sha1 hash in hexadecimal format.
+ copyPos := len(treeURL) - 40
+
+ if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage {
+ perPage = setting.API.DefaultGitTreesPerPage
+ }
+ if page <= 0 {
+ page = 1
+ }
+ tree.Page = page
+ tree.TotalCount = len(entries)
+ rangeStart := perPage * (page - 1)
+ if rangeStart >= len(entries) {
+ return tree, nil
+ }
+ var rangeEnd int
+ if len(entries) > perPage {
+ tree.Truncated = true
+ }
+ if rangeStart+perPage < len(entries) {
+ rangeEnd = rangeStart + perPage
+ } else {
+ rangeEnd = len(entries)
+ }
+ tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart)
+ for e := rangeStart; e < rangeEnd; e++ {
+ i := e - rangeStart
+
+ tree.Entries[i].Path = entries[e].Name()
+ tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode())
+ tree.Entries[i].Type = entries[e].Type()
+ tree.Entries[i].Size = entries[e].Size()
+ tree.Entries[i].SHA = entries[e].ID.String()
+
+ if entries[e].IsDir() {
+ copy(treeURL[copyPos:], entries[e].ID.String())
+ tree.Entries[i].URL = string(treeURL)
+ } else {
+ copy(blobURL[copyPos:], entries[e].ID.String())
+ tree.Entries[i].URL = string(blobURL)
+ }
+ }
+ return tree, nil
+}
--- /dev/null
+// 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 files
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetTreeBySHA(t *testing.T) {
+ unittest.PrepareTestEnv(t)
+ ctx := test.MockContext(t, "user2/repo1")
+ test.LoadRepo(t, ctx, 1)
+ test.LoadRepoCommit(t, ctx)
+ test.LoadUser(t, ctx, 2)
+ test.LoadGitRepo(t, ctx)
+ defer ctx.Repo.GitRepo.Close()
+
+ sha := ctx.Repo.Repository.DefaultBranch
+ page := 1
+ perPage := 10
+ ctx.SetParams(":id", "1")
+ ctx.SetParams(":sha", sha)
+
+ tree, err := GetTreeBySHA(ctx.Repo.Repository, ctx.Params(":sha"), page, perPage, true)
+ assert.NoError(t, err)
+ expectedTree := &api.GitTreeResponse{
+ SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/trees/65f1bf27bc3bf70f64657658635e66094edbcb4d",
+ Entries: []api.GitEntry{
+ {
+ Path: "README.md",
+ Mode: "100644",
+ Type: "blob",
+ Size: 30,
+ SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f",
+ URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/4b4851ad51df6a7d9f25c979345979eaeb5b349f",
+ },
+ },
+ Truncated: false,
+ Page: 1,
+ TotalCount: 1,
+ }
+
+ assert.EqualValues(t, expectedTree, tree)
+}
--- /dev/null
+// 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 files
+
+import (
+ "bytes"
+ "fmt"
+ "path"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/charset"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ repo_service "code.gitea.io/gitea/services/repository"
+
+ stdcharset "golang.org/x/net/html/charset"
+ "golang.org/x/text/transform"
+)
+
+// IdentityOptions for a person's identity like an author or committer
+type IdentityOptions struct {
+ Name string
+ Email string
+}
+
+// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
+type CommitDateOptions struct {
+ Author time.Time
+ Committer time.Time
+}
+
+// UpdateRepoFileOptions holds the repository file update options
+type UpdateRepoFileOptions struct {
+ LastCommitID string
+ OldBranch string
+ NewBranch string
+ TreePath string
+ FromTreePath string
+ Message string
+ Content string
+ SHA string
+ IsNewFile bool
+ Author *IdentityOptions
+ Committer *IdentityOptions
+ Dates *CommitDateOptions
+ Signoff bool
+}
+
+func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string, bool) {
+ reader, err := entry.Blob().DataAsync()
+ if err != nil {
+ // return default
+ return "UTF-8", false
+ }
+ defer reader.Close()
+ buf := make([]byte, 1024)
+ n, err := util.ReadAtMost(reader, buf)
+ if err != nil {
+ // return default
+ return "UTF-8", false
+ }
+ buf = buf[:n]
+
+ if setting.LFS.StartServer {
+ pointer, _ := lfs.ReadPointerFromBuffer(buf)
+ if pointer.IsValid() {
+ meta, err := repo.GetLFSMetaObjectByOid(pointer.Oid)
+ if err != nil && err != models.ErrLFSObjectNotExist {
+ // return default
+ return "UTF-8", false
+ }
+ if meta != nil {
+ dataRc, err := lfs.ReadMetaObject(pointer)
+ if err != nil {
+ // return default
+ return "UTF-8", false
+ }
+ defer dataRc.Close()
+ buf = make([]byte, 1024)
+ n, err = util.ReadAtMost(dataRc, buf)
+ if err != nil {
+ // return default
+ return "UTF-8", false
+ }
+ buf = buf[:n]
+ }
+ }
+ }
+
+ encoding, err := charset.DetectEncoding(buf)
+ if err != nil {
+ // just default to utf-8 and no bom
+ return "UTF-8", false
+ }
+ if encoding == "UTF-8" {
+ return encoding, bytes.Equal(buf[0:3], charset.UTF8BOM)
+ }
+ charsetEncoding, _ := stdcharset.Lookup(encoding)
+ if charsetEncoding == nil {
+ return "UTF-8", false
+ }
+
+ result, n, err := transform.String(charsetEncoding.NewDecoder(), string(buf))
+ if err != nil {
+ // return default
+ return "UTF-8", false
+ }
+
+ if n > 2 {
+ return encoding, bytes.Equal([]byte(result)[0:3], charset.UTF8BOM)
+ }
+
+ return encoding, false
+}
+
+// CreateOrUpdateRepoFile adds or updates a file in the given repository
+func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *UpdateRepoFileOptions) (*structs.FileResponse, error) {
+ // If no branch name is set, assume default branch
+ if opts.OldBranch == "" {
+ opts.OldBranch = repo.DefaultBranch
+ }
+ if opts.NewBranch == "" {
+ opts.NewBranch = opts.OldBranch
+ }
+
+ // oldBranch must exist for this operation
+ if _, err := repo_service.GetBranch(repo, opts.OldBranch); err != nil {
+ return nil, err
+ }
+
+ // A NewBranch can be specified for the file to be created/updated in a new branch.
+ // Check to make sure the branch does not already exist, otherwise we can't proceed.
+ // If we aren't branching to a new branch, make sure user can commit to the given branch
+ if opts.NewBranch != opts.OldBranch {
+ existingBranch, err := repo_service.GetBranch(repo, opts.NewBranch)
+ if existingBranch != nil {
+ return nil, models.ErrBranchAlreadyExists{
+ BranchName: opts.NewBranch,
+ }
+ }
+ if err != nil && !git.IsErrBranchNotExist(err) {
+ return nil, err
+ }
+ } else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
+ return nil, err
+ }
+
+ // If FromTreePath is not set, set it to the opts.TreePath
+ if opts.TreePath != "" && opts.FromTreePath == "" {
+ opts.FromTreePath = opts.TreePath
+ }
+
+ // Check that the path given in opts.treePath is valid (not a git path)
+ treePath := CleanUploadFileName(opts.TreePath)
+ if treePath == "" {
+ return nil, models.ErrFilenameInvalid{
+ Path: opts.TreePath,
+ }
+ }
+ // If there is a fromTreePath (we are copying it), also clean it up
+ fromTreePath := CleanUploadFileName(opts.FromTreePath)
+ if fromTreePath == "" && opts.FromTreePath != "" {
+ return nil, models.ErrFilenameInvalid{
+ Path: opts.FromTreePath,
+ }
+ }
+
+ message := strings.TrimSpace(opts.Message)
+
+ author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
+
+ t, err := NewTemporaryUploadRepository(repo)
+ if err != nil {
+ log.Error("%v", err)
+ }
+ defer t.Close()
+ if err := t.Clone(opts.OldBranch); err != nil {
+ return nil, err
+ }
+ if err := t.SetDefaultIndex(); err != nil {
+ return nil, err
+ }
+
+ // Get the commit of the original branch
+ commit, err := t.GetBranchCommit(opts.OldBranch)
+ if err != nil {
+ return nil, err // Couldn't get a commit for the branch
+ }
+
+ // Assigned LastCommitID in opts if it hasn't been set
+ if opts.LastCommitID == "" {
+ opts.LastCommitID = commit.ID.String()
+ } else {
+ lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
+ if err != nil {
+ return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
+ }
+ opts.LastCommitID = lastCommitID.String()
+
+ }
+
+ encoding := "UTF-8"
+ bom := false
+ executable := false
+
+ if !opts.IsNewFile {
+ fromEntry, err := commit.GetTreeEntryByPath(fromTreePath)
+ if err != nil {
+ return nil, err
+ }
+ if opts.SHA != "" {
+ // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
+ if opts.SHA != fromEntry.ID.String() {
+ return nil, models.ErrSHADoesNotMatch{
+ Path: treePath,
+ GivenSHA: opts.SHA,
+ CurrentSHA: fromEntry.ID.String(),
+ }
+ }
+ } else if opts.LastCommitID != "" {
+ // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
+ // an error, but only if we aren't creating a new branch.
+ if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
+ if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
+ return nil, err
+ } else if changed {
+ return nil, models.ErrCommitIDDoesNotMatch{
+ GivenCommitID: opts.LastCommitID,
+ CurrentCommitID: opts.LastCommitID,
+ }
+ }
+ // The file wasn't modified, so we are good to delete it
+ }
+ } else {
+ // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits
+ // haven't been made. We throw an error if one wasn't provided.
+ return nil, models.ErrSHAOrCommitIDNotProvided{}
+ }
+ encoding, bom = detectEncodingAndBOM(fromEntry, repo)
+ executable = fromEntry.IsExecutable()
+ }
+
+ // For the path where this file will be created/updated, we need to make
+ // sure no parts of the path are existing files or links except for the last
+ // item in the path which is the file name, and that shouldn't exist IF it is
+ // a new file OR is being moved to a new path.
+ treePathParts := strings.Split(treePath, "/")
+ subTreePath := ""
+ for index, part := range treePathParts {
+ subTreePath = path.Join(subTreePath, part)
+ entry, err := commit.GetTreeEntryByPath(subTreePath)
+ if err != nil {
+ if git.IsErrNotExist(err) {
+ // Means there is no item with that name, so we're good
+ break
+ }
+ return nil, err
+ }
+ if index < len(treePathParts)-1 {
+ if !entry.IsDir() {
+ return nil, models.ErrFilePathInvalid{
+ Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
+ Path: subTreePath,
+ Name: part,
+ Type: git.EntryModeBlob,
+ }
+ }
+ } else if entry.IsLink() {
+ return nil, models.ErrFilePathInvalid{
+ Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath),
+ Path: subTreePath,
+ Name: part,
+ Type: git.EntryModeSymlink,
+ }
+ } else if entry.IsDir() {
+ return nil, models.ErrFilePathInvalid{
+ Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath),
+ Path: subTreePath,
+ Name: part,
+ Type: git.EntryModeTree,
+ }
+ } else if fromTreePath != treePath || opts.IsNewFile {
+ // The entry shouldn't exist if we are creating new file or moving to a new path
+ return nil, models.ErrRepoFileAlreadyExists{
+ Path: treePath,
+ }
+ }
+
+ }
+
+ // Get the two paths (might be the same if not moving) from the index if they exist
+ filesInIndex, err := t.LsFiles(opts.TreePath, opts.FromTreePath)
+ if err != nil {
+ return nil, fmt.Errorf("UpdateRepoFile: %v", err)
+ }
+ // If is a new file (not updating) then the given path shouldn't exist
+ if opts.IsNewFile {
+ for _, file := range filesInIndex {
+ if file == opts.TreePath {
+ return nil, models.ErrRepoFileAlreadyExists{
+ Path: opts.TreePath,
+ }
+ }
+ }
+ }
+
+ // Remove the old path from the tree
+ if fromTreePath != treePath && len(filesInIndex) > 0 {
+ for _, file := range filesInIndex {
+ if file == fromTreePath {
+ if err := t.RemoveFilesFromIndex(opts.FromTreePath); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ content := opts.Content
+ if bom {
+ content = string(charset.UTF8BOM) + content
+ }
+ if encoding != "UTF-8" {
+ charsetEncoding, _ := stdcharset.Lookup(encoding)
+ if charsetEncoding != nil {
+ result, _, err := transform.String(charsetEncoding.NewEncoder(), content)
+ if err != nil {
+ // Look if we can't encode back in to the original we should just stick with utf-8
+ log.Error("Error re-encoding %s (%s) as %s - will stay as UTF-8: %v", opts.TreePath, opts.FromTreePath, encoding, err)
+ result = content
+ }
+ content = result
+ } else {
+ log.Error("Unknown encoding: %s", encoding)
+ }
+ }
+ // Reset the opts.Content to our adjusted content to ensure that LFS gets the correct content
+ opts.Content = content
+ var lfsMetaObject *models.LFSMetaObject
+
+ if setting.LFS.StartServer {
+ // Check there is no way this can return multiple infos
+ filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
+ Attributes: []string{"filter"},
+ Filenames: []string{treePath},
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ if filename2attribute2info[treePath] != nil && filename2attribute2info[treePath]["filter"] == "lfs" {
+ // OK so we are supposed to LFS this data!
+ pointer, err := lfs.GeneratePointer(strings.NewReader(opts.Content))
+ if err != nil {
+ return nil, err
+ }
+ lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: repo.ID}
+ content = pointer.StringContent()
+ }
+ }
+ // Add the object to the database
+ objectHash, err := t.HashObject(strings.NewReader(content))
+ if err != nil {
+ return nil, err
+ }
+
+ // Add the object to the index
+ if executable {
+ if err := t.AddObjectToIndex("100755", objectHash, treePath); err != nil {
+ return nil, err
+ }
+ } else {
+ if err := t.AddObjectToIndex("100644", objectHash, treePath); err != nil {
+ return nil, err
+ }
+ }
+
+ // Now write the tree
+ treeHash, err := t.WriteTree()
+ if err != nil {
+ return nil, err
+ }
+
+ // Now commit the tree
+ var commitHash string
+ if opts.Dates != nil {
+ commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
+ } else {
+ commitHash, err = t.CommitTree(author, committer, treeHash, message, opts.Signoff)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if lfsMetaObject != nil {
+ // We have an LFS object - create it
+ lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
+ if err != nil {
+ return nil, err
+ }
+ contentStore := lfs.NewContentStore()
+ exist, err := contentStore.Exists(lfsMetaObject.Pointer)
+ if err != nil {
+ return nil, err
+ }
+ if !exist {
+ if err := contentStore.Put(lfsMetaObject.Pointer, strings.NewReader(opts.Content)); err != nil {
+ if _, err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
+ return nil, fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
+ }
+ return nil, err
+ }
+ }
+ }
+
+ // Then push this tree to NewBranch
+ if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
+ log.Error("%T %v", err, err)
+ return nil, err
+ }
+
+ commit, err = t.GetCommit(commitHash)
+ if err != nil {
+ return nil, err
+ }
+
+ file, err := GetFileResponseFromCommit(repo, commit, opts.NewBranch, treePath)
+ if err != nil {
+ return nil, err
+ }
+ return file, nil
+}
+
+// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
+func VerifyBranchProtection(repo *models.Repository, doer *models.User, branchName string, treePath string) error {
+ protectedBranch, err := repo.GetBranchProtection(branchName)
+ if err != nil {
+ return err
+ }
+ if protectedBranch != nil {
+ isUnprotectedFile := false
+ glob := protectedBranch.GetUnprotectedFilePatterns()
+ if len(glob) != 0 {
+ isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath)
+ }
+ if !protectedBranch.CanUserPush(doer.ID) && !isUnprotectedFile {
+ return models.ErrUserCannotCommit{
+ UserName: doer.LowerName,
+ }
+ }
+ if protectedBranch.RequireSignedCommits {
+ _, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), branchName)
+ if err != nil {
+ if !models.IsErrWontSign(err) {
+ return err
+ }
+ return models.ErrUserCannotCommit{
+ UserName: doer.LowerName,
+ }
+ }
+ }
+ patterns := protectedBranch.GetProtectedFilePatterns()
+ for _, pat := range patterns {
+ if pat.Match(strings.ToLower(treePath)) {
+ return models.ErrFilePathProtected{
+ Path: treePath,
+ }
+ }
+ }
+ }
+ return nil
+}
--- /dev/null
+// 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 files
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// UploadRepoFileOptions contains the uploaded repository file options
+type UploadRepoFileOptions struct {
+ LastCommitID string
+ OldBranch string
+ NewBranch string
+ TreePath string
+ Message string
+ Files []string // In UUID format.
+ Signoff bool
+}
+
+type uploadInfo struct {
+ upload *models.Upload
+ lfsMetaObject *models.LFSMetaObject
+}
+
+func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
+ for _, info := range *infos {
+ if info.lfsMetaObject == nil {
+ continue
+ }
+ if !info.lfsMetaObject.Existing {
+ if _, err := t.repo.RemoveLFSMetaObjectByOid(info.lfsMetaObject.Oid); err != nil {
+ original = fmt.Errorf("%v, %v", original, err)
+ }
+ }
+ }
+ return original
+}
+
+// UploadRepoFiles uploads files to the given repository
+func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRepoFileOptions) error {
+ if len(opts.Files) == 0 {
+ return nil
+ }
+
+ uploads, err := models.GetUploadsByUUIDs(opts.Files)
+ if err != nil {
+ return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err)
+ }
+
+ names := make([]string, len(uploads))
+ infos := make([]uploadInfo, len(uploads))
+ for i, upload := range uploads {
+ // Check file is not lfs locked, will return nil if lock setting not enabled
+ filepath := path.Join(opts.TreePath, upload.Name)
+ lfsLock, err := repo.GetTreePathLock(filepath)
+ if err != nil {
+ return err
+ }
+ if lfsLock != nil && lfsLock.OwnerID != doer.ID {
+ return models.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: lfsLock.Owner.Name}
+ }
+
+ names[i] = upload.Name
+ infos[i] = uploadInfo{upload: upload}
+ }
+
+ t, err := NewTemporaryUploadRepository(repo)
+ if err != nil {
+ return err
+ }
+ defer t.Close()
+ if err := t.Clone(opts.OldBranch); err != nil {
+ return err
+ }
+ if err := t.SetDefaultIndex(); err != nil {
+ return err
+ }
+
+ var filename2attribute2info map[string]map[string]string
+ if setting.LFS.StartServer {
+ filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
+ Attributes: []string{"filter"},
+ Filenames: names,
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ // Copy uploaded files into repository.
+ for i := range infos {
+ if err := copyUploadedLFSFileIntoRepository(&infos[i], filename2attribute2info, t, opts.TreePath); err != nil {
+ return err
+ }
+ }
+
+ // Now write the tree
+ treeHash, err := t.WriteTree()
+ if err != nil {
+ return err
+ }
+
+ // make author and committer the doer
+ author := doer
+ committer := doer
+
+ // Now commit the tree
+ commitHash, err := t.CommitTree(author, committer, treeHash, opts.Message, opts.Signoff)
+ if err != nil {
+ return err
+ }
+
+ // Now deal with LFS objects
+ for i := range infos {
+ if infos[i].lfsMetaObject == nil {
+ continue
+ }
+ infos[i].lfsMetaObject, err = models.NewLFSMetaObject(infos[i].lfsMetaObject)
+ if err != nil {
+ // OK Now we need to cleanup
+ return cleanUpAfterFailure(&infos, t, err)
+ }
+ // Don't move the files yet - we need to ensure that
+ // everything can be inserted first
+ }
+
+ // OK now we can insert the data into the store - there's no way to clean up the store
+ // once it's in there, it's in there.
+ contentStore := lfs.NewContentStore()
+ for _, info := range infos {
+ if err := uploadToLFSContentStore(info, contentStore); err != nil {
+ return cleanUpAfterFailure(&infos, t, err)
+ }
+ }
+
+ // Then push this tree to NewBranch
+ if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
+ return err
+ }
+
+ return models.DeleteUploads(uploads...)
+}
+
+func copyUploadedLFSFileIntoRepository(info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error {
+ file, err := os.Open(info.upload.LocalPath())
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ var objectHash string
+ if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" {
+ // Handle LFS
+ // FIXME: Inefficient! this should probably happen in models.Upload
+ pointer, err := lfs.GeneratePointer(file)
+ if err != nil {
+ return err
+ }
+
+ info.lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
+
+ if objectHash, err = t.HashObject(strings.NewReader(pointer.StringContent())); err != nil {
+ return err
+ }
+ } else if objectHash, err = t.HashObject(file); err != nil {
+ return err
+ }
+
+ // Add the object to the index
+ return t.AddObjectToIndex("100644", objectHash, path.Join(treePath, info.upload.Name))
+}
+
+func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) error {
+ if info.lfsMetaObject == nil {
+ return nil
+ }
+ exist, err := contentStore.Exists(info.lfsMetaObject.Pointer)
+ if err != nil {
+ return err
+ }
+ if !exist {
+ file, err := os.Open(info.upload.LocalPath())
+ if err != nil {
+ return err
+ }
+
+ defer file.Close()
+ // FIXME: Put regenerates the hash and copies the file over.
+ // I guess this strictly ensures the soundness of the store but this is inefficient.
+ if err := contentStore.Put(info.lfsMetaObject.Pointer, file); err != nil {
+ // OK Now we need to cleanup
+ // Can't clean up the store, once uploaded there they're there.
+ return err
+ }
+ }
+ return nil
+}