module GitVersionBump

Constants

DATE
INTERNAL_REVISION
MAJOR_VERSION
MINOR_VERSION
PATCH_VERSION
VERSION

Public Class Methods

commit_date_version(use_local_git = false) click to toggle source

Calculate a version number based on the date of the most recent git commit.

Return a version format string of the form `“0.YYYYMMDD.N”`, where `YYYYMMDD` is the date of the “top-most” commit in the tree, and `N` is the number of other commits also made on that date.

This version format is not recommented for general use. It has benefit only in situations where the principles of Semantic Versioning have no real meaning, such as packages where there is little or no concept of “backwards compatibility” (eg packages which only contain images and other assets), or where the package can, for reasons outside that of the package itself, never break backwards compatibility (definitions of binary-packed structures shared amongst multiple systems).

The format of this commit-date-based version format allows for a strictly monotonically-increasing version number, aligned with the progression of the underlying git commit log.

One limitation of the format is that it doesn't deal with the issue of package builds made from multiple divergent trees. Unlike `git-describe`-based output, there is no “commit hash” identity included in the version string. This is because of (ludicrous) limitations of the Rubygems format definition – the moment there's a letter in the version number, the package is considered a “pre-release” version. Since hashes are hex, we're boned. Sorry about that. Don't make builds off a branch, basically.

# File lib/git-version-bump.rb, line 192
def self.commit_date_version(use_local_git = false)
        if use_local_git
                unless git_available?
                        raise RuntimeError,
                              "GVB.commit_date_version(use_local_git=true) called, but git isn't installed"
                end

                sq_git_dir = "'#{Dir.pwd.gsub("'", "'\\''")}'"
        else
                # Shell Quoted, for your convenience
                sq_git_dir = "'" + (File.dirname(caller_file) rescue nil || Dir.pwd).gsub("'", "'\\''") + "'"
        end

        commit_dates = %x`git -C #{sq_git_dir} log --format=%at`.
                       split("\n").
                       map { |l| Time.at(Integer(l)).strftime("%Y%m%d") }

        if $? == 0
                # We got a log; calculate our version number and we're done.
                version_date = commit_dates.first
                commit_count = commit_dates.select { |d| d == version_date }.length - 1
                dirty_suffix = if dirty_tree?
                        ".dirty.#{Time.now.strftime("%Y%m%d.%H%M%S")}"
                else
                        ""
                end

                return "0.#{version_date}.#{commit_count}#{dirty_suffix}"
        end

        # git failed us; either we're not in a git repo or else it's a git
        # repo that's not got any commits.

        # Are we in a git repo with no tags?  If so, dump out our
        # super-special version and be done with it.
        system("git -C #{sq_git_dir} status >/dev/null 2>&1")
        $? == 0 ? "0.0.0.1.ENOCOMMITS" : gem_version(use_local_git)
