Refactor editor upload, update and delete to use git plumbing and add LFS support (#5702)
* Use git plumbing for upload: #5621 repo_editor.go: UploadRepoFile
* Use git plumbing for upload: #5621 repo_editor.go: GetDiffPreview
* Use git plumbing for upload: #5621 repo_editor.go: DeleteRepoFile
* Use git plumbing for upload: #5621 repo_editor.go: UploadRepoFiles
* Move branch checkout functions out of repo_editor.go as they are no longer used there
* BUGFIX: The default permissions should be 100644
    This is a change from the previous code but is more in keeping
    with the default behaviour of git.
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Standardise cleanUploadFilename to more closely match git
See verify_path in: 7f4e641693/read-cache.c (L951)
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Redirect on bad paths
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Refactor to move the uploading functions out to a module
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add LFS support
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Update upload.go attribution header
Upload.go is essentially the remnants of repo_editor.go. The remaining code is essentially unchanged from the Gogs code, hence the Gogs attribution.
* Delete upload files after session committed
* Ensure that GIT_AUTHOR_NAME etc. are valid for git
see #5774
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add in test cases per @lafriks comment
* Add space between gitea and github imports
Signed-off-by: Andrew Thornton <art27@cantab.net>
* more examples in TestCleanUploadName
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix formatting
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Set the SSH_ORIGINAL_COMMAND to ensure hooks are run
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Switch off SSH_ORIGINAL_COMMAND
Signed-off-by: Andrew Thornton <art27@cantab.net>
			
			
This commit is contained in:
		
							parent
							
								
									fc038caa69
								
							
						
					
					
						commit
						296814e887
					
				| 
						 | 
					@ -1,7 +1,11 @@
 | 
				
			||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -16,6 +20,11 @@ type LFSMetaObject struct {
 | 
				
			||||||
	CreatedUnix  util.TimeStamp `xorm:"created"`
 | 
						CreatedUnix  util.TimeStamp `xorm:"created"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Pointer returns the string representation of an LFS pointer file
 | 
				
			||||||
 | 
					func (m *LFSMetaObject) Pointer() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s\n%s%s\nsize %d\n", LFSMetaFileIdentifier, LFSMetaFileOidPrefix, m.Oid, m.Size)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
 | 
					// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
 | 
				
			||||||
// This structure is fetched via SSH and passed by the Git LFS client to the server
 | 
					// This structure is fetched via SSH and passed by the Git LFS client to the server
 | 
				
			||||||
// endpoint for authorization.
 | 
					// endpoint for authorization.
 | 
				
			||||||
| 
						 | 
					@ -67,6 +76,16 @@ func NewLFSMetaObject(m *LFSMetaObject) (*LFSMetaObject, error) {
 | 
				
			||||||
	return m, sess.Commit()
 | 
						return m, sess.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenerateLFSOid generates a Sha256Sum to represent an oid for arbitrary content
 | 
				
			||||||
 | 
					func GenerateLFSOid(content io.Reader) (string, error) {
 | 
				
			||||||
 | 
						h := sha256.New()
 | 
				
			||||||
 | 
						if _, err := io.Copy(h, content); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sum := h.Sum(nil)
 | 
				
			||||||
 | 
						return hex.EncodeToString(sum), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
 | 
					// GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
 | 
				
			||||||
// It may return ErrLFSObjectNotExist or a database error. If the error is nil,
 | 
					// It may return ErrLFSObjectNotExist or a database error. If the error is nil,
 | 
				
			||||||
// the returned pointer is a valid LFSMetaObject.
 | 
					// the returned pointer is a valid LFSMetaObject.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,46 @@ import (
 | 
				
			||||||
	"github.com/Unknwon/com"
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// discardLocalRepoBranchChanges discards local commits/changes of
 | 
				
			||||||
 | 
					// given branch to make sure it is even to remote branch.
 | 
				
			||||||
 | 
					func discardLocalRepoBranchChanges(localPath, branch string) error {
 | 
				
			||||||
 | 
						if !com.IsExist(localPath) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// No need to check if nothing in the repository.
 | 
				
			||||||
 | 
						if !git.IsBranchExist(localPath, branch) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						refName := "origin/" + branch
 | 
				
			||||||
 | 
						if err := git.ResetHEAD(localPath, true, refName); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("git reset --hard %s: %v", refName, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DiscardLocalRepoBranchChanges discards the local repository branch changes
 | 
				
			||||||
 | 
					func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error {
 | 
				
			||||||
 | 
						return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// checkoutNewBranch checks out to a new branch from the a branch name.
 | 
				
			||||||
 | 
					func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error {
 | 
				
			||||||
 | 
						if err := git.Checkout(localPath, git.CheckoutOptions{
 | 
				
			||||||
 | 
							Timeout:   time.Duration(setting.Git.Timeout.Pull) * time.Second,
 | 
				
			||||||
 | 
							Branch:    newBranch,
 | 
				
			||||||
 | 
							OldBranch: oldBranch,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckoutNewBranch checks out a new branch
 | 
				
			||||||
 | 
					func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
 | 
				
			||||||
 | 
						return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch holds the branch information
 | 
					// Branch holds the branch information
 | 
				
			||||||
type Branch struct {
 | 
					type Branch struct {
 | 
				
			||||||
	Path string
 | 
						Path string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,572 +0,0 @@
 | 
				
			||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
					 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"mime/multipart"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/Unknwon/com"
 | 
					 | 
				
			||||||
	gouuid "github.com/satori/go.uuid"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/git"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/process"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ___________    .___.__  __    ___________.__.__
 | 
					 | 
				
			||||||
// \_   _____/  __| _/|__|/  |_  \_   _____/|__|  |   ____
 | 
					 | 
				
			||||||
//  |    __)_  / __ | |  \   __\  |    __)  |  |  | _/ __ \
 | 
					 | 
				
			||||||
//  |        \/ /_/ | |  ||  |    |     \   |  |  |_\  ___/
 | 
					 | 
				
			||||||
// /_______  /\____ | |__||__|    \___  /   |__|____/\___  >
 | 
					 | 
				
			||||||
//         \/      \/                 \/                 \/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// discardLocalRepoBranchChanges discards local commits/changes of
 | 
					 | 
				
			||||||
// given branch to make sure it is even to remote branch.
 | 
					 | 
				
			||||||
func discardLocalRepoBranchChanges(localPath, branch string) error {
 | 
					 | 
				
			||||||
	if !com.IsExist(localPath) {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// No need to check if nothing in the repository.
 | 
					 | 
				
			||||||
	if !git.IsBranchExist(localPath, branch) {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	refName := "origin/" + branch
 | 
					 | 
				
			||||||
	if err := git.ResetHEAD(localPath, true, refName); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git reset --hard %s: %v", refName, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DiscardLocalRepoBranchChanges discards the local repository branch changes
 | 
					 | 
				
			||||||
func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error {
 | 
					 | 
				
			||||||
	return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// checkoutNewBranch checks out to a new branch from the a branch name.
 | 
					 | 
				
			||||||
func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error {
 | 
					 | 
				
			||||||
	if err := git.Checkout(localPath, git.CheckoutOptions{
 | 
					 | 
				
			||||||
		Timeout:   time.Duration(setting.Git.Timeout.Pull) * time.Second,
 | 
					 | 
				
			||||||
		Branch:    newBranch,
 | 
					 | 
				
			||||||
		OldBranch: oldBranch,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CheckoutNewBranch checks out a new branch
 | 
					 | 
				
			||||||
func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
 | 
					 | 
				
			||||||
	return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdateRepoFileOptions holds the repository file update options
 | 
					 | 
				
			||||||
type UpdateRepoFileOptions struct {
 | 
					 | 
				
			||||||
	LastCommitID string
 | 
					 | 
				
			||||||
	OldBranch    string
 | 
					 | 
				
			||||||
	NewBranch    string
 | 
					 | 
				
			||||||
	OldTreeName  string
 | 
					 | 
				
			||||||
	NewTreeName  string
 | 
					 | 
				
			||||||
	Message      string
 | 
					 | 
				
			||||||
	Content      string
 | 
					 | 
				
			||||||
	IsNewFile    bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdateRepoFile adds or updates a file in repository.
 | 
					 | 
				
			||||||
func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (err error) {
 | 
					 | 
				
			||||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.OldBranch != opts.NewBranch {
 | 
					 | 
				
			||||||
		if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	localPath := repo.LocalCopyPath()
 | 
					 | 
				
			||||||
	oldFilePath := path.Join(localPath, opts.OldTreeName)
 | 
					 | 
				
			||||||
	filePath := path.Join(localPath, opts.NewTreeName)
 | 
					 | 
				
			||||||
	dir := path.Dir(filePath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Failed to create dir %s: %v", dir, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If it's meant to be a new file, make sure it doesn't exist.
 | 
					 | 
				
			||||||
	if opts.IsNewFile {
 | 
					 | 
				
			||||||
		if com.IsExist(filePath) {
 | 
					 | 
				
			||||||
			return ErrRepoFileAlreadyExist{filePath}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Ignore move step if it's a new file under a directory.
 | 
					 | 
				
			||||||
	// Otherwise, move the file when name changed.
 | 
					 | 
				
			||||||
	if com.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName {
 | 
					 | 
				
			||||||
		if err = git.MoveFile(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("git mv %s %s: %v", opts.OldTreeName, opts.NewTreeName, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = ioutil.WriteFile(filePath, []byte(opts.Content), 0666); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("WriteFile: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git add --all: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
 | 
					 | 
				
			||||||
		Committer: doer.NewGitSig(),
 | 
					 | 
				
			||||||
		Message:   opts.Message,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.Push(localPath, git.PushOptions{
 | 
					 | 
				
			||||||
		Remote: "origin",
 | 
					 | 
				
			||||||
		Branch: opts.NewBranch,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "OpenRepository: %v", err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Simulate push event.
 | 
					 | 
				
			||||||
	oldCommitID := opts.LastCommitID
 | 
					 | 
				
			||||||
	if opts.NewBranch != opts.OldBranch {
 | 
					 | 
				
			||||||
		oldCommitID = git.EmptySHA
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.GetOwner(); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("GetOwner: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = PushUpdate(
 | 
					 | 
				
			||||||
		opts.NewBranch,
 | 
					 | 
				
			||||||
		PushUpdateOptions{
 | 
					 | 
				
			||||||
			PusherID:     doer.ID,
 | 
					 | 
				
			||||||
			PusherName:   doer.Name,
 | 
					 | 
				
			||||||
			RepoUserName: repo.Owner.Name,
 | 
					 | 
				
			||||||
			RepoName:     repo.Name,
 | 
					 | 
				
			||||||
			RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
					 | 
				
			||||||
			OldCommitID:  oldCommitID,
 | 
					 | 
				
			||||||
			NewCommitID:  commit.ID.String(),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("PushUpdate: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	UpdateRepoIndexer(repo)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetDiffPreview produces and returns diff result of a file which is not yet committed.
 | 
					 | 
				
			||||||
func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *Diff, err error) {
 | 
					 | 
				
			||||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.DiscardLocalRepoBranchChanges(branch); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", branch, err)
 | 
					 | 
				
			||||||
	} else if err = repo.UpdateLocalCopyBranch(branch); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", branch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	localPath := repo.LocalCopyPath()
 | 
					 | 
				
			||||||
	filePath := path.Join(localPath, treePath)
 | 
					 | 
				
			||||||
	dir := filepath.Dir(filePath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Failed to create dir %s: %v", dir, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = ioutil.WriteFile(filePath, []byte(content), 0666); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("WriteFile: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cmd := exec.Command("git", "diff", treePath)
 | 
					 | 
				
			||||||
	cmd.Dir = localPath
 | 
					 | 
				
			||||||
	cmd.Stderr = os.Stderr
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	stdout, err := cmd.StdoutPipe()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("StdoutPipe: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = cmd.Start(); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Start: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pid := process.GetManager().Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd)
 | 
					 | 
				
			||||||
	defer process.GetManager().Remove(pid)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	diff, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("ParsePatch: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = cmd.Wait(); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Wait: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return diff, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ________         .__          __           ___________.__.__
 | 
					 | 
				
			||||||
// \______ \   ____ |  |   _____/  |_  ____   \_   _____/|__|  |   ____
 | 
					 | 
				
			||||||
//  |    |  \_/ __ \|  | _/ __ \   __\/ __ \   |    __)  |  |  | _/ __ \
 | 
					 | 
				
			||||||
//  |    `   \  ___/|  |_\  ___/|  | \  ___/   |     \   |  |  |_\  ___/
 | 
					 | 
				
			||||||
// /_______  /\___  >____/\___  >__|  \___  >  \___  /   |__|____/\___  >
 | 
					 | 
				
			||||||
//         \/     \/          \/          \/       \/                 \/
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteRepoFileOptions holds the repository delete file options
 | 
					 | 
				
			||||||
type DeleteRepoFileOptions struct {
 | 
					 | 
				
			||||||
	LastCommitID string
 | 
					 | 
				
			||||||
	OldBranch    string
 | 
					 | 
				
			||||||
	NewBranch    string
 | 
					 | 
				
			||||||
	TreePath     string
 | 
					 | 
				
			||||||
	Message      string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteRepoFile deletes a repository file
 | 
					 | 
				
			||||||
func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (err error) {
 | 
					 | 
				
			||||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.OldBranch != opts.NewBranch {
 | 
					 | 
				
			||||||
		if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	localPath := repo.LocalCopyPath()
 | 
					 | 
				
			||||||
	if err = os.Remove(path.Join(localPath, opts.TreePath)); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Remove: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git add --all: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
 | 
					 | 
				
			||||||
		Committer: doer.NewGitSig(),
 | 
					 | 
				
			||||||
		Message:   opts.Message,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.Push(localPath, git.PushOptions{
 | 
					 | 
				
			||||||
		Remote: "origin",
 | 
					 | 
				
			||||||
		Branch: opts.NewBranch,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "OpenRepository: %v", err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Simulate push event.
 | 
					 | 
				
			||||||
	oldCommitID := opts.LastCommitID
 | 
					 | 
				
			||||||
	if opts.NewBranch != opts.OldBranch {
 | 
					 | 
				
			||||||
		oldCommitID = git.EmptySHA
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.GetOwner(); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("GetOwner: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = PushUpdate(
 | 
					 | 
				
			||||||
		opts.NewBranch,
 | 
					 | 
				
			||||||
		PushUpdateOptions{
 | 
					 | 
				
			||||||
			PusherID:     doer.ID,
 | 
					 | 
				
			||||||
			PusherName:   doer.Name,
 | 
					 | 
				
			||||||
			RepoUserName: repo.Owner.Name,
 | 
					 | 
				
			||||||
			RepoName:     repo.Name,
 | 
					 | 
				
			||||||
			RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
					 | 
				
			||||||
			OldCommitID:  oldCommitID,
 | 
					 | 
				
			||||||
			NewCommitID:  commit.ID.String(),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("PushUpdate: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//  ____ ___        .__                    .___ ___________.___.__
 | 
					 | 
				
			||||||
// |    |   \______ |  |   _________     __| _/ \_   _____/|   |  |   ____   ______
 | 
					 | 
				
			||||||
// |    |   /\____ \|  |  /  _ \__  \   / __ |   |    __)  |   |  | _/ __ \ /  ___/
 | 
					 | 
				
			||||||
// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |   |     \   |   |  |_\  ___/ \___ \
 | 
					 | 
				
			||||||
// |______/  |   __/|____/\____(____  /\____ |   \___  /   |___|____/\___  >____  >
 | 
					 | 
				
			||||||
//           |__|                   \/      \/       \/                  \/     \/
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Upload represent a uploaded file to a repo to be deleted when moved
 | 
					 | 
				
			||||||
type Upload struct {
 | 
					 | 
				
			||||||
	ID   int64  `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
	UUID string `xorm:"uuid UNIQUE"`
 | 
					 | 
				
			||||||
	Name string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UploadLocalPath returns where uploads is stored in local file system based on given UUID.
 | 
					 | 
				
			||||||
func UploadLocalPath(uuid string) string {
 | 
					 | 
				
			||||||
	return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LocalPath returns where uploads are temporarily stored in local file system.
 | 
					 | 
				
			||||||
func (upload *Upload) LocalPath() string {
 | 
					 | 
				
			||||||
	return UploadLocalPath(upload.UUID)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewUpload creates a new upload object.
 | 
					 | 
				
			||||||
func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) {
 | 
					 | 
				
			||||||
	upload := &Upload{
 | 
					 | 
				
			||||||
		UUID: gouuid.NewV4().String(),
 | 
					 | 
				
			||||||
		Name: name,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	localPath := upload.LocalPath()
 | 
					 | 
				
			||||||
	if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("MkdirAll: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fw, err := os.Create(localPath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Create: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer fw.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err = fw.Write(buf); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Write: %v", err)
 | 
					 | 
				
			||||||
	} else if _, err = io.Copy(fw, file); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Copy: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _, err := x.Insert(upload); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return upload, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetUploadByUUID returns the Upload by UUID
 | 
					 | 
				
			||||||
func GetUploadByUUID(uuid string) (*Upload, error) {
 | 
					 | 
				
			||||||
	upload := &Upload{UUID: uuid}
 | 
					 | 
				
			||||||
	has, err := x.Get(upload)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	} else if !has {
 | 
					 | 
				
			||||||
		return nil, ErrUploadNotExist{0, uuid}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return upload, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetUploadsByUUIDs returns multiple uploads by UUIDS
 | 
					 | 
				
			||||||
func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) {
 | 
					 | 
				
			||||||
	if len(uuids) == 0 {
 | 
					 | 
				
			||||||
		return []*Upload{}, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Silently drop invalid uuids.
 | 
					 | 
				
			||||||
	uploads := make([]*Upload, 0, len(uuids))
 | 
					 | 
				
			||||||
	return uploads, x.In("uuid", uuids).Find(&uploads)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteUploads deletes multiple uploads
 | 
					 | 
				
			||||||
func DeleteUploads(uploads ...*Upload) (err error) {
 | 
					 | 
				
			||||||
	if len(uploads) == 0 {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sess := x.NewSession()
 | 
					 | 
				
			||||||
	defer sess.Close()
 | 
					 | 
				
			||||||
	if err = sess.Begin(); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ids := make([]int64, len(uploads))
 | 
					 | 
				
			||||||
	for i := 0; i < len(uploads); i++ {
 | 
					 | 
				
			||||||
		ids[i] = uploads[i].ID
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if _, err = sess.
 | 
					 | 
				
			||||||
		In("id", ids).
 | 
					 | 
				
			||||||
		Delete(new(Upload)); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("delete uploads: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, upload := range uploads {
 | 
					 | 
				
			||||||
		localPath := upload.LocalPath()
 | 
					 | 
				
			||||||
		if !com.IsFile(localPath) {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err := os.Remove(localPath); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("remove upload: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return sess.Commit()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteUpload delete a upload
 | 
					 | 
				
			||||||
func DeleteUpload(u *Upload) error {
 | 
					 | 
				
			||||||
	return DeleteUploads(u)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeleteUploadByUUID deletes a upload by UUID
 | 
					 | 
				
			||||||
func DeleteUploadByUUID(uuid string) error {
 | 
					 | 
				
			||||||
	upload, err := GetUploadByUUID(uuid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		if IsErrUploadNotExist(err) {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return fmt.Errorf("GetUploadByUUID: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := DeleteUpload(upload); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("DeleteUpload: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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.
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UploadRepoFiles uploads files to a repository
 | 
					 | 
				
			||||||
func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) (err error) {
 | 
					 | 
				
			||||||
	if len(opts.Files) == 0 {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	uploads, err := GetUploadsByUUIDs(opts.Files)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("DiscardLocalRepoBranchChanges [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("UpdateLocalCopyBranch [branch: %s]: %v", opts.OldBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if opts.OldBranch != opts.NewBranch {
 | 
					 | 
				
			||||||
		if err = repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("CheckoutNewBranch [old_branch: %s, new_branch: %s]: %v", opts.OldBranch, opts.NewBranch, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	localPath := repo.LocalCopyPath()
 | 
					 | 
				
			||||||
	dirPath := path.Join(localPath, opts.TreePath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Failed to create dir %s: %v", dirPath, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Copy uploaded files into repository.
 | 
					 | 
				
			||||||
	for _, upload := range uploads {
 | 
					 | 
				
			||||||
		tmpPath := upload.LocalPath()
 | 
					 | 
				
			||||||
		targetPath := path.Join(dirPath, upload.Name)
 | 
					 | 
				
			||||||
		if !com.IsFile(tmpPath) {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err = com.Copy(tmpPath, targetPath); err != nil {
 | 
					 | 
				
			||||||
			return fmt.Errorf("Copy: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = git.AddChanges(localPath, true); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git add --all: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
 | 
					 | 
				
			||||||
		Committer: doer.NewGitSig(),
 | 
					 | 
				
			||||||
		Message:   opts.Message,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("CommitChanges: %v", err)
 | 
					 | 
				
			||||||
	} else if err = git.Push(localPath, git.PushOptions{
 | 
					 | 
				
			||||||
		Remote: "origin",
 | 
					 | 
				
			||||||
		Branch: opts.NewBranch,
 | 
					 | 
				
			||||||
	}); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "OpenRepository: %v", err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error(4, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Simulate push event.
 | 
					 | 
				
			||||||
	oldCommitID := opts.LastCommitID
 | 
					 | 
				
			||||||
	if opts.NewBranch != opts.OldBranch {
 | 
					 | 
				
			||||||
		oldCommitID = git.EmptySHA
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = repo.GetOwner(); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("GetOwner: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = PushUpdate(
 | 
					 | 
				
			||||||
		opts.NewBranch,
 | 
					 | 
				
			||||||
		PushUpdateOptions{
 | 
					 | 
				
			||||||
			PusherID:     doer.ID,
 | 
					 | 
				
			||||||
			PusherName:   doer.Name,
 | 
					 | 
				
			||||||
			RepoUserName: repo.Owner.Name,
 | 
					 | 
				
			||||||
			RepoName:     repo.Name,
 | 
					 | 
				
			||||||
			RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
					 | 
				
			||||||
			OldCommitID:  oldCommitID,
 | 
					 | 
				
			||||||
			NewCommitID:  commit.ID.String(),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("PushUpdate: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return DeleteUploads(uploads...)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,155 @@
 | 
				
			||||||
 | 
					// Copyright 2016 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"mime/multipart"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
 | 
						gouuid "github.com/satori/go.uuid"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//  ____ ___        .__                    .___ ___________.___.__
 | 
				
			||||||
 | 
					// |    |   \______ |  |   _________     __| _/ \_   _____/|   |  |   ____   ______
 | 
				
			||||||
 | 
					// |    |   /\____ \|  |  /  _ \__  \   / __ |   |    __)  |   |  | _/ __ \ /  ___/
 | 
				
			||||||
 | 
					// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |   |     \   |   |  |_\  ___/ \___ \
 | 
				
			||||||
 | 
					// |______/  |   __/|____/\____(____  /\____ |   \___  /   |___|____/\___  >____  >
 | 
				
			||||||
 | 
					//           |__|                   \/      \/       \/                  \/     \/
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Upload represent a uploaded file to a repo to be deleted when moved
 | 
				
			||||||
 | 
					type Upload struct {
 | 
				
			||||||
 | 
						ID   int64  `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
						UUID string `xorm:"uuid UNIQUE"`
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UploadLocalPath returns where uploads is stored in local file system based on given UUID.
 | 
				
			||||||
 | 
					func UploadLocalPath(uuid string) string {
 | 
				
			||||||
 | 
						return path.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LocalPath returns where uploads are temporarily stored in local file system.
 | 
				
			||||||
 | 
					func (upload *Upload) LocalPath() string {
 | 
				
			||||||
 | 
						return UploadLocalPath(upload.UUID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewUpload creates a new upload object.
 | 
				
			||||||
 | 
					func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) {
 | 
				
			||||||
 | 
						upload := &Upload{
 | 
				
			||||||
 | 
							UUID: gouuid.NewV4().String(),
 | 
				
			||||||
 | 
							Name: name,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						localPath := upload.LocalPath()
 | 
				
			||||||
 | 
						if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("MkdirAll: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fw, err := os.Create(localPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Create: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer fw.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err = fw.Write(buf); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Write: %v", err)
 | 
				
			||||||
 | 
						} else if _, err = io.Copy(fw, file); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Copy: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := x.Insert(upload); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return upload, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUploadByUUID returns the Upload by UUID
 | 
				
			||||||
 | 
					func GetUploadByUUID(uuid string) (*Upload, error) {
 | 
				
			||||||
 | 
						upload := &Upload{UUID: uuid}
 | 
				
			||||||
 | 
						has, err := x.Get(upload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !has {
 | 
				
			||||||
 | 
							return nil, ErrUploadNotExist{0, uuid}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return upload, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUploadsByUUIDs returns multiple uploads by UUIDS
 | 
				
			||||||
 | 
					func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) {
 | 
				
			||||||
 | 
						if len(uuids) == 0 {
 | 
				
			||||||
 | 
							return []*Upload{}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Silently drop invalid uuids.
 | 
				
			||||||
 | 
						uploads := make([]*Upload, 0, len(uuids))
 | 
				
			||||||
 | 
						return uploads, x.In("uuid", uuids).Find(&uploads)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteUploads deletes multiple uploads
 | 
				
			||||||
 | 
					func DeleteUploads(uploads ...*Upload) (err error) {
 | 
				
			||||||
 | 
						if len(uploads) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sess := x.NewSession()
 | 
				
			||||||
 | 
						defer sess.Close()
 | 
				
			||||||
 | 
						if err = sess.Begin(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ids := make([]int64, len(uploads))
 | 
				
			||||||
 | 
						for i := 0; i < len(uploads); i++ {
 | 
				
			||||||
 | 
							ids[i] = uploads[i].ID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err = sess.
 | 
				
			||||||
 | 
							In("id", ids).
 | 
				
			||||||
 | 
							Delete(new(Upload)); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("delete uploads: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = sess.Commit(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, upload := range uploads {
 | 
				
			||||||
 | 
							localPath := upload.LocalPath()
 | 
				
			||||||
 | 
							if !com.IsFile(localPath) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := os.Remove(localPath); err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("remove upload: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteUploadByUUID deletes a upload by UUID
 | 
				
			||||||
 | 
					func DeleteUploadByUUID(uuid string) error {
 | 
				
			||||||
 | 
						upload, err := GetUploadByUUID(uuid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if IsErrUploadNotExist(err) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return fmt.Errorf("GetUploadByUUID: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := DeleteUploads(upload); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("DeleteUpload: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,100 @@
 | 
				
			||||||
 | 
					// 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 uploader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteRepoFileOptions holds the repository delete file options
 | 
				
			||||||
 | 
					type DeleteRepoFileOptions struct {
 | 
				
			||||||
 | 
						LastCommitID string
 | 
				
			||||||
 | 
						OldBranch    string
 | 
				
			||||||
 | 
						NewBranch    string
 | 
				
			||||||
 | 
						TreePath     string
 | 
				
			||||||
 | 
						Message      string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteRepoFile deletes a file in the given repository
 | 
				
			||||||
 | 
					func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepoFileOptions) error {
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(repo)
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.Clone(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.SetDefaultIndex(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						filesInIndex, err := t.LsFiles(opts.TreePath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("UpdateRepoFile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inFilelist := false
 | 
				
			||||||
 | 
						for _, file := range filesInIndex {
 | 
				
			||||||
 | 
							if file == opts.TreePath {
 | 
				
			||||||
 | 
								inFilelist = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !inFilelist {
 | 
				
			||||||
 | 
							return git.ErrNotExist{RelPath: opts.TreePath}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := t.RemoveFilesFromIndex(opts.TreePath); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now write the tree
 | 
				
			||||||
 | 
						treeHash, err := t.WriteTree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now commit the tree
 | 
				
			||||||
 | 
						commitHash, err := t.CommitTree(doer, treeHash, opts.Message)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Then push this tree to NewBranch
 | 
				
			||||||
 | 
						if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Simulate push event.
 | 
				
			||||||
 | 
						oldCommitID := opts.LastCommitID
 | 
				
			||||||
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
 | 
							oldCommitID = git.EmptySHA
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = repo.GetOwner(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("GetOwner: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = models.PushUpdate(
 | 
				
			||||||
 | 
							opts.NewBranch,
 | 
				
			||||||
 | 
							models.PushUpdateOptions{
 | 
				
			||||||
 | 
								PusherID:     doer.ID,
 | 
				
			||||||
 | 
								PusherName:   doer.Name,
 | 
				
			||||||
 | 
								RepoUserName: repo.Owner.Name,
 | 
				
			||||||
 | 
								RepoName:     repo.Name,
 | 
				
			||||||
 | 
								RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
				
			||||||
 | 
								OldCommitID:  oldCommitID,
 | 
				
			||||||
 | 
								NewCommitID:  commitHash,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("PushUpdate: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: Should we UpdateRepoIndexer(repo) here?
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					// 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 uploader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDiffPreview produces and returns diff result of a file which is not yet committed.
 | 
				
			||||||
 | 
					func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*models.Diff, error) {
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(repo)
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,359 @@
 | 
				
			||||||
 | 
					// 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 uploader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/process"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TemporaryUploadRepository is a type to wrap our upload repositories
 | 
				
			||||||
 | 
					type TemporaryUploadRepository struct {
 | 
				
			||||||
 | 
						repo     *models.Repository
 | 
				
			||||||
 | 
						basePath string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewTemporaryUploadRepository creates a new temporary upload repository
 | 
				
			||||||
 | 
					func NewTemporaryUploadRepository(repo *models.Repository) (*TemporaryUploadRepository, error) {
 | 
				
			||||||
 | 
						timeStr := com.ToStr(time.Now().Nanosecond()) // SHOULD USE SOMETHING UNIQUE
 | 
				
			||||||
 | 
						basePath := path.Join(models.LocalCopyPath(), "upload-"+timeStr+".git")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(path.Dir(basePath), os.ModePerm); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Failed to create dir %s: %v", basePath, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t := &TemporaryUploadRepository{repo: repo, basePath: basePath}
 | 
				
			||||||
 | 
						return t, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close the repository cleaning up all files
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) Close() {
 | 
				
			||||||
 | 
						if _, err := os.Stat(t.basePath); !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							os.RemoveAll(t.basePath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Clone the base repository to our path and set branch as the HEAD
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) Clone(branch string) error {
 | 
				
			||||||
 | 
						if _, stderr, err := process.GetManager().ExecTimeout(5*time.Minute,
 | 
				
			||||||
 | 
							fmt.Sprintf("Clone (git clone -s --bare): %s", t.basePath),
 | 
				
			||||||
 | 
							"git", "clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Clone: %v %s", err, stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetDefaultIndex sets the git index to our HEAD
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) SetDefaultIndex() error {
 | 
				
			||||||
 | 
						if _, stderr, err := process.GetManager().ExecDir(5*time.Minute,
 | 
				
			||||||
 | 
							t.basePath,
 | 
				
			||||||
 | 
							fmt.Sprintf("SetDefaultIndex (git read-tree HEAD): %s", t.basePath),
 | 
				
			||||||
 | 
							"git", "read-tree", "HEAD"); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("SetDefaultIndex: %v %s", err, stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timeout := 5 * time.Minute
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmdArgs := []string{"ls-files", "-z", "--"}
 | 
				
			||||||
 | 
						for _, arg := range filenames {
 | 
				
			||||||
 | 
							if arg != "" {
 | 
				
			||||||
 | 
								cmdArgs = append(cmdArgs, arg)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.CommandContext(ctx, "git", cmdArgs...)
 | 
				
			||||||
 | 
						desc := fmt.Sprintf("lsFiles: (git ls-files) %v", cmdArgs)
 | 
				
			||||||
 | 
						cmd.Dir = t.basePath
 | 
				
			||||||
 | 
						cmd.Stdout = stdOut
 | 
				
			||||||
 | 
						cmd.Stderr = stdErr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := cmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("exec(%s) failed: %v(%v)", desc, err, ctx.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pid := process.GetManager().Add(desc, cmd)
 | 
				
			||||||
 | 
						err := cmd.Wait()
 | 
				
			||||||
 | 
						process.GetManager().Remove(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v", pid, desc, err, ctx.Err(), stdOut, stdErr)
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						filelist := make([]string, len(filenames))
 | 
				
			||||||
 | 
						for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
 | 
				
			||||||
 | 
							filelist = append(filelist, string(line))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return filelist, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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')
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timeout := 5 * time.Minute
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmdArgs := []string{"update-index", "--remove", "-z", "--index-info"}
 | 
				
			||||||
 | 
						cmd := exec.CommandContext(ctx, "git", cmdArgs...)
 | 
				
			||||||
 | 
						desc := fmt.Sprintf("removeFilesFromIndex: (git update-index) %v", filenames)
 | 
				
			||||||
 | 
						cmd.Dir = t.basePath
 | 
				
			||||||
 | 
						cmd.Stdout = stdOut
 | 
				
			||||||
 | 
						cmd.Stderr = stdErr
 | 
				
			||||||
 | 
						cmd.Stdin = bytes.NewReader(stdIn.Bytes())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := cmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("exec(%s) failed: %v(%v)", desc, err, ctx.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pid := process.GetManager().Add(desc, cmd)
 | 
				
			||||||
 | 
						err := cmd.Wait()
 | 
				
			||||||
 | 
						process.GetManager().Remove(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v", pid, desc, err, ctx.Err(), stdOut, stdErr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HashObject writes the provided content to the object db and returns its hash
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error) {
 | 
				
			||||||
 | 
						timeout := 5 * time.Minute
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hashCmd := exec.CommandContext(ctx, "git", "hash-object", "-w", "--stdin")
 | 
				
			||||||
 | 
						hashCmd.Dir = t.basePath
 | 
				
			||||||
 | 
						hashCmd.Stdin = content
 | 
				
			||||||
 | 
						stdOutBuffer := new(bytes.Buffer)
 | 
				
			||||||
 | 
						stdErrBuffer := new(bytes.Buffer)
 | 
				
			||||||
 | 
						hashCmd.Stdout = stdOutBuffer
 | 
				
			||||||
 | 
						hashCmd.Stderr = stdErrBuffer
 | 
				
			||||||
 | 
						desc := fmt.Sprintf("hashObject: (git hash-object)")
 | 
				
			||||||
 | 
						if err := hashCmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("git hash-object: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pid := process.GetManager().Add(desc, hashCmd)
 | 
				
			||||||
 | 
						err := hashCmd.Wait()
 | 
				
			||||||
 | 
						process.GetManager().Remove(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v", pid, desc, err, ctx.Err(), stdOutBuffer, stdErrBuffer)
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.TrimSpace(stdOutBuffer.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 _, stderr, err := process.GetManager().ExecDir(5*time.Minute,
 | 
				
			||||||
 | 
							t.basePath,
 | 
				
			||||||
 | 
							fmt.Sprintf("addObjectToIndex (git update-index): %s", t.basePath),
 | 
				
			||||||
 | 
							"git", "update-index", "--add", "--replace", "--cacheinfo", mode, objectHash, objectPath); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("git update-index: %s", stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WriteTree writes the current index as a tree to the object db and returns its hash
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) WriteTree() (string, error) {
 | 
				
			||||||
 | 
						treeHash, stderr, err := process.GetManager().ExecDir(5*time.Minute,
 | 
				
			||||||
 | 
							t.basePath,
 | 
				
			||||||
 | 
							fmt.Sprintf("WriteTree (git write-tree): %s", t.basePath),
 | 
				
			||||||
 | 
							"git", "write-tree")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("git write-tree: %s", stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strings.TrimSpace(treeHash), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CommitTree creates a commit from a given tree for the user with provided message
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) CommitTree(doer *models.User, treeHash string, message string) (string, error) {
 | 
				
			||||||
 | 
						commitTimeStr := time.Now().Format(time.UnixDate)
 | 
				
			||||||
 | 
						sig := doer.NewGitSig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: Should we add SSH_ORIGINAL_COMMAND to this
 | 
				
			||||||
 | 
						// Because this may call hooks we should pass in the environment
 | 
				
			||||||
 | 
						env := append(os.Environ(),
 | 
				
			||||||
 | 
							"GIT_AUTHOR_NAME="+sig.Name,
 | 
				
			||||||
 | 
							"GIT_AUTHOR_EMAIL="+sig.Email,
 | 
				
			||||||
 | 
							"GIT_AUTHOR_DATE="+commitTimeStr,
 | 
				
			||||||
 | 
							"GIT_COMMITTER_NAME="+sig.Name,
 | 
				
			||||||
 | 
							"GIT_COMMITTER_EMAIL="+sig.Email,
 | 
				
			||||||
 | 
							"GIT_COMMITTER_DATE="+commitTimeStr,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						commitHash, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute,
 | 
				
			||||||
 | 
							t.basePath,
 | 
				
			||||||
 | 
							fmt.Sprintf("commitTree (git commit-tree): %s", t.basePath),
 | 
				
			||||||
 | 
							env,
 | 
				
			||||||
 | 
							"git", "commit-tree", treeHash, "-p", "HEAD", "-m", message)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("git commit-tree: %s", stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return strings.TrimSpace(commitHash), 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 {
 | 
				
			||||||
 | 
						isWiki := "false"
 | 
				
			||||||
 | 
						if strings.HasSuffix(t.repo.Name, ".wiki") {
 | 
				
			||||||
 | 
							isWiki = "true"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sig := doer.NewGitSig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: Should we add SSH_ORIGINAL_COMMAND to this
 | 
				
			||||||
 | 
						// Because calls hooks we need to pass in the environment
 | 
				
			||||||
 | 
						env := append(os.Environ(),
 | 
				
			||||||
 | 
							"GIT_AUTHOR_NAME="+sig.Name,
 | 
				
			||||||
 | 
							"GIT_AUTHOR_EMAIL="+sig.Email,
 | 
				
			||||||
 | 
							"GIT_COMMITTER_NAME="+sig.Name,
 | 
				
			||||||
 | 
							"GIT_COMMITTER_EMAIL="+sig.Email,
 | 
				
			||||||
 | 
							models.EnvRepoName+"="+t.repo.Name,
 | 
				
			||||||
 | 
							models.EnvRepoUsername+"="+t.repo.OwnerName,
 | 
				
			||||||
 | 
							models.EnvRepoIsWiki+"="+isWiki,
 | 
				
			||||||
 | 
							models.EnvPusherName+"="+doer.Name,
 | 
				
			||||||
 | 
							models.EnvPusherID+"="+fmt.Sprintf("%d", doer.ID),
 | 
				
			||||||
 | 
							models.ProtectedBranchRepoID+"="+fmt.Sprintf("%d", t.repo.ID),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, stderr, err := process.GetManager().ExecDirEnv(5*time.Minute,
 | 
				
			||||||
 | 
							t.basePath,
 | 
				
			||||||
 | 
							fmt.Sprintf("actuallyPush (git push): %s", t.basePath),
 | 
				
			||||||
 | 
							env,
 | 
				
			||||||
 | 
							"git", "push", t.repo.RepoPath(), strings.TrimSpace(commitHash)+":refs/heads/"+strings.TrimSpace(branch)); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("git push: %s", stderr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DiffIndex returns a Diff of the current index to the head
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) DiffIndex() (diff *models.Diff, err error) {
 | 
				
			||||||
 | 
						timeout := 5 * time.Minute
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdErr := new(bytes.Buffer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.CommandContext(ctx, "git", "diff-index", "--cached", "-p", "HEAD")
 | 
				
			||||||
 | 
						cmd.Dir = t.basePath
 | 
				
			||||||
 | 
						cmd.Stderr = stdErr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdout, err := cmd.StdoutPipe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("StdoutPipe: %v stderr %s", err, stdErr.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = cmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Start: %v stderr %s", err, stdErr.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pid := process.GetManager().Add(fmt.Sprintf("diffIndex [repo_path: %s]", t.repo.RepoPath()), cmd)
 | 
				
			||||||
 | 
						defer process.GetManager().Remove(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						diff, err = models.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("ParsePatch: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = cmd.Wait(); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Wait: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return diff, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckAttribute checks the given attribute of the provided files
 | 
				
			||||||
 | 
					func (t *TemporaryUploadRepository) CheckAttribute(attribute string, args ...string) (map[string]map[string]string, error) {
 | 
				
			||||||
 | 
						stdOut := new(bytes.Buffer)
 | 
				
			||||||
 | 
						stdErr := new(bytes.Buffer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						timeout := 5 * time.Minute
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), timeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmdArgs := []string{"check-attr", "-z", attribute, "--cached", "--"}
 | 
				
			||||||
 | 
						for _, arg := range args {
 | 
				
			||||||
 | 
							if arg != "" {
 | 
				
			||||||
 | 
								cmdArgs = append(cmdArgs, arg)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := exec.CommandContext(ctx, "git", cmdArgs...)
 | 
				
			||||||
 | 
						desc := fmt.Sprintf("checkAttr: (git check-attr) %s %v", attribute, cmdArgs)
 | 
				
			||||||
 | 
						cmd.Dir = t.basePath
 | 
				
			||||||
 | 
						cmd.Stdout = stdOut
 | 
				
			||||||
 | 
						cmd.Stderr = stdErr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := cmd.Start(); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("exec(%s) failed: %v(%v)", desc, err, ctx.Err())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pid := process.GetManager().Add(desc, cmd)
 | 
				
			||||||
 | 
						err := cmd.Wait()
 | 
				
			||||||
 | 
						process.GetManager().Remove(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v", pid, desc, err, ctx.Err(), stdOut, stdErr)
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(fields)%3 != 1 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Wrong number of fields in return from check-attr")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var name2attribute2info = make(map[string]map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := 0; i < (len(fields) / 3); i++ {
 | 
				
			||||||
 | 
							filename := string(fields[3*i])
 | 
				
			||||||
 | 
							attribute := string(fields[3*i+1])
 | 
				
			||||||
 | 
							info := string(fields[3*i+2])
 | 
				
			||||||
 | 
							attribute2info := name2attribute2info[filename]
 | 
				
			||||||
 | 
							if attribute2info == nil {
 | 
				
			||||||
 | 
								attribute2info = make(map[string]string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							attribute2info[attribute] = info
 | 
				
			||||||
 | 
							name2attribute2info[filename] = attribute2info
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return name2attribute2info, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,159 @@
 | 
				
			||||||
 | 
					// 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 uploader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/lfs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateRepoFileOptions holds the repository file update options
 | 
				
			||||||
 | 
					type UpdateRepoFileOptions struct {
 | 
				
			||||||
 | 
						LastCommitID string
 | 
				
			||||||
 | 
						OldBranch    string
 | 
				
			||||||
 | 
						NewBranch    string
 | 
				
			||||||
 | 
						OldTreeName  string
 | 
				
			||||||
 | 
						NewTreeName  string
 | 
				
			||||||
 | 
						Message      string
 | 
				
			||||||
 | 
						Content      string
 | 
				
			||||||
 | 
						IsNewFile    bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateRepoFile adds or updates a file in the given repository
 | 
				
			||||||
 | 
					func UpdateRepoFile(repo *models.Repository, doer *models.User, opts *UpdateRepoFileOptions) error {
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(repo)
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.Clone(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.SetDefaultIndex(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						filesInIndex, err := t.LsFiles(opts.NewTreeName, opts.OldTreeName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("UpdateRepoFile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts.IsNewFile {
 | 
				
			||||||
 | 
							for _, file := range filesInIndex {
 | 
				
			||||||
 | 
								if file == opts.NewTreeName {
 | 
				
			||||||
 | 
									return models.ErrRepoFileAlreadyExist{FileName: opts.NewTreeName}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//var stdout string
 | 
				
			||||||
 | 
						if opts.OldTreeName != opts.NewTreeName && len(filesInIndex) > 0 {
 | 
				
			||||||
 | 
							for _, file := range filesInIndex {
 | 
				
			||||||
 | 
								if file == opts.OldTreeName {
 | 
				
			||||||
 | 
									if err := t.RemoveFilesFromIndex(opts.OldTreeName); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check there is no way this can return multiple infos
 | 
				
			||||||
 | 
						filename2attribute2info, err := t.CheckAttribute("filter", opts.NewTreeName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content := opts.Content
 | 
				
			||||||
 | 
						var lfsMetaObject *models.LFSMetaObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if filename2attribute2info[opts.NewTreeName] != nil && filename2attribute2info[opts.NewTreeName]["filter"] == "lfs" {
 | 
				
			||||||
 | 
							// OK so we are supposed to LFS this data!
 | 
				
			||||||
 | 
							oid, err := models.GenerateLFSOid(strings.NewReader(opts.Content))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(opts.Content)), RepositoryID: repo.ID}
 | 
				
			||||||
 | 
							content = lfsMetaObject.Pointer()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add the object to the database
 | 
				
			||||||
 | 
						objectHash, err := t.HashObject(strings.NewReader(content))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add the object to the index
 | 
				
			||||||
 | 
						if err := t.AddObjectToIndex("100644", objectHash, opts.NewTreeName); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now write the tree
 | 
				
			||||||
 | 
						treeHash, err := t.WriteTree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now commit the tree
 | 
				
			||||||
 | 
						commitHash, err := t.CommitTree(doer, treeHash, opts.Message)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lfsMetaObject != nil {
 | 
				
			||||||
 | 
							// We have an LFS object - create it
 | 
				
			||||||
 | 
							lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath}
 | 
				
			||||||
 | 
							if !contentStore.Exists(lfsMetaObject) {
 | 
				
			||||||
 | 
								if err := contentStore.Put(lfsMetaObject, strings.NewReader(opts.Content)); err != nil {
 | 
				
			||||||
 | 
									if err2 := repo.RemoveLFSMetaObjectByOid(lfsMetaObject.Oid); err2 != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Then push this tree to NewBranch
 | 
				
			||||||
 | 
						if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Simulate push event.
 | 
				
			||||||
 | 
						oldCommitID := opts.LastCommitID
 | 
				
			||||||
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
 | 
							oldCommitID = git.EmptySHA
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = repo.GetOwner(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("GetOwner: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = models.PushUpdate(
 | 
				
			||||||
 | 
							opts.NewBranch,
 | 
				
			||||||
 | 
							models.PushUpdateOptions{
 | 
				
			||||||
 | 
								PusherID:     doer.ID,
 | 
				
			||||||
 | 
								PusherName:   doer.Name,
 | 
				
			||||||
 | 
								RepoUserName: repo.Owner.Name,
 | 
				
			||||||
 | 
								RepoName:     repo.Name,
 | 
				
			||||||
 | 
								RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
				
			||||||
 | 
								OldCommitID:  oldCommitID,
 | 
				
			||||||
 | 
								NewCommitID:  commitHash,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("PushUpdate: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						models.UpdateRepoIndexer(repo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,206 @@
 | 
				
			||||||
 | 
					// 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 uploader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/lfs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t, err := NewTemporaryUploadRepository(repo)
 | 
				
			||||||
 | 
						defer t.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.Clone(opts.OldBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := t.SetDefaultIndex(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						names := make([]string, len(uploads))
 | 
				
			||||||
 | 
						infos := make([]uploadInfo, len(uploads))
 | 
				
			||||||
 | 
						for i, upload := range uploads {
 | 
				
			||||||
 | 
							names[i] = upload.Name
 | 
				
			||||||
 | 
							infos[i] = uploadInfo{upload: upload}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						filename2attribute2info, err := t.CheckAttribute("filter", names...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Copy uploaded files into repository.
 | 
				
			||||||
 | 
						for i, uploadInfo := range infos {
 | 
				
			||||||
 | 
							file, err := os.Open(uploadInfo.upload.LocalPath())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var objectHash string
 | 
				
			||||||
 | 
							if filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" {
 | 
				
			||||||
 | 
								// Handle LFS
 | 
				
			||||||
 | 
								// FIXME: Inefficient! this should probably happen in models.Upload
 | 
				
			||||||
 | 
								oid, err := models.GenerateLFSOid(file)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								fileInfo, err := file.Stat()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								uploadInfo.lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: fileInfo.Size(), RepositoryID: t.repo.ID}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if objectHash, err = t.HashObject(strings.NewReader(uploadInfo.lfsMetaObject.Pointer())); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								infos[i] = uploadInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if objectHash, err = t.HashObject(file); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add the object to the index
 | 
				
			||||||
 | 
							if err := t.AddObjectToIndex("100644", objectHash, path.Join(opts.TreePath, uploadInfo.upload.Name)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now write the tree
 | 
				
			||||||
 | 
						treeHash, err := t.WriteTree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now commit the tree
 | 
				
			||||||
 | 
						commitHash, err := t.CommitTree(doer, treeHash, opts.Message)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now deal with LFS objects
 | 
				
			||||||
 | 
						for _, uploadInfo := range infos {
 | 
				
			||||||
 | 
							if uploadInfo.lfsMetaObject == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							uploadInfo.lfsMetaObject, err = models.NewLFSMetaObject(uploadInfo.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.ContentStore{BasePath: setting.LFS.ContentPath}
 | 
				
			||||||
 | 
						for _, uploadInfo := range infos {
 | 
				
			||||||
 | 
							if uploadInfo.lfsMetaObject == nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !contentStore.Exists(uploadInfo.lfsMetaObject) {
 | 
				
			||||||
 | 
								file, err := os.Open(uploadInfo.upload.LocalPath())
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return cleanUpAfterFailure(&infos, t, 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(uploadInfo.lfsMetaObject, file); err != nil {
 | 
				
			||||||
 | 
									// OK Now we need to cleanup
 | 
				
			||||||
 | 
									// Can't clean up the store, once uploaded there they're there.
 | 
				
			||||||
 | 
									return cleanUpAfterFailure(&infos, t, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Then push this tree to NewBranch
 | 
				
			||||||
 | 
						if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Simulate push event.
 | 
				
			||||||
 | 
						oldCommitID := opts.LastCommitID
 | 
				
			||||||
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
 | 
							oldCommitID = git.EmptySHA
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = repo.GetOwner(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("GetOwner: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = models.PushUpdate(
 | 
				
			||||||
 | 
							opts.NewBranch,
 | 
				
			||||||
 | 
							models.PushUpdateOptions{
 | 
				
			||||||
 | 
								PusherID:     doer.ID,
 | 
				
			||||||
 | 
								PusherName:   doer.Name,
 | 
				
			||||||
 | 
								RepoUserName: repo.Owner.Name,
 | 
				
			||||||
 | 
								RepoName:     repo.Name,
 | 
				
			||||||
 | 
								RefFullName:  git.BranchPrefix + opts.NewBranch,
 | 
				
			||||||
 | 
								OldCommitID:  oldCommitID,
 | 
				
			||||||
 | 
								NewCommitID:  commitHash,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("PushUpdate: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// FIXME: Should we models.UpdateRepoIndexer(repo) here?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return models.DeleteUploads(uploads...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ import (
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/templates"
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/uploader"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -62,6 +63,16 @@ func editFile(ctx *context.Context, isNewFile bool) {
 | 
				
			||||||
	ctx.Data["RequireSimpleMDE"] = true
 | 
						ctx.Data["RequireSimpleMDE"] = true
 | 
				
			||||||
	canCommit := renderCommitRights(ctx)
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
 | 
							if isNewFile {
 | 
				
			||||||
 | 
								ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", ctx.Repo.BranchName, treePath))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", ctx.Repo.BranchName, treePath))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
 | 
						treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !isNewFile {
 | 
						if !isNewFile {
 | 
				
			||||||
| 
						 | 
					@ -155,7 +166,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oldBranchName := ctx.Repo.BranchName
 | 
						oldBranchName := ctx.Repo.BranchName
 | 
				
			||||||
	branchName := oldBranchName
 | 
						branchName := oldBranchName
 | 
				
			||||||
	oldTreePath := ctx.Repo.TreePath
 | 
						oldTreePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
	lastCommit := form.LastCommit
 | 
						lastCommit := form.LastCommit
 | 
				
			||||||
	form.LastCommit = ctx.Repo.Commit.ID.String()
 | 
						form.LastCommit = ctx.Repo.Commit.ID.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,7 +309,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
 | 
				
			||||||
		message += "\n\n" + form.CommitMessage
 | 
							message += "\n\n" + form.CommitMessage
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := ctx.Repo.Repository.UpdateRepoFile(ctx.User, models.UpdateRepoFileOptions{
 | 
						if err := uploader.UpdateRepoFile(ctx.Repo.Repository, ctx.User, &uploader.UpdateRepoFileOptions{
 | 
				
			||||||
		LastCommitID: lastCommit,
 | 
							LastCommitID: lastCommit,
 | 
				
			||||||
		OldBranch:    oldBranchName,
 | 
							OldBranch:    oldBranchName,
 | 
				
			||||||
		NewBranch:    branchName,
 | 
							NewBranch:    branchName,
 | 
				
			||||||
| 
						 | 
					@ -328,7 +339,11 @@ func NewFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DiffPreviewPost render preview diff page
 | 
					// DiffPreviewPost render preview diff page
 | 
				
			||||||
func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
 | 
					func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
 | 
				
			||||||
	treePath := ctx.Repo.TreePath
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
						if len(treePath) == 0 {
 | 
				
			||||||
 | 
							ctx.Error(500, "file name to diff is invalid")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
 | 
						entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -339,7 +354,7 @@ func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	diff, err := ctx.Repo.Repository.GetDiffPreview(ctx.Repo.BranchName, treePath, form.Content)
 | 
						diff, err := uploader.GetDiffPreview(ctx.Repo.Repository, ctx.Repo.BranchName, treePath, form.Content)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.Error(500, "GetDiffPreview: "+err.Error())
 | 
							ctx.Error(500, "GetDiffPreview: "+err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					@ -358,7 +373,14 @@ func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
 | 
				
			||||||
func DeleteFile(ctx *context.Context) {
 | 
					func DeleteFile(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["PageIsDelete"] = true
 | 
						ctx.Data["PageIsDelete"] = true
 | 
				
			||||||
	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
						ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
				
			||||||
	ctx.Data["TreePath"] = ctx.Repo.TreePath
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
 | 
							ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", ctx.Repo.BranchName, treePath))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["TreePath"] = treePath
 | 
				
			||||||
	canCommit := renderCommitRights(ctx)
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["commit_summary"] = ""
 | 
						ctx.Data["commit_summary"] = ""
 | 
				
			||||||
| 
						 | 
					@ -426,7 +448,7 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
 | 
				
			||||||
		message += "\n\n" + form.CommitMessage
 | 
							message += "\n\n" + form.CommitMessage
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := ctx.Repo.Repository.DeleteRepoFile(ctx.User, models.DeleteRepoFileOptions{
 | 
						if err := uploader.DeleteRepoFile(ctx.Repo.Repository, ctx.User, &uploader.DeleteRepoFileOptions{
 | 
				
			||||||
		LastCommitID: ctx.Repo.CommitID,
 | 
							LastCommitID: ctx.Repo.CommitID,
 | 
				
			||||||
		OldBranch:    oldBranchName,
 | 
							OldBranch:    oldBranchName,
 | 
				
			||||||
		NewBranch:    branchName,
 | 
							NewBranch:    branchName,
 | 
				
			||||||
| 
						 | 
					@ -453,6 +475,12 @@ func UploadFile(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["PageIsUpload"] = true
 | 
						ctx.Data["PageIsUpload"] = true
 | 
				
			||||||
	renderUploadSettings(ctx)
 | 
						renderUploadSettings(ctx)
 | 
				
			||||||
	canCommit := renderCommitRights(ctx)
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
 | 
							ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", ctx.Repo.BranchName, treePath))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Repo.TreePath = treePath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
 | 
						treeNames, treePaths := getParentTreeFields(ctx.Repo.TreePath)
 | 
				
			||||||
	if len(treeNames) == 0 {
 | 
						if len(treeNames) == 0 {
 | 
				
			||||||
| 
						 | 
					@ -489,10 +517,6 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	form.TreePath = cleanUploadFileName(form.TreePath)
 | 
						form.TreePath = cleanUploadFileName(form.TreePath)
 | 
				
			||||||
	if len(form.TreePath) == 0 {
 | 
					 | 
				
			||||||
		ctx.Error(500, "Upload file name is invalid")
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	treeNames, treePaths := getParentTreeFields(form.TreePath)
 | 
						treeNames, treePaths := getParentTreeFields(form.TreePath)
 | 
				
			||||||
	if len(treeNames) == 0 {
 | 
						if len(treeNames) == 0 {
 | 
				
			||||||
| 
						 | 
					@ -559,7 +583,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 | 
				
			||||||
		message += "\n\n" + form.CommitMessage
 | 
							message += "\n\n" + form.CommitMessage
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := ctx.Repo.Repository.UploadRepoFiles(ctx.User, models.UploadRepoFileOptions{
 | 
						if err := uploader.UploadRepoFiles(ctx.Repo.Repository, ctx.User, &uploader.UploadRepoFileOptions{
 | 
				
			||||||
		LastCommitID: ctx.Repo.CommitID,
 | 
							LastCommitID: ctx.Repo.CommitID,
 | 
				
			||||||
		OldBranch:    oldBranchName,
 | 
							OldBranch:    oldBranchName,
 | 
				
			||||||
		NewBranch:    branchName,
 | 
							NewBranch:    branchName,
 | 
				
			||||||
| 
						 | 
					@ -576,12 +600,13 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func cleanUploadFileName(name string) string {
 | 
					func cleanUploadFileName(name string) string {
 | 
				
			||||||
	name = strings.TrimLeft(name, "./\\")
 | 
						// Rebase the filename
 | 
				
			||||||
	name = strings.Replace(name, "../", "", -1)
 | 
						name = strings.Trim(path.Clean("/"+name), " /")
 | 
				
			||||||
	name = strings.Replace(name, "..\\", "", -1)
 | 
						// Git disallows any filenames to have a .git directory in them.
 | 
				
			||||||
	name = strings.TrimPrefix(path.Clean(name), ".git/")
 | 
						for _, part := range strings.Split(name, "/") {
 | 
				
			||||||
	if name == ".git" {
 | 
							if strings.ToLower(part) == ".git" {
 | 
				
			||||||
		return ""
 | 
								return ""
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return name
 | 
						return name
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,16 +15,24 @@ func TestCleanUploadName(t *testing.T) {
 | 
				
			||||||
	models.PrepareTestEnv(t)
 | 
						models.PrepareTestEnv(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var kases = map[string]string{
 | 
						var kases = map[string]string{
 | 
				
			||||||
		".git/refs/master": "git/refs/master",
 | 
							".git/refs/master":               "",
 | 
				
			||||||
		"/root/abc":        "root/abc",
 | 
							"/root/abc":                      "root/abc",
 | 
				
			||||||
		"./../../abc":      "abc",
 | 
							"./../../abc":                    "abc",
 | 
				
			||||||
		"a/../.git":        "a/.git",
 | 
							"a/../.git":                      "",
 | 
				
			||||||
		"a/../../../abc":   "a/abc",
 | 
							"a/../../../abc":                 "abc",
 | 
				
			||||||
		"../../../acd":     "acd",
 | 
							"../../../acd":                   "acd",
 | 
				
			||||||
		"../../.git/abc":   "git/abc",
 | 
							"../../.git/abc":                 "",
 | 
				
			||||||
		"..\\..\\.git/abc": "git/abc",
 | 
							"..\\..\\.git/abc":               "..\\..\\.git/abc",
 | 
				
			||||||
 | 
							"..\\../.git/abc":                "",
 | 
				
			||||||
 | 
							"..\\../.git":                    "",
 | 
				
			||||||
 | 
							"abc/../def":                     "def",
 | 
				
			||||||
 | 
							".drone.yml":                     ".drone.yml",
 | 
				
			||||||
 | 
							".abc/def/.drone.yml":            ".abc/def/.drone.yml",
 | 
				
			||||||
 | 
							"..drone.yml.":                   "..drone.yml.",
 | 
				
			||||||
 | 
							"..a.dotty...name...":            "..a.dotty...name...",
 | 
				
			||||||
 | 
							"..a.dotty../.folder../.name...": "..a.dotty../.folder../.name...",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for k, v := range kases {
 | 
						for k, v := range kases {
 | 
				
			||||||
		assert.EqualValues(t, v, cleanUploadFileName(k))
 | 
							assert.EqualValues(t, cleanUploadFileName(k), v)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue