revision 1.1.4
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/README.org +269 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/revision +5 -0
- data/lib/revision/cli.rb +101 -0
- data/lib/revision/errors.rb +10 -0
- data/lib/revision/info.rb +137 -0
- data/lib/revision/releasable.rb +152 -0
- data/lib/revision/string_case.rb +36 -0
- data/lib/revision/version.rb +45 -0
- data/lib/revision.rb +6 -0
- data/releasables.yaml +10 -0
- data/revision.gemspec +40 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 94dfd966e2e44d185a37be41bb1fe745455f0e6d
|
4
|
+
data.tar.gz: cf971a767a20dbc24696adff7bbf634e821e8ea0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e5ec6a596855ad4f50c5ee1307c9cfac3e4c7fe4918f6e4c03b00879c8e7824caa1675bea87d9ac4b0601b165bf6f6b8e1dd2c7e5e95b31e3f6450fe759d994
|
7
|
+
data.tar.gz: 6b71b8ed50740a488904d66b35e52bdcfb14dd5ec5bfcd71cef1cd387e94cdbfd8816584a60fec3342e6133820c108a53cf299dc6964e03207b205fa28a08440
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.org
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
#+TITLE: Revision management gem
|
2
|
+
#+AUTHOR: Cormac Cannon
|
3
|
+
#+EMAIL: cormac.cannon@neuromoddevices.com
|
4
|
+
#+LANGUAGE: en
|
5
|
+
|
6
|
+
# Generic properties
|
7
|
+
# :eval never-export
|
8
|
+
# ... Prevent auto evaluation of blocks on export. Evaluate them interactively instead
|
9
|
+
# :results verbatim
|
10
|
+
# ... Output results verbatim rather than autodetecting (which usually ends up tabular). Override per script using :results tabular
|
11
|
+
#+PROPERTY: header-args :results verbatim :noweb yes :exports both :eval never-export
|
12
|
+
# #+PROPERTY: header-args :results verbatim :noweb yes :exports both
|
13
|
+
|
14
|
+
# HTML EXPORT SETUP
|
15
|
+
|
16
|
+
# 1. Auto-export to html on save (N.B. This doesn't work?)
|
17
|
+
# This can be done per-session by calling 'toggle-org-html-export-on-save'
|
18
|
+
# Local variables:
|
19
|
+
# eval: (add-hook 'after-save-hook 'org-html-export-to-html t t)
|
20
|
+
# End:
|
21
|
+
|
22
|
+
# 2. Apply a theme
|
23
|
+
#+SETUPFILE: org-themes/theme-bigblow.setup
|
24
|
+
# or alternatively #+SETUPFILE: theme-readtheorg.setup
|
25
|
+
|
26
|
+
* Overview
|
27
|
+
|
28
|
+
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
|
29
|
+
probably better off using bundler for ruby projects). It supports per-project configuration using a yaml-format file called =releasables.yaml= located at
|
30
|
+
the project root.
|
31
|
+
|
32
|
+
It currently supports the following functionality
|
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
|
+
|
39
|
+
* Installation
|
40
|
+
|
41
|
+
#+BEGIN_NOTE
|
42
|
+
The gem hasn't been uploaded to rubygems.org as yet.
|
43
|
+
#+END_NOTE
|
44
|
+
|
45
|
+
** From rubygems.org
|
46
|
+
|
47
|
+
#+BEGIN_SRC sh
|
48
|
+
gem install revision
|
49
|
+
#+END_SRC
|
50
|
+
|
51
|
+
** From source
|
52
|
+
|
53
|
+
*** Checkout
|
54
|
+
|
55
|
+
#+BEGIN_SRC sh
|
56
|
+
gem install bundler
|
57
|
+
git clone git@git.nmd.ie:gem/revision
|
58
|
+
cd revision
|
59
|
+
#+END_SRC
|
60
|
+
|
61
|
+
*** Install
|
62
|
+
|
63
|
+
#+BEGIN_SRC sh
|
64
|
+
bundle install
|
65
|
+
bundle exec rake install
|
66
|
+
#+END_SRC
|
67
|
+
|
68
|
+
#+RESULTS:
|
69
|
+
#+begin_example
|
70
|
+
Using rake 10.5.0
|
71
|
+
Using bundler 1.16.0
|
72
|
+
Using coderay 1.1.2
|
73
|
+
Using diff-lcs 1.3
|
74
|
+
Using git 1.3.0
|
75
|
+
Using method_source 0.9.0
|
76
|
+
Using pry 0.11.3
|
77
|
+
Using rubyzip 1.2.1
|
78
|
+
Using thor 0.19.4
|
79
|
+
Using revision 1.0.0 from source at `.`
|
80
|
+
Using rspec-support 3.7.0
|
81
|
+
Using rspec-core 3.7.0
|
82
|
+
Using rspec-expectations 3.7.0
|
83
|
+
Using rspec-mocks 3.7.0
|
84
|
+
Using rspec 3.7.0
|
85
|
+
Bundle complete! 5 Gemfile dependencies, 15 gems now installed.
|
86
|
+
Use `bundle info [gemname]` to see where a bundled gem is installed.
|
87
|
+
revision 1.0.0 built to pkg/revision-1.0.0.gem.
|
88
|
+
revision (1.0.0) installed.
|
89
|
+
#+end_example
|
90
|
+
|
91
|
+
* Usage
|
92
|
+
|
93
|
+
** Supported Commands
|
94
|
+
Run the executable with no arguments to get usage instructions in your console window...
|
95
|
+
|
96
|
+
#+BEGIN_SRC sh
|
97
|
+
revision
|
98
|
+
#+END_SRC
|
99
|
+
|
100
|
+
#+RESULTS:
|
101
|
+
#+begin_example
|
102
|
+
Loading releasable definitions from /home/cormacc/nextcloud/nmd/gem/revision/releasables.yaml ...
|
103
|
+
Commands:
|
104
|
+
revision archive # Archive releasables
|
105
|
+
revision changelog # Display change log on stdout
|
106
|
+
revision help [COMMAND] # Describe available commands or one specific command
|
107
|
+
revision info # Display info for all defined releasables
|
108
|
+
revision major # Increment major revision index
|
109
|
+
revision minor # Increment minor revision index
|
110
|
+
revision patch # Increment patch revision index
|
111
|
+
revision tag # Commit the current revision to a local git repo ...
|
112
|
+
|
113
|
+
Options:
|
114
|
+
[--dryrun], [--no-dryrun]
|
115
|
+
[--id=ID]
|
116
|
+
|
117
|
+
#+end_example
|
118
|
+
|
119
|
+
#+BEGIN_NOTE
|
120
|
+
The tool can be run from any subfolder of a project root -- it will traverse the tree until it finds
|
121
|
+
an ancestor containing =releasables.yaml= OR can go no further (in which case it throws an error).
|
122
|
+
#+END_NOTE
|
123
|
+
|
124
|
+
*** Operating on multiple releasables
|
125
|
+
|
126
|
+
A single =releasables.yaml= file can define multiple releasables, either implicitly (via inclusion) or explicitly
|
127
|
+
(see [[Configuration]] section below for examples). In this context, the ~info~ and ~archive~ commands
|
128
|
+
will operate on all defined releasables, whereas the remaining commands will require the releasable
|
129
|
+
to be specified using the ~--id=~ option, e.g.
|
130
|
+
|
131
|
+
#+BEGIN_SRC sh
|
132
|
+
revision minor --id=firmware
|
133
|
+
#+END_SRC
|
134
|
+
|
135
|
+
** Configuration
|
136
|
+
|
137
|
+
YAML syntax is used for the configuration file. The [[Syntax]] and [[Examples]] sections below should provide sufficient
|
138
|
+
introduction to the limit subset of language features required to use this tool, however further info
|
139
|
+
may be found at the following links:
|
140
|
+
|
141
|
+
- http://docs.ansible.com/ansible/latest/YAMLSyntax.html
|
142
|
+
- http://www.yaml.org/start.html
|
143
|
+
|
144
|
+
*** Syntax
|
145
|
+
|
146
|
+
The =releasables.yaml= file should contain a top level ~:releasables~ node.
|
147
|
+
Under this, you can create a list (or YAML /sequence/) of releasable definitions.
|
148
|
+
|
149
|
+
Each sequence item begins with a ~-~ and contains either a link to a folder containing its own =releasables.yaml=
|
150
|
+
defining one or more releasables ...
|
151
|
+
|
152
|
+
#+BEGIN_SRC yaml
|
153
|
+
- :folder: relative/path/to/a/releasable/folder
|
154
|
+
#+END_SRC
|
155
|
+
|
156
|
+
... or a single inline releasable definition.
|
157
|
+
|
158
|
+
#+BEGIN_NOTE
|
159
|
+
The lines beginning with '#' are explanatory comments
|
160
|
+
#+END_NOTE
|
161
|
+
|
162
|
+
#+BEGIN_SRC yaml
|
163
|
+
- :id: my_releasable
|
164
|
+
:revision:
|
165
|
+
# Source file containing the revision identifier
|
166
|
+
# This will also include changelog entries, embedded as comments
|
167
|
+
:src: lib/revision/version.rb
|
168
|
+
# Regex matching the source revision identifier. Must contain the following named capture groups
|
169
|
+
# - major, minor, patch :: Numeric (uint) sequences representing the three revision ID components
|
170
|
+
# - sep1, sep2 :: the characters separating the revision components
|
171
|
+
# - prefix, postfix :: sufficient syntactic context to match the revision ID uniquely
|
172
|
+
# N.B. this regex matches the version ID from the standard bundler gem skeleton,
|
173
|
+
# e.g. VERSION = "1.1.0"
|
174
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
175
|
+
# Comment char for the project language -- prefixed to each line of changelog entries
|
176
|
+
# Quotes only necessary here to prevent # being interpreted as the beginning of a YAML comment
|
177
|
+
:comment_prefix: "#"
|
178
|
+
# Sequence of build steps -- each item should be a valid shell command, prefixed with the YAML sequence item token, '- '
|
179
|
+
:build_steps:
|
180
|
+
- bundle exec rake install
|
181
|
+
# Sequence defining the files (build artefacts) to package in the release archive.
|
182
|
+
# Each artefact definition must include a :src: key/value pair.
|
183
|
+
# An optional :dest: value may be provided to rename the file during packaging, or just (as in the first entry below)
|
184
|
+
# to flatten the folder structure.
|
185
|
+
# Any <VER> (or <REV>) in the :src: or :dest: placeholders wil be replaced with the current revision ID
|
186
|
+
# The revision archive will also include the revision history extracted as a text file
|
187
|
+
:artefacts:
|
188
|
+
# A binary artefact -- the
|
189
|
+
- :src: pkg/revision-<VER>.gem
|
190
|
+
:dest: revision-<VER>.gem
|
191
|
+
# This document -- the :dest: value defaults to duplicating :src: if not specified
|
192
|
+
- :src: README.org
|
193
|
+
#+END_SRC
|
194
|
+
|
195
|
+
**** TODO (or at least consider) add support for overridable defaults
|
196
|
+
e.g. via a =.releasables= configuration file in the user home dir.
|
197
|
+
Though this would be bad for collaborative development as the config file would live outside source control.
|
198
|
+
Possibly useful to support inclusion of a controlled configuration file instead? Primarily to define
|
199
|
+
a revision regex and comment prefix for a group of related releasables....
|
200
|
+
|
201
|
+
*** Examples
|
202
|
+
**** C project
|
203
|
+
|
204
|
+
#+BEGIN_NOTE
|
205
|
+
The ~:regex:~ and ~:comment_prefix:~ keys are absent in the C example below. This project started life as
|
206
|
+
managing some embedded C projects, and the default values reflect this.
|
207
|
+
#+END_NOTE
|
208
|
+
|
209
|
+
#+BEGIN_SRC yaml
|
210
|
+
:releasables:
|
211
|
+
- :id: mbt_cd_firmware
|
212
|
+
:revision:
|
213
|
+
:src: src/FirmwareRevision.c
|
214
|
+
:build_steps:
|
215
|
+
- make --jobs=8 -f Makefile CONF=bootloadable
|
216
|
+
:artefacts:
|
217
|
+
- :src: dist/bootloadable/production/firmware.production.hex
|
218
|
+
:dest: mbt_cd_firmware_v<REV>.bootloadable.hex
|
219
|
+
- :src: dist/default/production/firmware.production.hex
|
220
|
+
:dest: mbt_cd_firmware_v<REV>.standalone.hex
|
221
|
+
#+END_SRC
|
222
|
+
|
223
|
+
**** Ruby project
|
224
|
+
|
225
|
+
#+BEGIN_SRC yaml
|
226
|
+
:releasables:
|
227
|
+
- :id: revision
|
228
|
+
:revision:
|
229
|
+
:src: lib/revision/version.rb
|
230
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
231
|
+
:comment_prefix: "#"
|
232
|
+
:build_steps:
|
233
|
+
- bundle exec rake install
|
234
|
+
:artefacts:
|
235
|
+
- :src: pkg/revision-<VER>.gem
|
236
|
+
#+END_SRC
|
237
|
+
|
238
|
+
*** Heirarchical project
|
239
|
+
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=.
|
240
|
+
|
241
|
+
i.e assuming the earlier examples were in folders =examples/c= and =examples/ruby= relative to a common root, a separate =releasables.yaml=
|
242
|
+
at that root could include them as follows...
|
243
|
+
|
244
|
+
#+BEGIN_SRC yaml
|
245
|
+
:releasables:
|
246
|
+
- :folder: examples/c
|
247
|
+
- :folder: examples/ruby
|
248
|
+
#+END_SRC
|
249
|
+
|
250
|
+
**** TODO consider supporting a higher-level aggregate revision ID
|
251
|
+
|
252
|
+
#+BEGIN_SRC yaml
|
253
|
+
:revision:
|
254
|
+
:src: release_log.txt
|
255
|
+
:releasables:
|
256
|
+
- :folder: examples/c
|
257
|
+
- :folder: examples/ruby
|
258
|
+
#+END_SRC
|
259
|
+
|
260
|
+
* Development
|
261
|
+
|
262
|
+
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.
|
263
|
+
|
264
|
+
To install this gem onto your local machine, run =bundle exec rake install=. To release a new version, update the version number in =version.rb=, 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
|
+
|
266
|
+
|
267
|
+
* Contributing
|
268
|
+
|
269
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/revision.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "revision"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/revision
ADDED
data/lib/revision/cli.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require_relative 'releasable'
|
4
|
+
require_relative 'info'
|
5
|
+
require_relative 'errors'
|
6
|
+
|
7
|
+
module Revision
|
8
|
+
class CLI < Thor
|
9
|
+
class_option :dryrun, :type => :boolean, :default =>false
|
10
|
+
class_option :id, :default => nil #Initialized after loading definition
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
#TODO Update this to traverse up the folder heirarchy until we find a releasables.yaml
|
15
|
+
wd = Dir.getwd
|
16
|
+
loop do
|
17
|
+
begin
|
18
|
+
@releasables = Releasable.from_folder(wd)
|
19
|
+
break
|
20
|
+
rescue Errors::NoDefinition
|
21
|
+
break if wd == File.expand_path('..',wd)
|
22
|
+
puts "No releasable found in #{wd}, trying parent..."
|
23
|
+
wd = File.expand_path('..',wd)
|
24
|
+
next
|
25
|
+
end
|
26
|
+
end
|
27
|
+
raise 'No definition file found in this directory or its ancestors' if @releasables.nil? || @releasables.empty?
|
28
|
+
@id = options[:id] || @releasables.keys[0]
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "info", 'Display info for all defined releasables'
|
32
|
+
def info
|
33
|
+
puts "Found #{@releasables.values.length} releasables"
|
34
|
+
puts ''
|
35
|
+
puts @releasables.values.map {|r| r.to_s}.join("\n\n")
|
36
|
+
puts ''
|
37
|
+
puts "Default releasable ID: #{@id}"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "patch", "Increment patch revision index"
|
41
|
+
def patch
|
42
|
+
do_increment('patch')
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "minor", "Increment minor revision index"
|
46
|
+
def minor
|
47
|
+
do_increment('minor')
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "major", "Increment major revision index"
|
51
|
+
def major
|
52
|
+
do_increment('major')
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "archive", "Archive releasables"
|
56
|
+
def archive
|
57
|
+
selected = options[:id].nil? ? @releasables.values : [@releasables[options[:id]]]
|
58
|
+
puts "Archiving #{selected.length} releasables..."
|
59
|
+
selected.each do |r|
|
60
|
+
r.build
|
61
|
+
r.archive
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "changelog", "Display change log on stdout"
|
66
|
+
def changelog
|
67
|
+
select_one.output_changelog($stdout)
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "tag", "Commit the current revision to a local git repo and add a version tag"
|
71
|
+
def tag
|
72
|
+
select_one.tag
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def select_one
|
78
|
+
raise "Please specify one of #{@releasables.keys}" if options[:id].nil? && @releasables.keys.length > 1
|
79
|
+
@releasables[@id]
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_increment(type)
|
83
|
+
r = select_one
|
84
|
+
increment_method = "#{type}_increment!"
|
85
|
+
say "Incrementing #{r.revision} to #{r.revision.public_send(increment_method)}"
|
86
|
+
options[:dryrun] ? r.revision.write_to_file : r.revision.write!
|
87
|
+
say ""
|
88
|
+
say "The automatic commit / tag step assumes you're only checking in changes to existing files"
|
89
|
+
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"
|
90
|
+
say ""
|
91
|
+
if ask("Commit changes to existing files and add a Git tag (y/N)?").upcase=='Y'
|
92
|
+
r.tag
|
93
|
+
if ask("Push changes/tag to origin (Y/n)?").upcase=='N' || !r.push
|
94
|
+
say "To push from the command line, type 'git push --tags' at a shell prompt"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative 'releasable'
|
2
|
+
require_relative 'string_case'
|
3
|
+
|
4
|
+
|
5
|
+
class Revision::Info
|
6
|
+
DEFAULT_REGEX = /(?<prefix>\s*Info\s+const\s+\S+_REVISION\s*=\s*\{\s*\{\s*)(?<major>\d+)(?<sep1>\s*,\s*)(?<minor>\d+)(?<sep2>\s*,\s*)(?<patch>\d+)(?<postfix>\s*\}\s*\};\s*)/
|
7
|
+
DEFAULT_COMMENT_PREFIX = ' *'.freeze
|
8
|
+
CHANGELOG_START_TAG = '<BEGIN CHANGELOG>'.freeze
|
9
|
+
CHANGELOG_END_TAG = '<END CHANGELOG>'.freeze
|
10
|
+
CHANGELOG_START = /.*#{CHANGELOG_START_TAG}.*/
|
11
|
+
CHANGELOG_END = /.*#{CHANGELOG_END_TAG}.*/
|
12
|
+
|
13
|
+
attr_accessor :major, :minor, :patch
|
14
|
+
attr_accessor :src
|
15
|
+
attr_accessor :regex
|
16
|
+
attr_accessor :comment_prefix
|
17
|
+
|
18
|
+
def initialize(file, regex: nil, comment_prefix: nil)
|
19
|
+
@src=file
|
20
|
+
@regex = regex.nil? ? DEFAULT_REGEX : /#{regex}/
|
21
|
+
@comment_prefix = comment_prefix || DEFAULT_COMMENT_PREFIX
|
22
|
+
matched = false
|
23
|
+
File.open(@src).each_line do |line|
|
24
|
+
if line =~ @regex
|
25
|
+
@major = Regexp.last_match[:major].to_i
|
26
|
+
@minor = Regexp.last_match[:minor].to_i
|
27
|
+
@patch = Regexp.last_match[:patch].to_i
|
28
|
+
matched = true
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
raise "Failed to match against #{@regex}" unless matched
|
33
|
+
end
|
34
|
+
|
35
|
+
def patch_increment!
|
36
|
+
@patch += 1
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def minor_increment!
|
41
|
+
@minor += 1
|
42
|
+
@patch = 0
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def major_increment!
|
47
|
+
@major += 1
|
48
|
+
@minor = 0
|
49
|
+
@patch = 0
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_changelog_placeholder
|
54
|
+
placeholder = [@comment_prefix,CHANGELOG_START_TAG,CHANGELOG_END_TAG].join("\n#{@comment_prefix} ")
|
55
|
+
# Handle C block comments as a special case for legacy project support
|
56
|
+
# Could generalise the comment definition, but feels like unnecessary complexity...
|
57
|
+
if @src =~ /\.(c|cpp|h|hpp)$/ && !@comment_prefix =~ /#{Regexp.escape('//')}/
|
58
|
+
placeholder = "/**\n${placeholder}\n*/"
|
59
|
+
end
|
60
|
+
placeholder + "\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
def write(output_file_name)
|
64
|
+
|
65
|
+
ref_info = self.class.new(@src, regex: @regex)
|
66
|
+
raise 'No revision identifiers incremented' if ref_info.to_s == self.to_s
|
67
|
+
|
68
|
+
entry = get_changelog_entry
|
69
|
+
|
70
|
+
text = File.read(@src)
|
71
|
+
text.gsub!(@regex) { |match| "#{$~[:prefix]}#{@major}#{$~[:sep1]}#{@minor}#{$~[:sep2]}#{@patch}#{$~[:postfix]}" }
|
72
|
+
|
73
|
+
#Insert start/end tags if not present
|
74
|
+
text += new_changelog unless text.match(CHANGELOG_START)
|
75
|
+
|
76
|
+
text.gsub!(CHANGELOG_START) { |match| [match, format_changelog_entry(entry)].join("\n") }
|
77
|
+
|
78
|
+
File.open(output_file_name, 'w') { |f| f.write(text) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def write!
|
82
|
+
write(@src)
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"#{@major}.#{@minor}.#{@patch}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def strip_comment_prefix(line)
|
90
|
+
line.gsub(/^\s*#{Regexp.escape(@comment_prefix)}\s?/,'')
|
91
|
+
end
|
92
|
+
|
93
|
+
def changelog
|
94
|
+
in_changelog = false
|
95
|
+
File.open(@src).each_line do |line|
|
96
|
+
if in_changelog
|
97
|
+
break if line =~ CHANGELOG_END
|
98
|
+
yield strip_comment_prefix(line.chomp)
|
99
|
+
else
|
100
|
+
in_changelog = line =~ CHANGELOG_START
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def last_changelog_entry
|
106
|
+
in_entry = false
|
107
|
+
lines = []
|
108
|
+
changelog do |line|
|
109
|
+
if line.length > 0
|
110
|
+
in_entry = true
|
111
|
+
lines << line
|
112
|
+
else
|
113
|
+
break if in_entry
|
114
|
+
end
|
115
|
+
end
|
116
|
+
lines
|
117
|
+
end
|
118
|
+
|
119
|
+
# Prefixes the entry with an empty line, then prefixes each line with comment chars
|
120
|
+
# and converts the line entries to a single string
|
121
|
+
def format_changelog_entry(entry_lines)
|
122
|
+
entry_lines.unshift('').map { |line| "#{@comment_prefix} #{line}"}.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_changelog_entry
|
126
|
+
entry_lines = []
|
127
|
+
entry_lines << "Version #{self} (#{Time.now.strftime("%d %b %Y")})"
|
128
|
+
puts('Changelog entry (one item per line / empty line to end)')
|
129
|
+
puts('N.B. Git commit entry will use revision ID and first line of entry')
|
130
|
+
while line = $stdin.readline.strip
|
131
|
+
break if line.length == 0
|
132
|
+
entry_lines << "- #{line}"
|
133
|
+
end
|
134
|
+
entry_lines
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
require 'zip'
|
4
|
+
require 'git'
|
5
|
+
|
6
|
+
module Revision
|
7
|
+
|
8
|
+
class Releasable
|
9
|
+
|
10
|
+
BUILD_DIR_BASE_NAME = "dist"
|
11
|
+
BUILD_CONFIGURATION_DEFAULT = "default"
|
12
|
+
BUILD_TARGET_DEFAULT = "production"
|
13
|
+
# RELATIVE_PATH_TO_BOOTLOADER = File.join("..","bootloader")
|
14
|
+
RELATIVE_PATH_TO_BOOTLOADER = "bootloader"
|
15
|
+
|
16
|
+
CONFIG_FILE_NAME = 'releasables.yaml'.freeze
|
17
|
+
|
18
|
+
REVISION_PLACEHOLDER = /<REV>|<VER>/
|
19
|
+
|
20
|
+
attr_reader :root, :id, :revision, :build_steps, :artefacts
|
21
|
+
|
22
|
+
# Load a file in yaml format containing one or more releasable definitions
|
23
|
+
# @param root [String] An optional root directory argument
|
24
|
+
# @return [Hash] Contents of the yaml file
|
25
|
+
def self.load_definitions(root: nil)
|
26
|
+
root ||= Dir.getwd
|
27
|
+
config_file = File.join(root, CONFIG_FILE_NAME)
|
28
|
+
raise Errors::NoDefinition.new(root) unless File.exist?(config_file)
|
29
|
+
puts "Loading releasable definitions from #{config_file} ..."
|
30
|
+
YAML.load_file(config_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Instantiate the Releasables defined in releasables.yaml
|
34
|
+
def self.from_folder(root = Dir.getwd)
|
35
|
+
config = load_definitions(:root=>root)
|
36
|
+
|
37
|
+
releasables = {}
|
38
|
+
config[:releasables].each do |config_entry|
|
39
|
+
if config_entry[:folder]
|
40
|
+
#Load entries from a nested releasable definition file
|
41
|
+
releasables = releasables.merge(from_folder(File.join(root, config_entry[:folder])))
|
42
|
+
else
|
43
|
+
r = new(root: root, config: config_entry)
|
44
|
+
releasables[r.id] = r
|
45
|
+
end
|
46
|
+
end
|
47
|
+
releasables
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(root: nil, config: {})
|
51
|
+
|
52
|
+
root ||= Dir.getwd
|
53
|
+
@root = Pathname.new(root).realpath
|
54
|
+
@id = config[:id] || File.basename(@root)
|
55
|
+
@revision = Info.new(File.join(@root,config[:revision][:src]), regex: config[:revision][:regex], comment_prefix: config[:revision][:comment_prefix])
|
56
|
+
@build_steps = config[:build_steps]
|
57
|
+
@artefacts = config[:artefacts]
|
58
|
+
@artefacts.each { |a| a[:dest] ||= a[:src] }
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
<<~EOT
|
63
|
+
#{@id} v#{@revision} @ #{@root}
|
64
|
+
|
65
|
+
Build pipeline:
|
66
|
+
- #{@build_steps.join("\n - ")}
|
67
|
+
|
68
|
+
Build artefacts:
|
69
|
+
#{artefacts.map{ |a| "- #{a[:src]}\n => #{a[:dest]}" }.join("\n")}
|
70
|
+
EOT
|
71
|
+
end
|
72
|
+
|
73
|
+
def build
|
74
|
+
puts "Executing #{@build_steps.length} build steps..."
|
75
|
+
Dir.chdir(@root) do
|
76
|
+
@build_steps.each_with_index do |step, index|
|
77
|
+
puts "... (#{index+1}/#{@build_steps.length}) #{step}"
|
78
|
+
system(step)
|
79
|
+
puts "WARNING: build step #{index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def tag
|
85
|
+
Dir.chdir(@root) do
|
86
|
+
tag_id = "v#{revision}"
|
87
|
+
changelog_entry = @revision.last_changelog_entry
|
88
|
+
#Insert a blank line between the revision header and release notes, as per git commit best practice
|
89
|
+
commit_lines = ["#{tag_id} #{changelog_entry[1]}", '']
|
90
|
+
if changelog_entry.length > 2
|
91
|
+
commit_lines << "Also..."
|
92
|
+
commit_lines += changelog_entry[2..-1]
|
93
|
+
end
|
94
|
+
commit_message = commit_lines.join("\n")
|
95
|
+
g = Git.init
|
96
|
+
puts "Committing..."
|
97
|
+
puts commit_message
|
98
|
+
g.commit_all(commit_message)
|
99
|
+
puts "Tagging as #{tag_id}"
|
100
|
+
g.add_tag(tag_id)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def push
|
105
|
+
pushed = false
|
106
|
+
Dir.chdir(@root) do
|
107
|
+
g = Git.init
|
108
|
+
begin
|
109
|
+
g.push('origin', g.current_branch, tags: true)
|
110
|
+
pushed = true
|
111
|
+
rescue GitExecuteError => e
|
112
|
+
puts "ERROR :: Cannot push to origin :: #{e}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
pushed
|
116
|
+
end
|
117
|
+
|
118
|
+
def archive_name
|
119
|
+
"#{@id}_v#{@revision}.zip"
|
120
|
+
end
|
121
|
+
|
122
|
+
def changelog_name
|
123
|
+
"#{@id}_revision_history_v#{@revision}.txt"
|
124
|
+
end
|
125
|
+
|
126
|
+
def archive
|
127
|
+
puts "Packaging #{@artefacts.length} build artefacts as #{archive_name}..."
|
128
|
+
if File.exist?(archive_name)
|
129
|
+
puts "... deleting existing archive"
|
130
|
+
File.delete(archive_name)
|
131
|
+
end
|
132
|
+
Zip::File.open(archive_name, Zip::File::CREATE) do |zipfile|
|
133
|
+
@artefacts.each_with_index do |a, index|
|
134
|
+
src = a[:src].gsub(REVISION_PLACEHOLDER, @revision.to_s)
|
135
|
+
dest = a[:dest].gsub(REVISION_PLACEHOLDER, @revision.to_s)
|
136
|
+
puts "... (#{index+1}/#{@artefacts.length}) #{src} => #{dest}"
|
137
|
+
zipfile.add(dest, File.join(@root, src))
|
138
|
+
end
|
139
|
+
puts "... embedding revision history as #{changelog_name} "
|
140
|
+
zipfile.get_output_stream(changelog_name) { |os| output_changelog(os)}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def output_changelog(output_stream)
|
145
|
+
output_stream.puts "Revision history for #{@id} version #{@revision}"
|
146
|
+
output_stream.puts ""
|
147
|
+
@revision.changelog {|line| output_stream.puts(line)}
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class String
|
2
|
+
# ruby mutation methods have the expectation to return self if a mutation occurred, nil otherwise. (see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-gsub-21)
|
3
|
+
def to_underscore!
|
4
|
+
gsub!(/::/, '/')
|
5
|
+
gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
6
|
+
gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
7
|
+
tr!("-", "_")
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Converts _SnakeCase_ to _snake_case_
|
12
|
+
def to_snake_case!
|
13
|
+
to_underscore!
|
14
|
+
downcase!
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Converts _ScreamingSnakeCase to _SCREAMING_SNAKE_CASE_
|
19
|
+
def to_screaming_snake_case!
|
20
|
+
to_underscore!
|
21
|
+
upcase!
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_underscore
|
25
|
+
dup.tap { |s| s.to_underscore! }
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_snake_case
|
29
|
+
dup.tap { |s| s.to_snake_case! }
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_screaming_snake_case
|
33
|
+
dup.tap { |s| s.to_screaming_snake_case! }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Defines the revision ID for the revision gem
|
2
|
+
module Revision
|
3
|
+
VERSION = "1.1.4"
|
4
|
+
end
|
5
|
+
|
6
|
+
# <BEGIN CHANGELOG>
|
7
|
+
#
|
8
|
+
# Version 1.1.4 (14 Dec 2017)
|
9
|
+
# - Minor message body reformatting
|
10
|
+
#
|
11
|
+
# Version 1.1.3 (14 Dec 2017)
|
12
|
+
# - Eliminated duplication of version ID in commit message body
|
13
|
+
#
|
14
|
+
# Version 1.1.2 (14 Dec 2017)
|
15
|
+
# - Removed redundant ':: ' from commit message headline
|
16
|
+
#
|
17
|
+
# Version 1.1.1 (14 Dec 2017)
|
18
|
+
# - Added git connection failure handling
|
19
|
+
# - Revision commit message now includes first line of changelog entry
|
20
|
+
# - Updated configuration syntax for consistency (:revision: :file: -> :revision: :src:)
|
21
|
+
#
|
22
|
+
# Version 1.1.0 (13 Dec 2017)
|
23
|
+
# - Updated to optionally push tags to the repo
|
24
|
+
#
|
25
|
+
# Version 1.0.1 (13 Dec 2017)
|
26
|
+
# - Corrected revision placeholder handling when archiving build artefacts
|
27
|
+
# - Added proper high-level usage documentation
|
28
|
+
#
|
29
|
+
# Version 1.0.0 (12 Dec 2017)
|
30
|
+
# - First fully functional release with new config file
|
31
|
+
#
|
32
|
+
# Version 0.1.4 (12 Dec 2017)
|
33
|
+
# - boo
|
34
|
+
# - hoo
|
35
|
+
# - hoo
|
36
|
+
#
|
37
|
+
# Version 0.1.3 (12 Dec 2017)
|
38
|
+
# - boo
|
39
|
+
#
|
40
|
+
# Version 0.1.2 (12 Dec 2017)
|
41
|
+
# - wahoo!
|
42
|
+
#
|
43
|
+
# Version 0.1.1 (12 Dec 2017)
|
44
|
+
# - Wahey!
|
45
|
+
# <END CHANGELOG>
|
data/lib/revision.rb
ADDED
data/releasables.yaml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
:releasables:
|
2
|
+
- :id: revision
|
3
|
+
:revision:
|
4
|
+
:src: lib/revision/version.rb
|
5
|
+
:regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
|
6
|
+
:comment_prefix: "#"
|
7
|
+
:build_steps:
|
8
|
+
- bundle exec rake install
|
9
|
+
:artefacts:
|
10
|
+
- :src: pkg/revision-<VER>.gem
|
data/revision.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "revision/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "revision"
|
7
|
+
spec.version = Revision::VERSION
|
8
|
+
spec.authors = ["Cormac Cannon"]
|
9
|
+
spec.email = ["cormac.cannon@neuromoddevices.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{Language-agnostic revision management tool}
|
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
|
+
# spec.homepage = "TBC"
|
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
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
f.match(%r{^(test|spec|features)/})
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^#{spec.bindir}/}) { |f| File.basename(f) }
|
29
|
+
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_runtime_dependency 'thor', '~> 0.19.1'
|
33
|
+
spec.add_runtime_dependency 'rubyzip'
|
34
|
+
spec.add_runtime_dependency 'git'
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
spec.add_development_dependency "pry"
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: revision
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cormac Cannon
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.19.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.19.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubyzip
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: git
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Updates project revision identifiers in software source files and associated
|
112
|
+
change log. Can also build and package project archives as a zip and optionally
|
113
|
+
commit, tag and push to a Git repo.
|
114
|
+
email:
|
115
|
+
- cormac.cannon@neuromoddevices.com
|
116
|
+
executables:
|
117
|
+
- revision
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- ".rspec"
|
123
|
+
- ".travis.yml"
|
124
|
+
- Gemfile
|
125
|
+
- README.org
|
126
|
+
- Rakefile
|
127
|
+
- bin/console
|
128
|
+
- bin/setup
|
129
|
+
- exe/revision
|
130
|
+
- lib/revision.rb
|
131
|
+
- lib/revision/cli.rb
|
132
|
+
- lib/revision/errors.rb
|
133
|
+
- lib/revision/info.rb
|
134
|
+
- lib/revision/releasable.rb
|
135
|
+
- lib/revision/string_case.rb
|
136
|
+
- lib/revision/version.rb
|
137
|
+
- releasables.yaml
|
138
|
+
- revision.gemspec
|
139
|
+
homepage:
|
140
|
+
licenses: []
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 2.6.13
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Language-agnostic revision management tool
|
162
|
+
test_files: []
|