You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

patch.go 5.4KB

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