reissue 0.4.17 → 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: b5665bd9b120f74096ef018dcb84b1976a3778158601b037f73f5c5b2529e0a5
4
- data.tar.gz: 1bd41e2eb052e9e64f04b2142b990b9c90f4812f7419db4aba0b88f89ab19c50
3
+ metadata.gz: 8c46dadfd4d0a4dcaacebe8bc3520bb390632354008a869dd89d89bd052dc51f
4
+ data.tar.gz: ef50ffe309580748f40293da43f9d8475def2fae718a1d487e1714c77132bb09
5
5
  SHA512:
6
- metadata.gz: d016635c53c1b5df5bf12fb89b7015e001a0d47338a806f3a4826fa7dd728cf49df6e6b92008a7ec8d48b1d3a2690c37cf2220893882560f7e4d36c164bd6691
7
- data.tar.gz: 230f4b4f1d62978286290fd58865242fb630a0b6efc198f4e3061d84c922dffc96c7319043b4af3cbb8522e38bb75a8aec428c8394761833951b252cfd9636ef
6
+ metadata.gz: 381cd331c021a6ba92eae1fa976dc411922b9f9b1c963eb43ff31fe63e58b6bad6153f2915ed76bd6ca92185ef20233aa4606cf5205a24363dcb2857da204f57
7
+ data.tar.gz: 37ce464c665abc6521c1defec054ee88dc56e50ee6779473849af00176a0428b9e6f2f4bd259d5deaca0f4d84f986ad4c4029cd60462fbc02df465fefbe72542
data/CHANGELOG.md CHANGED
@@ -5,14 +5,36 @@ 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.17] - 2026-02-24
8
+ ## [0.4.19] - 2026-03-09
9
9
 
10
- ### Fixed
10
+ ### Added
11
11
 
12
- - Forward tag_pattern to git fragment handler in call and finalize (dd35249)
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)
13
24
 
14
- ## [0.4.16] - 2026-02-24
25
+ ### Changed
15
26
 
16
- ### Fixed
27
+ - set_version uses RELEASE_VERSION_MATCH paralleling RELEASE_DATE_MATCH pattern (efec0e5)
17
28
 
18
- - Run bundle install after reissue:bump changes the version (104bfd8)
29
+ ## [0.4.18] - 2026-02-25
30
+
31
+ ### Added
32
+
33
+ - RELEASE_DATE constant tracking in VersionUpdater (496ec6b)
34
+ - Reset RELEASE_DATE to Unreleased when bumping version via Reissue.call (a7894cc)
35
+ - Update RELEASE_DATE to actual date during Reissue.finalize (8253796)
36
+ - RELEASE_DATE to the version.rb (6e08782)
37
+
38
+ ### Changed
39
+
40
+ - Pass version_file to Reissue.finalize from rake task (83682ff)
data/README.md CHANGED
@@ -170,6 +170,28 @@ Reissue::Task.create :reissue do |task|
170
170
  end
