Merge Request uses StateMachine now

This commit is contained in:
Andrew8xx8 2013-02-18 12:40:56 +04:00
parent f97296597c
commit b45e9aefd3
15 changed files with 107 additions and 132 deletions

View file

@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
end end
merge_requests = merge_requests.page(params[:page]).per(20) merge_requests = merge_requests.page(params[:page]).per(20)
merge_requests = merge_requests.includes(:author, :project).order("closed, created_at desc") merge_requests = merge_requests.includes(:author, :project).order("state, created_at desc")
# Filter by specific assignee_id (or lack thereof)? # Filter by specific assignee_id (or lack thereof)?
if params[:assignee_id].present? if params[:assignee_id].present?

View file

@ -80,7 +80,7 @@ class MergeRequestsController < ProjectResourceController
def automerge def automerge
return access_denied! unless can?(current_user, :accept_mr, @project) return access_denied! unless can?(current_user, :accept_mr, @project)
if @merge_request.open? && @merge_request.can_be_merged? if @merge_request.opened? && @merge_request.can_be_merged?
@merge_request.should_remove_source_branch = params[:should_remove_source_branch] @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user) @merge_request.automerge!(current_user)
@status = true @status = true

View file

@ -12,7 +12,7 @@ module MergeRequestsHelper
def mr_css_classes mr def mr_css_classes mr
classes = "merge_request" classes = "merge_request"
classes << " closed" if mr.closed classes << " closed" if mr.closed?
classes << " merged" if mr.merged? classes << " merged" if mr.merged?
classes classes
end end

View file

@ -9,15 +9,14 @@
# author_id :integer # author_id :integer
# assignee_id :integer # assignee_id :integer
# title :string(255) # title :string(255)
# closed :boolean default(FALSE), not null # state :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# st_commits :text(2147483647) # st_commits :text(2147483647)
# st_diffs :text(2147483647) # st_diffs :text(2147483647)
# merged :boolean default(FALSE), not null
# merge_status :integer default(1), not null # merge_status :integer default(1), not null
# milestone_id :integer
# #
# milestone_id :integer
require Rails.root.join("app/models/commit") require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model") require Rails.root.join("lib/static_model")
@ -25,11 +24,33 @@ require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base class MergeRequest < ActiveRecord::Base
include Issuable include Issuable
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id, attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,
:author_id_of_changes :author_id_of_changes
attr_accessor :should_remove_source_branch attr_accessor :should_remove_source_branch
state_machine :state, :initial => :opened do
event :close do
transition [:reopened, :opened] => :closed
end
event :merge do
transition [:reopened, :opened] => :merged
end
event :reopen do
transition :closed => :reopened
end
state :opened
state :reopened
state :closed
state :merged
end
BROKEN_DIFF = "--broken-diff" BROKEN_DIFF = "--broken-diff"
UNCHECKED = 1 UNCHECKED = 1
@ -43,6 +64,8 @@ class MergeRequest < ActiveRecord::Base
validates :target_branch, presence: true validates :target_branch, presence: true
validate :validate_branches validate :validate_branches
scope :merged, -> { with_state(:merged) }
def self.find_all_by_branch(branch_name) def self.find_all_by_branch(branch_name)
where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name) where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name)
end end
@ -98,7 +121,7 @@ class MergeRequest < ActiveRecord::Base
end end
def reloaded_diffs def reloaded_diffs
if open? && unmerged_diffs.any? if opened? && unmerged_diffs.any?
self.st_diffs = unmerged_diffs self.st_diffs = unmerged_diffs
self.save self.save
end end
@ -128,10 +151,6 @@ class MergeRequest < ActiveRecord::Base
commits.first commits.first
end end
def merged?
merged && merge_event
end
def merge_event def merge_event
self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end end
@ -146,17 +165,7 @@ class MergeRequest < ActiveRecord::Base
def probably_merged? def probably_merged?
unmerged_commits.empty? && unmerged_commits.empty? &&
commits.any? && open? commits.any? && opened?
end
def open?
!closed
end
def mark_as_merged!
self.merged = true
self.closed = true
save
end end
def mark_as_unmergable def mark_as_unmergable
@ -165,7 +174,7 @@ class MergeRequest < ActiveRecord::Base
end end
def reloaded_commits def reloaded_commits
if open? && unmerged_commits.any? if opened? && unmerged_commits.any?
self.st_commits = unmerged_commits self.st_commits = unmerged_commits
save save
end end
@ -181,7 +190,8 @@ class MergeRequest < ActiveRecord::Base
end end
def merge!(user_id) def merge!(user_id)
self.mark_as_merged! self.merge
Event.create( Event.create(
project: self.project, project: self.project,
action: Event::MERGED, action: Event::MERGED,

View file

@ -7,15 +7,20 @@ class MergeRequestObserver < ActiveRecord::Observer
end end
end end
def after_update(merge_request) def after_close(merge_request, transition)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned? send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
status = nil Note.create_status_change_note(merge_request, current_user, merge_request.state)
status = 'closed' if merge_request.is_being_closed? end
status = 'reopened' if merge_request.is_being_reopened?
if status def after_reopen(merge_request, transition)
Note.create_status_change_note(merge_request, current_user, status) send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end
Note.create_status_change_note(merge_request, current_user, merge_request.state)
end
def after_update(merge_request)
send_reassigned_email(merge_request) if merge_request.is_being_reassigned?
end end
protected protected

View file

@ -3,7 +3,7 @@
%strong Only masters can accept MR %strong Only masters can accept MR
- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project) - if @merge_request.opened? && @commits.any? && can?(current_user, :accept_mr, @project)
.automerge_widget.can_be_merged{style: "display:none"} .automerge_widget.can_be_merged{style: "display:none"}
.alert.alert-success .alert.alert-success
%span %span

View file

@ -1,11 +1,11 @@
.ui-box.ui-box-show .ui-box.ui-box-show
.ui-box-head .ui-box-head
%h4.box-title %h4.box-title
- if @merge_request.merged - if @merge_request.merged?
.error.status_info .error.status_info
%i.icon-ok %i.icon-ok
Merged Merged
- elsif @merge_request.closed - elsif @merge_request.closed?
.error.status_info Closed .error.status_info Closed
= gfm escape_once(@merge_request.title) = gfm escape_once(@merge_request.title)
@ -21,7 +21,7 @@
%strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone) %strong= link_to_gfm truncate(milestone.title, length: 20), project_milestone_path(milestone.project, milestone)
- if @merge_request.closed - if @merge_request.closed?
.ui-box-bottom .ui-box-bottom
- if @merge_request.merged? - if @merge_request.merged?
%span %span

