Compare commits
126 Commits
v1.5.3-mul
...
v2.1.0
Author | SHA1 | Date | |
---|---|---|---|
8246a6aea9 | |||
1e09ec2f22 | |||
4c018f4174 | |||
080aaa90aa | |||
6ac64298f6 | |||
76e932f3c9 | |||
06078e295b | |||
004434a414 | |||
4661d6d7af | |||
a972260284 | |||
f1d6c2dca3 | |||
aa1cba4c18 | |||
9b33a4edd8 | |||
938e6aea6f | |||
699d304845 | |||
2ce0b003ee | |||
10653669e8 | |||
c39f04a86a | |||
430f28afbf | |||
8e07307f04 | |||
c8fed30c65 | |||
11c388252c | |||
e41956c7e7 | |||
f3583f71f8 | |||
e8e53ed431 | |||
cff82c7ec9 | |||
c388aba95a | |||
02f0ffd558 | |||
b616fd026e | |||
ba79823749 | |||
0af5090b86 | |||
b750589747 | |||
894d4b1412 | |||
95615643f0 | |||
fb48683668 | |||
0fb7b7c32a | |||
3e4ab24cbb | |||
8c86d0f83f | |||
673922cfd2 | |||
b11cb71e96 | |||
bdadb3b4a0 | |||
9bdc8b271f | |||
c208033c6a | |||
01aec28fd9 | |||
484de7fc89 | |||
71f4fe31a8 | |||
883f800b96 | |||
b3805d65e3 | |||
4f8d5b9d3e | |||
efbd4fa1ef | |||
b7565b81a7 | |||
ec6352b0a0 | |||
ce4df54075 | |||
c855a500d1 | |||
baf52378ae | |||
e6ebd7ca5c | |||
83fef4e8a1 | |||
d3dc225920 | |||
6342438c4e | |||
324fe6cdde | |||
4faa5d5a97 | |||
381bc30cef | |||
7531167f24 | |||
66a1436815 | |||
1d1fedd99c | |||
823751817d | |||
1edcf07a19 | |||
4c95214a9b | |||
b37c0a038c | |||
d9841567d1 | |||
2cce94bfb0 | |||
d968e8b11b | |||
6c73093f9b | |||
d17c882ef3 | |||
d5f4e48a66 | |||
484d8bd85d | |||
74b6a9337d | |||
2dd62d446e | |||
b6a98c049d | |||
67e8822279 | |||
956a240181 | |||
179aca65d7 | |||
e4c8b68e33 | |||
731211fd81 | |||
006313b45a | |||
3c86e9a421 | |||
e49ebca896 | |||
d9652cf06a | |||
4517bf7ce3 | |||
04aa84a2af | |||
11baa5dc07 | |||
1281ebd51a | |||
46dc4f23d5 | |||
d469aaf0ef | |||
5692e041c6 | |||
55e7b1ec28 | |||
d99b58f9d5 | |||
b8ce455e56 | |||
c9e477ec05 | |||
979d417e9d | |||
b74a77cefc | |||
2af7d9797a | |||
b72489d629 | |||
1461de7c9d | |||
3061b7bc6e | |||
98ee7d63d3 | |||
066b4398c0 | |||
028e1bbdd5 | |||
8b8594e5e4 | |||
cefb562076 | |||
9eccdd1603 | |||
f429dfef59 | |||
963e38fd7d | |||
f411b5902d | |||
97dd1f3ef4 | |||
3413a77ac7 | |||
206756cd93 | |||
263b5927d1 | |||
a0ad81c6c7 | |||
23846c7f83 | |||
96ac64ddca | |||
a2ed0052e5 | |||
6e7e889e01 | |||
7e18a60b72 | |||
0be4e396cf | |||
b86bbce924 |
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
7
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
### Subject of the issue
|
||||
|
||||
Describe your issue here.
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
If this issue is describing a possible bug please provide (or link to) your GitHub Actions workflow.
|
43
.github/workflows/cpr-example-command.yml
vendored
Normal file
43
.github/workflows/cpr-example-command.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
name: Create Pull Request Example Command
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [cpr-example-command]
|
||||
jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Create report file
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: ./
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Add report file
|
||||
committer: Peter Evans <peter-evans@users.noreply.github.com>
|
||||
title: '[Example] Add report file'
|
||||
body: |
|
||||
New report
|
||||
- Contains *today's* date
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
labels: report, automated pr
|
||||
assignees: peter-evans
|
||||
reviewers: peter-evans
|
||||
milestone: 1
|
||||
project: Example Project
|
||||
project-column: To do
|
||||
branch: example-patches
|
||||
- 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
|
45
.github/workflows/create-pull-request-multi.yml
vendored
45
.github/workflows/create-pull-request-multi.yml
vendored
@ -1,45 +0,0 @@
|
||||
name: create-pull-request workflow
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pull-request-multi]
|
||||
jobs:
|
||||
createPullRequest:
|
||||
name: Testing on ${{ matrix.platform }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Create report file
|
||||
if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest'
|
||||
run: date +%s > report.txt
|
||||
- name: Create report file (windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: echo %DATE% %TIME% > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@multi-platform-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MESSAGE: Add report file
|
||||
COMMIT_AUTHOR_EMAIL: peter-evans@users.noreply.github.com
|
||||
COMMIT_AUTHOR_NAME: Peter Evans
|
||||
PULL_REQUEST_TITLE: '[Example] Add report file'
|
||||
PULL_REQUEST_BODY: |
|
||||
New report
|
||||
- Contains *today's* date
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
PULL_REQUEST_LABELS: report, automated pr
|
||||
PULL_REQUEST_ASSIGNEES: peter-evans
|
||||
PULL_REQUEST_REVIEWERS: peter-evans
|
||||
PULL_REQUEST_MILESTONE: 1
|
||||
PULL_REQUEST_BRANCH: example-patches
|
||||
BRANCH_SUFFIX: 'random'
|
||||
- name: Check output environment variable
|
||||
if: matrix.platform == 'ubuntu-latest' || matrix.platform == 'macos-latest'
|
||||
run: echo "Pull Request Number - $PULL_REQUEST_NUMBER"
|
||||
- name: Check output environment variable (windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: echo Pull Request Number - %PULL_REQUEST_NUMBER%
|
33
.github/workflows/create-pull-request.yml
vendored
33
.github/workflows/create-pull-request.yml
vendored
@ -1,33 +0,0 @@
|
||||
name: create-pull-request workflow
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pull-request]
|
||||
jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Create report file
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: ./
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MESSAGE: Add report file
|
||||
COMMIT_AUTHOR_EMAIL: peter-evans@users.noreply.github.com
|
||||
COMMIT_AUTHOR_NAME: Peter Evans
|
||||
PULL_REQUEST_TITLE: '[Example] Add report file'
|
||||
PULL_REQUEST_BODY: |
|
||||
New report
|
||||
- Contains *today's* date
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
PULL_REQUEST_LABELS: report, automated pr
|
||||
PULL_REQUEST_ASSIGNEES: peter-evans
|
||||
PULL_REQUEST_REVIEWERS: peter-evans
|
||||
PULL_REQUEST_MILESTONE: 1
|
||||
PULL_REQUEST_BRANCH: example-patches
|
||||
BRANCH_SUFFIX: short-commit-hash
|
||||
- name: Check output environment variable
|
||||
run: echo "Pull Request Number - $PULL_REQUEST_NUMBER"
|
5
.github/workflows/dockerhub-description.yml
vendored
5
.github/workflows/dockerhub-description.yml
vendored
@ -2,7 +2,10 @@ name: Update Docker Hub Description
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- master
|
||||
paths:
|
||||
- README.md
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
|
38
.github/workflows/slash-command-dispatch.yml
vendored
Normal file
38
.github/workflows/slash-command-dispatch.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Slash Command Dispatch
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
slashCommandDispatch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
reaction-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
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"
|
||||
}
|
||||
]
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
__pycache__
|
||||
node_modules
|
||||
.DS_Store
|
||||
|
20
Dockerfile
20
Dockerfile
@ -1,20 +0,0 @@
|
||||
FROM alpine:3.10.2
|
||||
|
||||
LABEL maintainer="Peter Evans <mail@peterevans.dev>"
|
||||
LABEL repository="https://github.com/peter-evans/create-pull-request"
|
||||
LABEL homepage="https://github.com/peter-evans/create-pull-request"
|
||||
|
||||
LABEL com.github.actions.name="Create Pull Request"
|
||||
LABEL com.github.actions.description="Creates a pull request for changes to your repository in the actions workspace"
|
||||
LABEL com.github.actions.icon="git-pull-request"
|
||||
LABEL com.github.actions.color="gray-dark"
|
||||
|
||||
COPY LICENSE README.md /
|
||||
|
||||
RUN apk add python3-dev git git-lfs
|
||||
|
||||
COPY requirements.txt /tmp/
|
||||
RUN pip3 install --requirement /tmp/requirements.txt
|
||||
|
||||
COPY create-pull-request.py /create-pull-request.py
|
||||
ENTRYPOINT [ "/create-pull-request.py" ]
|
327
README.md
327
README.md
@ -1,4 +1,4 @@
|
||||
# <img width="24" height="24" src="logo.svg"> Create Pull Request
|
||||
# <img width="24" height="24" src="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.
|
||||
@ -9,249 +9,192 @@ The changes will be automatically committed to a new branch and a pull request c
|
||||
|
||||
Create Pull Request action will:
|
||||
|
||||
1. Check for repository changes in the Actions workspace. This includes untracked (new) files as well as modified files.
|
||||
1. Check for repository changes in the Actions workspace. This includes:
|
||||
- untracked (new) files
|
||||
- tracked (modified) files
|
||||
- commits made during the workflow that have not been pushed
|
||||
2. Commit all changes to a new branch, or update an existing pull request branch.
|
||||
3. Create a pull request to merge the new branch into the currently active branch executing the workflow.
|
||||
3. Create a pull request to merge the new branch into the base—the branch checked out in the workflow.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Concepts and guidelines](docs/concepts-guidelines.md)
|
||||
- [Examples](docs/examples.md)
|
||||
- [Updating from v1](docs/updating.md)
|
||||
|
||||
## Usage
|
||||
|
||||
Linux
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
Multi platform - Linux, MacOS, Windows (beta)
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2-multi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
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 instead of the default `GITHUB_TOKEN`.
|
||||
### Action inputs
|
||||
|
||||
With the exception of `token`, all inputs are **optional**. If not set, sensible default values will be used.
|
||||
|
||||
**Note**: If you want pull requests created by this action to trigger an `on: pull_request` workflow then you must use a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) instead of the default `GITHUB_TOKEN`.
|
||||
See [this issue](https://github.com/peter-evans/create-pull-request/issues/48) for further details.
|
||||
|
||||
### Environment variables
|
||||
|
||||
These variables are *all optional*. If not set, sensible default values will be used.
|
||||
|
||||
| Name | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `COMMIT_MESSAGE` | The message to use when committing changes. | `Auto-committed changes by create-pull-request action` |
|
||||
| `COMMIT_AUTHOR_EMAIL` | The email address of the commit author. | For `push` events, the HEAD commit author. Otherwise, <GITHUB_ACTOR>@users.noreply.github.com, where `GITHUB_ACTOR` is the GitHub user that initiated the event. |
|
||||
| `COMMIT_AUTHOR_NAME` | The name of the commit author. | For `push` events, the HEAD commit author. Otherwise, <GITHUB_ACTOR>, the GitHub user that initiated the event. |
|
||||
| `PULL_REQUEST_TITLE` | The title of the pull request. | `Auto-generated by create-pull-request action` |
|
||||
| `PULL_REQUEST_BODY` | The body of the pull request. | `Auto-generated pull request by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub Action` |
|
||||
| `PULL_REQUEST_LABELS` | A comma separated list of labels. | none |
|
||||
| `PULL_REQUEST_ASSIGNEES` | A comma separated list of assignees (GitHub usernames). | none |
|
||||
| `PULL_REQUEST_REVIEWERS` | A comma separated list of reviewers (GitHub usernames) to request a review from. | none |
|
||||
| `PULL_REQUEST_TEAM_REVIEWERS` | A comma separated list of GitHub teams to request a review from. | none |
|
||||
| `PULL_REQUEST_MILESTONE` | The number of the milestone to associate this pull request with. | none |
|
||||
| `PULL_REQUEST_BRANCH` | The branch name. See **Branch naming** below for details. | `create-pull-request/patch` |
|
||||
| `PULL_REQUEST_BASE` | Overrides the base branch. **Use with caution!** | Defaults to the currently checked out branch. |
|
||||
| `BRANCH_SUFFIX` | The branch suffix type. Valid values are `short-commit-hash`, `timestamp`, `random` and `none`. See **Branch naming** below for details. | `short-commit-hash` |
|
||||
| `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. |
|
||||
| `title` | The title of the pull request. | `Changes by create-pull-request action` |
|
||||
| `body` | The body of the pull request. | `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` |
|
||||
| `labels` | A comma separated list of labels. | |
|
||||
| `assignees` | A comma separated list of assignees (GitHub usernames). | |
|
||||
| `reviewers` | A comma separated list of reviewers (GitHub usernames) to request a review from. | |
|
||||
| `team-reviewers` | A comma separated list of GitHub teams to request a review from. | |
|
||||
| `milestone` | The number of the milestone to associate this pull request with. | |
|
||||
| `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` |
|
||||
| `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. | |
|
||||
|
||||
**Output environment variables**
|
||||
**Outputs**
|
||||
|
||||
- `PULL_REQUEST_NUMBER` - The number of the pull request created.
|
||||
The pull request number is output as both an environment variable and a step output.
|
||||
Note that in order to read the step output the action step must have an id.
|
||||
|
||||
**Debug environment variables**
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Check outputs
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
|
||||
```
|
||||
|
||||
The following parameters are available for debugging and troubleshooting.
|
||||
### Checkout
|
||||
|
||||
- `DEBUG_EVENT` - If present, outputs the event data that triggered the workflow.
|
||||
- `SKIP_IGNORE` - If present, the `ignore_event` function will be skipped.
|
||||
This action expects repositories to be checked out with `actions/checkout@v2`.
|
||||
|
||||
If there is some reason you need to use `actions/checkout@v1` the following step can be added to checkout the branch.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v1
|
||||
- run: git checkout "${GITHUB_REF:11}"
|
||||
```
|
||||
|
||||
### Branch naming
|
||||
|
||||
For branch naming there are two strategies. Always create a new branch each time there are changes to be committed, OR, create a fixed-name pull request branch that will be updated with any new commits until it is merged or closed.
|
||||
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.
|
||||
|
||||
#### Strategy A - Always create a new pull request branch (default)
|
||||
#### Strategy A - Create and update a pull request branch (default)
|
||||
|
||||
This strategy is the default behaviour of the action. The input `branch` defaults to `create-pull-request/patch`. Changes will be committed to this branch and a pull request created. Any subsequent changes will be committed to the *same* branch and reflected in the open pull request. If the pull request is merged or closed a new one will be created. If subsequent changes cause the branch to no longer differ from the base the pull request will be automatically closed and the branch deleted.
|
||||
|
||||
#### Strategy B - Always create a new pull request branch
|
||||
|
||||
For this strategy there are three options to suffix the branch name.
|
||||
The branch name is defined by the variable `PULL_REQUEST_BRANCH` and defaults to `create-pull-request/patch`. The following options are values for `BRANCH_SUFFIX`.
|
||||
|
||||
- `short-commit-hash` (default) - Commits will be made to a branch suffixed with the short SHA1 commit hash. e.g. `create-pull-request/patch-fcdfb59`, `create-pull-request/patch-394710b`
|
||||
|
||||
- `timestamp` - Commits will be made to a branch suffixed by a timestamp. e.g. `create-pull-request/patch-1569322532`, `create-pull-request/patch-1569322552`
|
||||
The branch name is defined by the input `branch` and defaults to `create-pull-request/patch`. The following options are values for `branch-suffix`.
|
||||
|
||||
- `random` - Commits will be made to a branch suffixed with a random alpha-numeric string. This option should be used if multiple pull requests will be created during the execution of a workflow. e.g. `create-pull-request/patch-6qj97jr`, `create-pull-request/patch-5jrjhvd`
|
||||
|
||||
#### Strategy B - Create and update a pull request branch
|
||||
- `timestamp` - Commits will be made to a branch suffixed by a timestamp. e.g. `create-pull-request/patch-1569322532`, `create-pull-request/patch-1569322552`
|
||||
|
||||
To use this strategy, set `BRANCH_SUFFIX` to the value `none`. The variable `PULL_REQUEST_BRANCH` defaults to `create-pull-request/patch`. Commits will be made to this branch and a pull request created. Any subsequent changes will be committed to the *same* branch and reflected in the existing pull request.
|
||||
- `short-commit-hash` - Commits will be made to a branch suffixed with the short SHA1 commit hash. e.g. `create-pull-request/patch-fcdfb59`, `create-pull-request/patch-394710b`
|
||||
|
||||
### Ignoring files
|
||||
|
||||
If there are files or directories you want to ignore you can simply add them to a `.gitignore` file at the root of your repository. The action will respect this file.
|
||||
|
||||
## Examples
|
||||
### Committer and author
|
||||
|
||||
Here is an example that sets all the main environment variables.
|
||||
If neither `committer` or `author` inputs are supplied the action will default to making commits that appear to be made by the GitHub Actions bot user.
|
||||
|
||||
In most cases, where the committer and author are the same, just the committer can be set.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
committer: Peter Evans <peter-evans@users.noreply.github.com>
|
||||
```
|
||||
|
||||
### Controlling commits
|
||||
|
||||
As well as relying on the action to handle uncommitted changes, you can additionally make your own commits before the action runs.
|
||||
|
||||
```yml
|
||||
name: create-pull-request workflow
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pull-request]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Create commits
|
||||
run: |
|
||||
git config user.name 'Peter Evans'
|
||||
git config user.email 'peter-evans@users.noreply.github.com'
|
||||
date +%s > report.txt
|
||||
git commit -am "Modify tracked file during workflow"
|
||||
date +%s > new-report.txt
|
||||
git add -A
|
||||
git commit -m "Add untracked file during workflow"
|
||||
- name: Uncommitted change
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
## Reference Example
|
||||
|
||||
The following workflow is a reference example that sets all the main inputs.
|
||||
|
||||
See [examples](docs/examples.md) for more realistic use cases.
|
||||
|
||||
```yml
|
||||
name: Create Pull Request
|
||||
on: push
|
||||
jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Create report file
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MESSAGE: Add report file
|
||||
COMMIT_AUTHOR_EMAIL: peter-evans@users.noreply.github.com
|
||||
COMMIT_AUTHOR_NAME: Peter Evans
|
||||
PULL_REQUEST_TITLE: '[Example] Add report file'
|
||||
PULL_REQUEST_BODY: |
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Add report file
|
||||
committer: Peter Evans <peter-evans@users.noreply.github.com>
|
||||
author: Peter Evans <peter-evans@users.noreply.github.com>
|
||||
title: '[Example] Add report file'
|
||||
body: |
|
||||
New report
|
||||
- Contains *today's* date
|
||||
- Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
PULL_REQUEST_LABELS: report, automated pr
|
||||
PULL_REQUEST_ASSIGNEES: peter-evans
|
||||
PULL_REQUEST_REVIEWERS: peter-evans
|
||||
PULL_REQUEST_MILESTONE: 1
|
||||
PULL_REQUEST_BRANCH: example-patches
|
||||
BRANCH_SUFFIX: short-commit-hash
|
||||
- name: Check output environment variable
|
||||
run: echo "Pull Request Number - $PULL_REQUEST_NUMBER"
|
||||
```
|
||||
|
||||
This configuration will create pull requests that look like this:
|
||||
|
||||

