2022-02-09 23:28:55 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-02-09 23:28:55 +03:00
package files
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2022-02-09 23:28:55 +03:00
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
asymkey_service "code.gitea.io/gitea/services/asymkey"
)
// ApplyDiffPatchOptions holds the repository diff patch update options
type ApplyDiffPatchOptions struct {
LastCommitID string
OldBranch string
NewBranch string
Message string
Content string
SHA string
Author * IdentityOptions
Committer * IdentityOptions
Dates * CommitDateOptions
Signoff bool
}
// Validate validates the provided options
func ( opts * ApplyDiffPatchOptions ) Validate ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User ) error {
// If no branch name is set, assume master
if opts . OldBranch == "" {
opts . OldBranch = repo . DefaultBranch
}
if opts . NewBranch == "" {
opts . NewBranch = opts . OldBranch
}
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
if err != nil {
return err
}
defer closer . Close ( )
// oldBranch must exist for this operation
if _ , err := gitRepo . GetBranch ( opts . OldBranch ) ; err != nil {
return err
}
// A NewBranch can be specified for the patch to be applied to.
// Check to make sure the branch does not already exist, otherwise we can't proceed.
// If we aren't branching to a new branch, make sure user can commit to the given branch
if opts . NewBranch != opts . OldBranch {
existingBranch , err := gitRepo . GetBranch ( opts . NewBranch )
if existingBranch != nil {
return models . ErrBranchAlreadyExists {
BranchName : opts . NewBranch ,
}
}
if err != nil && ! git . IsErrBranchNotExist ( err ) {
return err
}
} else {
2023-01-16 11:00:22 +03:00
protectedBranch , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , repo . ID , opts . OldBranch )
2022-02-09 23:28:55 +03:00
if err != nil {
return err
}
2023-01-16 11:00:22 +03:00
if protectedBranch != nil {
protectedBranch . Repo = repo
if ! protectedBranch . CanUserPush ( ctx , doer ) {
return models . ErrUserCannotCommit {
UserName : doer . LowerName ,
}
2022-02-09 23:28:55 +03:00
}
}
if protectedBranch != nil && protectedBranch . RequireSignedCommits {
_ , _ , _ , err := asymkey_service . SignCRUDAction ( ctx , repo . RepoPath ( ) , doer , repo . RepoPath ( ) , opts . OldBranch )
if err != nil {
if ! asymkey_service . IsErrWontSign ( err ) {
return err
}
return models . ErrUserCannotCommit {
UserName : doer . LowerName ,
}
}
}
}
return nil
}
// ApplyDiffPatch applies a patch to the given repository
func ApplyDiffPatch ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User , opts * ApplyDiffPatchOptions ) ( * structs . FileResponse , error ) {
if err := opts . Validate ( ctx , repo , doer ) ; err != nil {
return nil , err
}
message := strings . TrimSpace ( opts . Message )
author , committer := GetAuthorAndCommitterUsers ( opts . Author , opts . Committer , doer )
t , err := NewTemporaryUploadRepository ( ctx , repo )
if err != nil {
log . Error ( "%v" , err )
}
defer t . Close ( )
if err := t . Clone ( opts . OldBranch ) ; err != nil {
return nil , err
}
if err := t . SetDefaultIndex ( ) ; err != nil {
return nil , err
}
// Get the commit of the original branch
commit , err := t . GetBranchCommit ( opts . OldBranch )
if err != nil {
return nil , err // Couldn't get a commit for the branch
}
// Assigned LastCommitID in opts if it hasn't been set
if opts . LastCommitID == "" {
opts . LastCommitID = commit . ID . String ( )
} else {
lastCommitID , err := t . gitRepo . ConvertToSHA1 ( opts . LastCommitID )
if err != nil {
2022-10-24 22:29:17 +03:00
return nil , fmt . Errorf ( "ApplyPatch: Invalid last commit ID: %w" , err )
2022-02-09 23:28:55 +03:00
}
opts . LastCommitID = lastCommitID . String ( )
if commit . ID . String ( ) != opts . LastCommitID {
return nil , models . ErrCommitIDDoesNotMatch {
GivenCommitID : opts . LastCommitID ,
CurrentCommitID : opts . LastCommitID ,
}
}
}
stdout := & strings . Builder { }
stderr := & strings . Builder { }
2022-10-23 17:44:45 +03:00
args := [ ] git . CmdArg { "apply" , "--index" , "--recount" , "--cached" , "--ignore-whitespace" , "--whitespace=fix" , "--binary" }
2022-02-09 23:28:55 +03:00
if git . CheckGitVersionAtLeast ( "2.32" ) == nil {
args = append ( args , "-3" )
}
cmd := git . NewCommand ( ctx , args ... )
2022-04-01 05:55:30 +03:00
if err := cmd . Run ( & git . RunOpts {
Dir : t . basePath ,
Stdout : stdout ,
Stderr : stderr ,
Stdin : strings . NewReader ( opts . Content ) ,
2022-02-09 23:28:55 +03:00
} ) ; err != nil {
2022-10-24 22:29:17 +03:00
return nil , fmt . Errorf ( "Error: Stdout: %s\nStderr: %s\nErr: %w" , stdout . String ( ) , stderr . String ( ) , err )
2022-02-09 23:28:55 +03:00
}
// 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 {
2022-03-28 22:48:41 +03:00
commitHash , err = t . CommitTreeWithDate ( "HEAD" , author , committer , treeHash , message , opts . Signoff , opts . Dates . Author , opts . Dates . Committer )
2022-02-09 23:28:55 +03:00
} else {
2022-03-28 22:48:41 +03:00
commitHash , err = t . CommitTree ( "HEAD" , author , committer , treeHash , message , opts . Signoff )
2022-02-09 23:28:55 +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
}
commit , err = t . GetCommit ( commitHash )
if err != nil {
return nil , err
}
fileCommitResponse , _ := GetFileCommitResponse ( repo , commit ) // ok if fails, then will be nil
verification := GetPayloadCommitVerification ( commit )
fileResponse := & structs . FileResponse {
Commit : fileCommitResponse ,
Verification : verification ,
}
return fileResponse , nil
}