Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e6b4d1790 | ||
|
|
a70c6ebe2a | ||
|
|
5bd05538d0 | ||
|
|
26bc40eea1 | ||
|
|
6bb0e7771c | ||
|
|
4c347a4514 | ||
|
|
cff2c3381d | ||
|
|
e48dab0c1c | ||
|
|
ac9f92d6e7 | ||
|
|
3ff256ce08 | ||
|
|
0bcb10560b | ||
|
|
3c4b2793c1 | ||
|
|
22870d7816 | ||
|
|
e324a22ee1 | ||
|
|
8b60386018 | ||
|
|
6852d55922 | ||
|
|
32e5bb80a5 | ||
|
|
619cf2115d | ||
|
|
69008aa567 | ||
|
|
ae4278bf24 | ||
|
|
88da40fea7 | ||
|
|
694e068136 | ||
|
|
64c34f6885 | ||
|
|
52ada17960 | ||
|
|
ce00b952cf | ||
|
|
0d42c285a3 | ||
|
|
ea1eaf1734 | ||
|
|
d5c5ea3e20 | ||
|
|
c7b64af0a4 | ||
|
|
289fda9fea | ||
|
|
b021b9e27a | ||
|
|
c26314237b | ||
|
|
ed7dd8d236 | ||
|
|
ca0e9d75fd | ||
|
|
87c27ee3eb | ||
|
|
4beea725d3 | ||
|
|
9d58699da5 | ||
|
|
bceebba814 | ||
|
|
0b82710b2e | ||
|
|
4aaaa0e760 | ||
|
|
918bfcb8a3 | ||
|
|
1ecfd1ae40 | ||
|
|
4dd195d7c3 | ||
|
|
72b5f45bb4 | ||
|
|
5394814b39 | ||
|
|
83918398f5 | ||
|
|
b6a458d96a | ||
|
|
7b5ff6b642 | ||
|
|
82eddd8828 | ||
|
|
6df2a462d1 | ||
|
|
3689bd07d7 | ||
|
|
943f19ac64 | ||
|
|
e59d6c7fff | ||
|
|
c65a4f39b3 | ||
|
|
2d18371789 | ||
|
|
cc7020a609 | ||
|
|
d8700620d6 | ||
|
|
b7064071dc | ||
|
|
339e82d37b | ||
|
|
a319015452 | ||
|
|
df0d07269b | ||
|
|
2aa04baf2e | ||
|
|
d29f1e9296 | ||
|
|
8ee12880b0 | ||
|
|
8d39de8771 | ||
|
|
c202684c92 | ||
|
|
e970adccb4 | ||
|
|
9f9af3e969 | ||
|
|
615d7c82e3 | ||
|
|
8246a6aea9 | ||
|
|
1e09ec2f22 | ||
|
|
4c018f4174 | ||
|
|
080aaa90aa | ||
|
|
6ac64298f6 | ||
|
|
76e932f3c9 | ||
|
|
06078e295b | ||
|
|
004434a414 | ||
|
|
4661d6d7af | ||
|
|
a972260284 | ||
|
|
f1d6c2dca3 | ||
|
|
aa1cba4c18 | ||
|
|
9b33a4edd8 |
17
.eslintrc.json
Normal file
17
.eslintrc.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Create Pull Request
|
||||
name: Create Pull Request Example Command
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pull-request]
|
||||
types: [cpr-example-command]
|
||||
jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -30,7 +30,15 @@ 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 }}"
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
|
||||
- name: Add reaction
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
|
||||
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
|
||||
reaction-type: hooray
|
||||
28
.github/workflows/slash-command-dispatch.yml
vendored
28
.github/workflows/slash-command-dispatch.yml
vendored
@@ -11,6 +11,28 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commands: test, pytest, clean
|
||||
permission: admin
|
||||
repository: peter-evans/create-pull-request-tests
|
||||
config: >
|
||||
[
|
||||
{
|
||||
"command": "test",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "pytest",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "clean",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests"
|
||||
},
|
||||
{
|
||||
"command": "cpr-example",
|
||||
"permission": "admin",
|
||||
"issue_type": "issue"
|
||||
}
|
||||
]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
__pycache__
|
||||
.python-version
|
||||
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
|
||||
18
README.md
18
README.md
@@ -1,4 +1,4 @@
|
||||
# <img width="24" height="24" src="assets/logo.svg"> Create Pull Request
|
||||
# <img width="24" height="24" src="docs/assets/logo.svg"> Create Pull Request
|
||||
[](https://github.com/marketplace/actions/create-pull-request)
|
||||
|
||||
A GitHub action to create a pull request for changes to your repository in the actions workspace.
|
||||
@@ -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)
|
||||
|
||||
@@ -33,15 +33,16 @@ Create Pull Request action will:
|
||||
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v2.x.x`
|
||||
|
||||
**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.
|
||||
|
||||
### Action inputs
|
||||
|
||||
These inputs are *all optional*. If not set, sensible default values will be used.
|
||||
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: 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 |
|
||||
| --- | --- | --- |
|
||||
| `token` | `GITHUB_TOKEN` or a `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
|
||||
| `path` | Relative path under `$GITHUB_WORKSPACE` to the repository. | `$GITHUB_WORKSPACE` |
|
||||
| `commit-message` | The message to use when committing changes. | `[create-pull-request] automated change` |
|
||||
| `committer` | The committer name and email address in the format `Display Name <email@address.com>`. | Defaults to the GitHub Actions bot user. See [Committer and author](#committer-and-author) for details. |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. | Defaults to the GitHub Actions bot user. See [Committer and author](#committer-and-author) for details. |
|
||||
@@ -55,6 +56,7 @@ These inputs are *all optional*. If not set, sensible default values will be use
|
||||
| `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. | |
|
||||
|
||||
@@ -180,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 }}"
|
||||
@@ -192,7 +196,7 @@ jobs:
|
||||
|
||||
This reference configuration will create pull requests that look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ name: 'Create Pull Request'
|
||||
description: 'Creates a pull request for changes to your repository in the actions workspace'
|
||||
inputs:
|
||||
token:
|
||||
description: 'The GitHub authentication token'
|
||||
description: 'GITHUB_TOKEN or a repo scoped PAT'
|
||||
required: true
|
||||
path:
|
||||
description: 'Relative path under $GITHUB_WORKSPACE to the repository.'
|
||||
commit-message:
|
||||
description: 'The message to use when committing changes.'
|
||||
committer:
|
||||
@@ -30,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:
|
||||
@@ -41,5 +46,5 @@ runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'git-pull-request'
|
||||
icon: 'git-pull-request'
|
||||
color: 'gray-dark'
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 417 KiB |
17
src/common.py → dist/cpr/common.py
vendored
17
src/common.py → dist/cpr/common.py
vendored
@@ -8,6 +8,23 @@ def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
|
||||
|
||||
def parse_github_repository(url):
|
||||
# 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$")
|
||||
|
||||
match = https_pattern.match(url)
|
||||
if match is not None:
|
||||
return "HTTPS", 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):
|
||||
# Parse the name and email address from a string in the following format
|
||||
# Display Name <email@address.com>
|
||||
@@ -56,23 +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]
|
||||
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
|
||||
# Update title and body
|
||||
pull_request.as_issue().edit(title=title, body=body)
|
||||
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
|
||||
else:
|
||||
print(str(e))
|
||||
raise
|
||||
@@ -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,6 +32,14 @@ def get_git_config_value(repo, name):
|
||||
return None
|
||||
|
||||
|
||||
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")
|
||||
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
|
||||
return remote_origin_url, protocol, github_repository
|
||||
|
||||
|
||||
def git_user_config_is_set(repo):
|
||||
name = get_git_config_value(repo, "user.name")
|
||||
email = get_git_config_value(repo, "user.email")
|
||||
@@ -94,8 +103,8 @@ def set_committer_author(repo, committer, author):
|
||||
|
||||
# Get required environment variables
|
||||
github_token = os.environ["GITHUB_TOKEN"]
|
||||
github_repository = os.environ["GITHUB_REPOSITORY"]
|
||||
# Get environment variables with defaults
|
||||
path = os.getenv("CPR_PATH", os.getcwd())
|
||||
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
||||
commit_message = os.getenv("CPR_COMMIT_MESSAGE", DEFAULT_COMMIT_MESSAGE)
|
||||
# Get environment variables with a default of 'None'
|
||||
@@ -103,8 +112,25 @@ committer = os.environ.get("CPR_COMMITTER")
|
||||
author = os.environ.get("CPR_AUTHOR")
|
||||
base = os.environ.get("CPR_BASE")
|
||||
|
||||
# Set the repo to the working directory
|
||||
repo = Repo(os.getcwd())
|
||||
# Set the repo path
|
||||
repo = Repo(path)
|
||||
|
||||
# Determine the GitHub repository from git config
|
||||
# This will be the target repository for the pull request
|
||||
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
|
||||
@@ -161,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)
|
||||
|
||||
@@ -201,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
2
dist/cpr/requirements.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
GitPython==3.1.0
|
||||
PyGithub==1.47
|
||||
24
src/test_common.py → dist/cpr/test_common.py
vendored
24
src/test_common.py → dist/cpr/test_common.py
vendored
@@ -9,6 +9,30 @@ def test_get_random_string():
|
||||
assert len(cmn.get_random_string(length=20)) == 20
|
||||
|
||||
|
||||
def test_parse_github_repository_success():
|
||||
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"
|
||||
|
||||
|
||||
def test_parse_github_repository_failure():
|
||||
url = "https://github.com/peter-evans"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_github_repository(url)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{url}' is not a valid GitHub repository URL"
|
||||
)
|
||||
|
||||
|
||||
def test_parse_display_name_email_success():
|
||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||
assert name == "abc def"
|
||||
391
dist/index.js
vendored
391
dist/index.js
vendored
@@ -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,88 +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.0", "x64");
|
||||
|
||||
// Install requirements
|
||||
await exec.exec("pip", [
|
||||
"install",
|
||||
"--requirement",
|
||||
`${src}/requirements.txt`
|
||||
]);
|
||||
|
||||
// Fetch action inputs
|
||||
const inputs = {
|
||||
token: core.getInput("token"),
|
||||
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.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);
|
||||
@@ -1136,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:
|
||||
@@ -1391,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:
|
||||
@@ -4243,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:
|
||||
@@ -4675,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) {
|
||||
|
||||
2
dist/src/requirements.txt
vendored
2
dist/src/requirements.txt
vendored
@@ -1,2 +0,0 @@
|
||||
GitPython==3.0.5
|
||||
PyGithub==1.45
|
||||
52
dist/src/setup-python.js
vendored
52
dist/src/setup-python.js
vendored
@@ -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;
|
||||
BIN
dist/vendor/Deprecated-1.2.7.tar.gz
vendored
Normal file
BIN
dist/vendor/Deprecated-1.2.7.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/GitPython-3.1.0.tar.gz
vendored
Normal file
BIN
dist/vendor/GitPython-3.1.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/PyGithub-1.47.tar.gz
vendored
Normal file
BIN
dist/vendor/PyGithub-1.47.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/PyJWT-1.7.1.tar.gz
vendored
Normal file
BIN
dist/vendor/PyJWT-1.7.1.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/certifi-2019.11.28.tar.gz
vendored
Normal file
BIN
dist/vendor/certifi-2019.11.28.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/chardet-3.0.4.tar.gz
vendored
Normal file
BIN
dist/vendor/chardet-3.0.4.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/gitdb-4.0.2.tar.gz
vendored
Normal file
BIN
dist/vendor/gitdb-4.0.2.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/idna-2.9.tar.gz
vendored
Normal file
BIN
dist/vendor/idna-2.9.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/requests-2.23.0.tar.gz
vendored
Normal file
BIN
dist/vendor/requests-2.23.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/smmap-3.0.1.tar.gz
vendored
Normal file
BIN
dist/vendor/smmap-3.0.1.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/urllib3-1.25.8.tar.gz
vendored
Normal file
BIN
dist/vendor/urllib3-1.25.8.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/wrapt-1.12.1.tar.gz
vendored
Normal file
BIN
dist/vendor/wrapt-1.12.1.tar.gz
vendored
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 416 B |
BIN
docs/assets/pull-request-example.png
Normal file
BIN
docs/assets/pull-request-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 414 KiB |
@@ -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,6 +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)
|
||||
- [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
|
||||
|
||||
@@ -30,7 +37,7 @@ The default can be overridden by specifying a `ref` on checkout.
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
ref: develop
|
||||
```
|
||||
|
||||
## How the action works
|
||||
@@ -45,7 +52,7 @@ Workflow steps:
|
||||
|
||||
The following git diagram shows how the action creates and updates a pull request branch.
|
||||
|
||||

|
||||

|
||||
|
||||
## Guidelines
|
||||
|
||||
@@ -105,3 +112,216 @@ jobs:
|
||||
# Check if the event is not triggered by a fork
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
```
|
||||
|
||||
### 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.
|
||||
This means the repository will *not* be checked out on an active branch.
|
||||
|
||||
If you would like to run `create-pull-request` action on the tagged commit you can achieve this by creating a temporary branch as follows.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create a temporary tag branch
|
||||
run: |
|
||||
git config --global user.name 'GitHub'
|
||||
git config --global user.email 'noreply@github.com'
|
||||
git checkout -b temp-${GITHUB_REF:10}
|
||||
git push --set-upstream origin temp-${GITHUB_REF:10}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base: master
|
||||
|
||||
- name: Delete tag branch
|
||||
run: |
|
||||
git push --delete origin temp-${GITHUB_REF:10}
|
||||
```
|
||||
|
||||
This is an alternative, simpler workflow to the one above. However, this is not guaranteed to checkout the tagged commit.
|
||||
There is a chance that in between the tag being pushed and checking out the `master` branch in the workflow, another commit is made to `master`. If that possibility is not a concern, this workflow will work fine.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
105
docs/examples.md
105
docs/examples.md
@@ -1,8 +1,10 @@
|
||||
# Examples
|
||||
|
||||
- [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)
|
||||
- [Keep Go up to date](#keep-go-up-to-date)
|
||||
- [Update SwaggerUI for GitHub Pages](#update-swaggerui-for-github-pages)
|
||||
- [Spider and download a website](#spider-and-download-a-website)
|
||||
- [Use case: Create a pull request to update X by calling the GitHub API](#use-case-create-a-pull-request-to-update-x-by-calling-the-github-api)
|
||||
@@ -16,6 +18,70 @@
|
||||
- [Debugging GitHub Actions](#debugging-github-actions)
|
||||
|
||||
|
||||
## Use case: Create a pull request to update X on push
|
||||
|
||||
This pattern will work well for updating any kind of static content based on pushed changes. Care should be taken when using this pattern in repositories with a high frequency of commits.
|
||||
|
||||
### Update project authors
|
||||
|
||||
Raises a pull request to update a file called `AUTHORS` with the git user names and email addresses of contributors.
|
||||
|
||||
```yml
|
||||
name: Update AUTHORS
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
updateAuthors:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update AUTHORS
|
||||
run: |
|
||||
git log --format='%aN <%aE>%n%cN <%cE>' | sort -u > AUTHORS
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: update authors
|
||||
title: Update AUTHORS
|
||||
body: Credit new contributors by updating AUTHORS
|
||||
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.
|
||||
@@ -51,39 +117,6 @@ jobs:
|
||||
branch: dep-updates
|
||||
```
|
||||
|
||||
### Keep Go up to date
|
||||
|
||||
Keep Go up to date with [ensure-latest-go](https://github.com/jmhodges/ensure-latest-go) action.
|
||||
|
||||
```yml
|
||||
name: Keeping Go up to date
|
||||
on:
|
||||
schedule:
|
||||
- cron: 47 4 * * *
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
fresh_go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
- uses: jmhodges/ensure-latest-go@v1.0.2
|
||||
id: ensure_go
|
||||
- run: echo "##[set-output name=pr_title;]update to latest Go release ${{ steps.ensure_go.outputs.go_version}}"
|
||||
id: pr_title_maker
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
title: ${{ steps.pr_title_maker.outputs.pr_title }}
|
||||
body: Auto-generated pull request created by the GitHub Actions [create-pull-request](https://github.com/peter-evans/create-pull-request) and [ensure-latest-go](https://github.com/jmhodges/ensure-latest-go).
|
||||
commit-message: ${{ steps.pr_title_maker.outputs.pr_title }}
|
||||
branch: ensure-latest-go/patch-${{ steps.ensure_go.outputs.go_version }}
|
||||
```
|
||||
|
||||
### Update SwaggerUI for GitHub Pages
|
||||
|
||||
When using [GitHub Pages to host Swagger documentation](https://github.com/peter-evans/swagger-github-pages), this workflow updates the repository with the latest distribution of [SwaggerUI](https://github.com/swagger-api/swagger-ui).
|
||||
@@ -225,7 +258,7 @@ An `on: repository_dispatch` workflow can be triggered from another workflow wit
|
||||
|
||||
## Use case: Create a pull request to modify/fix pull requests
|
||||
|
||||
**Note**: While the following approach does work in some cases, my strong recommendation would be to use a slash command style "ChatOps" solution for operations on pull requests. See [slash-command-dispatch](https://github.com/peter-evans/slash-command-dispatch) for such a solution.
|
||||
**Note**: While the following approach does work, my strong recommendation would be to use a slash command style "ChatOps" solution for operations on pull requests. See [slash-command-dispatch](https://github.com/peter-evans/slash-command-dispatch) for such a solution.
|
||||
|
||||
This is a pattern that lends itself to automated code linting and fixing. A pull request can be created to fix or modify something during an `on: pull_request` workflow. The pull request containing the fix will be raised with the original pull request as the base. This can be then be merged to update the original pull request and pass any required tests.
|
||||
|
||||
@@ -256,7 +289,7 @@ jobs:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: autopep8
|
||||
id: autopep8
|
||||
uses: peter-evans/autopep8@v1.1.0
|
||||
uses: peter-evans/autopep8@v1
|
||||
with:
|
||||
args: --exit-code --recursive --in-place --aggressive --aggressive .
|
||||
- name: Set autopep8 branch name
|
||||
|
||||
3
jest.config.js
Normal file
3
jest.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
process.env = Object.assign(process.env, {
|
||||
GITHUB_WORKSPACE: __dirname
|
||||
});
|
||||
5975
package-lock.json
generated
5975
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -4,7 +4,12 @@
|
||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"package": "ncc build index.js -o dist"
|
||||
"clean": "rm -rf dist",
|
||||
"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": {
|
||||
"type": "git",
|
||||
@@ -20,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.20.5"
|
||||
"@zeit/ncc": "0.22.0",
|
||||
"eslint": "6.8.0",
|
||||
"jest": "25.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,23 @@ def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
|
||||
|
||||
def parse_github_repository(url):
|
||||
# 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$")
|
||||
|
||||
match = https_pattern.match(url)
|
||||
if match is not None:
|
||||
return "HTTPS", 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):
|
||||
# Parse the name and email address from a string in the following format
|
||||
# Display Name <email@address.com>
|
||||
@@ -56,23 +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]
|
||||
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
|
||||
# Update title and body
|
||||
pull_request.as_issue().edit(title=title, body=body)
|
||||
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
|
||||
else:
|
||||
print(str(e))
|
||||
raise
|
||||
@@ -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,6 +32,14 @@ def get_git_config_value(repo, name):
|
||||
return None
|
||||
|
||||
|
||||
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")
|
||||
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
|
||||
return remote_origin_url, protocol, github_repository
|
||||
|
||||
|
||||
def git_user_config_is_set(repo):
|
||||
name = get_git_config_value(repo, "user.name")
|
||||
email = get_git_config_value(repo, "user.email")
|
||||
@@ -94,8 +103,8 @@ def set_committer_author(repo, committer, author):
|
||||
|
||||
# Get required environment variables
|
||||
github_token = os.environ["GITHUB_TOKEN"]
|
||||
github_repository = os.environ["GITHUB_REPOSITORY"]
|
||||
# Get environment variables with defaults
|
||||
path = os.getenv("CPR_PATH", os.getcwd())
|
||||
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
||||
commit_message = os.getenv("CPR_COMMIT_MESSAGE", DEFAULT_COMMIT_MESSAGE)
|
||||
# Get environment variables with a default of 'None'
|
||||
@@ -103,8 +112,25 @@ committer = os.environ.get("CPR_COMMITTER")
|
||||
author = os.environ.get("CPR_AUTHOR")
|
||||
base = os.environ.get("CPR_BASE")
|
||||
|
||||
# Set the repo to the working directory
|
||||
repo = Repo(os.getcwd())
|
||||
# Set the repo path
|
||||
repo = Repo(path)
|
||||
|
||||
# Determine the GitHub repository from git config
|
||||
# This will be the target repository for the pull request
|
||||
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
|
||||
@@ -161,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
|
||||
@@ -201,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
2
src/cpr/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
GitPython==3.1.0
|
||||
PyGithub==1.47
|
||||
@@ -9,6 +9,30 @@ def test_get_random_string():
|
||||
assert len(cmn.get_random_string(length=20)) == 20
|
||||
|
||||
|
||||
def test_parse_github_repository_success():
|
||||
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"
|
||||
|
||||
|
||||
def test_parse_github_repository_failure():
|
||||
url = "https://github.com/peter-evans"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_github_repository(url)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{url}' is not a valid GitHub repository URL"
|
||||
)
|
||||
|
||||
|
||||
def test_parse_display_name_email_success():
|
||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||
assert name == "abc def"
|
||||
97
src/git.js
Normal file
97
src/git.js
Normal 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
98
src/git.test.js
Normal 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();
|
||||
});
|
||||
@@ -1,27 +1,55 @@
|
||||
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.0", "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`
|
||||
]);
|
||||
|
||||
// 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"),
|
||||
@@ -35,13 +63,15 @@ 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)}`);
|
||||
|
||||
// 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;
|
||||
@@ -55,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}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
GitPython==3.0.5
|
||||
PyGithub==1.45
|
||||
Reference in New Issue
Block a user