reissue 0.4.18 → 0.4.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2d81243492d9418167e8a70f6f9b36525db84fb631751f3a592e0197e30c106
4
- data.tar.gz: ef91ae54abde7a9ad9936405f0f2a16d1d1b77896c5abb52613c60b55a7fe1fb
3
+ metadata.gz: 8c46dadfd4d0a4dcaacebe8bc3520bb390632354008a869dd89d89bd052dc51f
4
+ data.tar.gz: ef50ffe309580748f40293da43f9d8475def2fae718a1d487e1714c77132bb09
5
5
  SHA512:
6
- metadata.gz: dd54fb9cbe9012fa1629526e146a913d468bbe7e56cfd39281c23d740f6178e847a03d9add2e71430a56580fa0a28187e30385bbc8f71a6a38c8b703c3866b2d
7
- data.tar.gz: d023c5ab1a84d4789b4373c0810c6ee96973c27290a3df6e1357aa808e87ceb78d1b82c7313c095bcca3d9735f30e6ad5828b320d41a938f43db57c38ebe0c5f
6
+ metadata.gz: 381cd331c021a6ba92eae1fa976dc411922b9f9b1c963eb43ff31fe63e58b6bad6153f2915ed76bd6ca92185ef20233aa4606cf5205a24363dcb2857da204f57
7
+ data.tar.gz: 37ce464c665abc6521c1defec054ee88dc56e50ee6779473849af00176a0428b9e6f2f4bd259d5deaca0f4d84f986ad4c4029cd60462fbc02df465fefbe72542
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ 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.4.19] - 2026-03-09
9
+
10
+ ### Added
11
+
12
+ - Support parsing changelog version entries without a date field (e.g. ## [Unreleased]) (75c5d6c)
13
+ - Print ## [Unreleased] without trailing date suffix when version is Unreleased (aed6011)
14
+ - ChangelogUpdater handles Unreleased version strings without crashing (d5f4e3e)
15
+ - ChangelogUpdater#finalize accepts resolved_version parameter to replace Unreleased (d5f4e3e)
16
+ - VersionUpdater#set_version for writing arbitrary version strings like Unreleased (33b3d76)
17
+ - Reissue.deferred_call sets VERSION to Unreleased and adds ## [Unreleased] changelog entry (76e3d61)
18
+ - Reissue.deferred_finalize resolves version from segment, explicit version, or git trailers at release time (1bdf36f)
19
+ - deferred_versioning flag for Reissue::Task to defer version bumping until finalize (aac3ade)
20
+ - Rake reissue task sets VERSION to Unreleased in deferred mode (aac3ade)
21
+ - Rake reissue:finalize task accepts version or segment argument in deferred mode (aac3ade)
22
+ - reissue_deferred_versioning attribute in Hoe plugin (e5a78d4)
23
+ - End-to-end integration tests for the deferred versioning workflow (77a313e)
24
+
25
+ ### Changed
26
+
27
+ - set_version uses RELEASE_VERSION_MATCH paralleling RELEASE_DATE_MATCH pattern (efec0e5)
28
+
8
29
  ## [0.4.18] - 2026-02-25
9
30
 
10
31
  ### Added
@@ -17,9 +38,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
17
38
  ### Changed
18
39
 
19
40
  - Pass version_file to Reissue.finalize from rake task (83682ff)
20
-
21
- ## [0.4.17] - 2026-02-24
22
-
23
- ### Fixed
24
-
25
- - Forward tag_pattern to git fragment handler in call and finalize (dd35249)
data/lib/hoe/reissue.rb CHANGED
@@ -10,7 +10,7 @@ module Hoe::Reissue
10
10
  :reissue_changelog_sections,
11
11
  :reissue_commit, :reissue_commit_finalize,
12
12
  :reissue_push_finalize, :reissue_push_reissue,
13
- :reissue_updated_paths
13
+ :reissue_updated_paths, :reissue_deferred_versioning
14
14
 
15
15
  def initialize_reissue
16
16
  self.reissue_version_file = "lib/#{name}/version.rb"
@@ -27,6 +27,7 @@ module Hoe::Reissue
27
27
  self.reissue_push_finalize = false
28
28
  self.reissue_push_reissue = :branch
29
29
  self.reissue_updated_paths = []
30
+ self.reissue_deferred_versioning = false
30
31
  end
31
32
 
32
33
  def define_reissue_tasks
@@ -44,7 +45,8 @@ module Hoe::Reissue
44
45
  commit_finalize: reissue_commit_finalize,
45
46
  push_finalize: reissue_push_finalize,
46
47
  push_reissue: reissue_push_reissue,
47
- updated_paths: reissue_updated_paths
48
+ updated_paths: reissue_updated_paths,
49
+ deferred_versioning: reissue_deferred_versioning
48
50
  }
