Skip to content

Commit

Permalink
Implement GitRevisionFilter
Browse files Browse the repository at this point in the history
When the airbrake-ruby config doesn't specify `app_version` (99% of Rails apps),
then `GitRevisionFilter` tries to find current revision of the app and attach it
as `context/version`.
  • Loading branch information
kyrylo committed Jun 26, 2018
1 parent 510f199 commit ca91338
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Airbrake Ruby Changelog

### master

* Added `GitRevisionFilter`
([#333](https://github.com/airbrake/airbrake-ruby/pull/333))

### [v2.10.0][v2.10.0] (May 3, 2018)

* Added the `versions` option
Expand Down
1 change: 1 addition & 0 deletions lib/airbrake-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require 'airbrake-ruby/filters/context_filter'
require 'airbrake-ruby/filters/exception_attributes_filter'
require 'airbrake-ruby/filters/dependency_filter'
require 'airbrake-ruby/filters/git_revision_filter'
require 'airbrake-ruby/filter_chain'
require 'airbrake-ruby/notifier'
require 'airbrake-ruby/code_hunk'
Expand Down
57 changes: 57 additions & 0 deletions lib/airbrake-ruby/filters/git_revision_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module Airbrake
module Filters
# Attaches current git revision to `context`.
# @api private
# @since v2.11.0
class GitRevisionFilter
# @return [Integer]
attr_reader :weight

# @return [String]
PREFIX = 'ref: '.freeze

def initialize(root_directory)
@git_path = File.join(root_directory, '.git')
@weight = 120
end

# @macro call_filter
def call(notice)
return if notice[:context].key?(:version)
return unless File.exist?(@git_path)

revision = find_revision
notice[:context][:version] = revision if revision
end

private

def find_revision
head_path = File.join(@git_path, 'HEAD')
return unless File.exist?(head_path)

head = File.read(head_path)
return head unless head.start_with?(PREFIX)
head = head.chomp[PREFIX.size..-1]

ref_path = File.join(@git_path, head)
return File.read(ref_path).chomp if File.exist?(ref_path)

find_from_packed_refs(head)
end

def find_from_packed_refs(head)
packed_refs_path = File.join(@git_path, 'packed-refs')
return head unless File.exist?(packed_refs_path)

File.readlines(packed_refs_path).each do |line|
next if %w[# ^].include?(line[0])
next unless (parts = line.split(' ')).size == 2
return parts.first if parts.last == head
end

nil
end
end
end
end
6 changes: 6 additions & 0 deletions lib/airbrake-ruby/notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def clean_backtrace
clean_bt
end

# rubocop:disable Metrics/AbcSize
def add_default_filters
if (whitelist_keys = @config.whitelist_keys).any?
@filter_chain.add_filter(
Expand All @@ -165,6 +166,11 @@ def add_default_filters
@filter_chain.add_filter(
Airbrake::Filters::RootDirectoryFilter.new(root_directory)
)

@filter_chain.add_filter(
Airbrake::Filters::GitRevisionFilter.new(root_directory)
)
end
# rubocop:enable Metrics/AbcSize
end
end
128 changes: 128 additions & 0 deletions spec/filters/git_revision_filter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
require 'spec_helper'

RSpec.describe Airbrake::Filters::GitRevisionFilter do
subject { described_class.new('root/dir') }

let(:notice) do
Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
end

context "when context/version is defined" do
it "doesn't attach anything to context/version" do
notice[:context][:version] = '1.2.3'
subject.call(notice)
expect(notice[:context][:version]).to eq('1.2.3')
end
end

context "when .git directory doesn't exist" do
it "doesn't attach anything to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to be_nil
end
end

context "when .git directory exists" do
before do
expect(File).to receive(:exist?).with('root/dir/.git').and_return(true)
end

context "and when HEAD doesn't exist" do
before do
expect(File).to receive(:exist?).with('root/dir/.git/HEAD').and_return(false)
end

it "doesn't attach anything to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to be_nil
end
end

context "and when HEAD exists" do
before do
expect(File).to receive(:exist?).with('root/dir/.git/HEAD').and_return(true)
end

context "and also when HEAD doesn't start with 'ref: '" do
before do
expect(File).to(
receive(:read).with('root/dir/.git/HEAD').and_return('refs/foo')
)
end

it "attaches the content of HEAD to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to eq('refs/foo')
end
end

context "and also when HEAD starts with 'ref: " do
before do
expect(File).to(
receive(:read).with('root/dir/.git/HEAD').and_return("ref: refs/foo\n")
)
end

context "when the ref exists" do
before do
expect(File).to(
receive(:exist?).with('root/dir/.git/refs/foo').and_return(true)
)
expect(File).to(
receive(:read).with('root/dir/.git/refs/foo').and_return("d34db33f\n")
)
end

it "attaches the revision from the ref to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to eq('d34db33f')
end
end

context "when the ref doesn't exist" do
before do
expect(File).to(
receive(:exist?).with('root/dir/.git/refs/foo').and_return(false)
)
end

context "and when '.git/packed-refs' exists" do
before do
expect(File).to(
receive(:exist?).with('root/dir/.git/packed-refs').and_return(true)
)
expect(File).to(
receive(:readlines).with('root/dir/.git/packed-refs').and_return(
[
"# pack-refs with: peeled fully-peeled\n",
"ccb316eecff79c7528d1ad43e5fa165f7a44d52e refs/tags/v3.0.30\n",
"^d358900f73ee5bfd6ca3a592cf23ac6e82df83c1",
"d34db33f refs/foo\n"
]
)
)
end

it "attaches the revision from 'packed-refs' to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to eq('d34db33f')
end
end

context "and when '.git/packed-refs' doesn't exist" do
before do
expect(File).to(
receive(:exist?).with('root/dir/.git/packed-refs').and_return(false)
)
end

it "attaches the content of HEAD to context/version" do
subject.call(notice)
expect(notice[:context][:version]).to eq('refs/foo')
end
end
end
end
end
end
end

0 comments on commit ca91338

Please sign in to comment.