Compare commits

..
26 Commits
Author SHA1 Message Date
Peter Evans 1e6b4d1790 Merge pull request #145 from peter-evans/dev
Create pull requests in the parent repository of a checked out fork
2020-03-29 21:02:48 +09:00
Peter Evans a70c6ebe2a Merge pull request #144 from jderusse/fork
Allow custom repository when pushin in fork
2020-03-29 20:47:00 +09:00
Jérémy DerusséandPeter Evans 5bd05538d0 Update src/cpr/create_or_update_pull_request.py
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 13:36:48 +02:00
Jérémy Derussé 26bc40eea1 Add link in TOC 2020-03-29 12:44:57 +02:00
Jérémy DerusséandPeter Evans 6bb0e7771c Apply suggestions from code review
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 12:43:21 +02:00
Jérémy DerusséandTobias Nyholm 4c347a4514 Update docs/concepts-guidelines.md
Co-Authored-By: Tobias Nyholm <tobias.nyholm@gmail.com>
2020-03-28 16:42:57 +01:00
Jérémy Derussé cff2c3381d Add doc 2020-03-28 13:27:58 +01:00
Jérémy Derussé e48dab0c1c Add PR creation from Fork 2020-03-28 13:10:12 +01:00
Peter Evans ac9f92d6e7 Merge pull request #141 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.3
2020-03-27 08:15:40 +09:00
Renovate Bot 3ff256ce08 Update dependency jest to v25.2.3 2020-03-26 20:38:32 +00:00
Peter Evans 0bcb10560b Merge pull request #140 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.1
2020-03-26 19:03:52 +09:00
Renovate Bot 3c4b2793c1 Update dependency jest to v25.2.1 2020-03-26 09:43:37 +00:00
Peter Evans 22870d7816 Merge pull request #139 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.0
2020-03-26 10:23:31 +09:00
Renovate Bot e324a22ee1 Update dependency jest to v25.2.0 2020-03-25 18:18:56 +00:00
Peter Evans 8b60386018 Merge pull request #135 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.22.0
2020-03-24 09:20:19 +09:00
Renovate Bot 6852d55922 Update dependency @zeit/ncc to v0.22.0 2020-03-23 22:31:46 +00:00
Peter Evans 32e5bb80a5 Merge pull request #134 from peter-evans/dev
Update dependencies
2020-03-21 11:38:19 +09:00
Peter Evans 619cf2115d Update dependencies due to security vulnerabilities 2020-03-21 11:31:34 +09:00
Peter Evans 69008aa567 Update vendored dependencies 2020-03-21 11:29:37 +09:00
Renovate Bot ae4278bf24 Update dependency PyGithub to v1.47 2020-03-21 11:23:42 +09:00
Peter Evans 88da40fea7 Merge pull request #132 from peter-evans/dev
Update dependency GitPython to v3.1.0
2020-03-07 09:08:24 +09:00
Peter Evans 694e068136 Update vendored dependencies 2020-03-07 08:57:59 +09:00
Peter Evans 64c34f6885 Update README 2020-03-03 09:49:52 +09:00
Renovate Bot 52ada17960 Update dependency GitPython to v3.1.0 2020-02-25 13:12:00 +09:00
Peter Evans ce00b952cf Merge pull request #128 from peter-evans/dev
Unset and restore authorization extraheader only
2020-02-22 17:02:15 +09:00
Peter Evans 0d42c285a3 Unset and restore authorization extraheader only 2020-02-22 16:56:42 +09:00
25 changed files with 672 additions and 658 deletions
@@ -30,6 +30,7 @@ jobs:
project: Example Project
project-column: To do
branch: example-patches
request-to-parent: false
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
+3
View File
@@ -56,6 +56,7 @@ With the exception of `token`, all inputs are **optional**. If not set, sensible
| `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. | |
@@ -181,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 }}"
+4 -1
View File
@@ -32,6 +32,9 @@ inputs:
description: 'The name of the project column under which a card should be created.'
branch:
description: 'The pull request branch name.'
request-to-parent:
description: 'Create the pull request in the parent repository of the checked out fork.'
default: false
base:
description: 'The pull request base branch.'
branch-suffix:
@@ -43,5 +46,5 @@ runs:
using: 'node12'
main: 'dist/index.js'
branding:
icon: 'git-pull-request'
icon: 'git-pull-request'
color: 'gray-dark'
+17 -5
View File
@@ -56,25 +56,37 @@ def create_or_update_pull_request(
team_reviewers,
project_name,
project_column_name,
request_to_parent,
):
if request_to_parent is None:
request_to_parent = False
else:
request_to_parent = request_to_parent.lower() in ['true', '1', 't', 'y', 'yes', 'on']
github_repo = head_repo = Github(github_token).get_repo(github_repository)
if request_to_parent:
github_repo = github_repo.parent
if github_repo is None:
raise ValueError("The checked out repository is not a fork. Input 'request-to-parent' should be set to false.")
head_branch = f"{head_repo.owner.login}:{branch}"
# Create the pull request
github_repo = Github(github_token).get_repo(github_repository)
try:
pull_request = github_repo.create_pull(
title=title, body=body, base=base, head=branch
title=title, body=body, base=base, head=head_branch
)
print(f"Created pull request #{pull_request.number} ({branch} => {base})")
print(f"Created pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
except GithubException as e:
if e.status == 422:
# A pull request exists for this branch and base
head_branch = "{}:{}".format(github_repository.split("/")[0], branch)
# Get the pull request
pull_request = github_repo.get_pulls(
state="open", base=base, head=head_branch
)[0]
# Update title and body
pull_request.as_issue().edit(title=title, body=body)
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
else:
print(str(e))
raise
+1
View File
@@ -224,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 -2
View File
@@ -1,2 +1,2 @@
GitPython==3.0.8
PyGithub==1.46
GitPython==3.1.0
PyGithub==1.47
+24 -16
View File
@@ -4222,6 +4222,7 @@ const {
} = __webpack_require__(718);
const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
async function run() {
try {
@@ -4274,6 +4275,7 @@ 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")
};
@@ -4295,15 +4297,17 @@ 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;
// Get the repository path
var repoPath = getRepoPath(inputs.path);
// Get the extraheader config option if it exists
var extraHeaderOptionValue = await getAndUnsetConfigOption(
var extraHeaderOption = await getAndUnsetConfigOption(
repoPath,
EXTRAHEADER_OPTION
EXTRAHEADER_OPTION,
EXTRAHEADER_VALUE_REGEX
);
// Execute create pull request
@@ -4312,12 +4316,12 @@ async function run() {
core.setFailed(error.message);
} finally {
// Restore the extraheader config option
if (extraHeaderOptionValue) {
if (extraHeaderOption) {
if (
await addConfigOption(
repoPath,
EXTRAHEADER_OPTION,
extraHeaderOptionValue
extraHeaderOption.value
)
)
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
@@ -4379,39 +4383,43 @@ async function addConfigOption(repoPath, name, value) {
return result.exitCode === 0;
}
async function unsetConfigOption(repoPath, name) {
async function unsetConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--unset", name],
["config", "--local", "--unset", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function configOptionExists(repoPath, name) {
async function configOptionExists(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--name-only", "--get-regexp", name],
["config", "--local", "--name-only", "--get-regexp", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function getConfigOption(repoPath, name) {
async function getConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", name],
["config", "--local", "--get-regexp", name, valueRegex],
true
);
return result.stdout.trim();
const option = result.stdout.trim().split(`${name} `);
return {
name: name,
value: option[1]
}
}
async function getAndUnsetConfigOption(repoPath, name) {
if (await configOptionExists(repoPath, name)) {
const extraHeaderOptionValue = await getConfigOption(repoPath, name);
if (await unsetConfigOption(repoPath, name)) {
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 extraHeaderOptionValue;
return option;
}
}
return null;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+29
View File
@@ -13,6 +13,7 @@ This document covers terminology, how the action works, general usage guidelines
- [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)
@@ -180,6 +181,34 @@ How to use SSH (deploy keys) with create-pull-request action:
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.
+514 -604
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -29,8 +29,8 @@
"is-docker": "^2.0.0"
},
"devDependencies": {
"@zeit/ncc": "0.21.1",
"@zeit/ncc": "0.22.0",
"eslint": "6.8.0",
"jest": "25.1.0"
"jest": "25.2.3"
}
}
+17 -5
View File
@@ -56,25 +56,37 @@ def create_or_update_pull_request(
team_reviewers,
project_name,
project_column_name,
request_to_parent,
):
if request_to_parent is None:
request_to_parent = False
else:
request_to_parent = request_to_parent.lower() in ['true', '1', 't', 'y', 'yes', 'on']
github_repo = head_repo = Github(github_token).get_repo(github_repository)
if request_to_parent:
github_repo = github_repo.parent
if github_repo is None:
raise ValueError("The checked out repository is not a fork. Input 'request-to-parent' should be set to false.")
head_branch = f"{head_repo.owner.login}:{branch}"
# Create the pull request
github_repo = Github(github_token).get_repo(github_repository)
try:
pull_request = github_repo.create_pull(
title=title, body=body, base=base, head=branch
title=title, body=body, base=base, head=head_branch
)
print(f"Created pull request #{pull_request.number} ({branch} => {base})")
print(f"Created pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
except GithubException as e:
if e.status == 422:
# A pull request exists for this branch and base
head_branch = "{}:{}".format(github_repository.split("/")[0], branch)
# Get the pull request
pull_request = github_repo.get_pulls(
state="open", base=base, head=head_branch
)[0]
# Update title and body
pull_request.as_issue().edit(title=title, body=body)
print(f"Updated pull request #{pull_request.number} ({branch} => {base})")
print(f"Updated pull request #{pull_request.number} ({head_branch} => {github_repo.owner.login}:{base})")
else:
print(str(e))
raise
+2 -1
View File
@@ -192,7 +192,7 @@ result = coub.create_or_update_branch(repo, repo_url, commit_message, base, bran
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
@@ -224,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 -2
View File
@@ -1,2 +1,2 @@
GitPython==3.0.8
PyGithub==1.46
GitPython==3.1.0
PyGithub==1.47
+16 -12
View File
@@ -44,39 +44,43 @@ async function addConfigOption(repoPath, name, value) {
return result.exitCode === 0;
}
async function unsetConfigOption(repoPath, name) {
async function unsetConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--unset", name],
["config", "--local", "--unset", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function configOptionExists(repoPath, name) {
async function configOptionExists(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", "--name-only", "--get-regexp", name],
["config", "--local", "--name-only", "--get-regexp", name, valueRegex],
true
);
return result.exitCode === 0;
}
async function getConfigOption(repoPath, name) {
async function getConfigOption(repoPath, name, valueRegex=".") {
const result = await execGit(
repoPath,
["config", "--local", name],
["config", "--local", "--get-regexp", name, valueRegex],
true
);
return result.stdout.trim();
const option = result.stdout.trim().split(`${name} `);
return {
name: name,
value: option[1]
}
}
async function getAndUnsetConfigOption(repoPath, name) {
if (await configOptionExists(repoPath, name)) {
const extraHeaderOptionValue = await getConfigOption(repoPath, name);
if (await unsetConfigOption(repoPath, name)) {
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 extraHeaderOptionValue;
return option;
}
}
return null;
+30 -4
View File
@@ -29,12 +29,20 @@ test("execGit", async () => {
test("add and unset config option", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.add.and.unset.config.option", "true");
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");
@@ -51,18 +59,36 @@ test("get config option", async () => {
const repoPath = getRepoPath();
const add = await addConfigOption(repoPath, "test.get.config.option", "foo");
expect(add).toBeTruthy();
const get = await getConfigOption(repoPath, "test.get.config.option");
expect(get).toEqual("foo");
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).toEqual("foo");
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 () => {
+8 -4
View File
@@ -10,6 +10,7 @@ const {
} = require("./git");
const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
async function run() {
try {
@@ -62,6 +63,7 @@ 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")
};
@@ -83,15 +85,17 @@ 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;
// Get the repository path
var repoPath = getRepoPath(inputs.path);
// Get the extraheader config option if it exists
var extraHeaderOptionValue = await getAndUnsetConfigOption(
var extraHeaderOption = await getAndUnsetConfigOption(
repoPath,
EXTRAHEADER_OPTION
EXTRAHEADER_OPTION,
EXTRAHEADER_VALUE_REGEX
);
// Execute create pull request
@@ -100,12 +104,12 @@ async function run() {
core.setFailed(error.message);
} finally {
// Restore the extraheader config option
if (extraHeaderOptionValue) {
if (extraHeaderOption) {
if (
await addConfigOption(
repoPath,
EXTRAHEADER_OPTION,
extraHeaderOptionValue
extraHeaderOption.value
)
)
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);