end
date(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 80
def self.date(use_local_git=false)
        if use_local_git
                unless git_available?
                        raise RuntimeError,
                              "GVB.date(use_local_git=true), but git is not installed"
                end

                sq_git_dir = "'#{Dir.pwd.gsub("'", "'\\''")}'"
        else
                # Shell Quoted, for your convenience
                sq_git_dir = "'" + (File.dirname(caller_file) rescue nil || Dir.pwd).gsub("'", "'\\''") + "'"
        end

        # Are we in a git tree?
        system("git -C #{sq_git_dir} status >/dev/null 2>&1")
        if $? == 0
                # Yes, we're in git.

                if dirty_tree?
                        return Time.now.strftime("%F")
                else
                        # Clean tree.  Date of last commit is needed.
                        return %x`git -C #{sq_git_dir} show --format=format:%cd --date=short | head -n 1`.strip
                end
        else
                if use_local_git
                        raise RuntimeError,
                              "GVB.date(use_local_git=true) called from non-git location"
                end

                # Not in git; time to hit the gemspecs
                if spec = caller_gemspec
                        return spec.date.strftime("%F")
                end

                raise RuntimeError,
                      "GVB.date called from mysterious, non-gem location."
        end
end
internal_revision(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 76
def self.internal_revision(use_local_git=false)
        version(use_local_git).split('.', 4)[3].to_s
end
major_version(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 40
def self.major_version(use_local_git=false)
        ver = version(use_local_git)
        v   = ver.split('.')[0]

        unless v =~ /^[0-9]+$/
                raise ArgumentError,
                        "#{v} (part of #{ver.inspect}) is not a numeric version component.  Abandon ship!"
        end

        return v.to_i
end
minor_version(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 52
def self.minor_version(use_local_git=false)
        ver = version(use_local_git)
        v   = ver.split('.')[1]

        unless v =~ /^[0-9]+$/
                raise ArgumentError,
                        "#{v} (part of #{ver.inspect}) is not a numeric version component.  Abandon ship!"
        end

        return v.to_i
end
patch_version(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 64
def self.patch_version(use_local_git=false)
        ver = version(use_local_git)
        v   = ver.split('.')[2]

        unless v =~ /^[0-9]+$/
                raise ArgumentError,
                        "#{v} (part of #{ver.inspect}) is not a numeric version component.  Abandon ship!"
        end

        return v.to_i
end
tag_version(v, release_notes = false) click to toggle source
# File lib/git-version-bump.rb, line 120
        def self.tag_version(v, release_notes = false)
                if dirty_tree?
                        puts "You have uncommitted files.  Refusing to tag a dirty tree."
                else
                        if release_notes
                                # We need to find the tag before this one, so we can list all the commits
                                # between the two.  This is not a trivial operation.
                                prev_tag = %x`git describe --always`.strip.gsub(/-\d+-g[0-9a-f]+$/, '')

                                log_file = Tempfile.new('gvb')

                                log_file.puts "


                                        # Write your release notes above.  The first line should be the release name.
                                        # To help you remember what's in here, the commits since your last release
                                        # are listed below. This will become v#{v}
                                        #
".gsub(/^\t\t\t\t\t/, '')

                                log_file.close
                                system("git log --format='# %h  %s' #{prev_tag}..HEAD >>#{log_file.path}")

                                pre_hash = Digest::SHA1.hexdigest(File.read(log_file.path))
                                system("git config -e -f #{log_file.path}")
                                if Digest::SHA1.hexdigest(File.read(log_file.path)) == pre_hash
                                        puts "Release notes not edited; aborting"
                                        log_file.unlink
                                        return
                                end

                                puts "Tagging version #{v}..."
                                system("git tag -a -F #{log_file.path} v#{v}")
                                log_file.unlink
                        else
                                # Crikey this is a lot simpler
                                system("git tag -a -m 'Version v#{v}' v#{v}")
                        end

                        system("git push >/dev/null 2>&1")
                        system("git push --tags >/dev/null 2>&1")
                end
        end
version(use_local_git=false) click to toggle source
# File lib/git-version-bump.rb, line 8
def self.version(use_local_git=false)
        if use_local_git
                unless git_available?
                        raise RuntimeError,
                              "GVB.version(use_local_git=true) called, but git isn't installed"
                end

                sq_git_dir = "'#{Dir.pwd.gsub("'", "'\\''")}'"
        else
                # Shell Quoted, for your convenience
                sq_git_dir = "'" + (File.dirname(caller_file) rescue nil || Dir.pwd).gsub("'", "'\\''") + "'"
        end

        git_ver = %x`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]' 2>/dev/null`.
                    strip.
                    gsub(/^v/, '').
                    gsub('-', '.')

        # If git returned success, then it gave us a described version.
        # Success!
        return git_ver if $? == 0

        # git failed us; we're either not in a git repo or else we've never
        # tagged anything before.

        # Are we in a git repo with no tags?  If so, dump out our
        # super-special version and be done with it, otherwise try to use the
        # gem version.
        system("git -C #{sq_git_dir} status >/dev/null 2>&1")
         "0.0.0.1.ENOTAG" if $? == 0 ? "0.0.0.1.ENOTAG" : gem_version(use_local_git)
end

Private Class Methods

caller_file() click to toggle source
# File lib/git-version-bump.rb, line 246
def self.caller_file
        # Who called us?  Because this method gets called from other methods
        # within this file, we can't just look at Gem.location_of_caller, but
        # instead we need to parse the caller stack ourselves to find which
        # gem we're trying to version all over.
        Pathname(
          caller.
          map  { |l| l.split(':')[0] }.
          find { |l| l != __FILE__ }
        ).realpath.to_s rescue nil
end
caller_gemspec() click to toggle source
# File lib/git-version-bump.rb, line 258
def self.caller_gemspec
        cf = caller_file or return nil

        # Grovel through all the loaded gems to try and find the gem
        # that contains the caller's file.
        Gem.loaded_specs.values.each do |spec|
                search_dirs = spec.require_paths.map { |d| "#{spec.full_gem_path}/#{d}" } +
                              [File.join(spec.gem_dir, spec.bindir)]
                search_dirs.map! do |d|
                        begin
                                Pathname(d).realpath.to_s
                        rescue Errno::ENOENT
                                nil
                        end
                end.compact!

                if search_dirs.find { |d| cf.index(d) == 0 }
                        return spec
                end
        end

        raise VersionUnobtainable,
              "Unable to find gemspec for caller file #{cf}"
end
dirty_tree?() click to toggle source
# File lib/git-version-bump.rb, line 239
def self.dirty_tree?
        # Are we in a dirty, dirty tree?
        system("! git diff --no-ext-diff --quiet --exit-code 2>/dev/null || ! git diff-index --cached --quiet HEAD 2>/dev/null")

        $? == 0
end
gem_version(use_local_git = false) click to toggle source
# File lib/git-version-bump.rb, line 283
def self.gem_version(use_local_git = false)
        if use_local_git
                raise VersionUnobtainable,
                      "Unable to determine version from local git repo.  This should never happen."
        end

        if spec = caller_gemspec
                return spec.version.to_s
        else
                # If we got here, something went *badly* wrong -- presumably, we
                # weren't called from within a loaded gem, and so we've got *no*
                # idea what's going on.  Time to bail!
                if git_available?
                        raise VersionUnobtainable,
                              "GVB.version(#{use_local_git.inspect}) failed, and I really don't know why."
                else
                        raise VersionUnobtainable,
                              "GVB.version(#{use_local_git.inspect}) failed; perhaps you need to install git?"
                end
        end
end
git_available?() click to toggle source
# File lib/git-version-bump.rb, line 233
def self.git_available?
        system("git --version >/dev/null 2>&1")

        $? == 0
end