Compare commits

...

55 Commits

Author SHA1 Message Date
1e6b4d1790 Merge pull request #145 from peter-evans/dev
Create pull requests in the parent repository of a checked out fork
2020-03-29 21:02:48 +09:00
a70c6ebe2a Merge pull request #144 from jderusse/fork
Allow custom repository when pushin in fork
2020-03-29 20:47:00 +09:00
5bd05538d0 Update src/cpr/create_or_update_pull_request.py
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 13:36:48 +02:00
26bc40eea1 Add link in TOC 2020-03-29 12:44:57 +02:00
6bb0e7771c Apply suggestions from code review
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 12:43:21 +02:00
4c347a4514 Update docs/concepts-guidelines.md
Co-Authored-By: Tobias Nyholm <tobias.nyholm@gmail.com>
2020-03-28 16:42:57 +01:00
cff2c3381d Add doc 2020-03-28 13:27:58 +01:00
e48dab0c1c Add PR creation from Fork 2020-03-28 13:10:12 +01:00
ac9f92d6e7 Merge pull request #141 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.3
2020-03-27 08:15:40 +09:00
3ff256ce08 Update dependency jest to v25.2.3 2020-03-26 20:38:32 +00:00
0bcb10560b Merge pull request #140 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.1
2020-03-26 19:03:52 +09:00
3c4b2793c1 Update dependency jest to v25.2.1 2020-03-26 09:43:37 +00:00
22870d7816 Merge pull request #139 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.0
2020-03-26 10:23:31 +09:00
e324a22ee1 Update dependency jest to v25.2.0 2020-03-25 18:18:56 +00:00
8b60386018 Merge pull request #135 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.22.0
2020-03-24 09:20:19 +09:00
6852d55922 Update dependency @zeit/ncc to v0.22.0 2020-03-23 22:31:46 +00:00
32e5bb80a5 Merge pull request #134 from peter-evans/dev
Update dependencies
2020-03-21 11:38:19 +09:00
619cf2115d Update dependencies due to security vulnerabilities 2020-03-21 11:31:34 +09:00
69008aa567 Update vendored dependencies 2020-03-21 11:29:37 +09:00
ae4278bf24 Update dependency PyGithub to v1.47 2020-03-21 11:23:42 +09:00
88da40fea7 Merge pull request #132 from peter-evans/dev
Update dependency GitPython to v3.1.0
2020-03-07 09:08:24 +09:00
694e068136 Update vendored dependencies 2020-03-07 08:57:59 +09:00
64c34f6885 Update README 2020-03-03 09:49:52 +09:00
52ada17960 Update dependency GitPython to v3.1.0 2020-02-25 13:12:00 +09:00
ce00b952cf Merge pull request #128 from peter-evans/dev
Unset and restore authorization extraheader only
2020-02-22 17:02:15 +09:00
0d42c285a3 Unset and restore authorization extraheader only 2020-02-22 16:56:42 +09:00
ea1eaf1734 Merge pull request #127 from peter-evans/dev
Unset and restore extraheader config option
2020-02-22 14:53:46 +09:00
d5c5ea3e20 Unset and restore extraheader config option 2020-02-22 14:08:54 +09:00
c7b64af0a4 Merge pull request #123 from peter-evans/dev
Authenticate with git extraheader
2020-02-18 19:49:13 +09:00
289fda9fea Authenticate with git extraheader 2020-02-18 19:35:15 +09:00
b021b9e27a Update vendored dependencies 2020-02-18 15:59:18 +09:00
c26314237b Update dependency GitPython to v3.0.8 2020-02-18 15:57:13 +09:00
ed7dd8d236 Update documentation 2020-02-14 20:10:27 +09:00
ca0e9d75fd Update documentation 2020-02-14 00:23:21 +09:00
87c27ee3eb Merge pull request #118 from peter-evans/dev
Assume python3 on PATH when running in a container
2020-02-14 00:07:03 +09:00
4beea725d3 Call python3 when running in a container 2020-02-13 17:37:08 +09:00
9d58699da5 Skip python setup when running in a container 2020-02-13 16:26:04 +09:00
bceebba814 Merge pull request #117 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.21.1
2020-02-13 09:58:46 +09:00
0b82710b2e Update dependency @zeit/ncc to v0.21.1 2020-02-12 16:54:11 +00:00
4aaaa0e760 Update README 2020-02-11 16:36:16 +09:00
918bfcb8a3 Update README 2020-02-11 16:28:16 +09:00
1ecfd1ae40 Merge pull request #116 from peter-evans/dev
Update dependency PyGithub to v1.46
2020-02-11 14:03:17 +09:00
4dd195d7c3 Update vendored dependencies 2020-02-11 13:51:59 +09:00
72b5f45bb4 Update dependency PyGithub to v1.46 2020-02-11 13:49:50 +09:00
5394814b39 Update documentation 2020-02-11 01:21:05 +09:00
83918398f5 Update documentation 2020-02-11 01:15:59 +09:00
b6a458d96a Merge pull request #114 from peter-evans/dev
Add support for ssh protocol
2020-02-10 23:06:03 +09:00
7b5ff6b642 Add support for ssh protocol 2020-02-10 22:52:00 +09:00
82eddd8828 Merge pull request #112 from peter-evans/dev
Skip python setup for alpine linux
2020-02-10 18:47:40 +09:00
6df2a462d1 Skip python setup for alpine linux 2020-02-10 09:53:43 +09:00
3689bd07d7 Merge pull request #110 from peter-evans/dev
Update dependency GitPython to v3.0.7
2020-02-08 16:45:18 +09:00
943f19ac64 Update vendored dependencies 2020-02-08 16:12:36 +09:00
e59d6c7fff Update dependency GitPython to v3.0.7 2020-02-08 16:09:50 +09:00
c65a4f39b3 Update documentation 2020-02-08 16:04:23 +09:00
2d18371789 Update documentation 2020-02-08 12:52:06 +09:00
44 changed files with 6863 additions and 249 deletions

