Compare commits

...

288 Commits

Author SHA1 Message Date
7e7150d0e8 Merge pull request #147 from peter-evans/dev
Default token to github.token
2020-04-01 19:09:39 +09:00
eb99d45ce6 Default token to github.token 2020-04-01 18:50:53 +09:00
115b7391e1 Revert "Update documentation"
This reverts commit 628c2d7d35.
2020-03-30 17:36:34 +09:00
8305970523 Merge pull request #146 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.4
2020-03-30 09:02:53 +09:00
32f5c5dd5f Update dependency jest to v25.2.4 2020-03-29 20:05:26 +00:00
628c2d7d35 Update documentation 2020-03-29 21:34:47 +09:00
37582e8764 Update documentation 2020-03-29 21:27:56 +09:00
1e6b4d1790 Merge pull request #145 from peter-evans/dev
Create pull requests in the parent repository of a checked out fork
2020-03-29 21:02:48 +09:00
a70c6ebe2a Merge pull request #144 from jderusse/fork
Allow custom repository when pushin in fork
2020-03-29 20:47:00 +09:00
5bd05538d0 Update src/cpr/create_or_update_pull_request.py
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 13:36:48 +02:00
26bc40eea1 Add link in TOC 2020-03-29 12:44:57 +02:00
6bb0e7771c Apply suggestions from code review
Co-Authored-By: Peter Evans <peter-evans@users.noreply.github.com>
2020-03-29 12:43:21 +02:00
4c347a4514 Update docs/concepts-guidelines.md
Co-Authored-By: Tobias Nyholm <tobias.nyholm@gmail.com>
2020-03-28 16:42:57 +01:00
cff2c3381d Add doc 2020-03-28 13:27:58 +01:00
e48dab0c1c Add PR creation from Fork 2020-03-28 13:10:12 +01:00
ac9f92d6e7 Merge pull request #141 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.3
2020-03-27 08:15:40 +09:00
3ff256ce08 Update dependency jest to v25.2.3 2020-03-26 20:38:32 +00:00
0bcb10560b Merge pull request #140 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.1
2020-03-26 19:03:52 +09:00
3c4b2793c1 Update dependency jest to v25.2.1 2020-03-26 09:43:37 +00:00
22870d7816 Merge pull request #139 from peter-evans/renovate/jest-monorepo
Update dependency jest to v25.2.0
2020-03-26 10:23:31 +09:00
e324a22ee1 Update dependency jest to v25.2.0 2020-03-25 18:18:56 +00:00
8b60386018 Merge pull request #135 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.22.0
2020-03-24 09:20:19 +09:00
6852d55922 Update dependency @zeit/ncc to v0.22.0 2020-03-23 22:31:46 +00:00
32e5bb80a5 Merge pull request #134 from peter-evans/dev
Update dependencies
2020-03-21 11:38:19 +09:00
619cf2115d Update dependencies due to security vulnerabilities 2020-03-21 11:31:34 +09:00
69008aa567 Update vendored dependencies 2020-03-21 11:29:37 +09:00
ae4278bf24 Update dependency PyGithub to v1.47 2020-03-21 11:23:42 +09:00
88da40fea7 Merge pull request #132 from peter-evans/dev
Update dependency GitPython to v3.1.0
2020-03-07 09:08:24 +09:00
694e068136 Update vendored dependencies 2020-03-07 08:57:59 +09:00
64c34f6885 Update README 2020-03-03 09:49:52 +09:00
52ada17960 Update dependency GitPython to v3.1.0 2020-02-25 13:12:00 +09:00
ce00b952cf Merge pull request #128 from peter-evans/dev
Unset and restore authorization extraheader only
2020-02-22 17:02:15 +09:00
0d42c285a3 Unset and restore authorization extraheader only 2020-02-22 16:56:42 +09:00
ea1eaf1734 Merge pull request #127 from peter-evans/dev
Unset and restore extraheader config option
2020-02-22 14:53:46 +09:00
d5c5ea3e20 Unset and restore extraheader config option 2020-02-22 14:08:54 +09:00
c7b64af0a4 Merge pull request #123 from peter-evans/dev
Authenticate with git extraheader
2020-02-18 19:49:13 +09:00
289fda9fea Authenticate with git extraheader 2020-02-18 19:35:15 +09:00
b021b9e27a Update vendored dependencies 2020-02-18 15:59:18 +09:00
c26314237b Update dependency GitPython to v3.0.8 2020-02-18 15:57:13 +09:00
ed7dd8d236 Update documentation 2020-02-14 20:10:27 +09:00
ca0e9d75fd Update documentation 2020-02-14 00:23:21 +09:00
87c27ee3eb Merge pull request #118 from peter-evans/dev
Assume python3 on PATH when running in a container
2020-02-14 00:07:03 +09:00
4beea725d3 Call python3 when running in a container 2020-02-13 17:37:08 +09:00
9d58699da5 Skip python setup when running in a container 2020-02-13 16:26:04 +09:00
bceebba814 Merge pull request #117 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.21.1
2020-02-13 09:58:46 +09:00
0b82710b2e Update dependency @zeit/ncc to v0.21.1 2020-02-12 16:54:11 +00:00
4aaaa0e760 Update README 2020-02-11 16:36:16 +09:00
918bfcb8a3 Update README 2020-02-11 16:28:16 +09:00
1ecfd1ae40 Merge pull request #116 from peter-evans/dev
Update dependency PyGithub to v1.46
2020-02-11 14:03:17 +09:00
4dd195d7c3 Update vendored dependencies 2020-02-11 13:51:59 +09:00
72b5f45bb4 Update dependency PyGithub to v1.46 2020-02-11 13:49:50 +09:00
5394814b39 Update documentation 2020-02-11 01:21:05 +09:00
83918398f5 Update documentation 2020-02-11 01:15:59 +09:00
b6a458d96a Merge pull request #114 from peter-evans/dev
Add support for ssh protocol
2020-02-10 23:06:03 +09:00
7b5ff6b642 Add support for ssh protocol 2020-02-10 22:52:00 +09:00
82eddd8828 Merge pull request #112 from peter-evans/dev
Skip python setup for alpine linux
2020-02-10 18:47:40 +09:00
6df2a462d1 Skip python setup for alpine linux 2020-02-10 09:53:43 +09:00
3689bd07d7 Merge pull request #110 from peter-evans/dev
Update dependency GitPython to v3.0.7
2020-02-08 16:45:18 +09:00
943f19ac64 Update vendored dependencies 2020-02-08 16:12:36 +09:00
e59d6c7fff Update dependency GitPython to v3.0.7 2020-02-08 16:09:50 +09:00
c65a4f39b3 Update documentation 2020-02-08 16:04:23 +09:00
2d18371789 Update documentation 2020-02-08 12:52:06 +09:00
cc7020a609 Merge pull request #107 from peter-evans/dev
Determine target github repository from git config
2020-02-07 19:01:09 +09:00
d8700620d6 Determine target github repository from git config 2020-02-07 11:05:01 +09:00
b7064071dc Move assets 2020-01-25 16:29:22 +09:00
339e82d37b Update documentation 2020-01-24 13:00:38 +09:00
a319015452 Merge pull request #102 from peter-evans/dev
Vendor python dependencies
2020-01-23 18:59:19 +09:00
df0d07269b Vendor python dependencies 2020-01-23 16:59:19 +09:00
2aa04baf2e Merge pull request #101 from peter-evans/dev
Update title and body when pr exists
2020-01-23 09:54:43 +09:00
d29f1e9296 Update title and body when pr exists 2020-01-23 09:47:35 +09:00
8ee12880b0 Update documentation 2020-01-21 22:35:00 +09:00
8d39de8771 Update documentation 2020-01-21 22:27:02 +09:00
c202684c92 Merge pull request #98 from peter-evans/dev
Follow the python minor version
2020-01-17 17:48:11 +09:00
e970adccb4 Follow the python minor version 2020-01-17 17:39:10 +09:00
9f9af3e969 Merge pull request #97 from peter-evans/dev
Update Python to version 3.8.1
2020-01-17 15:52:01 +09:00
615d7c82e3 Update Python to version 3.8.1 2020-01-17 15:46:49 +09:00
8246a6aea9 Merge pull request #95 from peter-evans/dev
Add path input
2020-01-10 00:35:16 +09:00
1e09ec2f22 Add path input 2020-01-10 00:11:42 +09:00
4c018f4174 Merge pull request #93 from peter-evans/renovate/zeit-ncc-0.x
Update dependency @zeit/ncc to v0.21.0
2020-01-05 08:08:32 +09:00
080aaa90aa Update dependency @zeit/ncc to v0.21.0 2020-01-04 15:45:00 +00:00
6ac64298f6 Update slash command 2020-01-04 21:44:01 +09:00
76e932f3c9 Update slash command 2020-01-04 21:38:54 +09:00
06078e295b Update examples 2020-01-04 20:02:09 +09:00
004434a414 Update examples 2020-01-04 12:08:41 +09:00
4661d6d7af Update docs 2020-01-03 19:22:46 +09:00
a972260284 Update docs 2020-01-03 17:50:03 +09:00
f1d6c2dca3 Update example screenshot 2020-01-03 14:52:21 +09:00
aa1cba4c18 Update cpr example workflow 2020-01-03 14:44:00 +09:00
9b33a4edd8 Add slash command for cpr example 2020-01-03 14:37:39 +09:00
938e6aea6f Merge pull request #90 from peter-evans/v2-beta
v2 beta
2020-01-03 14:18:38 +09:00
699d304845 Update docs to v2 2020-01-03 14:18:10 +09:00
2ce0b003ee Merge branch 'master' into v2-beta 2020-01-03 14:00:21 +09:00
10653669e8 Update npm packages 2020-01-03 12:32:22 +09:00
c39f04a86a Add issue template 2020-01-03 12:20:20 +09:00
430f28afbf Add gitgraph to docs 2020-01-03 11:53:45 +09:00
8e07307f04 Remove multi workflow 2020-01-03 10:05:05 +09:00
c8fed30c65 Move docs 2020-01-03 09:51:43 +09:00
11c388252c Add concepts and guidelines doc 2020-01-03 09:33:16 +09:00
e41956c7e7 Update README 2019-12-31 16:40:36 +09:00
f3583f71f8 Update README 2019-12-31 16:39:30 +09:00
e8e53ed431 Update fixture paths 2019-12-31 13:40:46 +09:00
cff82c7ec9 Update fixture paths 2019-12-31 12:57:14 +09:00
c388aba95a Add env override for repo path 2019-12-31 12:33:03 +09:00
02f0ffd558 Update test fixture 2019-12-31 12:02:17 +09:00
b616fd026e Add pytest slash command 2019-12-30 14:18:41 +09:00
ba79823749 Update examples 2019-12-30 12:36:39 +09:00
0af5090b86 Update README 2019-12-30 12:11:55 +09:00
b750589747 Update README 2019-12-30 11:10:22 +09:00
894d4b1412 Update README 2019-12-29 17:35:53 +09:00
95615643f0 Add updating doc 2019-12-29 17:33:07 +09:00
fb48683668 Fix bug when symbolic ref fails 2019-12-29 16:41:59 +09:00
0fb7b7c32a Update README 2019-12-29 16:29:30 +09:00
3e4ab24cbb Update examples 2019-12-29 16:15:13 +09:00
8c86d0f83f Update workflows 2019-12-29 16:05:24 +09:00
673922cfd2 Update README 2019-12-29 15:50:32 +09:00
b11cb71e96 Update input defaults 2019-12-29 15:14:33 +09:00
bdadb3b4a0 Update PyGithub to v1.45 2019-12-29 14:40:49 +09:00
9bdc8b271f Bump version 2019-12-29 14:22:44 +09:00
c208033c6a Remove v1 action code 2019-12-29 14:22:23 +09:00
01aec28fd9 Remove todo comment 2019-12-28 19:05:35 +09:00
484de7fc89 Delete the branch if there is no diff with the base 2019-12-28 18:34:32 +09:00
71f4fe31a8 Update integration tests 2019-12-28 17:31:19 +09:00
883f800b96 Change missing project and column to an error 2019-12-28 17:10:28 +09:00
b3805d65e3 Update string formatting 2019-12-28 17:09:58 +09:00
4f8d5b9d3e Add a check for user config set in the workflow 2019-12-27 18:29:37 +09:00
efbd4fa1ef Fix setting author and committer 2019-12-27 15:32:37 +09:00
b7565b81a7 Add v2 alpha 2019-12-27 14:40:11 +09:00
ec6352b0a0 Merge pull request #85 from peter-evans/dev
Minor refactor to set url via git params
2019-12-08 16:30:10 +09:00
ce4df54075 Bump version 2019-12-08 16:28:43 +09:00
c855a500d1 Add clean slash command 2019-12-08 15:36:00 +09:00
baf52378ae Minor refactor to set url via git params 2019-12-08 15:02:54 +09:00
e6ebd7ca5c Merge pull request #83 from peter-evans/dev
Add inputs for committer-name and committer-email
2019-12-06 19:17:09 +09:00
83fef4e8a1 Update README 2019-12-06 18:36:27 +09:00
d3dc225920 Add inputs for committer-name and committer-email 2019-12-06 17:06:49 +09:00
6342438c4e Updated README 2019-12-05 18:28:50 +09:00
324fe6cdde Add slash command dispatch workflow 2019-12-03 19:50:15 +09:00
4faa5d5a97 Update README 2019-11-24 09:28:14 +09:00
381bc30cef Update example screenshot 2019-11-24 09:14:05 +09:00
7531167f24 Merge pull request #76 from peter-evans/dev
Add feature to create project card for pull request
2019-11-24 09:04:44 +09:00
66a1436815 Update input descriptions 2019-11-24 09:00:31 +09:00
1d1fedd99c Add feature to create project card for pull request 2019-11-24 08:48:32 +09:00
823751817d Update examples 2019-11-16 10:19:03 +09:00
1edcf07a19 Update examples 2019-11-16 10:17:29 +09:00
4c95214a9b Update README 2019-11-16 09:27:21 +09:00
b37c0a038c Update input description 2019-11-16 09:26:07 +09:00
d9841567d1 Update README 2019-11-16 09:25:06 +09:00
2cce94bfb0 Bump version 2019-11-16 09:25:00 +09:00
d968e8b11b Add warning and fail gracefully when ref is invalid for base 2019-11-16 09:24:46 +09:00
6c73093f9b Merge pull request #74 from peter-evans/renovate/gitpython-3.x
Update dependency GitPython to v3.0.5
2019-11-15 19:46:46 +09:00
d17c882ef3 Update examples 2019-11-15 08:27:12 +09:00
d5f4e48a66 Update dependency GitPython to v3.0.5 2019-11-14 01:46:43 +00:00
484d8bd85d Merge pull request #72 from peter-evans/dev
Logging and handling of PR events from forks
2019-11-13 23:59:38 +09:00
74b6a9337d Update documentation 2019-11-13 23:52:33 +09:00
2dd62d446e Bump version 2019-11-13 19:10:53 +09:00
b6a98c049d Add logging and handling for pr events from forks 2019-11-13 19:10:38 +09:00
67e8822279 Update examples 2019-11-13 11:05:55 +09:00
956a240181 Update examples 2019-11-11 22:49:11 +09:00
179aca65d7 Update examples 2019-11-11 22:45:23 +09:00
e4c8b68e33 Update dockerhub-description workflow 2019-11-10 22:08:30 +09:00
731211fd81 Fix bug with filtering pull requests 2019-11-10 18:14:10 +09:00
006313b45a Fix bug when updating a pull request 2019-11-10 16:56:55 +09:00
3c86e9a421 Add outputs description 2019-11-10 13:07:11 +09:00
e49ebca896 Merge pull request #69 from peter-evans/renovate/pin-dependencies
Pin dependency @zeit/ncc to 0.20.5
2019-11-09 23:36:02 +09:00
d9652cf06a Pin dependency @zeit/ncc to 0.20.5 2019-11-09 14:06:18 +00:00
4517bf7ce3 Merge pull request #68 from peter-evans/dev
Convert to hybrid multi-platfrom
2019-11-09 23:06:00 +09:00
04aa84a2af Update examples 2019-11-09 22:48:30 +09:00
11baa5dc07 Update examples 2019-11-09 22:46:01 +09:00
1281ebd51a Update README 2019-11-09 18:22:57 +09:00
46dc4f23d5 Update workflows 2019-11-09 18:22:03 +09:00
d469aaf0ef Update workflows 2019-11-09 17:59:23 +09:00
5692e041c6 Set a step output 2019-11-09 17:42:11 +09:00
55e7b1ec28 Update action to hybrid multi platform 2019-11-09 17:24:47 +09:00
d99b58f9d5 Merge pull request #67 from peter-evans/renovate/pygithub-1.x
Update dependency PyGithub to v1.44.1
2019-11-08 20:31:06 +09:00
b8ce455e56 Update dependency PyGithub to v1.44.1 2019-11-07 03:57:40 +00:00
c9e477ec05 Update examples 2019-11-05 14:10:23 +09:00
979d417e9d Update examples 2019-11-05 13:23:16 +09:00
b74a77cefc Update examples 2019-11-04 12:19:19 +09:00
2af7d9797a Update documentation 2019-11-04 11:13:18 +09:00
b72489d629 Update image version 2019-11-04 11:08:47 +09:00
1461de7c9d Update README 2019-11-04 11:01:08 +09:00
3061b7bc6e Update workflows 2019-11-04 11:01:00 +09:00
98ee7d63d3 Improvements to updating pull requests 2019-11-04 11:00:38 +09:00
066b4398c0 Merge pull request #66 from staabm/patch-2
Link ::set-output
2019-11-03 18:42:49 +09:00
028e1bbdd5 Link ::set-output 2019-11-03 10:38:30 +01:00
8b8594e5e4 Update examples 2019-11-03 18:29:46 +09:00
cefb562076 Merge pull request #63 from peter-evans/documentation
Update documentation
2019-11-03 17:46:08 +09:00
9eccdd1603 Update README 2019-11-03 17:44:22 +09:00
f429dfef59 Update examples 2019-11-03 17:41:42 +09:00
963e38fd7d Add examples documentation 2019-11-03 17:37:39 +09:00
f411b5902d Merge pull request #62 from peter-evans/asset-location
Update location of assets
2019-11-03 15:47:02 +09:00
97dd1f3ef4 Update location of assets 2019-11-03 15:46:11 +09:00
3413a77ac7 Update README 2019-10-30 16:10:02 +09:00
206756cd93 Update image version 2019-10-30 16:04:28 +09:00
263b5927d1 Remove ignore event logic 2019-10-30 15:27:54 +09:00
a0ad81c6c7 Merge pull request #58 from peter-evans/renovate/gitpython-3.x
Update dependency GitPython to v3.0.4
2019-10-29 13:31:52 +09:00
23846c7f83 Update dependency GitPython to v3.0.4 2019-10-22 11:43:55 +00:00
96ac64ddca Update README 2019-10-22 11:05:42 +09:00
a2ed0052e5 Update image version 2019-10-22 11:01:54 +09:00
6e7e889e01 Minor fixes and refactor 2019-10-22 10:55:33 +09:00
7e18a60b72 Merge pull request #57 from peter-evans/renovate/docker-alpine-3.x
Update alpine Docker tag to v3.10.3
2019-10-22 10:53:31 +09:00
0be4e396cf Update alpine Docker tag to v3.10.3 2019-10-21 17:57:06 +00:00
b86bbce924 Update README 2019-10-21 20:36:01 +09:00
e1e9dd3f1c Update image version 2019-10-21 20:32:02 +09:00
8c2a43987b Add an override for the base branch 2019-10-21 20:17:31 +09:00
c5ff844b40 Update README 2019-10-21 18:42:39 +09:00
1f47cf3861 Update workflows 2019-10-21 18:31:14 +09:00
bcdc7ae6f9 Merge pull request #55 from peter-evans/renovate/pygithub-1.x
Update dependency PyGithub to v1.44
2019-10-19 22:58:42 +09:00
91ca84eab5 Update dependency PyGithub to v1.44 2019-10-19 08:44:24 +00:00
875d189c30 Update README 2019-10-14 18:09:59 +09:00
7c3f5f4833 Update README 2019-10-14 16:12:27 +09:00
7a7e797068 Update README 2019-10-14 16:11:09 +09:00
a41f37fc96 Update README 2019-10-14 16:09:58 +09:00
10554ac6c3 Update README 2019-10-14 15:59:21 +09:00
2439726cfe Update README 2019-10-14 15:51:08 +09:00
4f04f4efd9 Update workflows 2019-10-14 15:50:52 +09:00
32d0abbc7c Update README 2019-10-14 15:07:11 +09:00
ed5b8b1eea Update README 2019-10-14 12:38:52 +09:00
ea241dfc79 Update README 2019-10-14 12:37:36 +09:00
0e70fec054 Update README 2019-10-14 12:36:55 +09:00
5077e8d6bd Rename dockerhub description workflow 2019-10-14 10:26:25 +09:00
901e854f30 Update README 2019-10-14 02:02:55 +09:00
f3b1bd6331 Update image version 2019-10-14 01:58:42 +09:00
51ade9f54b Fix for pull refs 2019-10-14 01:39:53 +09:00
3c86dbf9e6 Update README 2019-10-06 15:13:32 +09:00
b2d426ea4e Update README 2019-10-06 15:06:49 +09:00
fbe0cd3d1a Update README 2019-10-06 14:59:45 +09:00
5f9821866a Update workflows 2019-10-06 14:52:15 +09:00
ee153ec8cf Update image version 2019-10-06 14:52:01 +09:00
e138d84114 Add variables to override the commit author name and email 2019-10-06 14:38:15 +09:00
90843d3c39 Update README 2019-10-05 09:22:43 +09:00
90ad96bd01 Update workflows 2019-10-05 09:22:35 +09:00
f8cc05d28d Update README 2019-10-04 14:53:58 +09:00
fe78afb6d7 Update README 2019-10-04 14:46:26 +09:00
af7dbce93d Merge remote-tracking branch 'origin/master' 2019-10-04 14:36:11 +09:00
2e283eddc6 Add workflow to automatically update dockerhub description 2019-10-04 14:35:58 +09:00
3e0a17d097 Update action to use pre-built container 2019-10-04 14:35:22 +09:00
7ebddff547 Merge pull request #52 from peter-evans/renovate/gitpython-3.x
Update dependency GitPython to v3.0.3
2019-10-04 14:30:27 +09:00
1397aded03 Update dependency GitPython to v3.0.3 2019-10-02 19:31:01 +00:00
40fd4990fc Update README 2019-10-02 22:38:58 +09:00
5784478398 Update README 2019-10-02 19:28:30 +09:00
6e9abd8a8d Add action.yml 2019-10-02 19:20:07 +09:00
ef76e27efb Replace fixed-name branch rebase with stash merge 2019-10-02 19:05:46 +09:00
6be23859c3 Update README 2019-09-30 23:24:16 +09:00
aa09d77c75 Update README
Update README


