reissue 0.5.0 → 0.5.2
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
- data/CHANGELOG.md +21 -4
- data/README.md +8 -0
- data/lib/reissue/gem.rb +24 -0
- data/lib/reissue/rake.rb +112 -15
- data/lib/reissue/runbook.rb +27 -7
- data/lib/reissue/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec588273a5faaed4d2287b8bde8877deb2e64b4ff8ef84ff71ce5ce58dd85ffc
|
|
4
|
+
data.tar.gz: 4ae823291b5180145e82bdba30d87c89a69440f5ab41df9c6120813898afdc1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91d584ae8adb5bebee45f1ca5668df9e136399a56134350ea8cf76c25dff748406f909cfb3d4110d8c92907376c3105c6d1d94d89d7c9047b5d0955004f28e84
|
|
7
|
+
data.tar.gz: d27c47553f7ac4a2969a6ac15e91ad23e928f74c989526e7a20e6a92e8ef333363cd04817bed4ab89833bd2ae8f7bc234e02e6a1fa87e1110660a57551a39594
|
data/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
8
|
+
## [0.5.2] - 2026-09-17
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- finalize_message option sets the finalize commit subject from the version and date (3716c08)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Finalize commits use the subject "Finalize changelog for VERSION" without the release date (3716c08)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Shared release workflow retries post-release PR creation up to three times before failing, since the gem is already published and the branch pushed by that point (84bf0da)
|
|
21
|
+
|
|
22
|
+
## [0.5.1] - 2026-07-24
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- reissue:next_version task reports the version the next release would use without changing anything (9feb212)
|
|
27
|
+
- reissue:initialize[runbook] scaffolds a starter runbook file (dfa521a)
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Runbook finalize and new-version reset preserve a custom title and preamble (a0d874c)
|
|
15
32
|
|
|
16
33
|
### Fixed
|
|
17
34
|
|
|
18
|
-
- changelog
|
|
35
|
+
- Releases no longer publish a version whose changelog entry and git tag name the previous version (acf4c1a)
|
data/README.md
CHANGED
|
@@ -123,6 +123,10 @@ Reissue::Task.create :reissue do |task|
|
|
|
123
123
|
# Optional: Whether to commit the results of the finalize task. Defaults to true
|
|
124
124
|
task.commit_finalize = true
|
|
125
125
|
|
|
126
|
+
# Optional: A callable returning the finalize commit subject, given version and date.
|
|
127
|
+
# Defaults to: ->(version, date) { "Finalize changelog for #{version}" }
|
|
128
|
+
task.finalize_message = ->(version, date) { "Release #{version}" }
|
|
129
|
+
|
|
126
130
|
# Optional: Whether to push the changes automatically. Defaults to false
|
|
127
131
|
# Options: false, true (push working branch), :branch (create and push new branch)
|
|
128
132
|
task.push_finalize = :branch
|
|
@@ -357,6 +361,8 @@ Reissue::Task.create :reissue do |task|
|
|
|
357
361
|
end
|
|
358
362
|
```
|
|
359
363
|
|
|
364
|
+
You don't have to create the file yourself — the first `rake reissue` or `rake reissue:finalize` writes it when `runbook_file` is set. To start from a template you can customize, run `rake reissue:initialize[runbook]`, which creates `RUNBOOK.md` (pass a different name, e.g. `rake reissue:initialize[OPERATIONS.md]`, to use another path).
|
|
365
|
+
|
|
360
366
|
The runbook holds only the latest release. Items are checkboxes so operators can tick them off:
|
|
361
367
|
|
|
362
368
|
```markdown
|
|
@@ -370,6 +376,8 @@ Steps to perform after releasing the version below.
|
|
|
370
376
|
- [ ] Re-index search documents (def5678)
|
|
371
377
|
```
|
|
372
378
|
|
|
379
|
+
The title and the preamble beneath it are yours to edit. Give the file a custom heading and intro and reissue preserves them across finalize and new-version resets — only the version heading and the checklist are rewritten, the same way a custom preamble survives in `CHANGELOG.md`.
|
|
380
|
+
|
|
373
381
|
### Adding Runbook Items
|
|
374
382
|
|
|
375
383
|
Add `Runbook:` trailers to commit messages (case-insensitive, independent of the `fragment` setting):
|
data/lib/reissue/gem.rb
CHANGED
|
@@ -7,6 +7,30 @@ module Reissue
|
|
|
7
7
|
super
|
|
8
8
|
@updated_paths << "checksums"
|
|
9
9
|
end
|
|
10
|
+
|
|
11
|
+
# Keep bundler's gemspec in step with the bump.
|
|
12
|
+
#
|
|
13
|
+
# Bundler loads the gemspec when bundler/gem_tasks is required, well before
|
|
14
|
+
# reissue:bump rewrites the version file. `gem build` shells out and re-reads
|
|
15
|
+
# the file, so the gem itself carries the bumped version, but the release tag
|
|
16
|
+
# and bundler's confirmation messages read the copy held in memory. Without
|
|
17
|
+
# this they name the version from before the bump, and the release publishes
|
|
18
|
+
# one version under another version's tag.
|
|
19
|
+
def apply_version_bump(updater, bump)
|
|
20
|
+
new_version = super
|
|
21
|
+
sync_bundler_gemspec_version(new_version)
|
|
22
|
+
new_version
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param new_version [String] the version the bump landed on
|
|
26
|
+
def sync_bundler_gemspec_version(new_version)
|
|
27
|
+
return unless defined?(Bundler::GemHelper)
|
|
28
|
+
|
|
29
|
+
helper = Bundler::GemHelper.instance
|
|
30
|
+
return unless helper&.gemspec
|
|
31
|
+
|
|
32
|
+
helper.gemspec.version = ::Gem::Version.new(new_version)
|
|
33
|
+
end
|
|
10
34
|
end
|
|
11
35
|
end
|
|
12
36
|
Reissue::Task.prepend Reissue::Gem
|
data/lib/reissue/rake.rb
CHANGED
|
@@ -79,6 +79,11 @@ module Reissue
|
|
|
79
79
|
# Whether to commit the finalize change to the changelog. Default: true.
|
|
80
80
|
attr_accessor :commit_finalize
|
|
81
81
|
|
|
82
|
+
# Callable receiving the version and release date, returning the commit subject.
|
|
83
|
+
# The default omits the date to stay under the 50-character subject limit.
|
|
84
|
+
# Default: ->(version, date) { "Finalize changelog for #{version}" }
|
|
85
|
+
attr_accessor :finalize_message
|
|
86
|
+
|
|
82
87
|
# Whether to commit the clear fragments change.
|
|
83
88
|
# Returns false for :git fragments since there are no files to commit.
|
|
84
89
|
attr_writer :commit_clear_fragments
|
|
@@ -137,6 +142,7 @@ module Reissue
|
|
|
137
142
|
@clear_fragments = false
|
|
138
143
|
@commit = true
|
|
139
144
|
@commit_finalize = true
|
|
145
|
+
@finalize_message = ->(version, _date) { "Finalize changelog for #{version}" }
|
|
140
146
|
@commit_clear_fragments = true
|
|
141
147
|
@push_finalize = false
|
|
142
148
|
@version_limit = 2
|
|
@@ -188,6 +194,78 @@ module Reissue
|
|
|
188
194
|
end
|
|
189
195
|
end
|
|
190
196
|
|
|
197
|
+
# The version the next release would use, derived exactly the way reissue:bump
|
|
198
|
+
# derives it, without writing anything.
|
|
199
|
+
#
|
|
200
|
+
# @return [Gem::Version] the version a release would publish
|
|
201
|
+
def next_release_version
|
|
202
|
+
require_relative "fragment_handler"
|
|
203
|
+
require_relative "version_updater"
|
|
204
|
+
|
|
205
|
+
# reissue:bump only consults git trailers for :git fragments.
|
|
206
|
+
handler = (Reissue::FragmentHandler.for(:git, tag_pattern: tag_pattern) if fragment == :git)
|
|
207
|
+
tag_version = handler&.last_tag_version
|
|
208
|
+
bump = handler&.read_version_bump
|
|
209
|
+
updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
|
|
210
|
+
|
|
211
|
+
if deferred_versioning
|
|
212
|
+
# The version file holds "Unreleased"; finalize resolves the real version
|
|
213
|
+
# from the last tag and the trailer.
|
|
214
|
+
unless tag_version && bump
|
|
215
|
+
raise "Cannot determine the next version. Deferred versioning resolves it from " \
|
|
216
|
+
"the last version tag and a Version: trailer, and one of those is missing."
|
|
217
|
+
end
|
|
218
|
+
return updater.redo(tag_version, bump)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
current_version = ::Gem::Version.new(File.read(version_file).match(Reissue::VersionUpdater::VERSION_MATCH)[0])
|
|
222
|
+
|
|
223
|
+
if bump.nil?
|
|
224
|
+
current_version
|
|
225
|
+
elsif tag_version && tag_version != current_version
|
|
226
|
+
# A post-release bump already moved the file past the tag, so the trailer
|
|
227
|
+
# only counts if it would land beyond where the file already is.
|
|
228
|
+
[updater.redo(tag_version, bump), current_version].max
|
|
229
|
+
else
|
|
230
|
+
updater.redo(current_version, bump)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Apply a version bump and leave the changelog agreeing with it.
|
|
235
|
+
#
|
|
236
|
+
# @param updater [Reissue::VersionUpdater] the updater to bump with
|
|
237
|
+
# @param bump [Symbol] the segment to bump
|
|
238
|
+
# @return [String] the version the bump landed on
|
|
239
|
+
def apply_version_bump(updater, bump)
|
|
240
|
+
new_version = updater.call(bump)
|
|
241
|
+
realign_unreleased_changelog(new_version)
|
|
242
|
+
bundle
|
|
243
|
+
puts "Version bumped (#{bump}) to #{new_version}"
|
|
244
|
+
new_version
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Point the changelog's unreleased entry at the version just bumped to.
|
|
248
|
+
#
|
|
249
|
+
# The bump moves the version file forward, but the unreleased entry still
|
|
250
|
+
# carries the number from the previous cycle. reissue:finalize dates whatever
|
|
251
|
+
# that entry says, so leaving it alone publishes a gem built at the new
|
|
252
|
+
# version while the only dated changelog entry names the old one.
|
|
253
|
+
#
|
|
254
|
+
# @param new_version [String] the version the bump landed on
|
|
255
|
+
def realign_unreleased_changelog(new_version)
|
|
256
|
+
return unless changelog_file && File.exist?(changelog_file)
|
|
257
|
+
|
|
258
|
+
changelog = Reissue::Parser.parse(File.read(changelog_file))
|
|
259
|
+
unreleased = changelog["versions"].find { |version| version["date"] == "Unreleased" }
|
|
260
|
+
return if unreleased.nil? || unreleased["version"] == new_version
|
|
261
|
+
|
|
262
|
+
unreleased["version"] = new_version
|
|
263
|
+
|
|
264
|
+
changelog_updater = Reissue::ChangelogUpdater.new(changelog_file)
|
|
265
|
+
changelog_updater.instance_variable_set(:@changelog, changelog)
|
|
266
|
+
changelog_updater.write(changelog_file)
|
|
267
|
+
end
|
|
268
|
+
|
|
191
269
|
# Check if there are staged changes ready to commit.
|
|
192
270
|
def changes_to_commit?
|
|
193
271
|
_, _, status = Open3.capture3("git diff --cached --quiet")
|
|
@@ -312,7 +390,7 @@ module Reissue
|
|
|
312
390
|
)
|
|
313
391
|
end
|
|
314
392
|
|
|
315
|
-
|
|
393
|
+
message = finalize_message.call(version, date)
|
|
316
394
|
if commit_finalize
|
|
317
395
|
if finalize_with_branch?
|
|
318
396
|
tasker["#{name}:branch"].invoke("finalize/#{version}")
|
|
@@ -320,13 +398,13 @@ module Reissue
|
|
|
320
398
|
run_command("git add -u", "Failed to stage finalized changelog")
|
|
321
399
|
stage_updated_paths
|
|
322
400
|
if changes_to_commit?
|
|
323
|
-
run_command("git commit -m '#{
|
|
401
|
+
run_command("git commit -m '#{message}'", "Failed to commit finalized changelog")
|
|
324
402
|
tasker["#{name}:push"].invoke if push_finalize?
|
|
325
403
|
else
|
|
326
|
-
puts
|
|
404
|
+
puts message
|
|
327
405
|
end
|
|
328
406
|
else
|
|
329
|
-
puts
|
|
407
|
+
puts message
|
|
330
408
|
end
|
|
331
409
|
end
|
|
332
410
|
|
|
@@ -359,6 +437,15 @@ module Reissue
|
|
|
359
437
|
end
|
|
360
438
|
end
|
|
361
439
|
|
|
440
|
+
# Preview reports; it does not fail. next_release_version raises when
|
|
441
|
+
# deferred versioning has no tag or trailer to resolve from.
|
|
442
|
+
previewed_version = begin
|
|
443
|
+
next_release_version
|
|
444
|
+
rescue RuntimeError
|
|
445
|
+
nil
|
|
446
|
+
end
|
|
447
|
+
puts "Version to be released: #{previewed_version || "cannot be determined"}\n\n"
|
|
448
|
+
|
|
362
449
|
entries = handler.read
|
|
363
450
|
|
|
364
451
|
if entries.empty?
|
|
@@ -418,6 +505,11 @@ module Reissue
|
|
|
418
505
|
end
|
|
419
506
|
end
|
|
420
507
|
|
|
508
|
+
desc "Print the version the next release would use, without changing anything"
|
|
509
|
+
task "#{name}:next_version" do
|
|
510
|
+
puts next_release_version
|
|
511
|
+
end
|
|
512
|
+
|
|
421
513
|
desc "Bump version based on git trailers"
|
|
422
514
|
task "#{name}:bump" do
|
|
423
515
|
next if deferred_versioning
|
|
@@ -441,9 +533,7 @@ module Reissue
|
|
|
441
533
|
if tag_version && current_version == tag_version
|
|
442
534
|
if bump
|
|
443
535
|
updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
|
|
444
|
-
updater
|
|
445
|
-
bundle
|
|
446
|
-
puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
|
|
536
|
+
apply_version_bump(updater, bump)
|
|
447
537
|
end
|
|
448
538
|
elsif tag_version && current_version != tag_version
|
|
449
539
|
if bump
|
|
@@ -451,9 +541,7 @@ module Reissue
|
|
|
451
541
|
desired_version = updater.redo(tag_version, bump)
|
|
452
542
|
|
|
453
543
|
if desired_version > current_version
|
|
454
|
-
updater
|
|
455
|
-
bundle
|
|
456
|
-
puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
|
|
544
|
+
apply_version_bump(updater, bump)
|
|
457
545
|
else
|
|
458
546
|
puts "Version already bumped (#{tag_version} → #{current_version}), skipping"
|
|
459
547
|
end
|
|
@@ -462,9 +550,7 @@ module Reissue
|
|
|
462
550
|
end
|
|
463
551
|
elsif bump
|
|
464
552
|
updater = Reissue::VersionUpdater.new(version_file, version_redo_proc: version_redo_proc)
|
|
465
|
-
updater
|
|
466
|
-
bundle
|
|
467
|
-
puts "Version bumped (#{bump}) to #{updater.instance_variable_get(:@new_version)}"
|
|
553
|
+
apply_version_bump(updater, bump)
|
|
468
554
|
end
|
|
469
555
|
end
|
|
470
556
|
end
|
|
@@ -474,8 +560,8 @@ end
|
|
|
474
560
|
# Define the reissue:initialize task as a standalone task
|
|
475
561
|
# This works without any configuration, allowing users to bootstrap their project
|
|
476
562
|
namespace :reissue do
|
|
477
|
-
desc "Initialize reissue by creating a CHANGELOG.md and showing Rakefile configuration"
|
|
478
|
-
task :initialize do
|
|
563
|
+
desc "Initialize reissue by creating a CHANGELOG.md and showing Rakefile configuration. Pass [runbook] to also create a RUNBOOK.md."
|
|
564
|
+
task :initialize, [:runbook] do |task, args|
|
|
479
565
|
changelog_file = "CHANGELOG.md"
|
|
480
566
|
|
|
481
567
|
if File.exist?(changelog_file)
|
|
@@ -485,6 +571,16 @@ namespace :reissue do
|
|
|
485
571
|
puts "✓ Created #{changelog_file}"
|
|
486
572
|
end
|
|
487
573
|
|
|
574
|
+
if args[:runbook]
|
|
575
|
+
runbook_file = (args[:runbook] == "runbook") ? "RUNBOOK.md" : args[:runbook]
|
|
576
|
+
if File.exist?(runbook_file)
|
|
577
|
+
puts "✓ #{runbook_file} already exists"
|
|
578
|
+
else
|
|
579
|
+
Reissue::Runbook.new(runbook_file).generate
|
|
580
|
+
puts "✓ Created #{runbook_file}"
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
488
584
|
puts
|
|
489
585
|
puts "Add the following to your Rakefile to enable reissue tasks:"
|
|
490
586
|
puts
|
|
@@ -514,6 +610,7 @@ namespace :reissue do
|
|
|
514
610
|
|
|
515
611
|
# Post-release runbook (mainly for applications)
|
|
516
612
|
# task.runbook_file = "RUNBOOK.md" # Maintain a runbook from Runbook: git trailers
|
|
613
|
+
# # Seed a starter file with: rake reissue:initialize[runbook]
|
|
517
614
|
|
|
518
615
|
# Git workflow options
|
|
519
616
|
task.commit = true # Auto-commit version bumps
|
data/lib/reissue/runbook.rb
CHANGED
|
@@ -6,15 +6,20 @@ module Reissue
|
|
|
6
6
|
class Runbook
|
|
7
7
|
ITEM_PATTERN = /^- (?:\[(?<checked>[ xX])\] )?(?<text>.+)$/
|
|
8
8
|
|
|
9
|
+
DEFAULT_TITLE = "Runbook"
|
|
10
|
+
DEFAULT_PREAMBLE = "Steps to perform after releasing the version below."
|
|
11
|
+
|
|
9
12
|
def initialize(runbook_file)
|
|
10
13
|
@runbook_file = runbook_file
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
attr_reader :runbook_file
|
|
14
17
|
|
|
15
|
-
# Writes a header-only template for the given version
|
|
18
|
+
# Writes a header-only template for the given version, preserving any
|
|
19
|
+
# custom title and preamble already in the file.
|
|
16
20
|
def generate(version: "Unreleased")
|
|
17
|
-
|
|
21
|
+
title, preamble = parsed_header
|
|
22
|
+
File.write(runbook_file, template(heading(version), title:, preamble:))
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
alias_method :clear, :generate
|
|
@@ -25,18 +30,33 @@ module Reissue
|
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
# Stamps the header with the release version and date, merging directly
|
|
28
|
-
# edited items with trailer items (deduplicated by text).
|
|
33
|
+
# edited items with trailer items (deduplicated by text). Preserves any
|
|
34
|
+
# custom title and preamble already in the file.
|
|
29
35
|
def finalize(version:, date:, trailer_items: [])
|
|
36
|
+
title, preamble = parsed_header
|
|
30
37
|
merged = parsed_items
|
|
31
38
|
texts = merged.map { |item| item[:text] }
|
|
32
39
|
trailer_items.each do |text|
|
|
33
40
|
merged << {text: text, checked: false} unless texts.include?(text)
|
|
34
41
|
end
|
|
35
|
-
File.write(runbook_file, template(heading(version, date), merged))
|
|
42
|
+
File.write(runbook_file, template(heading(version, date), merged, title:, preamble:))
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
private
|
|
39
46
|
|
|
47
|
+
# Returns the [title, preamble] found before the first version heading,
|
|
48
|
+
# falling back to the defaults when the file is missing or header-only.
|
|
49
|
+
def parsed_header
|
|
50
|
+
return [DEFAULT_TITLE, DEFAULT_PREAMBLE] unless File.exist?(runbook_file)
|
|
51
|
+
|
|
52
|
+
header_chunk = File.read(runbook_file).split(/^## /, 2).first.to_s
|
|
53
|
+
title_match = header_chunk.match(/^#\s+(?<title>.+)$/)
|
|
54
|
+
title = title_match ? title_match[:title].strip : DEFAULT_TITLE
|
|
55
|
+
preamble = (title_match ? header_chunk.sub(title_match[0], "") : header_chunk).strip
|
|
56
|
+
preamble = DEFAULT_PREAMBLE if preamble.empty?
|
|
57
|
+
[title, preamble]
|
|
58
|
+
end
|
|
59
|
+
|
|
40
60
|
def parsed_items
|
|
41
61
|
return [] unless File.exist?(runbook_file)
|
|
42
62
|
|
|
@@ -51,11 +71,11 @@ module Reissue
|
|
|
51
71
|
date ? "## [#{version}] - #{date}" : "## [#{version}]"
|
|
52
72
|
end
|
|
53
73
|
|
|
54
|
-
def template(heading, items = [])
|
|
74
|
+
def template(heading, items = [], title: DEFAULT_TITLE, preamble: DEFAULT_PREAMBLE)
|
|
55
75
|
header = <<~MARKDOWN
|
|
56
|
-
#
|
|
76
|
+
# #{title}
|
|
57
77
|
|
|
58
|
-
|
|
78
|
+
#{preamble}
|
|
59
79
|
|
|
60
80
|
#{heading}
|
|
61
81
|
MARKDOWN
|
data/lib/reissue/version.rb
CHANGED