semverve 0.3.0 → 0.4.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/CONTRIBUTING.md +2 -19
- data/Gemfile.lock +1 -1
- data/README.md +147 -27
- data/Rakefile +0 -3
- data/UPGRADING.md +78 -0
- data/lib/semverve/adapters.rb +292 -0
- data/lib/semverve/configuration.rb +52 -27
- data/lib/semverve/finding.rb +55 -0
- data/lib/semverve/fix_result.rb +39 -0
- data/lib/semverve/{version_metadata.rb → package_metadata.rb} +36 -116
- data/lib/semverve/presets.rb +4 -100
- data/lib/semverve/project_metadata.rb +8 -5
- data/lib/semverve/rails_config_metadata.rb +176 -0
- data/lib/semverve/railtie.rb +1 -1
- data/lib/semverve/task.rb +94 -150
- data/lib/semverve/version.rb +2 -2
- data/lib/semverve/version_checks.rb +518 -0
- data/lib/semverve/version_code_references.rb +32 -94
- data/lib/semverve/version_literal_rewriter.rb +78 -0
- data/lib/semverve/version_match_policy.rb +62 -0
- data/lib/semverve/version_references.rb +22 -87
- data/lib/semverve.rb +4 -0
- metadata +10 -4
- data/lib/semverve/docs_publisher/task.rb +0 -166
- data/lib/semverve/docs_publisher.rb +0 -370
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# TODO: Turn this into its own gem.
|
|
4
|
-
|
|
5
|
-
require "fileutils"
|
|
6
|
-
require "open3"
|
|
7
|
-
require "tmpdir"
|
|
8
|
-
|
|
9
|
-
require_relative "error"
|
|
10
|
-
|
|
11
|
-
module Semverve
|
|
12
|
-
##
|
|
13
|
-
# Publishes generated documentation to a Git branch through a temporary
|
|
14
|
-
# worktree.
|
|
15
|
-
class DocsPublisher
|
|
16
|
-
##
|
|
17
|
-
# Small command runner used by the publisher.
|
|
18
|
-
class Shell
|
|
19
|
-
##
|
|
20
|
-
# Runs a command and raises when it fails.
|
|
21
|
-
#
|
|
22
|
-
# @param [Array<String>] command
|
|
23
|
-
# @param [String, nil] chdir
|
|
24
|
-
#
|
|
25
|
-
# @return [String]
|
|
26
|
-
def run(command, chdir: nil)
|
|
27
|
-
stdout, stderr, status = capture_command(command, chdir: chdir)
|
|
28
|
-
return stdout if status.success?
|
|
29
|
-
|
|
30
|
-
raise Error, "Command failed: #{command.join(" ")}\n#{stderr}"
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
##
|
|
34
|
-
# Captures a command's standard output and raises when it fails.
|
|
35
|
-
#
|
|
36
|
-
# @param [Array<String>] command
|
|
37
|
-
# @param [String, nil] chdir
|
|
38
|
-
#
|
|
39
|
-
# @return [String]
|
|
40
|
-
def capture(command, chdir: nil)
|
|
41
|
-
run(command, chdir: chdir)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
##
|
|
45
|
-
# Whether a command exits successfully.
|
|
46
|
-
#
|
|
47
|
-
# @param [Array<String>] command
|
|
48
|
-
# @param [String, nil] chdir
|
|
49
|
-
#
|
|
50
|
-
# @return [Boolean]
|
|
51
|
-
def success?(command, chdir: nil)
|
|
52
|
-
_stdout, _stderr, status = capture_command(command, chdir: chdir)
|
|
53
|
-
status.success?
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
# Captures a command, omitting +chdir+ when none was provided.
|
|
60
|
-
#
|
|
61
|
-
# @param [Array<String>] command
|
|
62
|
-
# @param [String, nil] chdir
|
|
63
|
-
#
|
|
64
|
-
# @return [Array(String, String, Process::Status)]
|
|
65
|
-
def capture_command(command, chdir:)
|
|
66
|
-
options = chdir ? {chdir: chdir} : {}
|
|
67
|
-
|
|
68
|
-
Open3.capture3(*command, **options)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
##
|
|
73
|
-
# Source project root.
|
|
74
|
-
#
|
|
75
|
-
# @return [String]
|
|
76
|
-
attr_accessor :root
|
|
77
|
-
|
|
78
|
-
##
|
|
79
|
-
# Directory containing generated documentation, relative to +root+.
|
|
80
|
-
#
|
|
81
|
-
# @return [String]
|
|
82
|
-
attr_accessor :source_dir
|
|
83
|
-
|
|
84
|
-
##
|
|
85
|
-
# Documentation directory on the publishing branch.
|
|
86
|
-
#
|
|
87
|
-
# @return [String]
|
|
88
|
-
attr_accessor :target_dir
|
|
89
|
-
|
|
90
|
-
##
|
|
91
|
-
# Branch that receives generated documentation.
|
|
92
|
-
#
|
|
93
|
-
# @return [String]
|
|
94
|
-
attr_accessor :branch
|
|
95
|
-
|
|
96
|
-
##
|
|
97
|
-
# Remote used when pushing the publishing branch.
|
|
98
|
-
#
|
|
99
|
-
# @return [String]
|
|
100
|
-
attr_accessor :remote
|
|
101
|
-
|
|
102
|
-
##
|
|
103
|
-
# Commit message for generated documentation updates.
|
|
104
|
-
#
|
|
105
|
-
# @return [String]
|
|
106
|
-
attr_accessor :commit_message
|
|
107
|
-
|
|
108
|
-
##
|
|
109
|
-
# Optional path for the temporary worktree.
|
|
110
|
-
#
|
|
111
|
-
# @return [String, nil]
|
|
112
|
-
attr_accessor :worktree_path
|
|
113
|
-
|
|
114
|
-
##
|
|
115
|
-
# Whether dirty source working trees are allowed.
|
|
116
|
-
#
|
|
117
|
-
# @return [Boolean]
|
|
118
|
-
attr_accessor :allow_dirty
|
|
119
|
-
|
|
120
|
-
##
|
|
121
|
-
# Whether the publishing branch should be pushed.
|
|
122
|
-
#
|
|
123
|
-
# @return [Boolean]
|
|
124
|
-
attr_accessor :push
|
|
125
|
-
|
|
126
|
-
##
|
|
127
|
-
# Whether to report changes without committing or pushing.
|
|
128
|
-
#
|
|
129
|
-
# @return [Boolean]
|
|
130
|
-
attr_accessor :dry_run
|
|
131
|
-
|
|
132
|
-
##
|
|
133
|
-
# Command runner used for Git commands.
|
|
134
|
-
#
|
|
135
|
-
# @return [#run, #capture, #success?]
|
|
136
|
-
attr_accessor :command_runner
|
|
137
|
-
|
|
138
|
-
##
|
|
139
|
-
# Output stream for status messages.
|
|
140
|
-
#
|
|
141
|
-
# @return [#puts]
|
|
142
|
-
attr_accessor :output
|
|
143
|
-
|
|
144
|
-
##
|
|
145
|
-
# Initializes a documentation publisher.
|
|
146
|
-
#
|
|
147
|
-
# @yieldparam [Semverve::DocsPublisher] publisher
|
|
148
|
-
#
|
|
149
|
-
# @return [Semverve::DocsPublisher]
|
|
150
|
-
def initialize
|
|
151
|
-
@root = Dir.pwd
|
|
152
|
-
@source_dir = "docs"
|
|
153
|
-
@target_dir = "docs"
|
|
154
|
-
@branch = "gh-pages"
|
|
155
|
-
@remote = "origin"
|
|
156
|
-
@commit_message = "Update generated documentation"
|
|
157
|
-
@worktree_path = nil
|
|
158
|
-
@allow_dirty = false
|
|
159
|
-
@push = true
|
|
160
|
-
@dry_run = false
|
|
161
|
-
@command_runner = Shell.new
|
|
162
|
-
@output = $stdout
|
|
163
|
-
|
|
164
|
-
yield self if block_given?
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
##
|
|
168
|
-
# Publishes generated documentation.
|
|
169
|
-
#
|
|
170
|
-
# @return [Boolean] whether documentation changes were found
|
|
171
|
-
def publish
|
|
172
|
-
validate!
|
|
173
|
-
ensure_clean_source_worktree unless allow_dirty
|
|
174
|
-
|
|
175
|
-
with_worktree do |worktree|
|
|
176
|
-
sync_docs_to(worktree)
|
|
177
|
-
|
|
178
|
-
unless publishing_worktree_changed?(worktree)
|
|
179
|
-
output.puts "Documentation is already current on #{branch}."
|
|
180
|
-
return false
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
if dry_run
|
|
184
|
-
output.puts "Documentation changes detected for #{branch}; dry run did not commit or push."
|
|
185
|
-
return true
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
commit_docs(worktree)
|
|
189
|
-
push_docs(worktree) if push
|
|
190
|
-
output.puts "Published documentation to #{remote}/#{branch}."
|
|
191
|
-
true
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
private
|
|
196
|
-
|
|
197
|
-
##
|
|
198
|
-
# Validates publishing configuration.
|
|
199
|
-
#
|
|
200
|
-
# @return [void]
|
|
201
|
-
def validate!
|
|
202
|
-
raise Error, "Documentation source directory does not exist: #{source_path}." unless File.directory?(source_path)
|
|
203
|
-
|
|
204
|
-
if target_dir.nil? || target_dir.empty? || target_dir == "." || target_dir.start_with?("/")
|
|
205
|
-
raise Error, "target_dir must be a relative directory such as \"docs\"."
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
##
|
|
210
|
-
# Absolute source project root.
|
|
211
|
-
#
|
|
212
|
-
# @return [String]
|
|
213
|
-
def source_root
|
|
214
|
-
@source_root ||= File.expand_path(root)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
##
|
|
218
|
-
# Absolute source documentation directory.
|
|
219
|
-
#
|
|
220
|
-
# @return [String]
|
|
221
|
-
def source_path
|
|
222
|
-
File.expand_path(source_dir, source_root)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
##
|
|
226
|
-
# Ensures the source working tree is clean.
|
|
227
|
-
#
|
|
228
|
-
# @return [void]
|
|
229
|
-
def ensure_clean_source_worktree
|
|
230
|
-
status = git_capture(source_root, "status", "--porcelain")
|
|
231
|
-
return if status.empty?
|
|
232
|
-
|
|
233
|
-
raise Error, "Working tree must be clean before publishing documentation. Commit, stash, or set allow_dirty."
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
##
|
|
237
|
-
# Yields a temporary publishing worktree and removes it afterward.
|
|
238
|
-
#
|
|
239
|
-
# @yieldparam [String] worktree
|
|
240
|
-
#
|
|
241
|
-
# @return [Object]
|
|
242
|
-
def with_worktree
|
|
243
|
-
temporary_path = worktree_path || Dir.mktmpdir("semverve-docs-publish-")
|
|
244
|
-
temporary_worktree = worktree_path.nil?
|
|
245
|
-
worktree_added = false
|
|
246
|
-
|
|
247
|
-
if temporary_worktree
|
|
248
|
-
FileUtils.rm_rf(temporary_path)
|
|
249
|
-
elsif File.exist?(temporary_path)
|
|
250
|
-
raise Error, "Worktree path already exists: #{temporary_path}."
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
add_worktree(temporary_path)
|
|
254
|
-
worktree_added = true
|
|
255
|
-
yield temporary_path
|
|
256
|
-
ensure
|
|
257
|
-
remove_worktree(temporary_path) if temporary_path && worktree_added
|
|
258
|
-
FileUtils.rm_rf(temporary_path) if temporary_path && temporary_worktree
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
##
|
|
262
|
-
# Adds a worktree for the publishing branch.
|
|
263
|
-
#
|
|
264
|
-
# @param [String] path
|
|
265
|
-
#
|
|
266
|
-
# @return [void]
|
|
267
|
-
def add_worktree(path)
|
|
268
|
-
if local_branch?
|
|
269
|
-
git_run(source_root, "worktree", "add", path, branch)
|
|
270
|
-
elsif remote_branch?
|
|
271
|
-
git_run(source_root, "worktree", "add", "-b", branch, path, "#{remote}/#{branch}")
|
|
272
|
-
else
|
|
273
|
-
raise Error, "Could not find #{branch} locally or at #{remote}/#{branch}."
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
##
|
|
278
|
-
# Removes a worktree.
|
|
279
|
-
#
|
|
280
|
-
# @param [String] path
|
|
281
|
-
#
|
|
282
|
-
# @return [void]
|
|
283
|
-
def remove_worktree(path)
|
|
284
|
-
git_run(source_root, "worktree", "remove", "--force", path) if File.directory?(path)
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
##
|
|
288
|
-
# Whether the publishing branch exists locally.
|
|
289
|
-
#
|
|
290
|
-
# @return [Boolean]
|
|
291
|
-
def local_branch?
|
|
292
|
-
command_runner.success?(["git", "show-ref", "--verify", "--quiet", "refs/heads/#{branch}"], chdir: source_root)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
##
|
|
296
|
-
# Whether the publishing branch exists as a remote-tracking branch.
|
|
297
|
-
#
|
|
298
|
-
# @return [Boolean]
|
|
299
|
-
def remote_branch?
|
|
300
|
-
command_runner.success?(["git", "show-ref", "--verify", "--quiet", "refs/remotes/#{remote}/#{branch}"], chdir: source_root)
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
##
|
|
304
|
-
# Copies generated documentation into the publishing worktree.
|
|
305
|
-
#
|
|
306
|
-
# @param [String] worktree
|
|
307
|
-
#
|
|
308
|
-
# @return [void]
|
|
309
|
-
def sync_docs_to(worktree)
|
|
310
|
-
target_path = File.expand_path(target_dir, worktree)
|
|
311
|
-
|
|
312
|
-
FileUtils.rm_rf(target_path)
|
|
313
|
-
FileUtils.mkdir_p(File.dirname(target_path))
|
|
314
|
-
FileUtils.cp_r(source_path, target_path)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
##
|
|
318
|
-
# Whether the publishing worktree has documentation changes.
|
|
319
|
-
#
|
|
320
|
-
# @param [String] worktree
|
|
321
|
-
#
|
|
322
|
-
# @return [Boolean]
|
|
323
|
-
def publishing_worktree_changed?(worktree)
|
|
324
|
-
!git_capture(worktree, "status", "--porcelain", "--", target_dir).empty?
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
##
|
|
328
|
-
# Commits documentation changes in the publishing worktree.
|
|
329
|
-
#
|
|
330
|
-
# @param [String] worktree
|
|
331
|
-
#
|
|
332
|
-
# @return [void]
|
|
333
|
-
def commit_docs(worktree)
|
|
334
|
-
git_run(worktree, "add", target_dir)
|
|
335
|
-
git_run(worktree, "commit", "-m", commit_message)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
##
|
|
339
|
-
# Pushes documentation changes.
|
|
340
|
-
#
|
|
341
|
-
# @param [String] worktree
|
|
342
|
-
#
|
|
343
|
-
# @return [void]
|
|
344
|
-
def push_docs(worktree)
|
|
345
|
-
git_run(worktree, "push", remote, branch)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
##
|
|
349
|
-
# Runs a Git command in a directory.
|
|
350
|
-
#
|
|
351
|
-
# @param [String] directory
|
|
352
|
-
# @param [Array<String>] arguments
|
|
353
|
-
#
|
|
354
|
-
# @return [String]
|
|
355
|
-
def git_run(directory, *arguments)
|
|
356
|
-
command_runner.run(["git", *arguments], chdir: directory)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
##
|
|
360
|
-
# Captures a Git command in a directory.
|
|
361
|
-
#
|
|
362
|
-
# @param [String] directory
|
|
363
|
-
# @param [Array<String>] arguments
|
|
364
|
-
#
|
|
365
|
-
# @return [String]
|
|
366
|
-
def git_capture(directory, *arguments)
|
|
367
|
-
command_runner.capture(["git", *arguments], chdir: directory)
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
end
|