Update README


Update README
2019-09-30 19:16:55 +09:00
b86b066e8c Update README 2019-09-30 18:58:24 +09:00
7556cef424 Update workflows 2019-09-30 18:57:49 +09:00
ff7ec1c208 Fixed branch, random suffix and output pr number 2019-09-30 18:57:37 +09:00
6f333b4ebc Update README 2019-09-30 10:08:06 +09:00
65682597a6 Update workflows 2019-09-30 10:07:58 +09:00
fc9f1a6380 Update image 2019-09-26 19:06:44 +09:00
4bb662aed6 Update README 2019-09-26 19:03:42 +09:00
a5c1d5b56d Update README 2019-09-26 18:19:51 +09:00
dc7361b29a Update image 2019-09-26 18:14:05 +09:00
3c9fba7af9 Update README 2019-09-26 18:07:43 +09:00
ae3acffc2b Merge pull request #46 from peter-evans/issue-params
Issue params
2019-09-26 17:54:10 +09:00
f3f5770a0e Update workflows 2019-09-26 17:49:23 +09:00
64178c87e0 Add new optional parameters 2019-09-26 17:48:53 +09:00
b1a0d5fc24 Update README 2019-09-26 13:35:59 +09:00
7beba58e42 Merge pull request #45 from peter-evans/git-lfs
Update Docker Image to include support for git-lfs
2019-09-26 13:27:02 +09:00
062968931c Update Docker Image to include support for git-lfs 2019-09-26 12:34:25 +09:00
76116b7f39 Update README 2019-09-25 23:58:47 +09:00
e635c55400 Update README 2019-09-25 09:42:45 +09:00
9e25487be4 Update workflows 2019-09-25 09:25:52 +09:00
39529236f7 Update README 2019-09-24 20:35:00 +09:00
f0728e6543 Update README 2019-09-24 20:27:25 +09:00
188c998f8f Update README 2019-09-24 20:23:49 +09:00
1fc2947eb6 Merge pull request #42 from peter-evans/new-params
New parameters
2019-09-24 20:13:43 +09:00
4e47f5e151 Add branch suffix parameter 2019-09-24 20:00:49 +09:00
e543bbd98a Merge pull request #41 from ScriptAutomate/master
Add skip_ignore arg to ignore_event function
2019-09-24 19:21:12 +09:00
4fb3c76ad7 Introduced SKIP_IGNORE env variable option
If the SKIP_IGNORE env var is present, the ignore_event function will not run
2019-09-24 01:08:16 -07:00
fcc56736a6 Add skip_ignore arg to ignore_event function
During testing, I found it useful to have this function skipped so I added a simple check for SKIP_IGNORE env variable
2019-09-24 00:56:53 -07:00
014d447f0c Update README 2019-09-20 10:23:15 +09:00
9d998cbb4c Remove need for repo scoped token 2019-09-20 10:14:51 +09:00
baab1666cc Update README 2019-09-20 09:47:11 +09:00
f639d94f5e Fix ignore_event for all non-push events 2019-09-20 09:39:09 +09:00
fd4e62ed7d Update README 2019-09-12 16:18:12 +09:00
21aff96eee Fix author email and name for scheduled jobs 2019-09-12 16:03:46 +09:00
f4703cdc23 Update README 2019-09-10 18:06:54 +09:00
7980880191 Fix missing colon 2019-09-10 17:58:01 +09:00
4efcea62da Merge pull request #30 from stefanbuck/fix-29-scheduled-jobs
Do not break Action when using schedule jobs
2019-09-10 17:50:03 +09:00
580fc69c02 Fix scheduled events issue 2019-09-10 09:14:39 +02:00
facb42d776 Merge pull request #26 from peter-evans/renovate/gitpython-3.x
Update dependency GitPython to v3.0.2
2019-08-22 13:50:04 +09:00
37fd4d3558 Update dependency GitPython to v3.0.2 2019-08-22 04:38:51 +00:00
bc78d4cf02 Merge pull request #23 from peter-evans/renovate/gitpython-3.x
Update dependency GitPython to v3.0.1
2019-08-15 12:41:54 +09:00
91ff2766bf Update dependency GitPython to v3.0.1 2019-08-15 03:27:28 +00:00
c216905beb Update README 2019-08-13 19:07:24 +09:00
045ccaa641 Ignore events for tags and remotes 2019-08-13 19:00:56 +09:00
a26e9f2362 Update README 2019-08-13 18:26:08 +09:00
54 changed files with 16134 additions and 227 deletions

