kettle-dev 2.5.14 → 2.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +32 -1
- data/README.md +3 -3
- data/exe/kettle-changelog +8 -2
- data/exe/kettle-gh-release +80 -10
- data/lib/kettle/dev/changelog_cli.rb +138 -14
- data/lib/kettle/dev/changelog_entry_adder.rb +2 -2
- data/lib/kettle/dev/release_cli.rb +125 -8
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +10 -10
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 561f0712647c4d27b03caa44fe76613f2d68602b056d2e0ada052c6c13334f51
|
|
4
|
+
data.tar.gz: 148358784c948bfa8368b36e683834e21636277539ad1b084450829a57721af7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db47e6daf32deff2da6749a388a8c802e65e492ce63d35f0cd606f8cdc8ddf73ab4fda2d137c0c834bff64043f3739440bae25b567391838f43eaf376c0031d9
|
|
7
|
+
data.tar.gz: d40265dd3c8eb72654de1cec852753e2f6deaa5f996809ad348b4ae90d00d65ad70a5fb24a82570e30764bd9425ebc23e394bf2bdfee9d47f8939a6efb635559
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,35 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [2.5.15] - 2026-08-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.5.15][2.5.15t]
|
|
36
|
+
- COVERAGE: 91.55% -- 5266/5752 lines in 46 files
|
|
37
|
+
- BRANCH COVERAGE: 75.74% -- 1986/2622 branches in 46 files
|
|
38
|
+
- 50.41% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Add verified GitHub Release backfill checks and NDJSON support to kettle-gh-release.
|
|
43
|
+
|
|
44
|
+
- Add a changelog reformat mode for historical release metadata repair.
|
|
45
|
+
|
|
46
|
+
- Add historical changelog reformat and GitHub release reconciliation checks.
|
|
47
|
+
|
|
48
|
+
- Add an explicit historical release-section backfill mode for tagged releases.
|
|
49
|
+
|
|
50
|
+
- Add kettle-gh-release update mode for existing GitHub Release notes.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- Allow the changelog entry writer to add to malformed Unreleased sections with duplicate headings.
|
|
55
|
+
|
|
56
|
+
- Honor K_CHANGELOG_PATH when adding an Unreleased changelog entry.
|
|
57
|
+
|
|
58
|
+
- Canonicalize legacy release headings and remove redundant historical placeholders during reformat.
|
|
59
|
+
|
|
60
|
+
- Roll back unpublished prepared changelog sections when version.rb returns to the published version.
|
|
61
|
+
|
|
33
62
|
## [2.5.14] - 2026-07-31
|
|
34
63
|
|
|
35
64
|
- TAG: [v2.5.14][2.5.14t]
|
|
@@ -3173,7 +3202,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3173
3202
|
- Selecting will run the selected workflow via `act`
|
|
3174
3203
|
- This may move to its own gem in the future.
|
|
3175
3204
|
|
|
3176
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.
|
|
3205
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.15...HEAD
|
|
3206
|
+
[2.5.15]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.14...v2.5.15
|
|
3207
|
+
[2.5.15t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.15
|
|
3177
3208
|
[2.5.14]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.13...v2.5.14
|
|
3178
3209
|
[2.5.14t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.14
|
|
3179
3210
|
[2.5.13]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.12...v2.5.13
|
data/README.md
CHANGED
|
@@ -849,8 +849,8 @@ Thanks for RTFM. ☺️
|
|
|
849
849
|
[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
|
|
850
850
|
[✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
|
|
851
851
|
[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
|
|
852
|
-
[✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https
|
|
853
|
-
[✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https
|
|
852
|
+
[✉️ruby-forum-top-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=flat&logo=discourse&label=Ruby%20Users%20Forum
|
|
853
|
+
[✉️ruby-forum-img]: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fwww.rubyforum.org&style=for-the-badge&logo=discourse&label=Ruby%20Users%20Forum
|
|
854
854
|
[✉️ruby-forum]: https://www.rubyforum.org/tag/kettle-dev
|
|
855
855
|
|
|
856
856
|
[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
|
|
@@ -1005,7 +1005,7 @@ Thanks for RTFM. ☺️
|
|
|
1005
1005
|
[📌gitmoji]: https://gitmoji.dev
|
|
1006
1006
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1007
1007
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1008
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-5.
|
|
1008
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-5.752-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1009
1009
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
1010
1010
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1011
1011
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/exe/kettle-changelog
CHANGED
|
@@ -63,7 +63,7 @@ end
|
|
|
63
63
|
|
|
64
64
|
if ARGV.include?("-h") || ARGV.include?("--help")
|
|
65
65
|
puts <<~USAGE
|
|
66
|
-
Usage: kettle-changelog [--version VERSION] [--yes] [--update-prep] [--pending-release] [--release-state] [--refresh-cache] [--add-unreleased-entry --section SECTION --entry TEXT] [--json|--events[=TYPES]] [--no-strict] [--no-coverage-threshold]
|
|
66
|
+
Usage: kettle-changelog [--version VERSION] [--yes] [--update-prep|--reformat|--backfill-released-version VERSION] [--pending-release] [--release-state] [--refresh-cache] [--add-unreleased-entry --section SECTION --entry TEXT] [--json|--events[=TYPES]] [--no-strict] [--no-coverage-threshold]
|
|
67
67
|
|
|
68
68
|
Detects the current version from lib/**/version.rb, the latest live release, and
|
|
69
69
|
the most recent CHANGELOG.md release section, then prompts to confirm the selected plan:
|
|
@@ -76,6 +76,9 @@ if ARGV.include?("-h") || ARGV.include?("--help")
|
|
|
76
76
|
--version VERSION Use this version instead of detecting VERSION from lib/**/version.rb
|
|
77
77
|
--yes Auto-approve the selected release plan
|
|
78
78
|
--update-prep Force updating the most recent prepared release section in place
|
|
79
|
+
--reformat Normalize CHANGELOG.md structure without release-state planning
|
|
80
|
+
--backfill-released-version VERSION
|
|
81
|
+
Add a tagged historical section without moving Unreleased entries
|
|
79
82
|
--pending-release Query whether the changelog has release work pending; exits 0 for yes, 1 for no
|
|
80
83
|
--release-state,
|
|
81
84
|
--release-status Print changelog release state, including latest published release and pending sources
|
|
@@ -159,6 +162,7 @@ begin
|
|
|
159
162
|
# Determine if strict mode is enabled (default: true)
|
|
160
163
|
strict_mode = !ARGV.include?("--no-strict") && ENV.fetch("K_CHANGELOG_STRICT", "true").downcase != "false"
|
|
161
164
|
update_prep = ARGV.delete("--update-prep")
|
|
165
|
+
reformat_only = ARGV.delete("--reformat")
|
|
162
166
|
yes = !!ARGV.delete("--yes")
|
|
163
167
|
pending_release_query = ARGV.delete("--pending-release")
|
|
164
168
|
release_state_query = ARGV.delete("--release-state") || ARGV.delete("--release-status")
|
|
@@ -189,6 +193,8 @@ begin
|
|
|
189
193
|
value
|
|
190
194
|
end
|
|
191
195
|
|
|
196
|
+
historical_backfill_version = extract_option_arg!(ARGV, "--backfill-released-version")
|
|
197
|
+
|
|
192
198
|
if add_unreleased_entry
|
|
193
199
|
section = extract_option_arg!(ARGV, "--section")
|
|
194
200
|
entry = extract_option_arg!(ARGV, "--entry")
|
|
@@ -202,7 +208,7 @@ begin
|
|
|
202
208
|
end
|
|
203
209
|
|
|
204
210
|
event_stream = events_output ? Kettle::Ndjson.event_stream($stdout, types: event_types) : nil
|
|
205
|
-
cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, version: version_override, refresh_cache: refresh_cache, yes: yes, event_stream: event_stream)
|
|
211
|
+
cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, reformat_only: reformat_only, historical_backfill_version: historical_backfill_version, version: version_override, refresh_cache: refresh_cache, yes: yes, event_stream: event_stream)
|
|
206
212
|
if release_state_query
|
|
207
213
|
state = cli.release_state
|
|
208
214
|
puts(json_output ? JSON.pretty_generate(state) : cli.release_state_table(state))
|
data/exe/kettle-gh-release
CHANGED
|
@@ -39,7 +39,7 @@ end
|
|
|
39
39
|
# Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
|
|
40
40
|
if ARGV.include?("-h") || ARGV.include?("--help")
|
|
41
41
|
puts <<~USAGE
|
|
42
|
-
Usage: #{script_basename} [version=<VERSION>]
|
|
42
|
+
Usage: #{script_basename} [--release-version VERSION|version=<VERSION>] [--check|--update] [--events] [--allow-unpublished]
|
|
43
43
|
|
|
44
44
|
Creates a GitHub release for a gem version.
|
|
45
45
|
|
|
@@ -48,29 +48,99 @@ if ARGV.include?("-h") || ARGV.include?("--help")
|
|
|
48
48
|
failed (e.g., due to token issues or network problems).
|
|
49
49
|
|
|
50
50
|
Arguments:
|
|
51
|
-
version
|
|
52
|
-
|
|
51
|
+
--release-version VERSION # Explicit version (e.g., --release-version 1.2.3)
|
|
52
|
+
version=<VERSION> # Backward-compatible explicit version form
|
|
53
|
+
--check # Verify RubyGems, remote tag, changelog, and token without creating a release
|
|
54
|
+
--update # Update an existing GitHub Release title/body from CHANGELOG.md
|
|
55
|
+
--events # Emit the final result as one NDJSON event
|
|
56
|
+
--allow-unpublished # Skip the RubyGems publication requirement (for intentionally non-RubyGems projects)
|
|
53
57
|
|
|
54
58
|
Environment:
|
|
55
59
|
GITHUB_TOKEN / GH_TOKEN # Required; needs repo:public_repo (classic) or contents:write scope
|
|
56
60
|
DEBUG=true # Print backtraces on errors
|
|
57
61
|
|
|
58
62
|
Examples:
|
|
59
|
-
#{script_basename}
|
|
60
|
-
#{script_basename} version
|
|
61
|
-
|
|
63
|
+
#{script_basename} --check --release-version 2.0.0
|
|
64
|
+
#{script_basename} --release-version 2.0.0
|
|
65
|
+
#{script_basename} version=2.0.0 # backward-compatible form
|
|
62
66
|
USAGE
|
|
63
67
|
exit 0
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
check_only = ARGV.delete("--check")
|
|
71
|
+
update_only = ARGV.delete("--update")
|
|
72
|
+
if check_only && update_only
|
|
73
|
+
warn("#{script_basename}: --check and --update cannot be combined")
|
|
74
|
+
exit 1
|
|
75
|
+
end
|
|
76
|
+
events = ARGV.delete("--events")
|
|
77
|
+
allow_unpublished = ARGV.delete("--allow-unpublished")
|
|
78
|
+
version_arg = ARGV.find { |arg| arg.start_with?("version=") }
|
|
79
|
+
ARGV.delete(version_arg) if version_arg
|
|
80
|
+
explicit_version = version_arg&.split("=", 2)&.last&.strip
|
|
81
|
+
|
|
82
|
+
if (index = ARGV.index("--release-version"))
|
|
83
|
+
explicit_version = ARGV.fetch(index + 1, nil).to_s.strip
|
|
84
|
+
ARGV.slice!(index, 2)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless ARGV.empty?
|
|
88
|
+
warn("#{script_basename}: unexpected argument(s): #{ARGV.join(" ")}")
|
|
89
|
+
exit 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if !explicit_version.nil? && explicit_version.empty?
|
|
93
|
+
warn("#{script_basename}: --release-version requires a version")
|
|
94
|
+
exit 1
|
|
95
|
+
end
|
|
69
96
|
|
|
70
97
|
begin
|
|
71
98
|
cli = Kettle::Dev::ReleaseCLI.new(start_step: 18)
|
|
72
99
|
version = explicit_version || cli.send(:detect_version)
|
|
73
|
-
cli.send(:
|
|
100
|
+
result = cli.send(:github_release_backfill_check, version, require_rubygems: !allow_unpublished)
|
|
101
|
+
|
|
102
|
+
if events
|
|
103
|
+
action = if check_only
|
|
104
|
+
"check"
|
|
105
|
+
elsif update_only
|
|
106
|
+
"update"
|
|
107
|
+
else
|
|
108
|
+
"create"
|
|
109
|
+
end
|
|
110
|
+
puts JSON.generate(
|
|
111
|
+
event_version: 1,
|
|
112
|
+
type: "github_release",
|
|
113
|
+
action: action,
|
|
114
|
+
status: result.fetch("ok") ? "ok" : "failed",
|
|
115
|
+
**result
|
|
116
|
+
)
|
|
117
|
+
elsif result.fetch("ok")
|
|
118
|
+
puts "GitHub release prerequisites satisfied for #{result.fetch("tag")}."
|
|
119
|
+
else
|
|
120
|
+
warn("GitHub release prerequisites failed for #{result.fetch("tag")}:")
|
|
121
|
+
result.fetch("diagnostics").each { |diagnostic| warn(" - #{diagnostic}") }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
exit 1 unless result.fetch("ok")
|
|
125
|
+
exit 0 if check_only
|
|
126
|
+
|
|
127
|
+
created, message = if update_only
|
|
128
|
+
cli.send(:maybe_update_github_release!, version)
|
|
129
|
+
else
|
|
130
|
+
cli.send(:maybe_create_github_release!, version)
|
|
131
|
+
end
|
|
132
|
+
if events
|
|
133
|
+
puts JSON.generate(
|
|
134
|
+
event_version: 1,
|
|
135
|
+
type: "github_release",
|
|
136
|
+
action: update_only ? "update" : "create",
|
|
137
|
+
status: created ? "ok" : "failed",
|
|
138
|
+
version: result.fetch("version"),
|
|
139
|
+
tag: result.fetch("tag"),
|
|
140
|
+
result: message
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
exit(created ? 0 : 1)
|
|
74
144
|
rescue LoadError => e
|
|
75
145
|
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
|
76
146
|
warn(Array(e.backtrace).join("\n")) if ENV["DEBUG"]
|
|
@@ -27,9 +27,11 @@ module Kettle
|
|
|
27
27
|
# @param strict [Boolean] when true (default), require coverage and yard data; raise errors if unavailable
|
|
28
28
|
# @param enforce_coverage_thresholds [Boolean] when true, fail strict coverage generation below project thresholds
|
|
29
29
|
# @param update_prep [Boolean] when true, update the most recent prepared release section in place
|
|
30
|
+
# @param reformat_only [Boolean] when true, normalize structure without release-state planning
|
|
31
|
+
# @param historical_backfill_version [String, nil] tagged release to document without moving Unreleased entries
|
|
30
32
|
# @param version [String, nil] explicit version override for gems without a literal VERSION constant
|
|
31
33
|
# @param yes [Boolean] when true, approve the selected release plan without prompting
|
|
32
|
-
def initialize(strict: true, enforce_coverage_thresholds: true, update_prep: false, version: nil, root: Kettle::Dev::CIHelpers.project_root, refresh_cache: false, yes: false, event_stream: nil)
|
|
34
|
+
def initialize(strict: true, enforce_coverage_thresholds: true, update_prep: false, reformat_only: false, historical_backfill_version: nil, version: nil, root: Kettle::Dev::CIHelpers.project_root, refresh_cache: false, yes: false, event_stream: nil)
|
|
33
35
|
@root = root
|
|
34
36
|
@changelog_path = resolved_changelog_path
|
|
35
37
|
@coverage_root = resolved_coverage_root
|
|
@@ -37,6 +39,8 @@ module Kettle
|
|
|
37
39
|
@strict = strict
|
|
38
40
|
@enforce_coverage_thresholds = enforce_coverage_thresholds
|
|
39
41
|
@update_prep = update_prep
|
|
42
|
+
@reformat_only = reformat_only
|
|
43
|
+
@historical_backfill_version = Kettle::Dev::Versioning.normalize_explicit_version(historical_backfill_version)
|
|
40
44
|
@version_override = Kettle::Dev::Versioning.normalize_explicit_version(version)
|
|
41
45
|
@refresh_cache = refresh_cache
|
|
42
46
|
@yes = !!yes
|
|
@@ -50,6 +54,17 @@ module Kettle
|
|
|
50
54
|
#
|
|
51
55
|
# @return [void]
|
|
52
56
|
def run
|
|
57
|
+
if @reformat_only
|
|
58
|
+
reformat_changelog!(File.read(@changelog_path))
|
|
59
|
+
emit_changelog_event(action: "reformat", status: "ok", plan: "reformat_only")
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if @historical_backfill_version
|
|
64
|
+
backfill_historical_release!(@historical_backfill_version)
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
53
68
|
version = detect_version
|
|
54
69
|
today = Time.now.strftime("%Y-%m-%d")
|
|
55
70
|
owner, repo = Kettle::Dev::CIHelpers.repo_info
|
|
@@ -67,6 +82,12 @@ module Kettle
|
|
|
67
82
|
return
|
|
68
83
|
end
|
|
69
84
|
|
|
85
|
+
if plan.fetch(:action) == :rollback_prepared_release
|
|
86
|
+
rollback_prepared_release!(changelog, owner, repo, version)
|
|
87
|
+
emit_changelog_event(action: "rollback", status: "ok", version: plan.fetch(:latest_changelog_version), plan: "rollback_prepared_release")
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
70
91
|
line_cov_line, branch_cov_line = coverage_lines
|
|
71
92
|
yard_line = yard_percent_documented
|
|
72
93
|
|
|
@@ -300,21 +321,23 @@ module Kettle
|
|
|
300
321
|
abort("Aborting: version.rb (#{version}) is lower than the latest released version for this release line (#{latest_target}).")
|
|
301
322
|
end
|
|
302
323
|
|
|
303
|
-
if
|
|
324
|
+
if latest_target == version && latest_changelog_version && Gem::Version.new(latest_changelog_version) > Gem::Version.new(version)
|
|
325
|
+
action = :rollback_prepared_release
|
|
326
|
+
elsif section_exists && latest_changelog_version != version
|
|
304
327
|
abort("Aborting: CHANGELOG.md already contains a #{version} section, but the most recent release section is #{latest_changelog_version || "missing"}.")
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
action = if section_exists && latest_target == version
|
|
308
|
-
if unreleased_block_has_entries?(unreleased_block)
|
|
309
|
-
abort("Aborting: version.rb (#{version}) matches the latest released version for this release line (#{latest_target}); bump version.rb before moving Unreleased entries into a release section.")
|
|
310
|
-
end
|
|
311
|
-
:reformat_only
|
|
312
|
-
elsif section_exists
|
|
313
|
-
:update_prepared_release
|
|
314
|
-
elsif latest_target == version
|
|
315
|
-
abort("Aborting: version.rb (#{version}) matches the latest released version, but CHANGELOG.md does not have #{version} as the most recent release section.")
|
|
316
328
|
else
|
|
317
|
-
|
|
329
|
+
action = if section_exists && latest_target == version
|
|
330
|
+
if unreleased_block_has_entries?(unreleased_block)
|
|
331
|
+
abort("Aborting: version.rb (#{version}) matches the latest released version for this release line (#{latest_target}); bump version.rb before moving Unreleased entries into a release section.")
|
|
332
|
+
end
|
|
333
|
+
:reformat_only
|
|
334
|
+
elsif section_exists
|
|
335
|
+
:update_prepared_release
|
|
336
|
+
elsif latest_target == version
|
|
337
|
+
abort("Aborting: version.rb (#{version}) matches the latest released version, but CHANGELOG.md does not have #{version} as the most recent release section.")
|
|
338
|
+
else
|
|
339
|
+
:new_release
|
|
340
|
+
end
|
|
318
341
|
end
|
|
319
342
|
|
|
320
343
|
{
|
|
@@ -396,6 +419,8 @@ module Kettle
|
|
|
396
419
|
|
|
397
420
|
def reformat_changelog!(changelog)
|
|
398
421
|
updated = convert_heading_tag_suffix_to_list(changelog)
|
|
422
|
+
updated = normalize_legacy_release_headings(updated)
|
|
423
|
+
updated = remove_redundant_historical_placeholders(updated)
|
|
399
424
|
updated = normalize_heading_spacing(updated)
|
|
400
425
|
updated = ensure_footer_spacing(updated)
|
|
401
426
|
updated = updated.rstrip + "\n"
|
|
@@ -403,6 +428,86 @@ module Kettle
|
|
|
403
428
|
puts "CHANGELOG.md reformatted. No new version section added."
|
|
404
429
|
end
|
|
405
430
|
|
|
431
|
+
def normalize_legacy_release_headings(changelog)
|
|
432
|
+
changelog.to_s.lines.map do |line|
|
|
433
|
+
match = line.match(/^##\s+(?!\[)(v?\d+(?:\.\d+)+(?:[.-][0-9A-Za-z]+)?)(\s+-\s+.*)?\s*$/)
|
|
434
|
+
next line unless match
|
|
435
|
+
|
|
436
|
+
version = match[1].delete_prefix("v")
|
|
437
|
+
next line unless Gem::Version.correct?(version)
|
|
438
|
+
|
|
439
|
+
"## [#{version}]#{match[2]}\n"
|
|
440
|
+
end.join
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def remove_redundant_historical_placeholders(changelog)
|
|
444
|
+
lines = changelog.to_s.lines
|
|
445
|
+
sections = changelog_release_sections(lines)
|
|
446
|
+
removals = sections.group_by { |section| section.fetch(:version) }.values.flat_map do |same_version|
|
|
447
|
+
next [] unless same_version.size > 1
|
|
448
|
+
next [] unless same_version.any? { |section| !section.fetch(:placeholder) }
|
|
449
|
+
|
|
450
|
+
same_version.filter_map do |section|
|
|
451
|
+
(section.fetch(:start)...section.fetch(:finish)) if section.fetch(:placeholder)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
return changelog if removals.empty?
|
|
455
|
+
|
|
456
|
+
lines.each_with_index.reject { |_line, index| removals.any? { |range| range.cover?(index) } }.map(&:first).join
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def changelog_release_sections(lines)
|
|
460
|
+
headings = lines.each_index.filter_map do |index|
|
|
461
|
+
match = lines[index].match(/^## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]/o)
|
|
462
|
+
{version: match[1], start: index} if match
|
|
463
|
+
end
|
|
464
|
+
headings.each_with_index.map do |heading, index|
|
|
465
|
+
next_heading = headings[index + 1]
|
|
466
|
+
footer = lines.each_index.find do |line_index|
|
|
467
|
+
line_index > heading.fetch(:start) && lines[line_index].start_with?(UNRELEASED_SECTION_HEADING)
|
|
468
|
+
end
|
|
469
|
+
finish = [next_heading&.fetch(:start), footer, lines.length].compact.min
|
|
470
|
+
body = lines[heading.fetch(:start)...finish].join
|
|
471
|
+
heading.merge(finish: finish, placeholder: body.include?("Historical release notes are unavailable in this changelog."))
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def backfill_historical_release!(version)
|
|
476
|
+
changelog = File.read(@changelog_path)
|
|
477
|
+
abort("CHANGELOG.md already contains a section for #{version}") if release_section_exists?(changelog, version)
|
|
478
|
+
|
|
479
|
+
unreleased_block, before, after = extract_unreleased(changelog)
|
|
480
|
+
abort("Could not find '## [Unreleased]' section in CHANGELOG.md") if unreleased_block.nil?
|
|
481
|
+
|
|
482
|
+
date = tagged_release_date(version)
|
|
483
|
+
owner, repo = Kettle::Dev::CIHelpers.repo_info
|
|
484
|
+
abort("Could not determine GitHub owner/repo from origin remote") unless owner && repo
|
|
485
|
+
|
|
486
|
+
section = <<~MD
|
|
487
|
+
## [#{version}] - #{date}
|
|
488
|
+
- TAG: [v#{version}][#{version}t]
|
|
489
|
+
|
|
490
|
+
### Changed
|
|
491
|
+
|
|
492
|
+
- Historical release notes are unavailable in this changelog.
|
|
493
|
+
|
|
494
|
+
MD
|
|
495
|
+
updated = before + "## [Unreleased]\n" + unreleased_block.rstrip + "\n\n" + section + after.to_s
|
|
496
|
+
updated = update_link_refs(updated, owner, repo, nil, version)
|
|
497
|
+
updated = normalize_heading_spacing(updated).rstrip + "\n"
|
|
498
|
+
File.write(@changelog_path, updated)
|
|
499
|
+
emit_changelog_event(action: "backfill", status: "ok", version: version, plan: "historical_release")
|
|
500
|
+
puts "CHANGELOG.md backfilled with historical v#{version} section. Unreleased entries were preserved."
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def tagged_release_date(version)
|
|
504
|
+
tag = "v#{version}"
|
|
505
|
+
output, status = Open3.capture2("git", "log", "-1", "--format=%as", tag, chdir: @root)
|
|
506
|
+
abort("Local git tag #{tag} is required to backfill a historical release section") unless status.success? && !output.strip.empty?
|
|
507
|
+
|
|
508
|
+
output.strip
|
|
509
|
+
end
|
|
510
|
+
|
|
406
511
|
def release_section_exists?(changelog, version)
|
|
407
512
|
changelog.match?(/^## \[#{Regexp.escape(version)}\]/)
|
|
408
513
|
end
|
|
@@ -562,6 +667,25 @@ module Kettle
|
|
|
562
667
|
puts "CHANGELOG.md updated in place for v#{prepared_version}."
|
|
563
668
|
end
|
|
564
669
|
|
|
670
|
+
def rollback_prepared_release!(changelog, owner, repo, published_version)
|
|
671
|
+
unreleased_block, before, after = extract_unreleased(changelog)
|
|
672
|
+
release_lines = after.to_s.lines
|
|
673
|
+
heading = release_lines.first.to_s.match(/\A## \[(#{CHANGELOG_VERSION_PATTERN_SOURCE})\]/o)
|
|
674
|
+
abort("Could not find a prepared release section after '## [Unreleased]' in CHANGELOG.md") unless heading
|
|
675
|
+
|
|
676
|
+
finish = release_lines.each_index.drop(1).find do |index|
|
|
677
|
+
release_lines[index].start_with?("## [", UNRELEASED_SECTION_HEADING)
|
|
678
|
+
end || release_lines.length
|
|
679
|
+
prepared_version = heading[1]
|
|
680
|
+
merged = merge_release_body_with_unreleased(release_lines[1...finish].join, unreleased_block)
|
|
681
|
+
unreleased = "## [Unreleased]\n" + merged.rstrip + "\n\n"
|
|
682
|
+
tail = release_lines[finish, release_lines.length].to_a.reject { |line| line.start_with?("[#{prepared_version}]:", "[#{prepared_version}t]:") }.join
|
|
683
|
+
updated = before + unreleased + tail
|
|
684
|
+
updated = update_link_refs(updated, owner, repo, nil, published_version)
|
|
685
|
+
File.write(@changelog_path, normalize_heading_spacing(updated).rstrip + "\n")
|
|
686
|
+
puts "CHANGELOG.md rolled back prepared v#{prepared_version} into Unreleased."
|
|
687
|
+
end
|
|
688
|
+
|
|
565
689
|
def merge_release_body_with_unreleased(release_body, unreleased_block)
|
|
566
690
|
existing = strip_release_metadata(release_body)
|
|
567
691
|
incoming = filter_unreleased_sections(unreleased_block)
|
|
@@ -9,7 +9,7 @@ module Kettle
|
|
|
9
9
|
@root = root
|
|
10
10
|
@section = section.to_s
|
|
11
11
|
@entry = normalize_entry(entry)
|
|
12
|
-
@changelog_path = File.join(@root, "CHANGELOG.md")
|
|
12
|
+
@changelog_path = ENV.fetch("K_CHANGELOG_PATH", File.join(@root, "CHANGELOG.md"))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def run
|
|
@@ -68,7 +68,7 @@ module Kettle
|
|
|
68
68
|
owner.location.start_line > unreleased.location.start_line &&
|
|
69
69
|
owner.location.end_line <= unreleased.location.end_line
|
|
70
70
|
end
|
|
71
|
-
raise Error, "expected
|
|
71
|
+
raise Error, "expected at least one #{heading(3, @section)} section under ## [Unreleased] in CHANGELOG.md, found none" if matches.empty?
|
|
72
72
|
|
|
73
73
|
matches.first
|
|
74
74
|
end
|
|
@@ -436,7 +436,8 @@ module Kettle
|
|
|
436
436
|
# 18. create GitHub release (optional)
|
|
437
437
|
if run_step?(18)
|
|
438
438
|
version ||= detect_version
|
|
439
|
-
maybe_create_github_release!(version)
|
|
439
|
+
created, message = maybe_create_github_release!(version)
|
|
440
|
+
abort("GitHub release creation failed: #{message}") if github_release_required? && !created
|
|
440
441
|
end
|
|
441
442
|
|
|
442
443
|
# 19. push tags to remotes (final step)
|
|
@@ -2340,24 +2341,27 @@ module Kettle
|
|
|
2340
2341
|
# Title: v<version>
|
|
2341
2342
|
# Body: the CHANGELOG section for this version, followed by the two link references for this version.
|
|
2342
2343
|
def maybe_create_github_release!(version)
|
|
2343
|
-
token =
|
|
2344
|
+
token = github_token
|
|
2344
2345
|
if token.empty?
|
|
2345
|
-
|
|
2346
|
-
|
|
2346
|
+
message = "GITHUB_TOKEN or GH_TOKEN is not set; skipping GitHub release creation. Set a token with repo:public_repo (classic) or contents:write scope."
|
|
2347
|
+
warn(message)
|
|
2348
|
+
return [false, message]
|
|
2347
2349
|
end
|
|
2348
2350
|
|
|
2349
2351
|
gh_remote = preferred_github_remote
|
|
2350
2352
|
url = remote_url(gh_remote || "origin")
|
|
2351
2353
|
owner, repo = parse_github_owner_repo(url)
|
|
2352
2354
|
unless owner && repo
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
+
message = "GitHub token present but could not determine GitHub owner/repo from remotes. Skipping release creation."
|
|
2356
|
+
warn(message)
|
|
2357
|
+
return [false, message]
|
|
2355
2358
|
end
|
|
2356
2359
|
|
|
2357
2360
|
section, compare_ref, tag_ref = extract_changelog_for_version(version)
|
|
2358
2361
|
unless section
|
|
2359
|
-
|
|
2360
|
-
|
|
2362
|
+
message = "CHANGELOG.md does not contain a section for #{version}. Skipping GitHub release creation."
|
|
2363
|
+
warn(message)
|
|
2364
|
+
return [false, message]
|
|
2361
2365
|
end
|
|
2362
2366
|
|
|
2363
2367
|
body = +""
|
|
@@ -2377,6 +2381,97 @@ module Kettle
|
|
|
2377
2381
|
else
|
|
2378
2382
|
warn("GitHub release creation skipped/failed: #{msg}")
|
|
2379
2383
|
end
|
|
2384
|
+
[ok, msg]
|
|
2385
|
+
end
|
|
2386
|
+
|
|
2387
|
+
def maybe_update_github_release!(version)
|
|
2388
|
+
token = github_token
|
|
2389
|
+
return [false, "GITHUB_TOKEN or GH_TOKEN is not set"] if token.empty?
|
|
2390
|
+
|
|
2391
|
+
owner, repo = parse_github_owner_repo(remote_url(preferred_github_remote || "origin"))
|
|
2392
|
+
return [false, "could not determine GitHub owner/repo from remotes"] unless owner && repo
|
|
2393
|
+
|
|
2394
|
+
body = github_release_body(version)
|
|
2395
|
+
return [false, "CHANGELOG.md does not contain a section for #{version}"] unless body
|
|
2396
|
+
|
|
2397
|
+
github_update_release(owner: owner, repo: repo, token: token, tag: "v#{version}", title: "v#{version}", body: body)
|
|
2398
|
+
end
|
|
2399
|
+
|
|
2400
|
+
def github_release_body(version)
|
|
2401
|
+
section, compare_ref, tag_ref = extract_changelog_for_version(version)
|
|
2402
|
+
return unless section
|
|
2403
|
+
|
|
2404
|
+
body = +section.rstrip
|
|
2405
|
+
body << "\n\n" << compare_ref if compare_ref
|
|
2406
|
+
body << tag_ref if tag_ref
|
|
2407
|
+
footer = extract_release_notes_footer
|
|
2408
|
+
body << "\n" << footer if footer && !footer.strip.empty?
|
|
2409
|
+
body
|
|
2410
|
+
end
|
|
2411
|
+
|
|
2412
|
+
# Validate the immutable inputs required to backfill a GitHub Release.
|
|
2413
|
+
# This must never create a tag or publish a gem as a side effect.
|
|
2414
|
+
def github_release_backfill_check(version, require_rubygems: true)
|
|
2415
|
+
normalized_version = version.to_s.delete_prefix("v")
|
|
2416
|
+
tag = "v#{normalized_version}"
|
|
2417
|
+
diagnostics = []
|
|
2418
|
+
token = github_token
|
|
2419
|
+
diagnostics << "GITHUB_TOKEN is not set" if token.empty?
|
|
2420
|
+
|
|
2421
|
+
gem_name = detect_gem_name
|
|
2422
|
+
if require_rubygems && !rubygems_version_published?(gem_name, normalized_version)
|
|
2423
|
+
diagnostics << "RubyGems.org does not list #{gem_name} #{normalized_version}"
|
|
2424
|
+
end
|
|
2425
|
+
|
|
2426
|
+
remote = preferred_github_remote
|
|
2427
|
+
if remote.nil?
|
|
2428
|
+
diagnostics << "no GitHub remote is configured"
|
|
2429
|
+
elsif !remote_tag_exists?(remote, tag)
|
|
2430
|
+
diagnostics << "remote #{remote.inspect} does not contain tag #{tag}"
|
|
2431
|
+
end
|
|
2432
|
+
|
|
2433
|
+
section, = extract_changelog_for_version(normalized_version)
|
|
2434
|
+
diagnostics << "CHANGELOG.md does not contain a section for #{normalized_version}" unless section
|
|
2435
|
+
|
|
2436
|
+
{
|
|
2437
|
+
"ok" => diagnostics.empty?,
|
|
2438
|
+
"version" => normalized_version,
|
|
2439
|
+
"tag" => tag,
|
|
2440
|
+
"gem_name" => gem_name,
|
|
2441
|
+
"remote" => remote,
|
|
2442
|
+
"diagnostics" => diagnostics
|
|
2443
|
+
}
|
|
2444
|
+
end
|
|
2445
|
+
|
|
2446
|
+
def github_release_token_configured?
|
|
2447
|
+
!github_token.empty?
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
def github_release_required?
|
|
2451
|
+
github_release_token_configured? && truthy_value?(ENV["KETTLE_RELEASE_REQUIRE_GITHUB_RELEASE"])
|
|
2452
|
+
end
|
|
2453
|
+
|
|
2454
|
+
def github_token
|
|
2455
|
+
token = ENV.fetch("GITHUB_TOKEN", "").strip
|
|
2456
|
+
return token unless token.empty?
|
|
2457
|
+
|
|
2458
|
+
ENV.fetch("GH_TOKEN", "").strip
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
def rubygems_version_published?(gem_name, version)
|
|
2462
|
+
versions = Kettle::Dev::RubyGemsVersions.fetch(gem_name, version_hint: version, refresh: true)
|
|
2463
|
+
Array(versions).any? { |entry| entry.is_a?(Hash) && entry["number"].to_s == version.to_s }
|
|
2464
|
+
rescue => error
|
|
2465
|
+
warn("Could not verify RubyGems.org publication for #{gem_name} #{version}: #{error.class}: #{error.message}")
|
|
2466
|
+
false
|
|
2467
|
+
end
|
|
2468
|
+
|
|
2469
|
+
def remote_tag_exists?(remote, tag)
|
|
2470
|
+
_stdout, _stderr, status = Open3.capture3("git", "ls-remote", "--exit-code", "--tags", remote, "refs/tags/#{tag}", chdir: @root)
|
|
2471
|
+
status.success?
|
|
2472
|
+
rescue SystemCallError => error
|
|
2473
|
+
warn("Could not verify remote tag #{tag}: #{error.class}: #{error.message}")
|
|
2474
|
+
false
|
|
2380
2475
|
end
|
|
2381
2476
|
|
|
2382
2477
|
# Returns [section_text, compare_ref_line, tag_ref_line]
|
|
@@ -2465,6 +2560,28 @@ module Kettle
|
|
|
2465
2560
|
rescue => e
|
|
2466
2561
|
[false, "#{e.class}: #{e.message}"]
|
|
2467
2562
|
end
|
|
2563
|
+
|
|
2564
|
+
def github_update_release(owner:, repo:, token:, tag:, title:, body:)
|
|
2565
|
+
lookup = URI("https://api.github.com/repos/#{owner}/#{repo}/releases/tags/#{tag}")
|
|
2566
|
+
get = Net::HTTP::Get.new(lookup)
|
|
2567
|
+
get["Accept"] = "application/vnd.github+json"
|
|
2568
|
+
get["Authorization"] = "token #{token}"
|
|
2569
|
+
get["User-Agent"] = "kettle-dev-release-cli"
|
|
2570
|
+
release = Net::HTTP.start(lookup.host, lookup.port, use_ssl: true) { |http| http.request(get) }
|
|
2571
|
+
return [false, "release #{tag} was not found (HTTP #{release.code})"] unless release.is_a?(Net::HTTPSuccess)
|
|
2572
|
+
|
|
2573
|
+
id = JSON.parse(release.body).fetch("id")
|
|
2574
|
+
uri = URI("https://api.github.com/repos/#{owner}/#{repo}/releases/#{id}")
|
|
2575
|
+
request = Net::HTTP::Patch.new(uri)
|
|
2576
|
+
request["Accept"] = "application/vnd.github+json"
|
|
2577
|
+
request["Authorization"] = "token #{token}"
|
|
2578
|
+
request["User-Agent"] = "kettle-dev-release-cli"
|
|
2579
|
+
request.body = JSON.dump(name: title, body: body)
|
|
2580
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
2581
|
+
response.is_a?(Net::HTTPSuccess) ? [true, "updated"] : [false, "HTTP #{response.code}: #{response.body}"]
|
|
2582
|
+
rescue => e
|
|
2583
|
+
[false, "#{e.class}: #{e.message}"]
|
|
2584
|
+
end
|
|
2468
2585
|
end
|
|
2469
2586
|
end
|
|
2470
2587
|
end
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "2.5.
|
|
8
|
+
VERSION = "2.5.15"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -256,7 +256,7 @@ dependencies:
|
|
|
256
256
|
version: '3.2'
|
|
257
257
|
- - ">="
|
|
258
258
|
- !ruby/object:Gem::Version
|
|
259
|
-
version: 3.2.
|
|
259
|
+
version: 3.2.4
|
|
260
260
|
type: :development
|
|
261
261
|
prerelease: false
|
|
262
262
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -266,7 +266,7 @@ dependencies:
|
|
|
266
266
|
version: '3.2'
|
|
267
267
|
- - ">="
|
|
268
268
|
- !ruby/object:Gem::Version
|
|
269
|
-
version: 3.2.
|
|
269
|
+
version: 3.2.4
|
|
270
270
|
- !ruby/object:Gem::Dependency
|
|
271
271
|
name: stone_checksums
|
|
272
272
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -276,7 +276,7 @@ dependencies:
|
|
|
276
276
|
version: '1.0'
|
|
277
277
|
- - ">="
|
|
278
278
|
- !ruby/object:Gem::Version
|
|
279
|
-
version: 1.0.
|
|
279
|
+
version: 1.0.8
|
|
280
280
|
type: :development
|
|
281
281
|
prerelease: false
|
|
282
282
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -286,7 +286,7 @@ dependencies:
|
|
|
286
286
|
version: '1.0'
|
|
287
287
|
- - ">="
|
|
288
288
|
- !ruby/object:Gem::Version
|
|
289
|
-
version: 1.0.
|
|
289
|
+
version: 1.0.8
|
|
290
290
|
- !ruby/object:Gem::Dependency
|
|
291
291
|
name: gitmoji-regex
|
|
292
292
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -296,7 +296,7 @@ dependencies:
|
|
|
296
296
|
version: '2.0'
|
|
297
297
|
- - ">="
|
|
298
298
|
- !ruby/object:Gem::Version
|
|
299
|
-
version: 2.0.
|
|
299
|
+
version: 2.0.11
|
|
300
300
|
type: :development
|
|
301
301
|
prerelease: false
|
|
302
302
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -306,7 +306,7 @@ dependencies:
|
|
|
306
306
|
version: '2.0'
|
|
307
307
|
- - ">="
|
|
308
308
|
- !ruby/object:Gem::Version
|
|
309
|
-
version: 2.0.
|
|
309
|
+
version: 2.0.11
|
|
310
310
|
description: "\U0001F372 Kettle::Dev is a meta tool from kettle-dev to streamline
|
|
311
311
|
development and testing. Acts as a shim dependency, pulling in many other dependencies,
|
|
312
312
|
to give you OOTB productivity with a RubyGem, or Ruby app project. Configures a
|
|
@@ -401,10 +401,10 @@ licenses:
|
|
|
401
401
|
- AGPL-3.0-only
|
|
402
402
|
metadata:
|
|
403
403
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
404
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.
|
|
405
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.
|
|
404
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v2.5.15
|
|
405
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v2.5.15/CHANGELOG.md
|
|
406
406
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
407
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.
|
|
407
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/2.5.15
|
|
408
408
|
funding_uri: https://github.com/sponsors/pboling
|
|
409
409
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
410
410
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|