gitea源码

cherry_pick.go 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package files
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/services/pull"
  15. )
  16. // ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error.
  17. type ErrCommitIDDoesNotMatch struct {
  18. GivenCommitID string
  19. CurrentCommitID string
  20. }
  21. // IsErrCommitIDDoesNotMatch checks if an error is a ErrCommitIDDoesNotMatch.
  22. func IsErrCommitIDDoesNotMatch(err error) bool {
  23. _, ok := err.(ErrCommitIDDoesNotMatch)
  24. return ok
  25. }
  26. func (err ErrCommitIDDoesNotMatch) Error() string {
  27. return fmt.Sprintf("file CommitID does not match [given: %s, expected: %s]", err.GivenCommitID, err.CurrentCommitID)
  28. }
  29. // CherryPick cherry-picks or reverts a commit to the given repository
  30. func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
  31. if err := opts.Validate(ctx, repo, doer); err != nil {
  32. return nil, err
  33. }
  34. message := strings.TrimSpace(opts.Message)
  35. t, err := NewTemporaryUploadRepository(repo)
  36. if err != nil {
  37. log.Error("NewTemporaryUploadRepository failed: %v", err)
  38. }
  39. defer t.Close()
  40. if err := t.Clone(ctx, opts.OldBranch, false); err != nil {
  41. return nil, err
  42. }
  43. if err := t.SetDefaultIndex(ctx); err != nil {
  44. return nil, err
  45. }
  46. if err := t.RefreshIndex(ctx); err != nil {
  47. return nil, err
  48. }
  49. // Get the commit of the original branch
  50. commit, err := t.GetBranchCommit(opts.OldBranch)
  51. if err != nil {
  52. return nil, err // Couldn't get a commit for the branch
  53. }
  54. // Assigned LastCommitID in opts if it hasn't been set
  55. if opts.LastCommitID == "" {
  56. opts.LastCommitID = commit.ID.String()
  57. } else {
  58. lastCommitID, err := t.gitRepo.ConvertToGitID(opts.LastCommitID)
  59. if err != nil {
  60. return nil, fmt.Errorf("CherryPick: Invalid last commit ID: %w", err)
  61. }
  62. opts.LastCommitID = lastCommitID.String()
  63. if commit.ID.String() != opts.LastCommitID {
  64. return nil, ErrCommitIDDoesNotMatch{
  65. GivenCommitID: opts.LastCommitID,
  66. CurrentCommitID: opts.LastCommitID,
  67. }
  68. }
  69. }
  70. commit, err = t.GetCommit(strings.TrimSpace(opts.Content))
  71. if err != nil {
  72. return nil, err
  73. }
  74. parent, err := commit.ParentID(0)
  75. if err != nil {
  76. parent = git.ObjectFormatFromName(repo.ObjectFormatName).EmptyTree()
  77. }
  78. base, right := parent.String(), commit.ID.String()
  79. if revert {
  80. right, base = base, right
  81. }
  82. description := fmt.Sprintf("CherryPick %s onto %s", right, opts.OldBranch)
  83. conflict, _, err := pull.AttemptThreeWayMerge(ctx,
  84. t.basePath, t.gitRepo, base, opts.LastCommitID, right, description)
  85. if err != nil {
  86. return nil, fmt.Errorf("failed to three-way merge %s onto %s: %w", right, opts.OldBranch, err)
  87. }
  88. if conflict {
  89. return nil, errors.New("failed to merge due to conflicts")
  90. }
  91. treeHash, err := t.WriteTree(ctx)
  92. if err != nil {
  93. // likely non-sensical tree due to merge conflicts...
  94. return nil, err
  95. }
  96. // Now commit the tree
  97. commitOpts := &CommitTreeUserOptions{
  98. ParentCommitID: "HEAD",
  99. TreeHash: treeHash,
  100. CommitMessage: message,
  101. SignOff: opts.Signoff,
  102. DoerUser: doer,
  103. AuthorIdentity: opts.Author,
  104. AuthorTime: nil,
  105. CommitterIdentity: opts.Committer,
  106. CommitterTime: nil,
  107. }
  108. if opts.Dates != nil {
  109. commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
  110. }
  111. commitHash, err := t.CommitTree(ctx, commitOpts)
  112. if err != nil {
  113. return nil, err
  114. }
  115. // Then push this tree to NewBranch
  116. if err := t.Push(ctx, doer, commitHash, opts.NewBranch); err != nil {
  117. return nil, err
  118. }
  119. commit, err = t.GetCommit(commitHash)
  120. if err != nil {
  121. return nil, err
  122. }
  123. fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
  124. verification := GetPayloadCommitVerification(ctx, commit)
  125. fileResponse := &structs.FileResponse{
  126. Commit: fileCommitResponse,
  127. Verification: verification,
  128. }
  129. return fileResponse, nil
  130. }