2019-04-17 19:06:35 +03:00
// 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"
2020-01-14 06:38:04 +03:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-05-11 13:21:34 +03:00
api "code.gitea.io/gitea/modules/structs"
2019-04-17 19:06:35 +03:00
)
// 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
2019-12-24 05:33:52 +03:00
Dates * CommitDateOptions
2019-04-17 19:06:35 +03:00
}
// 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
2020-01-14 06:38:04 +03:00
if _ , err := repo_module . GetBranch ( repo , opts . OldBranch ) ; err != nil {
2019-04-17 19:06:35 +03:00
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 {
2020-01-14 06:38:04 +03:00
newBranch , err := repo_module . GetBranch ( repo , opts . NewBranch )
2020-01-15 02:11:08 +03:00
if err != nil && ! git . IsErrBranchNotExist ( err ) {
2019-04-17 19:06:35 +03:00
return nil , err
}
if newBranch != nil {
return nil , models . ErrBranchAlreadyExists {
BranchName : opts . NewBranch ,
}
}
2020-01-15 11:32:57 +03:00
} else {
protectedBranch , err := repo . GetBranchProtection ( opts . OldBranch )
if err != nil {
return nil , err
}
2020-03-27 01:26:34 +03:00
if protectedBranch != nil {
if ! protectedBranch . CanUserPush ( doer . ID ) {
2020-01-15 11:32:57 +03:00
return nil , models . ErrUserCannotCommit {
UserName : doer . LowerName ,
}
}
2020-03-27 01:26:34 +03:00
if protectedBranch . RequireSignedCommits {
2020-09-19 19:44:55 +03:00
_ , _ , _ , err := repo . SignCRUDAction ( doer , repo . RepoPath ( ) , opts . OldBranch )
2020-03-27 01:26:34 +03:00
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 ,
}
}
}
2019-04-17 19:06:35 +03:00
}
}
// 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 )
2019-12-09 16:11:24 +03:00
author , committer := GetAuthorAndCommitterUsers ( opts . Author , opts . Committer , doer )
2019-04-17 19:06:35 +03:00
t , err := NewTemporaryUploadRepository ( repo )
if err != nil {
return nil , err
}
2019-06-12 22:41:28 +03:00
defer t . Close ( )
2019-04-17 19:06:35 +03:00
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 ( )
2019-08-05 23:39:39 +03:00
} 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 ( )
2019-04-17 19:06:35 +03:00
}
// 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
2019-12-24 05:33:52 +03:00
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 )
}
2019-04-17 19:06:35 +03:00
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
}
2019-06-29 18:19:24 +03:00
commit , err = t . GetCommit ( commitHash )
if err != nil {
return nil , err
}
2019-04-17 19:06:35 +03:00
file , err := GetFileResponseFromCommit ( repo , commit , opts . NewBranch , treePath )
if err != nil {
return nil , err
}
return file , nil
}