revision 1.2.7 → 1.5.2
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/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.org +181 -173
- data/lib/revision/cli.rb +21 -3
- data/lib/revision/info.rb +9 -7
- data/lib/revision/releasable.rb +102 -25
- data/lib/revision/version.rb +53 -27
- data/releasables.yaml +1 -1
- data/revision.gemspec +6 -14
- metadata +17 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4668b7330bbac8c0ff2be80e329372715b4e9a1c855324208dad6d0975c3fc53
|
4
|
+
data.tar.gz: 27b0b200fd4e66fc6af0a579d4443d345f08e4205f1e89aafecebf7597d85365
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 533d69c1f6746f5aeedbbe72420937ed6976eb6dad4dd2ca8a75a7b13df8fa749b65284b137764997f62157a395f42d3ab11f23295b6b5035e14a57b4f524988
|
7
|
+
data.tar.gz: cc6badb780cfff6bae1df61c5697df6c2c37db2221178fe9446c132aabbbe5d0b5a3ced994ca416b292dadef7276b99ef238b46ac502ea7b505955fcd951b05a
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.org
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#+TITLE: Revision management gem
|
2
2
|
#+AUTHOR: Cormac Cannon
|
3
|
-
#+EMAIL: cormac.cannon@neuromoddevices.com
|
4
3
|
#+LANGUAGE: en
|
5
4
|
|
6
5
|
# Generic properties
|
@@ -24,245 +23,254 @@
|
|
24
23
|
# or alternatively #+SETUPFILE: theme-readtheorg.setup
|
25
24
|
|
26
25
|
* Overview
|
26
|
+
This gem automates revision management for source projects. The tool is language agnostic (used for C, ruby and matlab projects, to date -- though you're probably better off using bundler for ruby projects). It supports per-project configuration using a yaml-format file called =releasables.yaml= located at the project root.
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
It currently supports the following functionality
|
29
|
+
- Manage 3-component revision IDs embedded natively in source file
|
30
|
+
- Embeds/updates/extracts changelog in source file header/footer comments
|
31
|
+
- Automatically prompts for a changelog entry each time a revision identifier is incremented
|
32
|
+
- Optionally commits and tags changes to a git repo after an update to the revision ID
|
33
|
+
- Builds and archives projects in zip format (including release notes and arbitrary release artefacts defined
|
34
|
+
- Deploys
|
31
35
|
|
32
|
-
|
33
|
-
- Manage 3-component revision IDs embedded natively in source file
|
34
|
-
- Embeds/updates/extracts changelog in source file header/footer comments
|
35
|
-
- Automatically prompts for a changelog entry each time a revision identifier is incremented
|
36
|
-
- Optionally commits and tags changes to a git repo after an update to the revision ID
|
37
|
-
- Builds and archives projects in zip format (including release notes and arbitrary release artefacts defined
|
38
|
-
- Deploys
|
36
|
+
Worked on sporadically to allow me to tag, archive and deploy projects in multiple languages in a consistent fashion.
|
39
37
|
|
40
38
|
* Installation
|
41
39
|
** Dependencies
|
42
|
-
|
40
|
+
Ruby / Rubygems installed
|
43
41
|
|
44
42
|
** From rubygems.org
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
#+BEGIN_SRC sh
|
45
|
+
gem install revision
|
46
|
+
#+END_SRC
|
49
47
|
|
50
48
|
** From source
|
51
49
|
|
50
|
+
#+BEGIN_SRC sh
|
51
|
+
git clone git@github.com:cormacc/revision
|
52
|
+
#+END_SRC
|
53
|
+
|
54
|
+
|
52
55
|
*** Checkout
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
57
|
+
#+BEGIN_SRC sh
|
58
|
+
gem install bundler
|
59
|
+
git clone git@github.com:cormacc/revision
|
60
|
+
cd revision
|
61
|
+
#+END_SRC
|
59
62
|
|
60
63
|
*** Install
|
61
64
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
65
|
+
#+BEGIN_SRC sh
|
66
|
+
bundle install
|
67
|
+
bundle exec rake install
|
68
|
+
#+END_SRC
|
69
|
+
|
70
|
+
#+RESULTS:
|
71
|
+
#+begin_example
|
72
|
+
Using rake 10.5.0
|
73
|
+
Using bundler 1.16.0
|
74
|
+
Using coderay 1.1.2
|
75
|
+
Using diff-lcs 1.3
|
76
|
+
Using git 1.3.0
|
77
|
+
Using method_source 0.9.0
|
78
|
+
Using pry 0.11.3
|
79
|
+
Using rubyzip 1.2.1
|
80
|
+
Using thor 0.19.4
|
81
|
+
Using revision 1.0.0 from source at `.`
|
82
|
+
Using rspec-support 3.7.0
|
83
|
+
Using rspec-core 3.7.0
|
84
|
+
Using rspec-expectations 3.7.0
|
85
|
+
Using rspec-mocks 3.7.0
|
86
|
+
Using rspec 3.7.0
|
87
|
+
Bundle complete! 5 Gemfile dependencies, 15 gems now installed.
|
88
|
+
Use `bundle info [gemname]` to see where a bundled gem is installed.
|
89
|
+
revision 1.0.0 built to pkg/revision-1.0.0.gem.
|
90
|
+
revision (1.0.0) installed.
|
91
|
+
#+end_example
|
89
92
|
|
90
93
|
* Usage
|
91
94
|
|
92
95
|
** Supported Commands
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
96
|
+
Run the executable with no arguments to get usage instructions in your console window...
|
97
|
+
|
98
|
+
#+BEGIN_SRC sh
|
99
|
+
revision
|
100
|
+
#+END_SRC
|
101
|
+
|
102
|
+
#+RESULTS:
|
103
|
+
#+begin_example
|
104
|
+
Loading releasable definitions from /home/cormacc/dev/gem/revision/releasables.yaml ...
|
105
|
+
Commands:
|
106
|
+
revision --version, -v # print the version
|
107
|
+
revision archive # Archive releasable(s)
|
108
|
+
revision build # Build releasable(s)
|
109
|
+
revision changelog # Display change log on stdout
|
110
|
+
revision deploy # Deploy releasable(s)
|
111
|
+
revision help [COMMAND] # Describe available commands or one specific command
|
112
|
+
revision info # Display info for all defined releasables
|
113
|
+
revision major # Increment major revision index
|
114
|
+
revision minor # Increment minor revision index
|
115
|
+
revision package # Build and archive releasables
|
116
|
+
revision patch # Increment patch revision index
|
117
|
+
revision tag # Commit the current revision to a local git repo ...
|
118
|
+
|
119
|
+
Options:
|
120
|
+
[--dryrun], [--no-dryrun]
|
121
|
+
[--id=ID]
|
122
|
+
|
123
|
+
#+end_example
|
124
|
+
|
125
|
+
#+BEGIN_NOTE
|
126
|
+
The tool can be run from any subfolder of a project root -- it will traverse the tree until it finds
|
127
|
+
an ancestor containing =releasables.yaml= OR can go no further (in which case it throws an error).
|
128
|
+
#+END_NOTE
|
122
129
|
|
123
130
|
*** Operating on multiple releasables
|
124
131
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
132
|
+
A single =releasables.yaml= file can define multiple releasables, either implicitly (via inclusion) or explicitly
|
133
|
+
(see [[Configuration]] section below for examples). In this context, the ~info~ and ~archive~ commands
|
134
|
+
will operate on all defined releasables, whereas the remaining commands will require the releasable
|
135
|
+
to be specified using the ~--id=~ option, e.g.
|
129
136
|
|
130
|
-
|
131
|
-
|
132
|
-
|
137
|
+
#+BEGIN_SRC sh
|
138
|
+
revision minor --id=firmware
|
139
|
+
#+END_SRC
|
133
140
|
|
134
141
|
** Configuration
|
135
142
|
|
136
|
-
|
137
|
-
|
138
|
-
|
143
|
+
YAML syntax is used for the configuration file. The [[Syntax]] and [[Examples]] sections below should provide sufficient
|
144
|
+
introduction to the limit subset of language features required to use this tool, however further info
|
145
|
+
may be found at the following links:
|
139
146
|
|
140
|
-
|
141
|
-
|
147
|
+
- http://docs.ansible.com/ansible/latest/YAMLSyntax.html
|
148
|
+
- http://www.yaml.org/start.html
|
142
149
|
|
143
150
|
*** Syntax
|
144
151
|
|
145
|
-
|
146
|
-
|
152
|
+
The =releasables.yaml= file should contain a top level ~:releasables~ node.
|
153
|
+
Under this, you can create a list (or YAML /sequence/) of releasable definitions.
|
147
154
|
|
148
|
-
|
149
|
-
|
155
|
+
Each sequence item begins with a ~-~ and contains either a link to a folder containing its own =releasables.yaml=
|
156
|
+
defining one or more releasables ...
|
150
157
|
|
151
|
-
|
152
|
-
|
153
|
-
|
158
|
+
#+BEGIN_SRC yaml
|
159
|
+
- :folder: relative/path/to/a/releasable/folder
|
160
|
+
#+END_SRC
|
154
161
|
|
155
|
-
|
162
|
+
... or a single inline releasable definition.
|
156
163
|
|
157
|
-
|
158
|
-
|
159
|
-
|
164
|
+
#+BEGIN_NOTE
|
165
|
+
The lines beginning with '#' are explanatory comments
|
166
|
+
#+END_NOTE
|
160
167
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
168
|
+
#+BEGIN_SRC yaml
|
169
|
+
- :id: my_releasable
|
170
|
+
:revision:
|
171
|
+
# Source file containing the revision identifier
|
172
|
+
# This will also include changelog entries, embedded as comments
|
173
|
+
:src: lib/revision/version.rb
|
174
|
+
# Regex matching the source revision identifier. Must contain the following named capture groups
|
175
|
+
# - major, minor, patch :: Numeric (uint) sequences representing the three revision ID components
|
176
|
+
# - sep1, sep2 :: the characters separating the revision components
|
177
|
+
# - prefix, postfix :: sufficient syntactic context to match the revision ID uniquely
|
178
|
+
# N.B. this regex matches the version ID from the standard bundler gem skeleton,
|
179
|
+
# e.g. VERSION = "1.1.0"
|
180
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
181
|
+
# Comment char for the project language -- prefixed to each line of changelog entries
|
182
|
+
# Quotes only necessary here to prevent # being interpreted as the beginning of a YAML comment
|
183
|
+
:comment_prefix: "#"
|
177
184
|
# Sequence of build steps -- each item should be a valid shell command, prefixed with the YAML sequence item token, '- '
|
178
185
|
:build_steps:
|
179
186
|
- bundle exec rake install
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
187
|
+
# Sequence defining the files (build artefacts) to package in the release archive.
|
188
|
+
# Each artefact definition must include a :src: key/value pair.
|
189
|
+
# An optional :dest: value may be provided to rename the file during packaging, or just (as in the first entry below)
|
190
|
+
# to flatten the folder structure.
|
191
|
+
# Any <VER> (or <REV>) in the :src: or :dest: placeholders wil be replaced with the current revision ID
|
192
|
+
# The revision archive will also include the revision history extracted as a text file
|
193
|
+
:artefacts:
|
194
|
+
# A binary artefact -- the
|
195
|
+
- :src: pkg/revision-<VER>.gem
|
196
|
+
:dest: revision-<VER>.gem
|
197
|
+
# This document -- the :dest: value defaults to duplicating :src: if not specified
|
198
|
+
- :src: README.org
|
199
|
+
#+END_SRC
|
193
200
|
|
194
201
|
**** TODO (or at least consider) add support for overridable defaults
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
202
|
+
e.g. via a =.releasables= configuration file in the user home dir.
|
203
|
+
Though this would be bad for collaborative development as the config file would live outside source control.
|
204
|
+
Possibly useful to support inclusion of a controlled configuration file instead? Primarily to define
|
205
|
+
a revision regex and comment prefix for a group of related releasables....
|
199
206
|
|
200
207
|
*** Examples
|
201
208
|
**** C project
|
202
209
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
210
|
+
#+BEGIN_NOTE
|
211
|
+
The ~:regex:~ and ~:comment_prefix:~ keys are absent in the C example below. This project started life as
|
212
|
+
managing some embedded C projects, and the default values reflect this.
|
213
|
+
#+END_NOTE
|
214
|
+
|
215
|
+
#+BEGIN_SRC yaml
|
216
|
+
:releasables:
|
217
|
+
- :id: mbt_cd_firmware
|
218
|
+
:revision:
|
219
|
+
:src: src/FirmwareRevision.c
|
220
|
+
:build_steps:
|
221
|
+
- make --jobs=8 -f Makefile CONF=bootloadable
|
215
222
|
:artefacts:
|
216
223
|
- :src: dist/bootloadable/production/firmware.production.hex
|
217
224
|
:dest: mbt_cd_firmware_v<REV>.bootloadable.hex
|
218
|
-
|
219
|
-
|
220
|
-
|
225
|
+
- :src: dist/default/production/firmware.production.hex
|
226
|
+
:dest: mbt_cd_firmware_v<REV>.standalone.hex
|
227
|
+
#+END_SRC
|
221
228
|
|
222
229
|
**** Ruby project
|
223
230
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
231
|
+
#+BEGIN_SRC yaml
|
232
|
+
:releasables:
|
233
|
+
- :id: revision
|
234
|
+
:revision:
|
235
|
+
:src: lib/revision/version.rb
|
236
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
237
|
+
:comment_prefix: "#"
|
238
|
+
:build_steps:
|
239
|
+
- bundle exec rake install
|
233
240
|
:artefacts:
|
234
241
|
- :src: pkg/revision-<VER>.gem
|
235
|
-
|
242
|
+
#+END_SRC
|
236
243
|
|
237
244
|
*** Heirarchical project
|
238
|
-
|
245
|
+
Rather than defining the releasable parameters inline, a =releasables.yaml= list entry can contain a (relative or absolute) link to another folder containing it's own =releasables.yaml=.
|
239
246
|
|
240
|
-
|
241
|
-
|
247
|
+
i.e assuming the earlier examples were in folders =examples/c= and =examples/ruby= relative to a common root, a separate =releasables.yaml=
|
248
|
+
at that root could include them as follows...
|
242
249
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
250
|
+
#+BEGIN_SRC yaml
|
251
|
+
:releasables:
|
252
|
+
- :folder: examples/c
|
253
|
+
- :folder: examples/ruby
|
254
|
+
#+END_SRC
|
248
255
|
|
249
256
|
**** TODO consider supporting a higher-level aggregate revision ID
|
250
257
|
|
251
|
-
|
252
|
-
|
253
|
-
|
258
|
+
#+BEGIN_SRC yaml
|
259
|
+
:revision:
|
260
|
+
:src: release_log.txt
|
254
261
|
:releasables:
|
255
262
|
- :folder: examples/c
|
256
|
-
|
257
|
-
|
263
|
+
- :folder: examples/ruby
|
264
|
+
#+END_SRC
|
258
265
|
|
259
266
|
* Development
|
260
267
|
|
261
|
-
|
268
|
+
After checking out the repo, run =bin/setup= to install dependencies. Then, run =rake spec= to run the tests. You can also run =bin/console= for an interactive prompt that will allow you to experiment.
|
262
269
|
|
263
|
-
|
270
|
+
To install this gem onto your local machine, run =bundle exec rake install=.
|
264
271
|
|
272
|
+
To release a new version, update the version number in =version.rb=. You can do this using the gem, by entering =revision minor= (or =patch= or =major= as appropriate), and then run =bundle exec rake release=, which will create a git tag for the version, push git commits and tags, and push the =.gem= file to [rubygems.org](https://rubygems.org).
|
265
273
|
|
266
274
|
* Contributing
|
267
275
|
|
268
|
-
|
276
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cormacc/revision.
|
data/lib/revision/cli.rb
CHANGED
@@ -119,8 +119,26 @@ module Revision
|
|
119
119
|
def do_increment(type)
|
120
120
|
r = select_one
|
121
121
|
increment_method = "#{type}_increment!"
|
122
|
-
say "Incrementing #{r.revision} to #{r.revision.public_send(increment_method)}"
|
123
|
-
options[:dryrun]
|
122
|
+
say "Incrementing #{r.revision} to #{r.revision.public_send(increment_method)} (#{r.revision.src})"
|
123
|
+
if options[:dryrun]
|
124
|
+
r.revision.write(r.revision.src + ".new")
|
125
|
+
r.secondary_revisions.each do |s|
|
126
|
+
say "Propagating revision update to #{s.src}"
|
127
|
+
s.major = r.revision.major
|
128
|
+
s.minor = r.revision.minor
|
129
|
+
s.patch = r.revision.patch
|
130
|
+
s.write(s.src + ".new")
|
131
|
+
end
|
132
|
+
else
|
133
|
+
r.revision.write!
|
134
|
+
r.secondary_revisions.each do |s|
|
135
|
+
say "Propagating revision update to #{s.src}"
|
136
|
+
s.major = r.revision.major
|
137
|
+
s.minor = r.revision.minor
|
138
|
+
s.patch = r.revision.patch
|
139
|
+
s.write!
|
140
|
+
end
|
141
|
+
end
|
124
142
|
say ''
|
125
143
|
say "The automatic commit / tag step assumes you're only checking in changes to existing files"
|
126
144
|
say "You can answer 'n' at the prompt and use 'revision tag' to generate a commit with the latest changelog entry and an associated tag after manually adding any new files to the repo"
|
@@ -128,7 +146,7 @@ module Revision
|
|
128
146
|
if ask("Rebuild and archive any releasables (Y/n)?").upcase!='N'
|
129
147
|
r.package
|
130
148
|
end
|
131
|
-
if ask("Commit changes to existing files and add a Git tag (
|
149
|
+
if ask("Commit changes to existing files and add a Git tag (Y/n)?").upcase!='N'
|
132
150
|
r.tag
|
133
151
|
if ask("Push changes/tag to origin (Y/n)?").upcase=='N' || !r.push
|
134
152
|
say "To push from the command line, type 'git push --tags' at a shell prompt"
|
data/lib/revision/info.rb
CHANGED
@@ -15,10 +15,11 @@ class Revision::Info
|
|
15
15
|
attr_accessor :regex
|
16
16
|
attr_accessor :comment_prefix
|
17
17
|
|
18
|
-
def initialize(file, regex: nil, comment_prefix: nil)
|
18
|
+
def initialize(file, regex: nil, comment_prefix: nil, embed_changelog: true)
|
19
19
|
@src=file
|
20
20
|
@regex = regex.nil? ? DEFAULT_REGEX : /#{regex}/
|
21
21
|
@comment_prefix = comment_prefix || DEFAULT_COMMENT_PREFIX
|
22
|
+
@embed_changelog = embed_changelog
|
22
23
|
matched = false
|
23
24
|
File.open(@src).each_line do |line|
|
24
25
|
if line =~ @regex
|
@@ -65,15 +66,16 @@ class Revision::Info
|
|
65
66
|
ref_info = self.class.new(@src, regex: @regex)
|
66
67
|
raise 'No revision identifiers incremented' if ref_info.to_s == self.to_s
|
67
68
|
|
68
|
-
entry = get_changelog_entry
|
69
69
|
|
70
70
|
text = File.read(@src)
|
71
71
|
text.gsub!(@regex) { |match| "#{$~[:prefix]}#{@major}#{$~[:sep1]}#{@minor}#{$~[:sep2]}#{@patch}#{$~[:postfix]}" }
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
if @embed_changelog
|
74
|
+
entry = get_changelog_entry
|
75
|
+
#Insert start/end tags if not present
|
76
|
+
text += new_changelog_placeholder unless text.match(CHANGELOG_START)
|
77
|
+
text.gsub!(CHANGELOG_START) { |match| [match, format_changelog_entry(entry)].join("\n") }
|
78
|
+
end
|
77
79
|
|
78
80
|
File.open(output_file_name, 'w') { |f| f.write(text) }
|
79
81
|
end
|
@@ -119,7 +121,7 @@ class Revision::Info
|
|
119
121
|
# Prefixes the entry with an empty line, then prefixes each line with comment chars
|
120
122
|
# and converts the line entries to a single string
|
121
123
|
def format_changelog_entry(entry_lines)
|
122
|
-
entry_lines.unshift('').map { |line| "#{@comment_prefix} #{line}"}.join("\n")
|
124
|
+
entry_lines.unshift('').map { |line| "#{@comment_prefix} #{line}".rstrip()}.join("\n")
|
123
125
|
end
|
124
126
|
|
125
127
|
def get_changelog_entry
|
data/lib/revision/releasable.rb
CHANGED
@@ -17,7 +17,7 @@ module Revision
|
|
17
17
|
|
18
18
|
REVISION_PLACEHOLDER = /<REV>|<VER>/
|
19
19
|
|
20
|
-
attr_reader :root, :id, :revision, :build_steps, :artefacts, :git_tag_prefix
|
20
|
+
attr_reader :root, :id, :revision, :build_steps, :artefacts, :git_tag_prefix, :secondary_revisions
|
21
21
|
|
22
22
|
# Load a file in yaml format containing one or more releasable definitions
|
23
23
|
# @param root [String] An optional root directory argument
|
@@ -53,12 +53,17 @@ module Revision
|
|
53
53
|
# @git_repo
|
54
54
|
# end
|
55
55
|
|
56
|
+
def _build_revision_info(definition, embed_changelog: true)
|
57
|
+
Info.new(File.join(@root,definition[:src]), regex: definition[:regex], comment_prefix: definition[:comment_prefix], embed_changelog: embed_changelog)
|
58
|
+
end
|
59
|
+
|
56
60
|
def initialize(root: nil, config: {})
|
57
61
|
|
58
62
|
root ||= Dir.getwd
|
59
63
|
@root = Pathname.new(root).realpath
|
60
64
|
@id = config[:id] || File.basename(@root)
|
61
|
-
@revision =
|
65
|
+
@revision = _build_revision_info(config[:revision], embed_changelog: true)
|
66
|
+
@secondary_revisions = config[:secondary_revisions].nil? ? [] : config[:secondary_revisions].map { |r| _build_revision_info(r, embed_changelog:false)}
|
62
67
|
@git_tag_prefix = config[:revision][:git_tag_prefix].nil? ? 'v' : "#{config[:revision][:git_tag_prefix]}_v"
|
63
68
|
# Legacy definition syntax compatibility
|
64
69
|
@build_def = config[:build] ? config[:build] : { environment: { variables: {}}, steps: config[:build_steps]}
|
@@ -88,6 +93,20 @@ module Revision
|
|
88
93
|
EOT
|
89
94
|
end
|
90
95
|
|
96
|
+
def exec_pipeline(type, steps, skip_steps=0)
|
97
|
+
exec_steps = steps[skip_steps..-1]
|
98
|
+
puts "#{type} :: Executing steps #{skip_steps+1} to #{steps.length}..."
|
99
|
+
Dir.chdir(@root) do
|
100
|
+
exec_steps.each_with_index do |step, index|
|
101
|
+
step_index = index+1+skip_steps
|
102
|
+
puts "... (#{step_index}/#{steps.length}) #{step}"
|
103
|
+
system(step)
|
104
|
+
puts "{type} :: WARNING: step #{step_index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
91
110
|
def build(skip_steps = 0)
|
92
111
|
if @build_def.dig(:environment, :variables)
|
93
112
|
@build_def[:environment][:variables].each do |key, value|
|
@@ -105,24 +124,29 @@ module Revision
|
|
105
124
|
ENV[key] = value
|
106
125
|
end
|
107
126
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
127
|
+
exec_pipeline('build', @build_def[:steps], skip_steps)
|
128
|
+
# steps = @build_def[:steps][skip_steps..-1]
|
129
|
+
# puts "Executing #{steps.length} of #{@build_def[:steps].length} build steps..."
|
130
|
+
# Dir.chdir(@root) do
|
131
|
+
# steps.each_with_index do |step, index|
|
132
|
+
# step_index = index+1+skip_steps
|
133
|
+
# puts "... (#{step_index}/#{@build_def[:steps].length}) #{step}"
|
134
|
+
# system(step)
|
135
|
+
# puts "WARNING: build step #{step_index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
|
136
|
+
# end
|
137
|
+
# end
|
118
138
|
end
|
119
139
|
|
120
140
|
def tag_id
|
121
141
|
"#{@git_tag_prefix}#{revision}"
|
122
142
|
end
|
123
143
|
|
144
|
+
def escape(a_string)
|
145
|
+
a_string.gsub('"',"\\\"")
|
146
|
+
end
|
147
|
+
|
124
148
|
def tag_annotation
|
125
|
-
@revision.last_changelog_entry.join("\n")
|
149
|
+
escape(@revision.last_changelog_entry.join("\n"))
|
126
150
|
end
|
127
151
|
|
128
152
|
def commit_message
|
@@ -133,7 +157,7 @@ module Revision
|
|
133
157
|
commit_lines << "Also..."
|
134
158
|
commit_lines += changelog_entry[2..-1]
|
135
159
|
end
|
136
|
-
commit_lines.join("\n")
|
160
|
+
escape(commit_lines.join("\n"))
|
137
161
|
end
|
138
162
|
|
139
163
|
def tag
|
@@ -142,6 +166,7 @@ module Revision
|
|
142
166
|
puts commit_message
|
143
167
|
system("git commit -a -m \"#{commit_message}\"")
|
144
168
|
puts "Tagging as #{tag_id}"
|
169
|
+
puts "git tag -a #{tag_id} -m \"#{tag_annotation}\""
|
145
170
|
system("git tag -a #{tag_id} -m \"#{tag_annotation}\"")
|
146
171
|
end
|
147
172
|
end
|
@@ -176,37 +201,89 @@ module Revision
|
|
176
201
|
src = File.join(@root,src)
|
177
202
|
dest = dest_prefix.empty? ? dest : File.join(dest_prefix, dest)
|
178
203
|
amap[src] = dest
|
179
|
-
puts "... (#{index+1}/#{@artefacts.length}) #{src} => #{dest}"
|
180
204
|
end
|
181
205
|
amap
|
182
206
|
end
|
183
207
|
|
184
208
|
def archive
|
185
209
|
puts "Archiving #{@artefacts.length} build artefacts as #{archive_name}..."
|
186
|
-
|
210
|
+
amap = artefact_map
|
187
211
|
if File.exist?(archive_name)
|
188
212
|
puts "... deleting existing archive"
|
189
213
|
File.delete(archive_name)
|
190
214
|
end
|
191
215
|
Zip::File.open(archive_name, Zip::File::CREATE) do |zipfile|
|
192
|
-
|
216
|
+
amap.each.with_index(1) do |entry, idx|
|
217
|
+
src, dest = entry
|
218
|
+
#TODO: Add directory processing....
|
219
|
+
puts "... (#{idx}/#{amap.length}) #{src} => #{dest}"
|
220
|
+
zipfile.add(dest, src)
|
221
|
+
end
|
193
222
|
puts "... embedding revision history as #{changelog_name} "
|
194
223
|
zipfile.get_output_stream(changelog_name) { |os| output_changelog(os)}
|
195
224
|
end
|
225
|
+
|
226
|
+
if @config.dig(:archive)
|
227
|
+
archive_root = File.expand_path(@config[:archive])
|
228
|
+
puts "... moving #{archive_name} to #{archive_root}"
|
229
|
+
FileUtils.mkdir_p(archive_root)
|
230
|
+
FileUtils.mv(archive_name, archive_root)
|
231
|
+
end
|
196
232
|
end
|
197
233
|
|
198
|
-
# def deploy(destination)
|
199
234
|
def deploy(destination='')
|
200
|
-
|
201
|
-
|
235
|
+
destinations = []
|
236
|
+
if not destination.empty?
|
237
|
+
destinations.append({dest: destination})
|
238
|
+
elsif @config.dig(:deploy)
|
239
|
+
if @config[:deploy].kind_of?(Array)
|
240
|
+
destinations.append(*@config[:deploy])
|
241
|
+
else
|
242
|
+
destinations.append(@config[:deploy])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
raise Errors::NotSpecified.new(':deploy') if destinations.empty?
|
247
|
+
|
248
|
+
if @config.dig(:deploy, :pre)
|
249
|
+
exec_pipeline('deploy (pre)', @config[:deploy][:pre])
|
250
|
+
end
|
251
|
+
|
252
|
+
destinations.each do |d|
|
253
|
+
destination = File.expand_path(d[:dest])
|
254
|
+
|
255
|
+
if d.dig(:pre)
|
256
|
+
exec_pipeline('deploy (pre / #{d[:dest]})', d[:pre])
|
257
|
+
end
|
258
|
+
|
259
|
+
puts "Deploying #{@artefacts.length} build artefacts to #{destination}..."
|
260
|
+
if not File.exist?(destination)
|
261
|
+
puts "... folder not found -> creating ... '#{destination}'"
|
262
|
+
FileUtils.mkdir_p(destination)
|
263
|
+
end
|
264
|
+
amap = artefact_map(destination)
|
265
|
+
amap.each.with_index(1) do |entry, idx|
|
266
|
+
src, dest = entry
|
267
|
+
puts "... (#{idx}/#{amap.length}) #{src} => #{dest}"
|
268
|
+
if File.exist?(dest)
|
269
|
+
puts "... deleting existing '#{dest}' ..."
|
270
|
+
FileUtils.rm_rf(dest)
|
271
|
+
end
|
272
|
+
puts "... deploying '#{src}' -> '#{dest}"
|
273
|
+
FileUtils.cp_r(src,dest)
|
274
|
+
end
|
275
|
+
File.open(File.join(destination,changelog_name),'w') { |f| output_changelog(f)}
|
276
|
+
|
277
|
+
if d.dig(:post)
|
278
|
+
exec_pipeline('deploy (post / #{d[:dest]})', d[:post])
|
279
|
+
end
|
202
280
|
end
|
203
281
|
|
204
|
-
raise Errors::NotSpecified.new(':deploy/:dest') if destination==''
|
205
|
-
destination = File.expand_path(destination)
|
206
282
|
|
207
|
-
|
208
|
-
|
209
|
-
|
283
|
+
|
284
|
+
if @config.dig(:deploy, :post)
|
285
|
+
exec_pipeline('deploy (post)', @config[:deploy][:post])
|
286
|
+
end
|
210
287
|
end
|
211
288
|
|
212
289
|
def package
|
data/lib/revision/version.rb
CHANGED
@@ -1,93 +1,119 @@
|
|
1
1
|
# Defines the revision ID for the revision gem
|
2
2
|
module Revision
|
3
|
-
VERSION = "1.2.
|
3
|
+
VERSION = "1.5.2".freeze
|
4
4
|
end
|
5
5
|
|
6
6
|
# <BEGIN CHANGELOG>
|
7
|
-
#
|
7
|
+
#
|
8
|
+
# Version 1.5.2 (10 Jun 2020)
|
9
|
+
# - Uprevving around undeletable git tag
|
10
|
+
#
|
11
|
+
## Version 1.5.1 (10 Jun 2020)
|
12
|
+
# - Escape " and ' in commit message when constructing git command line (' still problematic in some shells)
|
13
|
+
#
|
14
|
+
# Version 1.5.0 (13 Feb 2020)
|
15
|
+
# - Now handles multiple deployment destinations in releasables.yaml
|
16
|
+
#
|
17
|
+
# Version 1.4.1 (18 Nov 2019)
|
18
|
+
# - Updated to strip trailing whitespace after comment char for empty line
|
19
|
+
#
|
20
|
+
# Version 1.4.0 (10 Jun 2019)
|
21
|
+
# - Now allow the definition of one or more 'secondary_revisions', where a revision ID can be updated with or without an embedded changelog
|
22
|
+
#
|
23
|
+
# Version 1.3.1 (20 May 2019)
|
24
|
+
# - Corrected bug when deploying changelog:w
|
25
|
+
#
|
26
|
+
# Version 1.3.0 (20 May 2019)
|
27
|
+
# - Added :archive: (archive root definition)
|
28
|
+
# - Added optional :pre: and :post: steps to :deploy: definition
|
29
|
+
#
|
30
|
+
# Version 1.2.8 (20 May 2019)
|
31
|
+
# - Updated Git tag/commit behaviour -- now commit/tag/push by default
|
32
|
+
# - Updated deploy to remove existing targets and copy entire directory trees
|
33
|
+
#
|
8
34
|
# Version 1.2.7 (17 May 2019)
|
9
35
|
# - 'deploy' now expands '~' in paths
|
10
|
-
#
|
36
|
+
#
|
11
37
|
# Version 1.2.6 (17 May 2019)
|
12
38
|
# - Updated deploy to use default dest from yaml (if specified)
|
13
|
-
#
|
39
|
+
#
|
14
40
|
# Version 1.2.5 (07 Nov 2018)
|
15
41
|
# - Added 'deploy' command
|
16
|
-
#
|
42
|
+
#
|
17
43
|
# Version 1.2.4 (05 Sep 2018)
|
18
44
|
# - Added commit message and tag details to `revision info`
|
19
45
|
# - Minor refactoring
|
20
|
-
#
|
46
|
+
#
|
21
47
|
# Version 1.2.3 (03 Sep 2018)
|
22
48
|
# - Tidied up `revision info` output formatting
|
23
|
-
#
|
49
|
+
#
|
24
50
|
# Version 1.2.2 (03 Sep 2018)
|
25
51
|
# - Updated CLI to provide version info
|
26
|
-
#
|
52
|
+
#
|
27
53
|
# Version 1.2.1 (03 Sep 2018)
|
28
54
|
# - Update to allow releasable without any artefacts
|
29
|
-
#
|
55
|
+
#
|
30
56
|
# Version 1.2.0 (06 Mar 2018)
|
31
57
|
# - Build definition improvements (and new yaml structure)
|
32
58
|
# - Added platform-agnostic environment variable definition (handles '~' replacement and :/; path separators)
|
33
59
|
# - Added platform-agnostic packaging of binaries (i.e. appending .exe for windows when archiving)
|
34
|
-
#
|
60
|
+
#
|
35
61
|
# Version 1.1.10 (16 Feb 2018)
|
36
62
|
# - Modified 'archive' command to just archive existing artefact -- i.e. skip build phase
|
37
63
|
# - Added 'package' command that builds AND archives
|
38
|
-
#
|
64
|
+
#
|
39
65
|
# Version 1.1.9 (16 Feb 2018)
|
40
66
|
# - Fixed bug when adding first changelog entry to file without existing placeholders
|
41
67
|
# - Added standalone build command
|
42
|
-
#
|
68
|
+
#
|
43
69
|
# Version 1.1.8 (15 Dec 2017)
|
44
70
|
# - Added .yardopts to build documentation
|
45
|
-
#
|
71
|
+
#
|
46
72
|
# Version 1.1.7 (15 Dec 2017)
|
47
73
|
# - Corrected push -- was pushing tags without commit
|
48
|
-
#
|
74
|
+
#
|
49
75
|
# Version 1.1.6 (15 Dec 2017)
|
50
76
|
# - Added full changelog entry as tag message
|
51
77
|
# - Removed ruby-git dependency from gemspec
|
52
|
-
#
|
78
|
+
#
|
53
79
|
# Version 1.1.5 (15 Dec 2017)
|
54
80
|
# - Replaced ruby-git library with shell calls, as wasn't handling submodules correctly
|
55
|
-
#
|
81
|
+
#
|
56
82
|
# Version 1.1.4 (14 Dec 2017)
|
57
83
|
# - Minor message body reformatting
|
58
|
-
#
|
84
|
+
#
|
59
85
|
# Version 1.1.3 (14 Dec 2017)
|
60
86
|
# - Eliminated duplication of version ID in commit message body
|
61
|
-
#
|
87
|
+
#
|
62
88
|
# Version 1.1.2 (14 Dec 2017)
|
63
89
|
# - Removed redundant ':: ' from commit message headline
|
64
|
-
#
|
90
|
+
#
|
65
91
|
# Version 1.1.1 (14 Dec 2017)
|
66
92
|
# - Added git connection failure handling
|
67
93
|
# - Revision commit message now includes first line of changelog entry
|
68
94
|
# - Updated configuration syntax for consistency (:revision: :file: -> :revision: :src:)
|
69
|
-
#
|
95
|
+
#
|
70
96
|
# Version 1.1.0 (13 Dec 2017)
|
71
97
|
# - Updated to optionally push tags to the repo
|
72
|
-
#
|
98
|
+
#
|
73
99
|
# Version 1.0.1 (13 Dec 2017)
|
74
100
|
# - Corrected revision placeholder handling when archiving build artefacts
|
75
101
|
# - Added proper high-level usage documentation
|
76
|
-
#
|
102
|
+
#
|
77
103
|
# Version 1.0.0 (12 Dec 2017)
|
78
104
|
# - First fully functional release with new config file
|
79
|
-
#
|
105
|
+
#
|
80
106
|
# Version 0.1.4 (12 Dec 2017)
|
81
107
|
# - boo
|
82
108
|
# - hoo
|
83
109
|
# - hoo
|
84
|
-
#
|
110
|
+
#
|
85
111
|
# Version 0.1.3 (12 Dec 2017)
|
86
112
|
# - boo
|
87
|
-
#
|
113
|
+
#
|
88
114
|
# Version 0.1.2 (12 Dec 2017)
|
89
115
|
# - wahoo!
|
90
|
-
#
|
116
|
+
#
|
91
117
|
# Version 0.1.1 (12 Dec 2017)
|
92
118
|
# - Wahey!
|
93
|
-
# <END CHANGELOG>
|
119
|
+
# <END CHANGELOG>
|
data/releasables.yaml
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
- :id: revision
|
3
3
|
:revision:
|
4
4
|
:src: lib/revision/version.rb
|
5
|
-
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
5
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>".freeze)
|
6
6
|
:comment_prefix: "#"
|
7
7
|
:build_steps:
|
8
8
|
- bundle exec rake install
|
data/revision.gemspec
CHANGED
@@ -6,20 +6,12 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "revision"
|
7
7
|
spec.version = Revision::VERSION
|
8
8
|
spec.authors = ["Cormac Cannon"]
|
9
|
-
spec.
|
9
|
+
spec.licenses = ['MIT']
|
10
10
|
|
11
11
|
spec.summary = %q{Language-agnostic revision management tool}
|
12
12
|
spec.description = %q{Updates project revision identifiers in software source files and associated change log. Can also build and package project archives as a zip and optionally commit, tag and push to a Git repo.}
|
13
|
-
|
14
|
-
|
15
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
|
-
# if spec.respond_to?(:metadata)
|
18
|
-
# spec.metadata["allowed_push_host"] = "http://gems.nmd.ie"
|
19
|
-
# else
|
20
|
-
# raise "RubyGems 2.0 or newer is required to protect against " \
|
21
|
-
# "public gem pushes."
|
22
|
-
# end
|
13
|
+
spec.homepage = 'https://rubygems.org/gems/revision'
|
14
|
+
spec.metadata = { "source_code_uri" => "https://github.com/cormacc/revision" }
|
23
15
|
|
24
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
25
17
|
f.match(%r{^(test|spec|features)/})
|
@@ -29,11 +21,11 @@ Gem::Specification.new do |spec|
|
|
29
21
|
|
30
22
|
spec.require_paths = ["lib"]
|
31
23
|
|
32
|
-
spec.add_runtime_dependency 'thor', '~> 0
|
33
|
-
spec.add_runtime_dependency 'rubyzip'
|
24
|
+
spec.add_runtime_dependency 'thor', '~> 1.0'
|
25
|
+
spec.add_runtime_dependency 'rubyzip', '~> 2.0'
|
34
26
|
|
35
27
|
spec.add_development_dependency "bundler", "~> 2.0"
|
36
|
-
spec.add_development_dependency "rake", "~>
|
28
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
37
29
|
spec.add_development_dependency "rspec", "~> 3.0"
|
38
30
|
spec.add_development_dependency "pry"
|
39
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: revision
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cormac Cannon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '2.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '13.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '13.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,8 +97,7 @@ dependencies:
|
|
97
97
|
description: Updates project revision identifiers in software source files and associated
|
98
98
|
change log. Can also build and package project archives as a zip and optionally
|
99
99
|
commit, tag and push to a Git repo.
|
100
|
-
email:
|
101
|
-
- cormac.cannon@neuromoddevices.com
|
100
|
+
email:
|
102
101
|
executables:
|
103
102
|
- revision
|
104
103
|
extensions: []
|
@@ -123,9 +122,11 @@ files:
|
|
123
122
|
- lib/revision/version.rb
|
124
123
|
- releasables.yaml
|
125
124
|
- revision.gemspec
|
126
|
-
homepage:
|
127
|
-
licenses:
|
128
|
-
|
125
|
+
homepage: https://rubygems.org/gems/revision
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata:
|
129
|
+
source_code_uri: https://github.com/cormacc/revision
|
129
130
|
post_install_message:
|
130
131
|
rdoc_options: []
|
131
132
|
require_paths:
|
@@ -141,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
142
|
- !ruby/object:Gem::Version
|
142
143
|
version: '0'
|
143
144
|
requirements: []
|
144
|
-
rubygems_version: 3.
|
145
|
+
rubygems_version: 3.1.3
|
145
146
|
signing_key:
|
146
147
|
specification_version: 4
|
147
148
|
summary: Language-agnostic revision management tool
|