Class: GemKit::Release::Gate

Inherits:
Object
  • Object
show all
Defined in:
lib/gem_kit/release/gate.rb

Overview

The checks, in one place. Both the bump and the release ask the same two questions of a target version, and neither should be hand-rolling them:

Is anything promised to disappear in this version still here?
Does the changelog document this version?

Every method returns a list of human-readable problems. Empty means pass, which makes the callers trivial and lets CI use the same object.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ Gate

Returns a new instance of Gate.



16
17
18
# File 'lib/gem_kit/release/gate.rb', line 16

def initialize(project)
  @project = project
end

Instance Attribute Details

#projectObject (readonly)

Returns the value of attribute project.



14
15
16
# File 'lib/gem_kit/release/gate.rb', line 14

def project
  @project
end

Instance Method Details

#bump_problems(version) ⇒ Object

Everything standing between the project and bumping to version. Only the deprecation deadline applies — the changelog for a version cannot exist before the version does.



90
91
92
# File 'lib/gem_kit/release/gate.rb', line 90

def bump_problems(version)
  deprecation_problems(version).map { |problem| "deprecation due in #{version}: #{problem}" }
end

#changelog_problems(version = nil) ⇒ Object

Changelog format, plus "is there an entry for this version?" when one is given. Without a version this is the format check alone, which is the useful thing to run on every push.



41
42
43
44
45
46
47
48
# File 'lib/gem_kit/release/gate.rb', line 41

def changelog_problems(version = nil)
  changelog = Changelog.new(
    File.exist?(project.changelog_path) ? File.read(project.changelog_path) : nil,
    path: project.changelog_path,
  )

  version ? changelog.release_problems(version) : changelog.problems
end

#deprecation_problems(version) ⇒ Object

Deprecations whose deadline has arrived at version. This is the check that has to run before a bump: bumping onto a deadline is what breaks the promise, so the bump is the last moment anyone can be stopped.



23
24
25
26
27
28
29
# File 'lib/gem_kit/release/gate.rb', line 23

def deprecation_problems(version)
  project.load!

  GemKit::Deprecate.pending(version).map do |entry|
    "#{entry.removed_in.to_s.ljust(8)} #{entry}#{entry.declared_at ? "\n#{" " * 9}#{entry.declared_at}" : ""}"
  end
end

#git?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/gem_kit/release/gate.rb', line 70

def git?
  Dir.chdir(project.root) { system("git rev-parse --git-dir >/dev/null 2>&1") }
end

#release_problems(version = project.version, allow_dirty: false) ⇒ Object

Everything standing between the project and releasing version.



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/gem_kit/release/gate.rb', line 75

def release_problems(version = project.version, allow_dirty: false)
  problems = []

  changelog_problems(version).each { |problem| problems << problem }
  deprecation_problems(version).each do |problem|
    problems << "deprecation due in #{version}: #{problem}"
  end
  working_tree_problems.each { |problem| problems << problem } unless allow_dirty

  problems
end

#upcoming_deprecations(version) ⇒ Object

Deprecations still inside their grace period — worth printing on the way past, not worth blocking on.



33
34
35
36
# File 'lib/gem_kit/release/gate.rb', line 33

def upcoming_deprecations(version)
  project.load!
  GemKit::Deprecate.upcoming(version)
end

#working_tree_problemsObject

What is in the working tree but not in git. A gem built from an uncommitted tree is a gem whose source exists nowhere — and bump and changelog --write leave exactly two such files behind, which is precisely the moment someone reaches for release.

A directory that is not a git repository is not a problem: this gate has nothing to say about it.



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/gem_kit/release/gate.rb', line 57

def working_tree_problems
  return [] unless git?

  dirty = Dir.chdir(project.root) { `git status --porcelain`.lines.map(&:strip) }
  return [] if dirty.empty?

  ["#{dirty.size} uncommitted change(s) — the gem would match nothing in git:",
   *dirty.first(10).map { |line| "  #{line}" },
   *(dirty.size > 10 ? ["  … and #{dirty.size - 10} more"] : []),
   "",
   "The bump and the changelog belong in one commit. Or pass --allow-dirty."]
end