kettle-family 1.3.3 → 1.3.4

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: 2fd15138050afedb68be9d6d294f7e3a01dc1554949c8607b1176ddf8d061581
4
- data.tar.gz: 272997658ef8c5a96b14fbc1dcc8d124b545abe50d5bc0ca423823c8dffc3ad8
3
+ metadata.gz: 99e6506531c29efe6259ee3f592eb50d654eeef9f7074a1179f4f61960d7efdd
4
+ data.tar.gz: d3424e4481d1ced32dbb89228de517e3a0adf30a099883879c16fbb9f6feec8e
5
5
  SHA512:
6
- metadata.gz: a5bac59bea82eec800e67069ae00f8810c54a29a7fe3119b4c9ef93f94924af677b0da411635928841d9c0df47d4bfb11853d622cccaf1c72faf6f6419a552d6
7
- data.tar.gz: c4d9b06b6fed1f0822bdc62ec6d22b3cb379bc1fe7b49e388038e58780c567ecbe3f589e11faa7845e92b2b6b7d365499271f17d0ad8e942636d632f25dbf950
6
+ metadata.gz: dfe9f450d1c65671d3599bfe3625952dc7106cfbfe896960bc236d0df04c033dd73116a94b49b5dba82502022ff503339e7a1457ff6c507888df0736489b5dd7
7
+ data.tar.gz: 4fc7889e44bd08e673899b0f3ea040ecec5129e4b90ef2ef166f11c51de0e4a05c2066036a8a600bf9dc9e35fd495f95ac60f603799709ec08e0e74522f22ef8
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,23 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.3.4] - 2026-10-02
34
+
35
+ - TAG: [v1.3.4][1.3.4t]
36
+ - COVERAGE: 93.26% -- 6571/7046 lines in 37 files
37
+ - BRANCH COVERAGE: 77.70% -- 2666/3431 branches in 37 files
38
+ - 33.33% documented
39
+
40
+ ### Added
41
+
42
+ - kettle-family installs the latest stable Bundler and updates family lockfiles with `update-bundler`.
43
+
44
+ ### Fixed
45
+
46
+ - Lock each repository's worktree registry across process setup, parallel branch probes, and cleanup to prevent intermittent Git worktree failures.
47
+
48
+ - Family reports now include results from the update-bundler command.
49
+
33
50
  ## [1.3.3] - 2026-09-28
34
51
 
35
52
  - TAG: [v1.3.3][1.3.3t]
@@ -2830,7 +2847,9 @@ Please file a bug if you notice a violation of semantic versioning.
2830
2847
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
2831
2848
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
2832
2849
 