View file

@ -1,4 +1,4 @@
- if @merge_request.open? && @commits.any? - if @merge_request.opened? && @commits.any?
.ci_widget.ci-success{style: "display:none"} .ci_widget.ci-success{style: "display:none"}
.alert.alert-success .alert.alert-success
%i.icon-ok %i.icon-ok

View file

@ -7,7 +7,7 @@
%span.pull-right %span.pull-right
- if can?(current_user, :modify_merge_request, @merge_request) - if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open? - if @merge_request.opened?
.left.btn-group .left.btn-group
%a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} } %a.btn.grouped.dropdown-toggle{ data: {toggle: :dropdown} }
%i.icon-download-alt %i.icon-download-alt

View file

@ -77,7 +77,7 @@
%li=link_to('All Merge Requests', '#') %li=link_to('All Merge Requests', '#')
%ul.well-list %ul.well-list
- @merge_requests.each do |merge_request| - @merge_requests.each do |merge_request|
%li{data: {closed: merge_request.closed}} %li{data: {closed: merge_request.closed?}}
= link_to [@project, merge_request] do = link_to [@project, merge_request] do
%span.badge.badge-info ##{merge_request.id} %span.badge.badge-info ##{merge_request.id}
&ndash; &ndash;

View file

@ -67,10 +67,6 @@ FactoryGirl.define do
source_branch "master" source_branch "master"
target_branch "stable" target_branch "stable"
trait :closed do
closed true
end
# pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d) # pick 3 commits "at random" (from bcf03b5d~3 to bcf03b5d)
trait :with_diffs do trait :with_diffs do
target_branch "master" # pretend bcf03b5d~3 target_branch "master" # pretend bcf03b5d~3
@ -85,7 +81,16 @@ FactoryGirl.define do
end end
end end
trait :closed do
state :closed
end
trait :reopened do
state :reopened
end
factory :closed_merge_request, traits: [:closed] factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs] factory :merge_request_with_diffs, traits: [:with_diffs]
end end