17
.eslintrc.json Normal file
View File

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

7
.github/ISSUE_TEMPLATE.md vendored Normal file
View 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.

View 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:
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
request-to-parent: false
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
- name: Add reaction
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
reaction-type: hooray

View File

@ -0,0 +1,19 @@
name: Update Docker Hub Description
on:
push:
branches:
- master
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2.1.0
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
DOCKERHUB_REPOSITORY: peterevans/create-pull-request

View File

@ -1,21 +0,0 @@
on: push
name: create-pull-request action testing workflow
jobs:
createPullRequest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Create report file
uses: finnp/create-file-action@1.0.0
env:
FILE_DATA: This is created to test create-pull-request action.
FILE_NAME: report.txt
- name: Create Pull Request
uses: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
COMMIT_MESSAGE: Add report file
PULL_REQUEST_BODY: This PR is auto-generated by [create-pull-request](https://github.com/peter-evans/create-pull-request).
PULL_REQUEST_BRANCH: test-patches
PULL_REQUEST_TITLE: '[Test] Add report file'

View 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"
}
]

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__
.python-version
node_modules
.DS_Store

View File

@ -1,18 +0,0 @@
FROM python:3.7.3
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 /
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY create-pull-request.py /create-pull-request.py
ENTRYPOINT [ "/create-pull-request.py" ]

