Skip to content

Commit

Permalink
feat: add support for custom branch name created by bot (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
derberg authored Apr 27, 2023
1 parent 54b11fb commit beecbe8
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ exclude_private | Boolean value on whether to exclude private repositories from
exclude_forked | Boolean value on whether to exclude forked repositories from this action. | false | false
branches | By default, action creates branch from default branch and opens PR only against default branch. With this property you can override this behaviour. You can provide a comma-separated list of branches this action shoudl work against. You can also provide regex, but without comma as list of branches is split in code by comma. | false | default branch is used
destination | Name of the directory where all files matching "patterns_to_include" will be copied. It doesn't work with "patterns_to_remove". In the format `.github/workflows`. | false | -
bot_branch_name | Use it if you do not want this action to create a new branch and new pull request with every run. By default branch names are generated. This means every single change is a separate commit. Such a static hardcoded branch name has an advantage that if you make a lot of changes, instead of having 5 PRs merged with 5 commits, you get one PR that is updated with new changes as long as the PR is not yet merged. If you use static name, and by mistake someone closed a PR, without merging and removing branch, this action will not fail but update the branch and open a new PR. Example value that you could provide: `bot_branch_name: bot/update-files-from-global-repo` | false | -

## Examples

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ inputs:
description: >
Name of the directory where all files matching "patterns_to_include" will be copied. It doesn't work with "patterns_to_remove". In the format `.github/workflows`.
required: false
bot_branch_name:
description: >
Use it if you do not want this action to create a new branch and new pull request with every run. By default branch names are generated. This means every single change is a separate commit. Such a static hardcoded branch name has an advantage that if you make a lot of changes, instead of having 5 PRs merged with 5 commits, you get one PR that is updated with new changes as long as the PR is not yet merged. If you use static name, and by mistake someone closed a PR, without merging and removing branch, this action will not fail but update the branch and open a new PR. Example value that you could provide: `bot_branch_name: bot/update-files-from-global-repo`.
required: false
runs:
using: node16
main: dist/index.js
Expand Down
59 changes: 45 additions & 14 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8028,11 +8028,25 @@ async function getBranchesLocal(git) {
async function push(branchName, message, committerUsername, committerEmail, git) {
if (core.isDebug()) __webpack_require__(231).enable('simple-git');
core.info('Pushing changes to remote');

await git.addConfig('user.name', committerUsername);
await git.addConfig('user.email', committerEmail);
await git.commit(message);
await git.push(['-u', REMOTE, branchName]);
try {
await git.push(['-u', REMOTE, branchName]);
} catch (error) {
core.info('Not able to push:', error);
try {
await git.pull([REMOTE, branchName]);
} catch (error) {
core.info('Not able to pull:', error);
await git.merge(['-X', 'ours', branchName]);
core.debug('DEBUG: Git status after merge');
core.debug(JSON.stringify(await git.status(), null, 2));
await git.add('./*');
await git.commit(message);
await git.push(['-u', REMOTE, branchName]);
}
}
}

async function areFilesChanged(git) {
Expand Down Expand Up @@ -14299,6 +14313,7 @@ async function run() {
const commitMessage = core.getInput('commit_message');
const branches = core.getInput('branches');
const destination = core.getInput('destination');
const customBranchName = core.getInput('bot_branch_name');
const repoNameManual = eventPayload.inputs && eventPayload.inputs.repo_name;

const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
Expand Down Expand Up @@ -14373,7 +14388,7 @@ async function run() {
/*
* 4a. Creating folder where repo will be cloned and initializing git client
*/
const dir = path.join(process.cwd(), './clones', repo.name);
const dir = path.join(process.cwd(), './clones', `${repo.name }-${ Math.random().toString(36).substring(7)}`);
await mkdir(dir, {recursive: true});
const git = simpleGit({baseDir: dir});

Expand All @@ -14391,15 +14406,15 @@ async function run() {
* Should it be just default one or the ones provided by the user
*/
const branchesToOperateOn = await getBranchesList(myOctokit, owner, repo.name, branches, defaultBranch);
if (!branchesToOperateOn.length) {
if (!branchesToOperateOn[0].length) {
core.info('Repo has no branches that the action could operate on');
continue;
}

/*
* 4d. Per branch operation starts
*/
for (const branch of branchesToOperateOn) {
for (const branch of branchesToOperateOn[0]) {
/*
* 4da. Checkout branch in cloned repo
*/
Expand All @@ -14409,8 +14424,15 @@ async function run() {
/*
* 4db. Creating new branch in cloned repo
*/
const newBranchName = getBranchName(commitId, branchName);
await createBranch(newBranchName, git);
const newBranchName = customBranchName || getBranchName(commitId, branchName);
const wasBranchThereAlready = branchesToOperateOn[1].some(branch => branch.name === newBranchName);
core.debug(`DEBUG: was branch ${newBranchName} there already in the repository? - ${wasBranchThereAlready}`);
core.debug(JSON.stringify(branchesToOperateOn, null, 2));
if (wasBranchThereAlready) {
await checkoutBranch(newBranchName, git);
} else {
await createBranch(newBranchName, git);
}

/*
* 4dc. Files replication/update or deletion
Expand All @@ -14429,14 +14451,23 @@ async function run() {
await push(newBranchName, commitMessage, committerUsername, committerEmail, git);

/*
* 4fe. Opening a PR
*/
const pullRequestUrl = await createPr(myOctokit, newBranchName, repo.id, commitMessage, branchName);

* 4fe. Opening a PR. Doing in try/catch as it is not always failing because of timeouts, maybe branch already has a PR
* we need to try to create a PR cause there can be branch but someone closed PR, so branch is there but PR not
*/
let pullRequestUrl;
try {
pullRequestUrl = await createPr(myOctokit, newBranchName, repo.id, commitMessage, branchName);
} catch (error) {
if (wasBranchThereAlready)
core.info(`PR creation for ${repo.name} failed as the branch was there already. Insted only push was performed to existing ${newBranchName} branch`, error);
}

core.endGroup();

if (pullRequestUrl) {
core.info(`Workflow finished with success and PR for ${repo.name} is created -> ${pullRequestUrl}`);
} else if (!pullRequestUrl && wasBranchThereAlready) {
core.info(`Workflow finished without PR creation for ${repo.name}. Insted push was performed to existing ${newBranchName} branch`);
} else {
core.info(`Unable to create a PR because of timeouts. Create PR manually from the branch ${newBranchName} that was already created in the upstream`);
}
Expand Down Expand Up @@ -15806,7 +15837,7 @@ function getBranchName(commitId, branchName) {
* @param {String} repo repo name
* @param {String} branchesString comma-separated list of branches
* @param {String} defaultBranch name of the repo default branch
* @returns {String}
* @returns {Array<Object, Object>} first index is object with branches that user wants to operate on and that are in remote, next index has all remote branches
*/
async function getBranchesList(octokit, owner, repo, branchesString, defaultBranch) {
core.info('Getting list of branches the action should operate on');
Expand All @@ -15816,9 +15847,9 @@ async function getBranchesList(octokit, owner, repo, branchesString, defaultBran
//branches not available an remote will not be included
const filteredBranches = filterOutMissingBranches(branchesString, branchesFromRemote, defaultBranch);

core.info(`These is a final list of branches action will operate on: ${JSON.stringify(filteredBranches, null, 2)}`);
core.info(`This is a final list of branches action will operate on: ${JSON.stringify(filteredBranches, null, 2)}`);

return filteredBranches;
return [filteredBranches, branchesFromRemote];
}

/**
Expand Down
18 changes: 16 additions & 2 deletions lib/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,25 @@ async function getBranchesLocal(git) {
async function push(branchName, message, committerUsername, committerEmail, git) {
if (core.isDebug()) require('debug').enable('simple-git');
core.info('Pushing changes to remote');

await git.addConfig('user.name', committerUsername);
await git.addConfig('user.email', committerEmail);
await git.commit(message);
await git.push(['-u', REMOTE, branchName]);
try {
await git.push(['-u', REMOTE, branchName]);
} catch (error) {
core.info('Not able to push:', error);
try {
await git.pull([REMOTE, branchName]);
} catch (error) {
core.info('Not able to pull:', error);
await git.merge(['-X', 'ours', branchName]);
core.debug('DEBUG: Git status after merge');
core.debug(JSON.stringify(await git.status(), null, 2));
await git.add('./*');
await git.commit(message);
await git.push(['-u', REMOTE, branchName]);
}
}
}

async function areFilesChanged(git) {
Expand Down
35 changes: 26 additions & 9 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async function run() {
const commitMessage = core.getInput('commit_message');
const branches = core.getInput('branches');
const destination = core.getInput('destination');
const customBranchName = core.getInput('bot_branch_name');
const repoNameManual = eventPayload.inputs && eventPayload.inputs.repo_name;

const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
Expand Down Expand Up @@ -111,7 +112,7 @@ async function run() {
/*
* 4a. Creating folder where repo will be cloned and initializing git client
*/
const dir = path.join(process.cwd(), './clones', repo.name);
const dir = path.join(process.cwd(), './clones', `${repo.name }-${ Math.random().toString(36).substring(7)}`);
await mkdir(dir, {recursive: true});
const git = simpleGit({baseDir: dir});

Expand All @@ -129,15 +130,15 @@ async function run() {
* Should it be just default one or the ones provided by the user
*/
const branchesToOperateOn = await getBranchesList(myOctokit, owner, repo.name, branches, defaultBranch);
if (!branchesToOperateOn.length) {
if (!branchesToOperateOn[0].length) {
core.info('Repo has no branches that the action could operate on');
continue;
}

/*
* 4d. Per branch operation starts
*/
for (const branch of branchesToOperateOn) {
for (const branch of branchesToOperateOn[0]) {
/*
* 4da. Checkout branch in cloned repo
*/
Expand All @@ -147,8 +148,15 @@ async function run() {
/*
* 4db. Creating new branch in cloned repo
*/
const newBranchName = getBranchName(commitId, branchName);
await createBranch(newBranchName, git);
const newBranchName = customBranchName || getBranchName(commitId, branchName);
const wasBranchThereAlready = branchesToOperateOn[1].some(branch => branch.name === newBranchName);
core.debug(`DEBUG: was branch ${newBranchName} there already in the repository? - ${wasBranchThereAlready}`);
core.debug(JSON.stringify(branchesToOperateOn, null, 2));
if (wasBranchThereAlready) {
await checkoutBranch(newBranchName, git);
} else {
await createBranch(newBranchName, git);
}

/*
* 4dc. Files replication/update or deletion
Expand All @@ -167,14 +175,23 @@ async function run() {
await push(newBranchName, commitMessage, committerUsername, committerEmail, git);

/*
* 4fe. Opening a PR
*/
const pullRequestUrl = await createPr(myOctokit, newBranchName, repo.id, commitMessage, branchName);

* 4fe. Opening a PR. Doing in try/catch as it is not always failing because of timeouts, maybe branch already has a PR
* we need to try to create a PR cause there can be branch but someone closed PR, so branch is there but PR not
*/
let pullRequestUrl;
try {
pullRequestUrl = await createPr(myOctokit, newBranchName, repo.id, commitMessage, branchName);
} catch (error) {
if (wasBranchThereAlready)
core.info(`PR creation for ${repo.name} failed as the branch was there already. Insted only push was performed to existing ${newBranchName} branch`, error);
}

core.endGroup();

if (pullRequestUrl) {
core.info(`Workflow finished with success and PR for ${repo.name} is created -> ${pullRequestUrl}`);
} else if (!pullRequestUrl && wasBranchThereAlready) {
core.info(`Workflow finished without PR creation for ${repo.name}. Insted push was performed to existing ${newBranchName} branch`);
} else {
core.info(`Unable to create a PR because of timeouts. Create PR manually from the branch ${newBranchName} that was already created in the upstream`);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ function getBranchName(commitId, branchName) {
* @param {String} repo repo name
* @param {String} branchesString comma-separated list of branches
* @param {String} defaultBranch name of the repo default branch
* @returns {String}
* @returns {Array<Object, Object>} first index is object with branches that user wants to operate on and that are in remote, next index has all remote branches
*/
async function getBranchesList(octokit, owner, repo, branchesString, defaultBranch) {
core.info('Getting list of branches the action should operate on');
Expand All @@ -269,9 +269,9 @@ async function getBranchesList(octokit, owner, repo, branchesString, defaultBran
//branches not available an remote will not be included
const filteredBranches = filterOutMissingBranches(branchesString, branchesFromRemote, defaultBranch);

core.info(`These is a final list of branches action will operate on: ${JSON.stringify(filteredBranches, null, 2)}`);
core.info(`This is a final list of branches action will operate on: ${JSON.stringify(filteredBranches, null, 2)}`);

return filteredBranches;
return [filteredBranches, branchesFromRemote];
}

/**
Expand Down

0 comments on commit beecbe8

Please sign in to comment.