View file

@ -36,6 +36,10 @@ describe MergeRequest do
it { should include_module(Issuable) } it { should include_module(Issuable) }
end end
describe "#mr_and_commit_notes" do
end
describe "#mr_and_commit_notes" do describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) } let!(:merge_request) { create(:merge_request) }

View file

@ -7,7 +7,7 @@
# project_id :integer not null # project_id :integer not null
# description :text # description :text
# due_date :date # due_date :date
# closed :boolean default(FALSE), not null # state :string default(FALSE), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# #

View file

@ -121,10 +121,7 @@ describe Project do
let(:project) { create(:project) } let(:project) { create(:project) }
before do before do
@merge_request = create(:merge_request, @merge_request = create(:merge_request, project: project)
project: project,
merged: false,
closed: false)
@key = create(:key, user_id: project.owner.id) @key = create(:key, user_id: project.owner.id)
end end
@ -133,8 +130,7 @@ describe Project do
@merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" @merge_request.last_commit.id.should == "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user) project.update_merge_requests("8716fc78f3c65bbf7bcf7b574febd583bc5d2812", "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a", "refs/heads/stable", @key.user)
@merge_request.reload @merge_request.reload
@merge_request.merged.should be_true @merge_request.merged?.should be_true
@merge_request.closed.should be_true
end end
it "should update merge request commits with new one if pushed to source branch" do it "should update merge request commits with new one if pushed to source branch" do

View file