171
171
  ```
172
172
 
173
+ ## Tracking Release Dates
174
+
175
+ Reissue can automatically manage a `RELEASE_DATE` constant in your version file alongside `VERSION`. This is completely optional — if no `RELEASE_DATE` is present, nothing changes.
176
+
177
+ ### Opting In
178
+
179
+ Add a `RELEASE_DATE` constant to your version file:
180
+
181
+ ```ruby
182
+ module MyGem
183
+ VERSION = "0.1.0"
184
+ RELEASE_DATE = "Unreleased"
185
+ end
186
+ ```
187
+
188
+ ### What Happens Automatically
189
+
190
+ - **On finalize** (`rake build` / `rake reissue:finalize`): `RELEASE_DATE` is set to the actual release date (e.g., `"2024-06-15"`)
191
+ - **On bump** (`rake reissue` / post-release): `RELEASE_DATE` is reset to `"Unreleased"`
192
+
193
+ No configuration is needed — Reissue detects the constant and updates it automatically.
194
+
173
195
  ## Using Git Trailers for Changelog Entries
174
196
 
175
197
  Reissue can extract changelog entries directly from git commit messages using trailers. This keeps your changelog data close to the code changes.
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,19 +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
- )
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
+
252
304
  finalize_message = "Finalize the changelog for version #{version} on #{date}"
253
305
  if commit_finalize
254
306
  if finalize_with_branch?
255
- # Use "finalize/" prefix for the version being released
256
307
  tasker["#{name}:branch"].invoke("finalize/#{version}")
257
308
  end
258
309
  run_command("git add -u", "Failed to stage finalized changelog")
@@ -360,6 +411,7 @@ module Reissue
360
411
 
361
412
  desc "Bump version based on git trailers"
362
413
  task "#{name}:bump" do
414
+ next if deferred_versioning
363
415
  # Only check for version trailers when using git fragments
364
416
  next unless fragment == :git
365
417
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reissue
4
- VERSION = "0.4.17"
4
+ VERSION = "0.4.19"
5
+ RELEASE_DATE = "2026-03-09"
5
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,6 +117,21 @@ 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*"([^"]*)"/
121
+ RELEASE_DATE_MATCH = /RELEASE_DATE\s*=\s*"([^"]*)"/
122
+
123
+ def release_version_regex = RELEASE_VERSION_MATCH
124
+ def release_date_regex = RELEASE_DATE_MATCH
125
+
126
+ def update_release_date(date, version_file: @version_file)
127
+ body = File.read(@version_file)
128
+ return unless body.match?(release_date_regex)
129
+ updated = body.gsub(release_date_regex) do |match|
130
+ match.sub(/=\s*"[^"]*"/, "= \"#{date}\"")
131
+ end
132
+ File.write(version_file, updated)
133
+ end
134
+
111
135
  # Writes the updated version to the specified file.
112
136
  #
113
137
  # @param version_file [String] The version_file to the version file (optional, defaults to @version_file).
data/lib/reissue.rb CHANGED
@@ -50,6 +50,7 @@ module Reissue
50
50
 
51
51
  version_updater = VersionUpdater.new(version_file, version_redo_proc:)
52
52
  new_version = version_updater.call(segment, version_file:)
53
+ version_updater.update_release_date("Unreleased", version_file:)
53
54
  if changelog_file
54
55
  changelog_updater = ChangelogUpdater.new(changelog_file)
55
56
  changelog_updater.call(new_version, date:, changes:, changelog_file:, version_limit:, retain_changelogs:, fragment:, tag_pattern:)
@@ -57,6 +58,101 @@ module Reissue
57
58
  new_version
58
59
  end
59
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
+
60
156
  # Finalizes the changelog for an unreleased version to set the release date.
61
157
  #
62
158
  # @param date [String] The release date.
@@ -65,7 +161,7 @@ module Reissue
65
161
  # @param fragment_directory [String] @deprecated Use fragment parameter instead
66
162
  #
67
163
  # @return [Array] The version number and release date.
68
- def self.finalize(date = Date.today, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, fragment_directory: nil, tag_pattern: nil)
164
+ def self.finalize(date = Date.today, changelog_file: "CHANGELOG.md", retain_changelogs: false, fragment: nil, fragment_directory: nil, tag_pattern: nil, version_file: nil)
69
165
  # Handle deprecation
70
166
  if fragment_directory && !fragment
71
167
  warn "[DEPRECATION] `fragment_directory` parameter is deprecated. Please use `fragment` instead."
@@ -119,6 +215,11 @@ module Reissue
119
215
  # Now finalize with the date
120
216
  changelog = changelog_updater.finalize(date:, changelog_file:, retain_changelogs:)
121
217
 
218
+ if version_file
219
+ version_updater = VersionUpdater.new(version_file)
220
+ version_updater.update_release_date(date.to_s, version_file:)
221
+ end
222
+
122
223
  changelog["versions"].first.slice("version", "date").values
123
224
  end
124
225
 
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.17
4
+ version: 0.4.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay