kettle-family 1.2.21 → 1.2.22
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 +47 -1
- data/README.md +25 -16
- data/lib/kettle/family/appraisal_bootstrap.rb +95 -0
- data/lib/kettle/family/cli.rb +6 -0
- data/lib/kettle/family/config.rb +6 -0
- data/lib/kettle/family/release_state_check.rb +78 -18
- data/lib/kettle/family/release_waves.rb +33 -6
- data/lib/kettle/family/report.rb +13 -3
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +168 -5
- data/lib/kettle/family.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +9 -8
- metadata.gz.sig +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8352dd3e0095e5bdec1437a5e0098a50e375f64362ffa592aa01aa056d48172b
|
|
4
|
+
data.tar.gz: 947519ebac1a4a0ad9c468efd5defaaddcec2feabdaff037e3470e3c19a0cc2c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd6b1cf2bbef2d4cabdcb2c8fadaf2a71c4437b1fbef55c5d2ae716997dc69276ac1dd3cc95a2d8b457a5599754169f7d004bbe38ba52c2afdbae3b41e04d5b9
|
|
7
|
+
data.tar.gz: 2e6ea899f8012f72619f58fb1cefb1495511b8a3252d5e7c8be57c1f204ed92b55439e1d0c10278b57f4793bab8043d572ba38eed9b7b0b1f218f6468ab54997
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,50 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.2.22] - 2026-08-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.2.22][1.2.22t]
|
|
36
|
+
- COVERAGE: 93.87% -- 4626/4928 lines in 31 files
|
|
37
|
+
- BRANCH COVERAGE: 76.18% -- 1807/2372 branches in 31 files
|
|
38
|
+
- 29.31% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Template runs can now use --autostash to preserve dirty worktrees while synchronizing and templating.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Template runs now pull configured upstream branches before planning; branch-target runs synchronize each checked-out branch.
|
|
47
|
+
|
|
48
|
+
- Template autostash is now enabled by default; use --no-autostash to fail on dirty worktrees.
|
|
49
|
+
|
|
50
|
+
- Clarify that replayed template changelog entries are destination release work and affect release state.
|
|
51
|
+
|
|
52
|
+
- Release state now reports filter-aware transfer changelog lag as missing versus applicable entries.
|
|
53
|
+
|
|
54
|
+
- kettle-jem-template-20260801-001 - Generated README gem dashboard links now
|
|
55
|
+
use ClickGems instead of BestGems.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Evaluate release-state GitHub release and commit counts against each configured target branch.
|
|
60
|
+
|
|
61
|
+
- kettle-jem-template-20260801-002 - Generated RSpec helpers now normalize
|
|
62
|
+
managed configuration block bindings structurally, preventing mixed block
|
|
63
|
+
parameter names from producing invalid configuration after a merge.
|
|
64
|
+
- kettle-jem-template-20260801-003 - Generated project metadata and
|
|
65
|
+
documentation now normalize configured underscore hostnames to valid
|
|
66
|
+
hyphenated hostnames.
|
|
67
|
+
- kettle-jem-template-20260801-004 - Generated organization README logos now
|
|
68
|
+
use GitHub's stable organization avatar endpoint instead of assuming a
|
|
69
|
+
matching Galtzo-hosted asset exists.
|
|
70
|
+
|
|
71
|
+
- Template autostash now recovers lockfile-only restore conflicts by keeping the normalized lockfile.
|
|
72
|
+
|
|
73
|
+
- Allow family configuration to define ordered single-member release waves.
|
|
74
|
+
|
|
75
|
+
- Remove retired pboling/appraisal declarations before templating legacy members.
|
|
76
|
+
|
|
33
77
|
## [1.2.21] - 2026-08-01
|
|
34
78
|
|
|
35
79
|
- TAG: [v1.2.21][1.2.21t]
|
|
@@ -1608,7 +1652,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
1608
1652
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
1609
1653
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
1610
1654
|
|
|
1611
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.
|
|
1655
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.22...HEAD
|
|
1656
|
+
[1.2.22]: https://github.com/kettle-dev/kettle-family/compare/v1.2.21...v1.2.22
|
|
1657
|
+
[1.2.22t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.22
|
|
1612
1658
|
[1.2.21]: https://github.com/kettle-dev/kettle-family/compare/v1.2.20...v1.2.21
|
|
1613
1659
|
[1.2.21t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.21
|
|
1614
1660
|
[1.2.20]: https://github.com/kettle-dev/kettle-family/compare/v1.2.19...v1.2.20
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by
|
|
1
|
+
<a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by GitHub" src="https://github.com/kettle-dev.png?size=192" width="14%" align="right"/></a>
|
|
2
2
|
|
|
3
3
|
# 👩👩👧👧 Kettle::Family
|
|
4
4
|
|
|
5
|
-
[![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only][📄license-img]][📄license] [![
|
|
5
|
+
[![Version][👽versioni]][👽version] [![Ruby Users Forum][✉️ruby-forum-top-img]][✉️ruby-forum] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: AGPL-3.0-only][📄license-img]][📄license] [![Total downloads][👽dl-ranki]][👽dl-rank] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf]
|
|
6
6
|
|
|
7
7
|
`if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know on Discord][✉️discord-invite] or [RubyForum][✉️ruby-forum], as I may have missed the notification.
|
|
8
8
|
|
|
@@ -69,16 +69,16 @@ The _amazing_ test matrix is powered by the kettle-dev stack.
|
|
|
69
69
|
<details markdown="1">
|
|
70
70
|
<summary>How kettle-dev manages complexity in tests</summary>
|
|
71
71
|
|
|
72
|
-
| Gem | Source | Role |
|
|
72
|
+
| Gem | Source | Role | Total downloads |
|
|
73
73
|
|-----|--------|------|---------------------|
|
|
74
|
-
| [appraisal2](https://
|
|
75
|
-
| [appraisal2-rubocop](https://
|
|
76
|
-
| [kettle-dev](https://
|
|
77
|
-
| [kettle-jem](https://
|
|
78
|
-
| [kettle-soup-cover](https://
|
|
79
|
-
| [kettle-test](https://
|
|
80
|
-
| [rubocop-lts](https://
|
|
81
|
-
| [turbo_tests2](https://
|
|
74
|
+
| [appraisal2](https://clickgems.clickhouse.com/dashboard/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [](https://clickgems.clickhouse.com/dashboard/appraisal2) |
|
|
75
|
+
| [appraisal2-rubocop](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [](https://clickgems.clickhouse.com/dashboard/appraisal2-rubocop) |
|
|
76
|
+
| [kettle-dev](https://clickgems.clickhouse.com/dashboard/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [](https://clickgems.clickhouse.com/dashboard/kettle-dev) |
|
|
77
|
+
| [kettle-jem](https://clickgems.clickhouse.com/dashboard/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [](https://clickgems.clickhouse.com/dashboard/kettle-jem) |
|
|
78
|
+
| [kettle-soup-cover](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [](https://clickgems.clickhouse.com/dashboard/kettle-soup-cover) |
|
|
79
|
+
| [kettle-test](https://clickgems.clickhouse.com/dashboard/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [](https://clickgems.clickhouse.com/dashboard/kettle-test) |
|
|
80
|
+
| [rubocop-lts](https://clickgems.clickhouse.com/dashboard/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [](https://clickgems.clickhouse.com/dashboard/rubocop-lts) |
|
|
81
|
+
| [turbo_tests2](https://clickgems.clickhouse.com/dashboard/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [](https://clickgems.clickhouse.com/dashboard/turbo_tests2) |
|
|
82
82
|
|
|
83
83
|
</details>
|
|
84
84
|
|
|
@@ -342,6 +342,15 @@ when it does not match `V.rel`. The `T(n)` header shows the total number of
|
|
|
342
342
|
kettle-jem transfer changelog entries. Each row value in that column shows how
|
|
343
343
|
many entries are after that member's stored replay cursor.
|
|
344
344
|
|
|
345
|
+
Transfer entries are destination release notes. `kettle-jem` may replay one
|
|
346
|
+
only when the template change affects a destination gem's users or developers,
|
|
347
|
+
such as behavior, configuration, compatibility, packaging, documentation, or a
|
|
348
|
+
developer workflow. They are deliberately placed in `Unreleased` and count as
|
|
349
|
+
pending release work. The stable `kettle-jem-template-YYYYMMDD-NNN` prefix is a
|
|
350
|
+
replay identifier only; it does not make an entry exempt from `unreleased`,
|
|
351
|
+
`pending`, or `bump`. `T(n)` reports replay progress separately and never
|
|
352
|
+
changes those release-state decisions.
|
|
353
|
+
|
|
345
354
|
Use release-state tokens with `--only` to select gems by state instead of by
|
|
346
355
|
name. Multiple tokens are combined with logical AND, so this selects only gems
|
|
347
356
|
that are both unreleased and pending. Status tokens cannot be mixed with member
|
|
@@ -716,7 +725,7 @@ Thanks for RTFM. ☺️
|
|
|
716
725
|
[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
|
|
717
726
|
[⛳️gem-namespace]: https://github.com/kettle-dev/kettle-family
|
|
718
727
|
[⛳️namespace-img]: https://img.shields.io/badge/namespace-Kettle::Family-3C2D2D.svg?style=square&logo=ruby&logoColor=white
|
|
719
|
-
[⛳️gem-name]: https://
|
|
728
|
+
[⛳️gem-name]: https://clickgems.clickhouse.com/dashboard/kettle-family
|
|
720
729
|
[⛳️name-img]: https://img.shields.io/badge/name-kettle--family-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
|
|
721
730
|
[⛳️tag-img]: https://img.shields.io/github/tag/kettle-dev/kettle-family.svg
|
|
722
731
|
[⛳️tag]: https://github.com/kettle-dev/kettle-family/releases
|
|
@@ -765,9 +774,9 @@ Thanks for RTFM. ☺️
|
|
|
765
774
|
[📜gh-wiki]: https://github.com/kettle-dev/kettle-family/wiki
|
|
766
775
|
[📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
|
|
767
776
|
[📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
|
|
768
|
-
[👽dl-rank]: https://
|
|
769
|
-
[👽dl-ranki]: https://img.shields.io/gem/
|
|
770
|
-
[👽version]: https://
|
|
777
|
+
[👽dl-rank]: https://clickgems.clickhouse.com/dashboard/kettle-family
|
|
778
|
+
[👽dl-ranki]: https://img.shields.io/gem/dt/kettle-family.svg
|
|
779
|
+
[👽version]: https://clickgems.clickhouse.com/dashboard/kettle-family
|
|
771
780
|
[👽versioni]: https://img.shields.io/gem/v/kettle-family.svg
|
|
772
781
|
[🏀qlty-mnt]: https://qlty.sh/gh/kettle-dev/projects/kettle-family
|
|
773
782
|
[🏀qlty-mnti]: https://qlty.sh/gh/kettle-dev/projects/kettle-family/maintainability.svg
|
|
@@ -819,7 +828,7 @@ Thanks for RTFM. ☺️
|
|
|
819
828
|
[📌gitmoji]: https://gitmoji.dev
|
|
820
829
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
821
830
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
822
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.
|
|
831
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.928-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
823
832
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
824
833
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
825
834
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kettle/dev"
|
|
4
|
+
|
|
5
|
+
module Kettle
|
|
6
|
+
module Family
|
|
7
|
+
# Removes the retired pre-fork appraisal source before Bundler evaluates a
|
|
8
|
+
# legacy member. kettle-jem owns the current appraisal wiring.
|
|
9
|
+
class AppraisalBootstrap
|
|
10
|
+
LEGACY_GEM_NAME = "appraisal"
|
|
11
|
+
LEGACY_GITHUB = "pboling/appraisal"
|
|
12
|
+
LEGACY_BRANCH = "galtzo"
|
|
13
|
+
|
|
14
|
+
def initialize(mode: :dry_run)
|
|
15
|
+
@mode = mode
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :mode
|
|
19
|
+
|
|
20
|
+
def member_needs_bootstrap?(member)
|
|
21
|
+
edits_for(member).any?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def bootstrap_member(member)
|
|
25
|
+
edits = edits_for(member)
|
|
26
|
+
write_edits(edits)
|
|
27
|
+
CommandResult.new(
|
|
28
|
+
member.name,
|
|
29
|
+
"template_bootstrap_appraisal",
|
|
30
|
+
["internal", "appraisal-bootstrap", LEGACY_GITHUB],
|
|
31
|
+
member.root,
|
|
32
|
+
0,
|
|
33
|
+
true,
|
|
34
|
+
"removed retired #{LEGACY_GITHUB} appraisal dependency from #{edits.map { |edit| File.basename(edit.fetch(:path)) }.uniq.join(", ")}\n",
|
|
35
|
+
"",
|
|
36
|
+
0.0,
|
|
37
|
+
mode != :execute,
|
|
38
|
+
(mode == :execute) ? nil : "dry run"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def write_edits(edits)
|
|
45
|
+
return if mode != :execute || edits.empty?
|
|
46
|
+
|
|
47
|
+
Kettle::Dev::VersionBump.write_edits(edits)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def edits_for(member)
|
|
51
|
+
[File.join(member.root, "Gemfile"), File.join(member.root, "Appraisal.root.gemfile")].filter_map do |path|
|
|
52
|
+
file_edits(path)
|
|
53
|
+
end.flatten
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def file_edits(path)
|
|
57
|
+
return [] unless File.file?(path)
|
|
58
|
+
|
|
59
|
+
source = File.read(path)
|
|
60
|
+
parse_result = Kettle::Dev::VersionBump.parse_source(source, path)
|
|
61
|
+
Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
|
|
62
|
+
obsolete_appraisal_edit(path, source, node)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def obsolete_appraisal_edit(path, source, node)
|
|
67
|
+
return unless legacy_appraisal_call?(node)
|
|
68
|
+
|
|
69
|
+
line_start = source.rindex("\n", node.location.start_offset - 1).to_i + 1
|
|
70
|
+
line_end = source.index("\n", node.location.end_offset) || source.length
|
|
71
|
+
line_end += 1 if line_end < source.length
|
|
72
|
+
Kettle::Dev::VersionBump.file_edit(path, source, line_start, line_end, "")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def legacy_appraisal_call?(node)
|
|
76
|
+
return false unless node.is_a?(Prism::CallNode) && node.name == :gem
|
|
77
|
+
|
|
78
|
+
arguments = node.arguments&.arguments || []
|
|
79
|
+
name_node = arguments.first
|
|
80
|
+
return false unless name_node.is_a?(Prism::StringNode) && name_node.unescaped == LEGACY_GEM_NAME
|
|
81
|
+
|
|
82
|
+
keyword_values(arguments).values_at("github", "branch") == [LEGACY_GITHUB, LEGACY_BRANCH]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def keyword_values(arguments)
|
|
86
|
+
arguments.grep(Prism::KeywordHashNode).flat_map(&:elements).each_with_object({}) do |association, values|
|
|
87
|
+
next unless association.is_a?(Prism::AssocNode)
|
|
88
|
+
next unless association.key.is_a?(Prism::SymbolNode) && association.value.is_a?(Prism::StringNode)
|
|
89
|
+
|
|
90
|
+
values[association.key.unescaped] = association.value.unescaped
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -59,6 +59,10 @@ module Kettle
|
|
|
59
59
|
options[:commit] = false
|
|
60
60
|
end
|
|
61
61
|
base.option :allow_dirty, long: "--allow-dirty", desc: "Reserved for compatibility; member repos manage their own commit safety"
|
|
62
|
+
base.option :autostash, long: "--autostash", desc: "Use the default template autostash behavior"
|
|
63
|
+
base.option :no_autostash, long: "--no-autostash", desc: "Fail template runs when a member worktree is dirty" do
|
|
64
|
+
options[:autostash] = false
|
|
65
|
+
end
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
|
|
@@ -158,6 +162,7 @@ module Kettle
|
|
|
158
162
|
push: false,
|
|
159
163
|
commit: !options.key?(:commit) || options[:commit],
|
|
160
164
|
allow_dirty: truthy_option?(:allow_dirty),
|
|
165
|
+
autostash: !options.key?(:autostash) || options[:autostash],
|
|
161
166
|
target_version: nil,
|
|
162
167
|
reset_target: nil,
|
|
163
168
|
bup_args: [],
|
|
@@ -697,6 +702,7 @@ module Kettle
|
|
|
697
702
|
accept: options[:accept],
|
|
698
703
|
commit: options[:commit],
|
|
699
704
|
allow_dirty: options[:allow_dirty],
|
|
705
|
+
autostash: options[:autostash],
|
|
700
706
|
publish: options[:publish],
|
|
701
707
|
push: options[:push],
|
|
702
708
|
tag: options[:tag],
|
data/lib/kettle/family/config.rb
CHANGED
|
@@ -265,6 +265,12 @@ module Kettle
|
|
|
265
265
|
fetch_path("release", "jobs")
|
|
266
266
|
end
|
|
267
267
|
|
|
268
|
+
def release_waves
|
|
269
|
+
Array(fetch_path("release", "waves")).map do |wave|
|
|
270
|
+
Array(wave).map(&:to_s).map(&:strip).reject(&:empty?)
|
|
271
|
+
end.reject(&:empty?)
|
|
272
|
+
end
|
|
273
|
+
|
|
268
274
|
def release_auto_dependency_floors?
|
|
269
275
|
fetch_path("release", "auto_dependency_floors") != false
|
|
270
276
|
end
|
|
@@ -23,6 +23,7 @@ module Kettle
|
|
|
23
23
|
@members = members
|
|
24
24
|
@config = config
|
|
25
25
|
@github_latest_release_by_repo = {}
|
|
26
|
+
@transfer_changelog_status_by_root = {}
|
|
26
27
|
@transfer_changelog_lag_by_key = {}
|
|
27
28
|
@transfer_changelog_total_count = TRANSFER_CHANGELOG_TOTAL_UNSET
|
|
28
29
|
end
|
|
@@ -72,8 +73,8 @@ module Kettle
|
|
|
72
73
|
state = success ? JSON.parse(stdout) : {}
|
|
73
74
|
state = branch_filtered_state(member, state, branch) if success && branch
|
|
74
75
|
state = state_with_computed_booleans(state) if success
|
|
75
|
-
state = enrich_git_state(member.root, state) if success
|
|
76
|
-
state = enrich_github_release(member.root, state) if success
|
|
76
|
+
state = enrich_git_state(member.root, state, branch: branch) if success
|
|
77
|
+
state = enrich_github_release(member.root, state, branch: branch) if success
|
|
77
78
|
state = enrich_transfer_changelog_lag(member.root, state) if success
|
|
78
79
|
result(member: member, command: command, stdout: stdout, stderr: stderr, status: status.exitstatus, elapsed: elapsed, success: success, state: state, branch: branch)
|
|
79
80
|
rescue JSON::ParserError => error
|
|
@@ -98,8 +99,8 @@ module Kettle
|
|
|
98
99
|
success = status.success?
|
|
99
100
|
state = success ? JSON.parse(stdout) : {}
|
|
100
101
|
state = branch_filtered_state(member, state, branch) if success && branch
|
|
101
|
-
state = enrich_git_state(member.root, state) if success
|
|
102
|
-
state = enrich_github_release(member.root, state) if success
|
|
102
|
+
state = enrich_git_state(member.root, state, branch: branch) if success
|
|
103
|
+
state = enrich_github_release(member.root, state, branch: branch) if success
|
|
103
104
|
state = enrich_transfer_changelog_lag(member.root, state) if success
|
|
104
105
|
result(
|
|
105
106
|
member: member,
|
|
@@ -219,29 +220,29 @@ module Kettle
|
|
|
219
220
|
status.success?
|
|
220
221
|
end
|
|
221
222
|
|
|
222
|
-
def enrich_git_state(root, state)
|
|
223
|
+
def enrich_git_state(root, state, branch: nil)
|
|
223
224
|
return state unless git_work_tree?(root)
|
|
224
225
|
|
|
225
226
|
enriched = state.dup
|
|
226
|
-
current_branch = git_output(root, "branch", "--show-current")
|
|
227
|
+
current_branch = branch || git_output(root, "branch", "--show-current")
|
|
227
228
|
enriched["current_branch"] = current_branch unless current_branch.to_s.empty?
|
|
228
229
|
|
|
229
230
|
version = state["latest_released"].to_s.empty? ? state["latest_changelog_version"] : state["latest_released"]
|
|
230
231
|
return enriched if version.to_s.empty?
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
if
|
|
234
|
-
enriched["default_branch"] =
|
|
235
|
-
if (counts = release_counts_for_ref(root, version,
|
|
233
|
+
local_ref = branch || default_branch(root)
|
|
234
|
+
if local_ref
|
|
235
|
+
enriched["default_branch"] = local_ref
|
|
236
|
+
if (counts = release_counts_for_ref(root, version, local_ref))
|
|
236
237
|
enriched["behind"] = counts.fetch(:behind)
|
|
237
238
|
enriched["ahead"] = counts.fetch(:ahead)
|
|
238
239
|
end
|
|
239
240
|
end
|
|
240
241
|
|
|
241
|
-
|
|
242
|
-
if
|
|
243
|
-
enriched["remote_default_branch"] =
|
|
244
|
-
if (counts = release_counts_for_ref(root, version,
|
|
242
|
+
remote_ref = branch ? "origin/#{branch}" : remote_default_branch(root, local_ref)
|
|
243
|
+
if remote_ref && git_ref_exists?(root, "refs/remotes/#{remote_ref}")
|
|
244
|
+
enriched["remote_default_branch"] = remote_ref
|
|
245
|
+
if (counts = release_counts_for_ref(root, version, remote_ref))
|
|
245
246
|
enriched["remote_behind"] = counts.fetch(:behind)
|
|
246
247
|
enriched["remote_ahead"] = counts.fetch(:ahead)
|
|
247
248
|
end
|
|
@@ -319,17 +320,43 @@ module Kettle
|
|
|
319
320
|
state["unreleased_entries"] == true && state["version"].to_s == state["latest_released"].to_s
|
|
320
321
|
end
|
|
321
322
|
|
|
322
|
-
def enrich_github_release(root, state)
|
|
323
|
-
tag = github_latest_release(root)
|
|
323
|
+
def enrich_github_release(root, state, branch: nil)
|
|
324
|
+
tag = branch ? github_release_for_version(root, state["latest_released"]) : github_latest_release(root)
|
|
324
325
|
return state unless tag
|
|
325
326
|
|
|
326
327
|
state.merge("github_latest_release" => tag)
|
|
327
328
|
end
|
|
328
329
|
|
|
330
|
+
def github_release_for_version(root, version)
|
|
331
|
+
return nil if version.to_s.empty?
|
|
332
|
+
|
|
333
|
+
repo = github_repo_slug(root)
|
|
334
|
+
return nil unless repo
|
|
335
|
+
|
|
336
|
+
tag = "v#{version.to_s.delete_prefix("v")}"
|
|
337
|
+
stdout, _stderr, status = Open3.capture3("gh", "release", "view", tag, "--repo", repo, "--json", "tagName", "--jq", ".tagName")
|
|
338
|
+
status.success? ? normalize_github_release_tag(stdout) : nil
|
|
339
|
+
rescue SystemCallError
|
|
340
|
+
nil
|
|
341
|
+
end
|
|
342
|
+
|
|
329
343
|
def enrich_transfer_changelog_lag(root, state)
|
|
330
344
|
kettle_jem_state = kettle_jem_config_state(root)
|
|
331
345
|
return state unless kettle_jem_state
|
|
332
346
|
|
|
347
|
+
status = transfer_changelog_status(root)
|
|
348
|
+
if status
|
|
349
|
+
total = status.key?("total_count") ? status.fetch("total_count") : transfer_changelog_total_count
|
|
350
|
+
return state.merge(
|
|
351
|
+
"transfer_changelog_lag" => status.fetch("missing_count", 0).to_i,
|
|
352
|
+
"transfer_changelog_applicable" => status.fetch("applicable_count", 0).to_i,
|
|
353
|
+
"transfer_changelog_excluded_present" => status.fetch("excluded_present_count", 0).to_i,
|
|
354
|
+
"transfer_changelog_total" => total.to_i
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Compatibility with already released kettle-jem versions while a
|
|
359
|
+
# workspace is upgraded. New versions use the context-aware status API.
|
|
333
360
|
replay = kettle_jem_state["changelog_replay"]
|
|
334
361
|
replay = replay.is_a?(Hash) ? replay : {}
|
|
335
362
|
last_entry_key = replay["last_entry_key"] || replay[:last_entry_key]
|
|
@@ -368,6 +395,15 @@ module Kettle
|
|
|
368
395
|
nil
|
|
369
396
|
end
|
|
370
397
|
|
|
398
|
+
def transfer_changelog_status(root)
|
|
399
|
+
cache_key = File.expand_path(root.to_s)
|
|
400
|
+
@transfer_changelog_status_by_root.fetch(cache_key) do
|
|
401
|
+
@transfer_changelog_status_by_root[cache_key] = in_process_transfer_changelog_status(cache_key) || external_transfer_changelog_status(cache_key)
|
|
402
|
+
end
|
|
403
|
+
rescue
|
|
404
|
+
nil
|
|
405
|
+
end
|
|
406
|
+
|
|
371
407
|
def transfer_changelog_total_count
|
|
372
408
|
return @transfer_changelog_total_count unless @transfer_changelog_total_count.equal?(TRANSFER_CHANGELOG_TOTAL_UNSET)
|
|
373
409
|
|
|
@@ -383,11 +419,20 @@ module Kettle
|
|
|
383
419
|
nil
|
|
384
420
|
end
|
|
385
421
|
|
|
422
|
+
def in_process_transfer_changelog_status(root)
|
|
423
|
+
return nil unless load_kettle_jem_transfer_api
|
|
424
|
+
return nil unless ::Kettle::Jem.respond_to?(:transfer_changelog_status)
|
|
425
|
+
|
|
426
|
+
normalize_transfer_changelog_lag(::Kettle::Jem.transfer_changelog_status(root))
|
|
427
|
+
rescue
|
|
428
|
+
nil
|
|
429
|
+
end
|
|
430
|
+
|
|
386
431
|
def load_kettle_jem_transfer_api
|
|
387
|
-
return true if defined?(::Kettle::Jem) && ::Kettle::Jem.respond_to?(:transfer_changelog_lag)
|
|
432
|
+
return true if defined?(::Kettle::Jem) && (::Kettle::Jem.respond_to?(:transfer_changelog_lag) || ::Kettle::Jem.respond_to?(:transfer_changelog_status))
|
|
388
433
|
|
|
389
434
|
require "kettle/jem"
|
|
390
|
-
defined?(::Kettle::Jem) && ::Kettle::Jem.respond_to?(:transfer_changelog_lag)
|
|
435
|
+
defined?(::Kettle::Jem) && (::Kettle::Jem.respond_to?(:transfer_changelog_lag) || ::Kettle::Jem.respond_to?(:transfer_changelog_status))
|
|
391
436
|
rescue LoadError
|
|
392
437
|
false
|
|
393
438
|
end
|
|
@@ -409,6 +454,21 @@ module Kettle
|
|
|
409
454
|
nil
|
|
410
455
|
end
|
|
411
456
|
|
|
457
|
+
def external_transfer_changelog_status(root)
|
|
458
|
+
code = <<~RUBY
|
|
459
|
+
puts JSON.generate(Kettle::Jem.transfer_changelog_status(ARGV.fetch(0)))
|
|
460
|
+
RUBY
|
|
461
|
+
command = [RbConfig.ruby, "-rjson", "-rkettle/jem", "-e", code, root]
|
|
462
|
+
stdout, _stderr, status = if defined?(::Bundler)
|
|
463
|
+
::Bundler.with_unbundled_env { Open3.capture3(*command) }
|
|
464
|
+
else
|
|
465
|
+
Open3.capture3(*command)
|
|
466
|
+
end
|
|
467
|
+
status.success? ? normalize_transfer_changelog_lag(JSON.parse(stdout)) : nil
|
|
468
|
+
rescue JSON::ParserError, SystemCallError
|
|
469
|
+
nil
|
|
470
|
+
end
|
|
471
|
+
|
|
412
472
|
def normalize_transfer_changelog_lag(lag)
|
|
413
473
|
return nil unless lag.is_a?(Hash)
|
|
414
474
|
|
|
@@ -3,12 +3,43 @@
|
|
|
3
3
|
module Kettle
|
|
4
4
|
module Family
|
|
5
5
|
class ReleaseWaves
|
|
6
|
-
def initialize(members:)
|
|
6
|
+
def initialize(members:, configured_waves: [])
|
|
7
7
|
@members = members
|
|
8
|
+
@configured_waves = configured_waves
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def waves
|
|
11
|
-
|
|
12
|
+
configured = configured_waves_for_selected_members
|
|
13
|
+
configured_names = configured.flatten.map(&:name)
|
|
14
|
+
remaining_members = members.reject { |member| configured_names.include?(member.name) }
|
|
15
|
+
|
|
16
|
+
configured + inferred_waves(remaining_members)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :members, :configured_waves
|
|
22
|
+
|
|
23
|
+
def configured_waves_for_selected_members
|
|
24
|
+
duplicates = configured_waves.flatten.group_by(&:itself).select { |_name, names| names.length > 1 }.keys
|
|
25
|
+
raise Error, "release waves contain duplicate member(s): #{duplicates.join(", ")}" unless duplicates.empty?
|
|
26
|
+
|
|
27
|
+
selected_names = members.map(&:name)
|
|
28
|
+
selected_members = members.to_h { |member| [member.name, member] }
|
|
29
|
+
# Configured waves commonly name the complete family while --only selects
|
|
30
|
+
# a subset. Ignore entries outside the current selection.
|
|
31
|
+
return [] if configured_waves.empty?
|
|
32
|
+
|
|
33
|
+
configured_waves.filter_map do |wave|
|
|
34
|
+
names = wave & selected_names
|
|
35
|
+
names.map { |name| selected_members.fetch(name) } unless names.empty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inferred_waves(candidate_members)
|
|
40
|
+
return [] if candidate_members.empty?
|
|
41
|
+
|
|
42
|
+
by_name = candidate_members.to_h { |member| [member.name, member] }
|
|
12
43
|
pending = by_name.keys
|
|
13
44
|
completed = []
|
|
14
45
|
[].tap do |planned_waves|
|
|
@@ -30,10 +61,6 @@ module Kettle
|
|
|
30
61
|
end
|
|
31
62
|
end
|
|
32
63
|
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
attr_reader :members
|
|
36
|
-
|
|
37
64
|
def selected_hard_dependencies_for(member, by_name)
|
|
38
65
|
Array(member.dependencies).map(&:to_s).select { |dependency| by_name.key?(dependency) }
|
|
39
66
|
end
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -509,12 +509,12 @@ module Kettle
|
|
|
509
509
|
def append_release_state_results(lines)
|
|
510
510
|
lines << "release state:"
|
|
511
511
|
lines << " boolean columns:"
|
|
512
|
-
lines << " unrel: unreleased changelog entries are present"
|
|
512
|
+
lines << " unrel: unreleased changelog entries are present (including replayed transfer entries)"
|
|
513
513
|
lines << " prep: V.ch.md matches V.rb and is ready to publish"
|
|
514
514
|
lines << " pend: unrel or prep"
|
|
515
515
|
lines << " bump: unrel is yes and V.rb matches V.rel"
|
|
516
516
|
lines << " count columns:"
|
|
517
|
-
lines << " T(n): kettle-jem transfer changelog lag; n is the total
|
|
517
|
+
lines << " T(n): filter-aware kettle-jem transfer changelog lag; n is the total source entry count and row values are missing / applicable (x excluded-present)"
|
|
518
518
|
rows = release_state_header
|
|
519
519
|
results.each do |result|
|
|
520
520
|
rows << release_state_row(result)
|
|
@@ -539,7 +539,7 @@ module Kettle
|
|
|
539
539
|
state.fetch("latest_changelog_version", nil).to_s.empty? ? "none" : state.fetch("latest_changelog_version").to_s,
|
|
540
540
|
state.fetch("latest_released", nil).to_s.empty? ? "unknown" : state.fetch("latest_released").to_s,
|
|
541
541
|
release_state_github_release(state),
|
|
542
|
-
state
|
|
542
|
+
release_state_transfer_changelog_value(state),
|
|
543
543
|
release_state_ahead_behind(state),
|
|
544
544
|
yes_no(state.fetch("unreleased_entries", nil)),
|
|
545
545
|
yes_no(state.fetch("prepared_release_pending", nil)),
|
|
@@ -566,6 +566,16 @@ module Kettle
|
|
|
566
566
|
"T(#{total || "?"})"
|
|
567
567
|
end
|
|
568
568
|
|
|
569
|
+
def release_state_transfer_changelog_value(state)
|
|
570
|
+
missing = state.fetch("transfer_changelog_lag", 0).to_i
|
|
571
|
+
applicable = state.fetch("transfer_changelog_applicable", nil)
|
|
572
|
+
return missing.to_s if applicable.nil?
|
|
573
|
+
|
|
574
|
+
excluded_present = state.fetch("transfer_changelog_excluded_present", 0).to_i
|
|
575
|
+
value = "#{missing} / #{applicable.to_i}"
|
|
576
|
+
excluded_present.positive? ? "#{value} (x#{excluded_present})" : value
|
|
577
|
+
end
|
|
578
|
+
|
|
569
579
|
def release_state_github_release(state)
|
|
570
580
|
github_release = state.fetch("github_latest_release", nil).to_s
|
|
571
581
|
return "unknown" if github_release.empty?
|
|
@@ -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.2.
|
|
8
|
+
VERSION = "1.2.22"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -102,7 +102,7 @@ module Kettle
|
|
|
102
102
|
REGISTRY_WAIT_ATTEMPTS = 15
|
|
103
103
|
REGISTRY_WAIT_INTERVAL_SECONDS = 15
|
|
104
104
|
|
|
105
|
-
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, ci_workflows: nil, skip_bundle_audit: false, skip_remotes: nil, required_remotes: nil, auto_dependency_floors: nil, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, verbose: false, gem_signing_password: nil, secrets_provider: nil, jobs: nil, progress_io: nil, reset_target: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil, **options)
|
|
105
|
+
def initialize(command:, config:, members:, execute: false, accept: true, commit: true, allow_dirty: false, autostash: true, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, local_ci: false, continue_ci_failures: false, ci_workflows: nil, skip_bundle_audit: false, skip_remotes: nil, required_remotes: nil, auto_dependency_floors: nil, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, verbose: false, gem_signing_password: nil, secrets_provider: nil, jobs: nil, progress_io: nil, reset_target: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil, **options)
|
|
106
106
|
@command = command
|
|
107
107
|
@config = config
|
|
108
108
|
@members = members
|
|
@@ -110,6 +110,7 @@ module Kettle
|
|
|
110
110
|
@accept = accept
|
|
111
111
|
@commit = commit
|
|
112
112
|
@allow_dirty = allow_dirty
|
|
113
|
+
@autostash = autostash
|
|
113
114
|
@publish = publish
|
|
114
115
|
@push = push
|
|
115
116
|
@tag = tag
|
|
@@ -150,6 +151,8 @@ module Kettle
|
|
|
150
151
|
return current_branch_results(members)
|
|
151
152
|
end
|
|
152
153
|
|
|
154
|
+
return template_with_worktree_sync_results if command == "template" && execute
|
|
155
|
+
|
|
153
156
|
preflight = branch_checkout_dirty_preflight_results
|
|
154
157
|
return preflight if preflight.any? { |result| !result.ok? }
|
|
155
158
|
|
|
@@ -162,7 +165,151 @@ module Kettle
|
|
|
162
165
|
|
|
163
166
|
private
|
|
164
167
|
|
|
165
|
-
attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :ci_workflows, :skip_bundle_audit, :skip_remotes, :required_remotes, :auto_dependency_floors, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :verbose, :jobs, :progress_io, :reset_target, :bup_args, :bex_args, :start_member, :start_branch
|
|
168
|
+
attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :autostash, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :ci_workflows, :skip_bundle_audit, :skip_remotes, :required_remotes, :auto_dependency_floors, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :verbose, :jobs, :progress_io, :reset_target, :bup_args, :bex_args, :start_member, :start_branch
|
|
169
|
+
|
|
170
|
+
def template_with_worktree_sync_results
|
|
171
|
+
runner = command_runner
|
|
172
|
+
checkout_preflight = branch_checkout_dirty_preflight_results
|
|
173
|
+
if checkout_preflight.any? { |result| !result.ok? } && !autostash
|
|
174
|
+
return checkout_preflight
|
|
175
|
+
end
|
|
176
|
+
checkout_preflight = [] unless checkout_preflight.all?(&:ok?)
|
|
177
|
+
|
|
178
|
+
sync_results, stashes = template_worktree_sync_results(runner: runner)
|
|
179
|
+
unless sync_results.all?(&:ok?)
|
|
180
|
+
return checkout_preflight + sync_results + restore_template_autostashes(stashes, runner: runner)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
workflow_results = branch_checkout_dirty_preflight_results
|
|
184
|
+
if workflow_results.all?(&:ok?)
|
|
185
|
+
workflow_results += if !config.release_target_branches.empty?
|
|
186
|
+
branch_target_results
|
|
187
|
+
elsif member_local_branch_targets?
|
|
188
|
+
member_local_branch_target_results
|
|
189
|
+
else
|
|
190
|
+
current_branch_results(members)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
checkout_preflight + sync_results + workflow_results + restore_template_autostashes(stashes, runner: runner)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def template_worktree_sync_results(runner:)
|
|
198
|
+
stashes = []
|
|
199
|
+
results = []
|
|
200
|
+
template_sync_members.each do |member|
|
|
201
|
+
dirty_paths = GitStatus.dirty_paths(member.root)
|
|
202
|
+
if dirty_paths.any?
|
|
203
|
+
unless autostash
|
|
204
|
+
results << template_dirty_worktree_result(member, dirty_paths)
|
|
205
|
+
break
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
stash_result = runner.call(
|
|
209
|
+
member: member,
|
|
210
|
+
phase: "template_autostash",
|
|
211
|
+
command: ["sh", "-lc", template_autostash_command(member)]
|
|
212
|
+
)
|
|
213
|
+
results << stash_result
|
|
214
|
+
break unless stash_result.ok?
|
|
215
|
+
|
|
216
|
+
stash_ref = stash_result.stdout.to_s.lines.last.to_s.strip
|
|
217
|
+
if stash_ref.empty?
|
|
218
|
+
results << template_sync_failure_result(member, "could not determine the temporary template autostash reference")
|
|
219
|
+
break
|
|
220
|
+
end
|
|
221
|
+
stashes << {member: member, ref: stash_ref}
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
upstream = git_upstream_for(member)
|
|
225
|
+
next unless upstream
|
|
226
|
+
|
|
227
|
+
results << runner.call(member: member, phase: "template_sync", command: ["git", "pull", "--rebase"])
|
|
228
|
+
break unless results.last.ok?
|
|
229
|
+
end
|
|
230
|
+
[results, stashes]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def restore_template_autostashes(stashes, runner:)
|
|
234
|
+
stashes.reverse_each.map do |stash|
|
|
235
|
+
restore = runner.call(
|
|
236
|
+
member: stash.fetch(:member),
|
|
237
|
+
phase: "template_autostash_restore",
|
|
238
|
+
command: ["git", "stash", "pop", stash.fetch(:ref)]
|
|
239
|
+
)
|
|
240
|
+
next restore if restore.ok? || !template_lockfile_only_conflict?(stash.fetch(:member))
|
|
241
|
+
|
|
242
|
+
runner.call(
|
|
243
|
+
member: stash.fetch(:member),
|
|
244
|
+
phase: "template_autostash_lockfile_recovery",
|
|
245
|
+
command: [
|
|
246
|
+
"sh", "-lc",
|
|
247
|
+
"git checkout --ours -- Gemfile.lock && git add -- Gemfile.lock && git stash drop #{Shellwords.escape(stash.fetch(:ref))}"
|
|
248
|
+
]
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def template_lockfile_only_conflict?(member)
|
|
254
|
+
stdout, _stderr, status = Open3.capture3("git", "diff", "--name-only", "--diff-filter=U", chdir: member.root)
|
|
255
|
+
status.success? && stdout.lines.map(&:strip).reject(&:empty?) == ["Gemfile.lock"]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def template_branch_sync_results(branch_members, runner:)
|
|
259
|
+
template_sync_members_for(branch_members).each_with_object([]) do |member, memo|
|
|
260
|
+
next unless git_upstream_for(member)
|
|
261
|
+
|
|
262
|
+
memo << runner.call(member: member, phase: "template_sync", command: ["git", "pull", "--rebase"])
|
|
263
|
+
break memo unless memo.last.ok?
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def template_sync_members
|
|
268
|
+
template_sync_members_for(members)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def template_sync_members_for(candidates)
|
|
272
|
+
candidates.each_with_object([]) do |member, unique_members|
|
|
273
|
+
unique_members << member unless unique_members.any? { |candidate| git_root_for(candidate) == git_root_for(member) }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def git_upstream_for(member)
|
|
278
|
+
stdout, _stderr, status = Open3.capture3("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}", chdir: member.root)
|
|
279
|
+
status.success? ? stdout.strip : nil
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def template_autostash_command(member)
|
|
283
|
+
label = "kettle-family-template-#{member.name}-#{Process.pid}-#{Time.now.to_i}"
|
|
284
|
+
"git stash push --include-untracked --message #{Shellwords.escape(label)} && git stash list -1 --format=%gd"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def template_dirty_worktree_result(member, dirty_paths)
|
|
288
|
+
template_sync_failure_result(
|
|
289
|
+
member,
|
|
290
|
+
[
|
|
291
|
+
"dirty worktree blocks template sync; remove --no-autostash, or commit or stash changes before retrying",
|
|
292
|
+
*dirty_paths
|
|
293
|
+
].join("\n"),
|
|
294
|
+
reason: "dirty worktree blocks template sync"
|
|
295
|
+
)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def template_sync_failure_result(member, message, reason: "template sync failed")
|
|
299
|
+
CommandResult.new(
|
|
300
|
+
member_name: member.name,
|
|
301
|
+
phase: "template_sync_preflight",
|
|
302
|
+
command: ["internal", "template-sync"],
|
|
303
|
+
workdir: member.root,
|
|
304
|
+
status: 1,
|
|
305
|
+
success: false,
|
|
306
|
+
stdout: "",
|
|
307
|
+
stderr: message,
|
|
308
|
+
elapsed_seconds: 0.0,
|
|
309
|
+
skipped: false,
|
|
310
|
+
reason: reason
|
|
311
|
+
)
|
|
312
|
+
end
|
|
166
313
|
|
|
167
314
|
def current_branch_results(workflow_members)
|
|
168
315
|
return check_results(workflow_members) if command == "check"
|
|
@@ -231,8 +378,11 @@ module Kettle
|
|
|
231
378
|
debugger_results = template_debugger_bootstrap_results(workflow_members)
|
|
232
379
|
return debugger_results unless debugger_results.all?(&:ok?)
|
|
233
380
|
|
|
381
|
+
appraisal_results = template_appraisal_bootstrap_results(workflow_members)
|
|
382
|
+
return debugger_results + appraisal_results unless appraisal_results.all?(&:ok?)
|
|
383
|
+
|
|
234
384
|
bootstrap_results = template_bootstrap_dependency_results(workflow_members)
|
|
235
|
-
return debugger_results + bootstrap_results unless bootstrap_results.all?(&:ok?)
|
|
385
|
+
return debugger_results + appraisal_results + bootstrap_results unless bootstrap_results.all?(&:ok?)
|
|
236
386
|
|
|
237
387
|
template_progress = start_template_progress(workflow_members)
|
|
238
388
|
results = template_dependency_waves(workflow_members).each_with_object([]) do |wave, memo|
|
|
@@ -241,7 +391,7 @@ module Kettle
|
|
|
241
391
|
break memo unless wave_results.all?(&:ok?)
|
|
242
392
|
end
|
|
243
393
|
emit_template_progress_summary(results, progress: template_progress)
|
|
244
|
-
debugger_results + bootstrap_results + results
|
|
394
|
+
debugger_results + appraisal_results + bootstrap_results + results
|
|
245
395
|
end
|
|
246
396
|
|
|
247
397
|
def template_debugger_bootstrap_results(workflow_members)
|
|
@@ -253,6 +403,15 @@ module Kettle
|
|
|
253
403
|
[template_bootstrap_failure_result(workflow_members.first, error.message)]
|
|
254
404
|
end
|
|
255
405
|
|
|
406
|
+
def template_appraisal_bootstrap_results(workflow_members)
|
|
407
|
+
bootstrap = AppraisalBootstrap.new(mode: :execute)
|
|
408
|
+
workflow_members.filter_map do |member|
|
|
409
|
+
bootstrap.bootstrap_member(member) if bootstrap.member_needs_bootstrap?(member)
|
|
410
|
+
end
|
|
411
|
+
rescue => error
|
|
412
|
+
[template_bootstrap_failure_result(workflow_members.first, error.message)]
|
|
413
|
+
end
|
|
414
|
+
|
|
256
415
|
def template_results_for_wave(wave, progress:)
|
|
257
416
|
queue = Queue.new
|
|
258
417
|
wave.each_with_index { |member, index| queue << [index, member] }
|
|
@@ -433,6 +592,10 @@ module Kettle
|
|
|
433
592
|
|
|
434
593
|
branch_members = rediscovered_selected_members(selected_names)
|
|
435
594
|
branch_members = members if branch_members.empty?
|
|
595
|
+
if command == "template" && execute
|
|
596
|
+
memo.concat(template_branch_sync_results(branch_members, runner: runner))
|
|
597
|
+
break memo unless memo.last&.ok?
|
|
598
|
+
end
|
|
436
599
|
branch_results = current_branch_results(branch_members)
|
|
437
600
|
tag_branch_results(branch_results, branch)
|
|
438
601
|
memo.concat(branch_results)
|
|
@@ -914,7 +1077,7 @@ module Kettle
|
|
|
914
1077
|
end
|
|
915
1078
|
|
|
916
1079
|
def release_waves(release_members)
|
|
917
|
-
ReleaseWaves.new(members: release_members).waves
|
|
1080
|
+
ReleaseWaves.new(members: release_members, configured_waves: config.release_waves).waves
|
|
918
1081
|
end
|
|
919
1082
|
|
|
920
1083
|
def append_dependency_floor_results(released_members:, dependent_members:, runner:, memo:)
|
data/lib/kettle/family.rb
CHANGED
|
@@ -18,6 +18,7 @@ require_relative "family/unreleased_gem_cleanup"
|
|
|
18
18
|
require_relative "family/local_install"
|
|
19
19
|
require_relative "family/nomono_bootstrap"
|
|
20
20
|
require_relative "family/debugger_bootstrap"
|
|
21
|
+
require_relative "family/appraisal_bootstrap"
|
|
21
22
|
require_relative "family/release_waves"
|
|
22
23
|
require_relative "family/workflow"
|
|
23
24
|
require_relative "family/discovery"
|
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.2.
|
|
4
|
+
version: 1.2.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -74,7 +74,7 @@ dependencies:
|
|
|
74
74
|
version: '2.3'
|
|
75
75
|
- - ">="
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: 2.5.
|
|
77
|
+
version: 2.5.16
|
|
78
78
|
type: :runtime
|
|
79
79
|
prerelease: false
|
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -84,7 +84,7 @@ dependencies:
|
|
|
84
84
|
version: '2.3'
|
|
85
85
|
- - ">="
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: 2.5.
|
|
87
|
+
version: 2.5.16
|
|
88
88
|
- !ruby/object:Gem::Dependency
|
|
89
89
|
name: kettle-test
|
|
90
90
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,7 +94,7 @@ dependencies:
|
|
|
94
94
|
version: '2.0'
|
|
95
95
|
- - ">="
|
|
96
96
|
- !ruby/object:Gem::Version
|
|
97
|
-
version: 2.0.
|
|
97
|
+
version: 2.0.18
|
|
98
98
|
type: :runtime
|
|
99
99
|
prerelease: false
|
|
100
100
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -104,7 +104,7 @@ dependencies:
|
|
|
104
104
|
version: '2.0'
|
|
105
105
|
- - ">="
|
|
106
106
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: 2.0.
|
|
107
|
+
version: 2.0.18
|
|
108
108
|
- !ruby/object:Gem::Dependency
|
|
109
109
|
name: tsort
|
|
110
110
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -309,6 +309,7 @@ files:
|
|
|
309
309
|
- README.md
|
|
310
310
|
- exe/kettle-family
|
|
311
311
|
- lib/kettle/family.rb
|
|
312
|
+
- lib/kettle/family/appraisal_bootstrap.rb
|
|
312
313
|
- lib/kettle/family/branch_lane_audit.rb
|
|
313
314
|
- lib/kettle/family/branch_target_config.rb
|
|
314
315
|
- lib/kettle/family/changelog_check.rb
|
|
@@ -344,10 +345,10 @@ licenses:
|
|
|
344
345
|
- AGPL-3.0-only
|
|
345
346
|
metadata:
|
|
346
347
|
homepage_uri: https://kettle-family.galtzo.com
|
|
347
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.
|
|
348
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.
|
|
348
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.22
|
|
349
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.22/CHANGELOG.md
|
|
349
350
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
350
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.
|
|
351
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.22
|
|
351
352
|
funding_uri: https://github.com/sponsors/pboling
|
|
352
353
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
353
354
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
��8i���"��g?�� ����3����q��IwN,�����x��_��6��*�(Ċ��3[��~/
|
|
2
|
+
w�ĺ|�������yw�S_WVkT�K�W�7qgI��
|
|
3
|
+
Ƥ�z��*]']������*:AƁ��V��]�L~�����$�O(q3·`���NE��R���{>[yǀ3�k�F�<,��-!�ɮ�}^�B3bj\��3����5�,e2�NڏE���o���̉�!�7�vs�<��fj�Ve�&�`���Iw�%,�ؘŢ�
|
|
4
|
+
��[ �#��@
|