gitea/modules/repofiles/delete.go
zeripath 4979f15c3f
Add configurable Trust Models (#11712)
* Add configurable Trust Models

Gitea's default signature verification model differs from GitHub. GitHub
uses signatures to verify that the committer is who they say they are -
meaning that when GitHub makes a signed commit it must be the committer.
The GitHub model prevents re-publishing of commits after revocation of a
key and prevents re-signing of other people's commits to create a
completely trusted repository signed by one key or a set of trusted
keys.

The default behaviour of Gitea in contrast is to always display the
avatar and information related to a signature. This allows signatures to
be decoupled from the committer. That being said, allowing arbitary
users to present other peoples commits as theirs is not necessarily
desired therefore we have a trust model whereby signatures from
collaborators are marked trusted, signatures matching the commit line
are marked untrusted and signatures that match a user in the db but not
the committer line are marked unmatched.

The problem with this model is that this conflicts with Github therefore
we need to provide an option to allow users to choose the Github model
should they wish to.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Adjust locale strings

Signed-off-by: Andrew Thornton <art27@cantab.net>

* as per @6543

Co-authored-by: 6543 <6543@obermui.de>

* Update models/gpg_key.go

* Add migration for repository

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-09-20 00:44:55 +08:00

226 lines
6.3 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repofiles
import (
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
)
// DeleteRepoFileOptions holds the repository delete file options
type DeleteRepoFileOptions struct {
LastCommitID string
OldBranch string
NewBranch string
TreePath string
Message string
SHA string
Author *IdentityOptions
Committer *IdentityOptions
Dates *CommitDateOptions
}
// DeleteRepoFile deletes a file in the given repository
func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepoFileOptions) (*api.FileResponse, error) {
// If no branch name is set, assume the repo's default branch
if opts.OldBranch == "" {
opts.OldBranch = repo.DefaultBranch
}
if opts.NewBranch == "" {
opts.NewBranch = opts.OldBranch
}
// oldBranch must exist for this operation
if _, err := repo_module.GetBranch(repo, opts.OldBranch); err != nil {
return nil, err
}
// A NewBranch can be specified for the file to be created/updated in a new branch.
// Check to make sure the branch does not already exist, otherwise we can't proceed.
// If we aren't branching to a new branch, make sure user can commit to the given branch
if opts.NewBranch != opts.OldBranch {
newBranch, err := repo_module.GetBranch(repo, opts.NewBranch)
if err != nil && !git.IsErrBranchNotExist(err) {
return nil, err
}
if newBranch != nil {
return nil, models.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}
} else {
protectedBranch, err := repo.GetBranchProtection(opts.OldBranch)
if err != nil {
return nil, err
}
if protectedBranch != nil {
if !protectedBranch.CanUserPush(doer.ID) {
return nil, models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
if protectedBranch.RequireSignedCommits {
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(err) {
return nil, err
}
return nil, models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
}
patterns := protectedBranch.GetProtectedFilePatterns()
for _, pat := range patterns {
if pat.Match(strings.ToLower(opts.TreePath)) {
return nil, models.ErrFilePathProtected{
Path: opts.TreePath,
}
}
}
}
}
// Check that the path given in opts.treeName is valid (not a git path)
treePath := CleanUploadFileName(opts.TreePath)
if treePath == "" {
return nil, models.ErrFilenameInvalid{
Path: opts.TreePath,
}
}
message := strings.TrimSpace(opts.Message)
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
t, err := NewTemporaryUploadRepository(repo)
if err != nil {
return nil, err
}
defer t.Close()
if err := t.Clone(opts.OldBranch); err != nil {
return nil, err
}
if err := t.SetDefaultIndex(); err != nil {
return nil, err
}
// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.OldBranch)
if err != nil {
return nil, err // Couldn't get a commit for the branch
}
// Assigned LastCommitID in opts if it hasn't been set
if opts.LastCommitID == "" {
opts.LastCommitID = commit.ID.String()
} else {
lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
}
opts.LastCommitID = lastCommitID.String()
}
// Get the files in the index
filesInIndex, err := t.LsFiles(opts.TreePath)
if err != nil {
return nil, fmt.Errorf("DeleteRepoFile: %v", err)
}
// Find the file we want to delete in the index
inFilelist := false
for _, file := range filesInIndex {
if file == opts.TreePath {
inFilelist = true
break
}
}
if !inFilelist {
return nil, models.ErrRepoFileDoesNotExist{
Path: opts.TreePath,
}
}
// Get the entry of treePath and check if the SHA given is the same as the file
entry, err := commit.GetTreeEntryByPath(treePath)
if err != nil {
return nil, err
}
if opts.SHA != "" {
// If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error
if opts.SHA != entry.ID.String() {
return nil, models.ErrSHADoesNotMatch{
Path: treePath,
GivenSHA: opts.SHA,
CurrentSHA: entry.ID.String(),
}
}
} else if opts.LastCommitID != "" {
// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error, but only if we aren't creating a new branch.
if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch {
// CommitIDs don't match, but we don't want to throw a ErrCommitIDDoesNotMatch unless
// this specific file has been edited since opts.LastCommitID
if changed, err := commit.FileChangedSinceCommit(treePath, opts.LastCommitID); err != nil {
return nil, err
} else if changed {
return nil, models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
// The file wasn't modified, so we are good to delete it
}
} else {
// When deleting a file, a lastCommitID or SHA needs to be given to make sure other commits haven't been
// made. We throw an error if one wasn't provided.
return nil, models.ErrSHAOrCommitIDNotProvided{}
}
// Remove the file from the index
if err := t.RemoveFilesFromIndex(opts.TreePath); err != nil {
return nil, err
}
// Now write the tree
treeHash, err := t.WriteTree()
if err != nil {
return nil, err
}
// Now commit the tree
var commitHash string
if opts.Dates != nil {
commitHash, err = t.CommitTreeWithDate(author, committer, treeHash, message, opts.Dates.Author, opts.Dates.Committer)
} else {
commitHash, err = t.CommitTree(author, committer, treeHash, message)
}
if err != nil {
return nil, err
}
// Then push this tree to NewBranch
if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
return nil, err
}
commit, err = t.GetCommit(commitHash)
if err != nil {
return nil, err
}
file, err := GetFileResponseFromCommit(repo, commit, opts.NewBranch, treePath)
if err != nil {
return nil, err
}
return file, nil
}