kettle-family 0.1.27 → 0.1.28

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: e9e37861434b48448ce84ade0fe78e8f58f774ef8bf69c3b58cd15db95b7fec7
4
- data.tar.gz: 30c6f85a22d4af9d7590e703f74d22d021582cbe55425b8f576397f6e19cd27a
3
+ metadata.gz: 064f3f14703f9a178aba7cb549a0c55139f52397caa413adafa1137e44e504bb
4
+ data.tar.gz: c1c7ae833d6b815d7f7a52ba287fa186d51be53b09ab4e91d442b2cc6e94ab44
5
5
  SHA512:
6
- metadata.gz: 1493009541e59ff4b55d746edff3f44fb9a063eb1feb3e2f6a3d71a271c70ca91210be347141074fcae29b0d42cd357a6ce66bcf7a55b34bd35cf996309e2bff
7
- data.tar.gz: 0bac251bdbe718310a6a0b1026e04c40d1922df2489e0626270af541c83612e00745f67c56c38ee0d851f9eaa6eb3b380636c25389e6e6d6fea1291b63d59aa7
6
+ metadata.gz: 9c71cc770c2d719561d0d6ef91d4c43a329ac75559cebaaf62732efbc0dd02f03e52684d9fa60ff26ac5ea1ad785c51af7e33e56573dc270b6def694d9bfec39
7
+ data.tar.gz: 9a4bc82e5d51c3ff52ccc5b93243c6c36a55e4769b29c0f17f320f1386f1336908acddca9e7dcf508e04680717262a63e6e80c47d5d5aaafdd569ba87fc0d12a
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,18 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.1.28] - 2026-06-28
34
+
35
+ - TAG: [v0.1.28][0.1.28t]
36
+ - COVERAGE: 95.24% -- 1899/1994 lines in 21 files
37
+ - BRANCH COVERAGE: 74.86% -- 658/879 branches in 21 files
38
+ - 38.37% documented
39
+
40
+ ### Added
41
+
42
+ - `kettle-family --start-at MEMBER@BRANCH` now resumes member-local and family
43
+ branch-stack workflows at a specific release target branch.
44
+
33
45
  ## [0.1.27] - 2026-06-28
34
46
 
35
47
  - TAG: [v0.1.27][0.1.27t]
@@ -483,7 +495,9 @@ Please file a bug if you notice a violation of semantic versioning.
483
495
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
484
496
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
485
497
 