206
README.md
View File

@ -1,76 +1,196 @@
# Create Pull Request
# <img width="24" height="24" src="docs/assets/logo.svg"> Create Pull Request
[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-Create%20Pull%20Request-blue.svg?colorA=24292e&colorB=0366d6&style=flat&longCache=true&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAM6wAADOsB5dZE0gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAERSURBVCiRhZG/SsMxFEZPfsVJ61jbxaF0cRQRcRJ9hlYn30IHN/+9iquDCOIsblIrOjqKgy5aKoJQj4O3EEtbPwhJbr6Te28CmdSKeqzeqr0YbfVIrTBKakvtOl5dtTkK+v4HfA9PEyBFCY9AGVgCBLaBp1jPAyfAJ/AAdIEG0dNAiyP7+K1qIfMdonZic6+WJoBJvQlvuwDqcXadUuqPA1NKAlexbRTAIMvMOCjTbMwl1LtI/6KWJ5Q6rT6Ht1MA58AX8Apcqqt5r2qhrgAXQC3CZ6i1+KMd9TRu3MvA3aH/fFPnBodb6oe6HM8+lYHrGdRXW8M9bMZtPXUji69lmf5Cmamq7quNLFZXD9Rq7v0Bpc1o/tp0fisAAAAASUVORK5CYII=)](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.
Changes to a repository in the actions workspace persist between actions in a workflow.
This action is designed to be used in conjunction with other actions that modify or add files to your repository.
Changes to a repository in the Actions workspace persist between steps in a workflow.
This action is designed to be used in conjunction with other steps that modify or add files to your repository.
The changes will be automatically committed to a new branch and a pull request created.
Create Pull Request action will:
1. Check for repository changes in the actions workspace. This includes untracked (new) files as well as modified files.
2. Commit all changes to a new branch. The commit will be made using the name and email of the `HEAD` commit author.
3. Create a pull request to merge the new branch into the currently active branch executing the workflow.
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 base&mdash;the branch checked out in the workflow.
Note: In general, it's not good practice to modify your repository during workflows.
This action is experimental and may not work well for repositories that have a very high frequency of commits.
## Documentation
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
- [Examples](docs/examples.md)
- [Updating from v1](docs/updating.md)
## Usage
The default `GITHUB_TOKEN` does not have the access neccessary for this action to work correctly.
Create a new `repo` scoped token [here](https://github.com/settings/tokens) and pass that as a secret to the `REPO_ACCESS_TOKEN` environment variable.
```yml
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
```
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v2.x.x`
### Action inputs
All inputs are **optional**. If not set, sensible default values will be used.
**Note**: If you want pull requests created by this action to trigger an `on: push` or `on: pull_request` workflow then you must use a [Personal Access Token](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) instead of the default `GITHUB_TOKEN`. Alternatively, allow the action to [push using SSH](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#push-using-ssh-deploy-keys) by configuring a deploy key.
| Name | Description | Default |
| --- | --- | --- |
| `token` | `GITHUB_TOKEN` or a `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | `GITHUB_TOKEN` |
| `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` |
| `request-to-parent` | Create the pull request in the parent repository of the checked out fork. See [push pull request branches to a fork](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork) for details. | `false` |
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
| `branch-suffix` | The branch suffix type. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Branch naming](#branch-naming) for details. | |
**Outputs**
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.
```yml
- name: Create Pull Request
uses: peter-evans/create-pull-request@v1.0.0
env:
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v2
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
```
#### Environment variables
### Checkout
These variables are all optional. If not set, a default value will be used.
This action expects repositories to be checked out with `actions/checkout@v2`.
- `PULL_REQUEST_BRANCH` - The branch name. See **Branch naming** below for details.
- `COMMIT_MESSAGE` - The message to use when committing changes.
- `PULL_REQUEST_TITLE` - The title of the pull request.
- `PULL_REQUEST_BODY` - The body of the pull request.
If there is some reason you need to use `actions/checkout@v1` the following step can be added to checkout the branch.
#### Branch naming
The variable `PULL_REQUEST_BRANCH` defaults to `create-pull-request/patch`.
Commits will be made to a branch with this name and suffixed with the short SHA1 commit hash.
e.g.
```
create-pull-request/patch-fcdfb59
create-pull-request/patch-394710b
```yml
- uses: actions/checkout@v1
- run: git checkout "${GITHUB_REF:11}"
```
#### Ignoring files
### Branch naming
For branch naming there are two strategies. Create a fixed-name pull request branch that will be updated with new changes until it is merged or closed, OR, always create a new unique branch each time there are changes to be committed.
#### 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 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`
- `timestamp` - Commits will be made to a branch suffixed by a timestamp. e.g. `create-pull-request/patch-1569322532`, `create-pull-request/patch-1569322552`
- `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.
## Example
### Committer and author
Here is an example that sets all the 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@v1.0.0
env:
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
PULL_REQUEST_BRANCH: my-patches
COMMIT_MESSAGE: Auto-modify files by my-file-modifier-action
PULL_REQUEST_TITLE: Changes from my-file-modifier-action
PULL_REQUEST_BODY: This is an auto-generated PR with changes from my-file-modifier-action
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
committer: Peter Evans <peter-evans@users.noreply.github.com>
```
This configuration will create pull requests that look like this:
### Controlling commits
![Pull Request Example](pull-request-example.png?raw=true)
As well as relying on the action to handle uncommitted changes, you can additionally make your own commits before the action runs.
```yml
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
```
## 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@v2
- name: Create report file
run: date +%s > report.txt
- name: Create Pull Request
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
labels: report, automated pr
assignees: peter-evans
reviewers: peter-evans
team-reviewers: owners, maintainers
milestone: 1
project: Example Project
project-column: To do
branch: example-patches
request-to-parent: false
- name: Check outputs
run: |
echo "Pull Request Number - ${{ env.PULL_REQUEST_NUMBER }}"
echo "Pull Request Number - ${{ steps.cpr.outputs.pr_number }}"
```
This reference configuration will create pull requests that look like this:
![Pull Request Example](docs/assets/pull-request-example.png)
## License
MIT License - see the [LICENSE](LICENSE) file for details
[MIT](LICENSE)

50
action.yml Normal file
View File

@ -0,0 +1,50 @@
name: 'Create Pull Request'
description: 'Creates a pull request for changes to your repository in the actions workspace'
inputs:
token:
description: 'GITHUB_TOKEN or a repo scoped PAT'
default: ${{ github.token }}
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.'
request-to-parent:
description: 'Create the pull request in the parent repository of the checked out fork.'
default: false
base:
description: 'The pull request base branch.'
branch-suffix:
description: 'The branch suffix type.'
outputs:
pr_number:
description: 'The pull request number'
runs:
using: 'node12'
main: 'dist/index.js'
branding:
icon: 'git-pull-request'
color: 'gray-dark'

View File

@ -1,143 +0,0 @@
#!/usr/bin/env python3
''' Create Pull Request '''
import json
import os
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 os.environ.get('DEBUG_EVENT') is not None:
print(json.dumps(github_event, sort_keys=True, indent=2))
return github_event
def ignore_event(github_event):
# 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(**github_event)
if deleted == "True":
print("Ignoring delete branch event.")
return True
return False
def pr_branch_exists(repo, branch):
for ref in repo.remotes.origin.refs:
if ref.name == ("origin/%s" % branch):
return True
return False
def get_head_author(github_event):
email = "{head_commit[author][email]}".format(**github_event)
name = "{head_commit[author][name]}".format(**github_event)
return email, name
def get_head_short_sha1(repo):
return repo.git.rev_parse('--short', 'HEAD')
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://%s:x-oauth-basic@github.com/%s" % (token, github_repository))
def commit_changes(git, branch, commit_message):
git.checkout('HEAD', b=branch)
git.add('-A')
git.commit(m=commit_message)
return git.push('--set-upstream', 'origin', branch)
def create_pull_request(token, repo, head, base, title, body):
return Github(token).get_repo(repo).create_pull(
title=title,
body=body,
base=base,
head=head)
def process_event(github_event, repo, branch, base):
# Fetch required environment variables
github_token = os.environ['GITHUB_TOKEN']
github_repository = os.environ['GITHUB_REPOSITORY']
repo_access_token = os.environ['REPO_ACCESS_TOKEN']
# Fetch remaining optional environment variables
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")
# Get the HEAD committer's email and name
author_email, author_name = get_head_author(github_event)
# Set git configuration
set_git_config(repo.git, author_email, author_name)
# Update URL for the 'origin' remote
set_git_remote_url(repo.git, repo_access_token, github_repository)
# Commit the repository changes
print("Committing changes.")
commit_result = commit_changes(repo.git, branch, commit_message)
print(commit_result)
# Create the pull request
print("Creating a request to pull %s into %s." % (branch, base))
pull_request = create_pull_request(
github_token,
github_repository,
branch,
base,
title,
body
)
print("Created pull request %d." % pull_request.number)
# Get the JSON event data
github_event = get_github_event(os.environ['GITHUB_EVENT_PATH'])
# Check if this event should be ignored
if not ignore_event(github_event):
# 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 current branch as the target base branch
base = os.environ['GITHUB_REF'][11:]
# Skip if the current branch is a PR branch created by this action
if not base.startswith(branch):
# Suffix with the short SHA1 hash
branch = "%s-%s" % (branch, get_head_short_sha1(repo))
# Check if a PR branch already exists for this HEAD commit
if not pr_branch_exists(repo, 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(github_event, repo, branch, base)
else:
print("Repository has no modified or untracked files. Skipping.")
else:
print(
"Pull request branch '%s' already exists for this commit. Skipping." %
branch)
else:
print(
"Branch '%s' was created by this action. Skipping." % base)

48
dist/cpr/common.py vendored Normal file
View File

@ -0,0 +1,48 @@
#!/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_github_repository(url):
# Parse the protocol and github repository from a URL
# e.g. HTTPS, peter-evans/create-pull-request
https_pattern = re.compile(r"^https://github.com/(.+/.+)$")
ssh_pattern = re.compile(r"^git@github.com:(.+/.+).git$")
match = https_pattern.match(url)
if match is not None:
return "HTTPS", match.group(1)
match = ssh_pattern.match(url)
if match is not None:
return "SSH", match.group(1)
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
def parse_display_name_email(display_name_email):
# Parse the name and email address from a string in the following format
# Display Name <email@address.com>
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/cpr/create_or_update_branch.py vendored Normal file
View 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}

View File

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

228
dist/cpr/create_pull_request.py vendored Executable file
View File

@ -0,0 +1,228 @@
#!/usr/bin/env python3
""" Create Pull Request """
import base64
import common as cmn
import create_or_update_branch as coub
import create_or_update_pull_request as coupr
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 get_repository_detail(repo):
remote_origin_url = get_git_config_value(repo, "remote.origin.url")
if remote_origin_url is None:
raise ValueError("Failed to fetch 'remote.origin.url' from git config")
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
return remote_origin_url, protocol, github_repository
def git_user_config_is_set(repo):
name = get_git_config_value(repo, "user.name")
email = get_git_config_value(repo, "user.email")
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"]
# 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 the GitHub repository from git config
# This will be the target repository for the pull request
repo_url, protocol, github_repository = get_repository_detail(repo)
print(f"Target repository set to {github_repository}")
if protocol == "HTTPS":
print(f"::debug::Using HTTPS protocol")
# Encode and configure the basic credential for HTTPS access
basic_credential = base64.b64encode(
f"x-access-token:{github_token}".encode("utf-8")
).decode("utf-8")
# Mask the basic credential in logs and debug output
print(f"::add-mask::{basic_credential}")
repo.git.set_persistent_git_options(
c=f"http.https://github.com/.extraheader=AUTHORIZATION: basic {basic_credential}"
)
# Determine if the checked out ref is a valid base for a pull request
# The action needs the checked out HEAD ref to be a branch
# 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)
# 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"),
os.environ.get("CPR_REQUEST_TO_PARENT"),
)

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

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

63
dist/cpr/test_common.py vendored Normal file
View File

@ -0,0 +1,63 @@
#!/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_github_repository_success():
protocol, repository = cmn.parse_github_repository(
"https://github.com/peter-evans/create-pull-request"
)
assert protocol == "HTTPS"
assert repository == "peter-evans/create-pull-request"
protocol, repository = cmn.parse_github_repository(
"git@github.com:peter-evans/create-pull-request.git"
)
assert protocol == "SSH"
assert repository == "peter-evans/create-pull-request"
def test_parse_github_repository_failure():
url = "https://github.com/peter-evans"
with pytest.raises(ValueError) as e_info:
cmn.parse_github_repository(url)
assert (
e_info.value.args[0]
== f"The format of '{url}' is not a valid GitHub repository URL"
)
def test_parse_display_name_email_success():
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
assert name == "abc def"
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/cpr/test_create_or_update_branch.py vendored Normal file
View 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
)

5737
dist/index.js vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
dist/vendor/Deprecated-1.2.7.tar.gz vendored Normal file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

BIN
dist/vendor/PyJWT-1.7.1.tar.gz vendored Normal file

Binary file not shown.

BIN
dist/vendor/certifi-2019.11.28.tar.gz vendored Normal file

Binary file not shown.

BIN
dist/vendor/chardet-3.0.4.tar.gz vendored Normal file

Binary file not shown.

BIN
dist/vendor/gitdb-4.0.2.tar.gz vendored Normal file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

BIN
dist/vendor/urllib3-1.25.8.tar.gz vendored Normal file

Binary file not shown.

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

Binary file not shown.

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

6
docs/assets/logo.svg Normal file
View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="43%" height="43%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: #000000;"><title>git-pull-request</title>
<circle cx="18" cy="18" r="3"></circle>
<circle cx="6" cy="6" r="3"></circle>
<path d="M13 6h3a2 2 0 0 1 2 2v7"></path>
<line x1="6" y1="9" x2="6" y2="21"></line>
</svg>

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

320
docs/concepts-guidelines.md Normal file
View File

@ -0,0 +1,320 @@
# Concepts, guidelines and advanced usage
This document covers terminology, how the action works, general usage guidelines, and advanced usage.
- [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)
- [Security](#security)
- [Advanced usage](#advanced-usage)
- [Creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository)
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
- [Running in a container](#running-in-a-container)
- [Creating pull requests on tag push](#creating-pull-requests-on-tag-push)
## Terminology
[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`&mdash;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.
![Create Pull Request GitGraph](assets/cpr-gitgraph.png)
## 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
```
### Security
From a security perspective it's good practice to fork third-party actions, review the code, and use your fork of the action in workflows.
By using third-party actions directly the risk exists that it could be modified to do something malicious, such as capturing secrets.
This action uses [ncc](https://github.com/zeit/ncc) to compile the Node.js code and dependencies into a single file.
Python dependencies are vendored and committed to the repository [here](https://github.com/peter-evans/create-pull-request/tree/master/dist/vendor).
No dependencies are downloaded during the action execution.
Vendored Python dependencies can be reviewed by rebuilding the [dist](https://github.com/peter-evans/create-pull-request/tree/master/dist) directory and redownloading dependencies.
The following commands require Node and Python 3.
```
npm install
npm run clean
npm run package
```
The `dist` directory should be rebuilt leaving no git diff.
## Advanced usage
### Creating pull requests in a remote repository
Checking out a branch from a different repository from where the workflow is executing will make *that repository* the target for the created pull request. In this case, a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) is required.
```yml
- uses: actions/checkout@v2
with:
token: ${{ secrets.PAT }}
repository: owner/repo
# Make changes to pull request here
- uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.PAT }}
```
### Push using SSH (deploy keys)
[Deploy keys](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) can be set per repository and so are arguably more secure than using a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
Allowing the action to push with a configured deploy key will trigger `on: push` workflows. This makes it an alternative to using a PAT to trigger checks for pull requests.
How to use SSH (deploy keys) with create-pull-request action:
1. [Create a new SSH key pair](https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key) for your repository. Do not set a passphrase.
2. Copy the contents of the public key (.pub file) to a new repository [deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys) and check the box to "Allow write access."
3. Add a secret to the repository containing the entire contents of the private key.
4. As shown in the example steps below, use the [`webfactory/ssh-agent`](https://github.com/webfactory/ssh-agent) action to install the private key and clone your repository. Remember to checkout the `base` of your pull request if it's not the default branch, e.g. `git checkout my-branch`.
```yml
steps:
- uses: webfactory/ssh-agent@v0.2.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Checkout via SSH
run: git clone git@github.com:peter-evans/create-pull-request.git .
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
```
### Push pull request branches to a fork
Instead of pushing pull request branches to the repository you want to update, you can push them to a fork of that repository.
This allows you to employ the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) by using a dedicated user acting as a [machine account](https://help.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements).
This user has no access to the main repository.
It will use their own fork to push code and create the pull request.
1. Create a new GitHub user and login.
2. Fork the repository that you will be creating pull requests in.
3. Create a [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
4. Logout and log back in to your main user account.
5. Add a secret to your repository containing the above PAT.
6. As shown in the following example workflow, switch the git remote to the fork's URL after checkout and set the action input `request-on-parent` to `true`.
```yaml
- uses: actions/checkout@v2
- run: |
git config user.password ${{ secrets.PAT }}
git remote set-url origin https://github.com/bot-user/fork-project
git fetch --unshallow -p origin
# Make changes to pull request here
- uses: peter-evans/create-pull-request@v2
with:
token: ${{ secrets.PAT }}
request-on-parent: true
```
### Running in a container
This action can be run inside a container by installing the action's dependencies either in the Docker image itself, or during the workflow.
The action requires `python3`, `pip3` and `git` to be installed and on the `PATH`.
Note that `actions/checkout` requires Git 2.18 or higher to be installed, otherwise it will just download the source of the repository instead of cloning it.
**Alpine container example:**
```yml
jobs:
createPullRequestAlpine:
runs-on: ubuntu-latest
container:
image: alpine
steps:
- name: Install dependencies
run: apk --no-cache add git python3
- uses: actions/checkout@v2
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
```
**Ubuntu container example:**
```yml
jobs:
createPullRequestAlpine:
runs-on: ubuntu-latest
container:
image: ubuntu
steps:
- name: Install dependencies
run: |
apt-get update
apt-get install -y software-properties-common
add-apt-repository -y ppa:git-core/ppa
apt-get install -y python3 python3-pip git
- uses: actions/checkout@v2
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
```
### Creating pull requests on tag push
An `on: push` workflow will also trigger when tags are pushed.
During these events, the `actions/checkout` action will check out the `ref/tags/<tag>` git ref by default.
This means the repository will *not* be checked out on an active branch.
If you would like to run `create-pull-request` action on the tagged commit you can achieve this by creating a temporary branch as follows.
```yml
on:
push:
tags:
- 'v*.*.*'
jobs:
example:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create a temporary tag branch
run: |
git config --global user.name 'GitHub'
git config --global user.email 'noreply@github.com'
git checkout -b temp-${GITHUB_REF:10}
git push --set-upstream origin temp-${GITHUB_REF:10}
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
base: master
- name: Delete tag branch
run: |
git push --delete origin temp-${GITHUB_REF:10}
```
This is an alternative, simpler workflow to the one above. However, this is not guaranteed to checkout the tagged commit.
There is a chance that in between the tag being pushed and checking out the `master` branch in the workflow, another commit is made to `master`. If that possibility is not a concern, this workflow will work fine.
```yml
on:
push:
tags:
- 'v*.*.*'
jobs:
example:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: master
# Make changes to pull request here
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
```

414
docs/examples.md Normal file
View File

@ -0,0 +1,414 @@
# Examples
- [Use case: Create a pull request to update X on push](#use-case-create-a-pull-request-to-update-x-on-push)
- [Update project authors](#update-project-authors)
- [Keep a branch up-to-date with another](#keep-a-branch-up-to-date-with-another)
- [Use case: Create a pull request to update X periodically](#use-case-create-a-pull-request-to-update-x-periodically)
- [Update NPM dependencies](#update-npm-dependencies)
- [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:
commit-message: update authors
title: Update AUTHORS
body: Credit new contributors by updating AUTHORS
branch: update-authors
```
### Keep a branch up-to-date with another
This is a use case where a branch should be kept up to date with another by opening a pull request to update it. The pull request should then be updated with new changes until it is merged or closed.
In this example scenario, a branch called `production` should be updated via pull request to keep it in sync with `master`. Merging the pull request is effectively promoting those changes to production.
```yml
name: Create production promotion pull request
on:
push:
branches:
- master
jobs:
productionPromotion:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: production
- name: Reset promotion branch
run: |
git fetch origin master:master
git reset --hard master
- name: Create Pull Request
uses: peter-evans/create-pull-request@v2
with:
branch: production-promotion
```
## Use case: Create a pull request to update X periodically
This pattern will work well for updating any kind of static content from an external source. The workflow executes on a schedule and raises a pull request when there are changes.
### 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:
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:
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:
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
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:
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:
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:
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
View 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.

