]> source.dussan.org Git - gitea.git/commitdiff
Move repofiles from modules/repofiles to services/repository/files (#17774)
authorLunny Xiao <xiaolunwen@gmail.com>
Wed, 24 Nov 2021 07:56:24 +0000 (15:56 +0800)
committerGitHub <noreply@github.com>
Wed, 24 Nov 2021 07:56:24 +0000 (15:56 +0800)
* Move repofiles from modules to services

* rename services/repository/repofiles -> services/repository/files

* Fix test

Co-authored-by: 6543 <6543@obermui.de>
50 files changed:
integrations/api_repo_file_helpers.go
integrations/pull_update_test.go
integrations/repofiles_delete_test.go
integrations/repofiles_update_test.go
modules/convert/pull.go
modules/repofiles/blob.go [deleted file]
modules/repofiles/blob_test.go [deleted file]
modules/repofiles/commit.go [deleted file]
modules/repofiles/commit_status.go [deleted file]
modules/repofiles/content.go [deleted file]
modules/repofiles/content_test.go [deleted file]
modules/repofiles/delete.go [deleted file]
modules/repofiles/diff.go [deleted file]
modules/repofiles/diff_test.go [deleted file]
modules/repofiles/file.go [deleted file]
modules/repofiles/file_test.go [deleted file]
modules/repofiles/repofiles.go [deleted file]
modules/repofiles/repofiles_test.go [deleted file]
modules/repofiles/temp_repo.go [deleted file]
modules/repofiles/tree.go [deleted file]
modules/repofiles/tree_test.go [deleted file]
modules/repofiles/update.go [deleted file]
modules/repofiles/upload.go [deleted file]
modules/repofiles/verification.go [deleted file]
modules/repository/branch.go [deleted file]
modules/repository/check.go [deleted file]
routers/api/v1/repo/blob.go
routers/api/v1/repo/branch.go
routers/api/v1/repo/file.go
routers/api/v1/repo/status.go
routers/api/v1/repo/tree.go
routers/web/repo/branch.go
routers/web/repo/editor.go
services/cron/tasks_basic.go
services/cron/tasks_extended.go
services/repository/branch.go
services/repository/check.go [new file with mode: 0644]
services/repository/files/commit.go [new file with mode: 0644]
services/repository/files/content.go [new file with mode: 0644]
services/repository/files/content_test.go [new file with mode: 0644]
services/repository/files/delete.go [new file with mode: 0644]
services/repository/files/diff.go [new file with mode: 0644]
services/repository/files/diff_test.go [new file with mode: 0644]
services/repository/files/file.go [new file with mode: 0644]
services/repository/files/file_test.go [new file with mode: 0644]
services/repository/files/temp_repo.go [new file with mode: 0644]
services/repository/files/tree.go [new file with mode: 0644]
services/repository/files/tree_test.go [new file with mode: 0644]
services/repository/files/update.go [new file with mode: 0644]
services/repository/files/upload.go [new file with mode: 0644]

index 1dba136de959a81d2a2d99d0705b23326306463b..ef3b8c610bbb1d6c8d73ea98a3b245f556385486 100644 (file)
@@ -6,12 +6,12 @@ package integrations
 
 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,
@@ -19,7 +19,7 @@ func createFileInBranch(user *models.User, repo *models.Repository, treePath, br
                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) {
index 43cfe7debfa2217c8fdfd2e8f445a21b3df42c05..d7ea676b79ad99fd99ce25b695aa199234f4c0c9 100644 (file)
@@ -12,9 +12,9 @@ import (
 
        "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"
 )
@@ -97,22 +97,22 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq
        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(),
                },
@@ -120,22 +120,22 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq
        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(),
                },
index eb73348950260926fe5ff75e9abb2dda853e3768..8490f8d3a7cdef57d43b3c96a7a39202852f2480 100644 (file)
@@ -10,22 +10,22 @@ import (
 
        "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",
                },
@@ -80,7 +80,7 @@ func testDeleteRepoFile(t *testing.T, u *url.URL) {
        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)
@@ -92,7 +92,7 @@ func testDeleteRepoFile(t *testing.T, u *url.URL) {
        })
 
        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)
@@ -122,7 +122,7 @@ func testDeleteRepoFileWithoutBranchNames(t *testing.T, u *url.URL) {
        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)