@ -1,10 +1,14 @@
require 'spec_helper' require 'spec_helper'
describe MergeRequestObserver do describe MergeRequestObserver do
let(:some_user) { double(:user, id: 1) } let(:some_user) { create :user }
let(:assignee) { double(:user, id: 2) } let(:assignee) { create :user }
let(:author) { double(:user, id: 3) } let(:author) { create :user }
let(:mr) { double(:merge_request, id: 42, assignee: assignee, author: author) } let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
let(:unassigned_mr) { create(:merge_request, author: author) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
before(:each) { subject.stub(:current_user).and_return(some_user) } before(:each) { subject.stub(:current_user).and_return(some_user) }
@ -21,23 +25,21 @@ describe MergeRequestObserver do
end end
it 'sends an email to the assignee' do it 'sends an email to the assignee' do
Notify.should_receive(:new_merge_request_email).with(mr.id) Notify.should_receive(:new_merge_request_email).with(mr_mock.id)
subject.after_create(mr) subject.after_create(mr_mock)
end end
it 'does not send an email to the assignee if assignee created the merge request' do it 'does not send an email to the assignee if assignee created the merge request' do
subject.stub(:current_user).and_return(assignee) subject.stub(:current_user).and_return(assignee)
Notify.should_not_receive(:new_merge_request_email) Notify.should_not_receive(:new_merge_request_email)
subject.after_create(mr) subject.after_create(mr_mock)
end end
end end
context '#after_update' do context '#after_update' do
before(:each) do before(:each) do
mr.stub(:is_being_reassigned?).and_return(false) mr_mock.stub(:is_being_reassigned?).and_return(false)
mr.stub(:is_being_closed?).and_return(false)
mr.stub(:is_being_reopened?).and_return(false)
end end
it 'is called when a merge request is changed' do it 'is called when a merge request is changed' do
@ -52,97 +54,50 @@ describe MergeRequestObserver do
context 'a reassigned email' do context 'a reassigned email' do
it 'is sent if the merge request is being reassigned' do it 'is sent if the merge request is being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(true) mr_mock.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:send_reassigned_email).with(mr) subject.should_receive(:send_reassigned_email).with(mr_mock)
subject.after_update(mr) subject.after_update(mr_mock)
end end
it 'is not sent if the merge request is not being reassigned' do it 'is not sent if the merge request is not being reassigned' do
mr.should_receive(:is_being_reassigned?).and_return(false) mr_mock.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:send_reassigned_email) subject.should_not_receive(:send_reassigned_email)
subject.after_update(mr) subject.after_update(mr_mock)
end end
end end
end
context '#after_close' do
context 'a status "closed"' do context 'a status "closed"' do
it 'note is created if the merge request is being closed' do it 'note is created if the merge request is being closed' do
mr.should_receive(:is_being_closed?).and_return(true) Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed')
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr) assigned_mr.close
end
it 'note is not created if the merge request is not being closed' do
mr.should_receive(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being closed' do
mr.stub(:is_being_closed?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request not being closed' do
mr.stub(:is_being_closed?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'closed')
subject.after_update(mr)
end end
it 'notification is delivered only to author if the merge request is being closed' do it 'notification is delivered only to author if the merge request is being closed' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil) Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed')
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(true)
mr_without_assignee.stub(:is_being_reopened?).and_return(false)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'closed')
subject.after_update(mr_without_assignee) unassigned_mr.close
end end
end end
end
context '#after_reopen' do
context 'a status "reopened"' do context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do it 'note is created if the merge request is being reopened' do
mr.should_receive(:is_being_reopened?).and_return(true) Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened')
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr) closed_assigned_mr.reopen
end
it 'note is not created if the merge request is not being reopened' do
mr.should_receive(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is delivered if the merge request being reopened' do
mr.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end
it 'notification is not delivered if the merge request is not being reopened' do
mr.stub(:is_being_reopened?).and_return(false)
Note.should_not_receive(:create_status_change_note).with(mr, some_user, 'reopened')
subject.after_update(mr)
end end
it 'notification is delivered only to author if the merge request is being reopened' do it 'notification is delivered only to author if the merge request is being reopened' do
mr_without_assignee = double(:merge_request, id: 42, author: author, assignee: nil) Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened')
mr_without_assignee.stub(:is_being_reassigned?).and_return(false)
mr_without_assignee.stub(:is_being_closed?).and_return(false)
mr_without_assignee.stub(:is_being_reopened?).and_return(true)
Note.should_receive(:create_status_change_note).with(mr_without_assignee, some_user, 'reopened')
subject.after_update(mr_without_assignee) closed_unassigned_mr.reopen
end end
end end
end end
@ -151,23 +106,23 @@ describe MergeRequestObserver do
let(:previous_assignee) { double(:user, id: 3) } let(:previous_assignee) { double(:user, id: 3) }
before(:each) do before(:each) do
mr.stub(:assignee_id).and_return(assignee.id) mr_mock.stub(:assignee_id).and_return(assignee.id)
mr.stub(:assignee_id_was).and_return(previous_assignee.id) mr_mock.stub(:assignee_id_was).and_return(previous_assignee.id)
end end
def it_sends_a_reassigned_email_to(recipient) def it_sends_a_reassigned_email_to(recipient)
Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) Notify.should_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end end
def it_does_not_send_a_reassigned_email_to(recipient) def it_does_not_send_a_reassigned_email_to(recipient)
Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr.id, previous_assignee.id) Notify.should_not_receive(:reassigned_merge_request_email).with(recipient, mr_mock.id, previous_assignee.id)
end end
it 'sends a reassigned email to the previous and current assignees' do it 'sends a reassigned email to the previous and current assignees' do
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
context 'does not send an email to the user who made the reassignment' do context 'does not send an email to the user who made the reassignment' do
@ -176,14 +131,14 @@ describe MergeRequestObserver do
it_sends_a_reassigned_email_to previous_assignee.id it_sends_a_reassigned_email_to previous_assignee.id
it_does_not_send_a_reassigned_email_to assignee.id it_does_not_send_a_reassigned_email_to assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
it 'if the user is the previous assignee' do it 'if the user is the previous assignee' do
subject.stub(:current_user).and_return(previous_assignee) subject.stub(:current_user).and_return(previous_assignee)
it_sends_a_reassigned_email_to assignee.id it_sends_a_reassigned_email_to assignee.id
it_does_not_send_a_reassigned_email_to previous_assignee.id it_does_not_send_a_reassigned_email_to previous_assignee.id
subject.send(:send_reassigned_email, mr) subject.send(:send_reassigned_email, mr_mock)
end end
end end
end end