17
.eslintrc.json Normal file
View File

@ -0,0 +1,17 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}

View File

@ -30,6 +30,7 @@ jobs:
project: Example Project
project-column: To do
branch: example-patches
request-to-parent: false
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"

View File

@ -18,7 +18,7 @@ Create Pull Request action will:
## Documentation
- [Concepts and guidelines](docs/concepts-guidelines.md)
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
- [Examples](docs/examples.md)
- [Updating from v1](docs/updating.md)
@ -37,8 +37,7 @@ You can also pin to a [specific release](https://github.com/peter-evans/create-p
With the exception of `token`, all inputs are **optional**. If not set, sensible default values will be used.
**Note**: If you want pull requests created by this action to trigger an `on: pull_request` workflow then you must use a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) instead of the default `GITHUB_TOKEN`.
See [this issue](https://github.com/peter-evans/create-pull-request/issues/48) for further details.
**Note**: If you want pull requests created by this action to trigger an `on: push` or `on: pull_request` workflow then you must use a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) instead of the default `GITHUB_TOKEN`. Alternatively, allow the action to [push using SSH](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#push-using-ssh-deploy-keys) by configuring a deploy key.
| Name | Description | Default |
| --- | --- | --- |
@ -57,6 +56,7 @@ See [this issue](https://github.com/peter-evans/create-pull-request/issues/48) f
| `project` | The name of the project for which a card should be created. Requires `project-column`. | |
| `project-column` | The name of the project column under which a card should be created. Requires `project`. | |
| `branch` | The branch name. See [Branch naming](#branch-naming) for details. | `create-pull-request/patch` |
| `request-to-parent` | Create the pull request in the parent repository of the checked out fork. | `false` |
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
| `branch-suffix` | The branch suffix type. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Branch naming](#branch-naming) for details. | |
@ -182,10 +182,12 @@ jobs:
labels: report, automated pr
assignees: peter-evans
reviewers: peter-evans
team-reviewers: owners, maintainers
milestone: 1
project: Example Project
project-column: To do
branch: example-patches
request-to-parent: false
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"

View File

@ -32,6 +32,9 @@ inputs:
description: 'The name of the project column under which a card should be created.'
branch:
description: 'The pull request branch name.'
request-to-parent:
description: 'Create the pull request in the parent repository of the checked out fork.'
default: false
base:
description: 'The pull request base branch.'
branch-suffix:
@ -43,5 +46,5 @@ runs:
using: 'node12'
main: 'dist/index.js'
branding:
icon: 'git-pull-request'
icon: 'git-pull-request'
color: 'gray-dark'

View File

@ -9,16 +9,20 @@ def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
def parse_github_repository(url):
# Parse the github repository from a URL
# e.g. peter-evans/create-pull-request
pattern = re.compile(r"^https://github.com/(.+/.+)$")
# Parse the protocol and github repository from a URL
# e.g. HTTPS, peter-evans/create-pull-request
https_pattern = re.compile(r"^https://github.com/(.+/.+)$")
ssh_pattern = re.compile(r"^git@github.com:(.+/.+).git$")
# Check we have a match
match = pattern.match(url)
if match is None:
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
match = https_pattern.match(url)
if match is not None:
return "HTTPS", match.group(1)
return match.group(1)
match = ssh_pattern.match(url)
if match is not None:
return "SSH", match.group(1)
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
def parse_display_name_email(display_name_email):

View File

@ -56,25 +56,37 @@ def create_or_update_pull_request(
team_reviewers,
project_name,
project_column_name,
request_to_parent,
):
if request_to_parent is None:
request_to_parent = False
else:
request_to_parent = request_to_parent.lower() in ['true', '1', 't', 'y', 'yes', 'on']
github_repo = head_repo = Github(github_token).get_repo(github_repository)
if request_to_parent:
github_repo = github_repo.parent
if github_repo is None:
raise ValueError("The checked out repository is not a fork. Input 'request-to-parent' should be set to false.")
head_branch = f"{head_repo.owner.login}:{branch}"
# Create the pull request
github_repo = Github(github_token).get_repo(github_repository)
try:
pull_request = github_repo.create_pull(
title=title, body=body, base=base, head=branch
title=title, body=body, base=base, head=head_branch
)
print(f"Created pull request #{pull_request.number} ({branch} => {base})")
print(f"Created pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
except GithubException as e:
if e.status == 422:
# A pull request exists for this branch and base
head_branch = "{}:{}".format(github_repository.split("/")[0], branch)
# Get the pull request
pull_request = github_repo.get_pulls(
state="open", base=base, head=head_branch
)[0]
# Update title and body
pull_request.as_issue().edit(title=title, body=body)
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
else:
print(str(e))
raise

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
""" Create Pull Request """
import base64
import common as cmn
import create_or_update_branch as coub
import create_or_update_pull_request as coupr
@ -31,11 +32,12 @@ def get_git_config_value(repo, name):
return None
def get_github_repository():
def get_repository_detail(repo):
remote_origin_url = get_git_config_value(repo, "remote.origin.url")
if remote_origin_url is None:
raise ValueError("Failed to fetch 'remote.origin.url' from git config")
return cmn.parse_github_repository(remote_origin_url)
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
return remote_origin_url, protocol, github_repository
def git_user_config_is_set(repo):
@ -115,9 +117,21 @@ repo = Repo(path)
# Determine the GitHub repository from git config
# This will be the target repository for the pull request
github_repository = get_github_repository()
repo_url, protocol, github_repository = get_repository_detail(repo)
print(f"Target repository set to {github_repository}")
if protocol == "HTTPS":
print(f"::debug::Using HTTPS protocol")
# Encode and configure the basic credential for HTTPS access
basic_credential = base64.b64encode(
f"x-access-token:{github_token}".encode("utf-8")
).decode("utf-8")
# Mask the basic credential in logs and debug output
print(f"::add-mask::{basic_credential}")
repo.git.set_persistent_git_options(
c=f"http.https://github.com/.extraheader=AUTHORIZATION: basic {basic_credential}"
)
# Determine if the checked out ref is a valid base for a pull request
# The action needs the checked out HEAD ref to be a branch
# This check will fail in the following cases:
@ -173,9 +187,6 @@ except ValueError as e:
print(f"::error::{e} " + "Unable to continue. Exiting.")
sys.exit(1)
# Set the repository URL
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
# Create or update the pull request branch
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
@ -213,4 +224,5 @@ if result["action"] in ["created", "updated"]:
os.environ.get("CPR_TEAM_REVIEWERS"),
os.environ.get("CPR_PROJECT_NAME"),
os.environ.get("CPR_PROJECT_COLUMN_NAME"),
os.environ.get("CPR_REQUEST_TO_PARENT"),
)

2
dist/cpr/requirements.txt vendored Normal file
View File

@ -0,0 +1,2 @@
GitPython==3.1.0
PyGithub==1.47

View File

@ -10,9 +10,16 @@ def test_get_random_string():
def test_parse_github_repository_success():
repository = cmn.parse_github_repository(
protocol, repository = cmn.parse_github_repository(
"https://github.com/peter-evans/create-pull-request"
)
assert protocol == "HTTPS"
assert repository == "peter-evans/create-pull-request"
protocol, repository = cmn.parse_github_repository(
"git@github.com:peter-evans/create-pull-request.git"
)
assert protocol == "SSH"
assert repository == "peter-evans/create-pull-request"

395
dist/index.js vendored
View File

@ -34,7 +34,7 @@ module.exports =
/******/ // the startup function
/******/ function startup() {
/******/ // Load entry module and return exports
/******/ return __webpack_require__(104);
/******/ return __webpack_require__(676);
/******/ };
/******/
/******/ // run startup
@ -973,21 +973,6 @@ module.exports = util.assign(
module.exports = require("tls");
/***/ }),
/***/ 58:
/***/ (function(module, __unusedexports, __webpack_require__) {
// Unique ID creation requires a high quality random # generator. In node.js
// this is pretty straight-forward - we use the crypto API.
var crypto = __webpack_require__(417);
module.exports = function nodeRNG() {
return crypto.randomBytes(16);
};
/***/ }),
/***/ 87:
@ -998,92 +983,6 @@ module.exports = require("os");
/***/ }),
/***/ 104:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const { inspect } = __webpack_require__(669);
const core = __webpack_require__(470);
const exec = __webpack_require__(986);
const setupPython = __webpack_require__(139);
async function run() {
try {
// Allows ncc to find assets to be included in the distribution
const src = __webpack_require__.ab + "src";
core.debug(`src: ${src}`);
// Setup Python from the tool cache
setupPython("3.8.x", "x64");
// Install requirements
await exec.exec("pip", [
"install",
"--requirement",
`${src}/requirements.txt`,
"--no-index",
`--find-links=${__dirname}/vendor`
]);
// Fetch action inputs
const inputs = {
token: core.getInput("token"),
path: core.getInput("path"),
commitMessage: core.getInput("commit-message"),
committer: core.getInput("committer"),
author: core.getInput("author"),
title: core.getInput("title"),
body: core.getInput("body"),
labels: core.getInput("labels"),
assignees: core.getInput("assignees"),
reviewers: core.getInput("reviewers"),
teamReviewers: core.getInput("team-reviewers"),
milestone: core.getInput("milestone"),
project: core.getInput("project"),
projectColumn: core.getInput("project-column"),
branch: core.getInput("branch"),
base: core.getInput("base"),
branchSuffix: core.getInput("branch-suffix"),
};
core.debug(`Inputs: ${inspect(inputs)}`);
// Set environment variables from inputs.
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
if (inputs.path) process.env.CPR_PATH = inputs.path;
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
if (inputs.title) process.env.CPR_TITLE = inputs.title;
if (inputs.body) process.env.CPR_BODY = inputs.body;
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
if (inputs.base) process.env.CPR_BASE = inputs.base;
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
// Execute python script
await exec.exec("python", [`${src}/create_pull_request.py`]);
} catch (error) {
core.setFailed(error.message);
}
}
run();
/***/ }),
/***/ 129:
/***/ (function(module) {
module.exports = require("child_process");
/***/ }),
/***/ 139:
/***/ (function(module, __unusedexports, __webpack_require__) {
const core = __webpack_require__(470);
@ -1140,6 +1039,28 @@ let setupPython = function(versionSpec, arch) {
module.exports = setupPython;
/***/ }),
/***/ 129:
/***/ (function(module) {
module.exports = require("child_process");
/***/ }),
/***/ 139:
/***/ (function(module, __unusedexports, __webpack_require__) {
// Unique ID creation requires a high quality random # generator. In node.js
// this is pretty straight-forward - we use the crypto API.
var crypto = __webpack_require__(417);
module.exports = function nodeRNG() {
return crypto.randomBytes(16);
};
/***/ }),
/***/ 141:
@ -1395,6 +1316,43 @@ if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
exports.debug = debug; // for test
/***/ }),
/***/ 160:
/***/ (function(module, __unusedexports, __webpack_require__) {
"use strict";
const fs = __webpack_require__(747);
let isDocker;
function hasDockerEnv() {
try {
fs.statSync('/.dockerenv');
return true;
} catch (_) {
return false;
}
}
function hasDockerCGroup() {
try {
return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
} catch (_) {
return false;
}
}
module.exports = () => {
if (isDocker === undefined) {
isDocker = hasDockerEnv() || hasDockerCGroup();
}
return isDocker;
};
/***/ }),
/***/ 211:
@ -4247,6 +4205,237 @@ function isUnixExecutable(stats) {
}
//# sourceMappingURL=io-util.js.map
/***/ }),
/***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const { inspect } = __webpack_require__(669);
const isDocker = __webpack_require__(160);
const core = __webpack_require__(470);
const exec = __webpack_require__(986);
const setupPython = __webpack_require__(104);
const {
getRepoPath,
getAndUnsetConfigOption,
addConfigOption
} = __webpack_require__(718);
const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
async function run() {
try {
// Allows ncc to find assets to be included in the distribution
const cpr = __webpack_require__.ab + "cpr";
core.debug(`cpr: ${cpr}`);
// Determine how to access python and pip
const { pip, python } = (function() {
if (isDocker()) {
core.info("Running inside a Docker container");
// Python 3 assumed to be installed and on the PATH
return {
pip: "pip3",
python: "python3"
};
} else {
// Setup Python from the tool cache
setupPython("3.x", "x64");
return {
pip: "pip",
python: "python"
};
}
})();
// Install requirements
await exec.exec(pip, [
"install",
"--requirement",
`${cpr}/requirements.txt`,
"--no-index",
`--find-links=${__dirname}/vendor`
]);
// Fetch action inputs
const inputs = {
token: core.getInput("token"),
path: core.getInput("path"),
commitMessage: core.getInput("commit-message"),
committer: core.getInput("committer"),
author: core.getInput("author"),
title: core.getInput("title"),
body: core.getInput("body"),
labels: core.getInput("labels"),
assignees: core.getInput("assignees"),
reviewers: core.getInput("reviewers"),
teamReviewers: core.getInput("team-reviewers"),
milestone: core.getInput("milestone"),
project: core.getInput("project"),
projectColumn: core.getInput("project-column"),
branch: core.getInput("branch"),
request_to_parent: core.getInput("request-to-parent"),
base: core.getInput("base"),
branchSuffix: core.getInput("branch-suffix")
};
core.debug(`Inputs: ${inspect(inputs)}`);
// Set environment variables from inputs.
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
if (inputs.path) process.env.CPR_PATH = inputs.path;
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
if (inputs.title) process.env.CPR_TITLE = inputs.title;
if (inputs.body) process.env.CPR_BODY = inputs.body;
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
if (inputs.request_to_parent) process.env.CPR_REQUEST_TO_PARENT = inputs.request_to_parent;
if (inputs.base) process.env.CPR_BASE = inputs.base;
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
// Get the repository path
var repoPath = getRepoPath(inputs.path);
// Get the extraheader config option if it exists
var extraHeaderOption = await getAndUnsetConfigOption(
repoPath,
EXTRAHEADER_OPTION,
EXTRAHEADER_VALUE_REGEX
);
// Execute create pull request
await exec.exec(python, [`${cpr}/create_pull_request.py`]);
} catch (error) {
core.setFailed(error.message);
} finally {
// Restore the extraheader config option
if (extraHeaderOption) {
if (
await addConfigOption(
repoPath,
EXTRAHEADER_OPTION,
extraHeaderOption.value
)
)
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
}
}
}
run();
/***/ }),
/***/ 718:
/***/ (function(module, __unusedexports, __webpack_require__) {
const core = __webpack_require__(470);
const exec = __webpack_require__(986);
const path = __webpack_require__(622);
function getRepoPath(relativePath) {
let githubWorkspacePath = process.env["GITHUB_WORKSPACE"];
if (!githubWorkspacePath) {
throw new Error("GITHUB_WORKSPACE not defined");
}
githubWorkspacePath = path.resolve(githubWorkspacePath);
core.debug(`githubWorkspacePath: ${githubWorkspacePath}`);
repoPath = githubWorkspacePath;
if (relativePath) repoPath = path.resolve(repoPath, relativePath);
core.debug(`repoPath: ${repoPath}`);
return repoPath;
}
async function execGit(repoPath, args, ignoreReturnCode = false) {
const stdout = [];
const options = {
cwd: repoPath,
ignoreReturnCode: ignoreReturnCode,
listeners: {
stdout: data => {
stdout.push(data.toString());
}
}
};
var result = {};
result.exitCode = await exec.exec("git", args, options);
result.stdout = stdout.join("");
return result;
}
async function addConfigOption(repoPath, name, value) {
const result = await execGit(
repoPath,
["config", "--local", "--add", name, value],
true
);
return result.exitCode === 0;
}
async function unsetConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--unset", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function configOptionExists(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--name-only", "--get-regexp", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function getConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--get-regexp", name, valueRegex],
true
);
const option = result.stdout.trim().split(`${name} `);
return {
name: name,
value: option[1]
}
}
async function getAndUnsetConfigOption(repoPath, name, valueRegex=".") {
if (await configOptionExists(repoPath, name, valueRegex)) {
const option = await getConfigOption(repoPath, name, valueRegex);
if (await unsetConfigOption(repoPath, name, valueRegex)) {
core.debug(`Unset config option '${name}'`);
return option;
}
}
return null;
}
module.exports = {
getRepoPath,
execGit,
addConfigOption,
unsetConfigOption,
configOptionExists,
getConfigOption,
getAndUnsetConfigOption
};
/***/ }),
/***/ 722:
@ -4679,7 +4868,7 @@ module.exports = require("zlib");
/***/ 826:
/***/ (function(module, __unusedexports, __webpack_require__) {
var rng = __webpack_require__(58);
var rng = __webpack_require__(139);
var bytesToUuid = __webpack_require__(722);
function v4(options, buf, offset) {

View File

@ -1,2 +0,0 @@
GitPython==3.0.5
PyGithub==1.45

View File

@ -1,52 +0,0 @@
const core = require("@actions/core");
const tc = require("@actions/tool-cache");
const path = require("path");
const semver = require("semver");
/**
* Setup for Python from the GitHub Actions tool cache
* Converted from https://github.com/actions/setup-python
*
* @param {string} versionSpec version of Python
* @param {string} arch architecture (x64|x32)
*/
let setupPython = function(versionSpec, arch) {
return new Promise((resolve, reject) => {
const IS_WINDOWS = process.platform === "win32";
// Find the version of Python we want in the tool cache
const installDir = tc.find("Python", versionSpec, arch);
core.debug(`installDir: ${installDir}`);
// Set paths
core.exportVariable("pythonLocation", installDir);
core.addPath(installDir);
if (IS_WINDOWS) {
core.addPath(path.join(installDir, "Scripts"));
} else {
core.addPath(path.join(installDir, "bin"));
}
if (IS_WINDOWS) {
// Add --user directory
// `installDir` from tool cache should look like $AGENT_TOOLSDIRECTORY/Python/<semantic version>/x64/
// So if `findLocalTool` succeeded above, we must have a conformant `installDir`
const version = path.basename(path.dirname(installDir));
const major = semver.major(version);
const minor = semver.minor(version);
const userScriptsDir = path.join(
process.env["APPDATA"] || "",
"Python",
`Python${major}${minor}`,
"Scripts"
);
core.addPath(userScriptsDir);
}
// On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
resolve();
});
};
module.exports = setupPython;

Binary file not shown.

BIN
dist/vendor/GitPython-3.1.0.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
dist/vendor/PyGithub-1.47.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
dist/vendor/idna-2.9.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
dist/vendor/requests-2.23.0.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
dist/vendor/smmap-3.0.1.tar.gz vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/vendor/wrapt-1.12.1.tar.gz vendored Normal file

Binary file not shown.

View File

@ -1,6 +1,6 @@
# Concepts and guidelines
# Concepts, guidelines and advanced usage
This document covers terminology, how the action works, and general usage guidelines.
This document covers terminology, how the action works, general usage guidelines, and advanced usage.
- [Terminology](#terminology)
- [Events and checkout](#events-and-checkout)
@ -9,8 +9,13 @@ This document covers terminology, how the action works, and general usage guidel
- [Providing a consistent base](#providing-a-consistent-base)
- [Pull request events](#pull-request-events)
- [Restrictions on forked repositories](#restrictions-on-forked-repositories)
- [Tag push events](#tag-push-events)
- [Security](#security)
- [Advanced usage](#advanced-usage)
- [Creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository)
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
- [Running in a container](#running-in-a-container)
- [Creating pull requests on tag push](#creating-pull-requests-on-tag-push)
## Terminology
@ -108,7 +113,157 @@ jobs:
if: github.event.pull_request.head.repo.full_name == github.repository
```
### Tag push events
### Security
From a security perspective it's good practice to fork third-party actions, review the code, and use your fork of the action in workflows.
By using third-party actions directly the risk exists that it could be modified to do something malicious, such as capturing secrets.
This action uses [ncc](https://github.com/zeit/ncc) to compile the Node.js code and dependencies into a single file.
Python dependencies are vendored and committed to the repository [here](https://github.com/peter-evans/create-pull-request/tree/master/dist/vendor).
No dependencies are downloaded during the action execution.
Vendored Python dependencies can be reviewed by rebuilding the [dist](https://github.com/peter-evans/create-pull-request/tree/master/dist) directory and redownloading dependencies.
The following commands require Node and Python 3.
```
npm install
npm run clean
npm run package
```
The `dist` directory should be rebuilt leaving no git diff.
## Advanced usage
### Creating pull requests in a remote repository
Checking out a branch from a different repository from where the workflow is executing will make *that repository* the target for the created pull request. In this case, a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) is required.
```yml
- uses: actions/checkout@v2
with:
token: ${{ secrets.PAT }}
repository: owner/repo
# Make changes to pull request here
- uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.PAT }}
```
### Push using SSH (deploy keys)
[Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be set per repository and so are arguably more secure than using a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
Allowing the action to push with a configured deploy key will trigger `on: push` workflows. This makes it an alternative to using a PAT to trigger checks for pull requests.
How to use SSH (deploy keys) with create-pull-request action:
1. [Create a new SSH key pair](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key) for your repository. Do not set a passphrase.
2. Copy the contents of the public key (.pub file) to a new repository [deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) and check the box to "Allow write access."
3. Add a secret to the repository containing the entire contents of the private key.
4. As shown in the example steps below, use the [`webfactory/ssh-agent`](https://github.com/webfactory/ssh-agent) action to install the private key and clone your repository. Remember to checkout the `base` of your pull request if it's not the default branch, e.g. `git checkout my-branch`.
```yml
steps:
- uses: webfactory/ssh-agent@v0.2.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Checkout via SSH
run: git clone git@github.com:peter-evans/create-pull-request.git .
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
### Push pull request branches to a fork
To enforce security, you can use a dedicated user using [machine account](https://help.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements).
This user has no access to the main repository, it will use their own fork to push code and create the pull request.
1. Create a new github user, then login with this user.
2. fork the repository.
3. create a [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
4. logout and go back to your main user.
5. Add a secret to the repository containing the above PAT.
6. As shown in the example below, switch the git remote to the fork's url after checkout and set the action input `request-on-parent` to `true`.
```yaml
- uses: actions/checkout@v2
- run: |
git config user.password ${{ secrets.PAT }}
git remote set-url origin https://github.com/bot-user/fork-project
git fetch --unshallow -p origin
# Make changes to pull request here
- uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.PAT }}
request-on-parent: true
```
### Running in a container
This action can be run inside a container by installing the action's dependencies either in the Docker image itself, or during the workflow.
The action requires `python3`, `pip3` and `git` to be installed and on the `PATH`.
Note that `actions/checkout` requires Git 2.18 or higher to be installed, otherwise it will just download the source of the repository instead of cloning it.
**Alpine container example:**
```yml
jobs:
createPullRequestAlpine:
runs-on: ubuntu-latest
container:
image: alpine
steps:
- name: Install dependencies
run: apk --no-cache add git python3
- uses: actions/checkout@v2
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
**Ubuntu container example:**
```yml
jobs:
createPullRequestAlpine:
runs-on: ubuntu-latest
container:
image: ubuntu
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y software-properties-common
add-apt-repository -y ppa:git-core/ppa
apt-get install -y python3 python3-pip git
- uses: actions/checkout@v2
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
### Creating pull requests on tag push
An `on: push` workflow will also trigger when tags are pushed.
During these events, the `actions/checkout` action will check out the `ref/tags/<tag>` git ref by default.
@ -134,8 +289,7 @@ jobs:
git checkout -b temp-${GITHUB_REF:10}
git push --set-upstream origin temp-${GITHUB_REF:10}
- name: Create changes to pull request
run: <create changes here>
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
@ -164,31 +318,10 @@ jobs:
with:
ref: master
- name: Create changes to pull request
run: <create changes here>
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
### Security
From a security perspective it's good practice to fork third-party actions, review the code, and use your fork of the action in workflows.
By using third-party actions directly the risk exists that it could be modified to do something malicious, such as capturing secrets.
This action uses [ncc](https://github.com/zeit/ncc) to compile the Node.js code and dependencies into a single file.
Python dependencies are vendored and committed to the repository [here](https://github.com/peter-evans/create-pull-request/tree/master/dist/vendor).
No dependencies are downloaded during the action execution.
Vendored Python dependencies can be reviewed by rebuilding the [dist](https://github.com/peter-evans/create-pull-request/tree/master/dist) directory and redownloading dependencies.
The following commands require Node and Python 3.
```
npm install
npm run clean
npm run package
```
The `dist` directory should be rebuilt leaving no git diff.

View File

@ -2,6 +2,7 @@
- [Use case: Create a pull request to update X on push](#use-case-create-a-pull-request-to-update-x-on-push)
- [Update project authors](#update-project-authors)
- [Keep a branch up-to-date with another](#keep-a-branch-up-to-date-with-another)
- [Use case: Create a pull request to update X periodically](#use-case-create-a-pull-request-to-update-x-periodically)
- [Update NPM dependencies](#update-npm-dependencies)
- [Update SwaggerUI for GitHub Pages](#update-swaggerui-for-github-pages)
@ -51,6 +52,36 @@ jobs:
branch: update-authors
```
### Keep a branch up-to-date with another
This is a use case where a branch should be kept up to date with another by opening a pull request to update it. The pull request should then be updated with new changes until it is merged or closed.
In this example scenario, a branch called `production` should be updated via pull request to keep it in sync with `master`. Merging the pull request is effectively promoting those changes to production.
```yml
name: Create production promotion pull request
on:
push:
branches:
- master
jobs:
productionPromotion:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: production
- name: Reset promotion branch
run: |
git fetch origin master:master
git reset --hard master
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: production-promotion
```
## Use case: Create a pull request to update X periodically
This pattern will work well for updating any kind of static content from an external source. The workflow executes on a schedule and raises a pull request when there are changes.

3
jest.config.js Normal file
View File

@ -0,0 +1,3 @@
process.env = Object.assign(process.env, {
GITHUB_WORKSPACE: __dirname
});

5975
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,10 @@
"main": "index.js",
"scripts": {
"clean": "rm -rf dist",
"build": "ncc build index.js -o dist",
"vendor-deps": "pip download -r src/requirements.txt --no-binary=:all: -d dist/vendor",
"lint": "eslint src/index.js",
"test": "eslint src/index.js && jest",
"build": "ncc build src/index.js -o dist",
"vendor-deps": "pip download -r src/cpr/requirements.txt --no-binary=:all: -d dist/vendor",
"package": "npm run build && npm run vendor-deps"
},
"repository": {
@ -23,9 +25,12 @@
"dependencies": {
"@actions/core": "^1.1.1",
"@actions/exec": "^1.0.1",
"@actions/tool-cache": "^1.1.2"
"@actions/tool-cache": "^1.1.2",
"is-docker": "^2.0.0"
},
"devDependencies": {
"@zeit/ncc": "0.21.0"
"@zeit/ncc": "0.22.0",
"eslint": "6.8.0",
"jest": "25.2.3"
}
}

View File

@ -9,16 +9,20 @@ def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
def parse_github_repository(url):
# Parse the github repository from a URL
# e.g. peter-evans/create-pull-request
pattern = re.compile(r"^https://github.com/(.+/.+)$")
# Parse the protocol and github repository from a URL
# e.g. HTTPS, peter-evans/create-pull-request
https_pattern = re.compile(r"^https://github.com/(.+/.+)$")
ssh_pattern = re.compile(r"^git@github.com:(.+/.+).git$")
# Check we have a match
match = pattern.match(url)
if match is None:
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
match = https_pattern.match(url)
if match is not None:
return "HTTPS", match.group(1)
return match.group(1)
match = ssh_pattern.match(url)
if match is not None:
return "SSH", match.group(1)
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
def parse_display_name_email(display_name_email):

View File

@ -56,25 +56,37 @@ def create_or_update_pull_request(
team_reviewers,
project_name,
project_column_name,
request_to_parent,
):
if request_to_parent is None:
request_to_parent = False
else:
request_to_parent = request_to_parent.lower() in ['true', '1', 't', 'y', 'yes', 'on']
github_repo = head_repo = Github(github_token).get_repo(github_repository)
if request_to_parent:
github_repo = github_repo.parent
if github_repo is None:
raise ValueError("The checked out repository is not a fork. Input 'request-to-parent' should be set to false.")
head_branch = f"{head_repo.owner.login}:{branch}"
# Create the pull request
github_repo = Github(github_token).get_repo(github_repository)
try:
pull_request = github_repo.create_pull(
title=title, body=body, base=base, head=branch
title=title, body=body, base=base, head=head_branch
)
print(f"Created pull request #{pull_request.number} ({branch} => {base})")
print(f"Created pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
except GithubException as e:
if e.status == 422:
# A pull request exists for this branch and base
head_branch = "{}:{}".format(github_repository.split("/")[0], branch)
# Get the pull request
pull_request = github_repo.get_pulls(
state="open", base=base, head=head_branch
)[0]
# Update title and body
pull_request.as_issue().edit(title=title, body=body)
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
else:
print(str(e))
raise

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
""" Create Pull Request """
import base64
import common as cmn
import create_or_update_branch as coub
import create_or_update_pull_request as coupr
@ -31,11 +32,12 @@ def get_git_config_value(repo, name):
return None
def get_github_repository():
def get_repository_detail(repo):
remote_origin_url = get_git_config_value(repo, "remote.origin.url")
if remote_origin_url is None:
raise ValueError("Failed to fetch 'remote.origin.url' from git config")
return cmn.parse_github_repository(remote_origin_url)
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
return remote_origin_url, protocol, github_repository
def git_user_config_is_set(repo):
@ -115,9 +117,21 @@ repo = Repo(path)
# Determine the GitHub repository from git config
# This will be the target repository for the pull request
github_repository = get_github_repository()
repo_url, protocol, github_repository = get_repository_detail(repo)
print(f"Target repository set to {github_repository}")
if protocol == "HTTPS":
print(f"::debug::Using HTTPS protocol")
# Encode and configure the basic credential for HTTPS access
basic_credential = base64.b64encode(
f"x-access-token:{github_token}".encode("utf-8")
).decode("utf-8")
# Mask the basic credential in logs and debug output
print(f"::add-mask::{basic_credential}")
repo.git.set_persistent_git_options(
c=f"http.https://github.com/.extraheader=AUTHORIZATION: basic {basic_credential}"
)
# Determine if the checked out ref is a valid base for a pull request
# The action needs the checked out HEAD ref to be a branch
# This check will fail in the following cases:
@ -173,15 +187,12 @@ except ValueError as e:
print(f"::error::{e} " + "Unable to continue. Exiting.")
sys.exit(1)
# Set the repository URL
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
# Create or update the pull request branch
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
if result["action"] in ["created", "updated"]:
# The branch was created or updated
print(f"Pushing pull request branch to 'origin/{branch}'")
print(f"Pushing pull request branch to '{repo.full_name}/{branch}'")
repo.git.push("--force", repo_url, f"HEAD:refs/heads/{branch}")
# Set the base. It would have been 'None' if not specified as an input
@ -213,4 +224,5 @@ if result["action"] in ["created", "updated"]:
os.environ.get("CPR_TEAM_REVIEWERS"),
os.environ.get("CPR_PROJECT_NAME"),
os.environ.get("CPR_PROJECT_COLUMN_NAME"),
os.environ.get("CPR_REQUEST_TO_PARENT"),
)

2
src/cpr/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
GitPython==3.1.0
PyGithub==1.47

View File

@ -10,9 +10,16 @@ def test_get_random_string():
def test_parse_github_repository_success():
repository = cmn.parse_github_repository(
protocol, repository = cmn.parse_github_repository(
"https://github.com/peter-evans/create-pull-request"
)
assert protocol == "HTTPS"
assert repository == "peter-evans/create-pull-request"
protocol, repository = cmn.parse_github_repository(
"git@github.com:peter-evans/create-pull-request.git"
)
assert protocol == "SSH"
assert repository == "peter-evans/create-pull-request"

97
src/git.js Normal file
View File

@ -0,0 +1,97 @@
const core = require("@actions/core");
const exec = require("@actions/exec");
const path = require("path");
function getRepoPath(relativePath) {
let githubWorkspacePath = process.env["GITHUB_WORKSPACE"];
if (!githubWorkspacePath) {
throw new Error("GITHUB_WORKSPACE not defined");
}
githubWorkspacePath = path.resolve(githubWorkspacePath);
core.debug(`githubWorkspacePath: ${githubWorkspacePath}`);
repoPath = githubWorkspacePath;
if (relativePath) repoPath = path.resolve(repoPath, relativePath);
core.debug(`repoPath: ${repoPath}`);
return repoPath;
}
async function execGit(repoPath, args, ignoreReturnCode = false) {
const stdout = [];
const options = {
cwd: repoPath,
ignoreReturnCode: ignoreReturnCode,
listeners: {
stdout: data => {
stdout.push(data.toString());
}
}
};
var result = {};
result.exitCode = await exec.exec("git", args, options);
result.stdout = stdout.join("");
return result;
}
async function addConfigOption(repoPath, name, value) {
const result = await execGit(
repoPath,
["config", "--local", "--add", name, value],
true
);
return result.exitCode === 0;
}
async function unsetConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--unset", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function configOptionExists(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--name-only", "--get-regexp", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function getConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--get-regexp", name, valueRegex],
true
);
const option = result.stdout.trim().split(`${name} `);
return {
name: name,
value: option[1]
}
}
async function getAndUnsetConfigOption(repoPath, name, valueRegex=".") {
if (await configOptionExists(repoPath, name, valueRegex)) {
const option = await getConfigOption(repoPath, name, valueRegex);
if (await unsetConfigOption(repoPath, name, valueRegex)) {
core.debug(`Unset config option '${name}'`);
return option;
}
}
return null;
}
module.exports = {
getRepoPath,
execGit,
addConfigOption,
unsetConfigOption,
configOptionExists,
getConfigOption,
getAndUnsetConfigOption
};

98
src/git.test.js Normal file
View File

@ -0,0 +1,98 @@
const path = require("path");
const {
getRepoPath,
execGit,
addConfigOption,
unsetConfigOption,
configOptionExists,
getConfigOption,
getAndUnsetConfigOption
} = require("./git");
test("getRepoPath", async () => {
expect(getRepoPath()).toEqual(process.env["GITHUB_WORKSPACE"]);
expect(getRepoPath("foo")).toEqual(
path.resolve(process.env["GITHUB_WORKSPACE"], "foo")
);
});
test("execGit", async () => {
const repoPath = getRepoPath();
const result = await execGit(
repoPath,
["config", "--local", "--name-only", "--get-regexp", "remote.origin.url"],
true
);
expect(result.exitCode).toEqual(0);
expect(result.stdout.trim()).toEqual("remote.origin.url");
});
test("add and unset config option", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.add.and.unset.config.option", "foo");
expect(add).toBeTruthy();
const unset = await unsetConfigOption(repoPath, "test.add.and.unset.config.option");
expect(unset).toBeTruthy();
});
test("add and unset config option with value regex", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.add.and.unset.config.option", "foo bar");
expect(add).toBeTruthy();
const unset = await unsetConfigOption(repoPath, "test.add.and.unset.config.option", "^foo");
expect(unset).toBeTruthy();
});
test("configOptionExists returns true", async () => {
const repoPath = getRepoPath();
const result = await configOptionExists(repoPath, "remote.origin.url");
expect(result).toBeTruthy();
});
test("configOptionExists returns false", async () => {
const repoPath = getRepoPath();
const result = await configOptionExists(repoPath, "this.key.does.not.exist");
expect(result).toBeFalsy();
});
test("get config option", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.get.config.option", "foo");
expect(add).toBeTruthy();
const option = await getConfigOption(repoPath, "test.get.config.option");
expect(option.value).toEqual("foo");
const unset = await unsetConfigOption(repoPath, "test.get.config.option");
expect(unset).toBeTruthy();
});
test("get config option with value regex", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.get.config.option", "foo bar");
expect(add).toBeTruthy();
const option = await getConfigOption(repoPath, "test.get.config.option", "^foo");
expect(option.value).toEqual("foo bar");
const unset = await unsetConfigOption(repoPath, "test.get.config.option", "^foo");
expect(unset).toBeTruthy();
});
test("get and unset config option is successful", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.get.and.unset.config.option", "foo");
expect(add).toBeTruthy();
const getAndUnset = await getAndUnsetConfigOption(repoPath, "test.get.and.unset.config.option");
expect(getAndUnset.value).toEqual("foo");
});
test("get and unset config option is successful with value regex", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.get.and.unset.config.option", "foo bar");
expect(add).toBeTruthy();
const getAndUnset = await getAndUnsetConfigOption(repoPath, "test.get.and.unset.config.option", "^foo");
expect(getAndUnset.value).toEqual("foo bar");
});
test("get and unset config option is unsuccessful", async () => {
const repoPath = getRepoPath();
const getAndUnset = await getAndUnsetConfigOption(repoPath, "this.key.does.not.exist");
expect(getAndUnset).toBeNull();
});

View File

@ -1,22 +1,47 @@
const { inspect } = require("util");
const isDocker = require("is-docker");
const core = require("@actions/core");
const exec = require("@actions/exec");
const setupPython = require("./src/setup-python");
const setupPython = require("./setup-python");
const {
getRepoPath,
getAndUnsetConfigOption,
addConfigOption
} = require("./git");
const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
async function run() {
try {
// Allows ncc to find assets to be included in the distribution
const src = __dirname + "/src";
core.debug(`src: ${src}`);
const cpr = __dirname + "/cpr";
core.debug(`cpr: ${cpr}`);
// Setup Python from the tool cache
setupPython("3.8.x", "x64");
// Determine how to access python and pip
const { pip, python } = (function() {
if (isDocker()) {
core.info("Running inside a Docker container");
// Python 3 assumed to be installed and on the PATH
return {
pip: "pip3",
python: "python3"
};
} else {
// Setup Python from the tool cache
setupPython("3.x", "x64");
return {
pip: "pip",
python: "python"
};
}
})();
// Install requirements
await exec.exec("pip", [
await exec.exec(pip, [
"install",
"--requirement",
`${src}/requirements.txt`,
`${cpr}/requirements.txt`,
"--no-index",
`--find-links=${__dirname}/vendor`
]);
@ -38,8 +63,9 @@ async function run() {
project: core.getInput("project"),
projectColumn: core.getInput("project-column"),
branch: core.getInput("branch"),
request_to_parent: core.getInput("request-to-parent"),
base: core.getInput("base"),
branchSuffix: core.getInput("branch-suffix"),
branchSuffix: core.getInput("branch-suffix")
};
core.debug(`Inputs: ${inspect(inputs)}`);
@ -59,13 +85,35 @@ async function run() {
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
if (inputs.request_to_parent) process.env.CPR_REQUEST_TO_PARENT = inputs.request_to_parent;
if (inputs.base) process.env.CPR_BASE = inputs.base;
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
// Execute python script
await exec.exec("python", [`${src}/create_pull_request.py`]);
// Get the repository path
var repoPath = getRepoPath(inputs.path);
// Get the extraheader config option if it exists
var extraHeaderOption = await getAndUnsetConfigOption(
repoPath,
EXTRAHEADER_OPTION,
EXTRAHEADER_VALUE_REGEX
);
// Execute create pull request
await exec.exec(python, [`${cpr}/create_pull_request.py`]);
} catch (error) {
core.setFailed(error.message);
} finally {
// Restore the extraheader config option
if (extraHeaderOption) {
if (
await addConfigOption(
repoPath,
EXTRAHEADER_OPTION,
extraHeaderOption.value
)
)
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
}
}
}

View File

@ -1,2 +0,0 @@
GitPython==3.0.5
PyGithub==1.45