3
jest.config.js Normal file
View File

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

6049
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "create-pull-request",
"version": "2.0.0",
"description": "Creates a pull request for changes to your repository in the actions workspace",
"main": "index.js",
"scripts": {
"clean": "rm -rf dist",
"lint": "eslint src/index.js",
"test": "eslint src/index.js && jest",
"build": "ncc build src/index.js -o dist",
"vendor-deps": "pip download -r src/cpr/requirements.txt --no-binary=:all: -d dist/vendor",
"package": "npm run build && npm run vendor-deps"
},
"repository": {
"type": "git",
"url": "git+https://github.com/peter-evans/create-pull-request.git"
},
"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/tool-cache": "^1.1.2",
"is-docker": "^2.0.0"
},
"devDependencies": {
"@zeit/ncc": "0.22.0",
"eslint": "6.8.0",
"jest": "25.2.4"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

View File

@ -1,2 +0,0 @@
GitPython==3.0.0
PyGithub==1.43.8

48
src/cpr/common.py Normal file
View File

@ -0,0 +1,48 @@
#!/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_github_repository(url):
# Parse the protocol and github repository from a URL
# e.g. HTTPS, peter-evans/create-pull-request
https_pattern = re.compile(r"^https://github.com/(.+/.+)$")
ssh_pattern = re.compile(r"^git@github.com:(.+/.+).git$")
match = https_pattern.match(url)
if match is not None:
return "HTTPS", match.group(1)
match = ssh_pattern.match(url)
if match is not None:
return "SSH", match.group(1)
raise ValueError(f"The format of '{url}' is not a valid GitHub repository URL")
def parse_display_name_email(display_name_email):
# Parse the name and email address from a string in the following format
# Display Name <email@address.com>
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

View 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}

