diff --git a/Gemfile b/Gemfile index 88f91d8ed..62da0fee1 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'httparty' gem 'bullet' gem 'pry-rails' - +gem 'rugged', git: 'git://github.com/libgit2/rugged.git', branch: 'development', submodules: true gem 'colorize' group :assets do diff --git a/Gemfile.lock b/Gemfile.lock index 2a920dbfb..17c2c4a22 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,11 @@ +GIT + remote: git://github.com/libgit2/rugged.git + revision: 5f0f3fad0e8af98a10812673e8316b78e5075437 + branch: development + submodules: true + specs: + rugged (0.19.0) + GEM remote: https://rubygems.org/ specs: @@ -268,6 +276,7 @@ DEPENDENCIES resque resque-retry rspec-rails + rugged! rvm-capistrano sass sass-rails diff --git a/lib/git_blame.rb b/lib/git_blame.rb index 4f5c388c0..0a24bce43 100644 --- a/lib/git_blame.rb +++ b/lib/git_blame.rb @@ -12,11 +12,21 @@ def emails_in_branch(build) end def changes_since_last_green(build) - output = GitRepo.inside_repo(build.repository) do - # TODO: Push this down into GitRepo and integration test it. - Cocaine::CommandLine.new("git log --no-merges --format='::!::%H|%an <%ae>|%ad|%B::!::' '#{build.previous_successful_build.try(:ref)}...#{build.ref}'").run + GitRepo.inside_repo(build.repository) do |repo| + last_green_ref = build.previous_successful_build.try(:ref) + current_ref = build.ref + walker = Rugged::Walker.new(repo) + walker.sorting(Rugged::SORT_REVERSE) + if last_green_ref + walker.push(last_green_ref) + walker.hide(last_green_ref) + end + walker.push(current_ref) + walker.map do |commit| + author = commit.author + {hash: commit.oid, author: "#{author[:name]} <#{author[:email]}>", date: commit.time, message: commit.message.gsub("\n", " ")} + end end - parse_git_changes(output) end def changes_in_branch(build) diff --git a/lib/git_repo.rb b/lib/git_repo.rb index d47b66978..eb442ab5c 100644 --- a/lib/git_repo.rb +++ b/lib/git_repo.rb @@ -61,7 +61,7 @@ def inside_repo(repository, sync: true) Dir.chdir(cached_repo_path) do synchronize_with_remote('origin') if sync - yield + yield Rugged::Repository.new(cached_repo_path) end end diff --git a/spec/lib/git_blame_spec.rb b/spec/lib/git_blame_spec.rb index 86d712b26..3c6febff6 100644 --- a/spec/lib/git_blame_spec.rb +++ b/spec/lib/git_blame_spec.rb @@ -69,25 +69,53 @@ end describe "#changes_since_last_green" do + let!(:last_successful_build) { FactoryGirl.create(:build, state: :succeeded, project: project, ref: first_commit) } + let(:build) { FactoryGirl.create(:build, project: project, ref: head) } + let(:head) { repo.last_commit.oid } + let(:first_commit) { repo.last_commit.parents.first.parents.first.oid } + let(:commiter) { {email: "bilbo@theshire.com", name: 'Bilbo', time: Time.now} } + let(:repo) do + repo = fixture_repo + 3.times { |i| commit_to(repo, commiter, commit_message.call(i)) } + repo + end + let!(:frozen_time) { freeze_time! } subject { GitBlame.changes_since_last_green(build) } + let(:commit_message) { Proc.new {|i| "Making commit number #{i+1} to the repo!" }} before do GitBlame.unstub(:changes_since_last_green) + GitRepo.stub(:inside_repo).and_yield(repo) end - it "should parse the git log message and return a hash of information" do - GitRepo.stub(:inside_repo).and_return("::!::817b88be7488cab5e4f9d9975222db80d8bceb3b|User One |Fri Oct 19 17:43:47 2012 -0700|this is my commit message::!::") - git_changes = subject - git_changes.first[:hash].should == "817b88be7488cab5e4f9d9975222db80d8bceb3b" - git_changes.first[:author].should == "User One " - git_changes.first[:date].should == "Fri Oct 19 17:43:47 2012 -0700" - git_changes.first[:message].should == "this is my commit message" + context "with no prior green build" do + let!(:last_successful_build) { nil } + it "returns all the commits" do + git_changes = subject + git_changes.size.should == 3 + end end - it "should strip new lines in the commit message" do - GitRepo.stub(:inside_repo).and_return("::!::817b88|User One|Fri Oct 19|this is my commit message\nanother line::!::") + it "parses the git log message and return a hash of information" do git_changes = subject - git_changes.first[:message].should == "this is my commit message another line" + git_changes.size.should == 2 + git_changes.first[:hash].should_not == head + git_changes.first[:hash].should_not == first_commit + git_changes.first[:message].should == "Making commit number 2 to the repo!" + + git_changes.last[:hash].should == head + git_changes.last[:author].should == "Bilbo " + git_changes.last[:date].to_i.should == frozen_time.to_i + git_changes.last[:message].should == "Making commit number 3 to the repo!" + end + + context "with new lines in the commit message" do + let(:commit_message) { Proc.new {|i| "Making commit number #{i+1} to the\nrepo!" }} + + it "strips the new lines in the commit message" do + git_changes = subject + git_changes.last[:message].should == "Making commit number 3 to the repo!" + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8e648ee4b..ab1d0c33f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,7 @@ require 'factory_girl' require 'capybara/rspec' require 'git_blame' +require 'securerandom' FIXTURE_PATH = Rails.root.join('spec', 'fixtures') @@ -14,10 +15,54 @@ # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} +module SpecHelpers + def freeze_time!(frozen_time = Time.now) + Time.stub(:now).and_return(frozen_time) + frozen_time + end +end + +module RepositoryFixture + def fixture_repo + repo_path = Rails.root.join("tmp", SecureRandom.hex).to_s + repo = Rugged::Repository.init_at(repo_path) + RepositoryFixture.add_repository(repo_path) + repo + end + + def commit_to(repo, commiter, message) + oid = repo.write("This is a blob. #{SecureRandom.hex}", :blob) + builder = Rugged::Tree::Builder.new + builder << { type: :blob, name: "README.md", oid: oid, filemode: 0100644 } + + options = {} + options[:tree] = builder.write(repo) + + options[:author] = commiter + options[:committer] = commiter + options[:message] = message + options[:parents] = repo.empty? ? [] : [ repo.head.target ].compact + options[:update_ref] = 'HEAD' + + Rugged::Commit.create(repo, options) + end + + def self.add_repository(path) + @repos ||= [] + @repos << path + end + + def self.reset_repositories + created_repos = @repos || [] + @repos = [] + created_repos + end +end RSpec.configure do |config| config.mock_with :rspec - + config.include(SpecHelpers) + config.include(RepositoryFixture) # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = FIXTURE_PATH @@ -32,7 +77,6 @@ config.before :each do WebMock.disable_net_connect! JobBase.stub(:enqueue_in) - GitBlame.stub(:git_names_and_emails_since_last_green).and_return("") GitBlame.stub(:git_names_and_emails_in_branch).and_return("") GitBlame.stub(:changes_since_last_green).and_return([]) @@ -43,6 +87,12 @@ ActionMailer::Base.deliveries.clear end + config.after :each do + RepositoryFixture.reset_repositories.each do |path| + FileUtils.rm_rf(path) + end + end + config.before(:suite) do test_repo = Rails.root.join('tmp', 'build-partition', 'test-repo-cache') unless File.exist?(test_repo)