code.gitea.io/gitea@v1.21.7/services/repository/files/cherry_pick.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  	repo_model "code.gitea.io/gitea/models/repo"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/structs"
    17  	"code.gitea.io/gitea/services/pull"
    18  )
    19  
    20  // CherryPick cherrypicks or reverts a commit to the given repository
    21  func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
    22  	if err := opts.Validate(ctx, repo, doer); err != nil {
    23  		return nil, err
    24  	}
    25  	message := strings.TrimSpace(opts.Message)
    26  
    27  	author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
    28  
    29  	t, err := NewTemporaryUploadRepository(ctx, repo)
    30  	if err != nil {
    31  		log.Error("%v", err)
    32  	}
    33  	defer t.Close()
    34  	if err := t.Clone(opts.OldBranch, false); err != nil {
    35  		return nil, err
    36  	}
    37  	if err := t.SetDefaultIndex(); err != nil {
    38  		return nil, err
    39  	}
    40  	if err := t.RefreshIndex(); err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	// Get the commit of the original branch
    45  	commit, err := t.GetBranchCommit(opts.OldBranch)
    46  	if err != nil {
    47  		return nil, err // Couldn't get a commit for the branch
    48  	}
    49  
    50  	// Assigned LastCommitID in opts if it hasn't been set
    51  	if opts.LastCommitID == "" {
    52  		opts.LastCommitID = commit.ID.String()
    53  	} else {
    54  		lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
    55  		if err != nil {
    56  			return nil, fmt.Errorf("CherryPick: Invalid last commit ID: %w", err)
    57  		}
    58  		opts.LastCommitID = lastCommitID.String()
    59  		if commit.ID.String() != opts.LastCommitID {
    60  			return nil, models.ErrCommitIDDoesNotMatch{
    61  				GivenCommitID:   opts.LastCommitID,
    62  				CurrentCommitID: opts.LastCommitID,
    63  			}
    64  		}
    65  	}
    66  
    67  	commit, err = t.GetCommit(strings.TrimSpace(opts.Content))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	parent, err := commit.ParentID(0)
    72  	if err != nil {
    73  		parent = git.MustIDFromString(git.EmptyTreeSHA)
    74  	}
    75  
    76  	base, right := parent.String(), commit.ID.String()
    77  
    78  	if revert {
    79  		right, base = base, right
    80  	}
    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  
    89  	if conflict {
    90  		return nil, fmt.Errorf("failed to merge due to conflicts")
    91  	}
    92  
    93  	treeHash, err := t.WriteTree()
    94  	if err != nil {
    95  		// likely non-sensical tree due to merge conflicts...
    96  		return nil, err
    97  	}
    98  
    99  	// Now commit the tree
   100  	var commitHash string
   101  	if opts.Dates != nil {
   102  		commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
   103  	} else {
   104  		commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
   105  	}
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	// Then push this tree to NewBranch
   111  	if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	commit, err = t.GetCommit(commitHash)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	fileCommitResponse, _ := GetFileCommitResponse(repo, commit) // ok if fails, then will be nil
   121  	verification := GetPayloadCommitVerification(ctx, commit)
   122  	fileResponse := &structs.FileResponse{
   123  		Commit:       fileCommitResponse,
   124  		Verification: verification,
   125  	}
   126  
   127  	return fileResponse, nil
   128  }