Skip to content

Commit

Permalink
use async execFile, update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
ephys committed Apr 12, 2024
1 parent 8653556 commit 169d499
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 55 deletions.
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ jobs:
autoupdate:
runs-on: ubuntu-latest
steps:
# This step is only necessary if you want to update branches from forks,
# as it uses a completely different process (git) than updating branches from the same repository (api call).
- name: Configure git
run: |
# The username of the "UPDATE_FORK_PAT" owner
git config --global user.name "username"
# The email of the "UPDATE_FORK_PAT" owner
git config --global user.email "[email protected]"
- uses: sequelize/pr-auto-update-and-handle-conflicts@v1
with:
conflict-label: 'conflicted'
Expand All @@ -56,13 +64,23 @@ jobs:
update-excluded-authors: 'bot/renovate'
update-excluded-labels: 'no-autoupdate'
env:
# The GITHUB_TOKEN will handle operations that the GitHub Bot can perform,
# such as searching the repository, adding/removing labels, and drafting PRs.
# The GITHUB_TOKEN to use for all operations, unless one of the two properties
# below are specified.
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
# The PAT is used to perform operations that the GitHub Bot cannot perform: updating the PR branch
# If not specified, the GITHUB_TOKEN will be used, in which case the GITHUB_TOKEN must be one
# with the necessary permission to update the PR branch (assuming the feature is enabled).
PAT: '${{ secrets.PAT }}'

# The default GITHUB_TOKEN does not cause the branch update to trigger subsequent workflows,
# which means that CI checks will not run on the updated branch.
# To solve this, you need to use a different token. Either one that belongs to a user, or to a GitHub App.
# Defaults to the GITHUB_TOKEN env
UPDATE_BRANCH_PAT: '${{ secrets.UPDATE_BRANCH_PAT }}'

# Same reasoning as UPDATE_BRANCH_PAT, but for updating branches from a fork.
# This one _requires_ using a user PAT. A GitHub App PAT will not work if the update includes workflow files.
# This token must have the Read & Write permissions for "contents" and "workflows"
# If you do not want to update branches from forks, you can set the "update-requires-source" option to "branches"
# Defaults to the GITHUB_TOKEN env
UPDATE_FORK_PAT: '${{ secrets.PAT }}'
UPDATE_FORK_USERNAME: 'ephys'
```
## Options
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ inputs:
required: false
default: ''
update-requires-source:
description: 'Only update PRs that are either forks or branches from the same repository. Accepts "all" (default), "forks", and "branches"'
description: 'Only update PRs that are either forks or branches from the same repository. Accepts "all" (default), "forks", and "branch"'
required: false
default: 'all'
dry-run:
Expand Down
34 changes: 17 additions & 17 deletions lib/action.mjs

Large diffs are not rendered by default.

95 changes: 64 additions & 31 deletions src/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import core from '@actions/core';
import github from '@actions/github';
import type { PullRequestEvent, PushEvent } from '@octokit/webhooks-types';
import { isString } from '@sequelize/utils';
import { execFileSync } from 'node:child_process';
import childProcess from 'node:child_process';
import fs from 'node:fs/promises';
import * as path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import { promisify } from 'node:util';

const execFile = promisify(childProcess.execFile);

isString.assert(process.env.GITHUB_TOKEN, 'GITHUB_TOKEN env must be provided');

Expand All @@ -32,6 +35,10 @@ const updateBranchBot = process.env.UPDATE_BRANCH_PAT
* We recommend using a user PAT for this, as:
* - Using the GITHUB_TOKEN will not trigger subsequent workflows.
* - Using a bot PAT will cause an error if the branch update includes a workflow file.
*
* This will need the following permissions:
* - contents (read & write)
* - workflows (read & write)
*/
const updateForkPat = process.env.UPDATE_FORK_PAT || process.env.GITHUB_TOKEN;
const updateForkUsername = process.env.UPDATE_FORK_USERNAME || 'x-access-token';
Expand Down Expand Up @@ -73,7 +80,7 @@ const updateExcludedAuthors = getCommaSeparatedInput('update-excluded-authors');
const updateRequiresSource = getEnumInput('update-requires-source', [
'all',
'fork',
'branches',
'branch',
] as const);

interface RepositoryId {
Expand Down Expand Up @@ -305,22 +312,24 @@ async function updatePrBranch(repositoryId: RepositoryId, pullRequest: PullReque

updatedPrs.push(pullRequest.number);

console.info(`[PR ${pullRequest.number}] ✅ Updating branch.`);

if (dryRun) {
return;
}

// The "update-branch" endpoint does not allow modifying pull requests from repositories we do not own,
// even if the "allow maintainers to modify" setting is enabled on the PR.
if (!isForkPr(pullRequest)) {
console.info(`[PR ${pullRequest.number}] ✅ Updating branch via API.`);

// This operation cannot be done with GITHUB_TOKEN, as the GITHUB_TOKEN does not trigger subsequent workflows.
return updateBranchBot.rest.pulls.updateBranch({
...repositoryId,
pull_number: pullRequest.number,
});
}

console.info(`[PR ${pullRequest.number}] ✅ Updating fork via git.`);

// For fork PRs, we use git directly instead:
// - Clone the repository in a new directory
// - Merge the base branch into the PR branch
Expand All @@ -333,36 +342,60 @@ async function updatePrBranch(repositoryId: RepositoryId, pullRequest: PullReque
const parentRepositoryUrl = `https://${updateForkUsername}:${updateForkPat}@github.com/${pullRequest.baseRepository.nameWithOwner}.git`;

