gem_kit-release 0.2.2 → 0.3.1
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
- data/README.md +1 -1
- data/lib/gem_kit/release/cli.rb +14 -7
- data/lib/gem_kit/release/commands/bump.rb +69 -0
- data/lib/gem_kit/release/commands/release.rb +41 -4
- data/lib/gem_kit/release/gate.rb +59 -4
- data/lib/gem_kit/release/project.rb +47 -0
- data/lib/gem_kit/release/templates/RELEASE.md.erb +14 -12
- data/lib/gem_kit/release/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbdaca4c93bb73c92daa2e585aa8f6dbcef18a7717a6f9c3549f943272b13947
|
|
4
|
+
data.tar.gz: 8d41aaa5cd762b8c6660d6cffbb669d1d17ce5a604ea578d1673681eb6c39809
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e431cf45975cc92e084d02988ed88a68053bf538f386bf734a167a9e0a6914de609a8d22555ba4e238c41ff6ae932bbfa44e5933fd665f19b0c4468336d60e8b
|
|
7
|
+
data.tar.gz: 852b384995fbbd1ee34b28200cdcd472eddb6c3460e99101b70df56da4435b45be91fc6805959f21eb942bfc7287f883000e70e1566f684e518102727a29f1d1
|
data/README.md
CHANGED
|
@@ -65,7 +65,7 @@ gem kit changelog --write # have an AI CLI write the entry
|
|
|
65
65
|
gem kit changelog # lint it
|
|
66
66
|
gem kit deprecations # what is still outstanding
|
|
67
67
|
gem kit release --dry-run # run the gates
|
|
68
|
-
gem kit
|
|
68
|
+
gem kit release # ...then build, push and tag
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
| Command | What it does |
|
data/lib/gem_kit/release/cli.rb
CHANGED
|
@@ -105,21 +105,28 @@ module GemKit
|
|
|
105
105
|
Commands::Deprecations.new(options).call(version)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
desc "release", "Gate, build and
|
|
108
|
+
desc "release", "Gate, build, push and tag this gem"
|
|
109
109
|
long_desc <<~TXT
|
|
110
|
-
|
|
110
|
+
Three gates, all before `gem build` runs:
|
|
111
111
|
|
|
112
112
|
the changelog must have a non-empty, correctly formatted section for this
|
|
113
|
-
version, sitting at the top of the released list;
|
|
114
|
-
disappear in this version may still be in the tree
|
|
113
|
+
version, sitting at the top of the released list; nothing promised to
|
|
114
|
+
disappear in this version may still be in the tree; and the working tree
|
|
115
|
+
must be committed, because a gem built from uncommitted files is a gem
|
|
116
|
+
whose source exists nowhere.
|
|
117
|
+
|
|
118
|
+
Then builds the gemspec, pushes it, and tags the release — after the push,
|
|
119
|
+
so a tag never names a version that failed to publish. Requires RubyGems
|
|
120
|
+
push credentials.
|
|
115
121
|
|
|
116
|
-
Then builds the gemspec and pushes it. Requires RubyGems push credentials.
|
|
117
122
|
--dry-run runs the gates and stops, which is what belongs in CI.
|
|
118
123
|
TXT
|
|
119
124
|
method_option :dry_run, type: :boolean, default: false, aliases: "-n",
|
|
120
125
|
desc: "Run the gates and stop"
|
|
121
|
-
method_option :tag, type: :boolean, default:
|
|
122
|
-
desc: "Tag the release
|
|
126
|
+
method_option :tag, type: :boolean, default: true,
|
|
127
|
+
desc: "Tag the release and push the tag (--no-tag to skip)"
|
|
128
|
+
method_option :allow_dirty, type: :boolean, default: false,
|
|
129
|
+
desc: "Release even with uncommitted changes"
|
|
123
130
|
def release
|
|
124
131
|
Commands::Release.new(options).call
|
|
125
132
|
end
|
|
@@ -35,6 +35,8 @@ module GemKit
|
|
|
35
35
|
version_file.write(target)
|
|
36
36
|
say("#{current} -> #{target}")
|
|
37
37
|
|
|
38
|
+
relock(target)
|
|
39
|
+
|
|
38
40
|
upcoming = gate.upcoming_deprecations(target)
|
|
39
41
|
unless upcoming.empty?
|
|
40
42
|
say("#{upcoming.size} deprecation(s) still outstanding (none due in #{target}).")
|
|
@@ -45,6 +47,35 @@ module GemKit
|
|
|
45
47
|
say
|
|
46
48
|
say("#{RED}now run: gem kit changelog --write#{RESET}")
|
|
47
49
|
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# A Gemfile that says `gemspec` or `path: "."` records this gem's own
|
|
54
|
+
# version in Gemfile.lock, so a bump that only rewrites the version
|
|
55
|
+
# file leaves the two disagreeing — and bundler refuses outright
|
|
56
|
+
# under frozen mode, which is what bundlerEnv sets. Relock, so the
|
|
57
|
+
# bump is one coherent change rather than a trap for the next
|
|
58
|
+
# command anyone runs.
|
|
59
|
+
#
|
|
60
|
+
# `bundle lock` rather than `bundle install`: nothing needs
|
|
61
|
+
# installing, only the lockfile needs to agree.
|
|
62
|
+
def relock(target)
|
|
63
|
+
return unless project.self_locked?
|
|
64
|
+
|
|
65
|
+
say("Relocking #{File.basename(project.lockfile_path)}…")
|
|
66
|
+
|
|
67
|
+
ok = Dir.chdir(project.root) do
|
|
68
|
+
# BUNDLE_FROZEN is exactly what stops this, and inheriting it
|
|
69
|
+
# from an ambient devshell would defeat the point.
|
|
70
|
+
system({"BUNDLE_FROZEN" => "false", "BUNDLE_GEMFILE" => nil},
|
|
71
|
+
"bundle", "lock", out: File::NULL, err: File::NULL)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return if ok
|
|
75
|
+
|
|
76
|
+
say("#{RED}could not relock — run `bundle lock` before committing " \
|
|
77
|
+
"#{target}#{RESET}")
|
|
78
|
+
end
|
|
48
79
|
end
|
|
49
80
|
end
|
|
50
81
|
end
|
|
@@ -95,6 +126,44 @@ describe "gem_kit/release/commands/bump" do
|
|
|
95
126
|
end
|
|
96
127
|
end
|
|
97
128
|
|
|
129
|
+
# The version lives in two files when a gem is in its own bundle, and a bump
|
|
130
|
+
# that moves one of them leaves bundler refusing to do anything.
|
|
131
|
+
it "relocks a lockfile that records this gem's own version" do
|
|
132
|
+
with_gem do |dir|
|
|
133
|
+
File.write(File.join(dir, "Gemfile"), %(source "https://rubygems.org"\ngemspec\n))
|
|
134
|
+
File.write(File.join(dir, "Gemfile.lock"), <<~LOCK)
|
|
135
|
+
PATH
|
|
136
|
+
remote: .
|
|
137
|
+
specs:
|
|
138
|
+
demo (1.2.3)
|
|
139
|
+
|
|
140
|
+
GEM
|
|
141
|
+
remote: https://rubygems.org/
|
|
142
|
+
|
|
143
|
+
PLATFORMS
|
|
144
|
+
ruby
|
|
145
|
+
|
|
146
|
+
DEPENDENCIES
|
|
147
|
+
demo!
|
|
148
|
+
|
|
149
|
+
BUNDLED WITH
|
|
150
|
+
2.7.2
|
|
151
|
+
LOCK
|
|
152
|
+
|
|
153
|
+
_status, out, _err = invoke(["bump", "minor"], dir)
|
|
154
|
+
|
|
155
|
+
out.should.match(/Relocking Gemfile\.lock/)
|
|
156
|
+
File.read(File.join(dir, "Gemfile.lock")).should.match(/demo \(1\.3\.0\)/)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "says nothing about a lockfile that does not record this gem" do
|
|
161
|
+
with_gem do |dir|
|
|
162
|
+
_status, out, _err = invoke(["bump", "minor"], dir)
|
|
163
|
+
out.should.not.match(/Relocking/)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
98
167
|
it "mentions deprecations outstanding but not yet due" do
|
|
99
168
|
with_gem do |dir|
|
|
100
169
|
_status, out, _err = invoke(["bump", "minor"], dir, deprecations: [["Old", "New", "9.0"]])
|
|
@@ -5,15 +5,15 @@ require_relative "command"
|
|
|
5
5
|
module GemKit
|
|
6
6
|
module Release
|
|
7
7
|
module Commands
|
|
8
|
-
# Gate, build, push. The gates run before anything is built, because
|
|
9
|
-
# half-done release is worse than a refused one.
|
|
8
|
+
# Gate, build, push, tag. The gates run before anything is built, because
|
|
9
|
+
# a half-done release is worse than a refused one.
|
|
10
10
|
class Release < Command
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def call
|
|
15
15
|
version = project.version.to_s
|
|
16
|
-
problems = gate.release_problems(version)
|
|
16
|
+
problems = gate.release_problems(version, allow_dirty: options[:allow_dirty])
|
|
17
17
|
|
|
18
18
|
refuse("Refusing to release #{project.name} #{version}:", problems) unless problems.empty?
|
|
19
19
|
|
|
@@ -35,7 +35,11 @@ module GemKit
|
|
|
35
35
|
|
|
36
36
|
say("Released #{project.name} #{version}")
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
# Tagging is part of releasing, not a flag someone remembers. It runs
|
|
39
|
+
# after the push rather than before, so a tag never points at a
|
|
40
|
+
# version that failed to publish — and it pushes, because the gem is
|
|
41
|
+
# public by now and a tag only this machine has is half a release.
|
|
42
|
+
Tag.new(options.merge(push: true)).call unless options[:tag] == false
|
|
39
43
|
end
|
|
40
44
|
end
|
|
41
45
|
end
|
|
@@ -56,6 +60,39 @@ describe "gem_kit/release/commands/release" do
|
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
|
|
63
|
+
# A gem built from an uncommitted tree matches nothing in git.
|
|
64
|
+
it "refuses while the working tree is dirty, before building anything" do
|
|
65
|
+
with_gem do |dir|
|
|
66
|
+
system("git init -q #{dir}")
|
|
67
|
+
|
|
68
|
+
status, _out, err = invoke(["release"], dir)
|
|
69
|
+
|
|
70
|
+
status.should == 1
|
|
71
|
+
err.should.match(/uncommitted change/)
|
|
72
|
+
err.should.match(/--allow-dirty/)
|
|
73
|
+
Dir[File.join(dir, "*.gem")].should.be.empty
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "--allow-dirty releases anyway" do
|
|
78
|
+
with_gem do |dir|
|
|
79
|
+
system("git init -q #{dir}")
|
|
80
|
+
|
|
81
|
+
# --dry-run stops before the push, which is as far as a spec should go.
|
|
82
|
+
invoke(["release", "--dry-run", "--allow-dirty"], dir).first.should == 0
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "is satisfied once the tree is committed" do
|
|
87
|
+
with_gem do |dir|
|
|
88
|
+
system("git init -q #{dir}")
|
|
89
|
+
system("git -C #{dir} add -A")
|
|
90
|
+
system("git -C #{dir} -c user.name=t -c user.email=t@t commit -q -m x")
|
|
91
|
+
|
|
92
|
+
invoke(["release", "--dry-run"], dir).first.should == 0
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
59
96
|
it "refuses without a changelog entry, before building anything" do
|
|
60
97
|
with_gem(changelog: :none) do |dir|
|
|
61
98
|
status, _out, err = invoke(["release"], dir)
|
data/lib/gem_kit/release/gate.rb
CHANGED
|
@@ -47,14 +47,39 @@ module GemKit
|
|
|
47
47
|
version ? changelog.release_problems(version) : changelog.problems
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# What is in the working tree but not in git. A gem built from an
|
|
51
|
+
# uncommitted tree is a gem whose source exists nowhere — and `bump` and
|
|
52
|
+
# `changelog --write` leave exactly two such files behind, which is
|
|
53
|
+
# precisely the moment someone reaches for `release`.
|
|
54
|
+
#
|
|
55
|
+
# A directory that is not a git repository is not a problem: this gate
|
|
56
|
+
# has nothing to say about it.
|
|
57
|
+
def working_tree_problems
|
|
58
|
+
return [] unless git?
|
|
59
|
+
|
|
60
|
+
dirty = Dir.chdir(project.root) { `git status --porcelain`.lines.map(&:strip) }
|
|
61
|
+
return [] if dirty.empty?
|
|
62
|
+
|
|
63
|
+
["#{dirty.size} uncommitted change(s) — the gem would match nothing in git:",
|
|
64
|
+
*dirty.first(10).map { |line| " #{line}" },
|
|
65
|
+
*(dirty.size > 10 ? [" … and #{dirty.size - 10} more"] : []),
|
|
66
|
+
"",
|
|
67
|
+
"The bump and the changelog belong in one commit. Or pass --allow-dirty."]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def git?
|
|
71
|
+
Dir.chdir(project.root) { system("git rev-parse --git-dir >/dev/null 2>&1") }
|
|
72
|
+
end
|
|
73
|
+
|
|
50
74
|
# Everything standing between the project and releasing `version`.
|
|
51
|
-
def release_problems(version = project.version)
|
|
75
|
+
def release_problems(version = project.version, allow_dirty: false)
|
|
52
76
|
problems = []
|
|
53
77
|
|
|
54
78
|
changelog_problems(version).each { |problem| problems << problem }
|
|
55
79
|
deprecation_problems(version).each do |problem|
|
|
56
80
|
problems << "deprecation due in #{version}: #{problem}"
|
|
57
81
|
end
|
|
82
|
+
working_tree_problems.each { |problem| problems << problem } unless allow_dirty
|
|
58
83
|
|
|
59
84
|
problems
|
|
60
85
|
end
|
|
@@ -76,10 +101,10 @@ describe "gem_kit/release/gate" do
|
|
|
76
101
|
|
|
77
102
|
# A Project stub: the Gate only asks it for a changelog path and a version,
|
|
78
103
|
# and to load the library (a no-op here — the specs drive the registry).
|
|
79
|
-
stub_project = lambda do |changelog_path, version: "1.0.0"|
|
|
80
|
-
Struct.new(:changelog_path, :version) do
|
|
104
|
+
stub_project = lambda do |changelog_path, version: "1.0.0", root: nil|
|
|
105
|
+
Struct.new(:changelog_path, :version, :root) do
|
|
81
106
|
def load! = true
|
|
82
|
-
end.new(changelog_path, Gem::Version.new(version))
|
|
107
|
+
end.new(changelog_path, Gem::Version.new(version), root || File.dirname(changelog_path))
|
|
83
108
|
end
|
|
84
109
|
|
|
85
110
|
clean_changelog = <<~MD
|
|
@@ -155,6 +180,36 @@ describe "gem_kit/release/gate" do
|
|
|
155
180
|
end
|
|
156
181
|
end
|
|
157
182
|
|
|
183
|
+
# The gate that catches what `bump` and `changelog --write` leave behind.
|
|
184
|
+
it "reports an uncommitted working tree, and clears once it is committed" do
|
|
185
|
+
Dir.mktmpdir do |dir|
|
|
186
|
+
File.write(File.join(dir, "CHANGELOG.md"), clean_changelog)
|
|
187
|
+
system("git init -q #{dir}")
|
|
188
|
+
|
|
189
|
+
isolated.call do
|
|
190
|
+
gate = GemKit::Release::Gate.new(stub_project.call(File.join(dir, "CHANGELOG.md"), root: dir))
|
|
191
|
+
|
|
192
|
+
gate.release_problems("1.0.0").first.should.match(/uncommitted change/)
|
|
193
|
+
gate.release_problems("1.0.0", allow_dirty: true).should == []
|
|
194
|
+
|
|
195
|
+
system("git -C #{dir} add -A")
|
|
196
|
+
system("git -C #{dir} -c user.name=t -c user.email=t@t commit -q -m x")
|
|
197
|
+
gate.release_problems("1.0.0").should == []
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it "says nothing about a directory that is not a git repository" do
|
|
203
|
+
Dir.mktmpdir do |dir|
|
|
204
|
+
File.write(File.join(dir, "CHANGELOG.md"), clean_changelog)
|
|
205
|
+
|
|
206
|
+
isolated.call do
|
|
207
|
+
GemKit::Release::Gate.new(stub_project.call(File.join(dir, "CHANGELOG.md"), root: dir))
|
|
208
|
+
.working_tree_problems.should == []
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
158
213
|
it "checks changelog format alone when given no version" do
|
|
159
214
|
Dir.mktmpdir do |dir|
|
|
160
215
|
path = File.join(dir, "CHANGELOG.md")
|
|
@@ -105,6 +105,21 @@ module GemKit
|
|
|
105
105
|
multi_gem? ? "CHANGELOG-#{name}.md" : "CHANGELOG.md"
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
def lockfile_path
|
|
109
|
+
File.join(root, "Gemfile.lock")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A Gemfile that says `gemspec` or `path: "."` puts this gem in its own
|
|
113
|
+
# bundle, and the lockfile then records this gem's version. Bumping
|
|
114
|
+
# without relocking leaves the two disagreeing — which bundler reports as
|
|
115
|
+
# "the gemspecs for path gems changed", and refuses outright under
|
|
116
|
+
# frozen mode, as bundlerEnv sets.
|
|
117
|
+
def self_locked?
|
|
118
|
+
return false unless File.exist?(lockfile_path)
|
|
119
|
+
|
|
120
|
+
File.read(lockfile_path).match?(/^\s{4}#{Regexp.escape(name)} \(\d/)
|
|
121
|
+
end
|
|
122
|
+
|
|
108
123
|
# `v1.2.3` says which version but not which gem, which is fine until a
|
|
109
124
|
# repository holds two of them at the same version.
|
|
110
125
|
def tag_prefix
|
|
@@ -252,6 +267,38 @@ describe "gem_kit/release/project" do
|
|
|
252
267
|
end
|
|
253
268
|
end
|
|
254
269
|
|
|
270
|
+
# A gem whose own Gemfile pulls it in has its version written in the lockfile
|
|
271
|
+
# as well as the version file, and both have to move together.
|
|
272
|
+
it "knows whether the lockfile records this gem's own version" do
|
|
273
|
+
with_project.call do |dir|
|
|
274
|
+
GemKit::Release::Project.detect(dir).self_locked?.should.be.false
|
|
275
|
+
|
|
276
|
+
File.write(File.join(dir, "Gemfile.lock"), <<~LOCK)
|
|
277
|
+
PATH
|
|
278
|
+
remote: .
|
|
279
|
+
specs:
|
|
280
|
+
demo (1.2.3)
|
|
281
|
+
|
|
282
|
+
GEM
|
|
283
|
+
remote: https://rubygems.org/
|
|
284
|
+
LOCK
|
|
285
|
+
GemKit::Release::Project.detect(dir).self_locked?.should.be.true
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "does not mistake another gem's line in the lockfile for its own" do
|
|
290
|
+
with_project.call do |dir|
|
|
291
|
+
File.write(File.join(dir, "Gemfile.lock"), <<~LOCK)
|
|
292
|
+
GEM
|
|
293
|
+
remote: https://rubygems.org/
|
|
294
|
+
specs:
|
|
295
|
+
other-demo (1.0.0)
|
|
296
|
+
demo-helper (2.0.0)
|
|
297
|
+
LOCK
|
|
298
|
+
GemKit::Release::Project.detect(dir).self_locked?.should.be.false
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
255
302
|
it "finds an ERB version template when the project generates its version file" do
|
|
256
303
|
with_project.call do |dir|
|
|
257
304
|
GemKit::Release::Project.detect(dir).version_template.should.be.nil
|
|
@@ -8,8 +8,7 @@ The whole process, in order:
|
|
|
8
8
|
<%= step("gem kit changelog#{gem_flag} --write") %># 3. write the entry
|
|
9
9
|
<%= step("gem kit changelog#{gem_flag} <VERSION>") %># 4. check it
|
|
10
10
|
<%= step(%(git commit -am "Release ...")) %># 5. commit the bump + changelog
|
|
11
|
-
<%= step("gem kit release#{gem_flag}") %># 6. build
|
|
12
|
-
<%= step("gem kit tag#{gem_flag} --push") %># 7. tag it
|
|
11
|
+
<%= step("gem kit release#{gem_flag}") %># 6. gate, build, push, tag
|
|
13
12
|
```
|
|
14
13
|
|
|
15
14
|
Steps 2 and 6 are gates: they refuse to proceed when something is missing.
|
|
@@ -119,27 +118,30 @@ unwritten in git is the failure this process exists to prevent.
|
|
|
119
118
|
## 6. Release
|
|
120
119
|
|
|
121
120
|
```sh
|
|
122
|
-
gem kit release<%= gem_flag %>
|
|
121
|
+
gem kit release<%= gem_flag %> # or --dry-run, which is the CI check
|
|
123
122
|
```
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
Three gates, all before `gem build` runs:
|
|
126
125
|
|
|
127
126
|
1. **Changelog** — this version needs its own non-empty, correctly formatted
|
|
128
127
|
section, sitting at the top of the released list.
|
|
129
128
|
2. **Deprecations** — nothing promised to disappear in this version may still
|
|
130
129
|
be in the tree.
|
|
130
|
+
3. **The working tree** — everything committed. A gem built from uncommitted
|
|
131
|
+
files is a gem whose source exists nowhere, and step 2 and step 3 above
|
|
132
|
+
leave exactly two such files behind. `--allow-dirty` overrides it.
|
|
131
133
|
|
|
132
|
-
Then `gem build
|
|
134
|
+
Then `gem build`, `gem push`, and the tag — `<%= tag_prefix %><%= version %>`,
|
|
135
|
+
created and pushed *after* the gem, so a tag never names a version that failed
|
|
136
|
+
to publish. `--no-tag` skips it.
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
Requires RubyGems push credentials.
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
gem kit
|
|
138
|
-
|
|
140
|
+
The tag matters beyond bookkeeping: it is what the next
|
|
141
|
+
`gem kit changelog<%= gem_flag %> --write` uses to find the commit range, so a
|
|
142
|
+
missing one makes the following release's changelog harder to write.
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
the next `gem kit changelog<%= gem_flag %> --write` uses to find the commit range, so a missing
|
|
142
|
-
one makes the following release's changelog harder to write.
|
|
144
|
+
`gem kit tag<%= gem_flag %>` still exists for tagging on its own.
|
|
143
145
|
|
|
144
146
|
## When something goes wrong
|
|
145
147
|
|