2833
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.3...HEAD
2850
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.4...HEAD
2851
+ [1.3.4]: https://github.com/kettle-dev/kettle-family/compare/v1.3.3...v1.3.4
2852
+ [1.3.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.4
2834
2853
  [1.3.3]: https://github.com/kettle-dev/kettle-family/compare/v1.3.2...v1.3.3
2835
2854
  [1.3.3t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.3
2836
2855
  [1.3.2]: https://github.com/kettle-dev/kettle-family/compare/v1.3.1...v1.3.2
data/README.md CHANGED
@@ -510,6 +510,7 @@ The command names below are available from `kettle-family --help`:
510
510
  | `gha-sha-pins` | Inspect or update GitHub Actions SHA pins. |
511
511
  | `bup` | Run `bundle update --all`, or update a named dependency, at the family root and per member. |
512
512
  | `bupb` | Run `bundle update --bundler` at the family root and per member. |
513
+ | `update-bundler` | Install the latest stable Bundler from RubyGems, then update family lockfiles. |
513
514
  | `bex` | Run an arbitrary `bundle exec` command per member. |
514
515
  | `install` | Build and install selected local family gems. |
515
516
  | `bump` | Check, plan, or execute family version alignment. |
@@ -614,6 +615,11 @@ root-first order with `bundle update --bundler`. A root that is itself a member
614
615
  is updated only once. A gem name may be passed to `bup` to update only that
615
616
  dependency, for example `kettle-family bup --execute kettle-test`.
616
617
 
618
+ `update-bundler` installs the latest stable Bundler from `https://rubygems.org`
619
+ without enabling prereleases, then runs the same root-first lockfile update as
620
+ `bupb`. Family mise tasks select an installed stable Bundler explicitly to
621
+ start the command, so an installed prerelease cannot intercept startup.
622
+
617
623
  Executed member updates commit successful lockfile changes by default. The
618
624
  family-root lockfile is also validated and committed for monorepos, where the
619
625
  root and members share one Git repository. A sibling-repository family root is
@@ -1186,7 +1192,7 @@ Thanks for RTFM. ☺️
1186
1192
  [📌gitmoji]: https://gitmoji.dev
1187
1193
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1188
1194
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1189
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-7.011-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1195
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-7.046-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1190
1196
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
1191
1197
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1192
1198
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -4,14 +4,15 @@ require "command_kit"
4
4
  require "command_kit/commands"
5
5
  require "fileutils"
6
6
  require "optparse"
7
+ require "rubygems/dependency_installer"
7
8
 
8
9
  module Kettle
9
10
  module Family
10
11
  class CLI < CommandKit::Command
11
12
  include CommandKit::Commands
12
13
 
13
- COMMANDS = %w[version mise-trust discover plan report metadata check clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
14
- WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb bex release push pull sync up].freeze
14
+ COMMANDS = %w[version mise-trust discover plan report metadata check clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb update-bundler bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
15
+ WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb update-bundler bex release push pull sync up].freeze
15
16
 
16
17
  command_name "kettle-family"
17
18
  usage "[options] COMMAND [ARGS...]"
@@ -434,6 +435,31 @@ module Kettle
434
435
  description "Plan or execute bundle update --bundler."
435
436
  end
436
437
 
438
+ class UpdateBundler < WorkflowCommand
439
+ command_name "update-bundler"
440
+ usage "[options]"
441
+ description "Install the latest stable Bundler, then update family lockfiles."
442
+
443
+ def run(*args)
444
+ unexpected_arguments!(args)
445
+ install_latest_stable_bundler if truthy_option?(:execute)
446
+ run_family("update-bundler")
447
+ end
448
+
449
+ private
450
+
451
+ def install_latest_stable_bundler
452
+ original_sources = Gem.sources.to_a
453
+ Gem.sources = ["https://rubygems.org"]
454
+ Gem::DependencyInstaller.new(domain: :remote, prerelease: false, document: []).install("bundler")
455
+ Gem::Specification.reset
456
+ rescue => error
457
+ raise Error, "could not install latest stable Bundler from RubyGems: #{error.message}"
458
+ ensure
459
+ Gem.sources = original_sources if original_sources
460
+ end
461
+ end
462
+
437
463
  class Bex < WorkflowCommand
438
464
  command_name "bex"
439
465
  usage "[options] -- COMMAND [ARGS...]"
@@ -626,6 +652,7 @@ module Kettle
626
652
  command GhaShaPins
627
653
  command Bup
628
654
  command Bupb
655
+ command UpdateBundler
629
656
  command Bex
630
657
  command Install
631
658
  command Bump
@@ -23,6 +23,7 @@ module Kettle
23
23
  ].freeze
24
24
  TRANSFER_CHANGELOG_TOTAL_UNSET = Object.new.freeze
25
25
  CACHE_MISS = Object.new.freeze
26
+ WORKTREE_ADMIN_MUTEX = Mutex.new
26
27
 
27
28
  def initialize(members:, config: nil, jobs: nil)
28
29
  @members = members
@@ -57,21 +58,30 @@ module Kettle
57
58
  def branch_results(event_handler: nil)
58
59
  root = git_root
59
60
  selected_names = members.map(&:name)
60
- parallel_map(release_target_branches) do |branch|
61
- branch_results_for(branch: branch, root: root, selected_names: selected_names, event_handler: event_handler)
62
- end.flatten
61
+ with_branch_worktrees(root: root, branches: release_target_branches) do |entries|
62
+ parallel_map(entries) do |entry|
63
+ if entry[:error]
64
+ error_result(branch: entry[:branch], error: entry[:error])
65
+ else
66
+ branch_results_for_worktree(
67
+ branch: entry[:branch],
68
+ worktree_root: entry[:worktree_root],
69
+ selected_names: selected_names,
70
+ event_handler: event_handler
71
+ )
72
+ end
73
+ end.flatten
74
+ end
63
75
  end
64
76
 
65
- def branch_results_for(branch:, root:, selected_names:, event_handler:)
66
- with_branch_worktree(root: root, branch: branch) do |worktree_root|
67
- branch_members = discover_branch_members(worktree_root: worktree_root, selected_names: selected_names)
68
- if shared_changelog?
69
- results = parallel_map(branch_members) { |member| check_shared_changelog_member_or_local(member, branch: branch, event_handler: event_handler) }
70
- next normalize_shared_version_bump(results)
71
- end
72
-
73
- parallel_map(branch_members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
77
+ def branch_results_for_worktree(branch:, worktree_root:, selected_names:, event_handler:)
78
+ branch_members = discover_branch_members(worktree_root: worktree_root, selected_names: selected_names)
79
+ if shared_changelog?
80
+ results = parallel_map(branch_members) { |member| check_shared_changelog_member_or_local(member, branch: branch, event_handler: event_handler) }
81
+ return normalize_shared_version_bump(results)
74
82
  end
83
+
84
+ parallel_map(branch_members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
75
85
  rescue Error => error
76
86
  [error_result(branch: branch, error: error)]
77
87
  end
@@ -826,25 +836,52 @@ module Kettle
826
836
  File.realpath(stdout.strip)
827
837
  end
828
838
 
829
- def with_branch_worktree(root:, branch:)
830
- base = File.join(root, "tmp", "kettle-family-release-state")
831
- FileUtils.mkdir_p(base)
832
- worktree_root = File.join(base, "worktree-#{Process.pid}-#{SecureRandom.hex(8)}")
833
- add_branch_worktree(root: root, branch: branch, worktree_root: worktree_root)
834
- yield worktree_root
835
- ensure
836
- remove_branch_worktree(root: root, worktree_root: worktree_root)
839
+ def with_branch_worktrees(root:, branches:)
840
+ with_worktree_registry_lock(root: root) do
841
+ base = File.join(root, "tmp", "kettle-family-release-state")
842
+ FileUtils.mkdir_p(base)
843
+ entries = branches.map do |branch|
844
+ {branch: branch, worktree_root: File.join(base, "worktree-#{Process.pid}-#{SecureRandom.hex(8)}")}
845
+ end
846
+ entries.each do |entry|
847
+ add_branch_worktree(root: root, branch: entry[:branch], worktree_root: entry[:worktree_root])
848
+ rescue Error => error
849
+ entry[:error] = error
850
+ end
851
+ yield entries
852
+ ensure
853
+ entries&.reverse_each do |entry|
854
+ remove_branch_worktree(root: root, worktree_root: entry[:worktree_root])
855
+ end
856
+ end
857
+ end
858
+
859
+ def with_worktree_registry_lock(root:)
860
+ stdout, stderr, status = Open3.capture3("git", "rev-parse", "--git-common-dir", chdir: root)
861
+ raise Error, "could not determine git common directory for #{root}: #{stderr}" unless status.success?
862
+
863
+ lock_path = File.join(File.expand_path(stdout.strip, root), "kettle-family-worktree.lock")
864
+ File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock_file|
865
+ lock_file.flock(File::LOCK_EX)
866
+ yield
867
+ ensure
868
+ lock_file&.flock(File::LOCK_UN)
869
+ end
837
870
  end
838
871
 
839
872
  def add_branch_worktree(root:, branch:, worktree_root:)
840
- _stdout, stderr, status = Open3.capture3("git", "worktree", "add", "--detach", worktree_root, branch, chdir: root)
841
- raise Error, "could not add worktree for #{branch}: #{stderr}" unless status.success?
873
+ WORKTREE_ADMIN_MUTEX.synchronize do
874
+ _stdout, stderr, status = Open3.capture3("git", "worktree", "add", "--detach", worktree_root, branch, chdir: root)
875
+ raise Error, "could not add worktree for #{branch}: #{stderr}" unless status.success?
876
+ end
842
877
  end
843
878
 
844
879
  def remove_branch_worktree(root:, worktree_root:)
845
880
  return unless worktree_root && Dir.exist?(worktree_root)
846
881
 
847
- Open3.capture3("git", "worktree", "remove", "--force", worktree_root, chdir: root)
882
+ WORKTREE_ADMIN_MUTEX.synchronize do
883
+ Open3.capture3("git", "worktree", "remove", "--force", worktree_root, chdir: root)
884
+ end
848
885
  end
849
886
 
850
887
  def discover_branch_members(worktree_root:, selected_names:)
@@ -29,6 +29,7 @@ module Kettle
29
29
  template
30
30
  test
31
31
  up
32
+ update-bundler
32
33
  ].freeze
33
34
 
34
35
  attr_reader :family_name, :family_mode, :order_mode, :members, :selected_members, :config_path, :command, :results, :branch_lanes, :release_target_branches, :member_release_target_branches, :release_mode, :release_resume_arguments, :warnings, :event_log_dirs
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "1.3.3"
8
+ VERSION = "1.3.4"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -574,7 +574,7 @@ module Kettle
574
574
 
575
575
  runner = CommandRunner.new(execute: execute, accept: accept)
576
576
  results = []
577
- update_family_root_bundle(runner: runner, memo: results) if %w[bup bupb].include?(command)
577
+ update_family_root_bundle(runner: runner, memo: results) if %w[bup bupb update-bundler].include?(command)
578
578
  return results unless results.all?(&:ok?)
579
579
 
580
580
  if command == "gha-sha-pins" && execute
@@ -607,10 +607,10 @@ module Kettle
607
607
 
608
608
  normalize_lockfiles(member: member, runner: runner, memo: memo, phase: "normalize_lockfiles") if command == "template"
609
609
  commit_gha_sha_pins(member: member, runner: runner, memo: memo) if command == "gha-sha-pins"
610
- if command == "bupb"
610
+ if %w[bupb update-bundler].include?(command)
611
611
  bupb_appraisal_results(member: member, runner: runner, memo: memo)
612
612
  end
613
- if %w[bup bupb].include?(command) && memo.last&.ok? && validate_bundle_update_lockfile(member: member, memo: memo)
613
+ if %w[bup bupb update-bundler].include?(command) && memo.last&.ok? && validate_bundle_update_lockfile(member: member, memo: memo)
614
614
  commit_bundle_update(member: member, runner: runner, memo: memo)
615
615
  end
616
616
  commit_bex_changes(member: member, runner: runner, memo: memo) if command == "bex"
@@ -4083,7 +4083,7 @@ module Kettle
4083
4083
  return gha_sha_pins_command(command_text: command_text)
4084
4084
  end
4085
4085
  return bup_command if command == "bup"
4086
- return bupb_command if command == "bupb"
4086
+ return bupb_command if %w[bupb update-bundler].include?(command)
4087
4087
  return bex_command if command == "bex"
4088
4088
 
4089
4089
  command_for(command)
@@ -4108,11 +4108,11 @@ module Kettle
4108
4108
 
4109
4109
  def stable_bundler_version
4110
4110
  active_version = Gem.loaded_specs["bundler"]&.version
4111
- return active_version if active_version && !active_version.prerelease?
4112
-
4113
4111
  Gem::Specification
4114
4112
  .find_all_by_name("bundler")
4115
4113
  .map(&:version)
4114
+ .push(active_version)
4115
+ .compact
4116
4116
  .reject(&:prerelease?)
4117
4117
  .max
4118
4118
  end
@@ -4358,7 +4358,7 @@ module Kettle
4358
4358
  end
4359
4359
 
4360
4360
  def command_env
4361
- return bundle_update_env if %w[bup bupb].include?(command)
4361
+ return bundle_update_env if %w[bup bupb update-bundler].include?(command)
4362
4362
 
4363
4363
  workflow_env
4364
4364
  end
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: 1.3.3
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -94,7 +94,7 @@ dependencies:
94
94
  version: '3.1'
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: 3.1.4
97
+ version: 3.1.5
98
98
  type: :runtime
99
99
  prerelease: false
100
100
  version_requirements: !ruby/object:Gem::Requirement
@@ -104,7 +104,7 @@ dependencies:
104
104
  version: '3.1'
105
105
  - - ">="
106
106
  - !ruby/object:Gem::Version
107
- version: 3.1.4
107
+ version: 3.1.5
108
108
  - !ruby/object:Gem::Dependency
109
109
  name: kettle-gha-pins
110
110
  requirement: !ruby/object:Gem::Requirement
@@ -459,10 +459,10 @@ licenses:
459
459
  - AGPL-3.0-only
460
460
  metadata:
461
461
  homepage_uri: https://kettle-family.galtzo.com
462
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.3
463
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.3/CHANGELOG.md
462
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.4
463
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.4/CHANGELOG.md
464
464
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
465
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.3
465
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.4
466
466
  funding_uri: https://github.com/sponsors/pboling
467
467
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
468
468
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file