486
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.27...HEAD
498
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.1.28...HEAD
499
+ [0.1.28]: https://github.com/kettle-dev/kettle-family/compare/v0.1.27...v0.1.28
500
+ [0.1.28t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.28
487
501
  [0.1.27]: https://github.com/kettle-dev/kettle-family/compare/v0.1.26...v0.1.27
488
502
  [0.1.27t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.1.27
489
503
  [0.1.26]: https://github.com/kettle-dev/kettle-family/compare/v0.1.25...v0.1.26
data/README.md CHANGED
@@ -571,7 +571,7 @@ Thanks for RTFM. ☺️
571
571
  [📌gitmoji]: https://gitmoji.dev
572
572
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
573
573
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
574
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.949-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
574
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.994-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
575
575
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
576
576
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
577
577
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -81,7 +81,7 @@ module Kettle
81
81
  --root PATH Workspace or family root (default: current directory)
82
82
  --config PATH Family config path
83
83
  --only MEMBER Select exactly one member
84
- --start-at NAME Select from member through the end of order
84
+ --start-at NAME Select from member through the end of order; use MEMBER@BRANCH for branch stacks
85
85
  --json Print JSON report to stdout
86
86
  --report PATH Write JSON report to PATH
87
87
  --execute Execute external workflow commands
@@ -145,7 +145,7 @@ module Kettle
145
145
  parser.on("--root PATH") { |value| options[:root] = value }
146
146
  parser.on("--config PATH") { |value| options[:config] = value }
147
147
  parser.on("--only MEMBER") { |value| options[:only] = value }
148
- parser.on("--start-at MEMBER") { |value| options[:start_at] = value }
148
+ parser.on("--start-at MEMBER[@BRANCH]") { |value| options[:start_at] = value }
149
149
  parser.on("--json") { options[:json] = true }
150
150
  parser.on("--report PATH") { |value| options[:report] = value }
151
151
  parser.on("--execute") { options[:execute] = true }
@@ -180,6 +180,7 @@ module Kettle
180
180
 
181
181
  def build_report(command, options)
182
182
  config = Config.load(root: options[:root], path: options[:config])
183
+ start_at = parse_start_at(options[:start_at])
183
184
  members = Discovery.new(config: config).members
184
185
  ordered = if command == "install"
185
186
  install_order(members, config)
@@ -188,13 +189,13 @@ module Kettle
188
189
  else
189
190
  Orderer.new(members: members, mode: config.order_mode, hints: config.order_hints).ordered
190
191
  end
191
- selected = Selection.new(members: ordered).apply(only: options[:only], start_at: options[:start_at])
192
+ selected = Selection.new(members: ordered).apply(only: options[:only], start_at: start_at.member)
192
193
  result_members = if command == "branch-lanes"
193
194
  ordered
194
195
  else
195
196
  selected
196
197
  end
197
- results = command_results(command: command, config: config, members: result_members, options: options)
198
+ results = command_results(command: command, config: config, members: result_members, options: options, start_at: start_at)
198
199
  Report.new(
199
200
  family_name: config.family_name,
200
201
  family_mode: config.family_mode,
@@ -203,22 +204,24 @@ module Kettle
203
204
  selected_members: selected,
204
205
  config_path: config.path,
205
206
  branch_lanes: config.branch_lanes,
206
- release_target_branches: BranchTargetConfig.branch_targets_for(command, config.release_target_branches),
207
- member_release_target_branches: member_release_target_branches(command: command, members: selected, config: config),
207
+ release_target_branches: release_target_branches(command: command, config: config, start_at: start_at),
208
+ member_release_target_branches: member_release_target_branches(command: command, members: selected, config: config, start_at: start_at),
208
209
  release_mode: release_mode(command: command, options: options),
209
210
  command: command,
210
211
  results: results
211
212
  )
212
213
  end
213
214
 
214
- def command_results(command:, config:, members:, options:)
215
- return branch_target_command_results(command: command, config: config, members: members, options: options) if branch_target_command?(command, config)
216
- return member_local_branch_target_command_results(command: command, config: config, members: members, options: options) if member_local_branch_target_command?(command, config, members)
215
+ StartAt = Struct.new(:member, :branch)
217
216
 
218
- command_results_for_current_branch(command: command, config: config, members: members, options: options)
217
+ def command_results(command:, config:, members:, options:, start_at:)
218
+ return branch_target_command_results(command: command, config: config, members: members, options: options, start_at: start_at) if branch_target_command?(command, config)
219
+ return member_local_branch_target_command_results(command: command, config: config, members: members, options: options, start_at: start_at) if member_local_branch_target_command?(command, config, members)
220
+
221
+ command_results_for_current_branch(command: command, config: config, members: members, options: options, start_at: start_at)
219
222
  end
220
223
 
221
- def command_results_for_current_branch(command:, config:, members:, options:)
224
+ def command_results_for_current_branch(command:, config:, members:, options:, start_at: StartAt.new(nil, nil))
222
225
  return bump_version_results(members: members, options: options) if command == "bump-version"
223
226
  return add_changelog_results(members: members, options: options) if command == "add-changelog"
224
227
  return branch_lane_results(config: config, members: members) if command == "branch-lanes"
@@ -247,7 +250,9 @@ module Kettle
247
250
  debug: options[:debug],
248
251
  jobs: options[:jobs],
249
252
  progress_io: progress_io(command, options),
250
- bup_args: options[:bup_args]
253
+ bup_args: options[:bup_args],
254
+ start_member: start_at.member,
255
+ start_branch: start_at.branch
251
256
  ).results
252
257
  end
253
258
 
@@ -283,20 +288,23 @@ module Kettle
283
288
  members.any? { |member| member_local_release_config(member: member, config: config) }
284
289
  end
285
290
 
286
- def branch_target_command_results(command:, config:, members:, options:)
291
+ def branch_target_command_results(command:, config:, members:, options:, start_at:)
287
292
  runner = CommandRunner.new(execute: options[:execute])
288
293
  selected_names = members.map(&:name)
289
- BranchTargetConfig.branch_targets_for(command, config.release_target_branches).each_with_object([]) do |branch, memo|
294
+ release_target_branches(command: command, config: config, start_at: start_at).each_with_object([]) do |branch, memo|
290
295
  memo << runner.call(
291
296
  member: family_member(config),
292
297
  phase: "release_checkout",
293
298
  command: ["git", "checkout", branch]
294
299
  )
300
+ memo.last.branch = branch
295
301
  break memo unless memo.last.ok?
296
302
 
297
303
  branch_members = rediscovered_selected_members(config: config, selected_names: selected_names, command: command)
298
304
  branch_members = members if branch_members.empty?
299
- memo.concat(command_results_for_current_branch(command: command, config: config, members: branch_members, options: options))
305
+ branch_results = command_results_for_current_branch(command: command, config: config, members: branch_members, options: options)
306
+ branch_results.each { |result| result.branch = branch if result.respond_to?(:branch=) }
307
+ memo.concat(branch_results)
300
308
  break memo unless memo.last&.ok?
301
309
 
302
310
  commit_changelog_entries(branch_members: branch_members, runner: runner, memo: memo) if command == "add-changelog"
@@ -304,7 +312,7 @@ module Kettle
304
312
  end
305
313
  end
306
314
 
307
- def member_local_branch_target_command_results(command:, config:, members:, options:)
315
+ def member_local_branch_target_command_results(command:, config:, members:, options:, start_at:)
308
316
  runner = CommandRunner.new(execute: options[:execute])
309
317
  members.each_with_object([]) do |member, memo|
310
318
  member_config = member_local_release_config(member: member, config: config)
@@ -314,17 +322,20 @@ module Kettle
314
322
  next
315
323
  end
316
324
 
317
- BranchTargetConfig.branch_targets_for(command, member_config.release_target_branches).each do |branch|
325
+ member_branch_targets(command: command, member: member, member_config: member_config, start_at: start_at).each do |branch|
318
326
  memo << runner.call(
319
327
  member: member,
320
328
  phase: "release_checkout",
321
329
  command: ["git", "checkout", branch]
322
330
  )
331
+ memo.last.branch = branch
323
332
  break unless memo.last.ok?
324
333
 
325
334
  branch_members = rediscovered_selected_members(config: member_config, selected_names: [member.name], command: command)
326
335
  branch_members = [member] if branch_members.empty?
327
- memo.concat(command_results_for_current_branch(command: command, config: member_config, members: branch_members, options: options))
336
+ branch_results = command_results_for_current_branch(command: command, config: member_config, members: branch_members, options: options)
337
+ branch_results.each { |result| result.branch = branch if result.respond_to?(:branch=) }
338
+ memo.concat(branch_results)
328
339
  break unless memo.last&.ok?
329
340
  end
330
341
  break memo unless memo.last&.ok?
@@ -363,6 +374,16 @@ module Kettle
363
374
  raise OptionParser::InvalidArgument, "--upgrade must be one of: major, minor, patch"
364
375
  end
365
376
 
377
+ def parse_start_at(value)
378
+ return StartAt.new(nil, nil) unless value
379
+
380
+ member, branch = value.split("@", 2)
381
+ raise Error, "--start-at requires MEMBER before @BRANCH" if member.to_s.empty?
382
+ raise Error, "--start-at requires BRANCH after MEMBER@" if value.include?("@") && branch.to_s.empty?
383
+
384
+ StartAt.new(member, branch)
385
+ end
386
+
366
387
  def bump_version_results(members:, options:)
367
388
  results = VersionBump.new(
368
389
  members: members,
@@ -449,13 +470,36 @@ module Kettle
449
470
  options[:publish] ? "publish" : "build-only"
450
471
  end
451
472
 
452
- def member_release_target_branches(command:, members:, config:)
473
+ def release_target_branches(command:, config:, start_at:)
474
+ branch_targets = BranchTargetConfig.branch_targets_for(command, config.release_target_branches)
475
+ return branch_targets if branch_targets.empty?
476
+
477
+ slice_branch_targets(branch_targets, start_at.branch)
478
+ end
479
+
480
+ def member_release_target_branches(command:, members:, config:, start_at:)
453
481
  members.each_with_object({}) do |member, memo|
454
482
  member_config = member_local_release_config(member: member, config: config)
455
- memo[member.name] = BranchTargetConfig.branch_targets_for(command, member_config.release_target_branches) if member_config
483
+ memo[member.name] = member_branch_targets(command: command, member: member, member_config: member_config, start_at: start_at) if member_config
456
484
  end
457
485
  end
458
486
 
487
+ def member_branch_targets(command:, member:, member_config:, start_at:)
488
+ branch_targets = BranchTargetConfig.branch_targets_for(command, member_config.release_target_branches)
489
+ return branch_targets unless start_at.branch && start_at.member == member.name
490
+
491
+ slice_branch_targets(branch_targets, start_at.branch)
492
+ end
493
+
494
+ def slice_branch_targets(branch_targets, start_branch)
495
+ return branch_targets unless start_branch
496
+
497
+ index = branch_targets.index(start_branch)
498
+ raise Error, "unknown branch target #{start_branch.inspect}" unless index
499
+
500
+ branch_targets.drop(index)
501
+ end
502
+
459
503
  def member_local_release_config(member:, config:)
460
504
  BranchTargetConfig.member_local_release_config(member: member, config: config)
461
505
  end
@@ -13,11 +13,13 @@ module Kettle
13
13
  :stderr,
14
14
  :elapsed_seconds,
15
15
  :skipped,
16
- :reason
16
+ :reason,
17
+ :branch
17
18
  ) do
18
19
  def to_h
19
20
  {
20
21
  "member" => member_name,
22
+ "branch" => branch,
21
23
  "phase" => phase,
22
24
  "command" => command,
23
25
  "workdir" => workdir,
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Family
5
5
  module Version
6
- VERSION = "0.1.27"
6
+ VERSION = "0.1.28"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
@@ -44,7 +44,7 @@ module Kettle
44
44
  "BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
45
45
  }.freeze
46
46
 
47
- def initialize(command:, config:, members:, execute: false, accept: true, commit: true, allow_dirty: false, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, local_ci: false, continue_ci_failures: false, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, gem_signing_password: nil, jobs: nil, progress_io: nil, bup_args: [])
47
+ def initialize(command:, config:, members:, execute: false, accept: true, commit: true, allow_dirty: false, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, local_ci: false, continue_ci_failures: false, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, gem_signing_password: nil, jobs: nil, progress_io: nil, bup_args: [], start_member: nil, start_branch: nil)
48
48
  @command = command
49
49
  @config = config
50
50
  @members = members
@@ -67,6 +67,8 @@ module Kettle
67
67
  @jobs = jobs
68
68
  @progress_io = progress_io
69
69
  @bup_args = bup_args
70
+ @start_member = start_member
71
+ @start_branch = start_branch
70
72
  end
71
73
 
72
74
  def results
@@ -79,7 +81,7 @@ module Kettle
79
81
 
80
82
  private
81
83
 
82
- attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :jobs, :progress_io, :bup_args
84
+ attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :jobs, :progress_io, :bup_args, :start_member, :start_branch
83
85
 
84
86
  def current_branch_results(workflow_members)
85
87
  return check_results(workflow_members) if command == "check"
@@ -175,10 +177,13 @@ module Kettle
175
177
 
176
178
  branch_members = rediscovered_selected_members(selected_names)
177
179
  branch_members = members if branch_members.empty?
178
- memo.concat(current_branch_results(branch_members))
180
+ branch_results = current_branch_results(branch_members)
181
+ tag_branch_results(branch_results, branch)
182
+ memo.concat(branch_results)
179
183
  break memo unless memo.last&.ok?
180
184
 
181
185
  commit_normalized_lockfiles(branch_members: branch_members, runner: runner, memo: memo)
186
+ tag_branch_results(memo.last(1), branch)
182
187
  break memo unless memo.last&.ok?
183
188
  end
184
189
  end
@@ -236,10 +241,18 @@ module Kettle
236
241
  gem_signing_password: @gem_signing_password,
237
242
  jobs: jobs,
238
243
  progress_io: progress_io,
239
- bup_args: bup_args
244
+ bup_args: bup_args,
245
+ start_member: start_member,
246
+ start_branch: start_branch_for_member(member)
240
247
  )
