Compare commits
543 Commits
v3.0.0
...
signed-com
Author | SHA1 | Date | |
---|---|---|---|
714e9be377 | |||
35dbaca7d5 | |||
8101ce8ff4 | |||
a636b8113c | |||
0db6c3cf7b | |||
36e042a736 | |||
6284ea5854 | |||
03266d3789 | |||
404696dda5 | |||
0e209053e0 | |||
548d90536f | |||
3e7e19f0eb | |||
7c0b09154e | |||
9a6173b25c | |||
13c3ab4d5e | |||
081c241f6e | |||
5bb83f1307 | |||
70815fee7e | |||
93bc7fd9cd | |||
a7dcd19d46 | |||
ed59299f13 | |||
cb4a03daab | |||
ca2ba77cd9 | |||
d884c1e53f | |||
85c93e4595 | |||
cd5c7e4b8b | |||
67f9e1be23 | |||
c5a7806660 | |||
4383ba9ef0 | |||
36f7648874 | |||
5f7c1586fd | |||
db1713da3a | |||
ca98a71ccc | |||
ce008085c8 | |||
7318c0b7b6 | |||
e30bbbb3c9 | |||
bad19b8e0b | |||
098cf60ce4 | |||
1ec1358801 | |||
b5ed4c38bc | |||
b67febc3a3 | |||
7c71392b65 | |||
bdffaf9259 | |||
0a0317b2d8 | |||
d9e8da8566 | |||
59e72ed4d2 | |||
15410bdb79 | |||
ce1b5d49b6 | |||
479b20f068 | |||
8c75f4ab5f | |||
5874ea5902 | |||
6d6857d369 | |||
9153d834b6 | |||
c55203cfde | |||
6ce4eca6b6 | |||
36ef0ed92f | |||
8500972a13 | |||
bda5ade93c | |||
70a41aba78 | |||
57a101480a | |||
b3a2c5d525 | |||
02c7da59e8 | |||
bac6da8071 | |||
a4f52f8033 | |||
853c071bcf | |||
d2c126edc7 | |||
43d39c6836 | |||
5a9d206da2 | |||
e0743ed96c | |||
e1529cb8ab | |||
aad52e87e7 | |||
a64ebdd734 | |||
51b40aff5f | |||
49006b2a60 | |||
b1ddad2c99 | |||
bb809027fd | |||
e0037d470c | |||
94b1f99e3a | |||
69c27eaf4a | |||
7ea722a0f6 | |||
5ee839affd | |||
60fc256c67 | |||
0c67723361 | |||
4e288e851b | |||
278e5302dd | |||
5e5e72ede9 | |||
5ac05dbaf3 | |||
583b402db4 | |||
1a0e857f60 | |||
1f76dd3b26 | |||
efda6f2ece | |||
382b1bf0d8 | |||
040164963a | |||
9e49b1873f | |||
798d65c1e2 | |||
ac8dd8903f | |||
5b5eb8d8d0 | |||
7e6ae5af18 | |||
1c0b168d50 | |||
4e1ec66aad | |||
d07501c8ac | |||
f5ba48ac1e | |||
f0f8250914 | |||
fe61cf72ef | |||
4fa9ccf7a7 | |||
79098666d7 | |||
d7c27ba1b1 | |||
c526248631 | |||
2b66fab098 | |||
57390a8261 | |||
76c6f5c20e | |||
2f092153d6 | |||
c643f6fba5 | |||
8c6bf8e9fa | |||
57ae48c0ec | |||
d4d7b4e3a6 | |||
7865f819b0 | |||
ff2bf9be1f | |||
fa448197ec | |||
e42d7ab098 | |||
57a92650b1 | |||
a623c1421e | |||
1efedcfa53 | |||
73d77c9d42 | |||
ae78d6487c | |||
8c11f0ebce | |||
0c31730c33 | |||
c8e3bd8337 | |||
3e02667f27 | |||
5de036d28f | |||
76ff63671c | |||
6daff55809 | |||
3588b1aa44 | |||
60d8781d60 | |||
ae06da3674 | |||
d806186ee3 | |||
0652023649 | |||
8a825a8466 | |||
b5f830072e | |||
a5d4677573 | |||
4c2973e730 | |||
a05bf394be | |||
803660ec69 | |||
21a254626f | |||
bb0c945e6e | |||
bd41655446 | |||
6d9c0cdf58 | |||
4306b59af5 | |||
80507e9341 | |||
4e9c9ce808 | |||
989188a00c | |||
e4b37ab067 | |||
d721b8be51 | |||
023de218b7 | |||
0cd7ff0e63 | |||
fca1e78bd8 | |||
92a21ab9e6 | |||
157a260267 | |||
270c03dcf3 | |||
8290a989c8 | |||
3f6dd507d6 | |||
842881bb7f | |||
9d48be23dc | |||
88e822f486 | |||
ea82a8b8d7 | |||
ab8fb76677 | |||
506056c16d | |||
f908c6b99c | |||
1c8b8845ee | |||
ad8cdb00a0 | |||
5fa7af1a21 | |||
5848fcb314 | |||
e1f6e4238a | |||
f42c0a796a | |||
e70ffc3767 | |||
9fd47c87b5 | |||
020f4502ee | |||
5931b3373e | |||
657fe58e8f | |||
5f8399b325 | |||
eebb6ccce1 | |||
daee43366d | |||
7574e859c5 | |||
f2c0f053fc | |||
19e044019f | |||
153407881e | |||
143be5d671 | |||
51e8ca2340 | |||
712add83f2 | |||
a9e8aabc8b | |||
37be4ffd94 | |||
a5f0e5dc8e | |||
9ef70ee495 | |||
0a287739d3 | |||
4ddb8c8fe7 | |||
7b276d47e7 | |||
007717eeea | |||
30b8ee1ec7 | |||
8e2fa462cf | |||
efbb6ba75d | |||
4cdfca66b8 | |||
8fbd83c22f | |||
371aec12f4 | |||
83165e6fd4 | |||
0183f33490 | |||
37c110a6c3 | |||
284f54f989 | |||
9e5b234402 | |||
2d8e7db84c | |||
041b6ab163 | |||
31de0fdf3f | |||
28295f6636 | |||
8dcaf3883b | |||
2827897dcc | |||
c4f19d3a23 | |||
46035868a3 | |||
c09e1094b6 | |||
eab3ea092e | |||
e179caf91b | |||
f3a21bf340 | |||
5b4a9f6a9e | |||
1847e5d1d6 | |||
c246f7e912 | |||
2dd2b11b09 | |||
05d5a3c3f9 | |||
21479f22fc | |||
36a56dac07 | |||
b7f0c9773b | |||
6a62596740 | |||
d1ed29fe1e | |||
e34fedf492 | |||
495ffbb489 | |||
073c8e6ece | |||
375254568c | |||
09af4e30b5 | |||
38e0b6e68b | |||
a95ef54b72 | |||
021e16bf4a | |||
5141da944e | |||
6217f0d61d | |||
e5cb5210cd | |||
7414bc0848 | |||
f8fe2469e5 | |||
b8f683bda6 | |||
d5f5f4bf24 | |||
6c2fad6b3d | |||
a716f73d72 | |||
24b0f8edad | |||
0e6a687637 | |||
e913bb956f | |||
82604c59cc | |||
951d1b87f0 | |||
df0f02f84a | |||
274dd7d2dc | |||
a38ad37eff | |||
450c1f7d69 | |||
a057a6160f | |||
391a18894a | |||
f5be40c5f3 | |||
4eca541d3c | |||
5a4c812497 | |||
37c8100cff | |||
ec919b7792 | |||
000e3c6002 | |||
85176410d0 | |||
ac971fe9b8 | |||
3019dd596d | |||
e73c69172d | |||
d4a593d321 | |||
58815ba2b6 | |||
29267e1085 | |||
7f1f68b1c1 | |||
38e03b3c64 | |||
3861a52f7a | |||
f14600d64a | |||
9895f5748a | |||
84c5454c6e | |||
f63262067e | |||
aa2f52f5a1 | |||
bb5781b719 | |||
e398a27af3 | |||
d015db1f4a | |||
f962808be0 | |||
2dd85e3f19 | |||
2f0833515b | |||
ca4fd2f0d1 | |||
ea54357f43 | |||
df09107abb | |||
16ae6c427b | |||
852fead4ce | |||
9b996f5c6a | |||
e8211ce8f4 | |||
10f43fe334 | |||
1313abbfb6 | |||
73ff87b6a4 | |||
d4d765620e | |||
646f785c1f | |||
f73101ae67 | |||
78389986b6 | |||
c5bc9fd446 | |||
17815d689c | |||
a8d53a0985 | |||
daf3998db5 | |||
8861bdedc8 | |||
75a5de63f0 | |||
d36c8e0863 | |||
2b011faafd | |||
331d02c7e2 | |||
d7db273d6c | |||
ee93d78b55 | |||
6c704eb7a8 | |||
88bf0de51c | |||
b38e8b0abe | |||
b4d51739f9 | |||
ad43dccb4d | |||
c2f9cef04d | |||
b67934cbcf | |||
ef83023339 | |||
671dc9c9e0 | |||
ddab646771 | |||
3f9dbd5a76 | |||
171dd555b9 | |||
6e59b075e0 | |||
9c5916f06d | |||
33434f1c62 | |||
9ca978d38e | |||
8e154b2a92 | |||
18f90432be | |||
2721abb4d0 | |||
20dac2ed48 | |||
8557470a68 | |||
10db75894f | |||
5a6b15373e | |||
923ad837f1 | |||
f094b77505 | |||
af7c021bb9 | |||
97872c4843 | |||
bd72e1b792 | |||
f1a7646cea | |||
15b68d176d | |||
0dfc93c104 | |||
252fb19db2 | |||
4b867c4939 | |||
4fb3835236 | |||
19ace475b4 | |||
84d431ad62 | |||
d6d5519d05 | |||
0e8dfbd57d | |||
ffa8cc2261 | |||
00fb6900cb | |||
2fe7e77753 | |||
18f7dc018c | |||
89265e8d24 | |||
a7bb76508d | |||
357cebe268 | |||
f22a7da129 | |||
3f60247108 | |||
dcd5fd746d | |||
4b53b6fd1a | |||
10a1849302 | |||
c209d72428 | |||
ef9e028216 | |||
f8f85df783 | |||
b9117f2e0c | |||
598ffcd35c | |||
d8e8e547cc | |||
507420e035 | |||
67df31e08a | |||
f530141cd3 | |||
a5a72ba246 | |||
a6c8b3814a | |||
c0a9598b0e | |||
3c3d696d5b | |||
bd0f84d69c | |||
ad71e1f128 | |||
368c9da6f6 | |||
d8a389d1fa | |||
225cf628aa | |||
d7a8d0affc | |||
2dc79e58de | |||
95767e7d51 | |||
3263596ac4 | |||
3e21ec0c82 | |||
5050a372c9 | |||
7380612b49 | |||
771ad1b5f4 | |||
093c191148 | |||
00cb0abb4d | |||
fa0950476f | |||
b90b9c1e20 | |||
028a63020c | |||
9d59234a82 | |||
210f7aab2c | |||
6bb7394339 | |||
a518698c07 | |||
8be395fdd3 | |||
36d063872e | |||
9825ae65b1 | |||
243251cf92 | |||
28beef91aa | |||
01f7dd1d28 | |||
32c71c837c | |||
a6d621d73e | |||
09f51e6391 | |||
ed04db61de | |||
ea6b55fc9d | |||
88896f707d | |||
423630f7c0 | |||
66cc0cc1e2 | |||
2a63057d1e | |||
f83efdc34a | |||
fff683e9ca | |||
9f83247b25 | |||
670e508eb6 | |||
d9d6fd980e | |||
8bb8511e4d | |||
c1d92ef456 | |||
1ff93da091 | |||
0524c01297 | |||
548adff9dc | |||
28674474a4 | |||
4fb90330a4 | |||
e4c811acf5 | |||
99ccb3479b | |||
13616a4432 | |||
b5b91bc2b0 | |||
5666cd8fe9 | |||
ad897490d5 | |||
0735106af9 | |||
9aeedaa8c2 | |||
52d31873b6 | |||
09b9ac155b | |||
6ec5e3e26b | |||
8b46437b6d | |||
e361fd1788 | |||
052fc72b41 | |||
ed00d4629c | |||
34371f09e5 | |||
c27ea51ae0 | |||
5e9d0ee9ea | |||
b5f41d9b08 | |||
2455e15969 | |||
05bc46786e | |||
adc6552966 | |||
171fc6cce4 | |||
3fb765f674 | |||
d95c81ee98 | |||
8d5ed6557f | |||
7b1819c092 | |||
be0a8c9666 | |||
a0a6157bf1 | |||
9c5ec2e07d | |||
45c510e1f6 | |||
249b80db6b | |||
6c2b44c6ac | |||
76c58cf6a9 | |||
8c603dbb04 | |||
d01e0807ef | |||
ce699aa2d1 | |||
9984f611a7 | |||
ff0beed1b2 | |||
ddeca94037 | |||
0fd77ba8cc | |||
c7f493a800 | |||
91664dfb28 | |||
13ec5274b1 | |||
bcf9790963 | |||
88ea447de7 | |||
da928d5fcc | |||
2465e435b9 | |||
37b2bd1eca | |||
eb13e17e17 | |||
a1ecc20658 | |||
ffcad23634 | |||
f4b52b768a | |||
af682c8fcb | |||
7378b23cb0 | |||
370ae6d537 | |||
ae0797ee12 | |||
e05457394a | |||
44f76dd5b3 | |||
279e66ed27 | |||
ce9dd3641e | |||
1a00b34382 | |||
e17bb55cb7 | |||
1890e1ec35 | |||
a49ee3308e | |||
16fa12ee5f | |||
5ea31358e9 | |||
105f0d3816 | |||
8fb2374109 | |||
a68328a1ee | |||
9bf4b302a5 | |||
da6a868b86 | |||
cbd5e97fef | |||
095c53659b | |||
fc687f898b | |||
3b32bfa4f7 | |||
9c3bf74d10 | |||
b9ed02107c | |||
2f31350598 | |||
d14206169d | |||
82c423c73b | |||
7cf22579c6 | |||
5893420513 | |||
8bdec9b230 | |||
2570a753e0 | |||
1c466bedd5 | |||
a755ad5ffb | |||
52cabd40b9 | |||
12b7e7a141 | |||
8f96fd0252 | |||
5f45e30ff9 | |||
348abed1de | |||
06ab0ad94e | |||
de6e791e81 | |||
184bc2eccd | |||
4dab765cf5 | |||
329417f094 | |||
7311df63a5 | |||
47a0e6b298 | |||
9b02473bb0 | |||
40b1b245eb | |||
64c8d492d5 | |||
28fddffcf8 | |||
ba8309ff9c | |||
ee482f51bd | |||
bf7dd38b26 | |||
71a37c5c33 | |||
f9af0fbfc0 | |||
0c57887fe8 | |||
7ad9f6e012 | |||
dd364f2835 | |||
ddd52205b6 | |||
a81d0faef6 | |||
fc03a820b2 | |||
b2cdf099d0 | |||
f3d08d3f0b | |||
3a21b8d668 | |||
132b02c8e1 | |||
9507cdc7ac | |||
f4bf0d40c3 | |||
cf325ed84b |
@ -9,11 +9,15 @@
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: peter-evans
|
20
.github/dependabot.yml
vendored
Normal file
20
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "tuesday"
|
||||
labels:
|
||||
- "dependencies"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "tuesday"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
labels:
|
||||
- "dependencies"
|
13
.github/workflows/automerge-dependabot.yml
vendored
Normal file
13
.github/workflows/automerge-dependabot.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Auto-merge Dependabot
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
automerge:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- uses: peter-evans/enable-pull-request-automerge@v3
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
pull-request-number: ${{ github.event.pull_request.number }}
|
||||
merge-method: squash
|
59
.github/workflows/ci.yml
vendored
59
.github/workflows/ci.yml
vendored
@ -1,36 +1,39 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'docs/**'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 12.x
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run format-check
|
||||
- run: npm run lint
|
||||
- run: npm run test
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: action.yml
|
||||
path: action.yml
|
||||
@ -43,16 +46,16 @@ jobs:
|
||||
matrix:
|
||||
target: [built, committed]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
ref: main
|
||||
- if: matrix.target == 'built' || github.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- if: matrix.target == 'built' || github.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: action.yml
|
||||
path: .
|
||||
@ -65,8 +68,8 @@ jobs:
|
||||
uses: ./
|
||||
with:
|
||||
commit-message: '[CI] test ${{ matrix.target }}'
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
title: '[CI] test ${{ matrix.target }}'
|
||||
body: |
|
||||
- CI test case for target '${{ matrix.target }}'
|
||||
@ -74,10 +77,10 @@ jobs:
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
branch: ci-test-${{ matrix.target }}
|
||||
branch: ci-test-${{ matrix.target }}-${{ github.sha }}
|
||||
|
||||
- name: Close Pull
|
||||
uses: peter-evans/close-pull@v1
|
||||
uses: peter-evans/close-pull@v3
|
||||
with:
|
||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
comment: '[CI] test ${{ matrix.target }}'
|
||||
@ -89,7 +92,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v1
|
||||
uses: peter-evans/find-comment@v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
@ -98,7 +101,7 @@ jobs:
|
||||
|
||||
- if: steps.fc.outputs.comment-id == ''
|
||||
name: Create comment
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body: |
|
||||
@ -108,25 +111,23 @@ jobs:
|
||||
```
|
||||
|
||||
package:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: rm -rf dist
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update distribution
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
commit-message: 'build: update distribution'
|
||||
title: Update distribution
|
||||
body: |
|
||||
- Updates the distribution for changes on `master`
|
||||
- Updates the distribution for changes on `main`
|
||||
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
|
11
.github/workflows/cpr-example-command.yml
vendored
11
.github/workflows/cpr-example-command.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
||||
createPullRequest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Make changes to pull request
|
||||
run: date +%s > report.txt
|
||||
@ -16,8 +16,9 @@ jobs:
|
||||
uses: ./
|
||||
with:
|
||||
commit-message: Update report
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
title: '[Example] Update report'
|
||||
body: |
|
||||
Update report
|
||||
@ -33,13 +34,15 @@ jobs:
|
||||
milestone: 1
|
||||
draft: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
|
||||
- name: Add reaction
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
|
||||
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
|
||||
|
4
.github/workflows/slash-command-dispatch.yml
vendored
4
.github/workflows/slash-command-dispatch.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slash Command Dispatch
|
||||
uses: peter-evans/slash-command-dispatch@v1
|
||||
uses: peter-evans/slash-command-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
config: >
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
"named_args": true
|
||||
},
|
||||
{
|
||||
"command": "pytest",
|
||||
"command": "testv5",
|
||||
"permission": "admin",
|
||||
"repository": "peter-evans/create-pull-request-tests",
|
||||
"named_args": true
|
||||
|
31
.github/workflows/update-dep.yml
vendored
31
.github/workflows/update-dep.yml
vendored
@ -1,31 +0,0 @@
|
||||
name: Update Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * 4'
|
||||
jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
npx -p npm-check-updates ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v2
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
commit-message: Update dependencies
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: actions-bot <actions-bot@users.noreply.github.com>
|
||||
title: Update dependencies
|
||||
body: |
|
||||
- Dependency updates
|
||||
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
branch: update-dependencies
|
32
.github/workflows/update-major-version.yml
vendored
Normal file
32
.github/workflows/update-major-version.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Update Major Version
|
||||
run-name: Update ${{ github.event.inputs.main_version }} to ${{ github.event.inputs.target }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: The target tag or reference
|
||||
required: true
|
||||
main_version:
|
||||
type: choice
|
||||
description: The major version tag to update
|
||||
options:
|
||||
- v5
|
||||
- v6
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
- name: Git config
|
||||
run: |
|
||||
git config user.name actions-bot
|
||||
git config user.email actions-bot@users.noreply.github.com
|
||||
- name: Tag new target
|
||||
run: git tag -f ${{ github.event.inputs.main_version }} ${{ github.event.inputs.target }}
|
||||
- name: Push new tag
|
||||
run: git push origin ${{ github.event.inputs.main_version }} --force
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ lib/
|
||||
node_modules/
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
195
README.md
195
README.md
@ -1,6 +1,6 @@
|
||||
# <img width="24" height="24" src="docs/assets/logo.svg"> Create Pull Request
|
||||
[](https://github.com/peter-evans/create-pull-request/actions?query=workflow%3ACI)
|
||||
[](https://github.com/marketplace/actions/create-pull-request)
|
||||
[](https://github.com/marketplace/actions/create-pull-request)
|
||||
|
||||
A GitHub action to create a pull request for changes to your repository in the actions workspace.
|
||||
|
||||
@ -21,66 +21,117 @@ Create Pull Request action will:
|
||||
|
||||
- [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md)
|
||||
- [Examples](docs/examples.md)
|
||||
- [Updating to v3](docs/updating.md)
|
||||
- [Updating to v6](docs/updating.md)
|
||||
- [Common issues](docs/common-issues.md)
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v3.x.x`
|
||||
You can also pin to a [specific release](https://github.com/peter-evans/create-pull-request/releases) version in the format `@v6.x.x`
|
||||
|
||||
### Workflow permissions
|
||||
|
||||
For this action to work you must explicitly allow GitHub Actions to create pull requests.
|
||||
This setting can be found in a repository's settings under Actions > General > Workflow permissions.
|
||||
|
||||
For repositories belonging to an organization, this setting can be managed by admins in organization settings under Actions > General > Workflow permissions.
|
||||
|
||||
### Action inputs
|
||||
|
||||
All inputs are **optional**. If not set, sensible defaults 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 cannot use the default `GITHUB_TOKEN`. See the [documentation here](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#triggering-further-workflow-runs) for workarounds.
|
||||
**Note**: If you want pull requests created by this action to trigger an `on: push` or `on: pull_request` workflow then you cannot use the default `GITHUB_TOKEN`. See the [documentation here](docs/concepts-guidelines.md#triggering-further-workflow-runs) for workarounds.
|
||||
|
||||
| Name | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `token` | `GITHUB_TOKEN` or 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). | `GITHUB_TOKEN` |
|
||||
| `token` | `GITHUB_TOKEN` (permissions `contents: write` and `pull-requests: write`) or a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
|
||||
| `git-token` | The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. | Defaults to the value of `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. | `GitHub <noreply@github.com>` |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `add-paths` | A comma or newline-separated list of file paths to commit. Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax. If no paths are specified, all new and modified files are added. See [Add specific paths](#add-specific-paths). | |
|
||||
| `commit-message` | The message to use when committing changes. See [commit-message](#commit-message). | `[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 on github.com. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` |
|
||||
| `author` | The author name and email address in the format `Display Name <email@address.com>`. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` |
|
||||
| `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` |
|
||||
| `branch` | The pull request branch name. | `create-pull-request/patch` |
|
||||
| `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` |
|
||||
| `branch-suffix` | The branch suffix type when using the alternative branching strategy. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Alternative strategy](#alternative-strategy---always-create-a-new-pull-request-branch) for details. | |
|
||||
| `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. |
|
||||
| `push-to-fork` | A fork of the checked out parent repository to which the pull request branch will be pushed. e.g. `owner/repo-fork`. The pull request will be created to merge the fork's branch into the parent's base. 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. | |
|
||||
| `push-to-fork` | A fork of the checked-out parent repository to which the pull request branch will be pushed. e.g. `owner/repo-fork`. The pull request will be created to merge the fork's branch into the parent's base. See [push pull request branches to a fork](docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork) 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 or newline separated list of labels. | |
|
||||
| `assignees` | A comma or newline separated list of assignees (GitHub usernames). | |
|
||||
| `reviewers` | A comma or newline separated list of reviewers (GitHub usernames) to request a review from. | |
|
||||
| `team-reviewers` | A comma or newline separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) may be required. See [this issue](https://github.com/peter-evans/create-pull-request/issues/155). | |
|
||||
| `body-path` | The path to a file containing the pull request body. Takes precedence over `body`. | |
|
||||
| `labels` | A comma or newline-separated list of labels. | |
|
||||
| `assignees` | A comma or newline-separated list of assignees (GitHub usernames). | |
|
||||
| `reviewers` | A comma or newline-separated list of reviewers (GitHub usernames) to request a review from. | |
|
||||
| `team-reviewers` | A comma or newline-separated list of GitHub teams to request a review from. Note that a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), or equivalent [GitHub App permissions](docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens), are required. | |
|
||||
| `milestone` | The number of the milestone to associate this pull request with. | |
|
||||
| `draft` | Create a [draft pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). | `false` |
|
||||
| `draft` | Create a [draft pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests). It is not possible to change draft status after creation except through the web interface. | `false` |
|
||||
| `sign-commit` | Sign the commit as bot [refer: [Signature verification for bots](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#signature-verification-for-bots)]. This can be useful if your repo or org has enforced commit-signing. | `false` |
|
||||
|
||||
#### commit-message
|
||||
|
||||
In addition to a message, the `commit-message` input can also be used to populate the commit description. Leave a single blank line between the message and description.
|
||||
|
||||
```yml
|
||||
commit-message: |
|
||||
the first line is the commit message
|
||||
|
||||
the commit description starts
|
||||
after a blank line and can be
|
||||
multiple lines
|
||||
```
|
||||
|
||||
#### delete-branch
|
||||
|
||||
The `delete-branch` feature doesn't delete branches immediately on merge. (It can't do that because it would require the merge to somehow trigger the action.)
|
||||
The intention of the feature is that when the action next runs it will delete the `branch` if there is no diff.
|
||||
|
||||
Enabling this feature leads to the following behaviour:
|
||||
1. If a pull request was merged and the branch is left undeleted, when the action next runs it will delete the branch if there is no further diff.
|
||||
2. If a pull request is open, but there is now no longer a diff and the PR is unnecessary, the action will delete the branch causing the PR to close.
|
||||
|
||||
If you want branches to be deleted immediately on merge then you should use GitHub's `Automatically delete head branches` feature in your repository settings.
|
||||
|
||||
#### Proxy support
|
||||
|
||||
For self-hosted runners behind a corporate proxy set the `https_proxy` environment variable.
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
env:
|
||||
https_proxy: http://<proxy_address>:<port>
|
||||
```
|
||||
|
||||
### Action outputs
|
||||
|
||||
The pull request number is output as a step output.
|
||||
Note that in order to read the step output the action step must have an id.
|
||||
The following outputs can be used by subsequent workflow steps.
|
||||
|
||||
- `pull-request-number` - The pull request number.
|
||||
- `pull-request-url` - The URL of the pull request.
|
||||
- `pull-request-operation` - The pull request operation performed by the action, `created`, `updated` or `closed`.
|
||||
- `pull-request-head-sha` - The commit SHA of the pull request branch.
|
||||
- `pull-request-branch` - The branch name of the pull request.
|
||||
|
||||
Step outputs can be accessed as in the following example.
|
||||
Note that in order to read the step outputs the action step must have an id.
|
||||
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
- name: Check outputs
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
```
|
||||
|
||||
### Checkout
|
||||
|
||||
This action expects repositories to be checked out with `actions/checkout@v2`.
|
||||
|
||||
If there is some reason you need to use `actions/checkout@v1` the following step can be added to checkout the branch.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v1
|
||||
- run: git checkout "${GITHUB_REF:11}"
|
||||
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
|
||||
```
|
||||
|
||||
### Action behaviour
|
||||
@ -91,10 +142,10 @@ Any subsequent changes will be committed to the *same* branch and reflected in t
|
||||
|
||||
How the action behaves:
|
||||
|
||||
- If there are changes (i.e. a diff exists with the checked out base branch), the changes will be pushed to a new `branch` and a pull request created.
|
||||
- If there are no changes (i.e. no diff exists with the checked out base branch), no pull request will be created and the action exits silently.
|
||||
- If a pull request already exists and there are no further changes (i.e. no diff with the current pull request branch) then the action exits silently.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the base and pull request branch), the pull request is automatically closed and the branch deleted.
|
||||
- If there are changes (i.e. a diff exists with the checked-out base branch), the changes will be pushed to a new `branch` and a pull request created.
|
||||
- If there are no changes (i.e. no diff exists with the checked-out base branch), no pull request will be created and the action exits silently.
|
||||
- If a pull request already exists it will be updated if necessary. Local changes in the Actions workspace, or changes on the base branch, can cause an update. If no update is required the action exits silently.
|
||||
- If a pull request exists and new changes on the base branch make the pull request unnecessary (i.e. there is no longer a diff between the pull request branch and the base), the pull request is automatically closed. Additionally, if [`delete-branch`](#delete-branch) is set to `true` the `branch` will be deleted.
|
||||
|
||||
For further details about how the action works and usage guidelines, see [Concepts, guidelines and advanced usage](docs/concepts-guidelines.md).
|
||||
|
||||
@ -111,13 +162,48 @@ To use this strategy, set input `branch-suffix` with one of the following option
|
||||
|
||||
- `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`
|
||||
|
||||
### Controlling commits
|
||||
### Controlling committed files
|
||||
|
||||
The action defaults to adding all new and modified files.
|
||||
If there are files that should not be included in the pull request, you can use the following methods to control the committed content.
|
||||
|
||||
#### Remove files
|
||||
|
||||
The most straightforward way to handle unwanted files is simply to remove them in a step before the action runs.
|
||||
|
||||
```yml
|
||||
- run: |
|
||||
rm -rf temp-dir
|
||||
rm temp-file.txt
|
||||
```
|
||||
|
||||
#### Ignore 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.
|
||||
|
||||
#### Add specific paths
|
||||
|
||||
You can control which files are committed with the `add-paths` input.
|
||||
Paths should follow git's [pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) syntax.
|
||||
File changes that do not match one of the paths will be stashed and restored after the action has completed.
|
||||
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
add-paths: |
|
||||
*.java
|
||||
docs/*.md
|
||||
```
|
||||
|
||||
#### Create your own commits
|
||||
|
||||
As well as relying on the action to handle uncommitted changes, you can additionally make your own commits before the action runs.
|
||||
Note that the repository must be checked out on a branch with a remote, it won't work for [events which checkout a commit](docs/concepts-guidelines.md#events-which-checkout-a-commit).
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create commits
|
||||
run: |
|
||||
git config user.name 'Peter Evans'
|
||||
@ -130,13 +216,9 @@ As well as relying on the action to handle uncommitted changes, you can addition
|
||||
- name: Uncommitted change
|
||||
run: date +%s > report.txt
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
### Create a project card
|
||||
|
||||
To create a project card for the pull request, pass the `pull-request-number` step output to [create-or-update-project-card](https://github.com/peter-evans/create-or-update-project-card) action.
|
||||
@ -144,43 +226,50 @@ To create a project card for the pull request, pass the `pull-request-number` st
|
||||
```yml
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
|
||||
- name: Create or Update Project Card
|
||||
uses: peter-evans/create-or-update-project-card@v1
|
||||
if: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
uses: peter-evans/create-or-update-project-card@v2
|
||||
with:
|
||||
project-name: My project
|
||||
column-name: My column
|
||||
issue-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
```
|
||||
|
||||
### Auto-merge
|
||||
|
||||
Auto-merge can be enabled on a pull request allowing it to be automatically merged once requirements have been satisfied.
|
||||
See [enable-pull-request-automerge](https://github.com/peter-evans/enable-pull-request-automerge) action for usage details.
|
||||
|
||||
## Reference Example
|
||||
|
||||
The following workflow is a reference example that sets all the main inputs.
|
||||
The following workflow sets many of the action's inputs for reference purposes.
|
||||
Check the [defaults](#action-inputs) to avoid setting inputs unnecessarily.
|
||||
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Make changes to pull request
|
||||
run: date +%s > report.txt
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update report
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: example-patches
|
||||
delete-branch: true
|
||||
title: '[Example] Update report'
|
||||
body: |
|
||||
Update report
|
||||
@ -194,14 +283,10 @@ jobs:
|
||||
assignees: peter-evans
|
||||
reviewers: peter-evans
|
||||
team-reviewers: |
|
||||
owners
|
||||
maintainers
|
||||
developers
|
||||
qa-team
|
||||
milestone: 1
|
||||
draft: false
|
||||
|
||||
- name: Check output
|
||||
run: |
|
||||
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
|
||||
```
|
||||
|
||||
An example based on the above reference configuration creates pull requests that look like this:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,17 +5,18 @@ set -euo pipefail
|
||||
WORKINGDIR=$PWD
|
||||
|
||||
# Create and serve a remote repo
|
||||
mkdir -p /git/remote
|
||||
git init --bare /git/remote/test-base.git
|
||||
mkdir -p /git/remote/repos
|
||||
git config --global init.defaultBranch main
|
||||
git init --bare /git/remote/repos/test-base.git
|
||||
git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all /git/remote &>/dev/null &
|
||||
|
||||
# Give the daemon time to start
|
||||
sleep 2
|
||||
|
||||
# Create a local clone and make an initial commit
|
||||
mkdir -p /git/local
|
||||
git clone git://127.0.0.1/test-base.git /git/local/test-base
|
||||
cd /git/local/test-base
|
||||
mkdir -p /git/local/repos
|
||||
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||
cd /git/local/repos/test-base
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
echo "#test-base" > README.md
|
||||
@ -29,8 +30,8 @@ git config -l
|
||||
|
||||
# Clone a server-side fork of the base repo
|
||||
cd $WORKINGDIR
|
||||
git clone --mirror git://127.0.0.1/test-base.git /git/remote/test-fork.git
|
||||
cd /git/remote/test-fork.git
|
||||
git clone --mirror git://127.0.0.1/repos/test-base.git /git/remote/repos/test-fork.git
|
||||
cd /git/remote/repos/test-fork.git
|
||||
git log -1 --pretty=oneline
|
||||
|
||||
# Restore the working directory
|
||||
|
@ -1,49 +0,0 @@
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import {GitAuthHelper} from '../lib/git-auth-helper'
|
||||
|
||||
const REPO_PATH = '/git/local/test-base'
|
||||
|
||||
const extraheaderConfigKey = 'http.https://github.com/.extraheader'
|
||||
|
||||
describe('git-auth-helper tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitAuthHelper: GitAuthHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
})
|
||||
|
||||
it('tests save and restore with no persisted auth', async () => {
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
})
|
||||
|
||||
it('tests configure and removal of auth', async () => {
|
||||
await gitAuthHelper.configureToken('github-token')
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||
)
|
||||
|
||||
await gitAuthHelper.removeAuth()
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('tests save and restore of persisted auth', async () => {
|
||||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
|
||||
const exists = await git.configExists(extraheaderConfigKey)
|
||||
expect(exists).toBeFalsy()
|
||||
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
|
||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||
expect(configValue).toEqual(extraheaderConfigValue)
|
||||
|
||||
await gitAuthHelper.removeAuth()
|
||||
})
|
||||
})
|
86
__test__/git-config-helper.int.test.ts
Normal file
86
__test__/git-config-helper.int.test.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
|
||||
const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||
|
||||
describe('git-config-helper integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitConfigHelper: GitConfigHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
})
|
||||
|
||||
it('tests save and restore with no persisted auth', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.close()
|
||||
})
|
||||
|
||||
it('tests configure and removal of auth', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
await gitConfigHelper.configureToken('github-token')
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeTruthy()
|
||||
expect(await git.getConfigValue(extraheaderConfigKey)).toEqual(
|
||||
'AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2l0aHViLXRva2Vu'
|
||||
)
|
||||
|
||||
await gitConfigHelper.close()
|
||||
expect(await git.configExists(extraheaderConfigKey)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('tests save and restore of persisted auth', async () => {
|
||||
const extraheaderConfigValue = 'AUTHORIZATION: basic ***persisted-auth***'
|
||||
await git.config(extraheaderConfigKey, extraheaderConfigValue)
|
||||
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
const exists = await git.configExists(extraheaderConfigKey)
|
||||
expect(exists).toBeFalsy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
|
||||
const configValue = await git.getConfigValue(extraheaderConfigKey)
|
||||
expect(configValue).toEqual(extraheaderConfigValue)
|
||||
|
||||
const unset = await git.tryConfigUnset(
|
||||
extraheaderConfigKey,
|
||||
'^AUTHORIZATION:'
|
||||
)
|
||||
expect(unset).toBeTruthy()
|
||||
})
|
||||
|
||||
it('tests not adding/removing the safe.directory config when it already exists', async () => {
|
||||
await git.config('safe.directory', '/another-value', true, true)
|
||||
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', '/another-value', true)
|
||||
).toBeTruthy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
|
||||
const unset = await git.tryConfigUnset(
|
||||
'safe.directory',
|
||||
'/another-value',
|
||||
true
|
||||
)
|
||||
expect(unset).toBeTruthy()
|
||||
})
|
||||
|
||||
it('tests adding and removing the safe.directory config', async () => {
|
||||
const gitConfigHelper = await GitConfigHelper.create(git)
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeTruthy()
|
||||
|
||||
await gitConfigHelper.close()
|
||||
|
||||
expect(
|
||||
await git.configExists('safe.directory', REPO_PATH, true)
|
||||
).toBeFalsy()
|
||||
})
|
||||
})
|
93
__test__/git-config-helper.unit.test.ts
Normal file
93
__test__/git-config-helper.unit.test.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import {GitConfigHelper} from '../lib/git-config-helper'
|
||||
|
||||
describe('git-config-helper unit tests', () => {
|
||||
test('parseGitRemote successfully parses HTTPS remote URLs', async () => {
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('github.com')
|
||||
expect(remote1.protocol).toEqual('HTTPS')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = GitConfigHelper.parseGitRemote(
|
||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote2.hostname).toEqual('github.com')
|
||||
expect(remote2.protocol).toEqual('HTTPS')
|
||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote3 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.hostname).toEqual('github.com')
|
||||
expect(remote3.protocol).toEqual('HTTPS')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote4 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/ungit'
|
||||
)
|
||||
expect(remote4.hostname).toEqual('github.com')
|
||||
expect(remote4.protocol).toEqual('HTTPS')
|
||||
expect(remote4.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote5 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.com/peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote5.hostname).toEqual('github.com')
|
||||
expect(remote5.protocol).toEqual('HTTPS')
|
||||
expect(remote5.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote6 = GitConfigHelper.parseGitRemote(
|
||||
'https://github.internal.company/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote6.hostname).toEqual('github.internal.company')
|
||||
expect(remote6.protocol).toEqual('HTTPS')
|
||||
expect(remote6.repository).toEqual('peter-evans/create-pull-request')
|
||||
})
|
||||
|
||||
test('parseGitRemote successfully parses SSH remote URLs', async () => {
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.com:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('github.com')
|
||||
expect(remote1.protocol).toEqual('SSH')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.com:peter-evans/ungit.git'
|
||||
)
|
||||
expect(remote2.hostname).toEqual('github.com')
|
||||
expect(remote2.protocol).toEqual('SSH')
|
||||
expect(remote2.repository).toEqual('peter-evans/ungit')
|
||||
|
||||
const remote3 = GitConfigHelper.parseGitRemote(
|
||||
'git@github.internal.company:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.hostname).toEqual('github.internal.company')
|
||||
expect(remote3.protocol).toEqual('SSH')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
})
|
||||
|
||||
test('parseGitRemote successfully parses GIT remote URLs', async () => {
|
||||
// Unauthenticated git protocol for integration tests only
|
||||
const remote1 = GitConfigHelper.parseGitRemote(
|
||||
'git://127.0.0.1/repos/test-base.git'
|
||||
)
|
||||
expect(remote1.hostname).toEqual('127.0.0.1')
|
||||
expect(remote1.protocol).toEqual('GIT')
|
||||
expect(remote1.repository).toEqual('repos/test-base')
|
||||
})
|
||||
|
||||
test('parseGitRemote fails to parse a remote URL', async () => {
|
||||
const remoteUrl = 'https://github.com/peter-evans'
|
||||
try {
|
||||
GitConfigHelper.parseGitRemote(remoteUrl)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
@ -8,15 +8,15 @@ if [[ "$(docker images -q $IMAGE 2> /dev/null)" == "" || $ARG1 == "build" ]]; th
|
||||
echo "Building Docker image $IMAGE ..."
|
||||
|
||||
cat > Dockerfile << EOF
|
||||
FROM node:12-alpine
|
||||
FROM node:20-alpine
|
||||
RUN apk --no-cache add git git-daemon
|
||||
RUN npm install jest --global
|
||||
RUN npm install jest jest-environment-jsdom --global
|
||||
WORKDIR /cpr
|
||||
COPY __test__/entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
EOF
|
||||
|
||||
docker build -t $IMAGE .
|
||||
docker build --no-cache -t $IMAGE .
|
||||
rm Dockerfile
|
||||
fi
|
||||
|
||||
|
@ -25,6 +25,18 @@ describe('utils tests', () => {
|
||||
expect(array2.length).toEqual(0)
|
||||
})
|
||||
|
||||
test('stripOrgPrefixFromTeams strips org prefixes correctly', async () => {
|
||||
const array = utils.stripOrgPrefixFromTeams([
|
||||
'org/team1',
|
||||
'org/team2',
|
||||
'team3'
|
||||
])
|
||||
expect(array.length).toEqual(3)
|
||||
expect(array[0]).toEqual('team1')
|
||||
expect(array[1]).toEqual('team2')
|
||||
expect(array[2]).toEqual('team3')
|
||||
})
|
||||
|
||||
test('getRepoPath successfully returns the path to the repository', async () => {
|
||||
expect(utils.getRepoPath()).toEqual(process.env['GITHUB_WORKSPACE'])
|
||||
expect(utils.getRepoPath('foo')).toEqual(
|
||||
@ -32,45 +44,29 @@ describe('utils tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('getRemoteDetail successfully parses remote URLs', async () => {
|
||||
const remote1 = utils.getRemoteDetail(
|
||||
'https://github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote1.protocol).toEqual('HTTPS')
|
||||
expect(remote1.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote2 = utils.getRemoteDetail(
|
||||
'https://xxx:x-oauth-basic@github.com/peter-evans/create-pull-request'
|
||||
)
|
||||
expect(remote2.protocol).toEqual('HTTPS')
|
||||
expect(remote2.repository).toEqual('peter-evans/create-pull-request')
|
||||
|
||||
const remote3 = utils.getRemoteDetail(
|
||||
'git@github.com:peter-evans/create-pull-request.git'
|
||||
)
|
||||
expect(remote3.protocol).toEqual('SSH')
|
||||
expect(remote3.repository).toEqual('peter-evans/create-pull-request')
|
||||
})
|
||||
|
||||
test('getRemoteDetail fails to parse a remote URL', async () => {
|
||||
const remoteUrl = 'https://github.com/peter-evans'
|
||||
try {
|
||||
utils.getRemoteDetail(remoteUrl)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('getRemoteUrl successfully returns remote URLs', async () => {
|
||||
const url1 = utils.getRemoteUrl('HTTPS', 'peter-evans/create-pull-request')
|
||||
const url1 = utils.getRemoteUrl(
|
||||
'HTTPS',
|
||||
'github.com',
|
||||
'peter-evans/create-pull-request'
|
||||
)
|
||||
expect(url1).toEqual('https://github.com/peter-evans/create-pull-request')
|
||||
|
||||
const url2 = utils.getRemoteUrl('SSH', 'peter-evans/create-pull-request')
|
||||
const url2 = utils.getRemoteUrl(
|
||||
'SSH',
|
||||
'github.com',
|
||||
'peter-evans/create-pull-request'
|
||||
)
|
||||
expect(url2).toEqual('git@github.com:peter-evans/create-pull-request.git')
|
||||
|
||||
const url3 = utils.getRemoteUrl(
|
||||
'HTTPS',
|
||||
'mygithubserver.com',
|
||||
'peter-evans/create-pull-request'
|
||||
)
|
||||
expect(url3).toEqual(
|
||||
'https://mygithubserver.com/peter-evans/create-pull-request'
|
||||
)
|
||||
})
|
||||
|
||||
test('secondsSinceEpoch returns the number of seconds since the Epoch', async () => {
|
||||
@ -104,7 +100,7 @@ describe('utils tests', () => {
|
||||
utils.parseDisplayNameEmail(displayNameEmail1)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${displayNameEmail1}' is not a valid email address with display name`
|
||||
)
|
||||
@ -115,7 +111,7 @@ describe('utils tests', () => {
|
||||
utils.parseDisplayNameEmail(displayNameEmail2)
|
||||
// Fail the test if an error wasn't thrown
|
||||
expect(true).toEqual(false)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
expect(e.message).toEqual(
|
||||
`The format of '${displayNameEmail2}' is not a valid email address with display name`
|
||||
)
|
||||
|
37
action.yml
37
action.yml
@ -4,10 +4,19 @@ inputs:
|
||||
token:
|
||||
description: 'GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)'
|
||||
default: ${{ github.token }}
|
||||
git-token:
|
||||
description: >
|
||||
The Personal Access Token (PAT) that the action will use for git operations.
|
||||
Defaults to the value of `token`.
|
||||
path:
|
||||
description: >
|
||||
Relative path under $GITHUB_WORKSPACE to the repository.
|
||||
Defaults to $GITHUB_WORKSPACE.
|
||||
add-paths:
|
||||
description: >
|
||||
A comma or newline-separated list of file paths to commit.
|
||||
Paths should follow git's pathspec syntax.
|
||||
Defaults to adding all new and modified files.
|
||||
commit-message:
|
||||
description: 'The message to use when committing changes.'
|
||||
default: '[create-pull-request] automated change'
|
||||
@ -15,15 +24,22 @@ inputs:
|
||||
description: >
|
||||
The committer name and email address in the format `Display Name <email@address.com>`.
|
||||
Defaults to the GitHub Actions bot user.
|
||||
default: 'GitHub <noreply@github.com>'
|
||||
default: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
|
||||
author:
|
||||
description: >
|
||||
The author name and email address in the format `Display Name <email@address.com>`.
|
||||
Defaults to the user who triggered the workflow run.
|
||||
default: '${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>'
|
||||
default: '${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>'
|
||||
signoff:
|
||||
description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.'
|
||||
default: false
|
||||
branch:
|
||||
description: 'The pull request branch name.'
|
||||
default: 'create-pull-request/patch'
|
||||
delete-branch:
|
||||
description: >
|
||||
Delete the `branch` if it doesn't have an active pull request associated with it.
|
||||
default: false
|
||||
branch-suffix:
|
||||
description: 'The branch suffix type when using the alternative branching strategy.'
|
||||
base:
|
||||
@ -41,6 +57,8 @@ inputs:
|
||||
body:
|
||||
description: 'The body of the pull request.'
|
||||
default: 'Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action'
|
||||
body-path:
|
||||
description: 'The path to a file containing the pull request body. Takes precedence over `body`.'
|
||||
labels:
|
||||
description: 'A comma or newline separated list of labels.'
|
||||
assignees:
|
||||
@ -54,13 +72,24 @@ inputs:
|
||||
milestone:
|
||||
description: 'The number of the milestone to associate the pull request with.'
|
||||
draft:
|
||||
description: 'Create a draft pull request'
|
||||
description: 'Create a draft pull request. It is not possible to change draft status after creation except through the web interface'
|
||||
default: false
|
||||
sign-commit:
|
||||
description: 'Sign the commit as github-actions bot (and as custom app if a different github-token is provided)'
|
||||
default: true
|
||||
outputs:
|
||||
pull-request-number:
|
||||
description: 'The pull request number'
|
||||
pull-request-url:
|
||||
description: 'The URL of the pull request.'
|
||||
pull-request-operation:
|
||||
description: 'The pull request operation performed by the action, `created`, `updated` or `closed`.'
|
||||
pull-request-head-sha:
|
||||
description: 'The commit SHA of the pull request branch.'
|
||||
pull-request-branch:
|
||||
description: 'The pull request branch name'
|
||||
runs:
|
||||
using: 'node12'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'git-pull-request'
|
||||
|
69553
dist/index.js
vendored
69553
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
@ -29,37 +29,37 @@
|
||||
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 main = gitgraph.branch("main");
|
||||
main.commit("Last commit on base");
|
||||
const localMain = gitgraph.branch("<#1> main (local)");
|
||||
localMain.commit({
|
||||
subject: "<uncommitted changes>",
|
||||
body: "Changes to the local base during the workflow",
|
||||
})
|
||||
const remotePatch = gitgraph.branch("create-pull-request/patch");
|
||||
remotePatch.merge({
|
||||
branch: localMaster,
|
||||
branch: localMain,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes pushed to create the remote branch",
|
||||
},
|
||||
});
|
||||
master.commit("New commit on base");
|
||||
main.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",
|
||||
const localMain2 = gitgraph.branch("<#2> main (local)");
|
||||
localMain2.commit({
|
||||
subject: "<uncommitted changes>",
|
||||
body: "Changes to the updated local base during the workflow",
|
||||
})
|
||||
remotePatch.merge({
|
||||
branch: localMaster2,
|
||||
branch: localMain2,
|
||||
commitOptions: {
|
||||
subject: "[create-pull-request] automated change",
|
||||
body: "Changes force pushed to update the remote branch",
|
||||
},
|
||||
});
|
||||
|
||||
master.merge(remotePatch);
|
||||
main.merge(remotePatch);
|
||||
|
||||
</script>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 108 KiB |
Binary file not shown.
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 327 KiB |
53
docs/common-issues.md
Normal file
53
docs/common-issues.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Common issues
|
||||
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Create using an existing branch as the PR branch](#create-using-an-existing-branch-as-the-pr-branch)
|
||||
- [Frequently requested features](#use-case-create-a-pull-request-to-update-x-on-release)
|
||||
- [Disable force updates to existing PR branches](#disable-force-updates-to-existing-pr-branches)
|
||||
- [Add a no-verify option to bypass git hooks](#add-a-no-verify-option-to-bypass-git-hooks)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Create using an existing branch as the PR branch
|
||||
|
||||
A common point of confusion is to try and use an existing branch containing changes to raise in a PR as the `branch` input. This will not work because the action is primarily designed to be used in workflows where the PR branch does not exist yet. The action creates and manages the PR branch itself.
|
||||
|
||||
If you have an existing branch that you just want to create a PR for, then I recommend using the official [GitHub CLI](https://cli.github.com/manual/gh_pr_create) in a workflow step.
|
||||
|
||||
Alternatively, if you are trying to keep a branch up to date with another branch, then you can follow [this example](https://github.com/peter-evans/create-pull-request/blob/main/docs/examples.md#keep-a-branch-up-to-date-with-another).
|
||||
|
||||
## Frequently requested features
|
||||
|
||||
### Disable force updates to existing PR branches
|
||||
|
||||
This behaviour is fundamental to how the action works and is a conscious design decision. The "rule" that I based this design on is that when a workflow executes the action to create or update a PR, the result of those two possible actions should never be different. The easiest way to maintain that consistency is to rebase the PR branch and force push it.
|
||||
|
||||
If you want to avoid this behaviour there are some things that might work depending on your use case:
|
||||
- Check if the pull request branch exists in a separate step before the action runs and act accordingly.
|
||||
- Use the [alternative strategy](https://github.com/peter-evans/create-pull-request#alternative-strategy---always-create-a-new-pull-request-branch) of always creating a new PR that won't be updated by the action.
|
||||
- [Create your own commits](https://github.com/peter-evans/create-pull-request#create-your-own-commits) each time the action is created/updated.
|
||||
|
||||
### Add a no-verify option to bypass git hooks
|
||||
|
||||
Presently, there is no plan to add this feature to the action.
|
||||
The reason is that I'm trying very hard to keep the interface for this action to a minimum to prevent it becoming bloated and complicated.
|
||||
|
||||
Git hooks must be installed after a repository is checked out in order for them to work.
|
||||
So the straightforward solution is to just not install them during the workflow where this action is used.
|
||||
|
||||
- If hooks are automatically enabled by a framework, use an option provided by the framework to disable them. For example, for Husky users, they can be disabled with the `--ignore-scripts` flag, or by setting the `HUSKY` environment variable when the action runs.
|
||||
```yml
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
env:
|
||||
HUSKY: '0'
|
||||
```
|
||||
- If hooks are installed in a script, then add a condition checking if the `CI` environment variable exists.
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
[ -n "$CI" ] && exit 0
|
||||
```
|
||||
- If preventing the hooks installing is problematic, just delete them in a workflow step before the action runs.
|
||||
```yml
|
||||
- run: rm .git/hooks -rf
|
||||
```
|
@ -7,8 +7,8 @@ This document covers terminology, how the action works, general usage guidelines
|
||||
- [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)
|
||||
- [Events which checkout a commit](#events-which-checkout-a-commit)
|
||||
- [Restrictions on repository forks](#restrictions-on-repository-forks)
|
||||
- [Triggering further workflow runs](#triggering-further-workflow-runs)
|
||||
- [Security](#security)
|
||||
- [Advanced usage](#advanced-usage)
|
||||
@ -16,12 +16,12 @@ This document covers terminology, how the action works, general usage guidelines
|
||||
- [Push using SSH (deploy keys)](#push-using-ssh-deploy-keys)
|
||||
- [Push pull request branches to a fork](#push-pull-request-branches-to-a-fork)
|
||||
- [Authenticating with GitHub App generated tokens](#authenticating-with-github-app-generated-tokens)
|
||||
- [GPG commit signature verification](#gpg-commit-signature-verification)
|
||||
- [Running in a container or on self-hosted runners](#running-in-a-container-or-on-self-hosted-runners)
|
||||
- [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.
|
||||
[Pull requests](https://docs.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:
|
||||
|
||||
@ -30,21 +30,20 @@ A pull request references two branches:
|
||||
|
||||
## 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`.
|
||||
This action expects repositories to be checked out with the official GitHub Actions [checkout](https://github.com/actions/checkout) action.
|
||||
For each [event type](https://docs.github.com/en/actions/reference/events-that-trigger-workflows) there is a default `GITHUB_SHA` that will be checked out.
|
||||
|
||||
The default can be overridden by specifying a `ref` on checkout.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: develop
|
||||
```
|
||||
|
||||
## How the action works
|
||||
|
||||
By default, the action expects to be executed on the pull request `base`—the branch you intend to modify with the proposed changes.
|
||||
Unless the `base` input is supplied, the action expects the target repository to be checked out on the pull request `base`—the branch you intend to modify with the proposed changes.
|
||||
|
||||
Workflow steps:
|
||||
|
||||
@ -60,11 +59,11 @@ The following git diagram shows how the action creates and updates a pull reques
|
||||
|
||||
### 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.
|
||||
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.
|
||||
In the following example, the [`push`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push) and [`create`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#create) events both trigger the same workflow. This will cause the checkout action to checkout inconsistent branches and commits. Do *not* do this. It will cause multiple pull requests to be created for each additional `base` the action is executed against.
|
||||
|
||||
```yml
|
||||
on:
|
||||
@ -74,35 +73,51 @@ jobs:
|
||||
example:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
```
|
||||
|
||||
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.
|
||||
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
|
||||
### Events which checkout a commit
|
||||
|
||||
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`.
|
||||
The [default checkout](#events-and-checkout) for the majority of events will leave the repository checked out on a branch.
|
||||
However, some events such as `release` and `pull_request` will leave the repository in a "detached HEAD" state.
|
||||
This is because they checkout a commit, not a branch.
|
||||
In these cases, you *must supply* the `base` input so the action can rebase changes made during the workflow for the pull request.
|
||||
|
||||
Workflows triggered by [`pull_request`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) events will by default check out a merge commit. Set the `base` input as follows to base the new pull request on the current pull request's branch.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
base: ${{ github.head_ref }}
|
||||
```
|
||||
|
||||
### Restrictions on forked repositories
|
||||
Workflows triggered by [`release`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#release) events will by default check out a tag. For most use cases, you will need to set the `base` input to the branch name of the tagged commit.
|
||||
|
||||
GitHub Actions have imposed restrictions on events triggered by a forked repository. Specifically, the `pull_request` event triggered by a fork opening a pull request in the upstream repository.
|
||||
```yml
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
base: main
|
||||
```
|
||||
|
||||
- Events from forks cannot access secrets, except for for the default `GITHUB_TOKEN`.
|
||||
### Restrictions on repository forks
|
||||
|
||||
GitHub Actions have imposed restrictions on workflow runs triggered by public repository forks.
|
||||
Private repositories can be configured to [enable workflows](https://docs.github.com/en/github/administering-a-repository/disabling-or-limiting-github-actions-for-a-repository#enabling-workflows-for-private-repository-forks) from forks to run without restriction.
|
||||
|
||||
The restrictions apply to the `pull_request` event triggered by a fork opening a pull request in the upstream repository.
|
||||
|
||||
- Events from forks cannot access secrets, except 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)
|
||||
[GitHub Actions: Using encrypted secrets in a workflow](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-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)
|
||||
[GitHub Actions: Permissions for the GITHUB_TOKEN](https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token)
|
||||
|
||||
These restrictions mean that during a `pull_request` event triggered by a forked repository, actions have no write access to GitHub resources and will fail on attempt.
|
||||
These restrictions mean that during a `pull_request` event triggered by a forked repository, actions have no write access to GitHub resources and will fail on any attempt.
|
||||
|
||||
A job condition can be added to prevent workflows from executing when triggered by a repository fork.
|
||||
|
||||
@ -115,22 +130,28 @@ jobs:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
```
|
||||
|
||||
For further reading regarding the security of pull requests, see this GitHub blog post titled [Keeping your GitHub Actions and workflows secure: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
|
||||
### Triggering further workflow runs
|
||||
|
||||
Pull requests created by the action using the default `GITHUB_TOKEN` cannot trigger other workflows. If you have `on: pull_request` or `on: push` workflows acting as checks on pull requests, they will not run.
|
||||
|
||||
> When you use the repository's GITHUB_TOKEN to perform tasks on behalf of the GitHub Actions app, events triggered by the GITHUB_TOKEN will not create a new workflow run.
|
||||
> When you use the repository's `GITHUB_TOKEN` to perform tasks, events triggered by the `GITHUB_TOKEN` will not create a new workflow run. This prevents you from accidentally creating recursive workflow runs. For example, if a workflow run pushes code using the repository's `GITHUB_TOKEN`, a new workflow will not run even when the repository contains a workflow configured to run when `push` events occur.
|
||||
|
||||
[GitHub Actions: Events that trigger workflows](https://help.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token)
|
||||
[GitHub Actions: Triggering a workflow from a workflow](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow)
|
||||
|
||||
#### Workarounds to trigger further workflow runs
|
||||
|
||||
There are a number of workarounds with different pros and cons.
|
||||
|
||||
- Use the default `GITHUB_TOKEN` and allow the action to create pull requests that have no checks enabled. Manually close pull requests and immediately reopen them. This will enable `on: pull_request` workflows to run and be added as checks.
|
||||
- Use 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) created on an account that has write access to the repository that pull requests are being created in. This is the standard workaround and [recommended by GitHub](https://help.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token). However, the PAT cannot be scoped to a specific repository so the token becomes a very sensitive secret. If this is a concern, the PAT can instead be created for a dedicated [machine account](https://help.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements) that has collaborator access to the repository. Also note that because the account that owns the PAT will be the creator of pull requests, that user account will be unable to perform actions such as request changes or approve the pull request.
|
||||
- Use the default `GITHUB_TOKEN` and allow the action to create pull requests that have no checks enabled. Manually close pull requests and immediately reopen them. This will enable `on: pull_request` workflows to run and be added as checks. To prevent merging of pull requests without checks erroneously, use [branch protection rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests).
|
||||
|
||||
- Use a `repo` scoped [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) created on an account that has write access to the repository that pull requests are being created in. This is the standard workaround and [recommended by GitHub](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow). However, the PAT cannot be scoped to a specific repository so the token becomes a very sensitive secret. If this is a concern, the PAT can instead be created for a dedicated [machine account](https://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements) that has collaborator access to the repository. Also note that because the account that owns the PAT will be the creator of pull requests, that user account will be unable to perform actions such as request changes or approve the pull request.
|
||||
|
||||
- Use [SSH (deploy keys)](#push-using-ssh-deploy-keys) to push the pull request branch. This is arguably more secure than using a PAT because deploy keys can be set per repository. However, this method will only trigger `on: push` workflows.
|
||||
|
||||
- Use a [machine account that creates pull requests from its own fork](#push-pull-request-branches-to-a-fork). This is the most secure because the PAT created only grants access to the machine account's fork, not the main repository. This method will trigger `on: pull_request` workflows to run. Workflows triggered `on: push` will not run because the push event is in the fork.
|
||||
|
||||
- Use a [GitHub App to generate a token](#authenticating-with-github-app-generated-tokens) that can be used with this action. GitHub App generated tokens are more secure than using a PAT because GitHub App access permissions can be set with finer granularity and are scoped to only repositories where the App is installed. This method will trigger both `on: push` and `on: pull_request` workflows.
|
||||
|
||||
### Security
|
||||
@ -139,93 +160,99 @@ From a security perspective it's good practice to fork third-party actions, revi
|
||||
By using third-party actions directly the risk exists that it could be modified to do something malicious, such as capturing secrets.
|
||||
|
||||
Alternatively, use the action directly and reference the commit hash for the version you want to target.
|
||||
```
|
||||
```yml
|
||||
- uses: thirdparty/foo-action@172ec762f2ac8e050062398456fccd30444f8f30
|
||||
```
|
||||
|
||||
This action uses [ncc](https://github.com/zeit/ncc) to compile the Node.js code and dependencies into a single Javascript file under the [dist](https://github.com/peter-evans/create-pull-request/tree/master/dist) directory.
|
||||
This action uses [ncc](https://github.com/vercel/ncc) to compile the Node.js code and dependencies into a single JavaScript file under the [dist](https://github.com/peter-evans/create-pull-request/tree/main/dist) directory.
|
||||
|
||||
## 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.
|
||||
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://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) is required.
|
||||
|
||||
```yml
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
repository: owner/repo
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v3
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
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).
|
||||
[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://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
||||
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.
|
||||
Note that you cannot use deploy keys alone to [create a pull request in a remote repository](#creating-pull-requests-in-a-remote-repository) because then using a PAT would become a requirement. This method only makes sense if creating a pull request in the repository where the workflow is running.
|
||||
|
||||
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.
|
||||
1. [Create a new SSH key pair](https://docs.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 below, configure `actions/checkout` to use the deploy key you have created.
|
||||
|
||||
```yml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
### 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.
|
||||
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://docs.github.com/en/github/site-policy/github-terms-of-service#3-account-requirements).
|
||||
This user only has `read` access to the main repository.
|
||||
It will use their own fork to push code and create the pull request.
|
||||
Note that if you choose to use this method (not give the machine account `write` access to the repository) the following inputs cannot be used: `labels`, `assignees`, `reviewers`, `team-reviewers` and `milestone`.
|
||||
|
||||
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.
|
||||
3. Create a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
||||
4. Logout and log back into your main user account.
|
||||
5. Add a secret to your repository containing the above PAT.
|
||||
6. As shown in the following example workflow, set the `push-to-fork` input to the full repository name of the fork.
|
||||
|
||||
```yaml
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- uses: peter-evans/create-pull-request@v3
|
||||
- uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.MACHINE_USER_PAT }}
|
||||
push-to-fork: machine-user/fork-of-repository
|
||||
```
|
||||
|
||||
Note: You can also combine `push-to-fork` with [creating pull requests in a remote repository](#creating-pull-requests-in-a-remote-repository).
|
||||
|
||||
### Authenticating with GitHub App generated tokens
|
||||
|
||||
A GitHub App can be created for the sole purpose of generating tokens for use with GitHub actions.
|
||||
These tokens can be used in place of `GITHUB_TOKEN` or a [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line).
|
||||
These tokens can be used in place of `GITHUB_TOKEN` or a [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
||||
GitHub App generated tokens are more secure than using a PAT because GitHub App access permissions can be set with finer granularity and are scoped to only repositories where the App is installed.
|
||||
|
||||
1. Create a minimal [GitHub App](https://developer.github.com/apps/building-github-apps/creating-a-github-app/), setting the following fields:
|
||||
1. Create a minimal [GitHub App](https://docs.github.com/en/developers/apps/creating-a-github-app), setting the following fields:
|
||||
|
||||
- Set `GitHub App name`.
|
||||
- Set `Homepage URL` to anything you like, such as your GitHub profile page.
|
||||
- Uncheck `Active` under `Webhook`. You do not need to enter a `Webhook URL`.
|
||||
- Under `Repository permissions: Contents` select `Access: Read & write`.
|
||||
- Under `Repository permissions: Pull requests` select `Access: Read & write`.
|
||||
- Under `Organization permissions: Members` select `Access: Read-only`.
|
||||
- **NOTE**: Only needed if you would like add teams as reviewers to PRs.
|
||||
|
||||
2. Create a Private key from the App settings page and store it securely.
|
||||
|
||||
@ -237,7 +264,7 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
id: generate-token
|
||||
@ -248,11 +275,53 @@ GitHub App generated tokens are more secure than using a PAT because GitHub App
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
```
|
||||
|
||||
### GPG commit signature verification
|
||||
|
||||
The action can use GPG to sign commits with a GPG key that you generate yourself.
|
||||
|
||||
1. Follow GitHub's guide to [generate a new GPG key](https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key).
|
||||
|
||||
2. [Add the public key](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-gpg-key-to-your-github-account) to the user account associated with the [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that you will use with the action.
|
||||
|
||||
3. Copy the private key to your clipboard, replacing `email@example.com` with the email address of your GPG key.
|
||||
```
|
||||
# macOS
|
||||
gpg --armor --export-secret-key email@example.com | pbcopy
|
||||
```
|
||||
|
||||
4. Paste the private key into a repository secret where the workflow will run. e.g. `GPG_PRIVATE_KEY`
|
||||
|
||||
5. Create another repository secret for the key's passphrase, if applicable. e.g. `GPG_PASSPHRASE`
|
||||
|
||||
6. The following example workflow shows how to use [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) to import your GPG key and allow the action to sign commits.
|
||||
|
||||
Note that the `committer` email address *MUST* match the email address used to create your GPG key.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: crazy-max/ghaction-import-gpg@v5
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
committer: example <email@example.com>
|
||||
```
|
||||
|
||||
### Running in a container or on self-hosted runners
|
||||
|
||||
This action can be run inside a container, or on [self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners), by installing the necessary dependencies.
|
||||
@ -272,12 +341,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: apk --no-cache add git
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
||||
**Ubuntu container example:**
|
||||
@ -295,70 +364,10 @@ jobs:
|
||||
add-apt-repository -y ppa:git-core/ppa
|
||||
apt-get install -y git
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Make changes to pull request here
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
```
|
||||
|
||||
### 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@v3
|
||||
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@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
```
|
||||
|
239
docs/examples.md
239
docs/examples.md
@ -3,10 +3,14 @@
|
||||
- [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 on release](#use-case-create-a-pull-request-to-update-x-on-release)
|
||||
- [Update changelog](#update-changelog)
|
||||
- [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 Gradle dependencies](#update-gradle-dependencies)
|
||||
- [Update Cargo dependencies](#update-cargo-dependencies)
|
||||
- [Update SwaggerUI for GitHub Pages](#update-swaggerui-for-github-pages)
|
||||
- [Keep a fork up-to-date with its upstream](#keep-a-fork-up-to-date-with-its-upstream)
|
||||
- [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)
|
||||
@ -16,6 +20,7 @@
|
||||
- [Misc workflow tips](#misc-workflow-tips)
|
||||
- [Filtering push events](#filtering-push-events)
|
||||
- [Dynamic configuration using variables](#dynamic-configuration-using-variables)
|
||||
- [Using a markdown template](#using-a-markdown-template)
|
||||
- [Debugging GitHub Actions](#debugging-github-actions)
|
||||
|
||||
|
||||
@ -32,19 +37,19 @@ name: Update AUTHORS
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
updateAuthors:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
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@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update authors
|
||||
title: Update AUTHORS
|
||||
@ -56,31 +61,70 @@ jobs:
|
||||
|
||||
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.
|
||||
In this example scenario, a branch called `production` should be updated via pull request to keep it in sync with `main`. Merging the pull request is effectively promoting those changes to production.
|
||||
|
||||
```yml
|
||||
name: Create production promotion pull request
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
productionPromotion:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: production
|
||||
- name: Reset promotion branch
|
||||
run: |
|
||||
git fetch origin master:master
|
||||
git reset --hard master
|
||||
git fetch origin main:main
|
||||
git reset --hard main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: production-promotion
|
||||
```
|
||||
|
||||
## Use case: Create a pull request to update X on release
|
||||
|
||||
This pattern will work well for updating any kind of static content based on the tagged commit of a release. Note that because `release` is one of the [events which checkout a commit](concepts-guidelines.md#events-which-checkout-a-commit) it is necessary to supply the `base` input to the action.
|
||||
|
||||
### Update changelog
|
||||
|
||||
Raises a pull request to update the `CHANGELOG.md` file based on the tagged commit of the release.
|
||||
Note that [git-chglog](https://github.com/git-chglog/git-chglog/) requires some configuration files to exist in the repository before this workflow will work.
|
||||
|
||||
This workflow assumes the tagged release was made on a default branch called `main`.
|
||||
|
||||
```yml
|
||||
name: Update Changelog
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
updateChangelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update Changelog
|
||||
run: |
|
||||
curl -o git-chglog -L https://github.com/git-chglog/git-chglog/releases/download/0.9.1/git-chglog_linux_amd64
|
||||
chmod u+x git-chglog
|
||||
./git-chglog -o CHANGELOG.md
|
||||
rm git-chglog
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update changelog
|
||||
title: Update Changelog
|
||||
body: Update changelog to reflect release changes
|
||||
branch: update-changelog
|
||||
base: main
|
||||
```
|
||||
|
||||
## 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.
|
||||
@ -89,7 +133,7 @@ This pattern will work well for updating any kind of static content from an exte
|
||||
|
||||
This workflow will create a pull request for npm dependencies.
|
||||
It works best in combination with a build workflow triggered on `push` and `pull_request`.
|
||||
A [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) can be used in order for the creation of the pull request to trigger further workflows. See the [documentation here](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#triggering-further-workflow-runs) for further details.
|
||||
A [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) can be used in order for the creation of the pull request to trigger further workflows. See the [documentation here](concepts-guidelines.md#triggering-further-workflow-runs) for further details.
|
||||
|
||||
```yml
|
||||
name: Update Dependencies
|
||||
@ -100,16 +144,16 @@ jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '12.x'
|
||||
node-version: '16.x'
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
npx -p npm-check-updates ncu -u
|
||||
npm install
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
@ -129,17 +173,17 @@ The above workflow works best in combination with a build workflow triggered on
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 16.x
|
||||
- run: npm ci
|
||||
- run: npm run test
|
||||
- run: npm run build
|
||||
@ -160,16 +204,52 @@ jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 1.8
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Perform dependency resolution and write new lockfiles
|
||||
run: ./gradlew dependencies --write-locks
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
title: Update dependencies
|
||||
body: |
|
||||
- Dependency updates
|
||||
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
branch: update-dependencies
|
||||
```
|
||||
|
||||
### Update Cargo dependencies
|
||||
|
||||
The following workflow will create a pull request for Cargo dependencies.
|
||||
It optionally uses [`cargo-edit`](https://github.com/killercup/cargo-edit) to update `Cargo.toml` and keep it in sync with `Cargo.lock`.
|
||||
|
||||
```yml
|
||||
name: Update Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * 1'
|
||||
jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
cargo install cargo-edit
|
||||
cargo update
|
||||
cargo upgrade --to-lockfile
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: Update dependencies
|
||||
@ -197,12 +277,14 @@ jobs:
|
||||
updateSwagger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- 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)
|
||||
release_tag=$(curl -sL https://api.github.com/repos/swagger-api/swagger-ui/releases/latest | jq -r ".tag_name")
|
||||
echo "release_tag=$release_tag" >> $GITHUB_OUTPUT
|
||||
current_tag=$(<swagger-ui.version)
|
||||
echo "current_tag=$current_tag" >> $GITHUB_OUTPUT
|
||||
- name: Update Swagger UI
|
||||
if: steps.swagger-ui.outputs.current_tag != steps.swagger-ui.outputs.release_tag
|
||||
env:
|
||||
@ -225,7 +307,7 @@ jobs:
|
||||
# Update current release
|
||||
echo ${{ steps.swagger-ui.outputs.release_tag }} > swagger-ui.version
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update swagger-ui to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
title: Update SwaggerUI to ${{ steps.swagger-ui.outputs.release_tag }}
|
||||
@ -240,6 +322,41 @@ jobs:
|
||||
branch: swagger-ui-updates
|
||||
```
|
||||
|
||||
### Keep a fork up-to-date with its upstream
|
||||
|
||||
This example is designed to be run in a seperate repository from the fork repository itself.
|
||||
The aim of this is to prevent committing anything to the fork's default branch would cause it to differ from the upstream.
|
||||
|
||||
In the following example workflow, `owner/repo` is the upstream repository and `fork-owner/repo` is the fork. It assumes the default branch of the upstream repository is called `main`.
|
||||
|
||||
The [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) should have `repo` scope. Additionally, if the upstream makes changes to the `.github/workflows` directory, the action will be unable to push the changes to a branch and throw the error "_(refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission)_". To allow these changes to be pushed to the fork, add the `workflow` scope to the PAT. Of course, allowing this comes with the risk that the workflow changes from the upstream could run and do something unexpected. Disabling GitHub Actions in the fork is highly recommended to prevent this.
|
||||
|
||||
When you merge the pull request make sure to choose the [`Rebase and merge`](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-merges#rebase-and-merge-your-pull-request-commits) option. This will make the fork's commits match the commits on the upstream.
|
||||
|
||||
```yml
|
||||
name: Update fork
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
jobs:
|
||||
updateFork:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: fork-owner/repo
|
||||
- name: Reset the default branch with upstream changes
|
||||
run: |
|
||||
git remote add upstream https://github.com/owner/repo.git
|
||||
git fetch upstream main:upstream-main
|
||||
git reset --hard upstream-main
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
branch: upstream-changes
|
||||
```
|
||||
|
||||
### 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.
|
||||
@ -253,7 +370,7 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download website
|
||||
run: |
|
||||
wget \
|
||||
@ -267,7 +384,7 @@ jobs:
|
||||
--domains quotes.toscrape.com \
|
||||
http://quotes.toscrape.com/
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: update local website copy
|
||||
title: Automated Updates to Local Website Copy
|
||||
@ -277,7 +394,7 @@ jobs:
|
||||
|
||||
## 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.
|
||||
You can use the GitHub API to trigger a webhook event called [`repository_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#repository_dispatch) when you want to trigger a workflow for any 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.
|
||||
@ -295,7 +412,7 @@ on:
|
||||
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)
|
||||
- `[token]` is a `repo` scoped [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
|
||||
- `[repository]` is the name of the repository the workflow resides in.
|
||||
|
||||
```
|
||||
@ -312,7 +429,7 @@ An `on: repository_dispatch` workflow can be triggered from another workflow wit
|
||||
|
||||
```yml
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||
repository: username/my-repo
|
||||
@ -326,7 +443,8 @@ An `on: repository_dispatch` workflow can be triggered from another workflow wit
|
||||
|
||||
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.
|
||||
Note that due to [token restrictions on public repository forks](https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token), workflows for this use case do not work for pull requests raised from forks.
|
||||
Private repositories can be configured to [enable workflows](https://docs.github.com/en/github/administering-a-repository/disabling-or-limiting-github-actions-for-a-repository#enabling-workflows-for-private-repository-forks) from forks to run without restriction.
|
||||
|
||||
### autopep8
|
||||
|
||||
@ -348,7 +466,7 @@ jobs:
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: autopep8
|
||||
@ -358,10 +476,12 @@ jobs:
|
||||
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 }}"
|
||||
run: |
|
||||
branch-name="autopep8-patches/${{ github.head_ref }}"
|
||||
echo "branch-name=$branch-name" >> $GITHUB_OUTPUT
|
||||
- name: Create Pull Request
|
||||
if: steps.autopep8.outputs.exit-code == 2
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: autopep8 action fixes
|
||||
title: Fixes by autopep8 action
|
||||
@ -395,61 +515,72 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/heads/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
...
|
||||
|
||||
someOtherJob:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
...
|
||||
```
|
||||
|
||||
### 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.
|
||||
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) \
|
||||
pr_title="[Test] Add report file $(date +%d-%m-%Y)"
|
||||
pr_body="This PR was auto-generated on $(date +%d-%m-%Y) \
|
||||
by [create-pull-request](https://github.com/peter-evans/create-pull-request)."
|
||||
echo "pr_title=$pr_title" >> $GITHUB_OUTPUT
|
||||
echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
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.
|
||||
### Using a markdown template
|
||||
|
||||
In this example, a markdown template file is added to the repository at `.github/pull-request-template.md` with the following content.
|
||||
```
|
||||
This is a test pull request template
|
||||
Render template variables such as {{ .foo }} and {{ .bar }}.
|
||||
```
|
||||
|
||||
The template is rendered using the [render-template](https://github.com/chuhlomin/render-template) action and the result is used to create the pull request.
|
||||
```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@v3
|
||||
- name: Render template
|
||||
id: template
|
||||
uses: chuhlomin/render-template@v1.4
|
||||
with:
|
||||
title: ${{ env.PULL_REQUEST_TITLE }}
|
||||
body: ${{ env.PULL_REQUEST_BODY }}
|
||||
template: .github/pull-request-template.md
|
||||
vars: |
|
||||
foo: this
|
||||
bar: that
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
body: ${{ steps.template.outputs.result }}
|
||||
```
|
||||
|
||||
### 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.
|
||||
[Runner diagnostic logging](https://docs.github.com/en/actions/configuring-and-managing-workflows/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.
|
||||
[Step debug logging](https://docs.github.com/en/actions/configuring-and-managing-workflows/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
|
||||
|
@ -1,6 +1,57 @@
|
||||
## Updating from `v5` to `v6`
|
||||
|
||||
### Behaviour changes
|
||||
|
||||
- The default values for `author` and `committer` have changed. See "What's new" below for details. If you are overriding the default values you will not be affected by this change.
|
||||
- On completion, the action now removes the temporary git remote configuration it adds when using `push-to-fork`. This should not affect you unless you were using the temporary configuration for some other purpose after the action completes.
|
||||
|
||||
### What's new
|
||||
|
||||
- Updated runtime to Node.js 20
|
||||
- The action now requires a minimum version of [v2.308.0](https://github.com/actions/runner/releases/tag/v2.308.0) for the Actions runner. Update self-hosted runners to v2.308.0 or later to ensure compatibility.
|
||||
- The default value for `author` has been changed to `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>`. The change adds the `${{ github.actor_id }}+` prefix to the email address to align with GitHub's standard format for the author email address.
|
||||
- The default value for `committer` has been changed to `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>`. This is to align with the default GitHub Actions bot user account.
|
||||
- Adds input `git-token`, the [Personal Access Token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) that the action will use for git operations. This input defaults to the value of `token`. Use this input if you would like the action to use a different token for git operations than the one used for the GitHub API.
|
||||
- `push-to-fork` now supports pushing to sibling repositories in the same network.
|
||||
- Previously, when using `push-to-fork`, the action did not remove temporary git remote configuration it adds during execution. This has been fixed and the configuration is now removed when the action completes.
|
||||
- If the pull request body is truncated due to exceeding the maximum length, the action will now suffix the body with the message "...*[Pull request body truncated]*" to indicate that the body has been truncated.
|
||||
- The action now uses `--unshallow` only when necessary, rather than as a default argument of `git fetch`. This should improve performance, particularly for large git repositories with extensive commit history.
|
||||
- The action can now be executed on one GitHub server and create pull requests on a *different* GitHub server. Server products include GitHub hosted (github.com), GitHub Enterprise Server (GHES), and GitHub Enterprise Cloud (GHEC). For example, the action can be executed on GitHub hosted and create pull requests on a GHES or GHEC instance.
|
||||
|
||||
## Updating from `v4` to `v5`
|
||||
|
||||
### Behaviour changes
|
||||
|
||||
- The action will no longer leave the local repository checked out on the pull request `branch`. Instead, it will leave the repository checked out on the branch or commit that it was when the action started.
|
||||
- When using `add-paths`, uncommitted changes will no longer be destroyed. They will be stashed and restored at the end of the action run.
|
||||
|
||||
### What's new
|
||||
|
||||
- Adds input `body-path`, the path to a file containing the pull request body.
|
||||
- At the end of the action run the local repository is now checked out on the branch or commit that it was when the action started.
|
||||
- Any uncommitted tracked or untracked changes are now stashed and restored at the end of the action run. Currently, this can only occur when using the `add-paths` input, which allows for changes to not be committed. Previously, any uncommitted changes would be destroyed.
|
||||
- The proxy implementation has been revised but is not expected to have any change in behaviour. It continues to support the standard environment variables `http_proxy`, `https_proxy` and `no_proxy`.
|
||||
- Now sets the git `safe.directory` configuration for the local repository path. The configuration is removed when the action completes. Fixes issue https://github.com/peter-evans/create-pull-request/issues/1170.
|
||||
- Now determines the git directory path using the `git rev-parse --git-dir` command. This allows users with custom repository configurations to use the action.
|
||||
- Improved handling of the `team-reviewers` input and associated errors.
|
||||
|
||||
## Updating from `v3` to `v4`
|
||||
|
||||
### Behaviour changes
|
||||
|
||||
- The `add-paths` input no longer accepts `-A` as a valid value. When committing all new and modified files the `add-paths` input should be omitted.
|
||||
|
||||
- If using self-hosted runners or GitHub Enterprise Server, there are minimum requirements for `v4` to run. See "What's new" below for details.
|
||||
|
||||
### What's new
|
||||
|
||||
- Updated runtime to Node.js 16
|
||||
- The action now requires a minimum version of v2.285.0 for the [Actions Runner](https://github.com/actions/runner/releases/tag/v2.285.0).
|
||||
- If using GitHub Enterprise Server, the action requires [GHES 3.4](https://docs.github.com/en/enterprise-server@3.4/admin/release-notes) or later.
|
||||
|
||||
## Updating from `v2` to `v3`
|
||||
|
||||
### Breaking changes
|
||||
### Behaviour changes
|
||||
|
||||
- The `author` input now defaults to the user who triggered the workflow run. This default is set via [action.yml](../action.yml) as `${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>`, where `github.actor` is the GitHub user account associated with the run. For example, `peter-evans <peter-evans@users.noreply.github.com>`.
|
||||
|
||||
@ -31,7 +82,7 @@
|
||||
push-to-fork: machine-user/fork-of-repository
|
||||
```
|
||||
|
||||
### New features
|
||||
### What's new
|
||||
|
||||
- The action has been converted to Typescript giving it a significant performance improvement.
|
||||
|
||||
@ -48,7 +99,7 @@
|
||||
|
||||
## Updating from `v1` to `v2`
|
||||
|
||||
### Breaking changes
|
||||
### Behaviour changes
|
||||
|
||||
- `v2` now expects repositories to be checked out with `actions/checkout@v2`
|
||||
|
||||
@ -59,15 +110,15 @@
|
||||
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.
|
||||
- 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
|
||||
### What's new
|
||||
|
||||
- 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.
|
||||
- Unpushed commits made during the workflow before the action runs will now be considered as changes to be raised in the pull request. See [Create your own commits](https://github.com/peter-evans/create-pull-request#create-your-own-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.
|
||||
|
13404
package-lock.json
generated
13404
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-pull-request",
|
||||
"version": "3.0.0",
|
||||
"version": "6.0.0",
|
||||
"private": true,
|
||||
"description": "Creates a pull request for changes to your repository in the actions workspace",
|
||||
"main": "lib/main.js",
|
||||
@ -29,27 +29,35 @@
|
||||
},
|
||||
"homepage": "https://github.com/peter-evans/create-pull-request",
|
||||
"dependencies": {
|
||||
"@actions/core": "1.2.4",
|
||||
"@actions/exec": "1.0.4",
|
||||
"@actions/tool-cache": "1.3.4",
|
||||
"@octokit/core": "3.1.0",
|
||||
"@octokit/plugin-paginate-rest": "2.2.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "4.0.0",
|
||||
"uuid": "8.2.0"
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@octokit/core": "^4.2.4",
|
||||
"@octokit/graphql": "^8.1.1",
|
||||
"@octokit/graphql-schema": "^15.25.0",
|
||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"undici": "^6.19.4",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "25.2.2",
|
||||
"@types/node": "14.0.1",
|
||||
"@typescript-eslint/parser": "2.33.0",
|
||||
"@zeit/ncc": "0.22.1",
|
||||
"eslint": "7.0.0",
|
||||
"eslint-plugin-github": "3.4.1",
|
||||
"eslint-plugin-jest": "23.11.0",
|
||||
"jest": "26.0.1",
|
||||
"jest-circus": "26.0.1",
|
||||
"js-yaml": "3.13.1",
|
||||
"prettier": "2.0.5",
|
||||
"ts-jest": "25.5.1",
|
||||
"typescript": "3.9.2"
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.42",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.3",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,118 @@
|
||||
import * as core from '@actions/core'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import * as utils from './utils'
|
||||
|
||||
const CHERRYPICK_EMPTY =
|
||||
'The previous cherry-pick is now empty, possibly due to conflict resolution.'
|
||||
const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'
|
||||
|
||||
const FETCH_DEPTH_MARGIN = 10
|
||||
|
||||
export enum WorkingBaseType {
|
||||
Branch = 'branch',
|
||||
Commit = 'commit'
|
||||
}
|
||||
|
||||
export async function getWorkingBaseAndType(
|
||||
git: GitCommandManager
|
||||
): Promise<[string, WorkingBaseType]> {
|
||||
const symbolicRefResult = await git.exec(
|
||||
['symbolic-ref', 'HEAD', '--short'],
|
||||
true
|
||||
)
|
||||
if (symbolicRefResult.exitCode == 0) {
|
||||
// A ref is checked out
|
||||
return [symbolicRefResult.stdout.trim(), WorkingBaseType.Branch]
|
||||
} else {
|
||||
// A commit is checked out (detached HEAD)
|
||||
const headSha = await git.revParse('HEAD')
|
||||
return [headSha, WorkingBaseType.Commit]
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryFetch(
|
||||
git: GitCommandManager,
|
||||
remote: string,
|
||||
branch: string
|
||||
branch: string,
|
||||
depth: number
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote)
|
||||
await git.fetch([`${branch}:refs/remotes/${remote}/${branch}`], remote, [
|
||||
'--force',
|
||||
`--depth=${depth}`
|
||||
])
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildBranchFileChanges(
|
||||
git: GitCommandManager,
|
||||
base: string,
|
||||
branch: string
|
||||
): Promise<BranchFileChanges> {
|
||||
const branchFileChanges: BranchFileChanges = {
|
||||
additions: [],
|
||||
deletions: []
|
||||
}
|
||||
const changedFiles = await git.getChangedFiles([
|
||||
'--diff-filter=AM',
|
||||
`${base}..${branch}`
|
||||
])
|
||||
const deletedFiles = await git.getChangedFiles([
|
||||
'--diff-filter=D',
|
||||
`${base}..${branch}`
|
||||
])
|
||||
const repoPath = git.getWorkingDirectory()
|
||||
for (const file of changedFiles) {
|
||||
branchFileChanges.additions!.push({
|
||||
path: file,
|
||||
contents: utils.readFileBase64([repoPath, file])
|
||||
})
|
||||
}
|
||||
for (const file of deletedFiles) {
|
||||
branchFileChanges.deletions!.push({
|
||||
path: file
|
||||
})
|
||||
}
|
||||
return branchFileChanges
|
||||
}
|
||||
|
||||
// Return the number of commits that branch2 is ahead of branch1
|
||||
async function commitsAhead(
|
||||
git: GitCommandManager,
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<number> {
|
||||
const result = await git.revList(
|
||||
[`${branch1}...${branch2}`],
|
||||
['--right-only', '--count']
|
||||
)
|
||||
return Number(result)
|
||||
}
|
||||
|
||||
// Return true if branch2 is ahead of branch1
|
||||
async function isAhead(
|
||||
git: GitCommandManager,
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<boolean> {
|
||||
return (await commitsAhead(git, branch1, branch2)) > 0
|
||||
}
|
||||
|
||||
// Return the number of commits that branch2 is behind branch1
|
||||
async function commitsBehind(
|
||||
git: GitCommandManager,
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<number> {
|
||||
const result = await git.revList(
|
||||
[`${branch1}...${branch2}`],
|
||||
['--right-only', '--count']
|
||||
['--left-only', '--count']
|
||||
)
|
||||
return Number(result) > 0
|
||||
return Number(result)
|
||||
}
|
||||
|
||||
// Return true if branch2 is behind branch1
|
||||
@ -37,11 +121,7 @@ async function isBehind(
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<boolean> {
|
||||
const result = await git.revList(
|
||||
[`${branch1}...${branch2}`],
|
||||
['--left-only', '--count']
|
||||
)
|
||||
return Number(result) > 0
|
||||
return (await commitsBehind(git, branch1, branch2)) > 0
|
||||
}
|
||||
|
||||
// Return true if branch2 is even with branch1
|
||||
@ -56,15 +136,6 @@ async function isEven(
|
||||
)
|
||||
}
|
||||
|
||||
async function hasDiff(
|
||||
git: GitCommandManager,
|
||||
branch1: string,
|
||||
branch2: string
|
||||
): Promise<boolean> {
|
||||
const result = await git.diff([`${branch1}..${branch2}`])
|
||||
return result.length > 0
|
||||
}
|
||||
|
||||
function splitLines(multilineString: string): string[] {
|
||||
return multilineString
|
||||
.split('\n')
|
||||
@ -72,15 +143,42 @@ function splitLines(multilineString: string): string[] {
|
||||
.filter(x => x !== '')
|
||||
}
|
||||
|
||||
export interface BranchFileChanges {
|
||||
additions: {
|
||||
path: string
|
||||
contents: string
|
||||
}[]
|
||||
deletions: {
|
||||
path: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface CreateOrUpdateBranchResult {
|
||||
action: string
|
||||
base: string
|
||||
hasDiffWithBase: boolean
|
||||
headSha: string
|
||||
branchFileChanges?: BranchFileChanges
|
||||
}
|
||||
|
||||
export async function createOrUpdateBranch(
|
||||
git: GitCommandManager,
|
||||
commitMessage: string,
|
||||
base: string,
|
||||
branch: string,
|
||||
branchRemoteName: string
|
||||
branchRemoteName: string,
|
||||
signoff: boolean,
|
||||
addPaths: string[]
|
||||
): Promise<CreateOrUpdateBranchResult> {
|
||||
// Get the working base. This may or may not be the actual base.
|
||||
const workingBase = await git.symbolicRef('HEAD', ['--short'])
|
||||
// Get the working base.
|
||||
// When a ref, it may or may not be the actual base.
|
||||
// When a commit, we must rebase onto the actual base.
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
core.info(`Working base is ${workingBaseType} '${workingBase}'`)
|
||||
if (workingBaseType == WorkingBaseType.Commit && !base) {
|
||||
throw new Error(`When in 'detached HEAD' state, 'base' must be supplied.`)
|
||||
}
|
||||
|
||||
// If the base is not specified it is assumed to be the working base.
|
||||
base = base ? base : workingBase
|
||||
const baseRemote = 'origin'
|
||||
@ -89,30 +187,61 @@ export async function createOrUpdateBranch(
|
||||
const result: CreateOrUpdateBranchResult = {
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false
|
||||
hasDiffWithBase: false,
|
||||
headSha: ''
|
||||
}
|
||||
|
||||
// Save the working base changes to a temporary branch
|
||||
const tempBranch = uuidv4()
|
||||
await git.checkout(tempBranch, 'HEAD')
|
||||
// Commit any uncomitted changes
|
||||
if (await git.isDirty(true)) {
|
||||
// Commit any uncommitted changes
|
||||
if (await git.isDirty(true, addPaths)) {
|
||||
core.info('Uncommitted changes found. Adding a commit.')
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', commitMessage])
|
||||
const aopts = ['add']
|
||||
if (addPaths.length > 0) {
|
||||
aopts.push(...['--', ...addPaths])
|
||||
} else {
|
||||
aopts.push('-A')
|
||||
}
|
||||
await git.exec(aopts, true)
|
||||
const popts = ['-m', commitMessage]
|
||||
if (signoff) {
|
||||
popts.push('--signoff')
|
||||
}
|
||||
const commitResult = await git.commit(popts, true)
|
||||
// 'nothing to commit' can occur when core.autocrlf is set to true
|
||||
if (
|
||||
commitResult.exitCode != 0 &&
|
||||
!commitResult.stdout.includes(NOTHING_TO_COMMIT)
|
||||
) {
|
||||
throw new Error(`Unexpected error: ${commitResult.stderr}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform fetch and reset the working base
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
|
||||
// Reset the working base
|
||||
// Commits made during the workflow will be removed
|
||||
await git.fetch([`${workingBase}:${workingBase}`], baseRemote, ['--force'])
|
||||
if (workingBaseType == WorkingBaseType.Branch) {
|
||||
core.info(`Resetting working base branch '${workingBase}'`)
|
||||
await git.checkout(workingBase)
|
||||
await git.exec(['reset', '--hard', `${baseRemote}/${workingBase}`])
|
||||
}
|
||||
|
||||
// If the working base is not the base, rebase the temp branch commits
|
||||
// This will also be true if the working base type is a commit
|
||||
if (workingBase != base) {
|
||||
core.info(
|
||||
`Rebasing commits made to branch '${workingBase}' on to base branch '${base}'`
|
||||
`Rebasing commits made to ${workingBaseType} '${workingBase}' on to base branch '${base}'`
|
||||
)
|
||||
const fetchArgs = ['--force']
|
||||
if (branchRemoteName != 'fork') {
|
||||
// If pushing to a fork we cannot shallow fetch otherwise the 'shallow update not allowed' error occurs
|
||||
fetchArgs.push('--depth=1')
|
||||
}
|
||||
// Checkout the actual base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
await git.checkout(base)
|
||||
// Cherrypick commits from the temporary branch starting from the working base
|
||||
const commits = await git.revList(
|
||||
@ -131,15 +260,22 @@ export async function createOrUpdateBranch(
|
||||
// Reset the temp branch to the working index
|
||||
await git.checkout(tempBranch, 'HEAD')
|
||||
// Reset the base
|
||||
await git.fetch([`${base}:${base}`], baseRemote, ['--force'])
|
||||
await git.fetch([`${base}:${base}`], baseRemote, fetchArgs)
|
||||
}
|
||||
|
||||
// Determine the fetch depth for the pull request branch (best effort)
|
||||
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
||||
const fetchDepth =
|
||||
tempBranchCommitsAhead > 0
|
||||
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
|
||||
: FETCH_DEPTH_MARGIN
|
||||
|
||||
// Try to fetch the pull request branch
|
||||
if (!(await tryFetch(git, branchRemoteName, branch))) {
|
||||
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
|
||||
// The pull request branch does not exist
|
||||
core.info(`Pull request branch '${branch}' does not exist yet.`)
|
||||
// Create the pull request branch
|
||||
await git.checkout(branch, 'HEAD')
|
||||
await git.checkout(branch, tempBranch)
|
||||
// Check if the pull request branch is ahead of the base
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
if (result.hasDiffWithBase) {
|
||||
@ -158,9 +294,23 @@ export async function createOrUpdateBranch(
|
||||
// Checkout the pull request branch
|
||||
await git.checkout(branch)
|
||||
|
||||
if (await hasDiff(git, branch, tempBranch)) {
|
||||
// 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
|
||||
// Reset the branch if one of the following conditions is true.
|
||||
// - If the branch differs from the recreated temp branch.
|
||||
// - If the recreated temp branch is not ahead of the base. This means there will be
|
||||
// no pull request diff after the branch is reset. This will reset any undeleted
|
||||
// branches after merging. In particular, it catches a case where the branch was
|
||||
// squash merged but not deleted. We need to reset to make sure it doesn't appear
|
||||
// to have a diff with the base due to different commits for the same changes.
|
||||
// - If the number of commits ahead of the base branch differs between the branch and
|
||||
// temp branch. This catches a case where the base branch has been force pushed to
|
||||
// a new commit.
|
||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
||||
if (
|
||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||
branchCommitsAhead != tempBranchCommitsAhead ||
|
||||
!(tempBranchCommitsAhead > 0) // !isAhead
|
||||
) {
|
||||
core.info(`Resetting '${branch}'`)
|
||||
// Alternatively, git switch -C branch tempBranch
|
||||
await git.checkout(branch, tempBranch)
|
||||
@ -173,6 +323,7 @@ export async function createOrUpdateBranch(
|
||||
result.action = 'updated'
|
||||
core.info(`Updated branch '${branch}'`)
|
||||
} else {
|
||||
result.action = 'not-updated'
|
||||
core.info(
|
||||
`Branch '${branch}' is even with its remote and will not be updated`
|
||||
)
|
||||
@ -182,14 +333,22 @@ export async function createOrUpdateBranch(
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
}
|
||||
|
||||
// Build the branch file changes
|
||||
result.branchFileChanges = await buildBranchFileChanges(git, base, branch)
|
||||
|
||||
// Get the pull request branch SHA
|
||||
result.headSha = await git.revParse('HEAD')
|
||||
|
||||
// Delete the temporary branch
|
||||
await git.exec(['branch', '--delete', '--force', tempBranch])
|
||||
|
||||
// Checkout the working base to leave the local repository as it was found
|
||||
await git.checkout(workingBase)
|
||||
|
||||
// Restore any stashed changes
|
||||
if (stashed) {
|
||||
await git.stashPop()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
interface CreateOrUpdateBranchResult {
|
||||
action: string
|
||||
base: string
|
||||
hasDiffWithBase: boolean
|
||||
}
|
||||
|
@ -1,51 +1,53 @@
|
||||
import * as core from '@actions/core'
|
||||
import {createOrUpdateBranch} from './create-or-update-branch'
|
||||
import {
|
||||
createOrUpdateBranch,
|
||||
getWorkingBaseAndType,
|
||||
WorkingBaseType
|
||||
} from './create-or-update-branch'
|
||||
import {GitHubHelper} from './github-helper'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import {GitAuthHelper} from './git-auth-helper'
|
||||
import {GitConfigHelper} from './git-config-helper'
|
||||
import * as utils from './utils'
|
||||
|
||||
export interface Inputs {
|
||||
token: string
|
||||
gitToken: string
|
||||
path: string
|
||||
addPaths: string[]
|
||||
commitMessage: string
|
||||
committer: string
|
||||
author: string
|
||||
signoff: boolean
|
||||
branch: string
|
||||
deleteBranch: boolean
|
||||
branchSuffix: string
|
||||
base: string
|
||||
pushToFork: string
|
||||
title: string
|
||||
body: string
|
||||
bodyPath: string
|
||||
labels: string[]
|
||||
assignees: string[]
|
||||
reviewers: string[]
|
||||
teamReviewers: string[]
|
||||
milestone: number
|
||||
draft: boolean
|
||||
signCommit: boolean
|
||||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
let gitAuthHelper
|
||||
let gitConfigHelper, git
|
||||
try {
|
||||
// Get the repository path
|
||||
core.startGroup('Prepare git configuration')
|
||||
const repoPath = utils.getRepoPath(inputs.path)
|
||||
// Create a git command manager
|
||||
const git = await GitCommandManager.create(repoPath)
|
||||
|
||||
// Save and unset the extraheader auth config if it exists
|
||||
core.startGroup('Save persisted git credentials')
|
||||
gitAuthHelper = new GitAuthHelper(git)
|
||||
await gitAuthHelper.savePersistedAuth()
|
||||
git = await GitCommandManager.create(repoPath)
|
||||
gitConfigHelper = await GitConfigHelper.create(git)
|
||||
core.endGroup()
|
||||
|
||||
// Init the GitHub client
|
||||
const githubHelper = new GitHubHelper(inputs.token)
|
||||
|
||||
core.startGroup('Determining the base and head repositories')
|
||||
// Determine the base repository from git config
|
||||
const remoteUrl = await git.tryGetRemoteUrl()
|
||||
const baseRemote = utils.getRemoteDetail(remoteUrl)
|
||||
const baseRemote = gitConfigHelper.getGitRemote()
|
||||
// Init the GitHub client
|
||||
const githubHelper = new GitHubHelper(baseRemote.hostname, inputs.token)
|
||||
// Determine the head repository; the target for the pull request branch
|
||||
const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
|
||||
const branchRepository = inputs.pushToFork
|
||||
@ -53,17 +55,31 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
: baseRemote.repository
|
||||
if (inputs.pushToFork) {
|
||||
// Check if the supplied fork is really a fork of the base
|
||||
const parentRepository = await githubHelper.getRepositoryParent(
|
||||
branchRepository
|
||||
core.info(
|
||||
`Checking if '${branchRepository}' is a fork of '${baseRemote.repository}'`
|
||||
)
|
||||
if (parentRepository != baseRemote.repository) {
|
||||
const baseParentRepository = await githubHelper.getRepositoryParent(
|
||||
baseRemote.repository
|
||||
)
|
||||
const branchParentRepository =
|
||||
await githubHelper.getRepositoryParent(branchRepository)
|
||||
if (branchParentRepository == null) {
|
||||
throw new Error(
|
||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}'. Unable to continue.`
|
||||
`Repository '${branchRepository}' is not a fork. Unable to continue.`
|
||||
)
|
||||
}
|
||||
if (
|
||||
branchParentRepository != baseRemote.repository &&
|
||||
baseParentRepository != branchParentRepository
|
||||
) {
|
||||
throw new Error(
|
||||
`Repository '${branchRepository}' is not a fork of '${baseRemote.repository}', nor are they siblings. Unable to continue.`
|
||||
)
|
||||
}
|
||||
// Add a remote for the fork
|
||||
const remoteUrl = utils.getRemoteUrl(
|
||||
baseRemote.protocol,
|
||||
baseRemote.hostname,
|
||||
branchRepository
|
||||
)
|
||||
await git.exec(['remote', 'add', 'fork', remoteUrl])
|
||||
@ -76,36 +92,36 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
// Configure auth
|
||||
if (baseRemote.protocol == 'HTTPS') {
|
||||
core.startGroup('Configuring credential for HTTPS authentication')
|
||||
await gitAuthHelper.configureToken(inputs.token)
|
||||
await gitConfigHelper.configureToken(inputs.gitToken)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
// 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
|
||||
core.startGroup('Checking the checked out ref')
|
||||
const symbolicRefResult = await git.exec(
|
||||
['symbolic-ref', 'HEAD', '--short'],
|
||||
true
|
||||
)
|
||||
if (symbolicRefResult.exitCode != 0) {
|
||||
core.debug(`${symbolicRefResult.stderr}`)
|
||||
core.startGroup('Checking the base repository state')
|
||||
const [workingBase, workingBaseType] = await getWorkingBaseAndType(git)
|
||||
core.info(`Working base is ${workingBaseType} '${workingBase}'`)
|
||||
// When in detached HEAD state (checked out on a commit), we need to
|
||||
// know the 'base' branch in order to rebase changes.
|
||||
if (workingBaseType == WorkingBaseType.Commit && !inputs.base) {
|
||||
throw new Error(
|
||||
'The checked out ref is not a valid base for a pull request. Unable to continue.'
|
||||
`When the repository is checked out on a commit instead of a branch, the 'base' input must be supplied.`
|
||||
)
|
||||
}
|
||||
const workingBase = symbolicRefResult.stdout.trim()
|
||||
// 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 (workingBase.startsWith(inputs.branch)) {
|
||||
// If the base is not specified it is assumed to be the working base.
|
||||
const base = inputs.base ? inputs.base : workingBase
|
||||
// Throw an error if the base and branch are not different branches
|
||||
// of the 'origin' remote. An identically named branch in the `fork`
|
||||
// remote is perfectly fine.
|
||||
if (branchRemoteName == 'origin' && base == inputs.branch) {
|
||||
throw new Error(
|
||||
`Working base branch '${workingBase}' was created by this action. Unable to continue.`
|
||||
`The 'base' and 'branch' for a pull request must be different branches. Unable to continue.`
|
||||
)
|
||||
}
|
||||
// For self-hosted runners the repository state persists between runs.
|
||||
// This command prunes the stale remote ref when the pull request branch was
|
||||
// deleted after being merged or closed. Without this the push using
|
||||
// '--force-with-lease' fails due to "stale info."
|
||||
// https://github.com/peter-evans/create-pull-request/issues/633
|
||||
await git.exec(['remote', 'prune', branchRemoteName])
|
||||
core.endGroup()
|
||||
|
||||
// Apply the branch suffix if set
|
||||
@ -166,8 +182,12 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
inputs.commitMessage,
|
||||
inputs.base,
|
||||
inputs.branch,
|
||||
branchRemoteName
|
||||
branchRemoteName,
|
||||
inputs.signoff,
|
||||
inputs.addPaths
|
||||
)
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
core.endGroup()
|
||||
|
||||
if (['created', 'updated'].includes(result.action)) {
|
||||
@ -175,44 +195,78 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
core.startGroup(
|
||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||
)
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`HEAD:refs/heads/${inputs.branch}`
|
||||
])
|
||||
core.endGroup()
|
||||
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base
|
||||
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
await githubHelper.createOrUpdatePullRequest(
|
||||
inputs,
|
||||
baseRemote.repository,
|
||||
branchRepository
|
||||
if (inputs.signCommit) {
|
||||
await githubHelper.pushSignedCommit(
|
||||
branchRepository,
|
||||
inputs.branch,
|
||||
inputs.base,
|
||||
inputs.commitMessage,
|
||||
result.branchFileChanges
|
||||
)
|
||||
} else {
|
||||
// If there is no longer a diff with the base delete the branch
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
branchRemoteName,
|
||||
`${inputs.branch}:refs/heads/${inputs.branch}`
|
||||
])
|
||||
}
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
if (result.hasDiffWithBase) {
|
||||
// Create or update the pull request
|
||||
core.startGroup('Create or update the pull request')
|
||||
const pull = await githubHelper.createOrUpdatePullRequest(
|
||||
inputs,
|
||||
baseRemote.repository,
|
||||
branchRepository
|
||||
)
|
||||
core.endGroup()
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-number', pull.number)
|
||||
core.setOutput('pull-request-url', pull.html_url)
|
||||
if (pull.created) {
|
||||
core.setOutput('pull-request-operation', 'created')
|
||||
} else if (result.action == 'updated') {
|
||||
core.setOutput('pull-request-operation', 'updated')
|
||||
}
|
||||
core.setOutput('pull-request-head-sha', result.headSha)
|
||||
core.setOutput('pull-request-branch', inputs.branch)
|
||||
// Deprecated
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pull.number)
|
||||
core.endGroup()
|
||||
} else {
|
||||
// There is no longer a diff with the base
|
||||
// Check we are in a state where a branch exists
|
||||
if (['updated', 'not-updated'].includes(result.action)) {
|
||||
core.info(
|
||||
`Branch '${inputs.branch}' no longer differs from base branch '${inputs.base}'`
|
||||
)
|
||||
core.info(`Closing pull request and deleting branch '${inputs.branch}'`)
|
||||
await git.push([
|
||||
'--delete',
|
||||
'--force',
|
||||
branchRemoteName,
|
||||
`refs/heads/${inputs.branch}`
|
||||
])
|
||||
if (inputs.deleteBranch) {
|
||||
core.info(`Deleting branch '${inputs.branch}'`)
|
||||
await git.push([
|
||||
'--delete',
|
||||
'--force',
|
||||
branchRemoteName,
|
||||
`refs/heads/${inputs.branch}`
|
||||
])
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-operation', 'closed')
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
core.setFailed(utils.getErrorMessage(error))
|
||||
} finally {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
core.startGroup('Restore persisted git credentials')
|
||||
await gitAuthHelper.removeAuth()
|
||||
await gitAuthHelper.restorePersistedAuth()
|
||||
core.startGroup('Restore git configuration')
|
||||
if (inputs.pushToFork) {
|
||||
await git.exec(['remote', 'rm', 'fork'])
|
||||
}
|
||||
await gitConfigHelper.close()
|
||||
core.endGroup()
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ export class GitCommandManager {
|
||||
} else {
|
||||
args.push(ref)
|
||||
}
|
||||
// https://github.com/git/git/commit/a047fafc7866cc4087201e284dc1f53e8f9a32d5
|
||||
args.push('--')
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
@ -51,7 +53,10 @@ export class GitCommandManager {
|
||||
return await this.exec(args, allowAllExitCodes)
|
||||
}
|
||||
|
||||
async commit(options?: string[]): Promise<void> {
|
||||
async commit(
|
||||
options?: string[],
|
||||
allowAllExitCodes = false
|
||||
): Promise<GitOutput> {
|
||||
const args = ['commit']
|
||||
if (this.identityGitOptions) {
|
||||
args.unshift(...this.identityGitOptions)
|
||||
@ -61,20 +66,21 @@ export class GitCommandManager {
|
||||
args.push(...options)
|
||||
}
|
||||
|
||||
await this.exec(args)
|
||||
return await this.exec(args, allowAllExitCodes)
|
||||
}
|
||||
|
||||
async config(
|
||||
configKey: string,
|
||||
configValue: string,
|
||||
globalConfig?: boolean
|
||||
globalConfig?: boolean,
|
||||
add?: boolean
|
||||
): Promise<void> {
|
||||
await this.exec([
|
||||
'config',
|
||||
globalConfig ? '--global' : '--local',
|
||||
configKey,
|
||||
configValue
|
||||
])
|
||||
const args: string[] = ['config', globalConfig ? '--global' : '--local']
|
||||
if (add) {
|
||||
args.push('--add')
|
||||
}
|
||||
args.push(...[configKey, configValue])
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
async configExists(
|
||||
@ -96,19 +102,11 @@ export class GitCommandManager {
|
||||
return output.exitCode === 0
|
||||
}
|
||||
|
||||
async diff(options?: string[]): Promise<string> {
|
||||
const args = ['-c', 'core.pager=cat', 'diff']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args)
|
||||
return output.stdout.trim()
|
||||
}
|
||||
|
||||
async fetch(
|
||||
refSpec: string[],
|
||||
remoteName?: string,
|
||||
options?: string[]
|
||||
options?: string[],
|
||||
unshallow = false
|
||||
): Promise<void> {
|
||||
const args = ['-c', 'protocol.version=2', 'fetch']
|
||||
if (!refSpec.some(x => x === tagsRefSpec)) {
|
||||
@ -116,7 +114,9 @@ export class GitCommandManager {
|
||||
}
|
||||
|
||||
args.push('--progress', '--no-recurse-submodules')
|
||||
|
||||
if (
|
||||
unshallow &&
|
||||
utils.fileExistsSync(path.join(this.workingDirectory, '.git', 'shallow'))
|
||||
) {
|
||||
args.push('--unshallow')
|
||||
@ -149,22 +149,48 @@ export class GitCommandManager {
|
||||
return output.stdout.trim().split(`${configKey} `)[1]
|
||||
}
|
||||
|
||||
getGitDirectory(): Promise<string> {
|
||||
return this.revParse('--git-dir')
|
||||
}
|
||||
|
||||
getWorkingDirectory(): string {
|
||||
return this.workingDirectory
|
||||
}
|
||||
|
||||
async isDirty(untracked: boolean): Promise<boolean> {
|
||||
const diffArgs = ['--abbrev=40', '--full-index', '--raw']
|
||||
// Check staged changes
|
||||
if (await this.diff([...diffArgs, '--staged'])) {
|
||||
async hasDiff(options?: string[]): Promise<boolean> {
|
||||
const args = ['diff', '--quiet']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args, true)
|
||||
return output.exitCode === 1
|
||||
}
|
||||
|
||||
async getChangedFiles(options?: string[]): Promise<string[]> {
|
||||
const args = ['diff', '--name-only']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args)
|
||||
return output.stdout.split('\n').filter(filename => filename != '')
|
||||
}
|
||||
|
||||
async isDirty(untracked: boolean, pathspec?: string[]): Promise<boolean> {
|
||||
const pathspecArgs = pathspec ? ['--', ...pathspec] : []
|
||||
// Check untracked changes
|
||||
const sargs = ['--porcelain', '-unormal']
|
||||
sargs.push(...pathspecArgs)
|
||||
if (untracked && (await this.status(sargs))) {
|
||||
return true
|
||||
}
|
||||
// Check working index changes
|
||||
if (await this.diff(diffArgs)) {
|
||||
if (await this.hasDiff(pathspecArgs)) {
|
||||
return true
|
||||
}
|
||||
// Check untracked changes
|
||||
if (untracked && (await this.status(['--porcelain', '-unormal']))) {
|
||||
// Check staged changes
|
||||
const dargs = ['--staged']
|
||||
dargs.push(...pathspecArgs)
|
||||
if (await this.hasDiff(dargs)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -201,6 +227,23 @@ export class GitCommandManager {
|
||||
return output.stdout.trim()
|
||||
}
|
||||
|
||||
async stashPush(options?: string[]): Promise<boolean> {
|
||||
const args = ['stash', 'push']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
const output = await this.exec(args)
|
||||
return output.stdout.trim() !== 'No local changes to save'
|
||||
}
|
||||
|
||||
async stashPop(options?: string[]): Promise<void> {
|
||||
const args = ['stash', 'pop']
|
||||
if (options) {
|
||||
args.push(...options)
|
||||
}
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
async status(options?: string[]): Promise<string> {
|
||||
const args = ['status']
|
||||
if (options) {
|
||||
|
@ -3,27 +3,124 @@ import * as fs from 'fs'
|
||||
import {GitCommandManager} from './git-command-manager'
|
||||
import * as path from 'path'
|
||||
import {URL} from 'url'
|
||||
import * as utils from './utils'
|
||||
|
||||
export class GitAuthHelper {
|
||||
interface GitRemote {
|
||||
hostname: string
|
||||
protocol: string
|
||||
repository: string
|
||||
}
|
||||
|
||||
export class GitConfigHelper {
|
||||
private git: GitCommandManager
|
||||
private gitConfigPath: string
|
||||
private extraheaderConfigKey: string
|
||||
private gitConfigPath = ''
|
||||
private workingDirectory: string
|
||||
private safeDirectoryConfigKey = 'safe.directory'
|
||||
private safeDirectoryAdded = false
|
||||
private remoteUrl = ''
|
||||
private extraheaderConfigKey = ''
|
||||
private extraheaderConfigPlaceholderValue = 'AUTHORIZATION: basic ***'
|
||||
private extraheaderConfigValueRegex = '^AUTHORIZATION:'
|
||||
private persistedExtraheaderConfigValue = ''
|
||||
|
||||
constructor(git: GitCommandManager) {
|
||||
private constructor(git: GitCommandManager) {
|
||||
this.git = git
|
||||
this.gitConfigPath = path.join(
|
||||
this.git.getWorkingDirectory(),
|
||||
'.git',
|
||||
'config'
|
||||
this.workingDirectory = this.git.getWorkingDirectory()
|
||||
}
|
||||
|
||||
static async create(git: GitCommandManager): Promise<GitConfigHelper> {
|
||||
const gitConfigHelper = new GitConfigHelper(git)
|
||||
await gitConfigHelper.addSafeDirectory()
|
||||
await gitConfigHelper.fetchRemoteDetail()
|
||||
await gitConfigHelper.savePersistedAuth()
|
||||
return gitConfigHelper
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
// Remove auth and restore persisted auth config if it existed
|
||||
await this.removeAuth()
|
||||
await this.restorePersistedAuth()
|
||||
await this.removeSafeDirectory()
|
||||
}
|
||||
|
||||
async addSafeDirectory(): Promise<void> {
|
||||
const exists = await this.git.configExists(
|
||||
this.safeDirectoryConfigKey,
|
||||
this.workingDirectory,
|
||||
true
|
||||
)
|
||||
if (!exists) {
|
||||
await this.git.config(
|
||||
this.safeDirectoryConfigKey,
|
||||
this.workingDirectory,
|
||||
true,
|
||||
true
|
||||
)
|
||||
this.safeDirectoryAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
async removeSafeDirectory(): Promise<void> {
|
||||
if (this.safeDirectoryAdded) {
|
||||
await this.git.tryConfigUnset(
|
||||
this.safeDirectoryConfigKey,
|
||||
this.workingDirectory,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fetchRemoteDetail(): Promise<void> {
|
||||
this.remoteUrl = await this.git.tryGetRemoteUrl()
|
||||
}
|
||||
|
||||
getGitRemote(): GitRemote {
|
||||
return GitConfigHelper.parseGitRemote(this.remoteUrl)
|
||||
}
|
||||
|
||||
static parseGitRemote(remoteUrl: string): GitRemote {
|
||||
const httpsUrlPattern = new RegExp(
|
||||
'^(https?)://(?:.+@)?(.+?)/(.+/.+?)(\\.git)?$',
|
||||
'i'
|
||||
)
|
||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||
if (httpsMatch) {
|
||||
return {
|
||||
hostname: httpsMatch[2],
|
||||
protocol: 'HTTPS',
|
||||
repository: httpsMatch[3]
|
||||
}
|
||||
}
|
||||
|
||||
const sshUrlPattern = new RegExp('^git@(.+?):(.+/.+)\\.git$', 'i')
|
||||
const sshMatch = remoteUrl.match(sshUrlPattern)
|
||||
if (sshMatch) {
|
||||
return {
|
||||
hostname: sshMatch[1],
|
||||
protocol: 'SSH',
|
||||
repository: sshMatch[2]
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthenticated git protocol for integration tests only
|
||||
const gitUrlPattern = new RegExp('^git://(.+?)/(.+/.+)\\.git$', 'i')
|
||||
const gitMatch = remoteUrl.match(gitUrlPattern)
|
||||
if (gitMatch) {
|
||||
return {
|
||||
hostname: gitMatch[1],
|
||||
protocol: 'GIT',
|
||||
repository: gitMatch[2]
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
const serverUrl = this.getServerUrl()
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||
}
|
||||
|
||||
async savePersistedAuth(): Promise<void> {
|
||||
const serverUrl = new URL(`https://${this.getGitRemote().hostname}`)
|
||||
this.extraheaderConfigKey = `http.${serverUrl.origin}/.extraheader`
|
||||
// Save and unset persisted extraheader credential in git config if it exists
|
||||
this.persistedExtraheaderConfigValue = await this.getAndUnset()
|
||||
}
|
||||
@ -34,7 +131,7 @@ export class GitAuthHelper {
|
||||
await this.setExtraheaderConfig(this.persistedExtraheaderConfigValue)
|
||||
core.info('Persisted git credentials restored')
|
||||
} catch (e) {
|
||||
core.warning(e)
|
||||
core.warning(utils.getErrorMessage(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,6 +202,10 @@ export class GitAuthHelper {
|
||||
find: string,
|
||||
replace: string
|
||||
): Promise<void> {
|
||||
if (this.gitConfigPath.length === 0) {
|
||||
const gitDir = await this.git.getGitDirectory()
|
||||
this.gitConfigPath = path.join(this.workingDirectory, gitDir, 'config')
|
||||
}
|
||||
let content = (await fs.promises.readFile(this.gitConfigPath)).toString()
|
||||
const index = content.indexOf(find)
|
||||
if (index < 0 || index != content.lastIndexOf(find)) {
|
||||
@ -113,14 +214,4 @@ export class GitAuthHelper {
|
||||
content = content.replace(find, replace)
|
||||
await fs.promises.writeFile(this.gitConfigPath, content)
|
||||
}
|
||||
|
||||
private getServerUrl(): URL {
|
||||
// todo: remove GITHUB_URL after support for GHES Alpha is no longer needed
|
||||
// See https://github.com/actions/checkout/blob/main/src/url-helper.ts#L22-L29
|
||||
return new URL(
|
||||
process.env['GITHUB_SERVER_URL'] ||
|
||||
process.env['GITHUB_URL'] ||
|
||||
'https://github.com'
|
||||
)
|
||||
}
|
||||
}
|
@ -1,23 +1,42 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs} from './create-pull-request'
|
||||
import {Octokit, OctokitOptions} from './octokit-client'
|
||||
import type {
|
||||
Repository as TempRepository,
|
||||
Ref,
|
||||
Commit,
|
||||
FileChanges
|
||||
} from '@octokit/graphql-schema'
|
||||
import {BranchFileChanges} from './create-or-update-branch'
|
||||
import * as utils from './utils'
|
||||
|
||||
const ERROR_PR_REVIEW_FROM_AUTHOR =
|
||||
'Review cannot be requested from pull request author'
|
||||
const ERROR_PR_REVIEW_TOKEN_SCOPE =
|
||||
'Validation Failed: "Could not resolve to a node with the global id of'
|
||||
|
||||
interface Repository {
|
||||
owner: string
|
||||
repo: string
|
||||
}
|
||||
|
||||
interface Pull {
|
||||
number: number
|
||||
html_url: string
|
||||
created: boolean
|
||||
}
|
||||
|
||||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
constructor(token: string) {
|
||||
constructor(githubServerHostname: string, token: string) {
|
||||
const options: OctokitOptions = {}
|
||||
if (token) {
|
||||
options.auth = `${token}`
|
||||
}
|
||||
if (githubServerHostname !== 'github.com') {
|
||||
options.baseUrl = `https://${githubServerHostname}/api/v3`
|
||||
} else {
|
||||
options.baseUrl = 'https://api.github.com'
|
||||
}
|
||||
this.octokit = new Octokit(options)
|
||||
}
|
||||
|
||||
@ -32,14 +51,19 @@ export class GitHubHelper {
|
||||
private async createOrUpdate(
|
||||
inputs: Inputs,
|
||||
baseRepository: string,
|
||||
headBranch: string
|
||||
): Promise<number> {
|
||||
headRepository: string
|
||||
): Promise<Pull> {
|
||||
const [headOwner] = headRepository.split('/')
|
||||
const headBranch = `${headOwner}:${inputs.branch}`
|
||||
|
||||
// Try to create the pull request
|
||||
try {
|
||||
const {data: pull} = await this.octokit.pulls.create({
|
||||
core.info(`Attempting creation of pull request`)
|
||||
const {data: pull} = await this.octokit.rest.pulls.create({
|
||||
...this.parseRepository(baseRepository),
|
||||
title: inputs.title,
|
||||
head: headBranch,
|
||||
head_repo: headRepository,
|
||||
base: inputs.base,
|
||||
body: inputs.body,
|
||||
draft: inputs.draft
|
||||
@ -47,44 +71,52 @@ export class GitHubHelper {
|
||||
core.info(
|
||||
`Created pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||
)
|
||||
return pull.number
|
||||
return {
|
||||
number: pull.number,
|
||||
html_url: pull.html_url,
|
||||
created: true
|
||||
}
|
||||
} catch (e) {
|
||||
if (
|
||||
!e.message ||
|
||||
!e.message.includes(`A pull request already exists for ${headBranch}`)
|
||||
utils.getErrorMessage(e).includes(`A pull request already exists for`)
|
||||
) {
|
||||
core.info(`A pull request already exists for ${headBranch}`)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// Update the pull request that exists for this branch and base
|
||||
const {data: pulls} = await this.octokit.pulls.list({
|
||||
core.info(`Fetching existing pull request`)
|
||||
const {data: pulls} = await this.octokit.rest.pulls.list({
|
||||
...this.parseRepository(baseRepository),
|
||||
state: 'open',
|
||||
head: headBranch,
|
||||
base: inputs.base
|
||||
})
|
||||
const {data: pull} = await this.octokit.pulls.update({
|
||||
core.info(`Attempting update of pull request`)
|
||||
const {data: pull} = await this.octokit.rest.pulls.update({
|
||||
...this.parseRepository(baseRepository),
|
||||
pull_number: pulls[0].number,
|
||||
title: inputs.title,
|
||||
body: inputs.body,
|
||||
draft: inputs.draft
|
||||
body: inputs.body
|
||||
})
|
||||
core.info(
|
||||
`Updated pull request #${pull.number} (${headBranch} => ${inputs.base})`
|
||||
)
|
||||
return pull.number
|
||||
return {
|
||||
number: pull.number,
|
||||
html_url: pull.html_url,
|
||||
created: false
|
||||
}
|
||||
}
|
||||
|
||||
async getRepositoryParent(headRepository: string): Promise<string> {
|
||||
const {data: headRepo} = await this.octokit.repos.get({
|
||||
async getRepositoryParent(headRepository: string): Promise<string | null> {
|
||||
const {data: headRepo} = await this.octokit.rest.repos.get({
|
||||
...this.parseRepository(headRepository)
|
||||
})
|
||||
if (!headRepo.parent) {
|
||||
throw new Error(
|
||||
`Repository '${headRepository}' is not a fork. Unable to continue.`
|
||||
)
|
||||
return null
|
||||
}
|
||||
return headRepo.parent.full_name
|
||||
}
|
||||
@ -93,42 +125,39 @@ export class GitHubHelper {
|
||||
inputs: Inputs,
|
||||
baseRepository: string,
|
||||
headRepository: string
|
||||
): Promise<void> {
|
||||
const [headOwner] = headRepository.split('/')
|
||||
const headBranch = `${headOwner}:${inputs.branch}`
|
||||
|
||||
): Promise<Pull> {
|
||||
// Create or update the pull request
|
||||
const pullNumber = await this.createOrUpdate(
|
||||
const pull = await this.createOrUpdate(
|
||||
inputs,
|
||||
baseRepository,
|
||||
headBranch
|
||||
headRepository
|
||||
)
|
||||
|
||||
// Set outputs
|
||||
core.startGroup('Setting outputs')
|
||||
core.setOutput('pull-request-number', pullNumber)
|
||||
core.exportVariable('PULL_REQUEST_NUMBER', pullNumber)
|
||||
core.endGroup()
|
||||
|
||||
// Set milestone, labels and assignees
|
||||
const updateIssueParams = {}
|
||||
// Apply milestone
|
||||
if (inputs.milestone) {
|
||||
updateIssueParams['milestone'] = inputs.milestone
|
||||
core.info(`Applying milestone '${inputs.milestone}'`)
|
||||
}
|
||||
if (inputs.labels.length > 0) {
|
||||
updateIssueParams['labels'] = inputs.labels
|
||||
core.info(`Applying labels '${inputs.labels}'`)
|
||||
}
|
||||
if (inputs.assignees.length > 0) {
|
||||
updateIssueParams['assignees'] = inputs.assignees
|
||||
core.info(`Applying assignees '${inputs.assignees}'`)
|
||||
}
|
||||
if (Object.keys(updateIssueParams).length > 0) {
|
||||
await this.octokit.issues.update({
|
||||
await this.octokit.rest.issues.update({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pullNumber,
|
||||
...updateIssueParams
|
||||
issue_number: pull.number,
|
||||
milestone: inputs.milestone
|
||||
})
|
||||
}
|
||||
// Apply labels
|
||||
if (inputs.labels.length > 0) {
|
||||
core.info(`Applying labels '${inputs.labels}'`)
|
||||
await this.octokit.rest.issues.addLabels({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pull.number,
|
||||
labels: inputs.labels
|
||||
})
|
||||
}
|
||||
// Apply assignees
|
||||
if (inputs.assignees.length > 0) {
|
||||
core.info(`Applying assignees '${inputs.assignees}'`)
|
||||
await this.octokit.rest.issues.addAssignees({
|
||||
...this.parseRepository(baseRepository),
|
||||
issue_number: pull.number,
|
||||
assignees: inputs.assignees
|
||||
})
|
||||
}
|
||||
|
||||
@ -139,23 +168,227 @@ export class GitHubHelper {
|
||||
core.info(`Requesting reviewers '${inputs.reviewers}'`)
|
||||
}
|
||||
if (inputs.teamReviewers.length > 0) {
|
||||
requestReviewersParams['team_reviewers'] = inputs.teamReviewers
|
||||
core.info(`Requesting team reviewers '${inputs.teamReviewers}'`)
|
||||
const teams = utils.stripOrgPrefixFromTeams(inputs.teamReviewers)
|
||||
requestReviewersParams['team_reviewers'] = teams
|
||||
core.info(`Requesting team reviewers '${teams}'`)
|
||||
}
|
||||
if (Object.keys(requestReviewersParams).length > 0) {
|
||||
try {
|
||||
await this.octokit.pulls.requestReviewers({
|
||||
await this.octokit.rest.pulls.requestReviewers({
|
||||
...this.parseRepository(baseRepository),
|
||||
pull_number: pullNumber,
|
||||
pull_number: pull.number,
|
||||
...requestReviewersParams
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.message && e.message.includes(ERROR_PR_REVIEW_FROM_AUTHOR)) {
|
||||
core.warning(ERROR_PR_REVIEW_FROM_AUTHOR)
|
||||
} else {
|
||||
throw e
|
||||
if (utils.getErrorMessage(e).includes(ERROR_PR_REVIEW_TOKEN_SCOPE)) {
|
||||
core.error(
|
||||
`Unable to request reviewers. If requesting team reviewers a 'repo' scoped PAT is required.`
|
||||
)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return pull
|
||||
}
|
||||
|
||||
async pushSignedCommit(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
base: string,
|
||||
commitMessage: string,
|
||||
branchFileChanges?: BranchFileChanges
|
||||
): Promise<void> {
|
||||
core.info(`Use API to push a signed commit`)
|
||||
|
||||
const [repoOwner, repoName] = branchRepository.split('/')
|
||||
core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`)
|
||||
const refQuery = `
|
||||
query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) {
|
||||
repository(owner: $repoOwner, name: $repoName){
|
||||
id
|
||||
ref(qualifiedName: $branchName){
|
||||
id
|
||||
name
|
||||
prefix
|
||||
target{
|
||||
id
|
||||
oid
|
||||
commitUrl
|
||||
commitResourcePath
|
||||
abbreviatedOid
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
`
|
||||
|
||||
let branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||
refQuery,
|
||||
{
|
||||
repoOwner: repoOwner,
|
||||
repoName: repoName,
|
||||
branchName: branch
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Fetched information for branch '${branch}' - '${JSON.stringify(branchRef)}'`
|
||||
)
|
||||
|
||||
const branchExists = branchRef.repository.ref != null
|
||||
|
||||
// if the branch does not exist, then first we need to create the branch from base
|
||||
if (!branchExists) {
|
||||
core.debug(`Branch does not exist - '${branch}'`)
|
||||
branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||
refQuery,
|
||||
{
|
||||
repoOwner: repoOwner,
|
||||
repoName: repoName,
|
||||
branchName: base
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'`
|
||||
)
|
||||
|
||||
core.info(
|
||||
`Creating new branch '${branch}' from '${base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||
)
|
||||
if (branchRef.repository.ref != null) {
|
||||
core.debug(`Send request for creating new branch`)
|
||||
const newBranchMutation = `
|
||||
mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) {
|
||||
createRef(input: {
|
||||
name: $branchName,
|
||||
oid: $oid,
|
||||
repositoryId: $repoId
|
||||
}) {
|
||||
ref {
|
||||
id
|
||||
name
|
||||
prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const newBranch = await this.octokit.graphql<{createRef: {ref: Ref}}>(
|
||||
newBranchMutation,
|
||||
{
|
||||
repoId: branchRef.repository.id,
|
||||
oid: branchRef.repository.ref.target!.oid,
|
||||
branchName: 'refs/heads/' + branch
|
||||
}
|
||||
)
|
||||
core.debug(
|
||||
`Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
core.info(
|
||||
`Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'`
|
||||
)
|
||||
|
||||
const fileChanges = <FileChanges>{
|
||||
additions: branchFileChanges!.additions,
|
||||
deletions: branchFileChanges!.deletions
|
||||
}
|
||||
|
||||
const pushCommitMutation = `
|
||||
mutation PushCommit(
|
||||
$repoNameWithOwner: String!,
|
||||
$branchName: String!,
|
||||
$headOid: GitObjectID!,
|
||||
$commitMessage: String!,
|
||||
$fileChanges: FileChanges
|
||||
) {
|
||||
createCommitOnBranch(input: {
|
||||
branch: {
|
||||
repositoryNameWithOwner: $repoNameWithOwner,
|
||||
branchName: $branchName,
|
||||
}
|
||||
fileChanges: $fileChanges
|
||||
message: {
|
||||
headline: $commitMessage
|
||||
}
|
||||
expectedHeadOid: $headOid
|
||||
}){
|
||||
clientMutationId
|
||||
ref{
|
||||
id
|
||||
name
|
||||
prefix
|
||||
}
|
||||
commit{
|
||||
id
|
||||
abbreviatedOid
|
||||
oid
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const pushCommitVars = {
|
||||
branchName: branch,
|
||||
repoNameWithOwner: repoOwner + '/' + repoName,
|
||||
headOid: branchRef.repository.ref!.target!.oid,
|
||||
commitMessage: commitMessage,
|
||||
fileChanges: fileChanges
|
||||
}
|
||||
|
||||
const pushCommitVarsWithoutContents = {
|
||||
...pushCommitVars,
|
||||
fileChanges: {
|
||||
...pushCommitVars.fileChanges,
|
||||
additions: pushCommitVars.fileChanges.additions?.map(addition => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {contents, ...rest} = addition
|
||||
return rest
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(
|
||||
`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`
|
||||
)
|
||||
|
||||
const commit = await this.octokit.graphql<{
|
||||
createCommitOnBranch: {ref: Ref; commit: Commit}
|
||||
}>(pushCommitMutation, pushCommitVars)
|
||||
|
||||
core.debug(`Pushed commit - '${JSON.stringify(commit)}'`)
|
||||
core.info(
|
||||
`Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`
|
||||
)
|
||||
|
||||
if (branchExists) {
|
||||
// The branch existed so update the branch ref to point to the new commit
|
||||
// This is the same behavior as force pushing the branch
|
||||
core.info(
|
||||
`Updating branch '${branch}' to commit '${commit.createCommitOnBranch.commit.oid}'`
|
||||
)
|
||||
const updateBranchMutation = `
|
||||
mutation UpdateBranch($branchId: ID!, $commitOid: GitObjectID!) {
|
||||
updateRef(input: {
|
||||
refId: $branchId,
|
||||
oid: $commitOid,
|
||||
force: true
|
||||
}) {
|
||||
ref {
|
||||
id
|
||||
name
|
||||
prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const updatedBranch = await this.octokit.graphql<{updateRef: {ref: Ref}}>(
|
||||
updateBranchMutation,
|
||||
{
|
||||
branchId: branchRef.repository.ref!.id,
|
||||
commitOid: commit.createCommitOnBranch.commit.oid
|
||||
}
|
||||
)
|
||||
core.debug(`Updated branch - '${JSON.stringify(updatedBranch)}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
src/main.ts
31
src/main.ts
@ -7,28 +7,55 @@ async function run(): Promise<void> {
|
||||
try {
|
||||
const inputs: Inputs = {
|
||||
token: core.getInput('token'),
|
||||
gitToken: core.getInput('git-token'),
|
||||
path: core.getInput('path'),
|
||||
addPaths: utils.getInputAsArray('add-paths'),
|
||||
commitMessage: core.getInput('commit-message'),
|
||||
committer: core.getInput('committer'),
|
||||
author: core.getInput('author'),
|
||||
signoff: core.getBooleanInput('signoff'),
|
||||
branch: core.getInput('branch'),
|
||||
deleteBranch: core.getBooleanInput('delete-branch'),
|
||||
branchSuffix: core.getInput('branch-suffix'),
|
||||
base: core.getInput('base'),
|
||||
pushToFork: core.getInput('push-to-fork'),
|
||||
title: core.getInput('title'),
|
||||
body: core.getInput('body'),
|
||||
bodyPath: core.getInput('body-path'),
|
||||
labels: utils.getInputAsArray('labels'),
|
||||
assignees: utils.getInputAsArray('assignees'),
|
||||
reviewers: utils.getInputAsArray('reviewers'),
|
||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||
milestone: Number(core.getInput('milestone')),
|
||||
draft: core.getInput('draft') === 'true'
|
||||
draft: core.getBooleanInput('draft'),
|
||||
signCommit: core.getBooleanInput('sign-commit')
|
||||
}
|
||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||
|
||||
if (!inputs.token) {
|
||||
throw new Error(`Input 'token' not supplied. Unable to continue.`)
|
||||
}
|
||||
if (!inputs.gitToken) {
|
||||
inputs.gitToken = inputs.token
|
||||
}
|
||||
if (inputs.bodyPath) {
|
||||
if (!utils.fileExistsSync(inputs.bodyPath)) {
|
||||
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
|
||||
}
|
||||
// Update the body input with the contents of the file
|
||||
inputs.body = utils.readFile(inputs.bodyPath)
|
||||
}
|
||||
// 65536 characters is the maximum allowed for the pull request body.
|
||||
if (inputs.body.length > 65536) {
|
||||
core.warning(
|
||||
`Pull request body is too long. Truncating to 65536 characters.`
|
||||
)
|
||||
inputs.body = inputs.body.substring(0, 65536)
|
||||
}
|
||||
|
||||
await createPullRequest(inputs)
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
core.setFailed(utils.getErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,34 @@
|
||||
import {Octokit as Core} from '@octokit/core'
|
||||
import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'
|
||||
import {getProxyForUrl} from 'proxy-from-env'
|
||||
import {ProxyAgent, fetch as undiciFetch} from 'undici'
|
||||
export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
|
||||
export {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||
|
||||
export const Octokit = Core.plugin(paginateRest, restEndpointMethods)
|
||||
export const Octokit = Core.plugin(
|
||||
paginateRest,
|
||||
restEndpointMethods,
|
||||
autoProxyAgent
|
||||
)
|
||||
|
||||
const proxyFetch =
|
||||
(proxyUrl: string): typeof undiciFetch =>
|
||||
(url, opts) => {
|
||||
return undiciFetch(url, {
|
||||
...opts,
|
||||
dispatcher: new ProxyAgent({
|
||||
uri: proxyUrl
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Octokit plugin to support the standard environment variables http_proxy, https_proxy and no_proxy
|
||||
function autoProxyAgent(octokit: Core) {
|
||||
octokit.hook.before('request', options => {
|
||||
const proxy = getProxyForUrl(options.baseUrl)
|
||||
if (proxy) {
|
||||
options.request.fetch = proxyFetch(proxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
74
src/utils.ts
74
src/utils.ts
@ -16,6 +16,16 @@ export function getStringAsArray(str: string): string[] {
|
||||
.filter(x => x !== '')
|
||||
}
|
||||
|
||||
export function stripOrgPrefixFromTeams(teams: string[]): string[] {
|
||||
return teams.map(team => {
|
||||
const slashIndex = team.lastIndexOf('/')
|
||||
if (slashIndex > 0) {
|
||||
return team.substring(slashIndex + 1)
|
||||
}
|
||||
return team
|
||||
})
|
||||
}
|
||||
|
||||
export function getRepoPath(relativePath?: string): string {
|
||||
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
|
||||
if (!githubWorkspacePath) {
|
||||
@ -31,42 +41,14 @@ export function getRepoPath(relativePath?: string): string {
|
||||
return repoPath
|
||||
}
|
||||
|
||||
interface RemoteDetail {
|
||||
protocol: string
|
||||
export function getRemoteUrl(
|
||||
protocol: string,
|
||||
hostname: string,
|
||||
repository: string
|
||||
}
|
||||
|
||||
export function getRemoteDetail(remoteUrl: string): RemoteDetail {
|
||||
// Parse the protocol and github repository from a URL
|
||||
// e.g. HTTPS, peter-evans/create-pull-request
|
||||
const httpsUrlPattern = /^https:\/\/.*@?github.com\/(.+\/.+)$/i
|
||||
const sshUrlPattern = /^git@github.com:(.+\/.+).git$/i
|
||||
|
||||
const httpsMatch = remoteUrl.match(httpsUrlPattern)
|
||||
if (httpsMatch) {
|
||||
return {
|
||||
protocol: 'HTTPS',
|
||||
repository: httpsMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
const sshMatch = remoteUrl.match(sshUrlPattern)
|
||||
if (sshMatch) {
|
||||
return {
|
||||
protocol: 'SSH',
|
||||
repository: sshMatch[1]
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The format of '${remoteUrl}' is not a valid GitHub repository URL`
|
||||
)
|
||||
}
|
||||
|
||||
export function getRemoteUrl(protocol: string, repository: string): string {
|
||||
): string {
|
||||
return protocol == 'HTTPS'
|
||||
? `https://github.com/${repository}`
|
||||
: `git@github.com:${repository}.git`
|
||||
? `https://${hostname}/${repository}`
|
||||
: `git@${hostname}:${repository}.git`
|
||||
}
|
||||
|
||||
export function secondsSinceEpoch(): number {
|
||||
@ -122,12 +104,14 @@ export function fileExistsSync(path: string): boolean {
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (hasErrorCode(error) && error.code === 'ENOENT') {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
`Encountered an error when checking whether path '${path}' exists: ${getErrorMessage(
|
||||
error
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
@ -137,3 +121,21 @@ export function fileExistsSync(path: string): boolean {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function readFile(path: string): string {
|
||||
return fs.readFileSync(path, 'utf-8')
|
||||
}
|
||||
|
||||
export function readFileBase64(pathParts: string[]): string {
|
||||
return fs.readFileSync(path.resolve(...pathParts)).toString('base64')
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
function hasErrorCode(error: any): error is {code: string} {
|
||||
return typeof (error && error.code) === 'string'
|
||||
}
|
||||
|
||||
export function getErrorMessage(error: unknown) {
|
||||
if (error instanceof Error) return error.message
|
||||
return String(error)
|
||||
}
|
||||
|
Reference in New Issue
Block a user