Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
b6a458d96a | |||
7b5ff6b642 | |||
82eddd8828 | |||
6df2a462d1 | |||
3689bd07d7 | |||
943f19ac64 | |||
e59d6c7fff | |||
c65a4f39b3 | |||
2d18371789 | |||
cc7020a609 | |||
d8700620d6 | |||
b7064071dc | |||
339e82d37b | |||
a319015452 | |||
df0d07269b | |||
2aa04baf2e | |||
d29f1e9296 | |||
8ee12880b0 | |||
8d39de8771 | |||
c202684c92 | |||
e970adccb4 | |||
9f9af3e969 | |||
615d7c82e3 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
.python-version
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
19
README.md
19
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)
|
[](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.
|
A GitHub action to create a pull request for changes to your repository in the actions workspace.
|
||||||
@ -88,6 +88,21 @@ If there is some reason you need to use `actions/checkout@v1` the following step
|
|||||||
- run: git checkout "${GITHUB_REF:11}"
|
- run: git checkout "${GITHUB_REF:11}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Create changes to pull request here
|
||||||
|
|
||||||
|
- uses: peter-evans/create-pull-request@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
```
|
||||||
|
|
||||||
### Branch naming
|
### Branch naming
|
||||||
|
|
||||||
For branch naming there are two strategies. Create a fixed-name pull request branch that will be updated with new changes until it is merged or closed, OR, always create a new unique branch each time there are changes to be committed.
|
For branch naming there are two strategies. Create a fixed-name pull request branch that will be updated with new changes until it is merged or closed, OR, always create a new unique branch each time there are changes to be committed.
|
||||||
@ -194,7 +209,7 @@ jobs:
|
|||||||
|
|
||||||
This reference configuration will create pull requests that look like this:
|
This reference configuration will create pull requests that look like this:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
24
dist/index.js
vendored
24
dist/index.js
vendored
@ -1001,24 +1001,42 @@ module.exports = require("os");
|
|||||||
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
|
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
|
||||||
|
|
||||||
const { inspect } = __webpack_require__(669);
|
const { inspect } = __webpack_require__(669);
|
||||||
|
const fs = __webpack_require__(747);
|
||||||
const core = __webpack_require__(470);
|
const core = __webpack_require__(470);
|
||||||
const exec = __webpack_require__(986);
|
const exec = __webpack_require__(986);
|
||||||
const setupPython = __webpack_require__(139);
|
const setupPython = __webpack_require__(139);
|
||||||
|
|
||||||
|
function fileExists(path) {
|
||||||
|
try {
|
||||||
|
return fs.statSync(path).isFile();
|
||||||
|
} catch (e) {
|
||||||
|
core.debug(`e: ${inspect(e)}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
// Allows ncc to find assets to be included in the distribution
|
// Allows ncc to find assets to be included in the distribution
|
||||||
const src = __webpack_require__.ab + "src";
|
const src = __webpack_require__.ab + "src";
|
||||||
core.debug(`src: ${src}`);
|
core.debug(`src: ${src}`);
|
||||||
|
|
||||||
// Setup Python from the tool cache
|
// Check if the platfrom is Alpine Linux
|
||||||
setupPython("3.8.0", "x64");
|
const alpineLinux = fileExists("/etc/alpine-release");
|
||||||
|
core.debug(`alpineLinux: ${alpineLinux}`);
|
||||||
|
|
||||||
|
// Skip Python setup if the platform is Alpine Linux
|
||||||
|
if (!alpineLinux)
|
||||||
|
// Setup Python from the tool cache
|
||||||
|
setupPython("3.8.x", "x64");
|
||||||
|
|
||||||
// Install requirements
|
// Install requirements
|
||||||
await exec.exec("pip", [
|
await exec.exec("pip", [
|
||||||
"install",
|
"install",
|
||||||
"--requirement",
|
"--requirement",
|
||||||
`${src}/requirements.txt`
|
`${src}/requirements.txt`,
|
||||||
|
"--no-index",
|
||||||
|
`--find-links=${__dirname}/vendor`
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Fetch action inputs
|
// Fetch action inputs
|
||||||
|
17
dist/src/common.py
vendored
17
dist/src/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))
|
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):
|
def parse_display_name_email(display_name_email):
|
||||||
# Parse the name and email address from a string in the following format
|
# Parse the name and email address from a string in the following format
|
||||||
# Display Name <email@address.com>
|
# Display Name <email@address.com>
|
||||||
|
2
dist/src/create_or_update_pull_request.py
vendored
2
dist/src/create_or_update_pull_request.py
vendored
@ -72,6 +72,8 @@ def create_or_update_pull_request(
|
|||||||
pull_request = github_repo.get_pulls(
|
pull_request = github_repo.get_pulls(
|
||||||
state="open", base=base, head=head_branch
|
state="open", base=base, head=head_branch
|
||||||
)[0]
|
)[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} ({branch} => {base})")
|
||||||
else:
|
else:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
|
20
dist/src/create_pull_request.py
vendored
20
dist/src/create_pull_request.py
vendored
@ -31,6 +31,14 @@ def get_git_config_value(repo, name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_repository_detail():
|
||||||
|
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):
|
def git_user_config_is_set(repo):
|
||||||
name = get_git_config_value(repo, "user.name")
|
name = get_git_config_value(repo, "user.name")
|
||||||
email = get_git_config_value(repo, "user.email")
|
email = get_git_config_value(repo, "user.email")
|
||||||
@ -94,7 +102,6 @@ def set_committer_author(repo, committer, author):
|
|||||||
|
|
||||||
# Get required environment variables
|
# Get required environment variables
|
||||||
github_token = os.environ["GITHUB_TOKEN"]
|
github_token = os.environ["GITHUB_TOKEN"]
|
||||||
github_repository = os.environ["GITHUB_REPOSITORY"]
|
|
||||||
# Get environment variables with defaults
|
# Get environment variables with defaults
|
||||||
path = os.getenv("CPR_PATH", os.getcwd())
|
path = os.getenv("CPR_PATH", os.getcwd())
|
||||||
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
||||||
@ -107,6 +114,11 @@ base = os.environ.get("CPR_BASE")
|
|||||||
# Set the repo path
|
# Set the repo path
|
||||||
repo = 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()
|
||||||
|
print(f"Target repository set to {github_repository}")
|
||||||
|
|
||||||
# Determine if the checked out ref is a valid base for a pull request
|
# 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
|
# The action needs the checked out HEAD ref to be a branch
|
||||||
# This check will fail in the following cases:
|
# This check will fail in the following cases:
|
||||||
@ -162,8 +174,10 @@ except ValueError as e:
|
|||||||
print(f"::error::{e} " + "Unable to continue. Exiting.")
|
print(f"::error::{e} " + "Unable to continue. Exiting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Set the repository URL
|
# Set the auth token in the repo URL
|
||||||
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
# This supports checkout@v1. From v2 the auth token is saved for further use.
|
||||||
|
if protocol == "HTTPS":
|
||||||
|
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
||||||
|
|
||||||
# Create or update the pull request branch
|
# Create or update the pull request branch
|
||||||
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
|
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
|
||||||
|
2
dist/src/requirements.txt
vendored
2
dist/src/requirements.txt
vendored
@ -1,2 +1,2 @@
|
|||||||
GitPython==3.0.5
|
GitPython==3.0.7
|
||||||
PyGithub==1.45
|
PyGithub==1.45
|
||||||
|
24
dist/src/test_common.py
vendored
24
dist/src/test_common.py
vendored
@ -9,6 +9,30 @@ def test_get_random_string():
|
|||||||
assert len(cmn.get_random_string(length=20)) == 20
|
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():
|
def test_parse_display_name_email_success():
|
||||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||||
assert name == "abc def"
|
assert name == "abc def"
|
||||||
|
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.0.7.tar.gz
vendored
Normal file
BIN
dist/vendor/GitPython-3.0.7.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/PyGithub-1.45.tar.gz
vendored
Normal file
BIN
dist/vendor/PyGithub-1.45.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/gitdb2-2.0.6.tar.gz
vendored
Normal file
BIN
dist/vendor/gitdb2-2.0.6.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/idna-2.8.tar.gz
vendored
Normal file
BIN
dist/vendor/idna-2.8.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/requests-2.22.0.tar.gz
vendored
Normal file
BIN
dist/vendor/requests-2.22.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/six-1.14.0.tar.gz
vendored
Normal file
BIN
dist/vendor/six-1.14.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/vendor/smmap2-2.0.5.tar.gz
vendored
Normal file
BIN
dist/vendor/smmap2-2.0.5.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.11.2.tar.gz
vendored
Normal file
BIN
dist/vendor/wrapt-1.11.2.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 |
Before Width: | Height: | Size: 414 KiB After Width: | Height: | Size: 414 KiB |
@ -9,6 +9,8 @@ This document covers terminology, how the action works, and general usage guidel
|
|||||||
- [Providing a consistent base](#providing-a-consistent-base)
|
- [Providing a consistent base](#providing-a-consistent-base)
|
||||||
- [Pull request events](#pull-request-events)
|
- [Pull request events](#pull-request-events)
|
||||||
- [Restrictions on forked repositories](#restrictions-on-forked-repositories)
|
- [Restrictions on forked repositories](#restrictions-on-forked-repositories)
|
||||||
|
- [Tag push events](#tag-push-events)
|
||||||
|
- [Security](#security)
|
||||||
|
|
||||||
## Terminology
|
## Terminology
|
||||||
|
|
||||||
@ -45,7 +47,7 @@ Workflow steps:
|
|||||||
|
|
||||||
The following git diagram shows how the action creates and updates a pull request branch.
|
The following git diagram shows how the action creates and updates a pull request branch.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Guidelines
|
## Guidelines
|
||||||
|
|
||||||
@ -105,3 +107,88 @@ jobs:
|
|||||||
# Check if the event is not triggered by a fork
|
# Check if the event is not triggered by a fork
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tag push events
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
- name: Create changes to pull request
|
||||||
|
run: <create changes 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
|
||||||
|
|
||||||
|
- name: Create changes to pull request
|
||||||
|
run: <create changes 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.
|
||||||
|
@ -258,7 +258,7 @@ jobs:
|
|||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
- name: autopep8
|
- name: autopep8
|
||||||
id: autopep8
|
id: autopep8
|
||||||
uses: peter-evans/autopep8@v1.1.0
|
uses: peter-evans/autopep8@v1
|
||||||
with:
|
with:
|
||||||
args: --exit-code --recursive --in-place --aggressive --aggressive .
|
args: --exit-code --recursive --in-place --aggressive --aggressive .
|
||||||
- name: Set autopep8 branch name
|
- name: Set autopep8 branch name
|
||||||
|
24
index.js
24
index.js
@ -1,22 +1,40 @@
|
|||||||
const { inspect } = require("util");
|
const { inspect } = require("util");
|
||||||
|
const fs = require("fs");
|
||||||
const core = require("@actions/core");
|
const core = require("@actions/core");
|
||||||
const exec = require("@actions/exec");
|
const exec = require("@actions/exec");
|
||||||
const setupPython = require("./src/setup-python");
|
const setupPython = require("./src/setup-python");
|
||||||
|
|
||||||
|
function fileExists(path) {
|
||||||
|
try {
|
||||||
|
return fs.statSync(path).isFile();
|
||||||
|
} catch (e) {
|
||||||
|
core.debug(`e: ${inspect(e)}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
// Allows ncc to find assets to be included in the distribution
|
// Allows ncc to find assets to be included in the distribution
|
||||||
const src = __dirname + "/src";
|
const src = __dirname + "/src";
|
||||||
core.debug(`src: ${src}`);
|
core.debug(`src: ${src}`);
|
||||||
|
|
||||||
// Setup Python from the tool cache
|
// Check if the platfrom is Alpine Linux
|
||||||
setupPython("3.8.0", "x64");
|
const alpineLinux = fileExists("/etc/alpine-release");
|
||||||
|
core.debug(`alpineLinux: ${alpineLinux}`);
|
||||||
|
|
||||||
|
// Skip Python setup if the platform is Alpine Linux
|
||||||
|
if (!alpineLinux)
|
||||||
|
// Setup Python from the tool cache
|
||||||
|
setupPython("3.8.x", "x64");
|
||||||
|
|
||||||
// Install requirements
|
// Install requirements
|
||||||
await exec.exec("pip", [
|
await exec.exec("pip", [
|
||||||
"install",
|
"install",
|
||||||
"--requirement",
|
"--requirement",
|
||||||
`${src}/requirements.txt`
|
`${src}/requirements.txt`,
|
||||||
|
"--no-index",
|
||||||
|
`--find-links=${__dirname}/vendor`
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Fetch action inputs
|
// Fetch action inputs
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"package": "ncc build index.js -o dist"
|
"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",
|
||||||
|
"package": "npm run build && npm run vendor-deps"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -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))
|
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):
|
def parse_display_name_email(display_name_email):
|
||||||
# Parse the name and email address from a string in the following format
|
# Parse the name and email address from a string in the following format
|
||||||
# Display Name <email@address.com>
|
# Display Name <email@address.com>
|
||||||
|
@ -72,6 +72,8 @@ def create_or_update_pull_request(
|
|||||||
pull_request = github_repo.get_pulls(
|
pull_request = github_repo.get_pulls(
|
||||||
state="open", base=base, head=head_branch
|
state="open", base=base, head=head_branch
|
||||||
)[0]
|
)[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} ({branch} => {base})")
|
||||||
else:
|
else:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
|
@ -31,6 +31,14 @@ def get_git_config_value(repo, name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_repository_detail():
|
||||||
|
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):
|
def git_user_config_is_set(repo):
|
||||||
name = get_git_config_value(repo, "user.name")
|
name = get_git_config_value(repo, "user.name")
|
||||||
email = get_git_config_value(repo, "user.email")
|
email = get_git_config_value(repo, "user.email")
|
||||||
@ -94,7 +102,6 @@ def set_committer_author(repo, committer, author):
|
|||||||
|
|
||||||
# Get required environment variables
|
# Get required environment variables
|
||||||
github_token = os.environ["GITHUB_TOKEN"]
|
github_token = os.environ["GITHUB_TOKEN"]
|
||||||
github_repository = os.environ["GITHUB_REPOSITORY"]
|
|
||||||
# Get environment variables with defaults
|
# Get environment variables with defaults
|
||||||
path = os.getenv("CPR_PATH", os.getcwd())
|
path = os.getenv("CPR_PATH", os.getcwd())
|
||||||
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
branch = os.getenv("CPR_BRANCH", DEFAULT_BRANCH)
|
||||||
@ -107,6 +114,11 @@ base = os.environ.get("CPR_BASE")
|
|||||||
# Set the repo path
|
# Set the repo path
|
||||||
repo = 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()
|
||||||
|
print(f"Target repository set to {github_repository}")
|
||||||
|
|
||||||
# Determine if the checked out ref is a valid base for a pull request
|
# 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
|
# The action needs the checked out HEAD ref to be a branch
|
||||||
# This check will fail in the following cases:
|
# This check will fail in the following cases:
|
||||||
@ -162,8 +174,10 @@ except ValueError as e:
|
|||||||
print(f"::error::{e} " + "Unable to continue. Exiting.")
|
print(f"::error::{e} " + "Unable to continue. Exiting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Set the repository URL
|
# Set the auth token in the repo URL
|
||||||
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
# This supports checkout@v1. From v2 the auth token is saved for further use.
|
||||||
|
if protocol == "HTTPS":
|
||||||
|
repo_url = f"https://x-access-token:{github_token}@github.com/{github_repository}"
|
||||||
|
|
||||||
# Create or update the pull request branch
|
# Create or update the pull request branch
|
||||||
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
|
result = coub.create_or_update_branch(repo, repo_url, commit_message, base, branch)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
GitPython==3.0.5
|
GitPython==3.0.7
|
||||||
PyGithub==1.45
|
PyGithub==1.45
|
||||||
|
@ -9,6 +9,30 @@ def test_get_random_string():
|
|||||||
assert len(cmn.get_random_string(length=20)) == 20
|
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():
|
def test_parse_display_name_email_success():
|
||||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||||
assert name == "abc def"
|
assert name == "abc def"
|
||||||
|
Reference in New Issue
Block a user