241
248
  end
242
249
 
250
+ def start_branch_for_member(member)
251
+ return unless member.name == start_member
252
+
253
+ start_branch
254
+ end
255
+
243
256
  def release_member_results(release_members, include_family_changelog: false)
244
257
  runner = release_command_runner
245
258
  results = []
@@ -400,7 +413,17 @@ module Kettle
400
413
  end
401
414
 
402
415
  def branch_targets
403
- BranchTargetConfig.branch_targets_for(command, config.release_target_branches)
416
+ targets = BranchTargetConfig.branch_targets_for(command, config.release_target_branches)
417
+ slice_branch_targets(targets, start_branch)
418
+ end
419
+
420
+ def slice_branch_targets(targets, branch)
421
+ return targets unless branch
422
+
423
+ index = targets.index(branch)
424
+ raise Error, "unknown branch target #{branch.inspect}" unless index
425
+
426
+ targets.drop(index)
404
427
  end
405
428
 
406
429
  def member_local_release_config(member)
@@ -408,11 +431,17 @@ module Kettle
408
431
  end
409
432
 
410
433
  def checkout_branch_result(branch:, runner:)
411
- runner.call(
434
+ result = runner.call(
412
435
  member: family_member,
413
436
  phase: "release_checkout",
414
437
  command: ["git", "checkout", branch]
415
438
  )
439
+ result.branch = branch
440
+ result
441
+ end
442
+
443
+ def tag_branch_results(results, branch)
444
+ results.each { |result| result.branch = branch if result.respond_to?(:branch=) }
416
445
  end
417
446
 
418
447
  def append_release_internal_checks(member:, memo:)
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-family
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.27
4
+ version: 0.1.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -310,10 +310,10 @@ licenses:
310
310
  - AGPL-3.0-only
311
311
  metadata:
312
312
  homepage_uri: https://kettle-family.galtzo.com
313
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.27
314
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.27/CHANGELOG.md
313
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.1.28
314
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.1.28/CHANGELOG.md
315
315
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
316
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.27
316
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.1.28
317
317
  funding_uri: https://github.com/sponsors/pboling
318
318
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
319
319
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file