49
51
 
50
52
  Reissue::Task.create :reissue do
@@ -34,12 +34,19 @@ module Reissue
34
34
  changelog
35
35
  end
36
36
 
37
- def finalize(date: Date.today, changelog_file: @changelog_file, retain_changelogs: false)
37
+ def finalize(date: Date.today, changelog_file: @changelog_file, retain_changelogs: false, resolved_version: nil)
38
38
  @changelog = Parser.parse(File.read(changelog_file))
39
39
 
40
40
  # find the highest version number and if it is unreleased, update the date
41
41
  version = latest_version
42
+ version_value = version["version"]
42
43
  version_date = version["date"]
44
+
45
+ # In deferred mode, resolved_version replaces "Unreleased" version string
46
+ if resolved_version && version_value == "Unreleased"
47
+ version["version"] = resolved_version
48
+ end
49
+
43
50
  if version_date.nil? || version_date == "Unreleased"
44
51
  changelog["versions"].find do |v|
45
52
  v["version"] == version["version"]
@@ -124,7 +131,9 @@ module Reissue
124
131
  private
125
132
 
126
133
  def latest_version
127
- changelog["versions"].max_by { |v| ::Gem::Version.new(v["version"]) }
134
+ versioned, unversioned = changelog["versions"].partition { |v| v["version"] != "Unreleased" }
135
+ return unversioned.first if unversioned.any?
136
+ versioned.max_by { |v| ::Gem::Version.new(v["version"]) }
128
137
  end
129
138
  end
130
139
  end
@@ -63,9 +63,13 @@ module Reissue
63
63
  return unless @version.nil? && @date.nil? && @changes.empty?
64
64
  # the first line contains the version and date
65
65
  scanner = StringScanner.new(@chunk) << "\n"
66
- scanner.scan(/\[?(.[^\]]+)\]? - (.+)$/)
67
- @version = scanner[1].to_s.strip
68
- @date = scanner[2].to_s.strip || "Unreleased"
66
+ if scanner.scan(/\[?(.[^\]]+)\]? - (.+)$/)
67
+ @version = scanner[1].to_s.strip
68
+ @date = scanner[2].to_s.strip
69
+ elsif scanner.scan(/\[?([^\]\s]+)\]?\s*$/)
70
+ @version = scanner[1].to_s.strip
71
+ @date = nil
72
+ end
69
73
 
70
74
  # the rest of the text is the changes
71
75
  @changes = scanner.rest.strip.split("### ").reject(&:empty?).map { |change| Change.new(change) }
@@ -24,7 +24,11 @@ module Reissue
24
24
  changes = data.fetch("changes") do
25
25
  {}
26
26
  end
27
- version_string = "## [#{version}] - #{date}"
27
+ version_string = if version == "Unreleased" && date.nil?
28
+ "## [Unreleased]"
29
+ else
30
+ "## [#{version}] - #{date}"
31
+ end
28
32
  changes_string = changes.map do |section, section_changes|
29
33
  format_section(section, section_changes)
30
34
  end.join("\n\n")
data/lib/reissue/rake.rb CHANGED
@@ -107,6 +107,11 @@ module Reissue
107
107
  # Default: nil (uses default pattern matching "v1.2.3")
