code.gitea.io/gitea@v1.21.7/services/repository/files/patch.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package files 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models" 12 git_model "code.gitea.io/gitea/models/git" 13 repo_model "code.gitea.io/gitea/models/repo" 14 user_model "code.gitea.io/gitea/models/user" 15 "code.gitea.io/gitea/modules/git" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/structs" 18 asymkey_service "code.gitea.io/gitea/services/asymkey" 19 ) 20 21 // ApplyDiffPatchOptions holds the repository diff patch update options 22 type ApplyDiffPatchOptions struct { 23 LastCommitID string 24 OldBranch string 25 NewBranch string 26 Message string 27 Content string 28 SHA string 29 Author *IdentityOptions 30 Committer *IdentityOptions 31 Dates *CommitDateOptions 32 Signoff bool 33 } 34 35 // Validate validates the provided options 36 func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) error { 37 // If no branch name is set, assume master 38 if opts.OldBranch == "" { 39 opts.OldBranch = repo.DefaultBranch 40 } 41 if opts.NewBranch == "" { 42 opts.NewBranch = opts.OldBranch 43 } 44 45 gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath()) 46 if err != nil { 47 return err 48 } 49 defer closer.Close() 50 51 // oldBranch must exist for this operation 52 if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil { 53 return err 54 } 55 // A NewBranch can be specified for the patch to be applied to. 56 // Check to make sure the branch does not already exist, otherwise we can't proceed. 57 // If we aren't branching to a new branch, make sure user can commit to the given branch 58 if opts.NewBranch != opts.OldBranch { 59 existingBranch, err := gitRepo.GetBranch(opts.NewBranch) 60 if existingBranch != nil { 61 return git_model.ErrBranchAlreadyExists{ 62 BranchName: opts.NewBranch, 63 } 64 } 65 if err != nil && !git.IsErrBranchNotExist(err) { 66 return err 67 } 68 } else { 69 protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch) 70 if err != nil { 71 return err 72 } 73 if protectedBranch != nil { 74 protectedBranch.Repo = repo 75 if !protectedBranch.CanUserPush(ctx, doer) { 76 return models.ErrUserCannotCommit{ 77 UserName: doer.LowerName, 78 } 79 } 80 } 81 if protectedBranch != nil && protectedBranch.RequireSignedCommits { 82 _, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), opts.OldBranch) 83 if err != nil { 84 if !asymkey_service.IsErrWontSign(err) { 85 return err 86 } 87 return models.ErrUserCannotCommit{ 88 UserName: doer.LowerName, 89 } 90 } 91 } 92 } 93 return nil 94 } 95 96 // ApplyDiffPatch applies a patch to the given repository 97 func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) { 98 if err := opts.Validate(ctx, repo, doer); err != nil { 99 return nil, err 100 } 101 102 message := strings.TrimSpace(opts.Message) 103 104 author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer) 105 106 t, err := NewTemporaryUploadRepository(ctx, repo) 107 if err != nil { 108 log.Error("%v", err) 109 } 110 defer t.Close() 111 if err := t.Clone(opts.OldBranch, true); err != nil { 112 return nil, err 113 } 114 if err := t.SetDefaultIndex(); err != nil { 115 return nil, err 116 } 117 118 // Get the commit of the original branch 119 commit, err := t.GetBranchCommit(opts.OldBranch) 120 if err != nil { 121 return nil, err // Couldn't get a commit for the branch 122 } 123 124 // Assigned LastCommitID in opts if it hasn't been set 125 if opts.LastCommitID == "" { 126 opts.LastCommitID = commit.ID.String() 127 } else { 128 lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) 129 if err != nil { 130 return nil, fmt.Errorf("ApplyPatch: Invalid last commit ID: %w", err) 131 } 132 opts.LastCommitID = lastCommitID.String() 133 if commit.ID.String() != opts.LastCommitID { 134 return nil, models.ErrCommitIDDoesNotMatch{ 135 GivenCommitID: opts.LastCommitID, 136 CurrentCommitID: opts.LastCommitID, 137 } 138 } 139 } 140 141 stdout := &strings.Builder{} 142 stderr := &strings.Builder{} 143 144 cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") 145 if git.CheckGitVersionAtLeast("2.32") == nil { 146 cmdApply.AddArguments("-3") 147 } 148 149 if err := cmdApply.Run(&git.RunOpts{ 150 Dir: t.basePath, 151 Stdout: stdout, 152 Stderr: stderr, 153 Stdin: strings.NewReader(opts.Content), 154 }); err != nil { 155 return nil, fmt.Errorf("Error: Stdout: %s\nStderr: %s\nErr: %w", stdout.String(), stderr.String(), err) 156 } 157 158 // Now write the tree 159 treeHash, err := t.WriteTree() 160 if err != nil { 161 return nil, err 162 } 163 164 // Now commit the tree 165 var commitHash string 166 if opts.Dates != nil { 167 commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer) 168 } else { 169 commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff) 170 } 171 if err != nil { 172 return nil, err 173 } 174 175 // Then push this tree to NewBranch 176 if err := t.Push(doer, commitHash, opts.NewBranch); err != nil { 177 return nil, err 178 } 179 180 commit, err = t.GetCommit(commitHash) 181 if err != nil { 182 return nil, err 183 } 184 185 fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil 186 verification := GetPayloadCommitVerification(ctx, commit) 187 fileResponse := &structs.FileResponse{ 188 Commit: fileCommitResponse, 189 Verification: verification, 190 } 191 192 return fileResponse, nil 193 }