View File

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

228
src/cpr/create_pull_request.py Executable file
View File

@ -0,0 +1,228 @@
#!/usr/bin/env python3
""" Create Pull Request """
import base64
import common as cmn
import create_or_update_branch as coub
import create_or_update_pull_request as coupr
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 get_repository_detail(repo):
remote_origin_url = get_git_config_value(repo, "remote.origin.url")
if remote_origin_url is None:
raise ValueError("Failed to fetch 'remote.origin.url' from git config")
protocol, github_repository = cmn.parse_github_repository(remote_origin_url)
return remote_origin_url, protocol, github_repository
def git_user_config_is_set(repo):
name = get_git_config_value(repo, "user.name")
email = get_git_config_value(repo, "user.email")
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"]
# 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 the GitHub repository from git config
# This will be the target repository for the pull request
repo_url, protocol, github_repository = get_repository_detail(repo)
print(f"Target repository set to {github_repository}")
if protocol == "HTTPS":
print(f"::debug::Using HTTPS protocol")
# Encode and configure the basic credential for HTTPS access
basic_credential = base64.b64encode(
f"x-access-token:{github_token}".encode("utf-8")
).decode("utf-8")
# Mask the basic credential in logs and debug output
print(f"::add-mask::{basic_credential}")
repo.git.set_persistent_git_options(
c=f"http.https://github.com/.extraheader=AUTHORIZATION: basic {basic_credential}"
)
# Determine if the checked out ref is a valid base for a pull request
# The action needs the checked out HEAD ref to be a branch
# 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)
# 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 '{repo.full_name}/{branch}'")
repo.git.push("--force", repo_url, f"HEAD:refs/heads/{branch}")
# Set the base. It would have been 'None' if not specified as an input
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"),
os.environ.get("CPR_REQUEST_TO_PARENT"),
)

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

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

