FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
streamlink/script/github-release.py at master · streamlink/streamlink · GitHub
Uh oh!
There was an error while loading.
Please reload this page
.
streamlink
/
streamlink
Public
Uh oh!
There was an error while loading.
Please reload this page
.
Notifications
You must be signed in to change notification settings
Fork
1.2k
Star
11.7k
Code
Issues
68
Pull requests
10
Discussions
Actions
Security and quality
1
Insights
Additional navigation options
Code
Issues
Pull requests
Discussions
Actions
Security and quality
Insights
Expand file tree
Breadcrumbs
streamlink
/
script
/
github-release.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
executable file
·
588 lines (489 loc) · 19.2 KB
Breadcrumbs
streamlink
/
script
/
github-release.py
Copy path
File metadata and controls
executable file
·
588 lines (489 loc) · 19.2 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
#!/usr/bin/env python
from
__future__
import
annotations
import
argparse
import
logging
import
re
import
subprocess
import
sys
from
contextlib
import
contextmanager
from
dataclasses
import
dataclass
from
os
import
getenv
from
pathlib
import
Path
from
pprint
import
pformat
from
typing
import
IO
,
TYPE_CHECKING
,
Any
,
Literal
,
NewType
# noinspection PyPackageRequirements
import
jinja2
import
requests
if
TYPE_CHECKING
:
from
collections
.
abc
import
Callable
,
Generator
,
Mapping
log
=
logging
.
getLogger
(
__name__
)
ROOT
=
Path
(
__file__
).
parents
[
1
].
resolve
()
DEFAULT_REPO
=
"streamlink/streamlink"
RE_RELEASE_COMMIT_MESSAGE
=
re
.
compile
(
r"^release: (?P<version>\d+\.\d+\.\d+(?:-\S+)?)$"
)
RE_CHANGELOG_DELIM
=
re
.
compile
(
r"\n## streamlink "
)
RE_CHANGELOG_CONTENT
=
re
.
compile
(
r"""
^
(?P<version>\d+\.\d+\.\d+(?:-\S+)?)\s
\((?P<date>\d{4}-\d\d-\d\d)\)\n\n
(?P<changelog>.+?)\n\n
\[Full\ changelog]\(\S+\.\.\.(?P=version)\)\n\n
$
"""
,
re
.
VERBOSE
|
re
.
DOTALL
|
re
.
IGNORECASE
,
)
RE_CO_AUTHOR
=
re
.
compile
(
r"""
^\s*Co-Authored-By:\s+(?P<name>.+)\s+<(?P<email>.+?@.+?)>\s*$
"""
,
re
.
VERBOSE
|
re
.
MULTILINE
|
re
.
IGNORECASE
,
)
def
get_args
():
parser
=
argparse
.
ArgumentParser
(
description
=
(
"Create or update a GitHub release and upload release assets.
\n
"
+
"Reads the API key from the RELEASES_API_KEY or GITHUB_TOKEN env vars.
\n
"
+
"Performs a dry run if no API key was set."
),
formatter_class
=
argparse
.
RawTextHelpFormatter
,
)
parser
.
add_argument
(
"--debug"
,
action
=
"store_true"
,
help
=
"Enable debug logging"
,
)
parser
.
add_argument
(
"--dry-run"
,
action
=
"store_true"
,
help
=
"Don't make any GitHub API calls"
,
)
parser
.
add_argument
(
"--check"
,
metavar
=
"GITREF"
,
help
=
(
"Validate the changelog added by a specific git ref, usually HEAD.
\n
"
+
f"Must be a release commit using the '
{
RE_RELEASE_COMMIT_MESSAGE
.
pattern
}
' format.
\n
"
+
'Implies --dry-run and --tag="".'
),
)
parser
.
add_argument
(
"--repo"
,
metavar
=
"REPOSITORY"
,
default
=
getenv
(
"GITHUB_REPOSITORY"
,
DEFAULT_REPO
),
help
=
f"The repository name
\n
Default: env.GITHUB_REPOSITORY or
{
DEFAULT_REPO
}
"
,
)
parser
.
add_argument
(
"--tag"
,
metavar
=
"TAG"
,
help
=
"The tag name
\n
Default: latest tag read from current git branch"
,
)
parser
.
add_argument
(
"--template"
,
metavar
=
"FILE"
,
default
=
ROOT
/
".github"
/
"release_template.md"
,
type
=
Path
,
help
=
"The release template file
\n
Default: $GITROOT/.github/release_template.md"
,
)
parser
.
add_argument
(
"--changelog"
,
metavar
=
"FILE"
,
default
=
ROOT
/
"CHANGELOG.md"
,
type
=
Path
,
help
=
"The changelog file
\n
Default: $GITROOT/CHANGELOG.md"
,
)
parser
.
add_argument
(
"--no-contributors"
,
action
=
"store_true"
,
help
=
"Don't generate contributors list with GitHub usernames"
,
)
parser
.
add_argument
(
"--no-shortlog"
,
action
=
"store_true"
,
help
=
"Don't generate git shortlog"
,
)
parser
.
add_argument
(
"assets"
,
nargs
=
"*"
,
type
=
Path
,
help
=
"List of asset file paths to be uploaded"
,
)
return
parser
.
parse_args
()
Email
=
NewType
(
"Email"
,
str
)
@
dataclass
class
Author
:
email
:
Email
name
:
str
commits
:
int
=
0
class
Git
:
@
staticmethod
def
_output
(
*
gitargs
,
**
runkwargs
)
->
str
:
completedprocess
=
subprocess
.
run
(
[
"git"
,
"--no-pager"
,
*
map
(
str
,
gitargs
)],
capture_output
=
True
,
check
=
True
,
**
runkwargs
,
)
return
completedprocess
.
stdout
.
decode
().
rstrip
()
@
classmethod
def
tag
(
cls
,
ref
:
str
=
"HEAD"
)
->
str
:
try
:
return
cls
.
_output
(
"describe"
,
"--tags"
,
"--first-parent"
,
"--abbrev=0"
,
ref
,
)
except
subprocess
.
CalledProcessError
as
err
:
raise
ValueError
(
f"Could not get tag from git:
\n
{
err
.
stderr
}
"
)
from
err
@
classmethod
def
commit_msg
(
cls
,
ref
:
str
=
"HEAD"
)
->
str
:
try
:
return
cls
.
_output
(
"show"
,
"-s"
,
"--format=%s"
,
ref
,
)
except
subprocess
.
CalledProcessError
as
err
:
raise
ValueError
(
f"Could not get commit message from git:
\n
{
err
.
stderr
}
"
)
from
err
@
classmethod
def
shortlog
(
cls
,
start
:
str
,
end
:
str
)
->
str
:
try
:
return
cls
.
_output
(
"shortlog"
,
"--email"
,
"--no-merges"
,
"--pretty=%s"
,
f"
{
start
}
...
{
end
}
"
,
)
except
subprocess
.
CalledProcessError
as
err
:
raise
ValueError
(
f"Could not get shortlog from git:
\n
{
err
.
stderr
}
"
)
from
err
class
GitHubAPI
:
PER_PAGE
=
100
MAX_REQUESTS
=
10
def
__init__
(
self
,
repo
:
str
,
tag
:
str
,
dry_run
:
bool
=
False
):
self
.
authenticated
=
False
self
.
repo
=
repo
self
.
tag
=
tag
self
.
primary_headers
=
{
"Accept"
:
"application/vnd.github.v3+json"
,
"User-Agent"
:
repo
,
}
if
dry_run
:
log
.
info
(
"dry-run: Not making any GitHub API calls"
)
else
:
self
.
_get_api_key
()
def
_get_api_key
(
self
):
github_token
,
releases_api_key
=
getenv
(
"GITHUB_TOKEN"
),
getenv
(
"RELEASES_API_KEY"
)
# use the GitHub actions token (no authentication check necessary/possible)
if
github_token
:
self
.
primary_headers
.
update
(
Authorization
=
f"Bearer
{
github_token
}
"
)
# use custom user OAuth token (and make sure that it's valid)
elif
releases_api_key
:
self
.
primary_headers
.
update
(
Authorization
=
f"token
{
releases_api_key
}
"
)
res
=
self
.
call
(
endpoint
=
"/user"
,
raise_failure
=
False
)
if
res
.
status_code
>=
400
:
raise
ValueError
(
"Invalid API key"
)
else
:
log
.
info
(
"No API key provided. Continuing with dry-run..."
)
return
self
.
authenticated
=
True
def
call
(
self
,
host
:
str
=
"api.github.com"
,
method
:
Literal
[
"GET"
,
"POST"
,
"PATCH"
,
"DELETE"
]
=
"GET"
,
endpoint
:
str
=
"/"
,
headers
:
dict
[
str
,
Any
]
|
None
=
None
,
raise_failure
:
bool
=
True
,
**
kwargs
,
)
->
requests
.
Response
:
func
:
Callable
=
requests
.
post
if
method
==
"POST"
else
requests
.
patch
if
method
==
"PATCH"
else
requests
.
get
response
:
requests
.
Response
=
func
(
f"https://
{
host
}
{
endpoint
}
"
,
headers
=
{
**
(
headers
or
{}),
**
self
.
primary_headers
},
**
kwargs
,
)
if
raise_failure
and
response
.
status_code
>=
400
:
log
.
debug
(
f"GitHub API request failed:
\n
{
response
.
text
}
"
)
raise
requests
.
HTTPError
(
f"GitHub API request
{
method
}
{
endpoint
}
returned
{
response
.
status_code
}
"
)
return
response
@
staticmethod
def
get_response_json_key
(
response
:
requests
.
Response
,
key
:
str
)
->
Any
:
data
=
response
.
json
()
if
key
not
in
data
:
raise
KeyError
(
f"Missing key '
{
key
}
' in GitHub API response"
)
return
data
[
key
]
def
get_id
(
self
,
response
:
requests
.
Response
)
->
int
:
return
self
.
get_response_json_key
(
response
,
"id"
)
def
get_release_id
(
self
)
->
int
|
None
:
log
.
debug
(
f"Checking for existing release in
{
self
.
repo
}
tagged by
{
self
.
tag
}
"
)
response
=
self
.
call
(
endpoint
=
f"/repos/
{
self
.
repo
}
/releases/tags/
{
self
.
tag
}
"
,
raise_failure
=
False
,
)
return
None
if
response
.
status_code
>=
400
else
self
.
get_id
(
response
)
def
create_release
(
self
,
payload
:
dict
)
->
int
:
if
not
self
.
authenticated
:
log
.
info
(
f"dry-run: Would have created GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
with:
\n
{
pformat
(
payload
)
}
"
)
return
0
log
.
info
(
f"Creating new GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
res
=
self
.
call
(
method
=
"POST"
,
endpoint
=
f"/repos/
{
self
.
repo
}
/releases"
,
json
=
payload
,
)
log
.
info
(
f"Successfully created new GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
return
self
.
get_id
(
res
)
def
update_release
(
self
,
release_id
:
int
,
payload
:
dict
)
->
None
:
if
not
self
.
authenticated
:
log
.
info
(
f"dry-run: Would have updated GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
with:
\n
{
pformat
(
payload
)
}
"
)
return
log
.
info
(
f"Updating existing GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
self
.
call
(
method
=
"PATCH"
,
endpoint
=
f"/repos/
{
self
.
repo
}
/releases/
{
release_id
}
"
,
json
=
payload
,
)
log
.
info
(
f"Successfully updated existing GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
def
delete_release
(
self
,
release_id
:
int
)
->
None
:
if
not
self
.
authenticated
:
log
.
info
(
f"dry-run: Would have deleted GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
return
log
.
info
(
f"Deleting GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
self
.
call
(
method
=
"DELETE"
,
endpoint
=
f"/repos/
{
self
.
repo
}
/releases/
{
release_id
}
"
,
)
log
.
info
(
f"Successfully deleted GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
def
upload_asset
(
self
,
release_id
:
int
,
filename
:
str
,
filehandle
:
IO
):
if
not
self
.
authenticated
:
log
.
info
(
f"dry-run: Would have uploaded '
{
filename
}
' to GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
return
log
.
info
(
f"Uploading '
{
filename
}
' to GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
self
.
call
(
host
=
"uploads.github.com"
,
method
=
"POST"
,
endpoint
=
f"/repos/
{
self
.
repo
}
/releases/
{
release_id
}
/assets"
,
headers
=
{
"Content-Type"
:
"application/octet-stream"
},
params
=
{
"name"
:
filename
},
data
=
filehandle
,
)
log
.
info
(
f"Successfully uploaded '
{
filename
}
' to GitHub release
{
self
.
repo
}
#
{
self
.
tag
}
"
)
def
publish_release
(
self
,
name
:
str
,
body
:
str
,
filehandles
:
Mapping
[
str
,
IO
[
bytes
]]):
release_id
=
self
.
create_release
({
"tag_name"
:
self
.
tag
,
"draft"
:
True
,
"name"
:
name
,
"body"
:
body
,
})
try
:
for
filename
,
filehandle
in
filehandles
.
items
():
self
.
upload_asset
(
release_id
,
filename
,
filehandle
)
self
.
update_release
(
release_id
,
{
"draft"
:
False
},
)
except
requests
.
RequestException
as
err
:
log
.
error
(
f"Unable to publish release:
{
err
}
"
)
self
.
delete_release
(
release_id
)
def
get_contributors
(
self
,
start
:
str
,
end
:
str
)
->
list
[
Author
]:
log
.
debug
(
f"Getting contributors of
{
self
.
repo
}
in commit range
{
start
}
...
{
end
}
"
)
authors
:
dict
[
Email
,
Author
]
=
{}
co_authors
:
list
[
Email
]
=
[]
total_commits
:
int
|
None
=
None
parsed_commits
=
0
page
=
0
while
total_commits
is
None
or
parsed_commits
<
total_commits
:
page
+=
1
res
=
self
.
call
(
endpoint
=
f"/repos/
{
self
.
repo
}
/compare/
{
start
}
...
{
end
}
"
,
params
=
{
"page"
:
page
,
"per_page"
:
self
.
PER_PAGE
,
},
)
if
res
.
status_code
!=
200
:
raise
requests
.
HTTPError
(
f"Status code
{
res
.
status_code
}
for request
{
res
.
url
}
"
)
data
:
dict
=
res
.
json
()
if
total_commits
is
None
:
total_commits
=
data
.
get
(
"total_commits"
)
if
total_commits
is
None
:
raise
ValueError
(
"Could not get total_commits value"
)
if
total_commits
>
self
.
MAX_REQUESTS
*
self
.
PER_PAGE
:
raise
ValueError
(
"Too many commits in input range"
)
commits
:
list
[
dict
]
=
data
.
get
(
"commits"
, [])
parsed_commits
+=
len
(
commits
)
for
commitdata
in
commits
:
commit
=
commitdata
.
get
(
"commit"
)
or
{}
author
=
commitdata
.
get
(
"author"
)
or
{}
# ignore merge commits
if
len
(
commitdata
.
get
(
"parents"
)
or
[])
>
1
:
continue
# ignore bots
if
author
.
get
(
"type"
,
""
).
lower
()
==
"bot"
:
continue
# GitHub identifies its users by checking the commit-author's email address
commit_author_email
=
Email
((
commit
.
get
(
"author"
)
or
{}).
get
(
"email"
,
""
))
# The commit-author's name can differ from the GitHub user account name -> use the provided author login
author_name
:
str
|
None
=
author
.
get
(
"login"
)
if
not
commit_author_email
or
not
author_name
:
continue
if
commit_author_email
not
in
authors
:
authors
[
commit_author_email
]
=
Author
(
commit_author_email
,
author_name
)
authors
[
commit_author_email
].
commits
+=
1
# Co-Author data can be embedded in the commit message
# This data can only be used if the attached email address exists in other commits of the input range, as the
# data is arbitrary and doesn't include GitHub user login names
for
item
in
re
.
finditer
(
RE_CO_AUTHOR
,
commit
.
get
(
"message"
,
""
)):
co_author_email
=
Email
(
item
.
group
(
"email"
))
# Ignore Co-Author data if it's the actual commit-author
if
co_author_email
==
commit_author_email
:
continue
co_authors
.
append
(
co_author_email
)
# Look for any existing commit-author-email-addresses for each co-author
for
email
in
co_authors
:
if
email
in
authors
:
# and increase their commit count by one
authors
[
email
].
commits
+=
1
# sort by commits in descending order and by login name in ascending order
return
sorted
(
sorted
(
authors
.
values
(),
key
=
lambda
author
:
author
.
name
,
reverse
=
False
,
),
key
=
lambda
author
:
author
.
commits
,
reverse
=
True
,
)
class
Release
:
def
__init__
(
self
,
ref
:
str
,
version
:
str
,
template
:
Path
,
changelog
:
Path
):
self
.
ref
=
ref
self
.
version
=
version
self
.
template
=
template
self
.
changelog
=
changelog
@
staticmethod
def
_read_file
(
path
:
Path
)
->
str
:
with
path
.
open
(
"r"
,
encoding
=
"utf-8"
)
as
fh
:
contents
=
fh
.
read
()
if
not
contents
:
raise
OSError
()
return
contents
def
_read_template
(
self
):
log
.
debug
(
f"Opening release template file:
{
self
.
template
}
"
)
try
:
return
self
.
_read_file
(
self
.
template
)
except
OSError
as
err
:
raise
OSError
(
"Missing release template file"
)
from
err
def
_read_changelog
(
self
)
->
str
:
log
.
debug
(
f"Opening changelog file:
{
self
.
changelog
}
"
)
try
:
return
self
.
_read_file
(
self
.
changelog
)
except
OSError
as
err
:
raise
OSError
(
"Missing changelog file"
)
from
err
def
_get_changelog
(
self
)
->
dict
:
changelog
=
self
.
_read_changelog
()
log
.
debug
(
"Parsing changelog file"
)
sections
=
re
.
split
(
RE_CHANGELOG_DELIM
,
changelog
)
num
=
len
(
sections
)
for
idx
,
section
in
enumerate
(
iter
(
sections
)):
if
idx
==
0
:
continue
elif
idx
==
num
-
1
:
# workaround for whitespace at EOF,
# so we can have a sensible error message on missing changelog for current tag
section
+=
"
\n
"
if
not
(
match
:=
re
.
search
(
RE_CHANGELOG_CONTENT
,
section
)):
raise
ValueError
(
f"Invalid changelog format:
\n
{
section
}
"
)
if
match
.
group
(
"version"
)
==
self
.
version
:
return
match
.
groupdict
()
raise
KeyError
(
f"Missing changelog for release
{
self
.
version
}
"
)
@
staticmethod
@
contextmanager
def
get_file_handles
(
assets
:
list
[
Path
])
->
Generator
[
Mapping
[
str
,
IO
[
bytes
]],
None
,
None
]:
handles
=
{}
try
:
for
asset
in
assets
:
asset
=
ROOT
/
asset
if
not
asset
.
is_file
():
continue
log
.
info
(
f"Found release asset '
{
asset
.
name
}
'"
)
handles
[
asset
.
name
]
=
asset
.
open
(
"rb"
)
yield
handles
finally
:
for
handle
in
handles
.
values
():
handle
.
close
()
def
get_body
(
self
,
api
:
GitHubAPI
,
no_contributors
:
bool
=
False
,
no_shortlog
:
bool
=
False
,
)
->
str
:
template
=
self
.
_read_template
()
jinjatemplate
=
jinja2
.
Template
(
template
)
changelog
=
self
.
_get_changelog
()
context
=
dict
(
**
changelog
)
if
not
no_contributors
or
not
no_shortlog
:
# don't include the tagged release commit
prev_commit
=
f"
{
self
.
ref
}
~1"
# get the previous tag
start
=
Git
.
tag
(
prev_commit
)
if
not
start
:
raise
ValueError
(
f"Could not resolve tag from reference
{
prev_commit
}
"
)
if
not
no_contributors
:
context
.
update
(
contributors
=
api
.
get_contributors
(
start
,
prev_commit
),
)
if
not
no_shortlog
:
context
.
update
(
gitshortlog
=
Git
.
shortlog
(
start
,
prev_commit
),
)
return
jinjatemplate
.
render
(
context
)
def
main
()
->
None
:
args
:
argparse
.
Namespace
=
get_args
()
logging
.
basicConfig
(
level
=
logging
.
DEBUG
if
args
.
debug
else
logging
.
INFO
,
format
=
"[%(levelname)s] %(message)s"
,
)
dry_run
=
args
.
dry_run
if
args
.
check
:
dry_run
=
True
commit_msg
=
Git
.
commit_msg
(
args
.
check
)
if
not
(
match
:=
RE_RELEASE_COMMIT_MESSAGE
.
search
(
commit_msg
)):
log
.
warning
(
f"Git ref '
{
args
.
check
}
' is not a release commit, exiting..."
)
log
.
warning
(
commit_msg
)
return
ref
=
args
.
check
version
=
match
.
group
(
"version"
)
else
:
ref
=
version
=
args
.
tag
or
Git
.
tag
()
if
not
ref
:
raise
ValueError
(
"Missing git tag"
)
log
.
info
(
f"Repo:
{
args
.
repo
}
"
)
log
.
info
(
f"Ref:
{
ref
}
"
)
log
.
info
(
f"Version:
{
version
}
"
)
release
=
Release
(
ref
,
version
,
args
.
template
,
args
.
changelog
)
# get file handles of release assets first, to prevent unnecessary API requests if input files can't be found
with
release
.
get_file_handles
(
args
.
assets
)
as
filehandles
:
# initialize GitHub API
api
=
GitHubAPI
(
args
.
repo
,
ref
,
dry_run
=
dry_run
)
# prepare the release body with the changelog, contributors list and git shortlog
body
=
release
.
get_body
(
api
,
args
.
no_contributors
,
args
.
no_shortlog
)
# publish the new release
api
.
publish_release
(
name
=
f"Streamlink
{
version
}
"
,
body
=
body
,
filehandles
=
filehandles
,
)
log
.
info
(
"Done"
)
if
__name__
==
"__main__"
:
# noinspection PyBroadException
try
:
main
()
except
KeyboardInterrupt
:
sys
.
exit
(
130
)
except
Exception
:
log
.
exception
(
"Error"
,
exc_info
=
True
)
sys
.
exit
(
1
)
else
:
sys
.
exit
(
0
)
Back
|
FazBrowse Home
|
New Git URL