// clone fork repository in the correct branch
console.log(
`git clone ${forkRepositoryUrl} ${targetDirectoryName} --branch ${pullRequest.headRef.name}`,
);
execFileSync(
'git',
['clone', forkRepositoryUrl, targetDirectoryName, '--branch', pullRequest.headRef.name],
{
stdio: 'inherit',
},
);
{
const { stdout, stderr } = await execFile('git', [
'clone',
'--quiet',
forkRepositoryUrl,
targetDirectoryName,
'--branch',
pullRequest.headRef.name,
]);

stdout && console.info(`[PR ${pullRequest.number}] ${stdout}`);
stderr && console.error(`[PR ${pullRequest.number}] ${stderr}`);
}

// add parent repository as remote
console.log(`git remote add parent ${parentRepositoryUrl}`);
execFileSync('git', ['remote', 'add', 'parent', parentRepositoryUrl], {
cwd: targetDirectoryPath,
stdio: 'inherit',
});
{
const { stdout, stderr } = await execFile(
'git',
['remote', 'add', 'parent', parentRepositoryUrl],
{
cwd: targetDirectoryPath,
},
);

stdout && console.info(`[PR ${pullRequest.number}] ${stdout}`);
stderr && console.error(`[PR ${pullRequest.number}] ${stderr}`);
}

// merge parent branch in local branch
console.log(`git pull parent ${pullRequest.baseRef.name} --no-edit --no-rebase`);
execFileSync('git', ['pull', 'parent', pullRequest.baseRef.name, '--no-edit', '--no-rebase'], {
cwd: targetDirectoryPath,
stdio: 'inherit',
});

console.log(`git push origin ${pullRequest.headRef.name}`);
execFileSync('git', ['push', 'origin', pullRequest.headRef.name], {
cwd: targetDirectoryPath,
stdio: 'inherit',
});
{
const { stdout, stderr } = await execFile(
'git',
['pull', '--quiet', 'parent', pullRequest.baseRef.name, '--no-edit', '--no-rebase'],
{
cwd: targetDirectoryPath,
},
);

stdout && console.info(`[PR ${pullRequest.number}] ${stdout}`);
stderr && console.error(`[PR ${pullRequest.number}] ${stderr}`);
}

{
const { stdout, stderr } = await execFile(
'git',
['push', '--quiet', 'origin', pullRequest.headRef.name],
{
cwd: targetDirectoryPath,
},
);

stdout && console.info(`[PR ${pullRequest.number}] ${stdout}`);
stderr && console.error(`[PR ${pullRequest.number}] ${stderr}`);
}
}

async function handleConflict(repositoryId: RepositoryId, pullRequest: PullRequest): Promise<void> {
Expand Down Expand Up @@ -539,7 +572,7 @@ function prMatchesSource(pullRequest: PullRequest, source: typeof updateRequires
case 'fork':
return isForkPr(pullRequest);

case 'branches':
case 'branch':
return !isForkPr(pullRequest);
}
}
Expand Down

0 comments on commit 169d499

Please sign in to comment.