63
src/cpr/test_common.py Normal file
View File

@ -0,0 +1,63 @@
#!/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_github_repository_success():
protocol, repository = cmn.parse_github_repository(
"https://github.com/peter-evans/create-pull-request"
)
assert protocol == "HTTPS"
assert repository == "peter-evans/create-pull-request"
protocol, repository = cmn.parse_github_repository(
"git@github.com:peter-evans/create-pull-request.git"
)
assert protocol == "SSH"
assert repository == "peter-evans/create-pull-request"
def test_parse_github_repository_failure():
url = "https://github.com/peter-evans"
with pytest.raises(ValueError) as e_info:
cmn.parse_github_repository(url)
assert (
e_info.value.args[0]
== f"The format of '{url}' is not a valid GitHub repository URL"
)
def test_parse_display_name_email_success():
name, email = cmn.parse_display_name_email("abc def <abc@def.com>")
assert name == "abc def"
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"
)

View 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
)

97
src/git.js Normal file
View File

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

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

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

120
src/index.js Normal file
View File

@ -0,0 +1,120 @@
const { inspect } = require("util");
const isDocker = require("is-docker");
const core = require("@actions/core");
const exec = require("@actions/exec");
const setupPython = require("./setup-python");
const {
getRepoPath,
getAndUnsetConfigOption,
addConfigOption
} = require("./git");
const EXTRAHEADER_OPTION = "http.https://github.com/.extraheader";
const EXTRAHEADER_VALUE_REGEX = "^AUTHORIZATION:";
async function run() {
try {
// Allows ncc to find assets to be included in the distribution
const cpr = __dirname + "/cpr";
core.debug(`cpr: ${cpr}`);
// Determine how to access python and pip
const { pip, python } = (function() {
if (isDocker()) {
core.info("Running inside a Docker container");
// Python 3 assumed to be installed and on the PATH
return {
pip: "pip3",
python: "python3"
};
} else {
// Setup Python from the tool cache
setupPython("3.x", "x64");
return {
pip: "pip",
python: "python"
};
}
})();
// Install requirements
await exec.exec(pip, [
"install",
"--requirement",
`${cpr}/requirements.txt`,
"--no-index",
`--find-links=${__dirname}/vendor`
]);
// Fetch action inputs
const inputs = {
token: core.getInput("token"),
path: core.getInput("path"),
commitMessage: core.getInput("commit-message"),
committer: core.getInput("committer"),
author: core.getInput("author"),
title: core.getInput("title"),
body: core.getInput("body"),
labels: core.getInput("labels"),
assignees: core.getInput("assignees"),
reviewers: core.getInput("reviewers"),
teamReviewers: core.getInput("team-reviewers"),
milestone: core.getInput("milestone"),
project: core.getInput("project"),
projectColumn: core.getInput("project-column"),
branch: core.getInput("branch"),
request_to_parent: core.getInput("request-to-parent"),
base: core.getInput("base"),
branchSuffix: core.getInput("branch-suffix")
};
core.debug(`Inputs: ${inspect(inputs)}`);
// Set environment variables from inputs.
if (inputs.token) process.env.GITHUB_TOKEN = inputs.token;
if (inputs.path) process.env.CPR_PATH = inputs.path;
if (inputs.commitMessage) process.env.CPR_COMMIT_MESSAGE = inputs.commitMessage;
if (inputs.committer) process.env.CPR_COMMITTER = inputs.committer;
if (inputs.author) process.env.CPR_AUTHOR = inputs.author;
if (inputs.title) process.env.CPR_TITLE = inputs.title;
if (inputs.body) process.env.CPR_BODY = inputs.body;
if (inputs.labels) process.env.CPR_LABELS = inputs.labels;
if (inputs.assignees) process.env.CPR_ASSIGNEES = inputs.assignees;
if (inputs.reviewers) process.env.CPR_REVIEWERS = inputs.reviewers;
if (inputs.teamReviewers) process.env.CPR_TEAM_REVIEWERS = inputs.teamReviewers;
if (inputs.milestone) process.env.CPR_MILESTONE = inputs.milestone;
if (inputs.project) process.env.CPR_PROJECT_NAME = inputs.project;
if (inputs.projectColumn) process.env.CPR_PROJECT_COLUMN_NAME = inputs.projectColumn;
if (inputs.branch) process.env.CPR_BRANCH = inputs.branch;
if (inputs.request_to_parent) process.env.CPR_REQUEST_TO_PARENT = inputs.request_to_parent;
if (inputs.base) process.env.CPR_BASE = inputs.base;
if (inputs.branchSuffix) process.env.CPR_BRANCH_SUFFIX = inputs.branchSuffix;
// Get the repository path
var repoPath = getRepoPath(inputs.path);
// Get the extraheader config option if it exists
var extraHeaderOption = await getAndUnsetConfigOption(
repoPath,
EXTRAHEADER_OPTION,
EXTRAHEADER_VALUE_REGEX
);
// Execute create pull request
await exec.exec(python, [`${cpr}/create_pull_request.py`]);
} catch (error) {
core.setFailed(error.message);
} finally {
// Restore the extraheader config option
if (extraHeaderOption) {
if (
await addConfigOption(
repoPath,
EXTRAHEADER_OPTION,
extraHeaderOption.value
)
)
core.debug(`Restored config option '${EXTRAHEADER_OPTION}'`);
}
}
}
run();

52
src/setup-python.js Normal file
View 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;