107 lines · 3.6 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package pull import (	"context"	"fmt"	"strings" 	issues_model "forgejo.org/models/issues"	repo_model "forgejo.org/models/repo"	user_model "forgejo.org/models/user"	"forgejo.org/modules/git"	"forgejo.org/modules/log"	repo_module "forgejo.org/modules/repository"	"forgejo.org/modules/setting") // updateHeadByRebaseOnToBase handles updating a PR's head branch by rebasing it on the PR current base branchfunc updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) error {	// "Clone" base repo and add the cache headers for the head repo and branch	mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, "")	if err != nil {		return err	}	defer cancel() 	// Determine the old merge-base before the rebase - we use this for LFS push later on	oldMergeBase, _, _ := git.NewCommand(ctx, "merge-base").AddDashesAndList(baseBranch, trackingBranch).RunStdString(&git.RunOpts{Dir: mergeCtx.tmpBasePath})	oldMergeBase = strings.TrimSpace(oldMergeBase) 	// Rebase the tracking branch on to the base as the staging branch	if err := rebaseTrackingOnToBase(mergeCtx, repo_model.MergeStyleRebaseUpdate); err != nil {		return err	} 	if setting.LFS.StartServer {		// Now we need to ensure that the head repository contains any LFS objects between the new base and the old mergebase		// It's questionable about where this should go - either after or before the push		// I think in the interests of data safety - failures to push to the lfs should prevent		// the push as you can always re-rebase.		if err := LFSPush(ctx, mergeCtx.tmpBasePath, baseBranch, oldMergeBase, &issues_model.PullRequest{			HeadRepoID: pr.BaseRepoID,			BaseRepoID: pr.HeadRepoID,		}); err != nil {			log.Error("Unable to push lfs objects between %s and %s up to head branch in %-v: %v", baseBranch, oldMergeBase, pr, err)			return err		}	} 	// Now determine who the pushing author should be	var headUser *user_model.User	if err := pr.HeadRepo.LoadOwner(ctx); err != nil {		if !user_model.IsErrUserNotExist(err) {			log.Error("Can't find user: %d for head repository in %-v - %v", pr.HeadRepo.OwnerID, pr, err)			return err		}		log.Error("Can't find user: %d for head repository in %-v - defaulting to doer: %-v - %v", pr.HeadRepo.OwnerID, pr, doer, err)		headUser = doer	} else {		headUser = pr.HeadRepo.Owner	} 	pushCmd := git.NewCommand(ctx, "push", "-f", "head_repo").		AddDynamicArguments(stagingBranch + ":" + git.BranchPrefix + pr.HeadBranch) 	// Push back to the head repository.	// TODO: this cause an api call to "/api/internal/hook/post-receive/...",	//       that prevents us from doint the whole merge in one db transaction	mergeCtx.outbuf.Reset()	mergeCtx.errbuf.Reset() 	if err := pushCmd.Run(&git.RunOpts{		Env: repo_module.FullPushingEnvironment(			headUser,			doer,			pr.HeadRepo,			pr.HeadRepo.Name,			pr.ID,		),		Dir:    mergeCtx.tmpBasePath,		Stdout: mergeCtx.outbuf,		Stderr: mergeCtx.errbuf,	}); err != nil {		if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {			return &git.ErrPushOutOfDate{				StdOut: mergeCtx.outbuf.String(),				StdErr: mergeCtx.errbuf.String(),				Err:    err,			}		} else if strings.Contains(mergeCtx.errbuf.String(), "! [remote rejected]") {			err := &git.ErrPushRejected{				StdOut: mergeCtx.outbuf.String(),				StdErr: mergeCtx.errbuf.String(),				Err:    err,			}			err.GenerateMessage()			return err		}		return fmt.Errorf("git push: %s", mergeCtx.errbuf.String())	}	mergeCtx.outbuf.Reset()	mergeCtx.errbuf.Reset() 	return nil}