kettle-family 1.3.4 → 1.3.5
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +40 -1
- data/README.md +1 -1
- data/lib/kettle/family/cli.rb +63 -6
- data/lib/kettle/family/release_state_check.rb +63 -2
- data/lib/kettle/family/report.rb +1 -0
- data/lib/kettle/family/selection.rb +27 -5
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +81 -17
- data.tar.gz.sig +0 -0
- metadata +26 -26
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e18a444e22f9d52b19ef8396ce75bc80b85bb579fb353889fecb8547ddc8bec
|
|
4
|
+
data.tar.gz: b91bb8aa9018297be574e1eda7ddc26c6b8f314692cd4c4881c95fa4fe52062a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50d83077bd30c1c673772be79765f02d263cf499b382355f8b647889255abb9608155e8a0434d6c98d549ddad5188186f50e56b9608537e47b1cb5f2c1cfec5a
|
|
7
|
+
data.tar.gz: e81978953b9a9d94d2c518d770cd72e6b6efbcd5a3fd2c010da97f72cb9107d71624bd126cdf1799c1e17276f3546adaca3246089e151b6bccc7edc04db4cef2
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,43 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.3.5] - 2026-10-05
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.3.5][1.3.5t]
|
|
36
|
+
- COVERAGE: 93.16% -- 6636/7123 lines in 37 files
|
|
37
|
+
- BRANCH COVERAGE: 77.64% -- 2701/3479 branches in 37 files
|
|
38
|
+
- 33.64% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Kettle::Family::ReleaseStateCheck` accepts a `branches:` scope, and `Kettle::Family::Selection.branches_matching` reports which of a member branch stack currently satisfies a release-state token. Both are the seams for per-branch release and bump decisions.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- A blocked release now reports phase `release_blocked` instead of `release_skip`, so a hard failure is never reported as a skipped member in the progress summary or report.
|
|
47
|
+
|
|
48
|
+
- [kc] kettle-jem/prepare: updated 12 project files:
|
|
49
|
+
- dependencies (12)
|
|
50
|
+
|
|
51
|
+
- [kc] kettle-jem/template: updated 6 project files:
|
|
52
|
+
- code and tests (1)
|
|
53
|
+
- dependencies (1)
|
|
54
|
+
- documentation (1)
|
|
55
|
+
- other (2)
|
|
56
|
+
- workflows (1)
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- Already-released detection for branch-stack members is now scoped to the branch being released instead of the whole stack, so a clean published branch is no longer reported as having unreleased changes and blocked with a release_skip failure.
|
|
61
|
+
|
|
62
|
+
- A published version whose release tag is followed only by post-release maintenance commits (checksums, dependency-floor lockfile reconciliation) is now skipped as already released; only changes to the gemspec, version file, or changelog after the tag make the published version stale.
|
|
63
|
+
|
|
64
|
+
- Release-state status-token selection (`--only unreleased|prepared|pending|bump`) no longer collapses a branch-stack member to whichever branch happened to be evaluated last; a member is selected when any of its branches matches, and multiple tokens are still ANDed within a single branch.
|
|
65
|
+
|
|
66
|
+
- `kettle-family bump` now bumps each branch of a member branch stack independently: branches with no pending version bump are reported as skipped instead of being bumped, so an already-released branch is no longer given version bumps it never needed. An explicit `--only MEMBER` remains an unconditional request and is not filtered.
|
|
67
|
+
|
|
68
|
+
- Release-state worktrees orphaned by a killed run are reaped on the next run. Directories are named `worktree-PID-RANDOM`, so only those whose owning PID is no longer running are removed; live worktrees from concurrent runs are left alone.
|
|
69
|
+
|
|
33
70
|
## [1.3.4] - 2026-10-02
|
|
34
71
|
|
|
35
72
|
- TAG: [v1.3.4][1.3.4t]
|
|
@@ -2847,7 +2884,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
2847
2884
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
2848
2885
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
2849
2886
|
|
|
2850
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.
|
|
2887
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.5...HEAD
|
|
2888
|
+
[1.3.5]: https://github.com/kettle-dev/kettle-family/compare/v1.3.4...v1.3.5
|
|
2889
|
+
[1.3.5t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.5
|
|
2851
2890
|
[1.3.4]: https://github.com/kettle-dev/kettle-family/compare/v1.3.3...v1.3.4
|
|
2852
2891
|
[1.3.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.4
|
|
2853
2892
|
[1.3.3]: https://github.com/kettle-dev/kettle-family/compare/v1.3.2...v1.3.3
|
data/README.md
CHANGED
|
@@ -1192,7 +1192,7 @@ Thanks for RTFM. ☺️
|
|
|
1192
1192
|
[📌gitmoji]: https://gitmoji.dev
|
|
1193
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
|
|
1194
1194
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1195
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.
|
|
1195
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.123-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1196
1196
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
1197
1197
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1198
1198
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -28,6 +28,16 @@ module Kettle
|
|
|
28
28
|
main(argv, stdout: out, stderr: err)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Defined as a singleton method above the `private` keyword: a `private`
|
|
32
|
+
# instance modifier does not apply to `def self.` definitions.
|
|
33
|
+
def self.default_only_filter(command:, only:)
|
|
34
|
+
return only unless only.to_s.empty?
|
|
35
|
+
return "bump" if %w[bump bump-version].include?(command)
|
|
36
|
+
return "pending" if command == "release"
|
|
37
|
+
|
|
38
|
+
only
|
|
39
|
+
end
|
|
40
|
+
|
|
31
41
|
module SharedOptions
|
|
32
42
|
def self.included(base)
|
|
33
43
|
base.option :root, value: {type: String, usage: "PATH"}, desc: "Workspace or family root"
|
|
@@ -188,7 +198,12 @@ module Kettle
|
|
|
188
198
|
end
|
|
189
199
|
|
|
190
200
|
def run_family(command, overrides = {})
|
|
191
|
-
|
|
201
|
+
effective = family_options(overrides)
|
|
202
|
+
# Thread the *effective* --only (including the command's default
|
|
203
|
+
# release-state token) downstream so per-branch logic sees the same
|
|
204
|
+
# selection the member selector used.
|
|
205
|
+
effective[:only] = Kettle::Family::CLI.default_only_filter(command: command, only: effective[:only])
|
|
206
|
+
Kettle::Family::CLI.new(stdout: stdout, stderr: stderr).run_command(command, effective)
|
|
192
207
|
end
|
|
193
208
|
|
|
194
209
|
def truthy_option?(name)
|
|
@@ -822,11 +837,7 @@ module Kettle
|
|
|
822
837
|
end
|
|
823
838
|
|
|
824
839
|
def default_only_filter(command:, only:)
|
|
825
|
-
|
|
826
|
-
return "bump" if %w[bump bump-version].include?(command)
|
|
827
|
-
return "pending" if command == "release"
|
|
828
|
-
|
|
829
|
-
only
|
|
840
|
+
self.class.default_only_filter(command: command, only: only)
|
|
830
841
|
end
|
|
831
842
|
|
|
832
843
|
def direct_member_only_names(only)
|
|
@@ -1122,6 +1133,17 @@ module Kettle
|
|
|
1122
1133
|
memo.last.branch = branch
|
|
1123
1134
|
break unless memo.last.ok?
|
|
1124
1135
|
|
|
1136
|
+
if (skipped = branch_bump_skip_result(
|
|
1137
|
+
member: member,
|
|
1138
|
+
branch: branch,
|
|
1139
|
+
command: command,
|
|
1140
|
+
options: options,
|
|
1141
|
+
release_state_results: release_state_results
|
|
1142
|
+
))
|
|
1143
|
+
memo << skipped
|
|
1144
|
+
next
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1125
1147
|
branch_members = rediscovered_selected_members(config: member_config, selected_names: [member.name], command: command)
|
|
1126
1148
|
branch_members = [member] if branch_members.empty?
|
|
1127
1149
|
branch_results = command_results_for_current_branch(
|
|
@@ -1140,6 +1162,41 @@ module Kettle
|
|
|
1140
1162
|
end
|
|
1141
1163
|
end
|
|
1142
1164
|
|
|
1165
|
+
BUMP_COMMANDS = %w[bump bump-version].freeze
|
|
1166
|
+
|
|
1167
|
+
# Bumping a whole branch stack must be per-branch: a branch already
|
|
1168
|
+
# released with nothing pending has nothing to bump. `--only bump` is a
|
|
1169
|
+
# status-driven selection, so it is filtered by each branch's own
|
|
1170
|
+
# release-state. An explicit `--only MEMBER` is a direct request and is
|
|
1171
|
+
# honored as given.
|
|
1172
|
+
def branch_bump_skip_result(member:, branch:, command:, options:, release_state_results:)
|
|
1173
|
+
return unless BUMP_COMMANDS.include?(command)
|
|
1174
|
+
return unless status_token_only?(options[:only])
|
|
1175
|
+
|
|
1176
|
+
matching = Selection.branches_matching(release_state_results, member_name: member.name, tokens: %w[bump])
|
|
1177
|
+
return if matching.empty? || matching.include?(branch)
|
|
1178
|
+
|
|
1179
|
+
CommandResult.new(
|
|
1180
|
+
member_name: member.name,
|
|
1181
|
+
phase: command,
|
|
1182
|
+
command: ["internal", "branch-bump-skip", branch],
|
|
1183
|
+
workdir: member.root,
|
|
1184
|
+
status: 0,
|
|
1185
|
+
success: true,
|
|
1186
|
+
stdout: "#{member.name}@#{branch} has no unreleased entries at its released version; skipping bump on this branch",
|
|
1187
|
+
stderr: "",
|
|
1188
|
+
elapsed_seconds: 0.0,
|
|
1189
|
+
skipped: true,
|
|
1190
|
+
reason: "no version bump pending on #{branch}",
|
|
1191
|
+
branch: branch
|
|
1192
|
+
)
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
def status_token_only?(only)
|
|
1196
|
+
names = only.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
1197
|
+
!names.empty? && names.all? { |name| Selection.status_token?(name) }
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1143
1200
|
def rediscovered_selected_members(config:, selected_names:, command:)
|
|
1144
1201
|
discovered = Discovery.new(config: config).members
|
|
1145
1202
|
ordered = (command == "install") ? install_order(discovered, config) : Orderer.new(members: discovered, mode: config.order_mode, hints: config.order_hints).ordered
|
|
@@ -25,10 +25,16 @@ module Kettle
|
|
|
25
25
|
CACHE_MISS = Object.new.freeze
|
|
26
26
|
WORKTREE_ADMIN_MUTEX = Mutex.new
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# +branches+ scopes the check to specific release target branches. A
|
|
29
|
+
# branch-stack member produces one result per branch; a per-branch
|
|
30
|
+
# decision (is this branch already published? does it need a bump?) must
|
|
31
|
+
# never consult the whole stack, or every branch inherits the state of
|
|
32
|
+
# the branch that happens to have unreleased entries.
|
|
33
|
+
def initialize(members:, config: nil, jobs: nil, branches: nil)
|
|
29
34
|
@members = members
|
|
30
35
|
@config = config
|
|
31
36
|
@jobs = jobs
|
|
37
|
+
@branches = branches&.map(&:to_s)&.reject(&:empty?)
|
|
32
38
|
@github_latest_release_by_repo = {}
|
|
33
39
|
@transfer_changelog_status_by_root = {}
|
|
34
40
|
@transfer_changelog_lag_by_key = {}
|
|
@@ -38,6 +44,7 @@ module Kettle
|
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def results(event_handler: nil)
|
|
47
|
+
return scoped_checkout_results(event_handler: event_handler) if checked_out_scoped_results?
|
|
41
48
|
return branch_results(event_handler: event_handler) unless release_target_branches.empty?
|
|
42
49
|
if shared_changelog?
|
|
43
50
|
results = parallel_map(members) { |member| check_shared_changelog_member_or_local(member, event_handler: event_handler) }
|
|
@@ -53,7 +60,25 @@ module Kettle
|
|
|
53
60
|
|
|
54
61
|
private
|
|
55
62
|
|
|
56
|
-
attr_reader :members, :config, :jobs
|
|
63
|
+
attr_reader :members, :config, :jobs, :branches
|
|
64
|
+
|
|
65
|
+
# A single-branch scope whose checkout already sits on that branch needs
|
|
66
|
+
# no worktree: the primary checkout is the branch under test.
|
|
67
|
+
def checked_out_scoped_results?
|
|
68
|
+
return false unless branches&.one?
|
|
69
|
+
|
|
70
|
+
members.any? { |member| current_branch_for(member) == branches.first }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def scoped_checkout_results(event_handler: nil)
|
|
74
|
+
branch = branches.first
|
|
75
|
+
results = parallel_map(members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
|
|
76
|
+
results.flatten
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def current_branch_for(member)
|
|
80
|
+
git_output(member.root, "branch", "--show-current")
|
|
81
|
+
end
|
|
57
82
|
|
|
58
83
|
def branch_results(event_handler: nil)
|
|
59
84
|
root = git_root
|
|
@@ -769,6 +794,7 @@ module Kettle
|
|
|
769
794
|
|
|
770
795
|
def release_target_branches
|
|
771
796
|
return [] unless config
|
|
797
|
+
return @branches if @branches
|
|
772
798
|
|
|
773
799
|
config.release_target_branches
|
|
774
800
|
end
|
|
@@ -840,6 +866,7 @@ module Kettle
|
|
|
840
866
|
with_worktree_registry_lock(root: root) do
|
|
841
867
|
base = File.join(root, "tmp", "kettle-family-release-state")
|
|
842
868
|
FileUtils.mkdir_p(base)
|
|
869
|
+
prune_stale_worktrees(root: root)
|
|
843
870
|
entries = branches.map do |branch|
|
|
844
871
|
{branch: branch, worktree_root: File.join(base, "worktree-#{Process.pid}-#{SecureRandom.hex(8)}")}
|
|
845
872
|
end
|
|
@@ -869,6 +896,40 @@ module Kettle
|
|
|
869
896
|
end
|
|
870
897
|
end
|
|
871
898
|
|
|
899
|
+
# A killed run (SIGKILL, terminal close) skips the ensure-block cleanup and
|
|
900
|
+
# leaves registered worktrees behind. Registered-but-ownerless worktrees
|
|
901
|
+
# are pinned refs that accumulate forever, so reap them on the way in.
|
|
902
|
+
def prune_stale_worktrees(root:)
|
|
903
|
+
base = File.join(root, "tmp", "kettle-family-release-state")
|
|
904
|
+
WORKTREE_ADMIN_MUTEX.synchronize do
|
|
905
|
+
Open3.capture3("git", "worktree", "prune", chdir: root)
|
|
906
|
+
orphaned_worktree_paths(base).each do |path|
|
|
907
|
+
Open3.capture3("git", "worktree", "remove", "--force", path, chdir: root)
|
|
908
|
+
FileUtils.rm_rf(path)
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# Worktree directories are named worktree-PID-RANDOM. A PID that is no
|
|
914
|
+
# longer running cannot still own a live worktree.
|
|
915
|
+
def orphaned_worktree_paths(base)
|
|
916
|
+
Dir[File.join(base, "worktree-*")].select do |path|
|
|
917
|
+
pid = File.basename(path)[/worktree-(\d+)-/, 1]
|
|
918
|
+
next false if pid.nil? || pid == Process.pid.to_s
|
|
919
|
+
|
|
920
|
+
!process_alive?(pid)
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def process_alive?(pid)
|
|
925
|
+
Process.kill(0, pid)
|
|
926
|
+
true
|
|
927
|
+
rescue Errno::ESRCH
|
|
928
|
+
false
|
|
929
|
+
rescue Errno::EPERM
|
|
930
|
+
true
|
|
931
|
+
end
|
|
932
|
+
|
|
872
933
|
def add_branch_worktree(root:, branch:, worktree_root:)
|
|
873
934
|
WORKTREE_ADMIN_MUTEX.synchronize do
|
|
874
935
|
_stdout, stderr, status = Open3.capture3("git", "worktree", "add", "--detach", worktree_root, branch, chdir: root)
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -21,6 +21,21 @@ module Kettle
|
|
|
21
21
|
STATUS_TOKEN_KEYS.key?(value.to_s)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Defined as a singleton method above the `private` keyword: a `private`
|
|
25
|
+
# instance modifier does not apply to `def self.` definitions.
|
|
26
|
+
#
|
|
27
|
+
# Branches of one member that currently satisfy +tokens+. Used to decide
|
|
28
|
+
# per-branch bump work, so a bump never touches a branch that has nothing
|
|
29
|
+
# to release.
|
|
30
|
+
def self.branches_matching(release_state_results, member_name:, tokens:)
|
|
31
|
+
keys = Array(tokens).map(&:to_s).filter_map { |token| STATUS_TOKEN_KEYS[token] }
|
|
32
|
+
return [] if keys.empty?
|
|
33
|
+
|
|
34
|
+
Array(release_state_results).select do |result|
|
|
35
|
+
result.member_name == member_name && keys.all? { |key| result.state[key] == true }
|
|
36
|
+
end.map(&:branch).compact.uniq
|
|
37
|
+
end
|
|
38
|
+
|
|
24
39
|
def self.validate_release_state_only_filter!(only)
|
|
25
40
|
names = only.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
26
41
|
status_tokens = names.select { |name| status_token?(name) }
|
|
@@ -83,22 +98,29 @@ module Kettle
|
|
|
83
98
|
selected.drop(index)
|
|
84
99
|
end
|
|
85
100
|
|
|
101
|
+
# A branch-stack member yields one release-state result per branch, all
|
|
102
|
+
# sharing a member_name. Collapsing them by member_name alone would let
|
|
103
|
+
# whichever branch came last decide selection for every branch, so a
|
|
104
|
+
# member qualifies when *any* of its branches matches the token.
|
|
86
105
|
def select_release_state_status(selected, status_tokens)
|
|
87
106
|
results_by_member = release_state_results_by_member
|
|
88
|
-
failed = results_by_member.values.select { |result| !result.ok? }
|
|
89
|
-
raise Error, "release-state check failed for: #{failed.map(&:member_name).join(", ")}" unless failed.empty?
|
|
107
|
+
failed = results_by_member.values.flatten.select { |result| !result.ok? }
|
|
108
|
+
raise Error, "release-state check failed for: #{failed.map(&:member_name).uniq.join(", ")}" unless failed.empty?
|
|
90
109
|
|
|
91
110
|
selected.select do |candidate|
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
Array(results_by_member[candidate.name]).any? do |result|
|
|
112
|
+
status_tokens.all? { |token| truthy_state?(result.state[STATUS_TOKEN_KEYS.fetch(token)]) }
|
|
113
|
+
end
|
|
94
114
|
end
|
|
95
115
|
end
|
|
96
116
|
|
|
117
|
+
# Branch-stack members report one result per branch, all sharing a member
|
|
118
|
+
# name, so results are grouped per member instead of collapsed to one.
|
|
97
119
|
def release_state_results_by_member
|
|
98
120
|
raise Error, "--only release-state tokens require release-state results" unless release_state_results
|
|
99
121
|
|
|
100
122
|
release_state_results.each_with_object({}) do |result, memo|
|
|
101
|
-
memo[result.member_name]
|
|
123
|
+
(memo[result.member_name] ||= []) << result
|
|
102
124
|
end
|
|
103
125
|
end
|
|
104
126
|
|
|
@@ -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.
|
|
8
|
+
VERSION = "1.3.5"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -3978,28 +3978,38 @@ module Kettle
|
|
|
3978
3978
|
@release_progress&.update(member, status: "ci bundle #{relative} #{attempt}/#{REGISTRY_WAIT_ATTEMPTS}", mark: ">")
|
|
3979
3979
|
end
|
|
3980
3980
|
|
|
3981
|
+
# A published version only means "already released" when nothing
|
|
3982
|
+
# releasable landed after its tag. kettle-release commits checksums
|
|
3983
|
+
# (and the family commits dependency-floor lockfiles) *after* tagging,
|
|
3984
|
+
# so comparing HEAD to the tag directly never matches; compare the
|
|
3985
|
+
# release-relevant paths instead.
|
|
3981
3986
|
def already_released_result(member)
|
|
3982
3987
|
tag = release_tag_name(member.version)
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3988
|
+
branch = current_member_branch(member)
|
|
3989
|
+
scope = branch ? "#{member.name}@#{branch}" : member.name
|
|
3990
|
+
if no_releasable_changes_since_tag?(member, tag)
|
|
3991
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)} and nothing releasable " \
|
|
3992
|
+
"landed after #{tag}; skipping release"
|
|
3986
3993
|
reason = "already released"
|
|
3987
3994
|
skipped = true
|
|
3988
|
-
elsif unreleased_changes_pending?(member)
|
|
3989
|
-
stdout = "#{
|
|
3990
|
-
"
|
|
3995
|
+
elsif unreleased_changes_pending?(member, branch: branch)
|
|
3996
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)}, but release-state reports " \
|
|
3997
|
+
"unreleased changes on this branch. Bump only this branch with " \
|
|
3998
|
+
"`kettle-family bump patch --execute --only #{member.name}` before releasing."
|
|
3991
3999
|
reason = "published version has unreleased changes"
|
|
3992
4000
|
skipped = false
|
|
3993
4001
|
else
|
|
3994
|
-
stdout = "#{
|
|
3995
|
-
"
|
|
4002
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)}, and release-state reports " \
|
|
4003
|
+
"no unreleased changes on this branch; skipping release"
|
|
3996
4004
|
reason = "already released; no unreleased changes"
|
|
3997
4005
|
skipped = true
|
|
3998
4006
|
end
|
|
3999
4007
|
|
|
4000
4008
|
CommandResult.new(
|
|
4001
4009
|
member_name: member.name,
|
|
4002
|
-
phase:
|
|
4010
|
+
# A blocked release is a distinct terminal phase from a skip: it is a
|
|
4011
|
+
# hard failure and must never be reported as "skipped".
|
|
4012
|
+
phase: skipped ? "release_skip" : "release_blocked",
|
|
4003
4013
|
command: ["internal", "released-version-check", member.version],
|
|
4004
4014
|
workdir: member.root,
|
|
4005
4015
|
status: skipped ? 0 : 1,
|
|
@@ -4008,12 +4018,34 @@ module Kettle
|
|
|
4008
4018
|
stderr: "",
|
|
4009
4019
|
elapsed_seconds: 0.0,
|
|
4010
4020
|
skipped: skipped,
|
|
4011
|
-
reason: reason
|
|
4021
|
+
reason: reason,
|
|
4022
|
+
branch: branch
|
|
4012
4023
|
)
|
|
4013
4024
|
end
|
|
4014
4025
|
|
|
4015
|
-
def
|
|
4016
|
-
|
|
4026
|
+
def branch_suffix(branch)
|
|
4027
|
+
branch ? " on #{branch}" : ""
|
|
4028
|
+
end
|
|
4029
|
+
|
|
4030
|
+
def current_member_branch(member)
|
|
4031
|
+
return unless git_work_tree?(member.root)
|
|
4032
|
+
|
|
4033
|
+
stdout, _stderr, status = Open3.capture3("git", "branch", "--show-current", chdir: member.root)
|
|
4034
|
+
return unless status.success?
|
|
4035
|
+
|
|
4036
|
+
branch = stdout.strip
|
|
4037
|
+
branch.empty? ? nil : branch
|
|
4038
|
+
end
|
|
4039
|
+
|
|
4040
|
+
# Scope the check to the branch being released. Consulting the whole
|
|
4041
|
+
# branch stack here would report every already-published branch as
|
|
4042
|
+
# having unreleased changes whenever any other branch in the stack does.
|
|
4043
|
+
def unreleased_changes_pending?(member, branch: nil)
|
|
4044
|
+
results = ReleaseStateCheck.new(
|
|
4045
|
+
members: [member],
|
|
4046
|
+
config: config,
|
|
4047
|
+
branches: branch ? [branch] : nil
|
|
4048
|
+
).results
|
|
4017
4049
|
return true unless results.all?(&:ok?)
|
|
4018
4050
|
|
|
4019
4051
|
results.any? { |result| result.state.fetch("unreleased_entries", true) }
|
|
@@ -4023,12 +4055,42 @@ module Kettle
|
|
|
4023
4055
|
"v#{version}"
|
|
4024
4056
|
end
|
|
4025
4057
|
|
|
4026
|
-
|
|
4027
|
-
|
|
4058
|
+
# Post-release maintenance commits (checksums, lockfile reconciliation,
|
|
4059
|
+
# docs) land after the tag by design, so "HEAD != tag" cannot mean
|
|
4060
|
+
# "needs a release". Only a change to the gemspec, version file, or
|
|
4061
|
+
# changelog between the tag and HEAD makes the published version stale.
|
|
4062
|
+
def no_releasable_changes_since_tag?(member, tag)
|
|
4063
|
+
return false unless git_work_tree?(member.root)
|
|
4028
4064
|
|
|
4029
4065
|
tag_sha = git_rev_parse(member.root, "refs/tags/#{tag}^{}")
|
|
4030
|
-
|
|
4031
|
-
|
|
4066
|
+
return false if tag_sha.to_s.empty?
|
|
4067
|
+
return true if tag_sha == git_rev_parse(member.root, "HEAD")
|
|
4068
|
+
|
|
4069
|
+
releasable_changes_since_tag(member, tag).empty?
|
|
4070
|
+
end
|
|
4071
|
+
|
|
4072
|
+
def releasable_changes_since_tag(member, tag)
|
|
4073
|
+
paths = releasable_paths(member)
|
|
4074
|
+
return [] if paths.empty?
|
|
4075
|
+
|
|
4076
|
+
stdout, _stderr, status = Open3.capture3(
|
|
4077
|
+
"git", "diff", "--name-only", "#{tag}^{}", "HEAD", "--", *paths, chdir: member.root
|
|
4078
|
+
)
|
|
4079
|
+
# An unreadable diff is not evidence of "nothing to release".
|
|
4080
|
+
return [tag] unless status.success?
|
|
4081
|
+
|
|
4082
|
+
stdout.lines.map(&:strip).reject(&:empty?)
|
|
4083
|
+
end
|
|
4084
|
+
|
|
4085
|
+
def releasable_paths(member)
|
|
4086
|
+
paths = [member.gemspec_path, member.version_file, config.changelog_full_path(member)]
|
|
4087
|
+
paths.compact.filter_map do |path|
|
|
4088
|
+
next unless File.file?(path)
|
|
4089
|
+
|
|
4090
|
+
Pathname.new(path).relative_path_from(Pathname.new(member.root)).to_s
|
|
4091
|
+
rescue ArgumentError
|
|
4092
|
+
path.to_s
|
|
4093
|
+
end.uniq
|
|
4032
4094
|
end
|
|
4033
4095
|
|
|
4034
4096
|
def git_work_tree?(root)
|
|
@@ -4634,7 +4696,9 @@ module Kettle
|
|
|
4634
4696
|
def emit_release_progress_summary(results, progress:)
|
|
4635
4697
|
return unless progress
|
|
4636
4698
|
|
|
4637
|
-
release_results = results.select
|
|
4699
|
+
release_results = results.select do |result|
|
|
4700
|
+
result.phase == release_phase || result.phase == "release_skip" || result.phase == "release_blocked"
|
|
4701
|
+
end
|
|
4638
4702
|
progress.stop
|
|
4639
4703
|
progress.summary("release summary: #{release_results.count(&:ok?)}/#{release_results.length} members ok")
|
|
4640
4704
|
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.
|
|
4
|
+
version: 1.3.5
|
|
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.
|
|
97
|
+
version: 3.1.7
|
|
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.
|
|
107
|
+
version: 3.1.7
|
|
108
108
|
- !ruby/object:Gem::Dependency
|
|
109
109
|
name: kettle-gha-pins
|
|
110
110
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -304,7 +304,7 @@ dependencies:
|
|
|
304
304
|
version: '3.2'
|
|
305
305
|
- - ">="
|
|
306
306
|
- !ruby/object:Gem::Version
|
|
307
|
-
version: 3.2.
|
|
307
|
+
version: 3.2.5
|
|
308
308
|
type: :development
|
|
309
309
|
prerelease: false
|
|
310
310
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -314,7 +314,7 @@ dependencies:
|
|
|
314
314
|
version: '3.2'
|
|
315
315
|
- - ">="
|
|
316
316
|
- !ruby/object:Gem::Version
|
|
317
|
-
version: 3.2.
|
|
317
|
+
version: 3.2.5
|
|
318
318
|
- !ruby/object:Gem::Dependency
|
|
319
319
|
name: turbo_tests2
|
|
320
320
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -324,7 +324,7 @@ dependencies:
|
|
|
324
324
|
version: '3.2'
|
|
325
325
|
- - ">="
|
|
326
326
|
- !ruby/object:Gem::Version
|
|
327
|
-
version: 3.2.
|
|
327
|
+
version: 3.2.13
|
|
328
328
|
type: :development
|
|
329
329
|
prerelease: false
|
|
330
330
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -334,21 +334,7 @@ dependencies:
|
|
|
334
334
|
version: '3.2'
|
|
335
335
|
- - ">="
|
|
336
336
|
- !ruby/object:Gem::Version
|
|
337
|
-
version: 3.2.
|
|
338
|
-
- !ruby/object:Gem::Dependency
|
|
339
|
-
name: webrick
|
|
340
|
-
requirement: !ruby/object:Gem::Requirement
|
|
341
|
-
requirements:
|
|
342
|
-
- - "~>"
|
|
343
|
-
- !ruby/object:Gem::Version
|
|
344
|
-
version: '1.9'
|
|
345
|
-
type: :development
|
|
346
|
-
prerelease: false
|
|
347
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
348
|
-
requirements:
|
|
349
|
-
- - "~>"
|
|
350
|
-
- !ruby/object:Gem::Version
|
|
351
|
-
version: '1.9'
|
|
337
|
+
version: 3.2.13
|
|
352
338
|
- !ruby/object:Gem::Dependency
|
|
353
339
|
name: ruby-progressbar
|
|
354
340
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -392,7 +378,7 @@ dependencies:
|
|
|
392
378
|
version: '2.0'
|
|
393
379
|
- - ">="
|
|
394
380
|
- !ruby/object:Gem::Version
|
|
395
|
-
version: 2.0.
|
|
381
|
+
version: 2.0.15
|
|
396
382
|
type: :development
|
|
397
383
|
prerelease: false
|
|
398
384
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -402,7 +388,21 @@ dependencies:
|
|
|
402
388
|
version: '2.0'
|
|
403
389
|
- - ">="
|
|
404
390
|
- !ruby/object:Gem::Version
|
|
405
|
-
version: 2.0.
|
|
391
|
+
version: 2.0.15
|
|
392
|
+
- !ruby/object:Gem::Dependency
|
|
393
|
+
name: webrick
|
|
394
|
+
requirement: !ruby/object:Gem::Requirement
|
|
395
|
+
requirements:
|
|
396
|
+
- - "~>"
|
|
397
|
+
- !ruby/object:Gem::Version
|
|
398
|
+
version: '1.9'
|
|
399
|
+
type: :development
|
|
400
|
+
prerelease: false
|
|
401
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
402
|
+
requirements:
|
|
403
|
+
- - "~>"
|
|
404
|
+
- !ruby/object:Gem::Version
|
|
405
|
+
version: '1.9'
|
|
406
406
|
description: "\U0001F469\U0001F469\U0001F467\U0001F467 Kettle::Family provides
|
|
407
407
|
scripts and conventions for coordinating related Ruby gems as one family."
|
|
408
408
|
email:
|
|
@@ -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.
|
|
463
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.
|
|
462
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.5
|
|
463
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.5/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.
|
|
465
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.5
|
|
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
|