class Aufgaben::Release

Attributes

changelog[RW]
default_branch[RW]
files[RW]
new_version[RW]

Public Class Methods

new(name = :release, depends: []) { |self| ... } click to toggle source
Calls superclass method Aufgaben::Base::new
# File lib/aufgaben/release.rb, line 11
def initialize(name = :release, depends: [])
  super(name, depends: depends)
  @default_branch = "main"
  @changelog = "CHANGELOG.md"
  @files = []
  @new_version = nil

  yield self if block_given?
  define
end

Private Instance Methods

add_changelog(with:) click to toggle source
# File lib/aufgaben/release.rb, line 128
    def add_changelog(with:)
      # for test only
      git_remote_command = ENV["AUFGABEN_GIT_REMOTE_COMMAND"] || "git remote --verbose"

      repo = nil
      `#{git_remote_command}`.lines(chomp: true).each do |line|
        line.match(%r{git@github\.com:([^/]+/[^/]+)\.git}) do |m|
          repo = m.captures.first
          break
        end
        line.match(%r{https://github\.com/([^/]+/[^/]+)\.git}) do |m|
          repo = m.captures.first
          break
        end
      end

      abort "No remote repositories on GitHub!" unless repo

      File.write(changelog, <<~CONTENT, encoding: Encoding::UTF_8)
        # Changelog

        All notable changes to this project will be documented in this file.

        ## Unreleased

        [Full diff](https://github.com/#{repo}/compare/#{with}...HEAD)

        ## #{with}

        Initial release.
      CONTENT

      msg "'#{changelog}' is added."
    end
answer_yes?() click to toggle source
# File lib/aufgaben/release.rb, line 213
def answer_yes?
  puts
  loop do
    print "> Perform the release of version #{colored_new_version}? [y/N] "
    answer = STDIN.gets.chomp.downcase
    if answer == "y"
      return true
    elsif answer == "n" || answer == ""
      return false
    else
      puts "> Please type 'y' or 'n'."
      next
    end
  end
end
check_committed_files() click to toggle source
# File lib/aufgaben/release.rb, line 163
    def check_committed_files
      expected = [*expanded_files, changelog].sort
      actual = `git diff --name-only HEAD^...HEAD`.lines(chomp: true).sort

      if expected != actual
        abort <<~MSG
          The committed files are not what was expected.
          Expected: #{expected}
          Actual:   #{actual}
        MSG
      end
    end
colored_new_version() click to toggle source
# File lib/aufgaben/release.rb, line 229
def colored_new_version
  Color.new(new_version).green
end
current_version() click to toggle source
# File lib/aufgaben/release.rb, line 97
def current_version
  @current_version ||= `git describe --abbrev=0 --tags`.chomp
end
define() click to toggle source
# File lib/aufgaben/release.rb, line 24
def define
  desc "Perform a release work"
  task name, [:version] => depends do |_task, args|
    self.new_version = args[:version] if args[:version]

    unless new_version
      abort "Specify a new version. Usage: rake #{name}[version]"
    end

    msg "Start releasing..."

    msg "If you want to do this on dry-run mode, set 'DRY_RUN=1'." unless dry_run?

    git "checkout", default_branch, "--quiet"
    git "pull", "origin", default_branch, "--quiet"
    sh "bundle", "install", "--quiet"

    if current_version.empty?
      initial = true
    else
      initial = false
      unless Gem::Version.new(current_version) < Gem::Version.new(new_version)
        abort "Invalid version! current=#{current_version} new=#{new_version}"
      end
    end

    git "diff", "--exit-code", "--quiet" do |ok|
      abort "Uncommitted changes found!" unless ok
    end

    if initial
      msg "Releasing a new version: #{colored_new_version}"
    else
      msg "Releasing a new version: #{current_version} -> #{colored_new_version}"
    end

    git "--no-pager", "log", "--format=%C(auto)%h %Creset%s", "#{current_version}..HEAD"

    each_file do |file|
      update_version_in file, write: false
    end

    if dry_run?
      msg "This is a dry-run mode. No actual changes. Next, run this without `DRY_RUN=1`."
    else
      if interactive?
        answer_yes? or abort "Aborted."
      end

      if File.exist? changelog
        update_changelog from: current_version, to: new_version
      else
        add_changelog with: new_version
      end
      git "add", changelog

      each_file do |file|
        update_version_in file
        git "add", file
      end

      git "commit", "--quiet", "--message", "Version #{new_version}"
      git "tag", "--annotate", "--message", "Version #{new_version}", new_version
      git "show", "--pretty"

      check_committed_files

      git_push = Color.new("git push --follow-tags").green
      msg "The tag '#{colored_new_version}' is added. Run '#{git_push}'."
    end
  end
end
each_file(&block) click to toggle source
# File lib/aufgaben/release.rb, line 180
def each_file(&block)
  expanded_files.each(&block)
end
expanded_files() click to toggle source
# File lib/aufgaben/release.rb, line 176
def expanded_files
  @expanded_files ||= Dir.glob(files, File::FNM_EXTGLOB)
end
interactive?() click to toggle source
# File lib/aufgaben/release.rb, line 209
def interactive?
  !ENV["NONINTERACTIVE"]
end
msg(text) click to toggle source
# File lib/aufgaben/release.rb, line 203
def msg(text)
  puts
  puts "> #{text}"
  puts
end
update_changelog(from:, to:) click to toggle source
# File lib/aufgaben/release.rb, line 101
def update_changelog(from:, to:)
  compare_url = nil
  new_lines = File.readlines(changelog, chomp: true).map do |line|
    case
    when line == "## Unreleased"
      "## #{to}"
    when line.include?("#{from}...HEAD")
      line.match(%r{\((?<url>.+)/#{from}...HEAD\)}) do |m|
        compare_url = m[:url]
      end
      line.sub("#{from}...HEAD", "#{from}...#{to}")
    else
      line
    end
  end
  new_lines.insert(
    4,
    "## Unreleased",
    "",
    "[Full diff](#{compare_url}/#{to}...HEAD)",
    "",
  )
  File.write(changelog, new_lines.join("\n") + "\n")

  msg "'#{changelog}' is updated."
end
update_version_in(file, write: true) click to toggle source
# File lib/aufgaben/release.rb, line 184
def update_version_in(file, write: true)
  content = File.read(file)

  current_version_pattern = Regexp.new('\b' + Regexp.escape(current_version) + '\b')
  unless current_version_pattern.match?(content)
    abort "The current version '#{current_version}' is not found in '#{file}'!"
  end

  new_content = content.gsub(current_version_pattern, new_version)
  if content == new_content
    abort "No changes in '#{file}'"
  end

  if write
    File.write(file, new_content)
    msg "'#{file}' is updated."
  end
end