diff --git a/lib/build_strategies/production_build_strategy.rb b/lib/build_strategies/production_build_strategy.rb index 08b706489..1b6ace9a5 100644 --- a/lib/build_strategies/production_build_strategy.rb +++ b/lib/build_strategies/production_build_strategy.rb @@ -7,9 +7,14 @@ class << self # The primary function of promote_build is to update the branches specified # in on_green_update field of Repository. # - # A feature of promote build is that it will not cause the promotion ref to move - # backwards. For instance, if build 1 finishes after build 2, we don't cause the promotion ref to move - # backwards by overwriting promotion_ref with build 1 + # A feature of promote_build is that it will not cause the promotion ref to + # move backwards. For instance, if build 1 finishes after build 2, we don't + # cause the promotion ref to move backwards by overwriting promotion_ref + # with build 1 + # + # promote_build does use a force push in order to overwrite experimental + # branches that may have been manually placed on the promotion ref by a + # developer for testing. def promote_build(build_ref, repository) repository.promotion_refs.each do |promotion_ref| unless included_in_promotion_ref?(build_ref, promotion_ref) @@ -44,22 +49,18 @@ def merge_ref(build) end def update_branch(branch_name, ref_to_promote) - Cocaine::CommandLine.new("git push", "origin #{ref_to_promote}:refs/heads/#{branch_name}").run + Cocaine::CommandLine.new("git push", "--force origin #{ref_to_promote}:refs/heads/#{branch_name}").run end private def included_in_promotion_ref?(build_ref, promotion_ref) - return unless ref_exists?(promotion_ref) - - cherry_cmd = Cocaine::CommandLine.new("git cherry", "origin/#{promotion_ref} #{build_ref}") - cherry_cmd.run.lines.count == 0 + # --is-ancestor was added in git 1.8.0 + # exit -> 1: not an ancestor + # exit -> 128: the commit does not exist + ancestor_cmd = Cocaine::CommandLine.new("git merge-base", "--is-ancestor #{build_ref} origin/#{promotion_ref}", :expected_outcodes => [0, 1, 128]) + ancestor_cmd.run + ancestor_cmd.exit_status == 0 end - - def ref_exists?(promotion_ref) - show_ref = Cocaine::CommandLine.new("git show-ref", promotion_ref, :expected_outcodes => [0,1]) - show_ref.run.present? - end - end end diff --git a/spec/lib/build_strategies/production_build_strategy_spec.rb b/spec/lib/build_strategies/production_build_strategy_spec.rb index aa3885225..3beb56878 100644 --- a/spec/lib/build_strategies/production_build_strategy_spec.rb +++ b/spec/lib/build_strategies/production_build_strategy_spec.rb @@ -38,30 +38,21 @@ describe "#promote_build" do subject { described_class.promote_build(build.ref, project.repository) } - context "when pushing to a ref that doesn't exist" do + context "when the ref is an ancestor" do before(:each) { - mock_show_ref_command = double - expect(mock_show_ref_command).to receive(:run).and_return "deadbeef refs/remote/origin/branch" - allow(Cocaine::CommandLine).to receive(:new).with("git show-ref", anything, anything).and_return mock_show_ref_command - - mock_cherry_command = double - expect(mock_cherry_command).to receive(:run).and_return "+ deadbeef" - allow(Cocaine::CommandLine).to receive(:new).with("git cherry", "origin/#{project.repository.promotion_refs.first} #{build.ref}").and_return mock_cherry_command + expect(described_class).to receive(:included_in_promotion_ref?).and_return(true) } - it "pushes the ref" do - expect(described_class).to receive(:update_branch).with(project.repository.promotion_refs.first, build.ref) + it "does not perform an update" do + expect(described_class).to_not receive(:update_branch) subject end end - context "when pushing to a ref that doesn't exist" do + context "when the ref is not an ancestor" do before(:each) { - mock_git_command = double - expect(mock_git_command).to receive(:run).and_return "" - allow(Cocaine::CommandLine).to receive(:new).with("git show-ref", anything, anything).and_return mock_git_command + expect(described_class).to receive(:included_in_promotion_ref?).and_return(false) } - - it "fails gracefully if the ref is undefined" do + it "should update the promotion branch" do expect(described_class).to receive(:update_branch).with(project.repository.promotion_refs.first, build.ref) subject end @@ -76,7 +67,7 @@ it "should promote a sha" do mock_git_command = double expect(mock_git_command).to receive(:run).and_return "" - expect(Cocaine::CommandLine).to receive(:new).with("git push", "origin abc123:refs/heads/last-green").and_return mock_git_command + expect(Cocaine::CommandLine).to receive(:new).with("git push", "--force origin abc123:refs/heads/last-green").and_return mock_git_command subject end