@@ -151,7 +151,7 @@ func TestDeleteRepoFileErrors(t *testing.T) {
        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 + "]"
@@ -162,7 +162,7 @@ func TestDeleteRepoFileErrors(t *testing.T) {
                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 + "]"
@@ -172,7 +172,7 @@ func TestDeleteRepoFileErrors(t *testing.T) {
        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 + "]"
@@ -182,7 +182,7 @@ func TestDeleteRepoFileErrors(t *testing.T) {
        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: ]"
@@ -192,7 +192,7 @@ func TestDeleteRepoFileErrors(t *testing.T) {
        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 + "]"
index 9e99b36ae422640125231638bb988a657c1a830b..fe0e2c21c6caf06529a3a9ef8c17e92ef7d6f49f 100644 (file)
@@ -12,16 +12,16 @@ import (
 
        "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",
@@ -33,8 +33,8 @@ func getCreateRepoFileOptions(repo *models.Repository) *repofiles.UpdateRepoFile
        }
 }
 
-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",
@@ -198,7 +198,7 @@ func TestCreateOrUpdateRepoFileForCreate(t *testing.T) {
                opts := getCreateRepoFileOptions(repo)
 
                // test
-               fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+               fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
 
                // asserts
                assert.NoError(t, err)
@@ -234,7 +234,7 @@ func TestCreateOrUpdateRepoFileForUpdate(t *testing.T) {
                opts := getUpdateRepoFileOptions(repo)
 
                // test
-               fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+               fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
 
                // asserts
                assert.NoError(t, err)
@@ -269,7 +269,7 @@ func TestCreateOrUpdateRepoFileForUpdateWithFileMove(t *testing.T) {
                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)
@@ -319,7 +319,7 @@ func TestCreateOrUpdateRepoFileWithoutBranchNames(t *testing.T) {
                opts.NewBranch = ""
 
                // test
-               fileResponse, err := repofiles.CreateOrUpdateRepoFile(repo, doer, opts)
+               fileResponse, err := files_service.CreateOrUpdateRepoFile(repo, doer, opts)
 
                // asserts
                assert.NoError(t, err)
@@ -349,7 +349,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                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 + "]"
@@ -360,7 +360,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                        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 + "]"
@@ -370,7 +370,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                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 + "]"
@@ -380,7 +380,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                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: ]"
@@ -390,7 +390,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                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 + "]"
@@ -400,7 +400,7 @@ func TestCreateOrUpdateRepoFileErrors(t *testing.T) {
                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 + "]"
index ab17c13421357c70ef898d76b7a795f6ec189358..0f2b5af1b41f20f7962fad3e95e9d11856af7d5e 100644 (file)
@@ -10,7 +10,6 @@ import (
        "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"
 )
 
@@ -83,7 +82,14 @@ func ToAPIPullRequest(pr *models.PullRequest, doer *models.User) *api.PullReques
                },
        }
 
-       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
diff --git a/modules/repofiles/blob.go b/modules/repofiles/blob.go
deleted file mode 100644 (file)
index 02bc1eb..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/blob_test.go b/modules/repofiles/blob_test.go
deleted file mode 100644 (file)
index 8950c34..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "testing"
-
-       "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)
-}
diff --git a/modules/repofiles/commit.go b/modules/repofiles/commit.go
deleted file mode 100644 (file)
index 371e6cf..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/commit_status.go b/modules/repofiles/commit_status.go
deleted file mode 100644 (file)
index 3d93c58..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/content.go b/modules/repofiles/content.go
deleted file mode 100644 (file)
index 838bfab..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/content_test.go b/modules/repofiles/content_test.go
deleted file mode 100644 (file)
index f6a953f..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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)
-       })
-}
diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go
deleted file mode 100644 (file)
index 5ae418b..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/diff.go b/modules/repofiles/diff.go
deleted file mode 100644 (file)
index c98bbc7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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()
-}
diff --git a/modules/repofiles/diff_test.go b/modules/repofiles/diff_test.go
deleted file mode 100644 (file)
index 4bd1ef6..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "testing"
-
-       "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: ]")
-       })
-}
diff --git a/modules/repofiles/file.go b/modules/repofiles/file.go
deleted file mode 100644 (file)
index 4030924..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/file_test.go b/modules/repofiles/file_test.go
deleted file mode 100644 (file)
index 54205a8..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "testing"
-
-       "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)
-}
diff --git a/modules/repofiles/repofiles.go b/modules/repofiles/repofiles.go
deleted file mode 100644 (file)
index 1fc9004..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.package repofiles
-
-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
-}
diff --git a/modules/repofiles/repofiles_test.go b/modules/repofiles/repofiles_test.go
deleted file mode 100644 (file)
index 1686378..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "testing"
-
-       "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)
-       })
-}
diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go
deleted file mode 100644 (file)
index 700ce92..0000000
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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)
-}
diff --git a/modules/repofiles/tree.go b/modules/repofiles/tree.go
deleted file mode 100644 (file)
index 81579dc..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/tree_test.go b/modules/repofiles/tree_test.go
deleted file mode 100644 (file)
index 6917b89..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "testing"
-
-       "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)
-}
diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go
deleted file mode 100644 (file)
index d25accf..0000000
+++ /dev/null
@@ -1,479 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go
deleted file mode 100644 (file)
index e97f55a..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repofiles/verification.go b/modules/repofiles/verification.go
deleted file mode 100644 (file)
index 3889b79..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package repofiles
-
-import (
-       "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
-}
diff --git a/modules/repository/branch.go b/modules/repository/branch.go
deleted file mode 100644 (file)
index dcd8255..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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)
-}
diff --git a/modules/repository/check.go b/modules/repository/check.go
deleted file mode 100644 (file)
index 36bd4e2..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-// 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
-}
index 3918a49d5147c7b850c47cfe9d11db71113f698c..e3ce0e19168857106f3ec866047f1bbc4c13bb6b 100644 (file)
@@ -8,7 +8,7 @@ import (
        "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.
@@ -45,7 +45,7 @@ func GetBlob(ctx *context.APIContext) {
                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)
index 2e08b568266de0529b64bf44b86255e9b3e5a425..04da9012396b96421d9ba6c898c4424a51ebe4f1 100644 (file)
@@ -14,7 +14,6 @@ import (
        "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"
@@ -53,7 +52,7 @@ func GetBranch(ctx *context.APIContext) {
 
        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)
@@ -198,7 +197,7 @@ func CreateBranch(ctx *context.APIContext) {
                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
index 009b8a26d60bb1379e41f0c748b17774c5d29eff..e84f652ed945cf1ec525bdb1f3a4009f4455b1aa 100644 (file)
@@ -15,11 +15,11 @@ import (
        "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
@@ -240,22 +240,22 @@ func CreateFile(ctx *context.APIContext) {
                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,
                },
@@ -327,7 +327,7 @@ func UpdateFile(ctx *context.APIContext) {
                apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
        }
 
-       opts := &repofiles.UpdateRepoFileOptions{
+       opts := &files_service.UpdateRepoFileOptions{
                Content:      apiOpts.Content,
                SHA:          apiOpts.SHA,
                IsNewFile:    false,
@@ -336,15 +336,15 @@ func UpdateFile(ctx *context.APIContext) {
                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,
                },
@@ -387,7 +387,7 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
 }
 
 // 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,
@@ -401,7 +401,7 @@ func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileO
        }
        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
@@ -457,21 +457,21 @@ func DeleteFile(ctx *context.APIContext) {
                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,
                },
@@ -488,7 +488,7 @@ func DeleteFile(ctx *context.APIContext) {
                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
@@ -554,7 +554,7 @@ func GetContents(ctx *context.APIContext) {
        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
index b884432f73b86739ba717f87d0bb1d664dbe7f12..583b89dd7ab77a0eee11fb31441b5683c7f89a94 100644 (file)
@@ -11,10 +11,10 @@ import (
        "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
@@ -62,7 +62,7 @@ func NewCommitStatus(ctx *context.APIContext) {
                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
        }
index aec336235d3daba12a69265771608f6710b7fd59..2168a311d01a5c9651b76665a59f4d1952b55676 100644 (file)
@@ -8,7 +8,7 @@ import (
        "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.
@@ -60,7 +60,7 @@ func GetTree(ctx *context.APIContext) {
                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)
index a6ad3eff5aa831d8630c187a95f7e1fbd1610acd..64fb1afb2df9bff166ed4825ffdaf87c813f456a 100644 (file)
@@ -17,7 +17,6 @@ import (
        "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"
@@ -26,6 +25,7 @@ import (
        "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 (
@@ -165,7 +165,7 @@ func redirect(ctx *context.Context) {
 // 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)
@@ -242,7 +242,7 @@ func loadOneBranch(ctx *context.Context, rawBranch *git.Branch, protectedBranche
                }
        }
 
-       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
index 088edbfd29b8d41b9dbee366c7c6191e2d80f0ab..d4fc55979c060db1adec538d920df4a0ed1bfe8c 100644 (file)
@@ -19,8 +19,6 @@ import (
        "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"
@@ -28,6 +26,8 @@ import (
        "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 (
@@ -244,7 +244,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
                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,
@@ -255,7 +255,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
                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) {
@@ -369,7 +369,7 @@ func DiffPreviewPost(ctx *context.Context) {
                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
@@ -450,7 +450,7 @@ func DeleteFilePost(ctx *context.Context) {
                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,
@@ -614,7 +614,7 @@ func UploadFilePost(ctx *context.Context) {
        }
 
        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
@@ -658,7 +658,7 @@ func UploadFilePost(ctx *context.Context) {
                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,
@@ -806,7 +806,7 @@ func GetUniquePatchBranchName(ctx *context.Context) string {
        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
                        }
index 219173ccf014046803ac2158f60a2f8f88f5b0e4..4832ca98a734f2bf20191f61393003d95c39fe75 100644 (file)
@@ -10,11 +10,11 @@ import (
 
        "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() {
index b0cb5ee921ca8b71a04f2338ff899b2fb0f4d954..95293b1c1d06e59fb77fa87ea6751eddc14c1a3a 100644 (file)
@@ -9,7 +9,6 @@ import (
        "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"
@@ -56,7 +55,7 @@ func registerGarbageCollectRepositories() {
                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...)
        })
 }
 
@@ -96,7 +95,7 @@ func registerReinitMissingRepositories() {
                RunAtStart: false,
                Schedule:   "@every 72h",
        }, func(ctx context.Context, _ *models.User, _ Config) error {
-               return repo_module.ReinitMissingRepositories(ctx)
+               return repo_service.ReinitMissingRepositories(ctx)
        })
 }
 
@@ -106,7 +105,7 @@ func registerDeleteMissingRepositories() {
                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)
        })
 }
 
index 83980c5bd994f85799409df93cd4c0b6207ed505..f94a9afcb67925ca6679a7a49f0e040405fec435 100644 (file)
@@ -43,6 +43,20 @@ func CreateNewBranch(doer *models.User, repo *models.Repository, oldBranchName,
        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) {
diff --git a/services/repository/check.go b/services/repository/check.go
new file mode 100644 (file)
index 0000000..36bd4e2
--- /dev/null
@@ -0,0 +1,205 @@
+// 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
+}
diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go
new file mode 100644 (file)
index 0000000..ebae097
--- /dev/null
@@ -0,0 +1,73 @@
+// 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
+}
diff --git a/services/repository/files/content.go b/services/repository/files/content.go
new file mode 100644 (file)
index 0000000..afb775f
--- /dev/null
@@ -0,0 +1,245 @@
+// 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
+}
diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go
new file mode 100644 (file)
index 0000000..68b29b1
--- /dev/null
@@ -0,0 +1,245 @@
+// 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)
+}
diff --git a/services/repository/files/delete.go b/services/repository/files/delete.go
new file mode 100644 (file)
index 0000000..f8d7f62
--- /dev/null
@@ -0,0 +1,197 @@
+// 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
+}
diff --git a/services/repository/files/diff.go b/services/repository/files/diff.go
new file mode 100644 (file)
index 0000000..fadaf20
--- /dev/null
@@ -0,0 +1,42 @@
+// 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()
+}
diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go
new file mode 100644 (file)
index 0000000..be8e920
--- /dev/null
@@ -0,0 +1,172 @@
+// 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: ]")
+       })
+}
diff --git a/services/repository/files/file.go b/services/repository/files/file.go
new file mode 100644 (file)
index 0000000..ad445bb
--- /dev/null
@@ -0,0 +1,139 @@
+// 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
+}
diff --git a/services/repository/files/file_test.go b/services/repository/files/file_test.go
new file mode 100644 (file)
index 0000000..25303a6
--- /dev/null
@@ -0,0 +1,120 @@
+// 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)
+}
diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go
new file mode 100644 (file)
index 0000000..2a6d6c3
--- /dev/null
@@ -0,0 +1,345 @@
+// 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)
+}
diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go
new file mode 100644 (file)
index 0000000..ede2062
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package 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
+}
diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go
new file mode 100644 (file)
index 0000000..37a6025
--- /dev/null
@@ -0,0 +1,53 @@
+// 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)
+}
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
new file mode 100644 (file)
index 0000000..5d6c3da
--- /dev/null
@@ -0,0 +1,479 @@
+// 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
+}
diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go
new file mode 100644 (file)
index 0000000..98d0150
--- /dev/null
@@ -0,0 +1,207 @@
+// 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
+}