108
108
  attr_accessor :tag_pattern
109
109
 
110
+ # Whether to defer version bumping until finalize time. Default: false.
111
+ # When true, the reissue task sets VERSION to "Unreleased" instead of bumping,
112
+ # and the finalize task resolves the version from git tags + segment argument.
113
+ attr_accessor :deferred_versioning
114
+
110
115
  # The ordered list of valid changelog sections.
111
116
  # Controls both validation (which sections are accepted) and ordering (how they appear).
112
117
  # Default: nil (uses Reissue.changelog_sections)
@@ -132,6 +137,7 @@ module Reissue
132
137
  @push_reissue = :branch
133
138
  @tag_pattern = nil
134
139
  @changelog_sections = nil
140
+ @deferred_versioning = false
135
141
  end
136
142
 
137
143
  attr_reader :formatter, :tasker
@@ -194,16 +200,26 @@ module Reissue
194
200
 
195
201
  desc description
196
202
  task name, [:segment] do |task, args|
197
- segment = args[:segment] || "patch"
198
- new_version = formatter.call(
199
- segment:,
200
- version_file:,
201
- changelog_file:,
202
- version_limit:,
203
- version_redo_proc:,
204
- fragment: fragment,
205
- tag_pattern:
206
- )
203
+ if deferred_versioning
204
+ new_version = formatter.deferred_call(
205
+ version_file:,
206
+ changelog_file:,
207
+ version_limit:,
208
+ fragment: fragment,
209
+ tag_pattern:
210
+ )
211
+ else
212
+ segment = args[:segment] || "patch"
213
+ new_version = formatter.call(
214
+ segment:,
215
+ version_file:,
216
+ changelog_file:,
217
+ version_limit:,
218
+ version_redo_proc:,
219
+ fragment: fragment,
220
+ tag_pattern:
221
+ )
222
+ end
207
223
  bundle
208
224
 
209
225
  tasker["#{name}:clear_fragments"].invoke
@@ -211,11 +227,20 @@ module Reissue
211
227
  run_command("git add -u", "Failed to stage updated files")
212
228
  stage_updated_paths
213
229
 
214
- bump_message = "Bump version to #{new_version}"
230
+ bump_message = if deferred_versioning
231
+ "Prepare for next development version"
232
+ else
233
+ "Bump version to #{new_version}"
234
+ end
215
235
  if commit
216
236
  if reissue_version_with_branch?
217
237
  tasker["#{name}:branch"].reenable
218
- tasker["#{name}:branch"].invoke("reissue/#{new_version}")
238
+ branch_name = if deferred_versioning
239
+ "reissue/next"
240
+ else
241
+ "reissue/#{new_version}"
242
+ end
243
+ tasker["#{name}:branch"].invoke(branch_name)
219
244
  end
220
245
  run_command("git commit -m '#{bump_message}'", "Failed to commit version bump")
221
246
  tasker["#{name}:push"].invoke if push_reissue?
@@ -240,20 +265,45 @@ module Reissue
240
265
  end
241
266
 
242
267
  desc "Finalize the changelog for an unreleased version to set the release date."
243
- task "#{name}:finalize", [:date] do |task, args|
244
- date = args[:date] || Time.now.strftime("%Y-%m-%d")
245
- version, date = formatter.finalize(
246
- date,
247
- changelog_file:,
248
- retain_changelogs:,
249
- fragment: fragment,
250
- tag_pattern:,
251
- version_file:
252
- )
268
+ task "#{name}:finalize", [:version_or_segment, :date] do |task, args|
269
+ if deferred_versioning
270
+ version_or_segment = args[:version_or_segment]
271
+ date = args[:date] || Time.now.strftime("%Y-%m-%d")
272
+
273
+ version_arg = nil
274
+ segment_arg = nil
275
+ if version_or_segment&.match?(/^\d/)
276
+ version_arg = version_or_segment
277
+ elsif version_or_segment
278
+ segment_arg = version_or_segment
279
+ end
280
+
281
+ version, date = formatter.deferred_finalize(
282
+ date,
283
+ version: version_arg,
284
+ segment: segment_arg,
285
+ changelog_file:,
286
+ retain_changelogs:,
287
+ fragment: fragment,
288
+ tag_pattern:,
289
+ version_file:,
290
+ version_redo_proc:
291
+ )
292
+ else
293
+ date = args[:version_or_segment] || Time.now.strftime("%Y-%m-%d")
294
+ version, date = formatter.finalize(
295
+ date,
296
+ changelog_file:,
297
+ retain_changelogs:,
298
+ fragment: fragment,
299
+ tag_pattern:,
300
+ version_file:
301
+ )
302
+ end
303
+
253
304
  finalize_message = "Finalize the changelog for version #{version} on #{date}"
