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 }