|
||||
|
||||
|
||||
### Example workflow to automate periodic dependency updates
|
||||
|
||||
This example workflow executes once a week and will create a pull request for any dependency updates. This pattern will work well for updating any kind of static content from an external source.
|
||||
|
||||
```yml
|
||||
name: Update Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1'
|
||||
jobs:
|
||||
update-deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.x'
|
||||
- name: Update dependencies
|
||||
id: vars
|
||||
labels: report, automated pr
|
||||
assignees: peter-evans
|
||||
reviewers: peter-evans
|
||||
milestone: 1
|
||||
project: Example Project
|
||||
project-column: To do
|
||||
branch: example-patches
|
||||
- name: Check outputs
|
||||
run: |
|
||||
npm install -g npm-check-updates
|
||||
ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MESSAGE: update dependencies
|
||||
COMMIT_AUTHOR_EMAIL: peter-evans@users.noreply.github.com
|
||||
COMMIT_AUTHOR_NAME: Peter Evans
|
||||
PULL_REQUEST_TITLE: Automated Dependency Updates
|
||||
PULL_REQUEST_BODY: This is an auto-generated PR with dependency updates.
|
||||
PULL_REQUEST_LABELS: dep-updates, automated pr
|
||||
PULL_REQUEST_REVIEWERS: peter-evans
|
||||
PULL_REQUEST_BRANCH: dep-updates
|
||||
BRANCH_SUFFIX: none
|
||||
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
|
||||
```
|
||||
|
||||
### Example usage with "on: pull_request" workflows
|
||||
This reference configuration will create pull requests that look like this:
|
||||
|
||||
The following is an example workflow for a use-case where [autopep8 action](https://github.com/peter-evans/autopep8) runs as both a check on pull requests and raises a further pull request to apply code fixes. This is a pattern that would work well for any automated code linting and fixing.
|
||||
|
||||
How it works:
|
||||
|
||||
1. When a pull request is raised the workflow executes as a check
|
||||
2. If autopep8 makes any fixes a pull request will be raised for those fixes to be merged into the current pull request branch. The workflow then deliberately causes the check to fail.
|
||||
3. When the pull request containing the fixes is merged the workflow runs again. This time autopep8 makes no changes and the check passes.
|
||||
4. The original pull request can now be merged.
|
||||
|
||||
```yml
|
||||
name: autopep8
|
||||
on: pull_request
|
||||
jobs:
|
||||
autopep8:
|
||||
if: startsWith(github.head_ref, 'autopep8-patches') == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: autopep8
|
||||
id: autopep8
|
||||
uses: peter-evans/autopep8@v1.1.0
|
||||
with:
|
||||
args: --exit-code --recursive --in-place --aggressive --aggressive .
|
||||
- name: Set autopep8 branch name
|
||||
id: vars
|
||||
run: echo ::set-output name=branch-name::"autopep8-patches/$GITHUB_HEAD_REF"
|
||||
- name: Create Pull Request
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MESSAGE: autopep8 action fixes
|
||||
COMMIT_AUTHOR_EMAIL: peter-evans@users.noreply.github.com
|
||||
COMMIT_AUTHOR_NAME: Peter Evans
|
||||
PULL_REQUEST_TITLE: Fixes by autopep8 action
|
||||
PULL_REQUEST_BODY: This is an auto-generated PR with fixes by autopep8.
|
||||
PULL_REQUEST_LABELS: autopep8, automated pr
|
||||
PULL_REQUEST_REVIEWERS: peter-evans
|
||||
PULL_REQUEST_BRANCH: ${{ steps.vars.outputs.branch-name }}
|
||||
BRANCH_SUFFIX: none
|
||||
- name: Fail if autopep8 made changes
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
run: exit 1
|
||||
```
|
||||
|
||||
### Dynamic configuration using variables
|
||||
|
||||
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
||||
|
||||
The recommended method is to use `set-output`. Note that the step where output variables are defined must have an id.
|
||||
|
||||
```yml
|
||||
- name: Set output variables
|
||||
id: vars
|
||||
run: |
|
||||
echo ::set-output name=pr_title::"[Test] Add report file $(date +%d-%m-%Y)"
|
||||
echo ::set-output name=pr_body::"This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PULL_REQUEST_TITLE: ${{ steps.vars.outputs.pr_title }}
|
||||
PULL_REQUEST_BODY: ${{ steps.vars.outputs.pr_body }}
|
||||
```
|
||||
|
||||
Since the action reads environment variables from the system, it's technically not necessary to explicitly pass them as long as they exist in the environment. So the following method using `set-env` *also* works, but explicitly passing the configuration parameters using the previous method is perferred for its clarity.
|
||||
|
||||
```yml
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo ::set-env name=PULL_REQUEST_TITLE::"[Test] Add report file $(date +%d-%m-%Y)"
|
||||
echo ::set-env name=PULL_REQUEST_BODY::"This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v1.5.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||

|
||||
|
||||
## License
|
||||
|
||||
|
44
action.yml
44
action.yml
@ -1,9 +1,47 @@
|
||||
name: 'Create Pull Request'
|
||||
author: 'Peter Evans'
|
||||
description: 'Creates a pull request for changes to your repository in the actions workspace'
|
||||
inputs:
|
||||
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:
|
||||
description: 'The committer name and email address.'
|
||||
author:
|
||||
description: 'The author name and email address.'
|
||||
title:
|
||||
description: 'The title of the pull request.'
|
||||
body:
|
||||
description: 'The body of the pull request.'
|
||||
labels:
|
||||
description: 'A comma separated list of labels.'
|
||||
assignees:
|
||||
description: 'A comma separated list of assignees (GitHub usernames).'
|
||||
reviewers:
|
||||
description: 'A comma separated list of reviewers (GitHub usernames) to request a review from.'
|
||||
team-reviewers:
|
||||
description: 'A comma separated list of GitHub teams to request a review from.'
|
||||
milestone:
|
||||
description: 'The number of the milestone to associate this pull request with.'
|
||||
project:
|
||||
description: 'The name of the project for which a card should be created.'
|
||||
project-column:
|
||||
description: 'The name of the project column under which a card should be created.'
|
||||
branch:
|
||||
description: 'The pull request branch name.'
|
||||
base:
|
||||
description: 'The pull request base branch.'
|
||||
branch-suffix:
|
||||
description: 'The branch suffix type.'
|
||||
outputs:
|
||||
pr_number:
|
||||
description: 'The pull request number'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'index.js'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'git-pull-request'
|
||||
color: 'gray-dark'
|
||||
color: 'gray-dark'
|
||||
|
68
assets/cpr-gitgraph.htm
Normal file
68
assets/cpr-gitgraph.htm
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>create-pull-request GitHub action</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- partial:index.partial.html -->
|
||||
<div id="graph-container"></div>
|
||||
<!-- partial -->
|
||||
<script src='https://cdn.jsdelivr.net/npm/@gitgraph/js'></script>
|
||||
<script>
|
||||
const graphContainer = document.getElementById("graph-container");
|
||||
|
||||
const customTemplate = GitgraphJS.templateExtend(GitgraphJS.TemplateName.Metro, {
|
||||
commit: {
|
||||
message: {
|
||||
displayAuthor: false,
|
||||
displayHash: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Instantiate the graph.
|
||||
const gitgraph = GitgraphJS.createGitgraph(graphContainer, {
|
||||
template: customTemplate,
|
||||
orientation: "vertical-reverse"
|
||||
});
|
||||
|
||||
const master = gitgraph.branch("master");
|
||||
master.commit("Last commit on base");
|
||||
const localMaster = gitgraph.branch("<#1> master (local)");
|
||||
localMaster.commit({
|
||||
subject: "<uncommited changes>",
|
||||
body: "Changes made to the local base during the workflow",
|
||||
})
|
||||
const remotePatch = gitgraph.branch("create-pull-request/patch");
|
||||
remotePatch.merge({
|
||||
branch: localMaster,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes pushed to create the remote branch",
|
||||
},
|
||||
});
|
||||
master.commit("New commit on base");
|
||||
|
||||
const localMaster2 = gitgraph.branch("<#2> master (local)");
|
||||
localMaster2.commit({
|
||||
subject: "<uncommited changes>",
|
||||
body: "Changes made to the updated local base during the workflow",
|
||||
})
|
||||
remotePatch.merge({
|
||||
branch: localMaster2,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes force pushed to update the remote branch",
|
||||
},
|
||||
});
|
||||
|
||||
master.merge(remotePatch);
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
assets/cpr-gitgraph.png
Normal file
BIN
assets/cpr-gitgraph.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 207 KiB |
Before Width: | Height: | Size: 416 B After Width: | Height: | Size: 416 B |
BIN
assets/pull-request-example.png
Normal file
BIN
assets/pull-request-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 414 KiB |
@ -1,247 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
''' Create Pull Request '''
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
from git import Repo
|
||||
from github import Github
|
||||
|
||||
|
||||
def get_github_event(github_event_path):
|
||||
with open(github_event_path) as f:
|
||||
github_event = json.load(f)
|
||||
if bool(os.environ.get('DEBUG_EVENT')):
|
||||
print(os.environ['GITHUB_EVENT_NAME'])
|
||||
print(json.dumps(github_event, sort_keys=True, indent=2))
|
||||
return github_event
|
||||
|
||||
|
||||
def ignore_event(event_name, event_data):
|
||||
if event_name == "push":
|
||||
# Ignore push events on deleted branches
|
||||
# The event we want to ignore occurs when a PR is created but the repository owner decides
|
||||
# not to commit the changes. They close the PR and delete the branch. This creates a
|
||||
# "push" event that we want to ignore, otherwise it will create another branch and PR on
|
||||
# the same commit.
|
||||
deleted = "{deleted}".format(**event_data)
|
||||
if deleted == "True":
|
||||
print("Ignoring delete branch event.")
|
||||
return True
|
||||
ref = "{ref}".format(**event_data)
|
||||
if not ref.startswith('refs/heads/'):
|
||||
print("Ignoring events for tags and remotes.")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_head_short_sha1(repo):
|
||||
return repo.git.rev_parse('--short', 'HEAD')
|
||||
|
||||
|
||||
def get_random_suffix(size=7, chars=string.ascii_lowercase + string.digits):
|
||||
return ''.join(random.choice(chars) for _ in range(size))
|
||||
|
||||
|
||||
def remote_branch_exists(repo, branch):
|
||||
for ref in repo.remotes.origin.refs:
|
||||
if ref.name == ("origin/%s" % branch):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_author_default(event_name, event_data):
|
||||
if event_name == "push":
|
||||
email = "{head_commit[author][email]}".format(**event_data)
|
||||
name = "{head_commit[author][name]}".format(**event_data)
|
||||
else:
|
||||
email = os.environ['GITHUB_ACTOR'] + '@users.noreply.github.com'
|
||||
name = os.environ['GITHUB_ACTOR']
|
||||
return email, name
|
||||
|
||||
|
||||
def set_git_config(git, email, name):
|
||||
git.config('--global', 'user.email', '"%s"' % email)
|
||||
git.config('--global', 'user.name', '"%s"' % name)
|
||||
|
||||
|
||||
def set_git_remote_url(git, token, github_repository):
|
||||
git.remote(
|
||||
'set-url', 'origin', "https://x-access-token:%s@github.com/%s" %
|
||||
(token, github_repository))
|
||||
|
||||
|
||||
def checkout_branch(git, remote_exists, branch):
|
||||
if remote_exists:
|
||||
git.stash('--include-untracked')
|
||||
git.checkout(branch)
|
||||
try:
|
||||
git.stash('pop')
|
||||
except BaseException:
|
||||
git.checkout('--theirs', '.')
|
||||
git.reset()
|
||||
else:
|
||||
git.checkout('HEAD', b=branch)
|
||||
|
||||
|
||||
def push_changes(git, branch, commit_message):
|
||||
git.add('-A')
|
||||
git.commit(m=commit_message)
|
||||
return git.push('-f', '--set-upstream', 'origin', branch)
|
||||
|
||||
|
||||
def cs_string_to_list(str):
|
||||
# Split the comma separated string into a list
|
||||
l = [i.strip() for i in str.split(',')]
|
||||
# Remove empty strings
|
||||
return list(filter(None, l))
|
||||
|
||||
|
||||
def process_event(event_name, event_data, repo, branch, base, remote_exists):
|
||||
# Fetch required environment variables
|
||||
github_token = os.environ['GITHUB_TOKEN']
|
||||
github_repository = os.environ['GITHUB_REPOSITORY']
|
||||
# Fetch optional environment variables with default values
|
||||
commit_message = os.getenv(
|
||||
'COMMIT_MESSAGE',
|
||||
"Auto-committed changes by create-pull-request action")
|
||||
title = os.getenv(
|
||||
'PULL_REQUEST_TITLE',
|
||||
"Auto-generated by create-pull-request action")
|
||||
body = os.getenv(
|
||||
'PULL_REQUEST_BODY', "Auto-generated pull request by "
|
||||
"[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub Action")
|
||||
# Fetch optional environment variables with no default values
|
||||
pull_request_labels = os.environ.get('PULL_REQUEST_LABELS')
|
||||
pull_request_assignees = os.environ.get('PULL_REQUEST_ASSIGNEES')
|
||||
pull_request_milestone = os.environ.get('PULL_REQUEST_MILESTONE')
|
||||
pull_request_reviewers = os.environ.get('PULL_REQUEST_REVIEWERS')
|
||||
pull_request_team_reviewers = os.environ.get('PULL_REQUEST_TEAM_REVIEWERS')
|
||||
|
||||
# Update URL for the 'origin' remote
|
||||
set_git_remote_url(repo.git, github_token, github_repository)
|
||||
|
||||
# Push the local changes to the remote branch
|
||||
print("Pushing changes.")
|
||||
push_result = push_changes(repo.git, branch, commit_message)
|
||||
print(push_result)
|
||||
|
||||
# If the remote existed then a PR likely exists and we can finish here
|
||||
if remote_exists:
|
||||
print("Updated pull request branch %s." % branch)
|
||||
sys.exit()
|
||||
|
||||
# Create the pull request
|
||||
print("Creating a request to pull %s into %s." % (branch, base))
|
||||
github_repo = Github(github_token).get_repo(github_repository)
|
||||
pull_request = github_repo.create_pull(
|
||||
title=title,
|
||||
body=body,
|
||||
base=base,
|
||||
head=branch)
|
||||
print("Created pull request %d." % pull_request.number)
|
||||
os.system(
|
||||
'echo ::set-env name=PULL_REQUEST_NUMBER::%d' %
|
||||
pull_request.number)
|
||||
|
||||
# Set labels, assignees and milestone
|
||||
if pull_request_labels is not None:
|
||||
print("Applying labels")
|
||||
pull_request.as_issue().edit(labels=cs_string_to_list(pull_request_labels))
|
||||
if pull_request_assignees is not None:
|
||||
print("Applying assignees")
|
||||
pull_request.as_issue().edit(assignees=cs_string_to_list(pull_request_assignees))
|
||||
if pull_request_milestone is not None:
|
||||
print("Applying milestone")
|
||||
milestone = github_repo.get_milestone(int(pull_request_milestone))
|
||||
pull_request.as_issue().edit(milestone=milestone)
|
||||
|
||||
# Set pull request reviewers and team reviewers
|
||||
if pull_request_reviewers is not None:
|
||||
print("Requesting reviewers")
|
||||
pull_request.create_review_request(
|
||||
reviewers=cs_string_to_list(pull_request_reviewers))
|
||||
if pull_request_team_reviewers is not None:
|
||||
print("Requesting team reviewers")
|
||||
pull_request.create_review_request(
|
||||
team_reviewers=cs_string_to_list(pull_request_team_reviewers))
|
||||
|
||||
|
||||
# Get the JSON event data
|
||||
event_name = os.environ['GITHUB_EVENT_NAME']
|
||||
event_data = get_github_event(os.environ['GITHUB_EVENT_PATH'])
|
||||
# Check if this event should be ignored
|
||||
skip_ignore_event = bool(os.environ.get('SKIP_IGNORE'))
|
||||
if skip_ignore_event or not ignore_event(event_name, event_data):
|
||||
# Set the repo to the working directory
|
||||
repo = Repo(os.getcwd())
|
||||
|
||||
# Fetch/Set the branch name
|
||||
branch = os.getenv('PULL_REQUEST_BRANCH', 'create-pull-request/patch')
|
||||
|
||||
# Set the base branch
|
||||
github_ref = os.environ['GITHUB_REF']
|
||||
if github_ref.startswith('refs/pull/'):
|
||||
# Switch to the merging branch instead of the merge commit
|
||||
base = os.environ['GITHUB_HEAD_REF']
|
||||
else:
|
||||
base = github_ref[11:]
|
||||
|
||||
# Optional base override
|
||||
base = os.getenv('PULL_REQUEST_BASE', base)
|
||||
|
||||
# Checkout the base branch
|
||||
repo.git.checkout(base)
|
||||
|
||||
# Skip if the current branch is a PR branch created by this action
|
||||
if base.startswith(branch):
|
||||
print("Branch '%s' was created by this action. Skipping." % base)
|
||||
sys.exit()
|
||||
|
||||
# Fetch an optional environment variable to determine the branch suffix
|
||||
branch_suffix = os.getenv('BRANCH_SUFFIX', 'short-commit-hash')
|
||||
if branch_suffix == "short-commit-hash":
|
||||
# Suffix with the short SHA1 hash
|
||||
branch = "%s-%s" % (branch, get_head_short_sha1(repo))
|
||||
elif branch_suffix == "timestamp":
|
||||
# Suffix with the current timestamp
|
||||
branch = "%s-%s" % (branch, int(time.time()))
|
||||
elif branch_suffix == "random":
|
||||
# Suffix with the current timestamp
|
||||
branch = "%s-%s" % (branch, get_random_suffix())
|
||||
|
||||
# Check if the remote branch exists
|
||||
remote_exists = remote_branch_exists(repo, branch)
|
||||
|
||||
# If using short commit hash prefixes, check if a remote
|
||||
# branch already exists for this HEAD commit
|
||||
if branch_suffix == 'short-commit-hash' and remote_exists:
|
||||
print(
|
||||
"Pull request branch '%s' already exists for this commit. Skipping." %
|
||||
branch)
|
||||
sys.exit()
|
||||
|
||||
# Get the default for author email and name
|
||||
author_email, author_name = get_author_default(event_name, event_data)
|
||||
# Set commit author overrides
|
||||
author_email = os.getenv('COMMIT_AUTHOR_EMAIL', author_email)
|
||||
author_name = os.getenv('COMMIT_AUTHOR_NAME', author_name)
|
||||
# Set git configuration
|
||||
set_git_config(repo.git, author_email, author_name)
|
||||
# Checkout branch
|
||||
checkout_branch(repo.git, remote_exists, branch)
|
||||
|
||||
# Check if there are changes to pull request
|
||||
if repo.is_dirty() or len(repo.untracked_files) > 0:
|
||||
print("Repository has modified or untracked files.")
|
||||
process_event(
|
||||
event_name,
|
||||
event_data,
|
||||
repo,
|
||||
branch,
|
||||
base,
|
||||
remote_exists)
|
||||
else:
|
||||
print("Repository has no modified or untracked files. Skipping.")
|
5546
dist/index.js
vendored
Normal file
5546
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
dist/src/common.py
vendored
Normal file
31
dist/src/common.py
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
|
||||
|
||||
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>
|
||||
pattern = re.compile(r"^([^<]+)\s*<([^>]+)>$")
|
||||
|
||||
# Check we have a match
|
||||
match = pattern.match(display_name_email)
|
||||
if match is None:
|
||||
raise ValueError(
|
||||
f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
# Check that name and email are not just whitespace
|
||||
name = match.group(1).strip()
|
||||
email = match.group(2).strip()
|
||||
if len(name) == 0 or len(email) == 0:
|
||||
raise ValueError(
|
||||
f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
return name, email
|
145
dist/src/create_or_update_branch.py
vendored
Normal file
145
dist/src/create_or_update_branch.py
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create or Update Branch """
|
||||
import common as cmn
|
||||
from git import Repo, GitCommandError
|
||||
import os
|
||||
|
||||
|
||||
CHERRYPICK_EMPTY = (
|
||||
"The previous cherry-pick is now empty, possibly due to conflict resolution."
|
||||
)
|
||||
|
||||
|
||||
def fetch_successful(repo, repo_url, branch):
|
||||
try:
|
||||
repo.git.fetch(repo_url, f"{branch}:refs/remotes/origin/{branch}")
|
||||
except GitCommandError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_ahead(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is ahead of branch_1
|
||||
return (
|
||||
int(repo.git.rev_list("--right-only", "--count", f"{branch_1}...{branch_2}"))
|
||||
> 0
|
||||
)
|
||||
|
||||
|
||||
def is_behind(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is behind branch_1
|
||||
return (
|
||||
int(repo.git.rev_list("--left-only", "--count", f"{branch_1}...{branch_2}")) > 0
|
||||
)
|
||||
|
||||
|
||||
def is_even(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is even with branch_1
|
||||
return not is_ahead(repo, branch_1, branch_2) and not is_behind(
|
||||
repo, branch_1, branch_2
|
||||
)
|
||||
|
||||
|
||||
def has_diff(repo, branch_1, branch_2):
|
||||
diff = repo.git.diff(f"{branch_1}..{branch_2}")
|
||||
return len(diff) > 0
|
||||
|
||||
|
||||
def create_or_update_branch(repo, repo_url, commit_message, base, branch):
|
||||
# Set the default return values
|
||||
action = "none"
|
||||
diff = False
|
||||
|
||||
# Get the working base. This may or may not be the actual base.
|
||||
working_base = repo.git.symbolic_ref("HEAD", "--short")
|
||||
# If the base is not specified it is assumed to be the working base
|
||||
if base is None:
|
||||
base = working_base
|
||||
|
||||
# Save the working base changes to a temporary branch
|
||||
temp_branch = cmn.get_random_string(length=20)
|
||||
repo.git.checkout("HEAD", b=temp_branch)
|
||||
# Commit any uncomitted changes
|
||||
if repo.is_dirty(untracked_files=True):
|
||||
print(f"Uncommitted changes found. Adding a commit.")
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m=commit_message)
|
||||
|
||||
# Perform fetch and reset the working base
|
||||
# Commits made during the workflow will be removed
|
||||
repo.git.fetch("--force", repo_url, f"{working_base}:{working_base}")
|
||||
|
||||
# If the working base is not the base, rebase the temp branch commits
|
||||
if working_base != base:
|
||||
print(
|
||||
f"Rebasing commits made to branch '{working_base}' on to base branch '{base}'"
|
||||
)
|
||||
# Checkout the actual base
|
||||
repo.git.fetch("--force", repo_url, f"{base}:{base}")
|
||||
repo.git.checkout(base)
|
||||
# Cherrypick commits from the temporary branch starting from the working base
|
||||
commits = repo.git.rev_list("--reverse", f"{working_base}..{temp_branch}", ".")
|
||||
for commit in commits.splitlines():
|
||||
try:
|
||||
repo.git.cherry_pick(
|
||||
"--strategy",
|
||||
"recursive",
|
||||
"--strategy-option",
|
||||
"theirs",
|
||||
f"{commit}",
|
||||
)
|
||||
except GitCommandError as e:
|
||||
if CHERRYPICK_EMPTY not in e.stderr:
|
||||
print("Unexpected error: ", e)
|
||||
raise
|
||||
# Reset the temp branch to the working index
|
||||
repo.git.checkout("-B", temp_branch, "HEAD")
|
||||
# Reset the base
|
||||
repo.git.fetch("--force", repo_url, f"{base}:{base}")
|
||||
|
||||
# Try to fetch the pull request branch
|
||||
if not fetch_successful(repo, repo_url, branch):
|
||||
# The pull request branch does not exist
|
||||
print(f"Pull request branch '{branch}' does not exist yet")
|
||||
# Create the pull request branch
|
||||
repo.git.checkout("HEAD", b=branch)
|
||||
# Check if the pull request branch is ahead of the base
|
||||
diff = is_ahead(repo, base, branch)
|
||||
if diff:
|
||||
action = "created"
|
||||
print(f"Created branch '{branch}'")
|
||||
else:
|
||||
print(
|
||||
f"Branch '{branch}' is not ahead of base '{base}' and will not be created"
|
||||
)
|
||||
else:
|
||||
# The pull request branch exists
|
||||
print(
|
||||
f"Pull request branch '{branch}' already exists as remote branch 'origin/{branch}'"
|
||||
)
|
||||
# Checkout the pull request branch
|
||||
repo.git.checkout(branch)
|
||||
|
||||
if has_diff(repo, branch, temp_branch):
|
||||
# If the branch differs from the recreated temp version then the branch is reset
|
||||
# For changes on base this action is similar to a rebase of the pull request branch
|
||||
print(f"Resetting '{branch}'")
|
||||
repo.git.checkout("-B", branch, temp_branch)
|
||||
# repo.git.switch("-C", branch, temp_branch)
|
||||
|
||||
# Check if the pull request branch has been updated
|
||||
# If the branch was reset or updated it will be ahead
|
||||
# It may be behind if a reset now results in no diff with the base
|
||||
if not is_even(repo, f"origin/{branch}", branch):
|
||||
action = "updated"
|
||||
print(f"Updated branch '{branch}'")
|
||||
else:
|
||||
print(f"Branch '{branch}' is even with its remote and will not be updated")
|
||||
|
||||
# Check if the pull request branch is ahead of the base
|
||||
diff = is_ahead(repo, base, branch)
|
||||
|
||||
# Delete the temporary branch
|
||||
repo.git.branch("--delete", "--force", temp_branch)
|
||||
|
||||
return {"action": action, "diff": diff, "base": base}
|
126
dist/src/create_or_update_pull_request.py
vendored
Normal file
126
dist/src/create_or_update_pull_request.py
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create or Update Pull Request """
|
||||
from github import Github, GithubException
|
||||
import os
|
||||
|
||||
|
||||
def cs_string_to_list(str):
|
||||
# Split the comma separated string into a list
|
||||
l = [i.strip() for i in str.split(",")]
|
||||
# Remove empty strings
|
||||
return list(filter(None, l))
|
||||
|
||||
|
||||
def create_project_card(github_repo, project_name, project_column_name, pull_request):
|
||||
# Locate the project by name
|
||||
project = None
|
||||
for project_item in github_repo.get_projects("all"):
|
||||
if project_item.name == project_name:
|
||||
project = project_item
|
||||
break
|
||||
|
||||
if not project:
|
||||
print("::error::Project not found. Unable to create project card.")
|
||||
return
|
||||
|
||||
# Locate the column by name
|
||||
column = None
|
||||
for column_item in project.get_columns():
|
||||
if column_item.name == project_column_name:
|
||||
column = column_item
|
||||
break
|
||||
|
||||
if not column:
|
||||
print("::error::Project column not found. Unable to create project card.")
|
||||
return
|
||||
|
||||
# Create a project card for the pull request
|
||||
column.create_card(content_id=pull_request.id, content_type="PullRequest")
|
||||
print(
|
||||
"Added pull request #%d to project '%s' under column '%s'"
|
||||
% (pull_request.number, project.name, column.name)
|
||||
)
|
||||
|
||||
|
||||
def create_or_update_pull_request(
|
||||
github_token,
|
||||
github_repository,
|
||||
branch,
|
||||
base,
|
||||
title,
|
||||
body,
|
||||
labels,
|
||||
assignees,
|
||||
milestone,
|
||||
reviewers,
|
||||
team_reviewers,
|
||||
project_name,
|
||||
project_column_name,
|
||||
):
|
||||
# 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
|
||||
)
|
||||
print(f"Created pull request #{pull_request.number} ({branch} => {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})")
|
||||
else:
|
||||
print(str(e))
|
||||
raise
|
||||
|
||||
# Set the output variables
|
||||
os.system(f"echo ::set-env name=PULL_REQUEST_NUMBER::{pull_request.number}")
|
||||
os.system(f"echo ::set-output name=pr_number::{pull_request.number}")
|
||||
|
||||
# Set labels, assignees and milestone
|
||||
if labels is not None:
|
||||
print(f"Applying labels '{labels}'")
|
||||
pull_request.as_issue().edit(labels=cs_string_to_list(labels))
|
||||
if assignees is not None:
|
||||
print(f"Applying assignees '{assignees}'")
|
||||
pull_request.as_issue().edit(assignees=cs_string_to_list(assignees))
|
||||
if milestone is not None:
|
||||
print(f"Applying milestone '{milestone}'")
|
||||
milestone = github_repo.get_milestone(int(milestone))
|
||||
pull_request.as_issue().edit(milestone=milestone)
|
||||
|
||||
# Set pull request reviewers
|
||||
if reviewers is not None:
|
||||
print(f"Requesting reviewers '{reviewers}'")
|
||||
try:
|
||||
pull_request.create_review_request(reviewers=cs_string_to_list(reviewers))
|
||||
except GithubException as e:
|
||||
# Likely caused by "Review cannot be requested from pull request author."
|
||||
if e.status == 422:
|
||||
print("Request reviewers failed - {}".format(e.data["message"]))
|
||||
|
||||
# Set pull request team reviewers
|
||||
if team_reviewers is not None:
|
||||
print(f"Requesting team reviewers '{team_reviewers}'")
|
||||
pull_request.create_review_request(
|
||||
team_reviewers=cs_string_to_list(team_reviewers)
|
||||
)
|
||||
|
||||
# Create a project card for the pull request
|
||||
if project_name is not None and project_column_name is not None:
|
||||
try:
|
||||
create_project_card(
|
||||
github_repo, project_name, project_column_name, pull_request
|
||||
)
|
||||
except GithubException as e:
|
||||
# Likely caused by "Project already has the associated issue."
|
||||
if e.status == 422:
|
||||
print(
|
||||
"Create project card failed - {}".format(
|
||||
e.data["errors"][0]["message"]
|
||||
)
|
||||
)
|
205
dist/src/create_pull_request.py
vendored
Executable file
205
dist/src/create_pull_request.py
vendored
Executable file
@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create Pull Request """
|
||||
import common as cmn
|
||||
import create_or_update_branch as coub
|
||||
import create_or_update_pull_request as coupr
|
||||
from git import Repo, GitCommandError
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# Default the committer and author to the GitHub Actions bot
|
||||
DEFAULT_COMMITTER = "GitHub <noreply@github.com>"
|
||||
DEFAULT_AUTHOR = (
|
||||
"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
)
|
||||
DEFAULT_COMMIT_MESSAGE = "[create-pull-request] automated change"
|
||||
DEFAULT_TITLE = "Changes by create-pull-request action"
|
||||
DEFAULT_BODY = (
|
||||
"Automated changes by "
|
||||
+ "[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action"
|
||||
)
|
||||
DEFAULT_BRANCH = "create-pull-request/patch"
|
||||
|
||||
|
||||
def get_git_config_value(repo, name):
|
||||
try:
|
||||
return repo.git.config("--get", name)
|
||||
except GitCommandError:
|
||||
return None
|
||||
|
||||
|
||||
def git_user_config_is_set(repo):
|
||||
name = get_git_config_value(repo, "user.name")
|
||||
email = get_git_config_value(repo, "user.email")
|
||||
|
||||
if name is not None and email is not None:
|
||||
print(f"Git user already configured as '{name} <{email}>'")
|
||||
return True
|
||||
|
||||
committer_name = get_git_config_value(repo, "committer.name")
|
||||
committer_email = get_git_config_value(repo, "committer.email")
|
||||
author_name = get_git_config_value(repo, "author.name")
|
||||
author_email = get_git_config_value(repo, "author.email")
|
||||
|
||||
if (
|
||||
committer_name is not None
|
||||
and committer_email is not None
|
||||
and author_name is not None
|
||||
and author_email is not None
|
||||
):
|
||||
print(
|
||||
f"Git committer already configured as '{committer_name} <{committer_email}>'"
|
||||
)
|
||||
print(f"Git author already configured as '{author_name} <{author_email}>'")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def set_committer_author(repo, committer, author):
|
||||
# If either committer or author is supplied they will be cross used
|
||||
if committer is None and author is not None:
|
||||
print("Supplied author will also be used as the committer.")
|
||||
committer = author
|
||||
if author is None and committer is not None:
|
||||
print("Supplied committer will also be used as the author.")
|
||||
author = committer
|
||||
|
||||
# If no committer/author has been supplied but user configuration already
|
||||
# exists in git config we can exit and use the existing config as-is.
|
||||
if committer is None and author is None:
|
||||
if git_user_config_is_set(repo):
|
||||
return
|
||||
|
||||
# Set defaults if no committer/author has been supplied
|
||||
if committer is None and author is None:
|
||||
committer = DEFAULT_COMMITTER
|
||||
author = DEFAULT_AUTHOR
|
||||
|
||||
# Set git environment. This will not persist after the action completes.
|
||||
committer_name, committer_email = cmn.parse_display_name_email(committer)
|
||||
author_name, author_email = cmn.parse_display_name_email(author)
|
||||
repo.git.update_environment(
|
||||
GIT_COMMITTER_NAME=committer_name,
|
||||
GIT_COMMITTER_EMAIL=committer_email,
|
||||
GIT_AUTHOR_NAME=author_name,
|
||||
GIT_AUTHOR_EMAIL=author_email,
|
||||
)
|
||||
print(f"Configured git committer as '{committer_name} <{committer_email}>'")
|
||||
print(f"Configured git author as '{author_name} <{author_email}>'")
|
||||
|
||||
|
||||
# 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'
|
||||
committer = os.environ.get("CPR_COMMITTER")
|
||||
author = os.environ.get("CPR_AUTHOR")
|
||||
base = os.environ.get("CPR_BASE")
|
||||
|
||||
# Set the repo path
|
||||
repo = Repo(path)
|
||||
|
||||
# Determine if the checked out ref is a valid base for a pull request
|
||||
# The action needs the checked out HEAD ref to be a branch
|
||||
# This check will fail in the following cases:
|
||||
# - HEAD is detached
|
||||
# - HEAD is a merge commit (pull_request events)
|
||||
# - HEAD is a tag
|
||||
try:
|
||||
working_base = repo.git.symbolic_ref("HEAD", "--short")
|
||||
except GitCommandError as e:
|
||||
print(f"::debug::{e.stderr}")
|
||||
print(
|
||||
f"::error::The checked out ref is not a valid base for a pull request. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Exit if the working base is a PR branch created by this action.
|
||||
# This may occur when using a PAT instead of GITHUB_TOKEN because
|
||||
# a PAT allows workflow actions to trigger further events.
|
||||
if working_base.startswith(branch):
|
||||
print(
|
||||
f"::error::Working base branch '{working_base}' was created by this action. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch an optional environment variable to determine the branch suffix
|
||||
branch_suffix = os.environ.get("CPR_BRANCH_SUFFIX")
|
||||
if branch_suffix is not None:
|
||||
if branch_suffix == "short-commit-hash":
|
||||
# Suffix with the short SHA1 hash
|
||||
branch = "{}-{}".format(branch, repo.git.rev_parse("--short", "HEAD"))
|
||||
elif branch_suffix == "timestamp":
|
||||
# Suffix with the current timestamp
|
||||
branch = "{}-{}".format(branch, int(time.time()))
|
||||
elif branch_suffix == "random":
|
||||
# Suffix with a 7 character random string
|
||||
branch = "{}-{}".format(branch, cmn.get_random_string())
|
||||
else:
|
||||
print(
|
||||
f"::error::Branch suffix '{branch_suffix}' is not a valid value. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Output head branch
|
||||
print(f"Pull request branch to create or update set to '{branch}'")
|
||||
|
||||
# Set the committer and author
|
||||
try:
|
||||
set_committer_author(repo, committer, author)
|
||||
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}'")
|
||||
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
|
||||
base = result["base"]
|
||||
|
||||
# If there is no longer a diff with the base delete the branch and exit
|
||||
if not result["diff"]:
|
||||
print(f"Branch '{branch}' no longer differs from base branch '{base}'")
|
||||
print(f"Closing pull request and deleting branch '{branch}'")
|
||||
repo.git.push("--delete", "--force", repo_url, f"refs/heads/{branch}")
|
||||
sys.exit()
|
||||
|
||||
# Fetch optional environment variables with default values
|
||||
title = os.getenv("CPR_TITLE", DEFAULT_TITLE)
|
||||
body = os.getenv("CPR_BODY", DEFAULT_BODY)
|
||||
|
||||
# Create or update the pull request
|
||||
coupr.create_or_update_pull_request(
|
||||
github_token,
|
||||
github_repository,
|
||||
branch,
|
||||
base,
|
||||
title,
|
||||
body,
|
||||
os.environ.get("CPR_LABELS"),
|
||||
os.environ.get("CPR_ASSIGNEES"),
|
||||
os.environ.get("CPR_MILESTONE"),
|
||||
os.environ.get("CPR_REVIEWERS"),
|
||||
os.environ.get("CPR_TEAM_REVIEWERS"),
|
||||
os.environ.get("CPR_PROJECT_NAME"),
|
||||
os.environ.get("CPR_PROJECT_COLUMN_NAME"),
|
||||
)
|
2
dist/src/requirements.txt
vendored
Normal file
2
dist/src/requirements.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
GitPython==3.0.5
|
||||
PyGithub==1.45
|
52
dist/src/setup-python.js
vendored
Normal file
52
dist/src/setup-python.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
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;
|
39
dist/src/test_common.py
vendored
Normal file
39
dist/src/test_common.py
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Test Common """
|
||||
import common as cmn
|
||||
import pytest
|
||||
|
||||
|
||||
def test_get_random_string():
|
||||
assert len(cmn.get_random_string()) == 7
|
||||
assert len(cmn.get_random_string(length=20)) == 20
|
||||
|
||||
|
||||
def test_parse_display_name_email_success():
|
||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||
assert name == "abc def"
|
||||
assert email == "abc@def.com"
|
||||
|
||||
name, email = cmn.parse_display_name_email(
|
||||
"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
)
|
||||
assert name == "github-actions[bot]"
|
||||
assert email == "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
|
||||
def test_parse_display_name_email_failure():
|
||||
display_name_email = "abc@def.com"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_display_name_email(display_name_email)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
display_name_email = " < >"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_display_name_email(display_name_email)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
757
dist/src/test_create_or_update_branch.py
vendored
Normal file
757
dist/src/test_create_or_update_branch.py
vendored
Normal file
@ -0,0 +1,757 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Test Create or Update Branch """
|
||||
import create_or_update_branch as coub
|
||||
from git import Repo
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# Set git repo
|
||||
REPO_PATH = os.getenv("COUB_REPO_PATH", os.getcwd())
|
||||
repo = Repo(REPO_PATH)
|
||||
|
||||
# Set git environment
|
||||
author_name = "github-actions[bot]"
|
||||
author_email = "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
committer_name = "GitHub"
|
||||
committer_email = "noreply@github.com"
|
||||
repo.git.update_environment(
|
||||
GIT_AUTHOR_NAME=author_name,
|
||||
GIT_AUTHOR_EMAIL=author_email,
|
||||
GIT_COMMITTER_NAME=committer_name,
|
||||
GIT_COMMITTER_EMAIL=committer_email,
|
||||
)
|
||||
|
||||
REPO_URL = repo.git.config("--get", "remote.origin.url")
|
||||
|
||||
TRACKED_FILE = "tracked-file.txt"
|
||||
UNTRACKED_FILE = "untracked-file.txt"
|
||||
|
||||
DEFAULT_BRANCH = "tests/master"
|
||||
NOT_BASE_BRANCH = "tests/branch-that-is-not-the-base"
|
||||
NOT_EXIST_BRANCH = "tests/branch-that-does-not-exist"
|
||||
|
||||
COMMIT_MESSAGE = "[create-pull-request] automated change"
|
||||
BRANCH = "tests/create-pull-request/patch"
|
||||
BASE = DEFAULT_BRANCH
|
||||
|
||||
|
||||
def create_tracked_change(content=None):
|
||||
if content is None:
|
||||
content = str(time.time())
|
||||
# Create a tracked file change
|
||||
with open(os.path.join(REPO_PATH, TRACKED_FILE), "w") as f:
|
||||
f.write(content)
|
||||
return content
|
||||
|
||||
|
||||
def create_untracked_change(content=None):
|
||||
if content is None:
|
||||
content = str(time.time())
|
||||
# Create an untracked file change
|
||||
with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "w") as f:
|
||||
f.write(content)
|
||||
return content
|
||||
|
||||
|
||||
def get_tracked_content():
|
||||
# Read the content of the tracked file
|
||||
with open(os.path.join(REPO_PATH, TRACKED_FILE), "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_untracked_content():
|
||||
# Read the content of the untracked file
|
||||
with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def create_changes(tracked_content=None, untracked_content=None):
|
||||
tracked_content = create_tracked_change(tracked_content)
|
||||
untracked_content = create_untracked_change(untracked_content)
|
||||
return tracked_content, untracked_content
|
||||
|
||||
|
||||
def create_commits(number=2, final_tracked_content=None, final_untracked_content=None):
|
||||
for i in range(number):
|
||||
commit_number = i + 1
|
||||
if commit_number == number:
|
||||
tracked_content, untracked_content = create_changes(
|
||||
final_tracked_content, final_untracked_content
|
||||
)
|
||||
else:
|
||||
tracked_content, untracked_content = create_changes()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m=f"Commit {commit_number}")
|
||||
return tracked_content, untracked_content
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def before_after_all():
|
||||
print("Before all tests")
|
||||
# Check there are no local changes that might be
|
||||
# destroyed by running these tests
|
||||
assert not repo.is_dirty(untracked_files=True)
|
||||
|
||||
# Create a new default branch for the test run
|
||||
repo.remotes.origin.fetch()
|
||||
repo.git.checkout("master")
|
||||
repo.git.checkout("HEAD", b=NOT_BASE_BRANCH)
|
||||
create_tracked_change()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m="This commit should not appear in pr branches")
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{NOT_BASE_BRANCH}")
|
||||
# Create a new default branch for the test run
|
||||
repo.git.checkout("master")
|
||||
repo.git.checkout("HEAD", b=DEFAULT_BRANCH)
|
||||
create_tracked_change()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m="Add file to be a tracked file for tests")
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
|
||||
yield
|
||||
|
||||
print("After all tests")
|
||||
repo.git.checkout("master")
|
||||
# Delete the "not base branch" created for the test run
|
||||
repo.git.branch("--delete", "--force", NOT_BASE_BRANCH)
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{NOT_BASE_BRANCH}")
|
||||
# Delete the default branch created for the test run
|
||||
repo.git.branch("--delete", "--force", DEFAULT_BRANCH)
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{DEFAULT_BRANCH}")
|
||||
|
||||
|
||||
def before_test():
|
||||
print("Before test")
|
||||
# Checkout the default branch
|
||||
repo.git.checkout(DEFAULT_BRANCH)
|
||||
|
||||
|
||||
def after_test(delete_remote=True):
|
||||
print("After test")
|
||||
# Output git log
|
||||
print(repo.git.log("-5", pretty="oneline"))
|
||||
# Delete the pull request branch if it exists
|
||||
repo.git.checkout(DEFAULT_BRANCH)
|
||||
print(f"Deleting {BRANCH}")
|
||||
for branch in repo.branches:
|
||||
if branch.name == BRANCH:
|
||||
repo.git.branch("--delete", "--force", BRANCH)
|
||||
break
|
||||
if delete_remote:
|
||||
print(f"Deleting origin/{BRANCH}")
|
||||
for ref in repo.remotes.origin.refs:
|
||||
if ref.name == f"origin/{BRANCH}":
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch("--prune")
|
||||
break
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def before_after_tests():
|
||||
before_test()
|
||||
yield
|
||||
after_test()
|
||||
|
||||
|
||||
# Tests if a branch exists and can be fetched
|
||||
def coub_fetch_successful():
|
||||
assert coub.fetch_successful(repo, REPO_URL, NOT_BASE_BRANCH)
|
||||
assert not coub.fetch_successful(repo, REPO_URL, NOT_EXIST_BRANCH)
|
||||
|
||||
|
||||
# Tests no changes resulting in no new branch being created
|
||||
def coub_no_changes_on_create():
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
|
||||
|
||||
# Tests create and update with a tracked file change
|
||||
def coub_tracked_changes():
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
|
||||
# Tests create and update with an untracked file change
|
||||
def coub_untracked_changes():
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with identical changes
|
||||
# The pull request branch will not be updated
|
||||
def coub_identical_changes():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create identical tracked and untracked file changes
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the base inbetween
|
||||
def coub_commits_on_base():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and then an update with no changes
|
||||
# This effectively reverts the branch back to match the base and results in no diff
|
||||
def coub_changes_no_diff():
|
||||
# Save the default branch tracked content
|
||||
default_tracked_content = get_tracked_content()
|
||||
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Running with no update effectively reverts the branch back to match the base
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == default_tracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the base inbetween
|
||||
# The changes on base effectively revert the branch back to match the base and results in no diff
|
||||
def coub_commits_on_base_no_diff():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create the same tracked and untracked file changes that were made to the base
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the working base (during the workflow)
|
||||
def coub_commits_on_working_base():
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
def coub_changes_and_commits_on_working_base():
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
# with commits on the base inbetween
|
||||
def coub_changes_and_commits_on_base_and_working_base():
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests no changes resulting in no new branch being created
|
||||
def coub_wbnb_no_changes_on_create():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with a tracked file change
|
||||
def coub_wbnb_tracked_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with an untracked file change
|
||||
def coub_wbnb_untracked_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with identical changes
|
||||
# The pull request branch will not be updated
|
||||
def coub_wbnb_identical_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create identical tracked and untracked file changes
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the base inbetween
|
||||
def coub_wbnb_commits_on_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and then an update with no changes
|
||||
# This effectively reverts the branch back to match the base and results in no diff
|
||||
def coub_wbnb_changes_no_diff():
|
||||
# Save the default branch tracked content
|
||||
default_tracked_content = get_tracked_content()
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Running with no update effectively reverts the branch back to match the base
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == default_tracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the base inbetween
|
||||
# The changes on base effectively revert the branch back to match the base and results in no diff
|
||||
# This scenario will cause cherrypick to fail due to an empty commit.
|
||||
# The commit is empty because the changes now exist on the base.
|
||||
def coub_wbnb_commits_on_base_no_diff():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create the same tracked and untracked file changes that were made to the base
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the working base (during the workflow)
|
||||
def coub_wbnb_commits_on_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
def coub_wbnb_changes_and_commits_on_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
# with commits on the base inbetween
|
||||
def coub_wbnb_changes_and_commits_on_base_and_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# pytest -v -s ~/git/create-pull-request/src
|
||||
|
||||
test_coub_fetch_successful = coub_fetch_successful
|
||||
|
||||
test_coub_no_changes_on_create = coub_no_changes_on_create
|
||||
test_coub_tracked_changes = coub_tracked_changes
|
||||
test_coub_untracked_changes = coub_untracked_changes
|
||||
test_coub_identical_changes = coub_identical_changes
|
||||
test_coub_commits_on_base = coub_commits_on_base
|
||||
|
||||
test_coub_changes_no_diff = coub_changes_no_diff
|
||||
test_coub_commits_on_base_no_diff = coub_commits_on_base_no_diff
|
||||
|
||||
test_coub_commits_on_working_base = coub_commits_on_working_base
|
||||
test_coub_changes_and_commits_on_working_base = coub_changes_and_commits_on_working_base
|
||||
test_coub_changes_and_commits_on_base_and_working_base = (
|
||||
coub_changes_and_commits_on_base_and_working_base
|
||||
)
|
||||
|
||||
# WBNB
|
||||
test_coub_wbnb_no_changes_on_create = coub_wbnb_no_changes_on_create
|
||||
test_coub_wbnb_tracked_changes = coub_wbnb_tracked_changes
|
||||
test_coub_wbnb_untracked_changes = coub_wbnb_untracked_changes
|
||||
test_coub_wbnb_identical_changes = coub_wbnb_identical_changes
|
||||
test_coub_wbnb_commits_on_base = coub_wbnb_commits_on_base
|
||||
|
||||
test_coub_wbnb_changes_no_diff = coub_wbnb_changes_no_diff
|
||||
test_coub_wbnb_commits_on_base_no_diff = coub_wbnb_commits_on_base_no_diff
|
||||
|
||||
test_coub_wbnb_commits_on_working_base = coub_wbnb_commits_on_working_base
|
||||
test_coub_wbnb_changes_and_commits_on_working_base = (
|
||||
coub_wbnb_changes_and_commits_on_working_base
|
||||
)
|
||||
test_coub_wbnb_changes_and_commits_on_base_and_working_base = (
|
||||
coub_wbnb_changes_and_commits_on_base_and_working_base
|
||||
)
|
107
docs/concepts-guidelines.md
Normal file
107
docs/concepts-guidelines.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Concepts and guidelines
|
||||
|
||||
This document covers terminology, how the action works, and general usage guidelines.
|
||||
|
||||
- [Terminology](#terminology)
|
||||
- [Events and checkout](#events-and-checkout)
|
||||
- [How the action works](#how-the-action-works)
|
||||
- [Guidelines](#guidelines)
|
||||
- [Providing a consistent base](#providing-a-consistent-base)
|
||||
- [Pull request events](#pull-request-events)
|
||||
- [Restrictions on forked repositories](#restrictions-on-forked-repositories)
|
||||
|
||||
## Terminology
|
||||
|
||||
[Pull requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#about-pull-requests) are proposed changes to a repository branch that can be reviewed by a repository's collaborators before being accepted or rejected.
|
||||
|
||||
A pull request references two branches:
|
||||
|
||||
- The `base` of a pull request is the branch you intend to change once the proposed changes are merged.
|
||||
- The `branch` of a pull request represents what you intend the `base` to look like when merged. It is the `base` branch *plus* changes that have been made to it.
|
||||
|
||||
## Events and checkout
|
||||
|
||||
For each [event type](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows) there is a default `GITHUB_SHA` that will be checked out by the GitHub Actions [checkout](https://github.com/actions/checkout) action.
|
||||
|
||||
The majority of events will default to checking out the "last commit on default branch," which in most cases will be the latest commit on `master`.
|
||||
|
||||
The default can be overridden by specifying a `ref` on checkout.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: develop
|
||||
```
|
||||
|
||||
## How the action works
|
||||
|
||||
By default, the action expects to be executed on the pull request `base`—the branch you intend to modify with the proposed changes.
|
||||
|
||||
Workflow steps:
|
||||
|
||||
1. Checkout the `base` branch
|
||||
2. Make changes
|
||||
3. Execute `create-pull-request` action
|
||||
|
||||
The following git diagram shows how the action creates and updates a pull request branch.
|
||||
|
||||

|
||||
|
||||
## Guidelines
|
||||
|
||||
### Providing a consistent base
|
||||
|
||||
For the action to work correctly it should be executed in a workflow that checks out a *consistent base* branch. This will be the base of the pull request unless overridden with the `base` input.
|
||||
|
||||
This means your workflow should be consistently checking out the branch that you intend to modify once the PR is merged.
|
||||
|
||||
In the following example, the [`push`](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#push-event-push) and [`create`](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#create-event-create) events both trigger the same workflow. This will cause the checkout action to checkout commits from inconsistent branches. Do *not* do this. It will cause multiple pull requests to be created for each additional `base` the action is executed against.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
create:
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
```
|
||||
|
||||
Although rare, there may be use cases where it makes sense to execute the workflow on a branch that is not the base of the pull request. In these cases, the base branch can be specified with the `base` action input. The action will attempt to rebase changes made during the workflow on to the actual base.
|
||||
|
||||
### Pull request events
|
||||
|
||||
Workflows triggered by `pull_request` events will by default check out a [merge commit](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#pull-request-event-pull_request). To prevent the merge commit being included in created pull requests it is necessary to checkout the `head_ref`.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
```
|
||||
|
||||
### Restrictions on forked repositories
|
||||
|
||||
GitHub Actions have imposed restrictions on events triggered by a forked repository. For example, the `pull_request` event triggered by a fork opening a pull request in the upstream repository.
|
||||
|
||||
- Events from forks cannot access secrets, except for for the default `GITHUB_TOKEN`.
|
||||
> With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository.
|
||||
|
||||
[GitHub Actions: Using encrypted secrets in a workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#using-encrypted-secrets-in-a-workflow)
|
||||
|
||||
- The `GITHUB_TOKEN` has read-only access when an event is triggered by a forked repository.
|
||||
|
||||
[GitHub Actions: Permissions for the GITHUB_TOKEN](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#permissions-for-the-github_token)
|
||||
|
||||
These restrictions mean that during a `pull_request` event triggered by a forked repository the action will be unable to commit changes to a branch.
|
||||
|
||||
A job condition can be added to prevent workflows from executing when triggered by a repository fork.
|
||||
|
||||
```yml
|
||||
on: pull_request
|
||||
jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
# Check if the event is not triggered by a fork
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
```
|
391
docs/examples.md
Normal file
391
docs/examples.md
Normal file
@ -0,0 +1,391 @@
|
||||
# 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)
|
||||
- [Use case: Create a pull request to update X periodically](#use-case-create-a-pull-request-to-update-x-periodically)
|
||||
- [Update NPM dependencies](#update-npm-dependencies)
|
||||
- [Update SwaggerUI for GitHub Pages](#update-swaggerui-for-github-pages)
|
||||
- [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)
|
||||
- [Call the GitHub API from an external service](#call-the-github-api-from-an-external-service)
|
||||
- [Call the GitHub API from another GitHub Actions workflow](#call-the-github-api-from-another-github-actions-workflow)
|
||||
- [Use case: Create a pull request to modify/fix pull requests](#use-case-create-a-pull-request-to-modifyfix-pull-requests)
|
||||
- [autopep8](#autopep8)
|
||||
- [Misc workflow tips](#misc-workflow-tips)
|
||||
- [Filtering push events](#filtering-push-events)
|
||||
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
||||
- [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
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
### Update NPM dependencies
|
||||
|
||||
```yml
|
||||
name: Update Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1'
|
||||
jobs:
|
||||
update-deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '10.x'
|
||||
- name: Update dependencies
|
||||
id: vars
|
||||
run: |
|
||||
npm install -g npm-check-updates
|
||||
ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: update dependencies
|
||||
title: Automated Dependency Updates
|
||||
body: This is an auto-generated PR with dependency updates.
|
||||
branch: dep-updates
|
||||
```
|
||||
|
||||
### 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).
|
||||
|
||||
You must create a file called `swagger-ui.version` at the root of your repository before running.
|
||||
```yml
|
||||
name: Update Swagger UI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
jobs:
|
||||
updateSwagger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get Latest Swagger UI Release
|
||||
id: swagger-ui
|
||||
run: |
|
||||
echo ::set-output name=release_tag::$(curl -sL https://api.github.com/repos/swagger-api/swagger-ui/releases/latest | jq -r ".tag_name")
|
||||
echo ::set-output name=current_tag::$(<swagger-ui.version)
|
||||
- name: Update Swagger UI
|
||||
if: steps.swagger-ui.outputs.current_tag != steps.swagger-ui.outputs.release_tag
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
SWAGGER_YAML: "swagger.yaml"
|
||||
run: |
|
||||
# Delete the dist directory and index.html
|
||||
rm -fr dist index.html
|
||||
# Download the release
|
||||
curl -sL -o $RELEASE_TAG https://api.github.com/repos/swagger-api/swagger-ui/tarball/$RELEASE_TAG
|
||||
# Extract the dist directory
|
||||
tar -xzf $RELEASE_TAG --strip-components=1 $(tar -tzf $RELEASE_TAG | head -1 | cut -f1 -d"/")/dist
|
||||
rm $RELEASE_TAG
|
||||
# Move index.html to the root
|
||||
mv dist/index.html .
|
||||
# Fix references in index.html
|
||||
sed -i "s|https://petstore.swagger.io/v2/swagger.json|$SWAGGER_YAML|g" index.html
|
||||
sed -i "s|href=\"./|href=\"dist/|g" index.html
|
||||
sed -i "s|src=\"./|src=\"dist/|g" index.html
|
||||
# Update current release
|
||||
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
body: |
|
||||
Updates [swagger-ui][1] to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
|
||||
Auto-generated by [create-pull-request][2]
|
||||
|
||||
[1]: https://github.com/swagger-api/swagger-ui
|
||||
[2]: https://github.com/peter-evans/create-pull-request
|
||||
labels: dependencies, automated pr
|
||||
branch: swagger-ui-updates
|
||||
```
|
||||
|
||||
### Spider and download a website
|
||||
|
||||
This workflow spiders a website and downloads the content. Any changes to the website will be raised in a pull request.
|
||||
|
||||
```yml
|
||||
name: Download Website
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download website
|
||||
run: |
|
||||
wget \
|
||||
--recursive \
|
||||
--level=2 \
|
||||
--wait=1 \
|
||||
--no-clobber \
|
||||
--page-requisites \
|
||||
--html-extension \
|
||||
--convert-links \
|
||||
--domains quotes.toscrape.com \
|
||||
http://quotes.toscrape.com/
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: update local website copy
|
||||
title: Automated Updates to Local Website Copy
|
||||
body: This is an auto-generated PR with website updates.
|
||||
branch: website-updates
|
||||
```
|
||||
|
||||
## Use case: Create a pull request to update X by calling the GitHub API
|
||||
|
||||
You can use the GitHub API to trigger a webhook event called [`repository_dispatch`](https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#external-events-repository_dispatch) when you want to trigger a workflow for activity that happens outside of GitHub.
|
||||
This pattern will work well for updating any kind of static content from an external source.
|
||||
|
||||
You can modify any of the examples in the previous section to work in this fashion.
|
||||
|
||||
Set the workflow to execute `on: repository_dispatch`.
|
||||
|
||||
```yml
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [create-pull-request]
|
||||
```
|
||||
|
||||
### Call the GitHub API from an external service
|
||||
|
||||
An `on: repository_dispatch` workflow can be triggered by a call to the GitHub API as follows.
|
||||
|
||||
- `[username]` is a GitHub username
|
||||
- `[token]` is a `repo` scoped [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)
|
||||
- `[repository]` is the name of the repository the workflow resides in.
|
||||
|
||||
```
|
||||
curl -XPOST -u "[username]:[token]" \
|
||||
-H "Accept: application/vnd.github.everest-preview+json" \
|
||||
-H "Content-Type: application/json" \
|
||||
https://api.github.com/repos/[username]/[repository]/dispatches \
|
||||
--data '{"event_type": "create-pull-request"}'
|
||||
```
|
||||
|
||||
### Call the GitHub API from another GitHub Actions workflow
|
||||
|
||||
An `on: repository_dispatch` workflow can be triggered from another workflow with [repository-dispatch](https://github.com/peter-evans/repository-dispatch) action.
|
||||
|
||||
```yml
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
repository: username/my-repo
|
||||
event-type: create-pull-request
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
|
||||
```
|
||||
|
||||
## Use case: Create a pull request to modify/fix pull requests
|
||||
|
||||
**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.
|
||||
|
||||
Note that due to [limitations on forked repositories](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#permissions-for-the-github_token) workflows for this use case do not work for pull requests raised from forks.
|
||||
|
||||
### autopep8
|
||||
|
||||
The following is an example workflow for a use case where [autopep8 action](https://github.com/peter-evans/autopep8) runs as both a check on pull requests and raises a further pull request to apply code fixes.
|
||||
|
||||
How it works:
|
||||
|
||||
1. When a pull request is raised the workflow executes as a check
|
||||
2. If autopep8 makes any fixes a pull request will be raised for those fixes to be merged into the current pull request branch. The workflow then deliberately causes the check to fail.
|
||||
3. When the pull request containing the fixes is merged the workflow runs again. This time autopep8 makes no changes and the check passes.
|
||||
4. The original pull request can now be merged.
|
||||
|
||||
```yml
|
||||
name: autopep8
|
||||
on: pull_request
|
||||
jobs:
|
||||
autopep8:
|
||||
# Check if the PR is not raised by this workflow and is not from a fork
|
||||
if: startsWith(github.head_ref, 'autopep8-patches') == false && github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: autopep8
|
||||
id: autopep8
|
||||
uses: peter-evans/autopep8@v1.1.0
|
||||
with:
|
||||
args: --exit-code --recursive --in-place --aggressive --aggressive .
|
||||
- name: Set autopep8 branch name
|
||||
id: vars
|
||||
run: echo ::set-output name=branch-name::"autopep8-patches/${{ github.head_ref }}"
|
||||
- name: Create Pull Request
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: autopep8 action fixes
|
||||
title: Fixes by autopep8 action
|
||||
body: This is an auto-generated PR with fixes by autopep8.
|
||||
labels: autopep8, automated pr
|
||||
branch: ${{ steps.vars.outputs.branch-name }}
|
||||
- name: Fail if autopep8 made changes
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
run: exit 1
|
||||
```
|
||||
|
||||
## Misc workflow tips
|
||||
|
||||
### Filtering push events
|
||||
|
||||
For workflows using `on: push` you may want to ignore push events for tags and only execute for branches. Specifying `branches` causes only events on branches to trigger the workflow. The `'**'` wildcard will match any branch name.
|
||||
|
||||
```yml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
```
|
||||
|
||||
If you have a workflow that contains jobs to handle push events on branches as well as tags, you can make sure that the job where you use `create-pull-request` action only executes when `github.ref` is a branch by using an `if` condition as follows.
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
createPullRequest:
|
||||
if: startsWith(github.ref, 'refs/heads/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
...
|
||||
|
||||
someOtherJob:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
...
|
||||
```
|
||||
|
||||
### Dynamic configuration using variables
|
||||
|
||||
The following examples show how configuration for the action can be dynamically defined in a previous workflow step.
|
||||
|
||||
The recommended method is to use [`set-output`](https://help.github.com/en/github/automating-your-workflow-with-github-actions/development-tools-for-github-actions#set-an-output-parameter-set-output). Note that the step where output variables are defined must have an id.
|
||||
|
||||
```yml
|
||||
- name: Set output variables
|
||||
id: vars
|
||||
run: |
|
||||
echo ::set-output name=pr_title::"[Test] Add report file $(date +%d-%m-%Y)"
|
||||
echo ::set-output name=pr_body::"This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
title: ${{ steps.vars.outputs.pr_title }}
|
||||
body: ${{ steps.vars.outputs.pr_body }}
|
||||
```
|
||||
|
||||
Alternatively, [`set-env`](https://help.github.com/en/github/automating-your-workflow-with-github-actions/development-tools-for-github-actions#set-an-environment-variable-set-env) can be used to create environment variables.
|
||||
|
||||
```yml
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo ::set-env name=PULL_REQUEST_TITLE::"[Test] Add report file $(date +%d-%m-%Y)"
|
||||
echo ::set-env name=PULL_REQUEST_BODY::"This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
title: ${{ env.PULL_REQUEST_TITLE }}
|
||||
body: ${{ env.PULL_REQUEST_BODY }}
|
||||
```
|
||||
|
||||
### Debugging GitHub Actions
|
||||
|
||||
#### Runner Diagnostic Logging
|
||||
|
||||
[Runner diagnostic logging](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#enabling-runner-diagnostic-logging) provides additional log files that contain information about how a runner is executing an action.
|
||||
To enable runner diagnostic logging, set the secret `ACTIONS_RUNNER_DEBUG` to `true` in the repository that contains the workflow.
|
||||
|
||||
#### Step Debug Logging
|
||||
|
||||
[Step debug logging](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/managing-a-workflow-run#enabling-step-debug-logging) increases the verbosity of a job's logs during and after a job's execution.
|
||||
To enable step debug logging set the secret `ACTIONS_STEP_DEBUG` to `true` in the repository that contains the workflow.
|
||||
|
||||
#### Output Various Contexts
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- name: Dump job context
|
||||
env:
|
||||
JOB_CONTEXT: ${{ toJson(job) }}
|
||||
run: echo "$JOB_CONTEXT"
|
||||
- name: Dump steps context
|
||||
env:
|
||||
STEPS_CONTEXT: ${{ toJson(steps) }}
|
||||
run: echo "$STEPS_CONTEXT"
|
||||
- name: Dump runner context
|
||||
env:
|
||||
RUNNER_CONTEXT: ${{ toJson(runner) }}
|
||||
run: echo "$RUNNER_CONTEXT"
|
||||
- name: Dump strategy context
|
||||
env:
|
||||
STRATEGY_CONTEXT: ${{ toJson(strategy) }}
|
||||
run: echo "$STRATEGY_CONTEXT"
|
||||
- name: Dump matrix context
|
||||
env:
|
||||
MATRIX_CONTEXT: ${{ toJson(matrix) }}
|
||||
run: echo "$MATRIX_CONTEXT"
|
||||
```
|
25
docs/updating.md
Normal file
25
docs/updating.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Updating from `v1` to `v2`
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- `v2` now expects repositories to be checked out with `actions/checkout@v2`
|
||||
|
||||
To use `actions/checkout@v1` the following step to checkout the branch is necessary.
|
||||
```
|
||||
- uses: actions/checkout@v1
|
||||
- name: Checkout branch
|
||||
run: git checkout "${GITHUB_REF:11}"
|
||||
```
|
||||
|
||||
- The two branch naming strategies have been swapped. Fixed branch naming strategy is now the default. i.e. `branch-suffix: none` is now the default and should be removed from configuration if set.
|
||||
|
||||
- `author-name`, `author-email`, `committer-name`, `committer-email` have been removed in favour of `author` and `committer`.
|
||||
They can both be set in the format `Display Name <email@address.com>`
|
||||
|
||||
If neither `author` or `committer` are set the action will default to making commits as the GitHub Actions bot user.
|
||||
|
||||
## New features
|
||||
|
||||
- Unpushed commits made during the workflow before the action runs will now be considered as changes to be raised in the pull request. See [Controlling commits](https://github.com/peter-evans/create-pull-request#controlling-commits) for details.
|
||||
- New commits made to the pull request base will now be taken into account when pull requests are updated.
|
||||
- If an updated pull request no longer differs from its base it will automatically be closed and the pull request branch deleted.
|
79
index.js
79
index.js
@ -1,25 +1,70 @@
|
||||
const core = require('@actions/core');
|
||||
const exec = require('@actions/exec');
|
||||
const os = require('os');
|
||||
const { inspect } = require("util");
|
||||
const core = require("@actions/core");
|
||||
const exec = require("@actions/exec");
|
||||
const setupPython = require("./src/setup-python");
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
core.info(`platform: ${os.platform()}`)
|
||||
core.info(`action directory: ${__dirname}`)
|
||||
// Allows ncc to find assets to be included in the distribution
|
||||
const src = __dirname + "/src";
|
||||
core.debug(`src: ${src}`);
|
||||
|
||||
if (os.platform() == 'linux') {
|
||||
await exec.exec('sudo apt-get install python3-setuptools');
|
||||
}
|
||||
await exec.exec(`pip3 install --requirement ${__dirname}/requirements.txt`);
|
||||
if (os.platform() == 'win32') {
|
||||
await exec.exec(`python ${__dirname}/create-pull-request.py`);
|
||||
} else {
|
||||
await exec.exec(`python3 ${__dirname}/create-pull-request.py`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// 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"),
|
||||
path: core.getInput("path"),
|
||||
commitMessage: core.getInput("commit-message"),
|
||||
committer: core.getInput("committer"),
|
||||
author: core.getInput("author"),
|
||||
title: core.getInput("title"),
|
||||
body: core.getInput("body"),
|
||||
labels: core.getInput("labels"),
|
||||
assignees: core.getInput("assignees"),
|
||||
reviewers: core.getInput("reviewers"),
|
||||
teamReviewers: core.getInput("team-reviewers"),
|
||||
milestone: core.getInput("milestone"),
|
||||
project: core.getInput("project"),
|
||||
projectColumn: core.getInput("project-column"),
|
||||
branch: core.getInput("branch"),
|
||||
base: core.getInput("base"),
|
||||
branchSuffix: core.getInput("branch-suffix"),
|
||||
};
|
||||
core.debug(`Inputs: ${inspect(inputs)}`);
|
||||
|
||||
// Set environment variables from inputs.
|
||||
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
|
||||
if (inputs.path) process.env.CPR_PATH = inputs.path;
|
||||
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
|
||||
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
|
||||
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
|
||||
if (inputs.title) process.env.CPR_TITLE = inputs.title;
|
||||
if (inputs.body) process.env.CPR_BODY = inputs.body;
|
||||
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
|
||||
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
|
||||
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
|
||||
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
|
||||
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
|
||||
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
|
||||
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
|
||||
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
|
||||
if (inputs.base) process.env.CPR_BASE = inputs.base;
|
||||
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
|
||||
|
||||
// Execute python script
|
||||
await exec.exec("python", [`${src}/create_pull_request.py`]);
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
run();
|
||||
|
97
node_modules/@actions/core/README.md
generated
vendored
97
node_modules/@actions/core/README.md
generated
vendored
@ -1,97 +0,0 @@
|
||||
# `@actions/core`
|
||||
|
||||
> Core functions for setting results, logging, registering secrets and exporting variables across actions
|
||||
|
||||
## Usage
|
||||
|
||||
#### Inputs/Outputs
|
||||
|
||||
You can use this library to get inputs or set outputs:
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
const myInput = core.getInput('inputName', { required: true });
|
||||
|
||||
// Do stuff
|
||||
|
||||
core.setOutput('outputKey', 'outputVal');
|
||||
```
|
||||
|
||||
#### Exporting variables
|
||||
|
||||
You can also export variables for future steps. Variables get set in the environment.
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
// Do stuff
|
||||
|
||||
core.exportVariable('envVar', 'Val');
|
||||
```
|
||||
|
||||
#### PATH Manipulation
|
||||
|
||||
You can explicitly add items to the path for all remaining steps in a workflow:
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
core.addPath('pathToTool');
|
||||
```
|
||||
|
||||
#### Exit codes
|
||||
|
||||
You should use this library to set the failing exit code for your action:
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
try {
|
||||
// Do stuff
|
||||
}
|
||||
catch (err) {
|
||||
// setFailed logs the message and sets a failing exit code
|
||||
core.setFailed(`Action failed with error ${err}`);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Logging
|
||||
|
||||
Finally, this library provides some utilities for logging. Note that debug logging is hidden from the logs by default. This behavior can be toggled by enabling the [Step Debug Logs](../../docs/action-debugging.md#step-debug-logs).
|
||||
|
||||
```js
|
||||
const core = require('@actions/core');
|
||||
|
||||
const myInput = core.getInput('input');
|
||||
try {
|
||||
core.debug('Inside try block');
|
||||
|
||||
if (!myInput) {
|
||||
core.warning('myInput was not set');
|
||||
}
|
||||
|
||||
// Do stuff
|
||||
}
|
||||
catch (err) {
|
||||
core.error(`Error ${err}, action may still succeed though`);
|
||||
}
|
||||
```
|
||||
|
||||
This library can also wrap chunks of output in foldable groups.
|
||||
|
||||
```js
|
||||
const core = require('@actions/core')
|
||||
|
||||
// Manually wrap output
|
||||
core.startGroup('Do some function')
|
||||
doSomeFunction()
|
||||
core.endGroup()
|
||||
|
||||
// Wrap an asynchronous function call
|
||||
const result = await core.group('Do something async', async () => {
|
||||
const response = await doSomeHTTPRequest()
|
||||
return response
|
||||
})
|
||||
```
|
16
node_modules/@actions/core/lib/command.d.ts
generated
vendored
16
node_modules/@actions/core/lib/command.d.ts
generated
vendored
@ -1,16 +0,0 @@
|
||||
interface CommandProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
/**
|
||||
* Commands
|
||||
*
|
||||
* Command Format:
|
||||
* ##[name key=value;key=value]message
|
||||
*
|
||||
* Examples:
|
||||
* ##[warning]This is the user warning message
|
||||
* ##[set-secret name=mypassword]definitelyNotAPassword!
|
||||
*/
|
||||
export declare function issueCommand(command: string, properties: CommandProperties, message: string): void;
|
||||
export declare function issue(name: string, message?: string): void;
|
||||
export {};
|
66
node_modules/@actions/core/lib/command.js
generated
vendored
66
node_modules/@actions/core/lib/command.js
generated
vendored
@ -1,66 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const os = require("os");
|
||||
/**
|
||||
* Commands
|
||||
*
|
||||
* Command Format:
|
||||
* ##[name key=value;key=value]message
|
||||
*
|
||||
* Examples:
|
||||
* ##[warning]This is the user warning message
|
||||
* ##[set-secret name=mypassword]definitelyNotAPassword!
|
||||
*/
|
||||
function issueCommand(command, properties, message) {
|
||||
const cmd = new Command(command, properties, message);
|
||||
process.stdout.write(cmd.toString() + os.EOL);
|
||||
}
|
||||
exports.issueCommand = issueCommand;
|
||||
function issue(name, message = '') {
|
||||
issueCommand(name, {}, message);
|
||||
}
|
||||
exports.issue = issue;
|
||||
const CMD_STRING = '::';
|
||||
class Command {
|
||||
constructor(command, properties, message) {
|
||||
if (!command) {
|
||||
command = 'missing.command';
|
||||
}
|
||||
this.command = command;
|
||||
this.properties = properties;
|
||||
this.message = message;
|
||||
}
|
||||
toString() {
|
||||
let cmdStr = CMD_STRING + this.command;
|
||||
if (this.properties && Object.keys(this.properties).length > 0) {
|
||||
cmdStr += ' ';
|
||||
for (const key in this.properties) {
|
||||
if (this.properties.hasOwnProperty(key)) {
|
||||
const val = this.properties[key];
|
||||
if (val) {
|
||||
// safely append the val - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
cmdStr += `${key}=${escape(`${val || ''}`)},`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cmdStr += CMD_STRING;
|
||||
// safely append the message - avoid blowing up when attempting to
|
||||
// call .replace() if message is not a string for some reason
|
||||
const message = `${this.message || ''}`;
|
||||
cmdStr += escapeData(message);
|
||||
return cmdStr;
|
||||
}
|
||||
}
|
||||
function escapeData(s) {
|
||||
return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
||||
}
|
||||
function escape(s) {
|
||||
return s
|
||||
.replace(/\r/g, '%0D')
|
||||
.replace(/\n/g, '%0A')
|
||||
.replace(/]/g, '%5D')
|
||||
.replace(/;/g, '%3B');
|
||||
}
|
||||
//# sourceMappingURL=command.js.map
|
1
node_modules/@actions/core/lib/command.js.map
generated
vendored
1
node_modules/@actions/core/lib/command.js.map
generated
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":";;AAAA,yBAAwB;AAQxB;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA6B,EAC7B,OAAe;IAEf,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;AAC/C,CAAC;AAPD,oCAOC;AAED,SAAgB,KAAK,CAAC,IAAY,EAAE,UAAkB,EAAE;IACtD,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAFD,sBAEC;AAED,MAAM,UAAU,GAAG,IAAI,CAAA;AAEvB,MAAM,OAAO;IAKX,YAAY,OAAe,EAAE,UAA6B,EAAE,OAAe;QACzE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,iBAAiB,CAAA;SAC5B;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,QAAQ;QACN,IAAI,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,OAAO,CAAA;QAEtC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9D,MAAM,IAAI,GAAG,CAAA;YACb,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;gBACjC,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;oBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;oBAChC,IAAI,GAAG,EAAE;wBACP,8DAA8D;wBAC9D,6DAA6D;wBAC7D,MAAM,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,CAAA;qBAC9C;iBACF;aACF;SACF;QAED,MAAM,IAAI,UAAU,CAAA;QAEpB,kEAAkE;QAClE,6DAA6D;QAC7D,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAA;QACvC,MAAM,IAAI,UAAU,CAAC,OAAO,CAAC,CAAA;QAE7B,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC"}
|
99
node_modules/@actions/core/lib/core.d.ts
generated
vendored
99
node_modules/@actions/core/lib/core.d.ts
generated
vendored
@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Interface for getInput options
|
||||
*/
|
||||
export interface InputOptions {
|
||||
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
|
||||
required?: boolean;
|
||||
}
|
||||
/**
|
||||
* The code to exit an action
|
||||
*/
|
||||
export declare enum ExitCode {
|
||||
/**
|
||||
* A code indicating that the action was successful
|
||||
*/
|
||||
Success = 0,
|
||||
/**
|
||||
* A code indicating that the action was a failure
|
||||
*/
|
||||
Failure = 1
|
||||
}
|
||||
/**
|
||||
* sets env variable for this action and future actions in the job
|
||||
* @param name the name of the variable to set
|
||||
* @param val the value of the variable
|
||||
*/
|
||||
export declare function exportVariable(name: string, val: string): void;
|
||||
/**
|
||||
* exports the variable and registers a secret which will get masked from logs
|
||||
* @param name the name of the variable to set
|
||||
* @param val value of the secret
|
||||
*/
|
||||
export declare function exportSecret(name: string, val: string): void;
|
||||
/**
|
||||
* Prepends inputPath to the PATH (for this action and future actions)
|
||||
* @param inputPath
|
||||
*/
|
||||
export declare function addPath(inputPath: string): void;
|
||||
/**
|
||||
* Gets the value of an input. The value is also trimmed.
|
||||
*
|
||||
* @param name name of the input to get
|
||||
* @param options optional. See InputOptions.
|
||||
* @returns string
|
||||
*/
|
||||
export declare function getInput(name: string, options?: InputOptions): string;
|
||||
/**
|
||||
* Sets the value of an output.
|
||||
*
|
||||
* @param name name of the output to set
|
||||
* @param value value to store
|
||||
*/
|
||||
export declare function setOutput(name: string, value: string): void;
|
||||
/**
|
||||
* Sets the action status to failed.
|
||||
* When the action exits it will be with an exit code of 1
|
||||
* @param message add error issue message
|
||||
*/
|
||||
export declare function setFailed(message: string): void;
|
||||
/**
|
||||
* Writes debug message to user log
|
||||
* @param message debug message
|
||||
*/
|
||||
export declare function debug(message: string): void;
|
||||
/**
|
||||
* Adds an error issue
|
||||
* @param message error issue message
|
||||
*/
|
||||
export declare function error(message: string): void;
|
||||
/**
|
||||
* Adds an warning issue
|
||||
* @param message warning issue message
|
||||
*/
|
||||
export declare function warning(message: string): void;
|
||||
/**
|
||||
* Writes info to log with console.log.
|
||||
* @param message info message
|
||||
*/
|
||||
export declare function info(message: string): void;
|
||||
/**
|
||||
* Begin an output group.
|
||||
*
|
||||
* Output until the next `groupEnd` will be foldable in this group
|
||||
*
|
||||
* @param name The name of the output group
|
||||
*/
|
||||
export declare function startGroup(name: string): void;
|
||||
/**
|
||||
* End an output group.
|
||||
*/
|
||||
export declare function endGroup(): void;
|
||||
/**
|
||||
* Wrap an asynchronous function call in a group.
|
||||
*
|
||||
* Returns the same type as the function itself.
|
||||
*
|
||||
* @param name The name of the group
|
||||
* @param fn The function to wrap in the group
|
||||
*/
|
||||
export declare function group<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
177
node_modules/@actions/core/lib/core.js
generated
vendored
177
node_modules/@actions/core/lib/core.js
generated
vendored
@ -1,177 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const command_1 = require("./command");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
/**
|
||||
* The code to exit an action
|
||||
*/
|
||||
var ExitCode;
|
||||
(function (ExitCode) {
|
||||
/**
|
||||
* A code indicating that the action was successful
|
||||
*/
|
||||
ExitCode[ExitCode["Success"] = 0] = "Success";
|
||||
/**
|
||||
* A code indicating that the action was a failure
|
||||
*/
|
||||
ExitCode[ExitCode["Failure"] = 1] = "Failure";
|
||||
})(ExitCode = exports.ExitCode || (exports.ExitCode = {}));
|
||||
//-----------------------------------------------------------------------
|
||||
// Variables
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* sets env variable for this action and future actions in the job
|
||||
* @param name the name of the variable to set
|
||||
* @param val the value of the variable
|
||||
*/
|
||||
function exportVariable(name, val) {
|
||||
process.env[name] = val;
|
||||
command_1.issueCommand('set-env', { name }, val);
|
||||
}
|
||||
exports.exportVariable = exportVariable;
|
||||
/**
|
||||
* exports the variable and registers a secret which will get masked from logs
|
||||
* @param name the name of the variable to set
|
||||
* @param val value of the secret
|
||||
*/
|
||||
function exportSecret(name, val) {
|
||||
exportVariable(name, val);
|
||||
// the runner will error with not implemented
|
||||
// leaving the function but raising the error earlier
|
||||
command_1.issueCommand('set-secret', {}, val);
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
exports.exportSecret = exportSecret;
|
||||
/**
|
||||
* Prepends inputPath to the PATH (for this action and future actions)
|
||||
* @param inputPath
|
||||
*/
|
||||
function addPath(inputPath) {
|
||||
command_1.issueCommand('add-path', {}, inputPath);
|
||||
process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
|
||||
}
|
||||
exports.addPath = addPath;
|
||||
/**
|
||||
* Gets the value of an input. The value is also trimmed.
|
||||
*
|
||||
* @param name name of the input to get
|
||||
* @param options optional. See InputOptions.
|
||||
* @returns string
|
||||
*/
|
||||
function getInput(name, options) {
|
||||
const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
exports.getInput = getInput;
|
||||
/**
|
||||
* Sets the value of an output.
|
||||
*
|
||||
* @param name name of the output to set
|
||||
* @param value value to store
|
||||
*/
|
||||
function setOutput(name, value) {
|
||||
command_1.issueCommand('set-output', { name }, value);
|
||||
}
|
||||
exports.setOutput = setOutput;
|
||||
//-----------------------------------------------------------------------
|
||||
// Results
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Sets the action status to failed.
|
||||
* When the action exits it will be with an exit code of 1
|
||||
* @param message add error issue message
|
||||
*/
|
||||
function setFailed(message) {
|
||||
process.exitCode = ExitCode.Failure;
|
||||
error(message);
|
||||
}
|
||||
exports.setFailed = setFailed;
|
||||
//-----------------------------------------------------------------------
|
||||
// Logging Commands
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Writes debug message to user log
|
||||
* @param message debug message
|
||||
*/
|
||||
function debug(message) {
|
||||
command_1.issueCommand('debug', {}, message);
|
||||
}
|
||||
exports.debug = debug;
|
||||
/**
|
||||
* Adds an error issue
|
||||
* @param message error issue message
|
||||
*/
|
||||
function error(message) {
|
||||
command_1.issue('error', message);
|
||||
}
|
||||
exports.error = error;
|
||||
/**
|
||||
* Adds an warning issue
|
||||
* @param message warning issue message
|
||||
*/
|
||||
function warning(message) {
|
||||
command_1.issue('warning', message);
|
||||
}
|
||||
exports.warning = warning;
|
||||
/**
|
||||
* Writes info to log with console.log.
|
||||
* @param message info message
|
||||
*/
|
||||
function info(message) {
|
||||
process.stdout.write(message + os.EOL);
|
||||
}
|
||||
exports.info = info;
|
||||
/**
|
||||
* Begin an output group.
|
||||
*
|
||||
* Output until the next `groupEnd` will be foldable in this group
|
||||
*
|
||||
* @param name The name of the output group
|
||||
*/
|
||||
function startGroup(name) {
|
||||
command_1.issue('group', name);
|
||||
}
|
||||
exports.startGroup = startGroup;
|
||||
/**
|
||||
* End an output group.
|
||||
*/
|
||||
function endGroup() {
|
||||
command_1.issue('endgroup');
|
||||
}
|
||||
exports.endGroup = endGroup;
|
||||
/**
|
||||
* Wrap an asynchronous function call in a group.
|
||||
*
|
||||
* Returns the same type as the function itself.
|
||||
*
|
||||
* @param name The name of the group
|
||||
* @param fn The function to wrap in the group
|
||||
*/
|
||||
function group(name, fn) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
startGroup(name);
|
||||
let result;
|
||||
try {
|
||||
result = yield fn();
|
||||
}
|
||||
finally {
|
||||
endGroup();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
exports.group = group;
|
||||
//# sourceMappingURL=core.js.map
|
1
node_modules/@actions/core/lib/core.js.map
generated
vendored
1
node_modules/@actions/core/lib/core.js.map
generated
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
66
node_modules/@actions/core/package.json
generated
vendored
66
node_modules/@actions/core/package.json
generated
vendored
@ -1,66 +0,0 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"@actions/core@1.1.1",
|
||||
"/Users/peter.evans/git/create-pull-request"
|
||||
]
|
||||
],
|
||||
"_from": "@actions/core@1.1.1",
|
||||
"_id": "@actions/core@1.1.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-O5G6EmlzTVsng7VSpNtszIoQq6kOgMGNTFB/hmwKNNA4V71JyxImCIrL27vVHCt2Cb3ImkaCr6o27C2MV9Ylwg==",
|
||||
"_location": "/@actions/core",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "@actions/core@1.1.1",
|
||||
"name": "@actions/core",
|
||||
"escapedName": "@actions%2fcore",
|
||||
"scope": "@actions",
|
||||
"rawSpec": "1.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "1.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/@actions/core/-/core-1.1.1.tgz",
|
||||
"_spec": "1.1.1",
|
||||
"_where": "/Users/peter.evans/git/create-pull-request",
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"description": "Actions core lib",
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.2"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/core",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"core"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "lib/core.js",
|
||||
"name": "@actions/core",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"version": "1.1.1"
|
||||
}
|
7
node_modules/@actions/exec/LICENSE.md
generated
vendored
7
node_modules/@actions/exec/LICENSE.md
generated
vendored
@ -1,7 +0,0 @@
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
60
node_modules/@actions/exec/README.md
generated
vendored
60
node_modules/@actions/exec/README.md
generated
vendored
@ -1,60 +0,0 @@
|
||||
# `@actions/exec`
|
||||
|
||||
## Usage
|
||||
|
||||
#### Basic
|
||||
|
||||
You can use this package to execute your tools on the command line in a cross platform way:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
await exec.exec('node index.js');
|
||||
```
|
||||
|
||||
#### Args
|
||||
|
||||
You can also pass in arg arrays:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
await exec.exec('node', ['index.js', 'foo=bar']);
|
||||
```
|
||||
|
||||
#### Output/options
|
||||
|
||||
Capture output or specify [other options](https://github.com/actions/toolkit/blob/d9347d4ab99fd507c0b9104b2cf79fb44fcc827d/packages/exec/src/interfaces.ts#L5):
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
|
||||
let myOutput = '';
|
||||
let myError = '';
|
||||
|
||||
const options = {};
|
||||
options.listeners = {
|
||||
stdout: (data: Buffer) => {
|
||||
myOutput += data.toString();
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
myError += data.toString();
|
||||
}
|
||||
};
|
||||
options.cwd = './lib';
|
||||
|
||||
await exec.exec('node', ['index.js', 'foo=bar'], options);
|
||||
```
|
||||
|
||||
#### Exec tools not in the PATH
|
||||
|
||||
You can use it in conjunction with the `which` function from `@actions/io` to execute tools that are not in the PATH:
|
||||
|
||||
```js
|
||||
const exec = require('@actions/exec');
|
||||
const io = require('@actions/io');
|
||||
|
||||
const pythonPath: string = await io.which('python', true)
|
||||
|
||||
await exec.exec(`"${pythonPath}"`, ['main.py']);
|
||||
```
|
12
node_modules/@actions/exec/lib/exec.d.ts
generated
vendored
12
node_modules/@actions/exec/lib/exec.d.ts
generated
vendored
@ -1,12 +0,0 @@
|
||||
import * as im from './interfaces';
|
||||
/**
|
||||
* Exec a command.
|
||||
* Output will be streamed to the live console.
|
||||
* Returns promise with return code
|
||||
*
|
||||
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
|
||||
* @param args optional arguments for tool. Escaping is handled by the lib.
|
||||
* @param options optional exec options. See ExecOptions
|
||||
* @returns Promise<number> exit code
|
||||
*/
|
||||
export declare function exec(commandLine: string, args?: string[], options?: im.ExecOptions): Promise<number>;
|
37
node_modules/@actions/exec/lib/exec.js
generated
vendored
37
node_modules/@actions/exec/lib/exec.js
generated
vendored
@ -1,37 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tr = require("./toolrunner");
|
||||
/**
|
||||
* Exec a command.
|
||||
* Output will be streamed to the live console.
|
||||
* Returns promise with return code
|
||||
*
|
||||
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
|
||||
* @param args optional arguments for tool. Escaping is handled by the lib.
|
||||
* @param options optional exec options. See ExecOptions
|
||||
* @returns Promise<number> exit code
|
||||
*/
|
||||
function exec(commandLine, args, options) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const commandArgs = tr.argStringToArray(commandLine);
|
||||
if (commandArgs.length === 0) {
|
||||
throw new Error(`Parameter 'commandLine' cannot be null or empty.`);
|
||||
}
|
||||
// Path to tool to execute should be first arg
|
||||
const toolPath = commandArgs[0];
|
||||
args = commandArgs.slice(1).concat(args || []);
|
||||
const runner = new tr.ToolRunner(toolPath, args, options);
|
||||
return runner.exec();
|
||||
});
|
||||
}
|
||||
exports.exec = exec;
|
||||
//# sourceMappingURL=exec.js.map
|
1
node_modules/@actions/exec/lib/exec.js.map
generated
vendored
1
node_modules/@actions/exec/lib/exec.js.map
generated
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,mCAAkC;AAElC;;;;;;;;;GASG;AACH,SAAsB,IAAI,CACxB,WAAmB,EACnB,IAAe,EACf,OAAwB;;QAExB,MAAM,WAAW,GAAG,EAAE,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACpD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;YAC5B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;SACpE;QACD,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;QAC/B,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAA;QAC9C,MAAM,MAAM,GAAkB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QACxE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;IACtB,CAAC;CAAA;AAdD,oBAcC"}
|
35
node_modules/@actions/exec/lib/interfaces.d.ts
generated
vendored
35
node_modules/@actions/exec/lib/interfaces.d.ts
generated
vendored
@ -1,35 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
import * as stream from 'stream';
|
||||
/**
|
||||
* Interface for exec options
|
||||
*/
|
||||
export interface ExecOptions {
|
||||
/** optional working directory. defaults to current */
|
||||
cwd?: string;
|
||||
/** optional envvar dictionary. defaults to current process's env */
|
||||
env?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/** optional. defaults to false */
|
||||
silent?: boolean;
|
||||
/** optional out stream to use. Defaults to process.stdout */
|
||||
outStream?: stream.Writable;
|
||||
/** optional err stream to use. Defaults to process.stderr */
|
||||
errStream?: stream.Writable;
|
||||
/** optional. whether to skip quoting/escaping arguments if needed. defaults to false. */
|
||||
windowsVerbatimArguments?: boolean;
|
||||
/** optional. whether to fail if output to stderr. defaults to false */
|
||||
failOnStdErr?: boolean;
|
||||
/** optional. defaults to failing on non zero. ignore will not fail leaving it up to the caller */
|
||||
ignoreReturnCode?: boolean;
|
||||
/** optional. How long in ms to wait for STDIO streams to close after the exit event of the process before terminating. defaults to 10000 */
|
||||
delay?: number;
|
||||
/** optional. Listeners for output. Callback functions that will be called on these events */
|
||||
listeners?: {
|
||||
stdout?: (data: Buffer) => void;
|
||||
stderr?: (data: Buffer) => void;
|
||||
stdline?: (data: string) => void;
|
||||
errline?: (data: string) => void;
|
||||
debug?: (data: string) => void;
|
||||
};
|
||||
}
|
3
node_modules/@actions/exec/lib/interfaces.js
generated
vendored
3
node_modules/@actions/exec/lib/interfaces.js
generated
vendored
@ -1,3 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=interfaces.js.map
|
1
node_modules/@actions/exec/lib/interfaces.js.map
generated
vendored
1
node_modules/@actions/exec/lib/interfaces.js.map
generated
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
|
37
node_modules/@actions/exec/lib/toolrunner.d.ts
generated
vendored
37
node_modules/@actions/exec/lib/toolrunner.d.ts
generated
vendored
@ -1,37 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
import * as events from 'events';
|
||||
import * as im from './interfaces';
|
||||
export declare class ToolRunner extends events.EventEmitter {
|
||||
constructor(toolPath: string, args?: string[], options?: im.ExecOptions);
|
||||
private toolPath;
|
||||
private args;
|
||||
private options;
|
||||
private _debug;
|
||||
private _getCommandString;
|
||||
private _processLineBuffer;
|
||||
private _getSpawnFileName;
|
||||
private _getSpawnArgs;
|
||||
private _endsWith;
|
||||
private _isCmdFile;
|
||||
private _windowsQuoteCmdArg;
|
||||
private _uvQuoteCmdArg;
|
||||
private _cloneExecOptions;
|
||||
private _getSpawnOptions;
|
||||
/**
|
||||
* Exec a tool.
|
||||
* Output will be streamed to the live console.
|
||||
* Returns promise with return code
|
||||
*
|
||||
* @param tool path to tool to exec
|
||||
* @param options optional exec options. See ExecOptions
|
||||
* @returns number
|
||||
*/
|
||||
exec(): Promise<number>;
|
||||
}
|
||||
/**
|
||||
* Convert an arg string to an array of args. Handles escaping
|
||||
*
|
||||
* @param argString string of arguments
|
||||
* @returns string[] array of arguments
|
||||
*/
|
||||
export declare function argStringToArray(argString: string): string[];
|
574
node_modules/@actions/exec/lib/toolrunner.js
generated
vendored
574
node_modules/@actions/exec/lib/toolrunner.js
generated
vendored
@ -1,574 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const os = require("os");
|
||||
const events = require("events");
|
||||
const child = require("child_process");
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
/*
|
||||
* Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way.
|
||||
*/
|
||||
class ToolRunner extends events.EventEmitter {
|
||||
constructor(toolPath, args, options) {
|
||||
super();
|
||||
if (!toolPath) {
|
||||
throw new Error("Parameter 'toolPath' cannot be null or empty.");
|
||||
}
|
||||
this.toolPath = toolPath;
|
||||
this.args = args || [];
|
||||
this.options = options || {};
|
||||
}
|
||||
_debug(message) {
|
||||
if (this.options.listeners && this.options.listeners.debug) {
|
||||
this.options.listeners.debug(message);
|
||||
}
|
||||
}
|
||||
_getCommandString(options, noPrefix) {
|
||||
const toolPath = this._getSpawnFileName();
|
||||
const args = this._getSpawnArgs(options);
|
||||
let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool
|
||||
if (IS_WINDOWS) {
|
||||
// Windows + cmd file
|
||||
if (this._isCmdFile()) {
|
||||
cmd += toolPath;
|
||||
for (const a of args) {
|
||||
cmd += ` ${a}`;
|
||||
}
|
||||
}
|
||||
// Windows + verbatim
|
||||
else if (options.windowsVerbatimArguments) {
|
||||
cmd += `"${toolPath}"`;
|
||||
for (const a of args) {
|
||||
cmd += ` ${a}`;
|
||||
}
|
||||
}
|
||||
// Windows (regular)
|
||||
else {
|
||||
cmd += this._windowsQuoteCmdArg(toolPath);
|
||||
for (const a of args) {
|
||||
cmd += ` ${this._windowsQuoteCmdArg(a)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// OSX/Linux - this can likely be improved with some form of quoting.
|
||||
// creating processes on Unix is fundamentally different than Windows.
|
||||
// on Unix, execvp() takes an arg array.
|
||||
cmd += toolPath;
|
||||
for (const a of args) {
|
||||
cmd += ` ${a}`;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
_processLineBuffer(data, strBuffer, onLine) {
|
||||
try {
|
||||
let s = strBuffer + data.toString();
|
||||
let n = s.indexOf(os.EOL);
|
||||
while (n > -1) {
|
||||
const line = s.substring(0, n);
|
||||
onLine(line);
|
||||
// the rest of the string ...
|
||||
s = s.substring(n + os.EOL.length);
|
||||
n = s.indexOf(os.EOL);
|
||||
}
|
||||
strBuffer = s;
|
||||
}
|
||||
catch (err) {
|
||||
// streaming lines to console is best effort. Don't fail a build.
|
||||
this._debug(`error processing line. Failed with error ${err}`);
|
||||
}
|
||||
}
|
||||
_getSpawnFileName() {
|
||||
if (IS_WINDOWS) {
|
||||
if (this._isCmdFile()) {
|
||||
return process.env['COMSPEC'] || 'cmd.exe';
|
||||
}
|
||||
}
|
||||
return this.toolPath;
|
||||
}
|
||||
_getSpawnArgs(options) {
|
||||
if (IS_WINDOWS) {
|
||||
if (this._isCmdFile()) {
|
||||
let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`;
|
||||
for (const a of this.args) {
|
||||
argline += ' ';
|
||||
argline += options.windowsVerbatimArguments
|
||||
? a
|
||||
: this._windowsQuoteCmdArg(a);
|
||||
}
|
||||
argline += '"';
|
||||
return [argline];
|
||||
}
|
||||
}
|
||||
return this.args;
|
||||
}
|
||||
_endsWith(str, end) {
|
||||
return str.endsWith(end);
|
||||
}
|
||||
_isCmdFile() {
|
||||
const upperToolPath = this.toolPath.toUpperCase();
|
||||
return (this._endsWith(upperToolPath, '.CMD') ||
|
||||
this._endsWith(upperToolPath, '.BAT'));
|
||||
}
|
||||
_windowsQuoteCmdArg(arg) {
|
||||
// for .exe, apply the normal quoting rules that libuv applies
|
||||
if (!this._isCmdFile()) {
|
||||
return this._uvQuoteCmdArg(arg);
|
||||
}
|
||||
// otherwise apply quoting rules specific to the cmd.exe command line parser.
|
||||
// the libuv rules are generic and are not designed specifically for cmd.exe
|
||||
// command line parser.
|
||||
//
|
||||
// for a detailed description of the cmd.exe command line parser, refer to
|
||||
// http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912
|
||||
// need quotes for empty arg
|
||||
if (!arg) {
|
||||
return '""';
|
||||
}
|
||||
// determine whether the arg needs to be quoted
|
||||
const cmdSpecialChars = [
|
||||
' ',
|
||||
'\t',
|
||||
'&',
|
||||
'(',
|
||||
')',
|
||||
'[',
|
||||
']',
|
||||
'{',
|
||||
'}',
|
||||
'^',
|
||||
'=',
|
||||
';',
|
||||
'!',
|
||||
"'",
|
||||
'+',
|
||||
',',
|
||||
'`',
|
||||
'~',
|
||||
'|',
|
||||
'<',
|
||||
'>',
|
||||
'"'
|
||||
];
|
||||
let needsQuotes = false;
|
||||
for (const char of arg) {
|
||||
if (cmdSpecialChars.some(x => x === char)) {
|
||||
needsQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// short-circuit if quotes not needed
|
||||
if (!needsQuotes) {
|
||||
return arg;
|
||||
}
|
||||
// the following quoting rules are very similar to the rules that by libuv applies.
|
||||
//
|
||||
// 1) wrap the string in quotes
|
||||
//
|
||||
// 2) double-up quotes - i.e. " => ""
|
||||
//
|
||||
// this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately
|
||||
// doesn't work well with a cmd.exe command line.
|
||||
//
|
||||
// note, replacing " with "" also works well if the arg is passed to a downstream .NET console app.
|
||||
// for example, the command line:
|
||||
// foo.exe "myarg:""my val"""
|
||||
// is parsed by a .NET console app into an arg array:
|
||||
// [ "myarg:\"my val\"" ]
|
||||
// which is the same end result when applying libuv quoting rules. although the actual
|
||||
// command line from libuv quoting rules would look like:
|
||||
// foo.exe "myarg:\"my val\""
|
||||
//
|
||||
// 3) double-up slashes that precede a quote,
|
||||
// e.g. hello \world => "hello \world"
|
||||
// hello\"world => "hello\\""world"
|
||||
// hello\\"world => "hello\\\\""world"
|
||||
// hello world\ => "hello world\\"
|
||||
//
|
||||
// technically this is not required for a cmd.exe command line, or the batch argument parser.
|
||||
// the reasons for including this as a .cmd quoting rule are:
|
||||
//
|
||||
// a) this is optimized for the scenario where the argument is passed from the .cmd file to an
|
||||
// external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule.
|
||||
//
|
||||
// b) it's what we've been doing previously (by deferring to node default behavior) and we
|
||||
// haven't heard any complaints about that aspect.
|
||||
//
|
||||
// note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be
|
||||
// escaped when used on the command line directly - even though within a .cmd file % can be escaped
|
||||
// by using %%.
|
||||
//
|
||||
// the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts
|
||||
// the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing.
|
||||
//
|
||||
// one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would
|
||||
// often work, since it is unlikely that var^ would exist, and the ^ character is removed when the
|
||||
// variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args
|
||||
// to an external program.
|
||||
//
|
||||
// an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file.
|
||||
// % can be escaped within a .cmd file.
|
||||
let reverse = '"';
|
||||
let quoteHit = true;
|
||||
for (let i = arg.length; i > 0; i--) {
|
||||
// walk the string in reverse
|
||||
reverse += arg[i - 1];
|
||||
if (quoteHit && arg[i - 1] === '\\') {
|
||||
reverse += '\\'; // double the slash
|
||||
}
|
||||
else if (arg[i - 1] === '"') {
|
||||
quoteHit = true;
|
||||
reverse += '"'; // double the quote
|
||||
}
|
||||
else {
|
||||
quoteHit = false;
|
||||
}
|
||||
}
|
||||
reverse += '"';
|
||||
return reverse
|
||||
.split('')
|
||||
.reverse()
|
||||
.join('');
|
||||
}
|
||||
_uvQuoteCmdArg(arg) {
|
||||
// Tool runner wraps child_process.spawn() and needs to apply the same quoting as
|
||||
// Node in certain cases where the undocumented spawn option windowsVerbatimArguments
|
||||
// is used.
|
||||
//
|
||||
// Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV,
|
||||
// see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details),
|
||||
// pasting copyright notice from Node within this function:
|
||||
//
|
||||
// Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
if (!arg) {
|
||||
// Need double quotation for empty argument
|
||||
return '""';
|
||||
}
|
||||
if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) {
|
||||
// No quotation needed
|
||||
return arg;
|
||||
}
|
||||
if (!arg.includes('"') && !arg.includes('\\')) {
|
||||
// No embedded double quotes or backslashes, so I can just wrap
|
||||
// quote marks around the whole thing.
|
||||
return `"${arg}"`;
|
||||
}
|
||||
// Expected input/output:
|
||||
// input : hello"world
|
||||
// output: "hello\"world"
|
||||
// input : hello""world
|
||||
// output: "hello\"\"world"
|
||||
// input : hello\world
|
||||
// output: hello\world
|
||||
// input : hello\\world
|
||||
// output: hello\\world
|
||||
// input : hello\"world
|
||||
// output: "hello\\\"world"
|
||||
// input : hello\\"world
|
||||
// output: "hello\\\\\"world"
|
||||
// input : hello world\
|
||||
// output: "hello world\\" - note the comment in libuv actually reads "hello world\"
|
||||
// but it appears the comment is wrong, it should be "hello world\\"
|
||||
let reverse = '"';
|
||||
let quoteHit = true;
|
||||
for (let i = arg.length; i > 0; i--) {
|
||||
// walk the string in reverse
|
||||
reverse += arg[i - 1];
|
||||
if (quoteHit && arg[i - 1] === '\\') {
|
||||
reverse += '\\';
|
||||
}
|
||||
else if (arg[i - 1] === '"') {
|
||||
quoteHit = true;
|
||||
reverse += '\\';
|
||||
}
|
||||
else {
|
||||
quoteHit = false;
|
||||
}
|
||||
}
|
||||
reverse += '"';
|
||||
return reverse
|
||||
.split('')
|
||||
.reverse()
|
||||
.join('');
|
||||
}
|
||||
_cloneExecOptions(options) {
|
||||
options = options || {};
|
||||
const result = {
|
||||
cwd: options.cwd || process.cwd(),
|
||||
env: options.env || process.env,
|
||||
silent: options.silent || false,
|
||||
windowsVerbatimArguments: options.windowsVerbatimArguments || false,
|
||||
failOnStdErr: options.failOnStdErr || false,
|
||||
ignoreReturnCode: options.ignoreReturnCode || false,
|
||||
delay: options.delay || 10000
|
||||
};
|
||||
result.outStream = options.outStream || process.stdout;
|
||||
result.errStream = options.errStream || process.stderr;
|
||||
return result;
|
||||
}
|
||||
_getSpawnOptions(options, toolPath) {
|
||||
options = options || {};
|
||||
const result = {};
|
||||
result.cwd = options.cwd;
|
||||
result.env = options.env;
|
||||
result['windowsVerbatimArguments'] =
|
||||
options.windowsVerbatimArguments || this._isCmdFile();
|
||||
if (options.windowsVerbatimArguments) {
|
||||
result.argv0 = `"${toolPath}"`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Exec a tool.
|
||||
* Output will be streamed to the live console.
|
||||
* Returns promise with return code
|
||||
*
|
||||
* @param tool path to tool to exec
|
||||
* @param options optional exec options. See ExecOptions
|
||||
* @returns number
|
||||
*/
|
||||
exec() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._debug(`exec tool: ${this.toolPath}`);
|
||||
this._debug('arguments:');
|
||||
for (const arg of this.args) {
|
||||
this._debug(` ${arg}`);
|
||||
}
|
||||
const optionsNonNull = this._cloneExecOptions(this.options);
|
||||
if (!optionsNonNull.silent && optionsNonNull.outStream) {
|
||||
optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL);
|
||||
}
|
||||
const state = new ExecState(optionsNonNull, this.toolPath);
|
||||
state.on('debug', (message) => {
|
||||
this._debug(message);
|
||||
});
|
||||
const fileName = this._getSpawnFileName();
|
||||
const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName));
|
||||
const stdbuffer = '';
|
||||
if (cp.stdout) {
|
||||
cp.stdout.on('data', (data) => {
|
||||
if (this.options.listeners && this.options.listeners.stdout) {
|
||||
this.options.listeners.stdout(data);
|
||||
}
|
||||
if (!optionsNonNull.silent && optionsNonNull.outStream) {
|
||||
optionsNonNull.outStream.write(data);
|
||||
}
|
||||
this._processLineBuffer(data, stdbuffer, (line) => {
|
||||
if (this.options.listeners && this.options.listeners.stdline) {
|
||||
this.options.listeners.stdline(line);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const errbuffer = '';
|
||||
if (cp.stderr) {
|
||||
cp.stderr.on('data', (data) => {
|
||||
state.processStderr = true;
|
||||
if (this.options.listeners && this.options.listeners.stderr) {
|
||||
this.options.listeners.stderr(data);
|
||||
}
|
||||
if (!optionsNonNull.silent &&
|
||||
optionsNonNull.errStream &&
|
||||
optionsNonNull.outStream) {
|
||||
const s = optionsNonNull.failOnStdErr
|
||||
? optionsNonNull.errStream
|
||||
: optionsNonNull.outStream;
|
||||
s.write(data);
|
||||
}
|
||||
this._processLineBuffer(data, errbuffer, (line) => {
|
||||
if (this.options.listeners && this.options.listeners.errline) {
|
||||
this.options.listeners.errline(line);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
cp.on('error', (err) => {
|
||||
state.processError = err.message;
|
||||
state.processExited = true;
|
||||
state.processClosed = true;
|
||||
state.CheckComplete();
|
||||
});
|
||||
cp.on('exit', (code) => {
|
||||
state.processExitCode = code;
|
||||
state.processExited = true;
|
||||
this._debug(`Exit code ${code} received from tool '${this.toolPath}'`);
|
||||
state.CheckComplete();
|
||||
});
|
||||
cp.on('close', (code) => {
|
||||
state.processExitCode = code;
|
||||
state.processExited = true;
|
||||
state.processClosed = true;
|
||||
this._debug(`STDIO streams have closed for tool '${this.toolPath}'`);
|
||||
state.CheckComplete();
|
||||
});
|
||||
state.on('done', (error, exitCode) => {
|
||||
if (stdbuffer.length > 0) {
|
||||
this.emit('stdline', stdbuffer);
|
||||
}
|
||||
if (errbuffer.length > 0) {
|
||||
this.emit('errline', errbuffer);
|
||||
}
|
||||
cp.removeAllListeners();
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
else {
|
||||
resolve(exitCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.ToolRunner = ToolRunner;
|
||||
/**
|
||||
* Convert an arg string to an array of args. Handles escaping
|
||||
*
|
||||
* @param argString string of arguments
|
||||
* @returns string[] array of arguments
|
||||
*/
|
||||
function argStringToArray(argString) {
|
||||
const args = [];
|
||||
let inQuotes = false;
|
||||
let escaped = false;
|
||||
let arg = '';
|
||||
function append(c) {
|
||||
// we only escape double quotes.
|
||||
if (escaped && c !== '"') {
|
||||
arg += '\\';
|
||||
}
|
||||
arg += c;
|
||||
escaped = false;
|
||||
}
|
||||
for (let i = 0; i < argString.length; i++) {
|
||||
const c = argString.charAt(i);
|
||||
if (c === '"') {
|
||||
if (!escaped) {
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
else {
|
||||
append(c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c === '\\' && escaped) {
|
||||
append(c);
|
||||
continue;
|
||||
}
|
||||
if (c === '\\' && inQuotes) {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (c === ' ' && !inQuotes) {
|
||||
if (arg.length > 0) {
|
||||
args.push(arg);
|
||||
arg = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
append(c);
|
||||
}
|
||||
if (arg.length > 0) {
|
||||
args.push(arg.trim());
|
||||
}
|
||||
return args;
|
||||
}
|
||||
exports.argStringToArray = argStringToArray;
|
||||
class ExecState extends events.EventEmitter {
|
||||
constructor(options, toolPath) {
|
||||
super();
|
||||
this.processClosed = false; // tracks whether the process has exited and stdio is closed
|
||||
this.processError = '';
|
||||
this.processExitCode = 0;
|
||||
this.processExited = false; // tracks whether the process has exited
|
||||
this.processStderr = false; // tracks whether stderr was written to
|
||||
this.delay = 10000; // 10 seconds
|
||||
this.done = false;
|
||||
this.timeout = null;
|
||||
if (!toolPath) {
|
||||
throw new Error('toolPath must not be empty');
|
||||
}
|
||||
this.options = options;
|
||||
this.toolPath = toolPath;
|
||||
if (options.delay) {
|
||||
this.delay = options.delay;
|
||||
}
|
||||
}
|
||||
CheckComplete() {
|
||||
if (this.done) {
|
||||
return;
|
||||
}
|
||||
if (this.processClosed) {
|
||||
this._setResult();
|
||||
}
|
||||
else if (this.processExited) {
|
||||
this.timeout = setTimeout(ExecState.HandleTimeout, this.delay, this);
|
||||
}
|
||||
}
|
||||
_debug(message) {
|
||||
this.emit('debug', message);
|
||||
}
|
||||
_setResult() {
|
||||
// determine whether there is an error
|
||||
let error;
|
||||
if (this.processExited) {
|
||||
if (this.processError) {
|
||||
error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`);
|
||||
}
|
||||
else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) {
|
||||
error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`);
|
||||
}
|
||||
else if (this.processStderr && this.options.failOnStdErr) {
|
||||
error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`);
|
||||
}
|
||||
}
|
||||
// clear the timeout
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
this.done = true;
|
||||
this.emit('done', error, this.processExitCode);
|
||||
}
|
||||
static HandleTimeout(state) {
|
||||
if (state.done) {
|
||||
return;
|
||||
}
|
||||
if (!state.processClosed && state.processExited) {
|
||||
const message = `The STDIO streams did not close within ${state.delay /
|
||||
1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`;
|
||||
state._debug(message);
|
||||
}
|
||||
state._setResult();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=toolrunner.js.map
|
1
node_modules/@actions/exec/lib/toolrunner.js.map
generated
vendored
1
node_modules/@actions/exec/lib/toolrunner.js.map
generated
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"toolrunner.js","sourceRoot":"","sources":["../src/toolrunner.ts"],"names":[],"mappings":"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"}
|
67
node_modules/@actions/exec/package.json
generated
vendored
67
node_modules/@actions/exec/package.json
generated
vendored
@ -1,67 +0,0 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"@actions/exec@1.0.1",
|
||||
"/Users/peter.evans/git/create-pull-request"
|
||||
]
|
||||
],
|
||||
"_from": "@actions/exec@1.0.1",
|
||||
"_id": "@actions/exec@1.0.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==",
|
||||
"_location": "/@actions/exec",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "@actions/exec@1.0.1",
|
||||
"name": "@actions/exec",
|
||||
"escapedName": "@actions%2fexec",
|
||||
"scope": "@actions",
|
||||
"rawSpec": "1.0.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "1.0.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz",
|
||||
"_spec": "1.0.1",
|
||||
"_where": "/Users/peter.evans/git/create-pull-request",
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"description": "Actions exec lib",
|
||||
"devDependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"gitHead": "a2ab4bcf78e4f7080f0d45856e6eeba16f0bbc52",
|
||||
"homepage": "https://github.com/actions/toolkit/tree/master/packages/exec",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"exec"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "lib/exec.js",
|
||||
"name": "@actions/exec",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"version": "1.0.1"
|
||||
}
|
74
package-lock.json
generated
74
package-lock.json
generated
@ -1,18 +1,80 @@
|
||||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.1.1.tgz",
|
||||
"integrity": "sha512-O5G6EmlzTVsng7VSpNtszIoQq6kOgMGNTFB/hmwKNNA4V71JyxImCIrL27vVHCt2Cb3ImkaCr6o27C2MV9Ylwg=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.0.tgz",
|
||||
"integrity": "sha512-ZKdyhlSlyz38S6YFfPnyNgCDZuAF2T0Qv5eHflNWytPS8Qjvz39bZFMry9Bb/dpSnqWcNeav5yM2CTYpJeY+Dw=="
|
||||
},
|
||||
"@actions/exec": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.2.tgz",
|
||||
"integrity": "sha512-Yo/wfcFuxbVjAaAfvx3aGLhMEuonOahas2jf8BwyA52IkXTAmLi7YVZTpGAQG/lTxuGoNLg9slTWQD4rr7rMDQ==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz",
|
||||
"integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ=="
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.1.tgz",
|
||||
"integrity": "sha512-rhq+tfZukbtaus7xyUtwKfuiCRXd1hWSfmJNEpFgBQJ4woqPEpsBw04awicjwz9tyG2/MVhAEMfVn664Cri5zA=="
|
||||
},
|
||||
"@actions/tool-cache": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.1.2.tgz",
|
||||
"integrity": "sha512-IJczPaZr02ECa3Lgws/TJEVco9tjOujiQSZbO3dHuXXjhd5vrUtfOgGwhmz3/f97L910OraPZ8SknofUk6RvOQ==",
|
||||
"requires": {
|
||||
"@actions/core": "^1.1.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/io": "^1.0.1",
|
||||
"semver": "^6.1.0",
|
||||
"typed-rest-client": "^1.4.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"@zeit/ncc": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.21.0.tgz",
|
||||
"integrity": "sha512-RUMdvVK/w78oo+yBjruZltt0kJXYar2un/1bYQ2LuHG7GmFVm+QjxzEmySwREctaJdEnBvlMdUNWd9hXHxEI3g==",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz",
|
||||
"integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz",
|
||||
"integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM="
|
||||
},
|
||||
"typed-rest-client": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.7.1.tgz",
|
||||
"integrity": "sha512-fZRDWFtUp3J2E0jOiCJYZ9LDrYZHpjY95su//ekqXERS7C1qojP6movh7M4JGURJnBuTVsO0g2N4vEoW5o3Djw==",
|
||||
"requires": {
|
||||
"qs": "^6.9.1",
|
||||
"tunnel": "0.0.4",
|
||||
"underscore": "1.8.3"
|
||||
}
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
|
||||
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
|
||||
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
package.json
20
package.json
@ -1,22 +1,28 @@
|
||||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/peter-evans/create-pull-request.git"
|
||||
},
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
"Actions",
|
||||
"Pull Request"
|
||||
],
|
||||
"keywords": [],
|
||||
"author": "Peter Evans",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/peter-evans/create-pull-request/issues"
|
||||
},
|
||||
"homepage": "https://github.com/peter-evans/create-pull-request",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.1.1",
|
||||
"@actions/exec": "^1.0.1"
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/tool-cache": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@zeit/ncc": "0.21.0"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 441 KiB |
@ -1,2 +0,0 @@
|
||||
GitPython==3.0.3
|
||||
PyGithub==1.44
|
31
src/common.py
Normal file
31
src/common.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def get_random_string(length=7, chars=string.ascii_lowercase + string.digits):
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
|
||||
|
||||
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>
|
||||
pattern = re.compile(r"^([^<]+)\s*<([^>]+)>$")
|
||||
|
||||
# Check we have a match
|
||||
match = pattern.match(display_name_email)
|
||||
if match is None:
|
||||
raise ValueError(
|
||||
f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
# Check that name and email are not just whitespace
|
||||
name = match.group(1).strip()
|
||||
email = match.group(2).strip()
|
||||
if len(name) == 0 or len(email) == 0:
|
||||
raise ValueError(
|
||||
f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
return name, email
|
145
src/create_or_update_branch.py
Normal file
145
src/create_or_update_branch.py
Normal file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create or Update Branch """
|
||||
import common as cmn
|
||||
from git import Repo, GitCommandError
|
||||
import os
|
||||
|
||||
|
||||
CHERRYPICK_EMPTY = (
|
||||
"The previous cherry-pick is now empty, possibly due to conflict resolution."
|
||||
)
|
||||
|
||||
|
||||
def fetch_successful(repo, repo_url, branch):
|
||||
try:
|
||||
repo.git.fetch(repo_url, f"{branch}:refs/remotes/origin/{branch}")
|
||||
except GitCommandError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_ahead(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is ahead of branch_1
|
||||
return (
|
||||
int(repo.git.rev_list("--right-only", "--count", f"{branch_1}...{branch_2}"))
|
||||
> 0
|
||||
)
|
||||
|
||||
|
||||
def is_behind(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is behind branch_1
|
||||
return (
|
||||
int(repo.git.rev_list("--left-only", "--count", f"{branch_1}...{branch_2}")) > 0
|
||||
)
|
||||
|
||||
|
||||
def is_even(repo, branch_1, branch_2):
|
||||
# Return true if branch_2 is even with branch_1
|
||||
return not is_ahead(repo, branch_1, branch_2) and not is_behind(
|
||||
repo, branch_1, branch_2
|
||||
)
|
||||
|
||||
|
||||
def has_diff(repo, branch_1, branch_2):
|
||||
diff = repo.git.diff(f"{branch_1}..{branch_2}")
|
||||
return len(diff) > 0
|
||||
|
||||
|
||||
def create_or_update_branch(repo, repo_url, commit_message, base, branch):
|
||||
# Set the default return values
|
||||
action = "none"
|
||||
diff = False
|
||||
|
||||
# Get the working base. This may or may not be the actual base.
|
||||
working_base = repo.git.symbolic_ref("HEAD", "--short")
|
||||
# If the base is not specified it is assumed to be the working base
|
||||
if base is None:
|
||||
base = working_base
|
||||
|
||||
# Save the working base changes to a temporary branch
|
||||
temp_branch = cmn.get_random_string(length=20)
|
||||
repo.git.checkout("HEAD", b=temp_branch)
|
||||
# Commit any uncomitted changes
|
||||
if repo.is_dirty(untracked_files=True):
|
||||
print(f"Uncommitted changes found. Adding a commit.")
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m=commit_message)
|
||||
|
||||
# Perform fetch and reset the working base
|
||||
# Commits made during the workflow will be removed
|
||||
repo.git.fetch("--force", repo_url, f"{working_base}:{working_base}")
|
||||
|
||||
# If the working base is not the base, rebase the temp branch commits
|
||||
if working_base != base:
|
||||
print(
|
||||
f"Rebasing commits made to branch '{working_base}' on to base branch '{base}'"
|
||||
)
|
||||
# Checkout the actual base
|
||||
repo.git.fetch("--force", repo_url, f"{base}:{base}")
|
||||
repo.git.checkout(base)
|
||||
# Cherrypick commits from the temporary branch starting from the working base
|
||||
commits = repo.git.rev_list("--reverse", f"{working_base}..{temp_branch}", ".")
|
||||
for commit in commits.splitlines():
|
||||
try:
|
||||
repo.git.cherry_pick(
|
||||
"--strategy",
|
||||
"recursive",
|
||||
"--strategy-option",
|
||||
"theirs",
|
||||
f"{commit}",
|
||||
)
|
||||
except GitCommandError as e:
|
||||
if CHERRYPICK_EMPTY not in e.stderr:
|
||||
print("Unexpected error: ", e)
|
||||
raise
|
||||
# Reset the temp branch to the working index
|
||||
repo.git.checkout("-B", temp_branch, "HEAD")
|
||||
# Reset the base
|
||||
repo.git.fetch("--force", repo_url, f"{base}:{base}")
|
||||
|
||||
# Try to fetch the pull request branch
|
||||
if not fetch_successful(repo, repo_url, branch):
|
||||
# The pull request branch does not exist
|
||||
print(f"Pull request branch '{branch}' does not exist yet")
|
||||
# Create the pull request branch
|
||||
repo.git.checkout("HEAD", b=branch)
|
||||
# Check if the pull request branch is ahead of the base
|
||||
diff = is_ahead(repo, base, branch)
|
||||
if diff:
|
||||
action = "created"
|
||||
print(f"Created branch '{branch}'")
|
||||
else:
|
||||
print(
|
||||
f"Branch '{branch}' is not ahead of base '{base}' and will not be created"
|
||||
)
|
||||
else:
|
||||
# The pull request branch exists
|
||||
print(
|
||||
f"Pull request branch '{branch}' already exists as remote branch 'origin/{branch}'"
|
||||
)
|
||||
# Checkout the pull request branch
|
||||
repo.git.checkout(branch)
|
||||
|
||||
if has_diff(repo, branch, temp_branch):
|
||||
# If the branch differs from the recreated temp version then the branch is reset
|
||||
# For changes on base this action is similar to a rebase of the pull request branch
|
||||
print(f"Resetting '{branch}'")
|
||||
repo.git.checkout("-B", branch, temp_branch)
|
||||
# repo.git.switch("-C", branch, temp_branch)
|
||||
|
||||
# Check if the pull request branch has been updated
|
||||
# If the branch was reset or updated it will be ahead
|
||||
# It may be behind if a reset now results in no diff with the base
|
||||
if not is_even(repo, f"origin/{branch}", branch):
|
||||
action = "updated"
|
||||
print(f"Updated branch '{branch}'")
|
||||
else:
|
||||
print(f"Branch '{branch}' is even with its remote and will not be updated")
|
||||
|
||||
# Check if the pull request branch is ahead of the base
|
||||
diff = is_ahead(repo, base, branch)
|
||||
|
||||
# Delete the temporary branch
|
||||
repo.git.branch("--delete", "--force", temp_branch)
|
||||
|
||||
return {"action": action, "diff": diff, "base": base}
|
126
src/create_or_update_pull_request.py
Normal file
126
src/create_or_update_pull_request.py
Normal file
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create or Update Pull Request """
|
||||
from github import Github, GithubException
|
||||
import os
|
||||
|
||||
|
||||
def cs_string_to_list(str):
|
||||
# Split the comma separated string into a list
|
||||
l = [i.strip() for i in str.split(",")]
|
||||
# Remove empty strings
|
||||
return list(filter(None, l))
|
||||
|
||||
|
||||
def create_project_card(github_repo, project_name, project_column_name, pull_request):
|
||||
# Locate the project by name
|
||||
project = None
|
||||
for project_item in github_repo.get_projects("all"):
|
||||
if project_item.name == project_name:
|
||||
project = project_item
|
||||
break
|
||||
|
||||
if not project:
|
||||
print("::error::Project not found. Unable to create project card.")
|
||||
return
|
||||
|
||||
# Locate the column by name
|
||||
column = None
|
||||
for column_item in project.get_columns():
|
||||
if column_item.name == project_column_name:
|
||||
column = column_item
|
||||
break
|
||||
|
||||
if not column:
|
||||
print("::error::Project column not found. Unable to create project card.")
|
||||
return
|
||||
|
||||
# Create a project card for the pull request
|
||||
column.create_card(content_id=pull_request.id, content_type="PullRequest")
|
||||
print(
|
||||
"Added pull request #%d to project '%s' under column '%s'"
|
||||
% (pull_request.number, project.name, column.name)
|
||||
)
|
||||
|
||||
|
||||
def create_or_update_pull_request(
|
||||
github_token,
|
||||
github_repository,
|
||||
branch,
|
||||
base,
|
||||
title,
|
||||
body,
|
||||
labels,
|
||||
assignees,
|
||||
milestone,
|
||||
reviewers,
|
||||
team_reviewers,
|
||||
project_name,
|
||||
project_column_name,
|
||||
):
|
||||
# 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
|
||||
)
|
||||
print(f"Created pull request #{pull_request.number} ({branch} => {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})")
|
||||
else:
|
||||
print(str(e))
|
||||
raise
|
||||
|
||||
# Set the output variables
|
||||
os.system(f"echo ::set-env name=PULL_REQUEST_NUMBER::{pull_request.number}")
|
||||
os.system(f"echo ::set-output name=pr_number::{pull_request.number}")
|
||||
|
||||
# Set labels, assignees and milestone
|
||||
if labels is not None:
|
||||
print(f"Applying labels '{labels}'")
|
||||
pull_request.as_issue().edit(labels=cs_string_to_list(labels))
|
||||
if assignees is not None:
|
||||
print(f"Applying assignees '{assignees}'")
|
||||
pull_request.as_issue().edit(assignees=cs_string_to_list(assignees))
|
||||
if milestone is not None:
|
||||
print(f"Applying milestone '{milestone}'")
|
||||
milestone = github_repo.get_milestone(int(milestone))
|
||||
pull_request.as_issue().edit(milestone=milestone)
|
||||
|
||||
# Set pull request reviewers
|
||||
if reviewers is not None:
|
||||
print(f"Requesting reviewers '{reviewers}'")
|
||||
try:
|
||||
pull_request.create_review_request(reviewers=cs_string_to_list(reviewers))
|
||||
except GithubException as e:
|
||||
# Likely caused by "Review cannot be requested from pull request author."
|
||||
if e.status == 422:
|
||||
print("Request reviewers failed - {}".format(e.data["message"]))
|
||||
|
||||
# Set pull request team reviewers
|
||||
if team_reviewers is not None:
|
||||
print(f"Requesting team reviewers '{team_reviewers}'")
|
||||
pull_request.create_review_request(
|
||||
team_reviewers=cs_string_to_list(team_reviewers)
|
||||
)
|
||||
|
||||
# Create a project card for the pull request
|
||||
if project_name is not None and project_column_name is not None:
|
||||
try:
|
||||
create_project_card(
|
||||
github_repo, project_name, project_column_name, pull_request
|
||||
)
|
||||
except GithubException as e:
|
||||
# Likely caused by "Project already has the associated issue."
|
||||
if e.status == 422:
|
||||
print(
|
||||
"Create project card failed - {}".format(
|
||||
e.data["errors"][0]["message"]
|
||||
)
|
||||
)
|
205
src/create_pull_request.py
Executable file
205
src/create_pull_request.py
Executable file
@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Create Pull Request """
|
||||
import common as cmn
|
||||
import create_or_update_branch as coub
|
||||
import create_or_update_pull_request as coupr
|
||||
from git import Repo, GitCommandError
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# Default the committer and author to the GitHub Actions bot
|
||||
DEFAULT_COMMITTER = "GitHub <noreply@github.com>"
|
||||
DEFAULT_AUTHOR = (
|
||||
"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
)
|
||||
DEFAULT_COMMIT_MESSAGE = "[create-pull-request] automated change"
|
||||
DEFAULT_TITLE = "Changes by create-pull-request action"
|
||||
DEFAULT_BODY = (
|
||||
"Automated changes by "
|
||||
+ "[create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action"
|
||||
)
|
||||
DEFAULT_BRANCH = "create-pull-request/patch"
|
||||
|
||||
|
||||
def get_git_config_value(repo, name):
|
||||
try:
|
||||
return repo.git.config("--get", name)
|
||||
except GitCommandError:
|
||||
return None
|
||||
|
||||
|
||||
def git_user_config_is_set(repo):
|
||||
name = get_git_config_value(repo, "user.name")
|
||||
email = get_git_config_value(repo, "user.email")
|
||||
|
||||
if name is not None and email is not None:
|
||||
print(f"Git user already configured as '{name} <{email}>'")
|
||||
return True
|
||||
|
||||
committer_name = get_git_config_value(repo, "committer.name")
|
||||
committer_email = get_git_config_value(repo, "committer.email")
|
||||
author_name = get_git_config_value(repo, "author.name")
|
||||
author_email = get_git_config_value(repo, "author.email")
|
||||
|
||||
if (
|
||||
committer_name is not None
|
||||
and committer_email is not None
|
||||
and author_name is not None
|
||||
and author_email is not None
|
||||
):
|
||||
print(
|
||||
f"Git committer already configured as '{committer_name} <{committer_email}>'"
|
||||
)
|
||||
print(f"Git author already configured as '{author_name} <{author_email}>'")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def set_committer_author(repo, committer, author):
|
||||
# If either committer or author is supplied they will be cross used
|
||||
if committer is None and author is not None:
|
||||
print("Supplied author will also be used as the committer.")
|
||||
committer = author
|
||||
if author is None and committer is not None:
|
||||
print("Supplied committer will also be used as the author.")
|
||||
author = committer
|
||||
|
||||
# If no committer/author has been supplied but user configuration already
|
||||
# exists in git config we can exit and use the existing config as-is.
|
||||
if committer is None and author is None:
|
||||
if git_user_config_is_set(repo):
|
||||
return
|
||||
|
||||
# Set defaults if no committer/author has been supplied
|
||||
if committer is None and author is None:
|
||||
committer = DEFAULT_COMMITTER
|
||||
author = DEFAULT_AUTHOR
|
||||
|
||||
# Set git environment. This will not persist after the action completes.
|
||||
committer_name, committer_email = cmn.parse_display_name_email(committer)
|
||||
author_name, author_email = cmn.parse_display_name_email(author)
|
||||
repo.git.update_environment(
|
||||
GIT_COMMITTER_NAME=committer_name,
|
||||
GIT_COMMITTER_EMAIL=committer_email,
|
||||
GIT_AUTHOR_NAME=author_name,
|
||||
GIT_AUTHOR_EMAIL=author_email,
|
||||
)
|
||||
print(f"Configured git committer as '{committer_name} <{committer_email}>'")
|
||||
print(f"Configured git author as '{author_name} <{author_email}>'")
|
||||
|
||||
|
||||
# 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'
|
||||
committer = os.environ.get("CPR_COMMITTER")
|
||||
author = os.environ.get("CPR_AUTHOR")
|
||||
base = os.environ.get("CPR_BASE")
|
||||
|
||||
# Set the repo path
|
||||
repo = Repo(path)
|
||||
|
||||
# Determine if the checked out ref is a valid base for a pull request
|
||||
# The action needs the checked out HEAD ref to be a branch
|
||||
# This check will fail in the following cases:
|
||||
# - HEAD is detached
|
||||
# - HEAD is a merge commit (pull_request events)
|
||||
# - HEAD is a tag
|
||||
try:
|
||||
working_base = repo.git.symbolic_ref("HEAD", "--short")
|
||||
except GitCommandError as e:
|
||||
print(f"::debug::{e.stderr}")
|
||||
print(
|
||||
f"::error::The checked out ref is not a valid base for a pull request. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Exit if the working base is a PR branch created by this action.
|
||||
# This may occur when using a PAT instead of GITHUB_TOKEN because
|
||||
# a PAT allows workflow actions to trigger further events.
|
||||
if working_base.startswith(branch):
|
||||
print(
|
||||
f"::error::Working base branch '{working_base}' was created by this action. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Fetch an optional environment variable to determine the branch suffix
|
||||
branch_suffix = os.environ.get("CPR_BRANCH_SUFFIX")
|
||||
if branch_suffix is not None:
|
||||
if branch_suffix == "short-commit-hash":
|
||||
# Suffix with the short SHA1 hash
|
||||
branch = "{}-{}".format(branch, repo.git.rev_parse("--short", "HEAD"))
|
||||
elif branch_suffix == "timestamp":
|
||||
# Suffix with the current timestamp
|
||||
branch = "{}-{}".format(branch, int(time.time()))
|
||||
elif branch_suffix == "random":
|
||||
# Suffix with a 7 character random string
|
||||
branch = "{}-{}".format(branch, cmn.get_random_string())
|
||||
else:
|
||||
print(
|
||||
f"::error::Branch suffix '{branch_suffix}' is not a valid value. "
|
||||
+ "Unable to continue. Exiting."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Output head branch
|
||||
print(f"Pull request branch to create or update set to '{branch}'")
|
||||
|
||||
# Set the committer and author
|
||||
try:
|
||||
set_committer_author(repo, committer, author)
|
||||
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}'")
|
||||
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
|
||||
base = result["base"]
|
||||
|
||||
# If there is no longer a diff with the base delete the branch and exit
|
||||
if not result["diff"]:
|
||||
print(f"Branch '{branch}' no longer differs from base branch '{base}'")
|
||||
print(f"Closing pull request and deleting branch '{branch}'")
|
||||
repo.git.push("--delete", "--force", repo_url, f"refs/heads/{branch}")
|
||||
sys.exit()
|
||||
|
||||
# Fetch optional environment variables with default values
|
||||
title = os.getenv("CPR_TITLE", DEFAULT_TITLE)
|
||||
body = os.getenv("CPR_BODY", DEFAULT_BODY)
|
||||
|
||||
# Create or update the pull request
|
||||
coupr.create_or_update_pull_request(
|
||||
github_token,
|
||||
github_repository,
|
||||
branch,
|
||||
base,
|
||||
title,
|
||||
body,
|
||||
os.environ.get("CPR_LABELS"),
|
||||
os.environ.get("CPR_ASSIGNEES"),
|
||||
os.environ.get("CPR_MILESTONE"),
|
||||
os.environ.get("CPR_REVIEWERS"),
|
||||
os.environ.get("CPR_TEAM_REVIEWERS"),
|
||||
os.environ.get("CPR_PROJECT_NAME"),
|
||||
os.environ.get("CPR_PROJECT_COLUMN_NAME"),
|
||||
)
|
2
src/requirements.txt
Normal file
2
src/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
GitPython==3.0.5
|
||||
PyGithub==1.45
|
52
src/setup-python.js
Normal file
52
src/setup-python.js
Normal file
@ -0,0 +1,52 @@
|
||||
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;
|
39
src/test_common.py
Normal file
39
src/test_common.py
Normal file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Test Common """
|
||||
import common as cmn
|
||||
import pytest
|
||||
|
||||
|
||||
def test_get_random_string():
|
||||
assert len(cmn.get_random_string()) == 7
|
||||
assert len(cmn.get_random_string(length=20)) == 20
|
||||
|
||||
|
||||
def test_parse_display_name_email_success():
|
||||
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
|
||||
assert name == "abc def"
|
||||
assert email == "abc@def.com"
|
||||
|
||||
name, email = cmn.parse_display_name_email(
|
||||
"github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
||||
)
|
||||
assert name == "github-actions[bot]"
|
||||
assert email == "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
|
||||
def test_parse_display_name_email_failure():
|
||||
display_name_email = "abc@def.com"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_display_name_email(display_name_email)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
||||
|
||||
display_name_email = " < >"
|
||||
with pytest.raises(ValueError) as e_info:
|
||||
cmn.parse_display_name_email(display_name_email)
|
||||
assert (
|
||||
e_info.value.args[0]
|
||||
== f"The format of '{display_name_email}' is not a valid email address with display name"
|
||||
)
|
757
src/test_create_or_update_branch.py
Normal file
757
src/test_create_or_update_branch.py
Normal file
@ -0,0 +1,757 @@
|
||||
#!/usr/bin/env python3
|
||||
""" Test Create or Update Branch """
|
||||
import create_or_update_branch as coub
|
||||
from git import Repo
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# Set git repo
|
||||
REPO_PATH = os.getenv("COUB_REPO_PATH", os.getcwd())
|
||||
repo = Repo(REPO_PATH)
|
||||
|
||||
# Set git environment
|
||||
author_name = "github-actions[bot]"
|
||||
author_email = "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
committer_name = "GitHub"
|
||||
committer_email = "noreply@github.com"
|
||||
repo.git.update_environment(
|
||||
GIT_AUTHOR_NAME=author_name,
|
||||
GIT_AUTHOR_EMAIL=author_email,
|
||||
GIT_COMMITTER_NAME=committer_name,
|
||||
GIT_COMMITTER_EMAIL=committer_email,
|
||||
)
|
||||
|
||||
REPO_URL = repo.git.config("--get", "remote.origin.url")
|
||||
|
||||
TRACKED_FILE = "tracked-file.txt"
|
||||
UNTRACKED_FILE = "untracked-file.txt"
|
||||
|
||||
DEFAULT_BRANCH = "tests/master"
|
||||
NOT_BASE_BRANCH = "tests/branch-that-is-not-the-base"
|
||||
NOT_EXIST_BRANCH = "tests/branch-that-does-not-exist"
|
||||
|
||||
COMMIT_MESSAGE = "[create-pull-request] automated change"
|
||||
BRANCH = "tests/create-pull-request/patch"
|
||||
BASE = DEFAULT_BRANCH
|
||||
|
||||
|
||||
def create_tracked_change(content=None):
|
||||
if content is None:
|
||||
content = str(time.time())
|
||||
# Create a tracked file change
|
||||
with open(os.path.join(REPO_PATH, TRACKED_FILE), "w") as f:
|
||||
f.write(content)
|
||||
return content
|
||||
|
||||
|
||||
def create_untracked_change(content=None):
|
||||
if content is None:
|
||||
content = str(time.time())
|
||||
# Create an untracked file change
|
||||
with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "w") as f:
|
||||
f.write(content)
|
||||
return content
|
||||
|
||||
|
||||
def get_tracked_content():
|
||||
# Read the content of the tracked file
|
||||
with open(os.path.join(REPO_PATH, TRACKED_FILE), "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_untracked_content():
|
||||
# Read the content of the untracked file
|
||||
with open(os.path.join(REPO_PATH, UNTRACKED_FILE), "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def create_changes(tracked_content=None, untracked_content=None):
|
||||
tracked_content = create_tracked_change(tracked_content)
|
||||
untracked_content = create_untracked_change(untracked_content)
|
||||
return tracked_content, untracked_content
|
||||
|
||||
|
||||
def create_commits(number=2, final_tracked_content=None, final_untracked_content=None):
|
||||
for i in range(number):
|
||||
commit_number = i + 1
|
||||
if commit_number == number:
|
||||
tracked_content, untracked_content = create_changes(
|
||||
final_tracked_content, final_untracked_content
|
||||
)
|
||||
else:
|
||||
tracked_content, untracked_content = create_changes()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m=f"Commit {commit_number}")
|
||||
return tracked_content, untracked_content
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def before_after_all():
|
||||
print("Before all tests")
|
||||
# Check there are no local changes that might be
|
||||
# destroyed by running these tests
|
||||
assert not repo.is_dirty(untracked_files=True)
|
||||
|
||||
# Create a new default branch for the test run
|
||||
repo.remotes.origin.fetch()
|
||||
repo.git.checkout("master")
|
||||
repo.git.checkout("HEAD", b=NOT_BASE_BRANCH)
|
||||
create_tracked_change()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m="This commit should not appear in pr branches")
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{NOT_BASE_BRANCH}")
|
||||
# Create a new default branch for the test run
|
||||
repo.git.checkout("master")
|
||||
repo.git.checkout("HEAD", b=DEFAULT_BRANCH)
|
||||
create_tracked_change()
|
||||
repo.git.add("-A")
|
||||
repo.git.commit(m="Add file to be a tracked file for tests")
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
|
||||
yield
|
||||
|
||||
print("After all tests")
|
||||
repo.git.checkout("master")
|
||||
# Delete the "not base branch" created for the test run
|
||||
repo.git.branch("--delete", "--force", NOT_BASE_BRANCH)
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{NOT_BASE_BRANCH}")
|
||||
# Delete the default branch created for the test run
|
||||
repo.git.branch("--delete", "--force", DEFAULT_BRANCH)
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{DEFAULT_BRANCH}")
|
||||
|
||||
|
||||
def before_test():
|
||||
print("Before test")
|
||||
# Checkout the default branch
|
||||
repo.git.checkout(DEFAULT_BRANCH)
|
||||
|
||||
|
||||
def after_test(delete_remote=True):
|
||||
print("After test")
|
||||
# Output git log
|
||||
print(repo.git.log("-5", pretty="oneline"))
|
||||
# Delete the pull request branch if it exists
|
||||
repo.git.checkout(DEFAULT_BRANCH)
|
||||
print(f"Deleting {BRANCH}")
|
||||
for branch in repo.branches:
|
||||
if branch.name == BRANCH:
|
||||
repo.git.branch("--delete", "--force", BRANCH)
|
||||
break
|
||||
if delete_remote:
|
||||
print(f"Deleting origin/{BRANCH}")
|
||||
for ref in repo.remotes.origin.refs:
|
||||
if ref.name == f"origin/{BRANCH}":
|
||||
repo.git.push("--delete", "--force", REPO_URL, f"refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch("--prune")
|
||||
break
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def before_after_tests():
|
||||
before_test()
|
||||
yield
|
||||
after_test()
|
||||
|
||||
|
||||
# Tests if a branch exists and can be fetched
|
||||
def coub_fetch_successful():
|
||||
assert coub.fetch_successful(repo, REPO_URL, NOT_BASE_BRANCH)
|
||||
assert not coub.fetch_successful(repo, REPO_URL, NOT_EXIST_BRANCH)
|
||||
|
||||
|
||||
# Tests no changes resulting in no new branch being created
|
||||
def coub_no_changes_on_create():
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
|
||||
|
||||
# Tests create and update with a tracked file change
|
||||
def coub_tracked_changes():
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
|
||||
# Tests create and update with an untracked file change
|
||||
def coub_untracked_changes():
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with identical changes
|
||||
# The pull request branch will not be updated
|
||||
def coub_identical_changes():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create identical tracked and untracked file changes
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the base inbetween
|
||||
def coub_commits_on_base():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and then an update with no changes
|
||||
# This effectively reverts the branch back to match the base and results in no diff
|
||||
def coub_changes_no_diff():
|
||||
# Save the default branch tracked content
|
||||
default_tracked_content = get_tracked_content()
|
||||
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Running with no update effectively reverts the branch back to match the base
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == default_tracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the base inbetween
|
||||
# The changes on base effectively revert the branch back to match the base and results in no diff
|
||||
def coub_commits_on_base_no_diff():
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create the same tracked and untracked file changes that were made to the base
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with commits on the working base (during the workflow)
|
||||
def coub_commits_on_working_base():
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
def coub_changes_and_commits_on_working_base():
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
# with commits on the base inbetween
|
||||
def coub_changes_and_commits_on_base_and_working_base():
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, None, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests no changes resulting in no new branch being created
|
||||
def coub_wbnb_no_changes_on_create():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with a tracked file change
|
||||
def coub_wbnb_tracked_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create a tracked file change
|
||||
tracked_content = create_tracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with an untracked file change
|
||||
def coub_wbnb_untracked_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create an untracked file change
|
||||
untracked_content = create_untracked_change()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with identical changes
|
||||
# The pull request branch will not be updated
|
||||
def coub_wbnb_identical_changes():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create identical tracked and untracked file changes
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "none"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the base inbetween
|
||||
def coub_wbnb_commits_on_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and then an update with no changes
|
||||
# This effectively reverts the branch back to match the base and results in no diff
|
||||
def coub_wbnb_changes_no_diff():
|
||||
# Save the default branch tracked content
|
||||
default_tracked_content = get_tracked_content()
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Running with no update effectively reverts the branch back to match the base
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == default_tracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the base inbetween
|
||||
# The changes on base effectively revert the branch back to match the base and results in no diff
|
||||
# This scenario will cause cherrypick to fail due to an empty commit.
|
||||
# The commit is empty because the changes now exist on the base.
|
||||
def coub_wbnb_commits_on_base_no_diff():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create the same tracked and untracked file changes that were made to the base
|
||||
create_changes(tracked_content, untracked_content)
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"] == False
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with commits on the working base (during the workflow)
|
||||
def coub_wbnb_commits_on_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
tracked_content, untracked_content = create_commits()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
def coub_wbnb_changes_and_commits_on_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# Working Base is Not Base (WBNB)
|
||||
# Tests create and update with changes and commits on the working base (during the workflow)
|
||||
# with commits on the base inbetween
|
||||
def coub_wbnb_changes_and_commits_on_base_and_working_base():
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "created"
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
# Push pull request branch to remote
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
after_test(delete_remote=False)
|
||||
before_test()
|
||||
|
||||
# Create commits on the base
|
||||
create_commits()
|
||||
repo.git.push("--force", REPO_URL, f"HEAD:refs/heads/{DEFAULT_BRANCH}")
|
||||
repo.remotes.origin.fetch()
|
||||
|
||||
# Set the working base to a branch that is not the pull request base
|
||||
repo.git.checkout(NOT_BASE_BRANCH)
|
||||
# Create commits on the working base
|
||||
create_commits()
|
||||
# Create tracked and untracked file changes
|
||||
tracked_content, untracked_content = create_changes()
|
||||
result = coub.create_or_update_branch(repo, REPO_URL, COMMIT_MESSAGE, BASE, BRANCH)
|
||||
assert result["action"] == "updated"
|
||||
assert result["diff"]
|
||||
assert get_tracked_content() == tracked_content
|
||||
assert get_untracked_content() == untracked_content
|
||||
|
||||
|
||||
# pytest -v -s ~/git/create-pull-request/src
|
||||
|
||||
test_coub_fetch_successful = coub_fetch_successful
|
||||
|
||||
test_coub_no_changes_on_create = coub_no_changes_on_create
|
||||
test_coub_tracked_changes = coub_tracked_changes
|
||||
test_coub_untracked_changes = coub_untracked_changes
|
||||
test_coub_identical_changes = coub_identical_changes
|
||||
test_coub_commits_on_base = coub_commits_on_base
|
||||
|
||||
test_coub_changes_no_diff = coub_changes_no_diff
|
||||
test_coub_commits_on_base_no_diff = coub_commits_on_base_no_diff
|
||||
|
||||
test_coub_commits_on_working_base = coub_commits_on_working_base
|
||||
test_coub_changes_and_commits_on_working_base = coub_changes_and_commits_on_working_base
|
||||
test_coub_changes_and_commits_on_base_and_working_base = (
|
||||
coub_changes_and_commits_on_base_and_working_base
|
||||
)
|
||||
|
||||
# WBNB
|
||||
test_coub_wbnb_no_changes_on_create = coub_wbnb_no_changes_on_create
|
||||
test_coub_wbnb_tracked_changes = coub_wbnb_tracked_changes
|
||||
test_coub_wbnb_untracked_changes = coub_wbnb_untracked_changes
|
||||
test_coub_wbnb_identical_changes = coub_wbnb_identical_changes
|
||||
test_coub_wbnb_commits_on_base = coub_wbnb_commits_on_base
|
||||
|
||||
test_coub_wbnb_changes_no_diff = coub_wbnb_changes_no_diff
|
||||
test_coub_wbnb_commits_on_base_no_diff = coub_wbnb_commits_on_base_no_diff
|
||||
|
||||
test_coub_wbnb_commits_on_working_base = coub_wbnb_commits_on_working_base
|
||||
test_coub_wbnb_changes_and_commits_on_working_base = (
|
||||
coub_wbnb_changes_and_commits_on_working_base
|
||||
)
|
||||
test_coub_wbnb_changes_and_commits_on_base_and_working_base = (
|
||||
coub_wbnb_changes_and_commits_on_base_and_working_base
|
||||
)
|
Reference in New Issue
Block a user