254
305
  if commit_finalize
255
306
  if finalize_with_branch?
256
- # Use "finalize/" prefix for the version being released
257
307
  tasker["#{name}:branch"].invoke("finalize/#{version}")
258
308
  end
259
309
  run_command("git add -u", "Failed to stage finalized changelog")
@@ -361,6 +411,7 @@ module Reissue
361
411
 
362
412
  desc "Bump version based on git trailers"
363
413
  task "#{name}:bump" do
414
+ next if deferred_versioning
364
415
  # Only check for version trailers when using git fragments
365
416
  next unless fragment == :git
366
417
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reissue
4
- VERSION = "0.4.18"
5
- RELEASE_DATE = "2026-02-25"
4
+ VERSION = "0.4.19"
5
+ RELEASE_DATE = "2026-03-09"
6
6
  end
@@ -77,6 +77,15 @@ module Reissue
77
77
  @new_version
78
78
  end
79
79
 
80
+ def set_version(version_string, version_file: @version_file)
81
+ body = File.read(@version_file)
82
+ updated = body.gsub(release_version_regex) do |match|
83
+ match.sub(/=\s*"[^"]*"/, "= \"#{version_string}\"")
84
+ end
85
+ File.write(version_file, updated)
86
+ version_string
87
+ end
88
+
80
89
  # A proc that can be used to redo the version string.
81
90
  attr_accessor :version_redo_proc
82
91
 
@@ -108,8 +117,10 @@ module Reissue
108
117
  VERSION_MATCH = /(?<major>\d+)\.(?<minor>[a-zA-Z\d]+)\.(?<patch>[a-zA-Z\d]+)(?<add>\.(?<pre>[a-zA-Z\d]+))?/
109
118
  def version_regex = VERSION_MATCH
110
119
 
120
+ RELEASE_VERSION_MATCH = /VERSION\s*=\s*"([^"]*)"/
111
121
  RELEASE_DATE_MATCH = /RELEASE_DATE\s*=\s*"([^"]*)"/
112
122
 
123
+ def release_version_regex = RELEASE_VERSION_MATCH
113
124
  def release_date_regex = RELEASE_DATE_MATCH
114
125
 
115
126
  def update_release_date(date, version_file: @version_file)
data/lib/reissue.rb CHANGED
@@ -58,6 +58,101 @@ module Reissue
58
58
  new_version
59
59
  end
60
60
 
