code.gitea.io/gitea@v1.21.7/services/pull/update_rebase.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package pull
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	issues_model "code.gitea.io/gitea/models/issues"
    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  	repo_module "code.gitea.io/gitea/modules/repository"
    17  	"code.gitea.io/gitea/modules/setting"
    18  )
    19  
    20  // updateHeadByRebaseOnToBase handles updating a PR's head branch by rebasing it on the PR current base branch
    21  func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, message string) error {
    22  	// "Clone" base repo and add the cache headers for the head repo and branch
    23  	mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, "")
    24  	if err != nil {
    25  		return err
    26  	}
    27  	defer cancel()
    28  
    29  	// Determine the old merge-base before the rebase - we use this for LFS push later on
    30  	oldMergeBase, _, _ := git.NewCommand(ctx, "merge-base").AddDashesAndList(baseBranch, trackingBranch).RunStdString(&git.RunOpts{Dir: mergeCtx.tmpBasePath})
    31  	oldMergeBase = strings.TrimSpace(oldMergeBase)
    32  
    33  	// Rebase the tracking branch on to the base as the staging branch
    34  	if err := rebaseTrackingOnToBase(mergeCtx, repo_model.MergeStyleRebaseUpdate); err != nil {
    35  		return err
    36  	}
    37  
    38  	if setting.LFS.StartServer {
    39  		// Now we need to ensure that the head repository contains any LFS objects between the new base and the old mergebase
    40  		// It's questionable about where this should go - either after or before the push
    41  		// I think in the interests of data safety - failures to push to the lfs should prevent
    42  		// the push as you can always re-rebase.
    43  		if err := LFSPush(ctx, mergeCtx.tmpBasePath, baseBranch, oldMergeBase, &issues_model.PullRequest{
    44  			HeadRepoID: pr.BaseRepoID,
    45  			BaseRepoID: pr.HeadRepoID,
    46  		}); err != nil {
    47  			log.Error("Unable to push lfs objects between %s and %s up to head branch in %-v: %v", baseBranch, oldMergeBase, pr, err)
    48  			return err
    49  		}
    50  	}
    51  
    52  	// Now determine who the pushing author should be
    53  	var headUser *user_model.User
    54  	if err := pr.HeadRepo.LoadOwner(ctx); err != nil {
    55  		if !user_model.IsErrUserNotExist(err) {
    56  			log.Error("Can't find user: %d for head repository in %-v - %v", pr.HeadRepo.OwnerID, pr, err)
    57  			return err
    58  		}
    59  		log.Error("Can't find user: %d for head repository in %-v - defaulting to doer: %-v - %v", pr.HeadRepo.OwnerID, pr, doer, err)
    60  		headUser = doer
    61  	} else {
    62  		headUser = pr.HeadRepo.Owner
    63  	}
    64  
    65  	pushCmd := git.NewCommand(ctx, "push", "-f", "head_repo").
    66  		AddDynamicArguments(stagingBranch + ":" + git.BranchPrefix + pr.HeadBranch)
    67  
    68  	// Push back to the head repository.
    69  	// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
    70  	//       that prevents us from doint the whole merge in one db transaction
    71  	mergeCtx.outbuf.Reset()
    72  	mergeCtx.errbuf.Reset()
    73  
    74  	if err := pushCmd.Run(&git.RunOpts{
    75  		Env: repo_module.FullPushingEnvironment(
    76  			headUser,
    77  			doer,
    78  			pr.HeadRepo,
    79  			pr.HeadRepo.Name,
    80  			pr.ID,
    81  		),
    82  		Dir:    mergeCtx.tmpBasePath,
    83  		Stdout: mergeCtx.outbuf,
    84  		Stderr: mergeCtx.errbuf,
    85  	}); err != nil {
    86  		if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
    87  			return &git.ErrPushOutOfDate{
    88  				StdOut: mergeCtx.outbuf.String(),
    89  				StdErr: mergeCtx.errbuf.String(),
    90  				Err:    err,
    91  			}
    92  		} else if strings.Contains(mergeCtx.errbuf.String(), "! [remote rejected]") {
    93  			err := &git.ErrPushRejected{
    94  				StdOut: mergeCtx.outbuf.String(),
    95  				StdErr: mergeCtx.errbuf.String(),
    96  				Err:    err,
    97  			}
    98  			err.GenerateMessage()
    99  			return err
   100  		}
   101  		return fmt.Errorf("git push: %s", mergeCtx.errbuf.String())
   102  	}
   103  	mergeCtx.outbuf.Reset()
   104  	mergeCtx.errbuf.Reset()
   105  
   106  	return nil
   107  }