code.gitea.io/gitea@v1.19.3/modules/doctor/mergebase.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package doctor
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	issues_model "code.gitea.io/gitea/models/issues"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  
    17  	"xorm.io/builder"
    18  )
    19  
    20  func iteratePRs(ctx context.Context, repo *repo_model.Repository, each func(*repo_model.Repository, *issues_model.PullRequest) error) error {
    21  	return db.Iterate(
    22  		ctx,
    23  		builder.Eq{"base_repo_id": repo.ID},
    24  		func(ctx context.Context, bean *issues_model.PullRequest) error {
    25  			return each(repo, bean)
    26  		},
    27  	)
    28  }
    29  
    30  func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) error {
    31  	numRepos := 0
    32  	numPRs := 0
    33  	numPRsUpdated := 0
    34  	err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
    35  		numRepos++
    36  		return iteratePRs(ctx, repo, func(repo *repo_model.Repository, pr *issues_model.PullRequest) error {
    37  			numPRs++
    38  			pr.BaseRepo = repo
    39  			repoPath := repo.RepoPath()
    40  
    41  			oldMergeBase := pr.MergeBase
    42  
    43  			if !pr.HasMerged {
    44  				var err error
    45  				pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath})
    46  				if err != nil {
    47  					var err2 error
    48  					pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
    49  					if err2 != nil {
    50  						logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2)
    51  						return nil
    52  					}
    53  				}
    54  			} else {
    55  				parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
    56  				if err != nil {
    57  					logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
    58  					return nil
    59  				}
    60  				parents := strings.Split(strings.TrimSpace(parentsString), " ")
    61  				if len(parents) < 2 {
    62  					return nil
    63  				}
    64  
    65  				refs := append([]string{}, parents[1:]...)
    66  				refs = append(refs, pr.GetGitRefName())
    67  				cmd := git.NewCommand(ctx, "merge-base").AddDashesAndList(refs...)
    68  				pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
    69  				if err != nil {
    70  					logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
    71  					return nil
    72  				}
    73  			}
    74  			pr.MergeBase = strings.TrimSpace(pr.MergeBase)
    75  			if pr.MergeBase != oldMergeBase {
    76  				if autofix {
    77  					if err := pr.UpdateCols("merge_base"); err != nil {
    78  						logger.Critical("Failed to update merge_base. ERROR: %v", err)
    79  						return fmt.Errorf("Failed to update merge_base. ERROR: %w", err)
    80  					}
    81  				} else {
    82  					logger.Info("#%d onto %s in %s/%s: MergeBase should be %s but is %s", pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, oldMergeBase, pr.MergeBase)
    83  				}
    84  				numPRsUpdated++
    85  			}
    86  			return nil
    87  		})
    88  	})
    89  
    90  	if autofix {
    91  		logger.Info("%d PR mergebases updated of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
    92  	} else {
    93  		if numPRsUpdated == 0 {
    94  			logger.Info("All %d PRs in %d repos have a correct mergebase", numPRs, numRepos)
    95  		} else if err == nil {
    96  			logger.Critical("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
    97  			return fmt.Errorf("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
    98  		} else {
    99  			logger.Warn("%d PRs with incorrect mergebases of %d PRs total in %d repos", numPRsUpdated, numPRs, numRepos)
   100  		}
   101  	}
   102  
   103  	return err
   104  }
   105  
   106  func init() {
   107  	Register(&Check{
   108  		Title:     "Recalculate merge bases",
   109  		Name:      "recalculate-merge-bases",
   110  		IsDefault: false,
   111  		Run:       checkPRMergeBase,
   112  		Priority:  7,
   113  	})
   114  }