61
+ def self.deferred_call(version_file:, changelog_file: "CHANGELOG.md", version_limit: 2, retain_changelogs: false, fragment: nil, tag_pattern: nil)
62
+ version_updater = VersionUpdater.new(version_file)
63
+ version_updater.set_version("Unreleased", version_file:)
64
+ version_updater.update_release_date("Unreleased", version_file:)
65
+
66
+ if changelog_file
67
+ changelog_updater = ChangelogUpdater.new(changelog_file)
68
+ changelog_updater.call("Unreleased", date: nil, changes: {}, changelog_file:, version_limit:, retain_changelogs:, fragment:, tag_pattern:)
69
+ end
70
+
71
+ "Unreleased"
72
+ end
73
+
74
+ def self.deferred_finalize(date = Date.today, version: nil, segment: nil, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, tag_pattern: nil, version_file: nil, version_redo_proc: nil)
75
+ resolved_version = if version&.match?(/^\d/)
76
+ version
77
+ elsif segment || version
78
+ seg = segment || version
79
+ resolved = resolve_version_from_tag(seg.to_s, tag_pattern: tag_pattern, version_redo_proc: version_redo_proc)
80
+ unless resolved
81
+ raise "Cannot determine next version. No git tags found and no explicit version provided. " \
82
+ "Usage: rake reissue:finalize[patch] or rake reissue:finalize[1.2.0]"
83
+ end
84
+ resolved
85
+ elsif fragment == :git
86
+ handler = FragmentHandler.for(:git, tag_pattern: tag_pattern)
87
+ bump = handler.read_version_bump
88
+ if bump
89
+ resolved = resolve_version_from_tag(bump.to_s, tag_pattern: tag_pattern, version_redo_proc: version_redo_proc)
90
+ unless resolved
91
+ raise "Cannot determine next version. No git tags found and no explicit version provided. " \
92
+ "Usage: rake reissue:finalize[patch] or rake reissue:finalize[1.2.0]"
93
+ end
94
+ resolved
95
+ else
96
+ raise "Cannot determine next version. No git tags found and no version argument provided. " \
97
+ "Usage: rake reissue:finalize[patch] or rake reissue:finalize[1.2.0]"
98
+ end
99
+ else
100
+ raise "Cannot determine next version. No version argument provided. " \
101
+ "Usage: rake reissue:finalize[patch] or rake reissue:finalize[1.2.0]"
102
+ end
103
+
104
+ if version_file
105
+ version_updater = VersionUpdater.new(version_file)
106
+ version_updater.set_version(resolved_version, version_file: version_file)
107
+ version_updater.update_release_date(date.to_s, version_file: version_file)
108
+ end
109
+
110
+ changelog_updater = ChangelogUpdater.new(changelog_file)
111
+
112
+ if fragment
113
+ changelog = Parser.parse(File.read(changelog_file))
114
+ unreleased = changelog["versions"].find { |v| v["version"] == "Unreleased" }
115
+
116
+ if unreleased
117
+ changelog["versions"].delete(unreleased)
118
+ changelog_updater.instance_variable_set(:@changelog, changelog)
119
+ changelog_updater.write(changelog_file, retain_changelogs: false)
120
+
121
+ handler = FragmentHandler.for(fragment, tag_pattern: tag_pattern)
122
+ fragment_changes = handler.read
123
+
124
+ merged_changes = (unreleased["changes"] || {}).dup
125
+ fragment_changes.each do |section, entries|
126
+ merged_changes[section] ||= []
127
+ entries.each do |entry|
128
+ merged_changes[section] << entry unless merged_changes[section].include?(entry)
129
+ end
130
+ end
131
+
132
+ changelog_updater.update(
133
+ "Unreleased",
134
+ date: nil,
135
+ changes: merged_changes,
136
+ fragment: nil,
137
+ version_limit: changelog["versions"].size + 1
138
+ )
139
+ changelog_updater.write(changelog_file, retain_changelogs: false)
140
+ end
141
+ end
142
+
143
+ changelog = changelog_updater.finalize(date: date, changelog_file: changelog_file, retain_changelogs: retain_changelogs, resolved_version: resolved_version)
144
+ changelog["versions"].first.slice("version", "date").values
145
+ end
146
+
147
+ private_class_method def self.resolve_version_from_tag(segment, tag_pattern: nil, version_redo_proc: nil)
148
+ handler = FragmentHandler.for(:git, tag_pattern: tag_pattern)
149
+ last_version = handler.last_tag_version
150
+ return nil unless last_version
151
+
152
+ updater = VersionUpdater.new(File::NULL, version_redo_proc: version_redo_proc)
153
+ updater.redo(last_version, segment).to_s
154
+ end
155
+
61
156
  # Finalizes the changelog for an unreleased version to set the release date.
62
157
  #
63
158
  # @param date [String] The release date.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reissue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.18
4
+ version: 0.4.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay