git-lite-version-bump 0.17.0

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.
@@ -0,0 +1,157 @@
1
+ Maintain your program versions entirely within git. No local files
2
+ required! All versioning information is stored using git tags.
3
+
4
+ This gem contains a command-line tool and set of Rake tasks to increment
5
+ and display your version numbers via git tags, and some associated Ruby code to use
6
+ inside a gemspec or your program to retrieve the current version number, for
7
+ use in builds and at runtime.
8
+
9
+
10
+ # Usage
11
+
12
+ Most of your day-to-day usage of `git-version-bump` will be via the command
13
+ line. When you bump a version, a new tag will be created representing the newly
14
+ incremented version number at the current commit. If no tags currently
15
+ exist, the previous version will be taken to be `0.0.0` and then incremented
16
+ accordingly.
17
+
18
+
19
+ ## On the command line
20
+
21
+ Pretty damned trivial:
22
+
23
+ git version-bump <major|minor|patch|show>
24
+
25
+ You can also shorten the specifier to any unique substring:
26
+
27
+ git version-bump ma
28
+ git version-bump mi
29
+ git version-bump p
30
+ git version-bump s
31
+
32
+ I recommend adding an alias to your `~/.gitconfig` file, for less typing:
33
+
34
+ [alias]
35
+ vb = version-bump
36
+
37
+ You can also add your own release notes to your release tags, by using the
38
+ `-n` (or `--notes`, if you like typing) option:
39
+
40
+ git version-bump -n minor
41
+
42
+ This will open an editor, containing a list of the commits since the last
43
+ release tag, in which you can type your release notes. If you follow
44
+ standard git commit style (a "heading" line, then a blank line, followed by
45
+ free-form text) you're perfectly positioned to use
46
+ [github-release](http://theshed.hezmatt.org/github-release) to make
47
+ gorgeous-looking release announcements to Github.
48
+
49
+
50
+ ## In your `Rakefile`
51
+
52
+ If you'd like to have access to the version-bumping goodness via `rake`, add
53
+ the following line to your `Rakefile`:
54
+
55
+ require 'git-version-bump/rake-tasks'
56
+
57
+ You will now have the following rake tasks available:
58
+
59
+ rake version:bump:major # bump major version (x.y.z -> x+1.0.0)
60
+ rake version:bump:minor # bump minor version (x.y.z -> x.y+1.0)
61
+ rake version:bump:patch # bump patch version (x.y.z -> x.y.z+1)
62
+ rake version:bump:show # Print current version number
63
+
64
+ (Since `version:bump:major` is a lot of typing, there are also shortcuts:
65
+ `v:b:major`, `v:b:maj`, `v:b:minor`, `v:b:min`, `v:b:patch`, `v:b:pat`, and
66
+ `v:b:p`)
67
+
68
+
69
+ ## In your Ruby code
70
+
71
+ To get access to this version information in your code (such as in your
72
+ `gemspec`, or the definition of a `::VERSION` constant), you can `require
73
+ 'git-version-bump'` and use the following methods:
74
+
75
+ GVB.version # Return the entire version string
76
+ GVB.major_version # Return just the 'major' portion of the version
77
+ GVB.minor_version # Return just the 'minor' portion of the version
78
+ GVB.patch_version # Return just the 'patch' portion of the version
79
+ GVB.internal_revision # Return "internal revision" information, or nil
80
+ GVB.date # Return the date of the most recent commit, or
81
+ # today's date if the tree is dirty
82
+
83
+ The "internal revision" is set when the tree is dirty, or when the latest
84
+ git commit doesn't correspond with a tag. In that case, the internal
85
+ revision will describe, in the manner of `git describe`, the full details of
86
+ the version of the code in use. This information will be part of the
87
+ version string provided by `gvb_version`.
88
+
89
+ If any of these methods are called when there isn't a tag or other version
90
+ information available, the version will be assumed to be `0.0.0.1.ENOTAG`
91
+ with a date of `1970-01-01`.
92
+
93
+
94
+ ### In your gemspec
95
+
96
+ Typically, you want to encode your version and commit date into your
97
+ gemspec, like this:
98
+
99
+ Gem::Specification.new do |s|
100
+ s.version = GVB.version
101
+ s.date = GVB.date
102
+
103
+ ...
104
+ end
105
+
106
+ The beauty of this method is that whenever you run a `rake build`, you'll
107
+ get a gem which is *accurately* versioned for the current state of your
108
+ repository. No more wondering if the `foobar-1.2.3` gem installed on your
109
+ system was built from pristine sources, or with that experimental patch you
110
+ were trying out...
111
+
112
+
113
+ ### In your gem
114
+
115
+ If, like me, you're one of those people who likes to be able to easily see
116
+ what version of a library you're running, then you probably like to define a
117
+ `VERSION` constant somewhere in your gem's namespace. That, too, is simple
118
+ to do:
119
+
120
+ require 'git-version-bump'
121
+
122
+ class Foobar
123
+ VERSION = GVB.version
124
+ end
125
+
126
+ This will work correctly inside your git tree, and also in your installed
127
+ gem. Magical!
128
+
129
+ #### For projects using lite tags
130
+
131
+ If you are using GitHub releases for your project or some other method that
132
+ involves light tags (tags with no annotations), you might notice that these
133
+ tags are not detected by git-version-bump by default. If you want these
134
+ commits to be detected then use the following configuration:
135
+
136
+ require 'git-version-bump'
137
+
138
+ class Foobar
139
+ # First parameter is use_local_git, second is include_lite_tags
140
+ VERSION = GVB.version(false, true)
141
+ end
142
+
143
+
144
+ # Contributing
145
+
146
+ Send your pull requests to the [Github
147
+ repo](https://github.com/mpalmer/git-version-bump), or send patches to
148
+ `theshed+git-version-bump@hezmatt.org`. Bug reports can be sent to the same
149
+ place, although I greatly prefer patches.
150
+
151
+
152
+ # Licence
153
+
154
+ Unless otherwise specified, all code in this repository is licenced under
155
+ the terms of the GNU Public Licence, version 3, as published by the Free
156
+ Software Foundation. The full terms of this licence can be found in the
157
+ file LICENCE.
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rdoc/task'
7
+
8
+ Rake::RDocTask.new do |rd|
9
+ rd.main = "README.md"
10
+ rd.title = 'git-version-bump'
11
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
12
+ end
13
+
14
+ task :release do
15
+ sh "git push --follow-tags"
16
+ sh "git release"
17
+ end
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'git-version-bump'
4
+
5
+ if ARGV[0].nil? or
6
+ ARGV[0].empty? or
7
+ (ARGV.length == 1 && (ARGV[0] == "-d" || ARGV[0] == "--dry-run")) or
8
+ ARGV[0] == '-h' or
9
+ ARGV[0] == '--help'
10
+ $stderr.puts <<-EOF.gsub(/^\t\t/, '')
11
+ Usage: git version-bump [-n|--notes] [-d|--dry-run] <major|minor|patch|show>
12
+
13
+ 'major': x.y.z -> x+1.0.0
14
+ 'minor': x.y.z -> x.y+1.0
15
+ 'patch': x.y.z -> x.y.z+1
16
+
17
+ 'show': Display the current GVB version
18
+
19
+ -d, --dry-run: Calculate and return the bump value, but don't update git workspace or remote
20
+ -n, --notes: Prompt for "release notes" to add to the release tag
21
+ -l, --lite-tags: Include non-annotated git tags
22
+ EOF
23
+ end
24
+
25
+ release_notes = ARGV.delete('-n') || ARGV.delete('--notes')
26
+ dry_run = ARGV.delete('-d') || ARGV.delete('--dry-run')
27
+ lite_tags = ARGV.delete('-l') || ARGV.delete('--lite-tags')
28
+
29
+ if ARGV[0].nil? or ARGV[0].empty?
30
+ exit 1
31
+ elsif ARGV[0] == '-h' or ARGV[0] == '--help'
32
+ exit 0
33
+ end
34
+
35
+ result = case ARGV[0].downcase
36
+ when /^maj?o?r?$/
37
+ "#{GVB.major_version(true) + 1}.0.0"
38
+ when /^min?o?r?$/
39
+ "#{GVB.major_version(true)}.#{GVB.minor_version(true)+1}.0"
40
+ when /^pa?t?c?h?$/
41
+ "#{GVB.major_version(true)}.#{GVB.minor_version(true)}.#{GVB.patch_version(true)+1}"
42
+ when /^sh?o?w?$/
43
+ puts GVB.version(true)
44
+ exit 0
45
+ else
46
+ $stderr.puts "Unknown argument: #{ARGV[0]}. Try --help."
47
+ exit 1
48
+ end
49
+
50
+ if dry_run
51
+ puts result
52
+ else
53
+ GVB.tag_version result, release_notes, lite_tags
54
+ puts "Version is now #{GVB.version(true)}."
55
+ end
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'git-version-bump'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "git-lite-version-bump"
7
+
8
+ s.version = GVB.version
9
+ s.date = GVB.date
10
+
11
+ s.platform = Gem::Platform::RUBY
12
+ s.required_ruby_version = ">= 1.8.7"
13
+
14
+ s.homepage = "http://github.com/rbclark/git-version-bump"
15
+ s.summary = "Manage your app version entirely via git tags (even lite tags)"
16
+ s.authors = ["Matt Palmer"]
17
+
18
+ s.extra_rdoc_files = ["README.md"]
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = ["git-version-bump"]
21
+
22
+ s.add_development_dependency 'github-release'
23
+ s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'bundler'
25
+ s.add_development_dependency 'rdoc'
26
+ end
@@ -0,0 +1,324 @@
1
+ require 'tempfile'
2
+ require 'digest/sha1'
3
+ require 'pathname'
4
+
5
+ module GitVersionBump
6
+ class VersionUnobtainable < StandardError; end
7
+
8
+ DEVNULL = Gem.win_platform? ? "NUL" : "/dev/null"
9
+
10
+ def self.version(use_local_git=false, include_lite_tags=false)
11
+ if use_local_git
12
+ unless git_available?
13
+ raise RuntimeError,
14
+ "GVB.version(use_local_git=true) called, but git isn't installed"
15
+ end
16
+
17
+ sq_git_dir = shell_quoted_string(Dir.pwd)
18
+ else
19
+ sq_git_dir = shell_quoted_string((File.dirname(caller_file) rescue nil || Dir.pwd))
20
+ end
21
+
22
+ git_cmd = "git -C #{sq_git_dir} describe --dirty='.1.dirty.#{Time.now.strftime("%Y%m%d.%H%M%S")}' --match='v[0-9]*.[0-9]*.*[0-9]'"
23
+ git_cmd << " --tags" if include_lite_tags
24
+
25
+ git_ver = `#{git_cmd} 2> #{DEVNULL}`.
26
+ strip.
27
+ gsub(/^v/, '').
28
+ gsub('-', '.')
29
+
30
+ # If git returned success, then it gave us a described version.
31
+ # Success!
32
+ return git_ver if $? == 0
33
+
34
+ # git failed us; we're either not in a git repo or else we've never
35
+ # tagged anything before.
36
+
37
+ # Are we in a git repo with no tags? If so, dump out our
38
+ # super-special version and be done with it, otherwise try to use the
39
+ # gem version.
40
+ system("git -C #{sq_git_dir} status > #{DEVNULL} 2>&1")
41
+ $? == 0 ? "0.0.0.1.ENOTAG" : gem_version(use_local_git)
42
+ end
43
+
44
+ def self.major_version(use_local_git=false, include_lite_tags=false)
45
+ ver = version(use_local_git, include_lite_tags)
46
+ v = ver.split('.')[0]
47
+
48
+ unless v =~ /^[0-9]+$/
49
+ raise ArgumentError,
50
+ "#{v} (part of #{ver.inspect}) is not a numeric version component. Abandon ship!"
51
+ end
52
+
53
+ return v.to_i
54
+ end
55
+
56
+ def self.minor_version(use_local_git=false, include_lite_tags=false)
57
+ ver = version(use_local_git, include_lite_tags)
58
+ v = ver.split('.')[1]
59
+
60
+ unless v =~ /^[0-9]+$/
61
+ raise ArgumentError,
62
+ "#{v} (part of #{ver.inspect}) is not a numeric version component. Abandon ship!"
63
+ end
64
+
65
+ return v.to_i
66
+ end
67
+
68
+ def self.patch_version(use_local_git=false, include_lite_tags=false)
69
+ ver = version(use_local_git, include_lite_tags)
70
+ v = ver.split('.')[2]
71
+
72
+ unless v =~ /^[0-9]+$/
73
+ raise ArgumentError,
74
+ "#{v} (part of #{ver.inspect}) is not a numeric version component. Abandon ship!"
75
+ end
76
+
77
+ return v.to_i
78
+ end
79
+
80
+ def self.internal_revision(use_local_git=false, include_lite_tags=false)
81
+ version(use_local_git, include_lite_tags).split('.', 4)[3].to_s
82
+ end
83
+
84
+ def self.date(use_local_git=false)
85
+ if use_local_git
86
+ unless git_available?
87
+ raise RuntimeError,
88
+ "GVB.date(use_local_git=true), but git is not installed"
89
+ end
90
+
91
+ sq_git_dir = shell_quoted_string(Dir.pwd)
92
+ else
93
+ sq_git_dir = shell_quoted_string((File.dirname(caller_file) rescue nil || Dir.pwd))
94
+ end
95
+
96
+ # Are we in a git tree?
97
+ system("git -C #{sq_git_dir} status > #{DEVNULL} 2>&1")
98
+ if $? == 0
99
+ # Yes, we're in git.
100
+
101
+ if dirty_tree?
102
+ return Time.now.strftime("%F")
103
+ else
104
+ # Clean tree. Date of last commit is needed.
105
+ return `git -C #{sq_git_dir} show --no-show-signature --format=format:%cd --date=short`.lines.first.strip
106
+ end
107
+ else
108
+ if use_local_git
109
+ raise RuntimeError,
110
+ "GVB.date(use_local_git=true) called from non-git location"
111
+ end
112
+
113
+ # Not in git; time to hit the gemspecs
114
+ if spec = caller_gemspec
115
+ return spec.date.strftime("%F")
116
+ end
117
+
118
+ raise RuntimeError,
119
+ "GVB.date called from mysterious, non-gem location."
120
+ end
121
+ end
122
+
123
+ def self.tag_version(v, release_notes = false, include_lite_tags=false)
124
+ if dirty_tree?
125
+ puts "You have uncommitted files. Refusing to tag a dirty tree."
126
+ else
127
+ if release_notes
128
+ # We need to find the tag before this one, so we can list all the commits
129
+ # between the two. This is not a trivial operation.
130
+ git_cmd = 'git describe --always'
131
+ git_cmd << ' --tags' if include_lite_tags
132
+ prev_tag = `#{git_cmd}`.strip.gsub(/-\d+-g[0-9a-f]+$/, '')
133
+
134
+ log_file = Tempfile.new('gvb')
135
+
136
+ log_file.puts <<-EOF.gsub(/^\t\t\t\t\t/, '')
137
+
138
+
139
+
140
+ # Write your release notes above. The first line should be the release name.
141
+ # To help you remember what's in here, the commits since your last release
142
+ # are listed below. This will become v#{v}
143
+ #
144
+ EOF
145
+
146
+ log_file.close
147
+ system("git log --no-show-signature --format='# %h %s' #{prev_tag}..HEAD >>#{log_file.path}")
148
+
149
+ pre_hash = Digest::SHA1.hexdigest(File.read(log_file.path))
150
+ system("git config -e -f #{log_file.path}")
151
+ if Digest::SHA1.hexdigest(File.read(log_file.path)) == pre_hash
152
+ puts "Release notes not edited; aborting"
153
+ log_file.unlink
154
+ return
155
+ end
156
+
157
+ puts "Tagging version #{v}..."
158
+ system("git tag -a -F #{log_file.path} v#{v}")
159
+ log_file.unlink
160
+ else
161
+ # Crikey this is a lot simpler
162
+ system("git tag -a -m 'Version v#{v}' v#{v}")
163
+ end
164
+
165
+ system("git push > #{DEVNULL} 2>&1")
166
+ system("git push --tags > #{DEVNULL} 2>&1")
167
+ end
168
+ end
169
+
170
+ # Calculate a version number based on the date of the most recent git commit.
171
+ #
172
+ # Return a version format string of the form `"0.YYYYMMDD.N"`, where
173
+ # `YYYYMMDD` is the date of the "top-most" commit in the tree, and `N` is
174
+ # the number of other commits also made on that date.
175
+ #
176
+ # This version format is not recommented for general use. It has benefit
177
+ # only in situations where the principles of Semantic Versioning have no
178
+ # real meaning, such as packages where there is little or no concept of
179
+ # "backwards compatibility" (eg packages which only contain images and
180
+ # other assets), or where the package can, for reasons outside that of
181
+ # the package itself, never break backwards compatibility (definitions of
182
+ # binary-packed structures shared amongst multiple systems).
183
+ #
184
+ # The format of this commit-date-based version format allows for a strictly
185
+ # monotonically-increasing version number, aligned with the progression of the
186
+ # underlying git commit log.
187
+ #
188
+ # One limitation of the format is that it doesn't deal with the issue of
189
+ # package builds made from multiple divergent trees. Unlike
190
+ # `git-describe`-based output, there is no "commit hash" identity
191
+ # included in the version string. This is because of (ludicrous)
192
+ # limitations of the Rubygems format definition -- the moment there's a
193
+ # letter in the version number, the package is considered a "pre-release"
194
+ # version. Since hashes are hex, we're boned. Sorry about that. Don't
195
+ # make builds off a branch, basically.
196
+ #
197
+ def self.commit_date_version(use_local_git = false)
198
+ if use_local_git
199
+ unless git_available?
200
+ raise RuntimeError,
201
+ "GVB.commit_date_version(use_local_git=true) called, but git isn't installed"
202
+ end
203
+
204
+ sq_git_dir = shell_quoted_string(Dir.pwd)
205
+ else
206
+ sq_git_dir = shell_quoted_string((File.dirname(caller_file) rescue nil || Dir.pwd))
207
+ end
208
+
209
+ commit_dates = `git -C #{sq_git_dir} log --format=%at`.
210
+ split("\n").
211
+ map { |l| Time.at(Integer(l)).strftime("%Y%m%d") }
212
+
213
+ if $? == 0
214
+ # We got a log; calculate our version number and we're done.
215
+ version_date = commit_dates.first
216
+ commit_count = commit_dates.select { |d| d == version_date }.length - 1
217
+ dirty_suffix = if dirty_tree?
218
+ ".dirty.#{Time.now.strftime("%Y%m%d.%H%M%S")}"
219
+ else
220
+ ""
221
+ end
222
+
223
+ return "0.#{version_date}.#{commit_count}#{dirty_suffix}"
224
+ end
225
+
226
+ # git failed us; either we're not in a git repo or else it's a git
227
+ # repo that's not got any commits.
228
+
229
+ # Are we in a git repo with no tags? If so, dump out our
230
+ # super-special version and be done with it.
231
+ system("git -C #{sq_git_dir} status > #{DEVNULL} 2>&1")
232
+ $? == 0 ? "0.0.0.1.ENOCOMMITS" : gem_version(use_local_git)
233
+ end
234
+
235
+ private
236
+
237
+ def self.git_available?
238
+ system("git --version > #{DEVNULL} 2>&1")
239
+
240
+ $? == 0
241
+ end
242
+
243
+ def self.dirty_tree?
244
+ # Are we in a dirty, dirty tree?
245
+ system("! git diff --no-ext-diff --quiet --exit-code 2> #{DEVNULL} || ! git diff-index --cached --quiet HEAD 2> #{DEVNULL}")
246
+
247
+ $? == 0
248
+ end
249
+
250
+ def self.caller_file
251
+ # Who called us? Because this method gets called from other methods
252
+ # within this file, we can't just look at Gem.location_of_caller, but
253
+ # instead we need to parse the caller stack ourselves to find which
254
+ # gem we're trying to version all over.
255
+ Pathname(
256
+ caller.
257
+ map { |l| l.split(':')[0] }.
258
+ find { |l| l != __FILE__ }
259
+ ).realpath.to_s rescue nil
260
+ end
261
+
262
+ def self.caller_gemspec
263
+ cf = caller_file or return nil
264
+
265
+ # Grovel through all the loaded gems to try and find the gem
266
+ # that contains the caller's file.
267
+ Gem.loaded_specs.values.each do |spec|
268
+ search_dirs = spec.require_paths.map { |d| "#{spec.full_gem_path}/#{d}" } +
269
+ [File.join(spec.full_gem_path, spec.bindir)]
270
+ search_dirs.map! do |d|
271
+ begin
272
+ Pathname(d).realpath.to_s
273
+ rescue Errno::ENOENT
274
+ nil
275
+ end
276
+ end.compact!
277
+
278
+ if search_dirs.find { |d| cf.index(d) == 0 }
279
+ return spec
280
+ end
281
+ end
282
+
283
+ raise VersionUnobtainable,
284
+ "Unable to find gemspec for caller file #{cf}"
285
+ end
286
+
287
+ def self.gem_version(use_local_git = false)
288
+ if use_local_git
289
+ raise VersionUnobtainable,
290
+ "Unable to determine version from local git repo. This should never happen."
291
+ end
292
+
293
+ if spec = caller_gemspec
294
+ return spec.version.to_s
295
+ else
296
+ # If we got here, something went *badly* wrong -- presumably, we
297
+ # weren't called from within a loaded gem, and so we've got *no*
298
+ # idea what's going on. Time to bail!
299
+ if git_available?
300
+ raise VersionUnobtainable,
301
+ "GVB.version(#{use_local_git.inspect}) failed, and I really don't know why."
302
+ else
303
+ raise VersionUnobtainable,
304
+ "GVB.version(#{use_local_git.inspect}) failed; perhaps you need to install git?"
305
+ end
306
+ end
307
+ end
308
+
309
+ def self.shell_quoted_string(dir_string)
310
+ if Gem.win_platform?
311
+ return "\"#{dir_string}\""
312
+ else
313
+ # Shell Quoted, for your convenience
314
+ return "'#{dir_string.gsub("'", "'\\''")}'"
315
+ end
316
+ end
317
+
318
+ private_class_method :shell_quoted_string
319
+
320
+ end
321
+
322
+ GVB = GitVersionBump unless defined? GVB
323
+
324
+ require 'git-version-bump/version'