export async function discoverNewConflictsForPr()

in ng-dev/pr/discover-new-conflicts/index.ts [23:132]


export async function discoverNewConflictsForPr(newPrNumber: number, updatedAfter: number) {
  /** The singleton instance of the authenticated git client. */
  const git = AuthenticatedGitClient.get();
  // If there are any local changes in the current repository state, the
  // check cannot run as it needs to move between branches.
  if (git.hasUncommittedChanges()) {
    error('Cannot run with local changes. Please make sure there are no local changes.');
    process.exit(1);
  }

  /** The active github branch or revision before we performed any Git commands. */
  const previousBranchOrRevision = git.getCurrentBranchOrRevision();
  /* Progress bar to indicate progress. */
  const progressBar = new Bar({format: `[{bar}] ETA: {eta}s | {value}/{total}`});
  /* PRs which were found to be conflicting. */
  const conflicts: Array<PullRequestFromGithub> = [];

  info(`Requesting pending PRs from Github`);
  /** List of PRs from github currently known as mergable. */
  const allPendingPRs = await fetchPendingPullRequestsFromGithub(git);

  if (allPendingPRs === null) {
    error('Unable to find any pending PRs in the repository');
    process.exit(1);
  }

  /** The PR which is being checked against. */
  const requestedPr = allPendingPRs.find((pr) => pr.number === newPrNumber);
  if (requestedPr === undefined) {
    error(
      `The request PR, #${newPrNumber} was not found as a pending PR on github, please confirm`,
    );
    error(`the PR number is correct and is an open PR`);
    process.exit(1);
  }

  const pendingPrs = allPendingPRs.filter((pr) => {
    return (
      // PRs being merged into the same target branch as the requested PR
      pr.baseRef.name === requestedPr.baseRef.name &&
      // PRs which either have not been processed or are determined as mergable by Github
      pr.mergeable !== 'CONFLICTING' &&
      // PRs updated after the provided date
      new Date(pr.updatedAt).getTime() >= updatedAfter
    );
  });
  info(`Retrieved ${allPendingPRs.length} total pending PRs`);
  info(`Checking ${pendingPrs.length} PRs for conflicts after a merge of #${newPrNumber}`);

  // Fetch and checkout the PR being checked.
  git.run(['fetch', '-q', requestedPr.headRef.repository.url, requestedPr.headRef.name]);
  git.run(['checkout', '-q', '-B', tempWorkingBranch, 'FETCH_HEAD']);

  // Rebase the PR against the PRs target branch.
  git.run(['fetch', '-q', requestedPr.baseRef.repository.url, requestedPr.baseRef.name]);
  try {
    git.run(['rebase', 'FETCH_HEAD'], {stdio: 'ignore'});
  } catch (err) {
    if (err instanceof GitCommandError) {
      error('The requested PR currently has conflicts');
      git.checkout(previousBranchOrRevision, true);
      process.exit(1);
    }
    throw err;
  }

  // Start the progress bar
  progressBar.start(pendingPrs.length, 0);

  // Check each PR to determine if it can merge cleanly into the repo after the target PR.
  for (const pr of pendingPrs) {
    // Fetch and checkout the next PR
    git.run(['fetch', '-q', pr.headRef.repository.url, pr.headRef.name]);
    git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
    // Check if the PR cleanly rebases into the repo after the target PR.
    try {
      git.run(['rebase', tempWorkingBranch], {stdio: 'ignore'});
    } catch (err) {
      if (err instanceof GitCommandError) {
        conflicts.push(pr);
      } else {
        throw err;
      }
    }
    // Abort any outstanding rebase attempt.
    git.runGraceful(['rebase', '--abort'], {stdio: 'ignore'});

    progressBar.increment(1);
  }
  // End the progress bar as all PRs have been processed.
  progressBar.stop();
  info();
  info(`Result:`);

  git.checkout(previousBranchOrRevision, true);

  // If no conflicts are found, exit successfully.
  if (conflicts.length === 0) {
    info(`No new conflicting PRs found after #${newPrNumber} merging`);
    process.exit(0);
  }

  // Inform about discovered conflicts, exit with failure.
  error.group(`${conflicts.length} PR(s) which conflict(s) after #${newPrNumber} merges:`);
  for (const pr of conflicts) {
    error(`  - #${pr.number}: ${pr.title}`);
  }
  error.groupEnd();
  process.exit(1);
}