Commit 0d715bcd812ca6c99884e117f28a400669aa8e57

Authored by Dmitriy Zaporozhets
2 parents 4f07a6a9 4d373005

Merge branch 'mr-on-fork' of https://github.com/karlhungus/gitlabhq into karlhungus-mr-on-fork

Conflicts:
	app/views/projects/commit/show.html.haml
	app/views/projects/compare/show.html.haml
	app/views/projects/merge_requests/branch_from.js.haml
Showing 102 changed files with 1646 additions and 456 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 102 files displayed.

app/assets/stylesheets/common.scss
@@ -400,6 +400,18 @@ img.emoji { @@ -400,6 +400,18 @@ img.emoji {
400 margin-bottom: 10px; 400 margin-bottom: 10px;
401 } 401 }
402 402
  403 +.label-project {
  404 + @include border-radius(4px);
  405 + padding: 2px 4px;
  406 + border: none;
  407 + font-size: 14px;
  408 + background: #474D57;
  409 + color: #fff;
  410 + font-family: $monospace_font;
  411 + text-shadow: 0 1px 1px #111;
  412 + font-weight: normal;
  413 +}
  414 +
403 .group-name { 415 .group-name {
404 font-size: 14px; 416 font-size: 14px;
405 line-height: 24px; 417 line-height: 24px;
app/contexts/filter_context.rb
@@ -12,7 +12,7 @@ class FilterContext @@ -12,7 +12,7 @@ class FilterContext
12 12
13 def apply_filter items 13 def apply_filter items
14 if params[:project_id] 14 if params[:project_id]
15 - items = items.where(project_id: params[:project_id]) 15 + items = items.by_project(params[:project_id])
16 end 16 end
17 17
18 if params[:search].present? 18 if params[:search].present?
app/contexts/merge_requests_load_context.rb
@@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext @@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
14 end 14 end
15 15
16 merge_requests = merge_requests.page(params[:page]).per(20) 16 merge_requests = merge_requests.page(params[:page]).per(20)
17 - merge_requests = merge_requests.includes(:author, :project).order("created_at desc") 17 + merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc")
18 18
19 # Filter by specific assignee_id (or lack thereof)? 19 # Filter by specific assignee_id (or lack thereof)?
20 if params[:assignee_id].present? 20 if params[:assignee_id].present?
app/contexts/search_context.rb
@@ -19,7 +19,7 @@ class SearchContext @@ -19,7 +19,7 @@ class SearchContext
19 if params[:search_code].present? 19 if params[:search_code].present?
20 result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? 20 result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
21 else 21 else
22 - result[:merge_requests] = MergeRequest.where(project_id: project_ids).search(query).limit(10) 22 + result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).limit(10)
23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10) 23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10)
24 result[:wiki_pages] = [] 24 result[:wiki_pages] = []
25 end 25 end
app/controllers/projects/merge_requests_controller.rb
@@ -24,8 +24,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -24,8 +24,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
24 format.html 24 format.html
25 format.js 25 format.js
26 26
27 - format.diff { render text: @merge_request.to_diff }  
28 - format.patch { render text: @merge_request.to_patch } 27 + format.diff { render text: @merge_request.to_diff(current_user) }
  28 + format.patch { render text: @merge_request.to_patch(current_user) }
29 end 29 end
30 end 30 end
31 31
@@ -33,27 +33,37 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -33,27 +33,37 @@ class Projects::MergeRequestsController < Projects::ApplicationController
33 @commit = @merge_request.last_commit 33 @commit = @merge_request.last_commit
34 34
35 @comments_allowed = @reply_allowed = true 35 @comments_allowed = @reply_allowed = true
36 - @comments_target = { noteable_type: 'MergeRequest',  
37 - noteable_id: @merge_request.id } 36 + @comments_target = {noteable_type: 'MergeRequest',
  37 + noteable_id: @merge_request.id}
38 @line_notes = @merge_request.notes.where("line_code is not null") 38 @line_notes = @merge_request.notes.where("line_code is not null")
39 end 39 end
40 40
41 def new 41 def new
42 - @merge_request = @project.merge_requests.new(params[:merge_request]) 42 + @merge_request = MergeRequest.new(params[:merge_request])
  43 + @merge_request.source_project = @project unless @merge_request.source_project
  44 + @merge_request.target_project = @project unless @merge_request.target_project
  45 + @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
  46 + @source_project = @merge_request.source_project
  47 + @merge_request
43 end 48 end
44 49
45 def edit 50 def edit
  51 + @source_project = @merge_request.source_project
  52 + @target_project = @merge_request.target_project
  53 + @target_branches = @merge_request.target_project.repository.branch_names
46 end 54 end
47 55
48 def create 56 def create
49 - @merge_request = @project.merge_requests.new(params[:merge_request]) 57 + @merge_request = MergeRequest.new(params[:merge_request])
50 @merge_request.author = current_user 58 @merge_request.author = current_user
51 - 59 + @target_branches ||= []
52 if @merge_request.save 60 if @merge_request.save
53 @merge_request.reload_code 61 @merge_request.reload_code
54 - redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.' 62 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
55 else 63 else
56 - render "new" 64 + @source_project = @merge_request.source_project
  65 + @target_project = @merge_request.target_project
  66 + render action: "new"
57 end 67 end
58 end 68 end
59 69
@@ -61,7 +71,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -61,7 +71,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
61 if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) 71 if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
62 @merge_request.reload_code 72 @merge_request.reload_code
63 @merge_request.mark_as_unchecked 73 @merge_request.mark_as_unchecked
64 - redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.' 74 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
65 else 75 else
66 render "edit" 76 render "edit"
67 end 77 end
@@ -89,22 +99,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -89,22 +99,35 @@ class Projects::MergeRequestsController < Projects::ApplicationController
89 end 99 end
90 100
91 def branch_from 101 def branch_from
  102 + #This is always source
  103 + @source_project = @merge_request.nil? ? @project : @merge_request.source_project
92 @commit = @repository.commit(params[:ref]) 104 @commit = @repository.commit(params[:ref])
93 end 105 end
94 106
95 def branch_to 107 def branch_to
96 - @commit = @repository.commit(params[:ref]) 108 + @target_project = selected_target_project
  109 + @commit = @target_project.repository.commit(params[:ref])
  110 + end
  111 +
  112 + def update_branches
  113 + @target_project = selected_target_project
  114 + @target_branches = @target_project.repository.branch_names
  115 + @target_branches
97 end 116 end
98 117
99 def ci_status 118 def ci_status
100 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) 119 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
101 - response = { status: status } 120 + response = {status: status}
102 121
103 render json: response 122 render json: response
104 end 123 end
105 124
106 protected 125 protected
107 126
  127 + def selected_target_project
  128 + ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
  129 + end
  130 +
108 def merge_request 131 def merge_request
109 @merge_request ||= @project.merge_requests.find(params[:id]) 132 @merge_request ||= @project.merge_requests.find(params[:id])
110 end 133 end
@@ -123,11 +146,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -123,11 +146,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
123 146
124 def validates_merge_request 147 def validates_merge_request
125 # Show git not found page if target branch doesn't exist 148 # Show git not found page if target branch doesn't exist
126 - return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch) 149 + return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
127 150
128 # Show git not found page if source branch doesn't exist 151 # Show git not found page if source branch doesn't exist
129 # and there is no saved commits between source & target branch 152 # and there is no saved commits between source & target branch
130 - return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank? 153 + return invalid_mr if !@merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
131 end 154 end
132 155
133 def define_show_vars 156 def define_show_vars
app/helpers/commits_helper.rb
@@ -108,8 +108,8 @@ module CommitsHelper @@ -108,8 +108,8 @@ module CommitsHelper
108 end 108 end
109 end 109 end
110 110
111 - def commit_to_html commit  
112 - escape_javascript(render 'projects/commits/commit', commit: commit) 111 + def commit_to_html commit, project
  112 + escape_javascript(render 'projects/commits/commit', commit: commit, project: project) unless commit.nil?
113 end 113 end
114 114
115 def diff_line_content(line) 115 def diff_line_content(line)
app/helpers/merge_requests_helper.rb
@@ -2,14 +2,27 @@ module MergeRequestsHelper @@ -2,14 +2,27 @@ module MergeRequestsHelper
2 def new_mr_path_from_push_event(event) 2 def new_mr_path_from_push_event(event)
3 new_project_merge_request_path( 3 new_project_merge_request_path(
4 event.project, 4 event.project,
5 - merge_request: {  
6 - source_branch: event.branch_name,  
7 - target_branch: event.project.repository.root_ref,  
8 - title: event.branch_name.titleize  
9 - } 5 + new_mr_from_push_event(event, event.project)
10 ) 6 )
11 end 7 end
12 8
  9 + def new_mr_path_for_fork_from_push_event(event)
  10 + new_project_merge_request_path(
  11 + event.project,
  12 + new_mr_from_push_event(event, event.project.forked_from_project)
  13 + )
  14 + end
  15 +
  16 + def new_mr_from_push_event(event, target_project)
  17 + return :merge_request => {
  18 + source_project_id: event.project.id,
  19 + target_project_id: target_project.id,
  20 + source_branch: event.branch_name,
  21 + target_branch: target_project.repository.root_ref,
  22 + title: event.branch_name.titleize
  23 + }
  24 + end
  25 +
13 def mr_css_classes mr 26 def mr_css_classes mr
14 classes = "merge-request" 27 classes = "merge-request"
15 classes << " closed" if mr.closed? 28 classes << " closed" if mr.closed?
@@ -18,6 +31,14 @@ module MergeRequestsHelper @@ -18,6 +31,14 @@ module MergeRequestsHelper
18 end 31 end
19 32
20 def ci_build_details_path merge_request 33 def ci_build_details_path merge_request
21 - merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha) 34 + merge_request.source_project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
  35 + end
  36 +
  37 + def merge_path_description(merge_request, separator)
  38 + if merge_request.for_fork?
  39 + "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
  40 + else
  41 + "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
  42 + end
22 end 43 end
23 end 44 end
app/mailers/emails/merge_requests.rb
@@ -2,28 +2,65 @@ module Emails @@ -2,28 +2,65 @@ module Emails
2 module MergeRequests 2 module MergeRequests
3 def new_merge_request_email(recipient_id, merge_request_id) 3 def new_merge_request_email(recipient_id, merge_request_id)
4 @merge_request = MergeRequest.find(merge_request_id) 4 @merge_request = MergeRequest.find(merge_request_id)
5 - @project = @merge_request.project  
6 mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) 5 mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
7 end 6 end
8 7
9 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) 8 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
10 @merge_request = MergeRequest.find(merge_request_id) 9 @merge_request = MergeRequest.find(merge_request_id)
11 @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id 10 @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
12 - @project = @merge_request.project  
13 mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title)) 11 mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
14 end 12 end
15 13
16 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) 14 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
17 @merge_request = MergeRequest.find(merge_request_id) 15 @merge_request = MergeRequest.find(merge_request_id)
18 - @project = @merge_request.project  
19 @updated_by = User.find updated_by_user_id 16 @updated_by = User.find updated_by_user_id
20 mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title)) 17 mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title))
21 end 18 end
22 19
23 def merged_merge_request_email(recipient_id, merge_request_id) 20 def merged_merge_request_email(recipient_id, merge_request_id)
24 @merge_request = MergeRequest.find(merge_request_id) 21 @merge_request = MergeRequest.find(merge_request_id)
25 - @project = @merge_request.project  
26 mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title)) 22 mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title))
27 end 23 end
28 end 24 end
  25 +
  26 + # Over rides default behavour to show source/target
  27 + # Formats arguments into a String suitable for use as an email subject
  28 + #
  29 + # extra - Extra Strings to be inserted into the subject
  30 + #
  31 + # Examples
  32 + #
  33 + # >> subject('Lorem ipsum')
  34 + # => "GitLab Merge Request | Lorem ipsum"
  35 + #
  36 + # # Automatically inserts Project name:
  37 + # Forked MR
  38 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  39 + # => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
  40 + # => source branch => source
  41 + # => target branch => target
  42 + # >> subject('Lorem ipsum')
  43 + # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
  44 + #
  45 + # Non Forked MR
  46 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  47 + # => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  48 + # => source branch => source
  49 + # => target branch => target
  50 + # >> subject('Lorem ipsum')
  51 + # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
  52 + # # Accepts multiple arguments
  53 + # >> subject('Lorem ipsum', 'Dolor sit amet')
  54 + # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
  55 + def subject(*extra)
  56 + subject = "GitLab Merge Request |"
  57 + if @merge_request.for_fork?
  58 + subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
  59 + else
  60 + subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
  61 + end
  62 + subject << " | " + extra.join(' | ') if extra.present?
  63 + subject
  64 + end
  65 +
29 end 66 end
app/mailers/notify.rb
@@ -6,6 +6,7 @@ class Notify &lt; ActionMailer::Base @@ -6,6 +6,7 @@ class Notify &lt; ActionMailer::Base
6 6
7 add_template_helper ApplicationHelper 7 add_template_helper ApplicationHelper
8 add_template_helper GitlabMarkdownHelper 8 add_template_helper GitlabMarkdownHelper
  9 + add_template_helper MergeRequestsHelper
9 10
10 default_url_options[:host] = Gitlab.config.gitlab.host 11 default_url_options[:host] = Gitlab.config.gitlab.host
11 default_url_options[:protocol] = Gitlab.config.gitlab.protocol 12 default_url_options[:protocol] = Gitlab.config.gitlab.protocol
app/models/concerns/issuable.rb
@@ -9,19 +9,14 @@ module Issuable @@ -9,19 +9,14 @@ module Issuable
9 include Mentionable 9 include Mentionable
10 10
11 included do 11 included do
12 - belongs_to :project  
13 belongs_to :author, class_name: "User" 12 belongs_to :author, class_name: "User"
14 belongs_to :assignee, class_name: "User" 13 belongs_to :assignee, class_name: "User"
15 belongs_to :milestone 14 belongs_to :milestone
16 has_many :notes, as: :noteable, dependent: :destroy 15 has_many :notes, as: :noteable, dependent: :destroy
17 16
18 - validates :project, presence: true  
19 validates :author, presence: true 17 validates :author, presence: true
20 validates :title, presence: true, length: { within: 0..255 } 18 validates :title, presence: true, length: { within: 0..255 }
21 19
22 - scope :opened, -> { with_state(:opened) }  
23 - scope :closed, -> { with_state(:closed) }  
24 - scope :of_group, ->(group) { where(project_id: group.project_ids) }  
25 scope :assigned_to, ->(u) { where(assignee_id: u.id)} 20 scope :assigned_to, ->(u) { where(assignee_id: u.id)}
26 scope :recent, -> { order("created_at DESC") } 21 scope :recent, -> { order("created_at DESC") }
27 scope :assigned, -> { where("assignee_id IS NOT NULL") } 22 scope :assigned, -> { where("assignee_id IS NOT NULL") }
app/models/issue.rb
@@ -17,8 +17,18 @@ @@ -17,8 +17,18 @@
17 # 17 #
18 18
19 class Issue < ActiveRecord::Base 19 class Issue < ActiveRecord::Base
  20 +
20 include Issuable 21 include Issuable
21 22
  23 + belongs_to :project
  24 + validates :project, presence: true
  25 +
  26 + scope :of_group, ->(group) { where(project_id: group.project_ids) }
  27 + scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
  28 + scope :opened, -> { with_state(:opened) }
  29 + scope :closed, -> { with_state(:closed) }
  30 + scope :by_project, ->(project_id) {where(project_id:project_id)}
  31 +
22 attr_accessible :title, :assignee_id, :position, :description, 32 attr_accessible :title, :assignee_id, :position, :description,
23 :milestone_id, :label_list, :author_id_of_changes, 33 :milestone_id, :label_list, :author_id_of_changes,
24 :state_event 34 :state_event
app/models/merge_request.rb
@@ -2,30 +2,35 @@ @@ -2,30 +2,35 @@
2 # 2 #
3 # Table name: merge_requests 3 # Table name: merge_requests
4 # 4 #
5 -# id :integer not null, primary key  
6 -# target_branch :string(255) not null  
7 -# source_branch :string(255) not null  
8 -# project_id :integer not null  
9 -# author_id :integer  
10 -# assignee_id :integer  
11 -# title :string(255)  
12 -# created_at :datetime  
13 -# updated_at :datetime  
14 -# st_commits :text(2147483647)  
15 -# st_diffs :text(2147483647)  
16 -# milestone_id :integer  
17 -# state :string(255)  
18 -# merge_status :string(255) 5 +# id :integer not null, primary key
  6 +# target_project_id :integer not null
  7 +# target_branch :string(255) not null
  8 +# source_project_id :integer not null
  9 +# source_branch :string(255) not null
  10 +# author_id :integer
  11 +# assignee_id :integer
  12 +# title :string(255)
  13 +# created_at :datetime
  14 +# updated_at :datetime
  15 +# st_commits :text(2147483647)
  16 +# st_diffs :text(2147483647)
  17 +# milestone_id :integer
  18 +# state :string(255)
  19 +# merge_status :string(255)
19 # 20 #
20 21
21 require Rails.root.join("app/models/commit") 22 require Rails.root.join("app/models/commit")
22 require Rails.root.join("lib/static_model") 23 require Rails.root.join("lib/static_model")
23 24
24 class MergeRequest < ActiveRecord::Base 25 class MergeRequest < ActiveRecord::Base
  26 +
25 include Issuable 27 include Issuable
26 28
27 - attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,  
28 - :author_id_of_changes, :state_event 29 + belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
  30 + belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
  31 +
  32 + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event
  33 +
29 34
30 attr_accessor :should_remove_source_branch 35 attr_accessor :should_remove_source_branch
31 36
@@ -74,30 +79,37 @@ class MergeRequest &lt; ActiveRecord::Base @@ -74,30 +79,37 @@ class MergeRequest &lt; ActiveRecord::Base
74 serialize :st_commits 79 serialize :st_commits
75 serialize :st_diffs 80 serialize :st_diffs
76 81
  82 + validates :source_project, presence: true
77 validates :source_branch, presence: true 83 validates :source_branch, presence: true
  84 + validates :target_project, presence: true
78 validates :target_branch, presence: true 85 validates :target_branch, presence: true
79 - validate :validate_branches 86 + validate :validate_branches
80 87
  88 + scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
  89 + scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
  90 + scope :opened, -> { with_state(:opened) }
  91 + scope :closed, -> { with_state(:closed) }
81 scope :merged, -> { with_state(:merged) } 92 scope :merged, -> { with_state(:merged) }
82 - scope :by_branch, ->(branch_name) { where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name) } 93 + scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
83 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } 94 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
84 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } 95 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
85 - 96 + scope :by_project, ->(project_id) { where("source_project_id = :project_id OR target_project_id = :project_id", project_id: project_id) }
  97 + scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
86 # Closed scope for merge request should return 98 # Closed scope for merge request should return
87 # both merged and closed mr's 99 # both merged and closed mr's
88 scope :closed, -> { with_states(:closed, :merged) } 100 scope :closed, -> { with_states(:closed, :merged) }
89 101
90 def validate_branches 102 def validate_branches
91 - if target_branch == source_branch  
92 - errors.add :branch_conflict, "You can not use same branch for source and target branches" 103 + if target_project==source_project && target_branch == source_branch
  104 + errors.add :branch_conflict, "You can not use same project/branch for source and target"
93 end 105 end
94 106
95 if opened? || reopened? 107 if opened? || reopened?
96 - similar_mrs = self.project.merge_requests.where(source_branch: source_branch, target_branch: target_branch).opened 108 + similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
97 similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id 109 similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
98 110
99 if similar_mrs.any? 111 if similar_mrs.any?
100 - errors.add :base, "There is already an open merge request for this branches" 112 + errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
101 end 113 end
102 end 114 end
103 end 115 end
@@ -137,7 +149,13 @@ class MergeRequest &lt; ActiveRecord::Base @@ -137,7 +149,13 @@ class MergeRequest &lt; ActiveRecord::Base
137 end 149 end
138 150
139 def unmerged_diffs 151 def unmerged_diffs
140 - project.repository.diffs_between(source_branch, target_branch) 152 + if for_fork?
  153 + diffs = Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite
  154 + else
  155 + diffs = target_project.repository.diffs_between(source_branch, target_branch)
  156 + end
  157 + diffs ||= []
  158 + diffs
141 end 159 end
142 160
143 def last_commit 161 def last_commit
@@ -145,11 +163,11 @@ class MergeRequest &lt; ActiveRecord::Base @@ -145,11 +163,11 @@ class MergeRequest &lt; ActiveRecord::Base
145 end 163 end
146 164
147 def merge_event 165 def merge_event
148 - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last 166 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
149 end 167 end
150 168
151 def closed_event 169 def closed_event
152 - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last 170 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
153 end 171 end
154 172
155 def commits 173 def commits
@@ -165,15 +183,23 @@ class MergeRequest &lt; ActiveRecord::Base @@ -165,15 +183,23 @@ class MergeRequest &lt; ActiveRecord::Base
165 if opened? && unmerged_commits.any? 183 if opened? && unmerged_commits.any?
166 self.st_commits = dump_commits(unmerged_commits) 184 self.st_commits = dump_commits(unmerged_commits)
167 save 185 save
  186 +
168 end 187 end
169 commits 188 commits
170 end 189 end
171 190
172 def unmerged_commits 191 def unmerged_commits
173 - self.project.repository.  
174 - commits_between(self.target_branch, self.source_branch). 192 + if for_fork?
  193 + commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between
  194 + else
  195 + commits = target_project.repository.commits_between(self.target_branch, self.source_branch)
  196 + end
  197 + if commits.present?
  198 + commits = Commit.decorate(commits).
175 sort_by(&:created_at). 199 sort_by(&:created_at).
176 reverse 200 reverse
  201 + end
  202 + commits
177 end 203 end
178 204
179 def merge!(user_id) 205 def merge!(user_id)
@@ -199,21 +225,29 @@ class MergeRequest &lt; ActiveRecord::Base @@ -199,21 +225,29 @@ class MergeRequest &lt; ActiveRecord::Base
199 # Returns the raw diff for this merge request 225 # Returns the raw diff for this merge request
200 # 226 #
201 # see "git diff" 227 # see "git diff"
202 - def to_diff  
203 - project.repo.git.native(:diff, {timeout: 30, raise: true}, "#{target_branch}...#{source_branch}") 228 + def to_diff(current_user)
  229 + Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
204 end 230 end
205 231
206 # Returns the commit as a series of email patches. 232 # Returns the commit as a series of email patches.
207 # 233 #
208 # see "git format-patch" 234 # see "git format-patch"
209 - def to_patch  
210 - project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}") 235 + def to_patch(current_user)
  236 + Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
211 end 237 end
212 238
213 def last_commit_short_sha 239 def last_commit_short_sha
214 @last_commit_short_sha ||= last_commit.sha[0..10] 240 @last_commit_short_sha ||= last_commit.sha[0..10]
215 end 241 end
216 242
  243 + def for_fork?
  244 + target_project != source_project
  245 + end
  246 +
  247 + def disallow_source_branch_removal?
  248 + (source_project.root_ref? source_branch) || for_fork?
  249 + end
  250 +
217 private 251 private
218 252
219 def dump_commits(commits) 253 def dump_commits(commits)
app/models/note.rb
@@ -42,18 +42,18 @@ class Note &lt; ActiveRecord::Base @@ -42,18 +42,18 @@ class Note &lt; ActiveRecord::Base
42 42
43 # Scopes 43 # Scopes
44 scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } 44 scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
45 - scope :inline, -> { where("line_code IS NOT NULL") }  
46 - scope :not_inline, -> { where(line_code: [nil, '']) } 45 + scope :inline, ->{ where("line_code IS NOT NULL") }
  46 + scope :not_inline, ->{ where(line_code: [nil, '']) }
47 47
48 scope :common, ->{ where(noteable_type: ["", nil]) } 48 scope :common, ->{ where(noteable_type: ["", nil]) }
49 scope :fresh, ->{ order("created_at ASC, id ASC") } 49 scope :fresh, ->{ order("created_at ASC, id ASC") }
50 scope :inc_author_project, ->{ includes(:project, :author) } 50 scope :inc_author_project, ->{ includes(:project, :author) }
51 scope :inc_author, ->{ includes(:author) } 51 scope :inc_author, ->{ includes(:author) }
52 52
53 - def self.create_status_change_note(noteable, author, status) 53 + def self.create_status_change_note(noteable, project, author, status)
54 create({ 54 create({
55 noteable: noteable, 55 noteable: noteable,
56 - project: noteable.project, 56 + project: project,
57 author: author, 57 author: author,
58 note: "_Status changed to #{status}_" 58 note: "_Status changed to #{status}_"
59 }, without_protection: true) 59 }, without_protection: true)
@@ -62,7 +62,7 @@ class Note &lt; ActiveRecord::Base @@ -62,7 +62,7 @@ class Note &lt; ActiveRecord::Base
62 def commit_author 62 def commit_author
63 @commit_author ||= 63 @commit_author ||=
64 project.users.find_by_email(noteable.author_email) || 64 project.users.find_by_email(noteable.author_email) ||
65 - project.users.find_by_name(noteable.author_name) 65 + project.users.find_by_name(noteable.author_name)
66 rescue 66 rescue
67 nil 67 nil
68 end 68 end
app/models/project.rb
@@ -53,7 +53,7 @@ class Project &lt; ActiveRecord::Base @@ -53,7 +53,7 @@ class Project &lt; ActiveRecord::Base
53 53
54 has_many :services, dependent: :destroy 54 has_many :services, dependent: :destroy
55 has_many :events, dependent: :destroy 55 has_many :events, dependent: :destroy
56 - has_many :merge_requests, dependent: :destroy 56 + has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
57 has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" 57 has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
58 has_many :milestones, dependent: :destroy 58 has_many :milestones, dependent: :destroy
59 has_many :notes, dependent: :destroy 59 has_many :notes, dependent: :destroy
app/observers/activity_observer.rb
1 class ActivityObserver < BaseObserver 1 class ActivityObserver < BaseObserver
2 - observe :issue, :merge_request, :note, :milestone 2 + observe :issue, :note, :milestone
3 3
4 def after_create(record) 4 def after_create(record)
5 event_author_id = record.author_id 5 event_author_id = record.author_id
@@ -13,47 +13,27 @@ class ActivityObserver &lt; BaseObserver @@ -13,47 +13,27 @@ class ActivityObserver &lt; BaseObserver
13 end 13 end
14 14
15 if event_author_id 15 if event_author_id
16 - Event.create(  
17 - project: record.project,  
18 - target_id: record.id,  
19 - target_type: record.class.name,  
20 - action: Event.determine_action(record),  
21 - author_id: event_author_id  
22 - ) 16 + create_event(record, Event.determine_action(record))
23 end 17 end
24 end 18 end
25 19
26 def after_close(record, transition) 20 def after_close(record, transition)
27 - Event.create(  
28 - project: record.project,  
29 - target_id: record.id,  
30 - target_type: record.class.name,  
31 - action: Event::CLOSED,  
32 - author_id: record.author_id_of_changes  
33 - ) 21 + create_event(record, Event::CLOSED)
34 end 22 end
35 23
36 def after_reopen(record, transition) 24 def after_reopen(record, transition)
37 - Event.create(  
38 - project: record.project,  
39 - target_id: record.id,  
40 - target_type: record.class.name,  
41 - action: Event::REOPENED,  
42 - author_id: record.author_id_of_changes  
43 - ) 25 + create_event(record, Event::REOPENED)
44 end 26 end
45 27
46 - def after_merge(record, transition)  
47 - # Since MR can be merged via sidekiq  
48 - # to prevent event duplication do this check  
49 - return true if record.merge_event 28 + protected
50 29
  30 + def create_event(record, status)
51 Event.create( 31 Event.create(
52 - project: record.project,  
53 - target_id: record.id,  
54 - target_type: record.class.name,  
55 - action: Event::MERGED,  
56 - author_id: record.author_id_of_changes 32 + project: record.project,
  33 + target_id: record.id,
  34 + target_type: record.class.name,
  35 + action: status,
  36 + author_id: record.author_id
57 ) 37 )
58 end 38 end
59 end 39 end
app/observers/issue_observer.rb
@@ -23,6 +23,6 @@ class IssueObserver &lt; BaseObserver @@ -23,6 +23,6 @@ class IssueObserver &lt; BaseObserver
23 23
24 # Create issue note with service comment like 'Status changed to closed' 24 # Create issue note with service comment like 'Status changed to closed'
25 def create_note(issue) 25 def create_note(issue)
26 - Note.create_status_change_note(issue, current_user, issue.state) 26 + Note.create_status_change_note(issue, issue.project, current_user, issue.state)
27 end 27 end
28 end 28 end
app/observers/merge_request_observer.rb
1 -class MergeRequestObserver < BaseObserver 1 +class MergeRequestObserver < ActivityObserver
  2 + observe :merge_request
  3 +
2 def after_create(merge_request) 4 def after_create(merge_request)
  5 + if merge_request.author_id
  6 + create_event(merge_request, Event.determine_action(merge_request))
  7 + end
  8 +
3 notification.new_merge_request(merge_request, current_user) 9 notification.new_merge_request(merge_request, current_user)
4 end 10 end
5 11
6 def after_close(merge_request, transition) 12 def after_close(merge_request, transition)
7 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 13 + create_event(merge_request, Event::CLOSED)
  14 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
8 15
9 notification.close_mr(merge_request, current_user) 16 notification.close_mr(merge_request, current_user)
10 end 17 end
11 18
12 def after_merge(merge_request, transition) 19 def after_merge(merge_request, transition)
13 notification.merge_mr(merge_request) 20 notification.merge_mr(merge_request)
  21 + # Since MR can be merged via sidekiq
  22 + # to prevent event duplication do this check
  23 + return true if merge_request.merge_event
  24 +
  25 + Event.create(
  26 + project: merge_request.target_project,
  27 + target_id: merge_request.id,
  28 + target_type: merge_request.class.name,
  29 + action: Event::MERGED,
  30 + author_id: merge_request.author_id_of_changes
  31 + )
14 end 32 end
15 33
16 def after_reopen(merge_request, transition) 34 def after_reopen(merge_request, transition)
17 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 35 + create_event(merge_request, Event::REOPENED)
  36 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
18 end 37 end
19 38
20 def after_update(merge_request) 39 def after_update(merge_request)
21 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? 40 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
22 end 41 end
  42 +
  43 + def create_event(record, status)
  44 + Event.create(
  45 + project: record.target_project,
  46 + target_id: record.id,
  47 + target_type: record.class.name,
  48 + action: status,
  49 + author_id: record.author_id
  50 + )
  51 + end
  52 +
23 end 53 end
app/services/notification_service.rb
@@ -23,7 +23,7 @@ class NotificationService @@ -23,7 +23,7 @@ class NotificationService
23 # * project team members with notification level higher then Participating 23 # * project team members with notification level higher then Participating
24 # 24 #
25 def new_issue(issue, current_user) 25 def new_issue(issue, current_user)
26 - new_resource_email(issue, 'new_issue_email') 26 + new_resource_email(issue, issue.project, 'new_issue_email')
27 end 27 end
28 28
29 # When we close an issue we should send next emails: 29 # When we close an issue we should send next emails:
@@ -33,7 +33,7 @@ class NotificationService @@ -33,7 +33,7 @@ class NotificationService
33 # * project team members with notification level higher then Participating 33 # * project team members with notification level higher then Participating
34 # 34 #
35 def close_issue(issue, current_user) 35 def close_issue(issue, current_user)
36 - close_resource_email(issue, current_user, 'closed_issue_email') 36 + close_resource_email(issue, issue.project, current_user, 'closed_issue_email')
37 end 37 end
38 38
39 # When we reassign an issue we should send next emails: 39 # When we reassign an issue we should send next emails:
@@ -42,7 +42,7 @@ class NotificationService @@ -42,7 +42,7 @@ class NotificationService
42 # * issue new assignee if his notification level is not Disabled 42 # * issue new assignee if his notification level is not Disabled
43 # 43 #
44 def reassigned_issue(issue, current_user) 44 def reassigned_issue(issue, current_user)
45 - reassign_resource_email(issue, current_user, 'reassigned_issue_email') 45 + reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email')
46 end 46 end
47 47
48 48
@@ -51,7 +51,7 @@ class NotificationService @@ -51,7 +51,7 @@ class NotificationService
51 # * mr assignee if his notification level is not Disabled 51 # * mr assignee if his notification level is not Disabled
52 # 52 #
53 def new_merge_request(merge_request, current_user) 53 def new_merge_request(merge_request, current_user)
54 - new_resource_email(merge_request, 'new_merge_request_email') 54 + new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email')
55 end 55 end
56 56
57 # When we reassign a merge_request we should send next emails: 57 # When we reassign a merge_request we should send next emails:
@@ -60,7 +60,7 @@ class NotificationService @@ -60,7 +60,7 @@ class NotificationService
60 # * merge_request assignee if his notification level is not Disabled 60 # * merge_request assignee if his notification level is not Disabled
61 # 61 #
62 def reassigned_merge_request(merge_request, current_user) 62 def reassigned_merge_request(merge_request, current_user)
63 - reassign_resource_email(merge_request, current_user, 'reassigned_merge_request_email') 63 + reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email')
64 end 64 end
65 65
66 # When we close a merge request we should send next emails: 66 # When we close a merge request we should send next emails:
@@ -70,7 +70,7 @@ class NotificationService @@ -70,7 +70,7 @@ class NotificationService
70 # * project team members with notification level higher then Participating 70 # * project team members with notification level higher then Participating
71 # 71 #
72 def close_mr(merge_request, current_user) 72 def close_mr(merge_request, current_user)
73 - close_resource_email(merge_request, current_user, 'closed_merge_request_email') 73 + close_resource_email(merge_request, merge_request.target_project, current_user, 'closed_merge_request_email')
74 end 74 end
75 75
76 # When we merge a merge request we should send next emails: 76 # When we merge a merge request we should send next emails:
@@ -80,8 +80,8 @@ class NotificationService @@ -80,8 +80,8 @@ class NotificationService
80 # * project team members with notification level higher then Participating 80 # * project team members with notification level higher then Participating
81 # 81 #
82 def merge_mr(merge_request) 82 def merge_mr(merge_request)
83 - recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.project)  
84 - recipients = recipients.concat(project_watchers(merge_request.project)).uniq 83 + recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
  84 + recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
85 85
86 recipients.each do |recipient| 86 recipients.each do |recipient|
87 mailer.merged_merge_request_email(recipient.id, merge_request.id) 87 mailer.merged_merge_request_email(recipient.id, merge_request.id)
@@ -191,14 +191,14 @@ class NotificationService @@ -191,14 +191,14 @@ class NotificationService
191 end 191 end
192 end 192 end
193 193
194 - def new_resource_email(target, method) 194 + def new_resource_email(target, project, method)
195 if target.respond_to?(:participants) 195 if target.respond_to?(:participants)
196 recipients = target.participants 196 recipients = target.participants
197 else 197 else
198 recipients = [] 198 recipients = []
199 end 199 end
200 - recipients = reject_muted_users(recipients, target.project)  
201 - recipients = recipients.concat(project_watchers(target.project)).uniq 200 + recipients = reject_muted_users(recipients, project)
  201 + recipients = recipients.concat(project_watchers(project)).uniq
202 recipients.delete(target.author) 202 recipients.delete(target.author)
203 203
204 recipients.each do |recipient| 204 recipients.each do |recipient|
@@ -206,9 +206,9 @@ class NotificationService @@ -206,9 +206,9 @@ class NotificationService
206 end 206 end
207 end 207 end
208 208
209 - def close_resource_email(target, current_user, method)  
210 - recipients = reject_muted_users([target.author, target.assignee], target.project)  
211 - recipients = recipients.concat(project_watchers(target.project)).uniq 209 + def close_resource_email(target, project, current_user, method)
  210 + recipients = reject_muted_users([target.author, target.assignee], project)
  211 + recipients = recipients.concat(project_watchers(project)).uniq
212 recipients.delete(current_user) 212 recipients.delete(current_user)
213 213
214 recipients.each do |recipient| 214 recipients.each do |recipient|
@@ -216,14 +216,14 @@ class NotificationService @@ -216,14 +216,14 @@ class NotificationService
216 end 216 end
217 end 217 end
218 218
219 - def reassign_resource_email(target, current_user, method) 219 + def reassign_resource_email(target, project, current_user, method)
220 recipients = User.where(id: [target.assignee_id, target.assignee_id_was]) 220 recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
221 221
222 # Add watchers to email list 222 # Add watchers to email list
223 - recipients = recipients.concat(project_watchers(target.project)) 223 + recipients = recipients.concat(project_watchers(project))
224 224
225 # reject users with disabled notifications 225 # reject users with disabled notifications
226 - recipients = reject_muted_users(recipients, target.project) 226 + recipients = reject_muted_users(recipients, project)
227 227
228 # Reject me from recipients if I reassign an item 228 # Reject me from recipients if I reassign an item
229 recipients.delete(current_user) 229 recipients.delete(current_user)
app/views/events/_event_last_push.html.haml
@@ -9,6 +9,9 @@ @@ -9,6 +9,9 @@
9 = time_ago_in_words(event.created_at) 9 = time_ago_in_words(event.created_at)
10 ago. 10 ago.
11 .pull-right 11 .pull-right
12 - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do 12 + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-new-mr" do
13 Create Merge Request 13 Create Merge Request
  14 + - if !event.project.nil? && event.project.forked?
  15 + = link_to new_mr_path_for_fork_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do
  16 + Create Merge Request on fork
14 %hr 17 %hr
app/views/notify/closed_merge_request_email.html.haml
1 %p 1 %p
2 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" 2 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/closed_merge_request_email.text.haml
1 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" 1 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}"
2 2
3 -Merge Request url: #{project_merge_request_url(@merge_request.project, @merge_request)} 3 +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
5 -Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} 5 += merge_path_description(@merge_request, 'to')
6 6
7 Author: #{@merge_request.author_name} 7 Author: #{@merge_request.author_name}
8 Assignee: #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.assignee_name}
app/views/notify/merged_merge_request_email.html.haml
1 %p 1 %p
2 = "Merge Request #{@merge_request.id} was merged" 2 = "Merge Request #{@merge_request.id} was merged"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/merged_merge_request_email.text.haml
1 = "Merge Request #{@merge_request.id} was merged" 1 = "Merge Request #{@merge_request.id} was merged"
2 2
3 -Merge Request Url: #{project_merge_request_url(@merge_request.project, @merge_request)} 3 +Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
5 -Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} 5 += merge_path_description(@merge_request, 'to')
6 6
7 Author: #{@merge_request.author_name} 7 Author: #{@merge_request.author_name}
8 Assignee: #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.assignee_name}
app/views/notify/new_merge_request_email.html.haml
1 %p 1 %p
2 = "New Merge Request !#{@merge_request.id}" 2 = "New Merge Request !#{@merge_request.id}"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/new_merge_request_email.text.erb
1 New Merge Request <%= @merge_request.id %> 1 New Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>  
4 - 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
5 4
6 -Branches: <%= @merge_request.source_branch %> to <%= @merge_request.target_branch %> 5 +<%= merge_path_description(@merge_request, 'to') %>
7 Author: <%= @merge_request.author_name %> 6 Author: <%= @merge_request.author_name %>
8 Asignee: <%= @merge_request.assignee_name %> 7 Asignee: <%= @merge_request.assignee_name %>
9 8
app/views/notify/note_merge_request_email.html.haml
1 %p 1 %p
2 - if @note.for_diff_line? 2 - if @note.for_diff_line?
3 - = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}") 3 + = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
4 - else 4 - else
5 - = link_to "New comment", project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}") 5 + = link_to "New comment", project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
6 for Merge Request ##{@merge_request.id} 6 for Merge Request ##{@merge_request.id}
7 %cite "#{truncate(@merge_request.title, length: 20)}" 7 %cite "#{truncate(@merge_request.title, length: 20)}"
8 = render 'note_message' 8 = render 'note_message'
app/views/notify/note_merge_request_email.text.erb
1 New comment for Merge Request <%= @merge_request.id %> 1 New comment for Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")) %> 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
4 4
5 5
6 <%= @note.author_name %> 6 <%= @note.author_name %>
app/views/notify/reassigned_merge_request_email.html.haml
1 %p 1 %p
2 = "Reassigned Merge Request !#{@merge_request.id}" 2 = "Reassigned Merge Request !#{@merge_request.id}"
3 - = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.project, @merge_request) 3 + = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request)
4 %p 4 %p
5 Assignee changed 5 Assignee changed
6 - if @previous_assignee 6 - if @previous_assignee
app/views/notify/reassigned_merge_request_email.text.erb
1 Reassigned Merge Request <%= @merge_request.id %> 1 Reassigned Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %> 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
4 4
5 5
6 Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %> 6 Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %>
app/views/projects/commit/show.html.haml
1 = render "commit_box" 1 = render "commit_box"
2 -= render "projects/commits/diffs", diffs: @commit.diffs 2 += render "projects/commits/diffs", diffs: @commit.diffs, project: @project
3 = render "projects/notes/notes_with_form" 3 = render "projects/notes/notes_with_form"
app/views/projects/commits/_commit.html.haml
1 %li.commit 1 %li.commit
2 .browse_code_link_holder 2 .browse_code_link_holder
3 %p 3 %p
4 - %strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right" 4 + %strong= link_to "Browse Code »", project_tree_path(project, commit), class: "right"
5 %p 5 %p
6 - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" 6 + = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
7 = commit_author_link(commit, avatar: true, size: 24) 7 = commit_author_link(commit, avatar: true, size: 24)
8 &nbsp; 8 &nbsp;
9 - = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title" 9 + = link_to_gfm truncate(commit.title, length: 70), project_commit_path(project, commit.id), class: "row_title"
10 10
11 %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } 11 %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
12 = time_ago_in_words(commit.committed_date) 12 = time_ago_in_words(commit.committed_date)
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 &nbsp; 14 &nbsp;
15 15
16 %span.notes_count 16 %span.notes_count
17 - - notes = @project.notes.for_commit_id(commit.id) 17 + - notes = project.notes.for_commit_id(commit.id)
18 - if notes.any? 18 - if notes.any?
19 %span.badge.badge-info 19 %span.badge.badge-info
20 %i.icon-comment 20 %i.icon-comment
app/views/projects/commits/_commits.html.haml
@@ -3,7 +3,6 @@ @@ -3,7 +3,6 @@
3 .title 3 .title
4 %i.icon-calendar 4 %i.icon-calendar
5 %span= day.stamp("28 Aug, 2010") 5 %span= day.stamp("28 Aug, 2010")
6 -  
7 .pull-right 6 .pull-right
8 %small= pluralize(commits.count, 'commit') 7 %small= pluralize(commits.count, 'commit')
9 - %ul.well-list= render commits 8 + %ul.well-list= render commits, project: @project
app/views/projects/commits/_diffs.html.haml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 %p To prevent performance issue we rejected diff information. 5 %p To prevent performance issue we rejected diff information.
6 %p 6 %p
7 But if you still want to see diff 7 But if you still want to see diff
8 - = link_to "click this link", project_commit_path(@project, @commit, force_show_diff: true), class: "underlined_link" 8 + = link_to "click this link", project_commit_path(project, @commit, force_show_diff: true), class: "underlined_link"
9 9
10 %p.commit-stat-summary 10 %p.commit-stat-summary
11 Showing 11 Showing
@@ -23,8 +23,8 @@ @@ -23,8 +23,8 @@
23 - unless @suppress_diff 23 - unless @suppress_diff
24 - diffs.each_with_index do |diff, i| 24 - diffs.each_with_index do |diff, i|
25 - next if diff.diff.empty? 25 - next if diff.diff.empty?
26 - - file = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, diff.new_path)  
27 - - file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) unless file.exists? 26 + - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path)
  27 + - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists?
28 - next unless file.exists? 28 - next unless file.exists?
29 .file{id: "diff-#{i}"} 29 .file{id: "diff-#{i}"}
30 .header 30 .header
@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 %span= diff.old_path 32 %span= diff.old_path
33 33
34 - if @commit.parent_ids.present? 34 - if @commit.parent_ids.present?
35 - = link_to project_blob_path(@project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do 35 + = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
36 View file @ 36 View file @
37 %span.commit-short-id= @commit.short_id(6) 37 %span.commit-short-id= @commit.short_id(6)
38 - else 38 - else
@@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
40 - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode 40 - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
41 %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" 41 %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
42 42
43 - = link_to project_blob_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do 43 + = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
44 View file @ 44 View file @
45 %span.commit-short-id= @commit.short_id(6) 45 %span.commit-short-id= @commit.short_id(6)
46 46
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 - if file.text? 50 - if file.text?
51 = render "projects/commits/text_file", diff: diff, index: i 51 = render "projects/commits/text_file", diff: diff, index: i
52 - elsif file.image? 52 - elsif file.image?
53 - - old_file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id 53 + - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id
54 = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i 54 = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
55 - else 55 - else
56 %p.nothing_here_message No preview for this file type 56 %p.nothing_here_message No preview for this file type
app/views/projects/compare/show.html.haml
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 %div.ui-box 15 %div.ui-box
16 .title 16 .title
17 Commits (#{@commits.count}) 17 Commits (#{@commits.count})
18 - %ul.well-list= render Commit.decorate(@commits) 18 + %ul.well-list= render Commit.decorate(@commits), project: @project
19 19
20 - unless @diffs.empty? 20 - unless @diffs.empty?
21 %h4 Diff 21 %h4 Diff
22 - = render "projects/commits/diffs", diffs: @diffs 22 + = render "projects/commits/diffs", diffs: @diffs, project: @project
app/views/projects/merge_requests/_form.html.haml
@@ -12,18 +12,20 @@ @@ -12,18 +12,20 @@
12 .row 12 .row
13 .span5 13 .span5
14 .light-well 14 .light-well
15 - %h5.cgray From (Head Branch)  
16 - = f.select(:source_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'}) 15 + %h5.cgray From
  16 + .padded= f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span4'})
  17 + .padded= f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span4'})
17 .mr_source_commit.prepend-top-10 18 .mr_source_commit.prepend-top-10
18 -  
19 .span2 19 .span2
20 %h1.merge-request-angle 20 %h1.merge-request-angle
21 %i.icon-angle-right 21 %i.icon-angle-right
22 .span5 22 .span5
23 .light-well 23 .light-well
24 - %h5.cgray To (Base Branch)  
25 - = f.select(:target_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'})  
26 - .mr_target_commit.prepend-top-10 24 + %h5.cgray To
  25 + - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
  26 + .padded= f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span4'})
  27 + .padded= f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span4'})
  28 + .mr_target_commit.prepend-top-10
27 29
28 %hr 30 %hr
29 31
@@ -47,32 +49,34 @@ @@ -47,32 +49,34 @@
47 Milestone 49 Milestone
48 .input= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) 50 .input= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
49 51
50 -  
51 .form-actions 52 .form-actions
52 - if @merge_request.new_record? 53 - if @merge_request.new_record?
53 = f.submit 'Submit merge request', class: "btn btn-create" 54 = f.submit 'Submit merge request', class: "btn btn-create"
54 -else 55 -else
55 = f.submit 'Save changes', class: "btn btn-save" 56 = f.submit 'Save changes', class: "btn btn-save"
56 - if @merge_request.new_record? 57 - if @merge_request.new_record?
57 - = link_to project_merge_requests_path(@project), class: "btn btn-cancel" do 58 + = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do
58 Cancel 59 Cancel
59 - else 60 - else
60 - = link_to project_merge_request_path(@project, @merge_request), class: "btn btn-cancel" do 61 + = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do
61 Cancel 62 Cancel
62 63
63 :javascript 64 :javascript
64 disableButtonIfEmptyField("#merge_request_title", ".btn-save"); 65 disableButtonIfEmptyField("#merge_request_title", ".btn-save");
65 66
66 var source_branch = $("#merge_request_source_branch") 67 var source_branch = $("#merge_request_source_branch")
67 - , target_branch = $("#merge_request_target_branch"); 68 + , target_branch = $("#merge_request_target_branch")
  69 + , target_project = $("#merge_request_target_project_id");
68 70
69 - $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });  
70 - $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() }); 71 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
  72 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
71 73
72 - source_branch.live("change", function() {  
73 - $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: $(this).val() }); 74 + target_project.on("change", function() {
  75 + $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
74 }); 76 });
75 -  
76 - target_branch.live("change", function() {  
77 - $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() }); 77 + source_branch.on("change", function() {
  78 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
  79 + });
  80 + target_branch.on("change", function() {
  81 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
78 }); 82 });
app/views/projects/merge_requests/_merge_request.html.haml
1 %li{ class: mr_css_classes(merge_request) } 1 %li{ class: mr_css_classes(merge_request) }
2 .merge-request-title 2 .merge-request-title
3 %span.light= "##{merge_request.id}" 3 %span.light= "##{merge_request.id}"
4 - = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.project, merge_request), class: "row_title" 4 + = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
5 - if merge_request.merged? 5 - if merge_request.merged?
6 %small.pull-right 6 %small.pull-right
7 %i.icon-ok 7 %i.icon-ok
8 = "MERGED" 8 = "MERGED"
9 - else 9 - else
10 %span.pull-right 10 %span.pull-right
11 - %i.icon-angle-right  
12 - = merge_request.target_branch 11 + - if merge_request.for_fork?
  12 + = "#{merge_request.source_project.path_with_namespace}/#{merge_request.source_branch}"
  13 + %i.icon-angle-right
  14 + = "#{merge_request.target_project.path_with_namespace}/#{merge_request.target_branch}"
  15 + - else
  16 + = "#{merge_request.source_branch}"
  17 + %i.icon-angle-right
  18 + = "#{merge_request.target_branch}"
13 .merge-request-info 19 .merge-request-info
14 - if merge_request.author 20 - if merge_request.author
15 - authored by #{link_to_member(@project, merge_request.author)} 21 + authored by #{link_to_member(merge_request.source_project, merge_request.author)}
16 - if merge_request.votes_count > 0 22 - if merge_request.votes_count > 0
17 = render 'votes/votes_inline', votable: merge_request 23 = render 'votes/votes_inline', votable: merge_request
18 - if merge_request.notes.any? 24 - if merge_request.notes.any?
app/views/projects/merge_requests/branch_from.js.haml
1 :plain 1 :plain
2 - $(".mr_source_commit").html("#{commit_to_html(@commit)}"); 2 + $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}");
3 var mrTitle = $('#merge_request_title'); 3 var mrTitle = $('#merge_request_title');
4 4
5 if(mrTitle.is(":empty")) { 5 if(mrTitle.is(":empty")) {
app/views/projects/merge_requests/branch_to.js.haml
1 :plain 1 :plain
2 - $(".mr_target_commit").html("#{commit_to_html(@commit)}"); 2 + $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}");
app/views/projects/merge_requests/show/_commits.html.haml
@@ -7,19 +7,19 @@ @@ -7,19 +7,19 @@
7 - if @commits.count > 8 7 - if @commits.count > 8
8 %ul.first-commits.well-list 8 %ul.first-commits.well-list
9 - @commits.first(8).each do |commit| 9 - @commits.first(8).each do |commit|
10 - = render "projects/commits/commit", commit: commit 10 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
11 %li.bottom 11 %li.bottom
12 8 of #{@commits.count} commits displayed. 12 8 of #{@commits.count} commits displayed.
13 %strong 13 %strong
14 %a.show-all-commits Click here to show all 14 %a.show-all-commits Click here to show all
15 %ul.all-commits.hide.well-list 15 %ul.all-commits.hide.well-list
16 - @commits.each do |commit| 16 - @commits.each do |commit|
17 - = render "projects/commits/commit", commit: commit 17 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
18 18
19 - else 19 - else
20 %ul.well-list 20 %ul.well-list
21 - @commits.each do |commit| 21 - @commits.each do |commit|
22 - = render "projects/commits/commit", commit: commit 22 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
23 23
24 - else 24 - else
25 %h4.nothing_here_message 25 %h4.nothing_here_message
app/views/projects/merge_requests/show/_diffs.html.haml
1 - if @merge_request.valid_diffs? 1 - if @merge_request.valid_diffs?
2 - = render "projects/commits/diffs", diffs: @merge_request.diffs 2 + = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
3 - elsif @merge_request.broken_diffs? 3 - elsif @merge_request.broken_diffs?
4 %h4.nothing_here_message 4 %h4.nothing_here_message
5 Can't load diff. 5 Can't load diff.
6 You can 6 You can
7 - = link_to "download it", project_merge_request_path(@project, @merge_request, format: :diff), class: "vlink" 7 + = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink"
8 instead. 8 instead.
9 - else 9 - else
10 %h4.nothing_here_message Nothing to merge 10 %h4.nothing_here_message Nothing to merge
app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -3,17 +3,49 @@ @@ -3,17 +3,49 @@
3 %a.close{href: "#"} × 3 %a.close{href: "#"} ×
4 %h3 How To Merge 4 %h3 How To Merge
5 .modal-body 5 .modal-body
6 - %p  
7 - %strong Step 1.  
8 - Checkout target branch and get recent objects from GitLab  
9 - %pre.dark  
10 - :preserve  
11 - git checkout #{@merge_request.target_branch}  
12 - git fetch origin  
13 - %p  
14 - %strong Step 2.  
15 - Merge source branch into target branch and push changes to GitLab  
16 - %pre.dark  
17 - :preserve  
18 - git merge origin/#{@merge_request.source_branch}  
19 - git push origin #{@merge_request.target_branch} 6 + - if @merge_request.for_fork?
  7 + - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
  8 + - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
  9 + %p
  10 + %strong Step 1.
  11 + Checkout target branch and get recent objects from GitLab
  12 + Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote}
  13 + remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote}
  14 + %pre.dark
  15 + :preserve
  16 + git checkout #{target_remote} #{@merge_request.target_branch}
  17 + git fetch #{source_remote}
  18 + %p
  19 + %strong Step 2.
  20 + Merge source branch into target branch and push changes to GitLab
  21 + %pre.dark
  22 + :preserve
  23 + git merge #{source_remote}/#{@merge_request.source_branch}
  24 + git push #{target_remote} #{@merge_request.target_branch}
  25 + - else
  26 + %p
  27 + %strong Step 1.
  28 + Checkout target branch and get recent objects from GitLab
  29 + %pre.dark
  30 + :preserve
  31 + git checkout #{@merge_request.target_branch}
  32 + git fetch origin
  33 + %p
  34 + %strong Step 2.
  35 + Merge source branch into target branch and push changes to GitLab
  36 + %pre.dark
  37 + :preserve
  38 + git merge origin/#{@merge_request.source_branch}
  39 + git push origin #{@merge_request.target_branch}
  40 +
  41 +
  42 +:javascript
  43 + $(function(){
  44 + var modal = $('#modal_merge_info').modal({modal: true, show:false});
  45 + $('.how_to_merge_link').bind("click", function(){
  46 + modal.show();
  47 + });
  48 + $('.modal-header .close').bind("click", function(){
  49 + modal.hide();
  50 + })
  51 + })
app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 for instructions 15 for instructions
16 .accept_group 16 .accept_group
17 = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" 17 = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
18 - - unless @project.root_ref? @merge_request.source_branch 18 + - unless @merge_request.disallow_source_branch_removal?
19 .remove_branch_holder 19 .remove_branch_holder
20 = label_tag :should_remove_source_branch, class: "checkbox" do 20 = label_tag :should_remove_source_branch, class: "checkbox" do
21 = check_box_tag :should_remove_source_branch 21 = check_box_tag :should_remove_source_branch
app/views/projects/merge_requests/show/_mr_title.html.haml
1 %h3.page-title 1 %h3.page-title
2 = "Merge Request ##{@merge_request.id}:" 2 = "Merge Request ##{@merge_request.id}:"
3 &nbsp; 3 &nbsp;
4 - %span.label-branch= @merge_request.source_branch  
5 - &rarr;  
6 - %span.label-branch= @merge_request.target_branch 4 + -if @merge_request.for_fork?
  5 + %span.label-project= @merge_request.source_project.path_with_namespace
  6 + %span.label-branch= @merge_request.source_branch
  7 + &rarr;
  8 + %span.label-project= @merge_request.target_project.path_with_namespace
  9 + %span.label-branch= @merge_request.target_branch
  10 + - else
  11 + %span.label-branch= @merge_request.source_branch
  12 + &rarr;
  13 + %span.label-branch= @merge_request.target_branch
7 14
8 %span.pull-right 15 %span.pull-right
9 - if can?(current_user, :modify_merge_request, @merge_request) 16 - if can?(current_user, :modify_merge_request, @merge_request)
@@ -19,7 +26,7 @@ @@ -19,7 +26,7 @@
19 26
20 = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" 27 = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
21 28
22 - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do 29 + = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do
23 %i.icon-edit 30 %i.icon-edit
24 Edit 31 Edit
25 32
app/views/projects/merge_requests/update_branches.js.haml 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +:plain
  2 + $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
  3 + $(".target_branch").trigger("liszt:updated");
  4 + $(".mr_target_commit").html("");
  5 + $(".target_branch").trigger("change");
0 \ No newline at end of file 6 \ No newline at end of file
app/views/projects/notes/_discussion.html.haml
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 discussion on this merge request diff 23 discussion on this merge request diff
24 - elsif note.for_commit? 24 - elsif note.for_commit?
25 started a discussion on commit 25 started a discussion on commit
26 - #{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)} 26 + #{link_to note.noteable.short_id, project_commit_path(note.project, note.noteable)}
27 = link_to_commit_diff_line_note(note) if note.for_diff_line? 27 = link_to_commit_diff_line_note(note) if note.for_diff_line?
28 - else 28 - else
29 %cite.cgray started a discussion 29 %cite.cgray started a discussion
app/views/search/_result.html.haml
@@ -22,11 +22,14 @@ @@ -22,11 +22,14 @@
22 - @merge_requests.each do |merge_request| 22 - @merge_requests.each do |merge_request|
23 %li 23 %li
24 merge request: 24 merge request:
25 - = link_to [merge_request.project, merge_request] do 25 + = link_to [merge_request.target_project, merge_request] do
26 %span ##{merge_request.id} 26 %span ##{merge_request.id}
27 %strong.term 27 %strong.term
28 = truncate merge_request.title, length: 50 28 = truncate merge_request.title, length: 50
29 - %span.light (#{merge_request.project.name_with_namespace}) 29 + - if merge_request.for_fork?
  30 + %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} &rarr; #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
  31 + - else
  32 + %span.light (#{merge_request.source_branch} &rarr; #{merge_request.target_branch})
30 - @issues.each do |issue| 33 - @issues.each do |issue|
31 %li 34 %li
32 issue: 35 issue:
app/views/shared/_merge_requests.html.haml
1 - if @merge_requests.any? 1 - if @merge_requests.any?
2 - - @merge_requests.group_by(&:project).each do |group| 2 + - @merge_requests.group_by(&:target_project).each do |group|
3 .ui-box 3 .ui-box
4 - project = group[0] 4 - project = group[0]
5 .title 5 .title
config/routes.rb
@@ -257,6 +257,7 @@ Gitlab::Application.routes.draw do @@ -257,6 +257,7 @@ Gitlab::Application.routes.draw do
257 collection do 257 collection do
258 get :branch_from 258 get :branch_from
259 get :branch_to 259 get :branch_to
  260 + get :update_branches
260 end 261 end
261 end 262 end
262 263
db/fixtures/development/10_merge_requests.rb
@@ -23,7 +23,8 @@ Gitlab::Seeder.quiet do @@ -23,7 +23,8 @@ Gitlab::Seeder.quiet do
23 id: i, 23 id: i,
24 source_branch: branches.first, 24 source_branch: branches.first,
25 target_branch: branches.last, 25 target_branch: branches.last,
26 - project_id: project.id, 26 + source_project_id: project.id,
  27 + target_project_id: project.id,
27 author_id: user_id, 28 author_id: user_id,
28 assignee_id: user_id, 29 assignee_id: user_id,
29 milestone: project.milestones.sample, 30 milestone: project.milestones.sample,
db/fixtures/test/001_repo.rb
@@ -19,5 +19,18 @@ FileUtils.cd(REPO_PATH) do @@ -19,5 +19,18 @@ FileUtils.cd(REPO_PATH) do
19 # Remove the copy 19 # Remove the copy
20 FileUtils.rm(SEED_REPO) 20 FileUtils.rm(SEED_REPO)
21 end 21 end
  22 +puts ' done.'
  23 +print "Creating seed satellite..."
  24 +
  25 +SATELLITE_PATH = Rails.root.join('tmp', 'satellite')
  26 +# Make directory
  27 +FileUtils.mkdir_p(SATELLITE_PATH)
  28 +# Clear any potential directory
  29 +FileUtils.rm_rf("#{SATELLITE_PATH}/gitlabhq")
  30 +# Chdir, clone from the seed
  31 +FileUtils.cd(SATELLITE_PATH) do
  32 + # Clone the satellite
22 33
  34 + `git clone --quiet #{REPO_PATH}/gitlabhq #{SATELLITE_PATH}/gitlabhq`
  35 +end
23 puts ' done.' 36 puts ' done.'
db/migrate/20130419190306_allow_merges_for_forks.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class AllowMergesForForks < ActiveRecord::Migration
  2 +
  3 + def self.up
  4 + add_column :merge_requests, :target_project_id, :integer, :null => false
  5 + MergeRequest.update_all("target_project_id = project_id")
  6 + rename_column :merge_requests, :project_id, :source_project_id
  7 + end
  8 +
  9 + def self.down
  10 + remove_column :merge_requests, :target_project_id
  11 + rename_column :merge_requests, :source_project_id,:project_id
  12 + end
  13 +
  14 +end
@@ -84,9 +84,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do @@ -84,9 +84,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do
84 add_index "keys", ["user_id"], :name => "index_keys_on_user_id" 84 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
85 85
86 create_table "merge_requests", :force => true do |t| 86 create_table "merge_requests", :force => true do |t|
87 - t.string "target_branch", :null => false  
88 - t.string "source_branch", :null => false  
89 - t.integer "project_id", :null => false 87 + t.string "target_branch", :null => false
  88 + t.string "source_branch", :null => false
  89 + t.integer "source_project_id", :null => false
90 t.integer "author_id" 90 t.integer "author_id"
91 t.integer "assignee_id" 91 t.integer "assignee_id"
92 t.string "title" 92 t.string "title"
@@ -97,14 +97,15 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do @@ -97,14 +97,15 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do
97 t.integer "milestone_id" 97 t.integer "milestone_id"
98 t.string "state" 98 t.string "state"
99 t.string "merge_status" 99 t.string "merge_status"
  100 + t.integer "target_project_id", :null => false
100 end 101 end
101 102
102 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" 103 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
103 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" 104 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
104 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" 105 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
105 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" 106 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
106 - add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"  
107 add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch" 107 add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch"
  108 + add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id"
108 add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch" 109 add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch"
109 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" 110 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
110 111
features/dashboard/dashboard.feature
@@ -16,6 +16,7 @@ Feature: Dashboard @@ -16,6 +16,7 @@ Feature: Dashboard
16 And I visit dashboard page 16 And I visit dashboard page
17 Then I should see groups list 17 Then I should see groups list
18 18
  19 + @javascript
19 Scenario: I should see last push widget 20 Scenario: I should see last push widget
20 Then I should see last push widget 21 Then I should see last push widget
21 And I click "Create Merge Request" link 22 And I click "Create Merge Request" link
features/project/forked_merge_requests.feature 0 → 100644
@@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
  1 +Feature: Project Forked Merge Requests
  2 + Background:
  3 + Given I sign in as a user
  4 + And I am a member of project "Shop"
  5 + And I have a project forked off of "Shop" called "Forked Shop"
  6 +
  7 + @javascript
  8 + Scenario: I can visit the target projects commit for a forked merge request
  9 + Given I visit project "Forked Shop" merge requests page
  10 + And I click link "New Merge Request"
  11 + And I fill out a "Merge Request On Forked Project" merge request
  12 + And I follow the target commit link
  13 + Then I should see the commit under the forked from project
  14 +
  15 + @javascript
  16 + Scenario: I submit new unassigned merge request to a forked project
  17 + Given I visit project "Forked Shop" merge requests page
  18 + And I click link "New Merge Request"
  19 + And I fill out a "Merge Request On Forked Project" merge request
  20 + And I submit the merge request
  21 + Then I should see merge request "Merge Request On Forked Project"
  22 +
  23 + @javascript
  24 + Scenario: I should see a push widget for forked merge requests
  25 + Given project "Forked Shop" has push event
  26 + And I visit dashboard page
  27 + Then I should see last push widget
  28 + And I click "Create Merge Request on fork" link
  29 + Then I see prefilled new Merge Request page for the forked project
  30 +
  31 + @javascript
  32 + Scenario: I can edit a forked merge request
  33 + Given I visit project "Forked Shop" merge requests page
  34 + And I click link "New Merge Request"
  35 + And I fill out a "Merge Request On Forked Project" merge request
  36 + And I submit the merge request
  37 + And I should see merge request "Merge Request On Forked Project"
  38 + And I click link edit "Merge Request On Forked Project"
  39 + Then I see the edit page prefilled for "Merge Request On Forked Project"
  40 + And I update the merge request title
  41 + And I save the merge request
  42 + Then I should see the edited merge request
  43 +
  44 + @javascript
  45 + Scenario: I cannot submit an invalid merge request
  46 + Given I visit project "Forked Shop" merge requests page
  47 + And I click link "New Merge Request"
  48 + And I fill out an invalid "Merge Request On Forked Project" merge request
  49 + And I submit the merge request
  50 + Then I should see validation errors
0 \ No newline at end of file 51 \ No newline at end of file
features/project/merge_requests.feature
@@ -29,6 +29,7 @@ Feature: Project Merge Requests @@ -29,6 +29,7 @@ Feature: Project Merge Requests
29 And I click link "Close" 29 And I click link "Close"
30 Then I should see closed merge request "Bug NS-04" 30 Then I should see closed merge request "Bug NS-04"
31 31
  32 + @javascript
32 Scenario: I submit new unassigned merge request 33 Scenario: I submit new unassigned merge request
33 Given I click link "New Merge Request" 34 Given I click link "New Merge Request"
34 And I submit new merge request "Wiki Feature" 35 And I submit new merge request "Wiki Feature"
features/steps/dashboard/dashboard.rb
@@ -22,6 +22,7 @@ class Dashboard &lt; Spinach::FeatureSteps @@ -22,6 +22,7 @@ class Dashboard &lt; Spinach::FeatureSteps
22 22
23 Then 'I see prefilled new Merge Request page' do 23 Then 'I see prefilled new Merge Request page' do
24 current_path.should == new_project_merge_request_path(@project) 24 current_path.should == new_project_merge_request_path(@project)
  25 + find("#merge_request_target_project_id").value.should == @project.id.to_s
25 find("#merge_request_source_branch").value.should == "new_design" 26 find("#merge_request_source_branch").value.should == "new_design"
26 find("#merge_request_target_branch").value.should == "master" 27 find("#merge_request_target_branch").value.should == "master"
27 find("#merge_request_title").value.should == "New Design" 28 find("#merge_request_title").value.should == "New Design"
features/steps/dashboard/dashboard_event_filters.rb
@@ -61,7 +61,7 @@ class EventFilters &lt; Spinach::FeatureSteps @@ -61,7 +61,7 @@ class EventFilters &lt; Spinach::FeatureSteps
61 end 61 end
62 62
63 And 'this project has merge request event' do 63 And 'this project has merge request event' do
64 - merge_request = create :merge_request, author: @user, project: @project 64 + merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
65 Event.create( 65 Event.create(
66 project: @project, 66 project: @project,
67 action: Event::MERGED, 67 action: Event::MERGED,
features/steps/dashboard/dashboard_merge_requests.rb
@@ -6,18 +6,24 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps @@ -6,18 +6,24 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps
6 merge_requests = @user.merge_requests 6 merge_requests = @user.merge_requests
7 merge_requests.each do |mr| 7 merge_requests.each do |mr|
8 page.should have_content(mr.title[0..10]) 8 page.should have_content(mr.title[0..10])
9 - page.should have_content(mr.project.name) 9 + page.should have_content(mr.target_project.name)
  10 + page.should have_content(mr.source_project.name)
10 end 11 end
11 end 12 end
12 13
13 And 'I have authored merge requests' do 14 And 'I have authored merge requests' do
14 - project1 = create :project  
15 - project2 = create :project 15 + project1_source = create :project
  16 + project1_target= create :project
  17 + project2_source = create :project
  18 + project2_target = create :project
16 19
17 - project1.team << [@user, :master]  
18 - project2.team << [@user, :master]  
19 20
20 - merge_request1 = create :merge_request, author: @user, project: project1  
21 - merge_request2 = create :merge_request, author: @user, project: project2 21 + project1_source.team << [@user, :master]
  22 + project1_target.team << [@user, :master]
  23 + project2_source.team << [@user, :master]
  24 + project2_target.team << [@user, :master]
  25 +
  26 + merge_request1 = create :merge_request, author: @user, source_project: project1_source, target_project: project1_target
  27 + merge_request2 = create :merge_request, author: @user, source_project: project2_source, target_project: project2_target
22 end 28 end
23 end 29 end
features/steps/group/group.rb
@@ -60,7 +60,8 @@ class Groups &lt; Spinach::FeatureSteps @@ -60,7 +60,8 @@ class Groups &lt; Spinach::FeatureSteps
60 60
61 Given 'project from group has merge requests assigned to me' do 61 Given 'project from group has merge requests assigned to me' do
62 create :merge_request, 62 create :merge_request,
63 - project: project, 63 + source_project: project,
  64 + target_project: project,
64 assignee: current_user, 65 assignee: current_user,
65 author: current_user 66 author: current_user
66 end 67 end
features/steps/project/project_fork.rb
@@ -4,6 +4,8 @@ class ForkProject &lt; Spinach::FeatureSteps @@ -4,6 +4,8 @@ class ForkProject &lt; Spinach::FeatureSteps
4 include SharedProject 4 include SharedProject
5 5
6 step 'I click link "Fork"' do 6 step 'I click link "Fork"' do
  7 + page.should have_content "Shop"
  8 + page.should have_content "Fork"
7 Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true) 9 Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true)
8 click_link "Fork" 10 click_link "Fork"
9 end 11 end
@@ -17,9 +19,13 @@ class ForkProject &lt; Spinach::FeatureSteps @@ -17,9 +19,13 @@ class ForkProject &lt; Spinach::FeatureSteps
17 step 'I should see the forked project page' do 19 step 'I should see the forked project page' do
18 page.should have_content "Project was successfully forked." 20 page.should have_content "Project was successfully forked."
19 current_path.should include current_user.namespace.path 21 current_path.should include current_user.namespace.path
  22 + @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
20 end 23 end
21 24
22 step 'I already have a project named "Shop" in my namespace' do 25 step 'I already have a project named "Shop" in my namespace' do
  26 + current_user.namespace ||= create(:namespace)
  27 + current_user.namespace.should_not be_nil
  28 + current_user.namespace.path.should_not be_nil
23 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace) 29 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
24 end 30 end
25 31
features/steps/project/project_forked_merge_requests.rb 0 → 100644
@@ -0,0 +1,198 @@ @@ -0,0 +1,198 @@
  1 +class ProjectForkedMergeRequests < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedNote
  5 + include SharedPaths
  6 +
  7 + Given 'I am a member of project "Shop"' do
  8 + @project = Project.find_by_name "Shop"
  9 + @project ||= create(:project_with_code, name: "Shop")
  10 + @project.team << [@user, :reporter]
  11 + end
  12 +
  13 + And 'I have a project forked off of "Shop" called "Forked Shop"' do
  14 + @forking_user = @user
  15 + forked_project_link = build(:forked_project_link)
  16 + @forked_project = Project.find_by_name "Forked Shop"
  17 + @forked_project ||= create(:source_project_with_code, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id , namespace: @forking_user.namespace)
  18 + forked_project_link.forked_from_project = @project
  19 + forked_project_link.forked_to_project = @forked_project
  20 + @forked_project.team << [@forking_user , :master]
  21 + forked_project_link.save!
  22 + end
  23 +
  24 + Given 'I click link "New Merge Request"' do
  25 + click_link "New Merge Request"
  26 + end
  27 +
  28 + Then 'I should see merge request "Merge Request On Forked Project"' do
  29 + page.should have_content "Merge Request On Forked Project"
  30 + @project.merge_requests.size.should >= 1
  31 + @merge_request = @project.merge_requests.last
  32 + current_path.should == project_merge_request_path(@project, @merge_request)
  33 + @merge_request.title.should == "Merge Request On Forked Project"
  34 + @merge_request.source_project.should == @forked_project
  35 + @merge_request.source_branch.should == "master"
  36 + @merge_request.target_branch.should == "stable"
  37 + page.should have_content @forked_project.path_with_namespace
  38 + page.should have_content @project.path_with_namespace
  39 + page.should have_content @merge_request.source_branch
  40 + page.should have_content @merge_request.target_branch
  41 + end
  42 +
  43 + And 'I fill out a "Merge Request On Forked Project" merge request' do
  44 + #The ordering here is a bit whacky on purpose:
  45 + #Select the target right away, to give update_branches time to run and clean up the target_branches
  46 + find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
  47 + select @project.path_with_namespace, from: "merge_request_target_project_id"
  48 +
  49 +
  50 + fill_in "merge_request_title", with: "Merge Request On Forked Project"
  51 + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
  52 +
  53 + find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
  54 +
  55 + #Ensure the option exists in the select
  56 + find(:select, "merge_request_source_branch", {}).should have_content "master"
  57 + select "master", from: "merge_request_source_branch"
  58 + #Ensure the option is selected
  59 + find(:select, "merge_request_source_branch", {}).value.should have_content "master"
  60 + verify_commit_link(".mr_source_commit",@forked_project)
  61 +
  62 +
  63 + #This could fail if the javascript hasn't run yet, there is a timing issue here -- this is why we do the select at the top
  64 + #Ensure the option exists in the select
  65 + find(:select, "merge_request_target_branch", {}).should have_content "stable"
  66 + #We must give apparently lots of time for update branches to finish
  67 +
  68 + (find(:select, "merge_request_target_branch", {}).find(:option, "stable",{}).select_option).should be_true
  69 + #Ensure the option is selected
  70 + find(:select, "merge_request_target_branch", {}).value.should have_content "stable"
  71 + verify_commit_link(".mr_target_commit",@project)
  72 + end
  73 +
  74 + And 'I submit the merge request' do
  75 + click_button "Submit merge request"
  76 + end
  77 +
  78 + And 'I follow the target commit link' do
  79 + commit = @project.repository.commit
  80 + click_link commit.short_id(8)
  81 + end
  82 +
  83 + Then 'I should see the commit under the forked from project' do
  84 + commit = @project.repository.commit
  85 + page.should have_content(commit.message)
  86 + end
  87 +
  88 + And 'I click "Create Merge Request on fork" link' do
  89 + click_link "Create Merge Request on fork"
  90 + end
  91 +
  92 + Then 'I see prefilled new Merge Request page for the forked project' do
  93 + current_path.should == new_project_merge_request_path(@forked_project)
  94 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  95 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  96 + find("#merge_request_source_branch").value.should have_content "new_design"
  97 + find("#merge_request_target_branch").value.should have_content "master"
  98 + find("#merge_request_title").value.should == "New Design"
  99 + verify_commit_link(".mr_target_commit",@project)
  100 + verify_commit_link(".mr_source_commit",@forked_project)
  101 + end
  102 +
  103 + And 'I update the merge request title' do
  104 + fill_in "merge_request_title", with: "An Edited Forked Merge Request"
  105 + end
  106 +
  107 + And 'I save the merge request' do
  108 + click_button "Save changes"
  109 + end
  110 +
  111 + Then 'I should see the edited merge request' do
  112 + page.should have_content "An Edited Forked Merge Request"
  113 + @project.merge_requests.size.should >= 1
  114 + @merge_request = @project.merge_requests.last
  115 + current_path.should == project_merge_request_path(@project, @merge_request)
  116 + @merge_request.source_project.should == @forked_project
  117 + @merge_request.source_branch.should == "master"
  118 + @merge_request.target_branch.should == "stable"
  119 + page.should have_content @forked_project.path_with_namespace
  120 + page.should have_content @project.path_with_namespace
  121 + page.should have_content @merge_request.source_branch
  122 + page.should have_content @merge_request.target_branch
  123 + end
  124 +
  125 + Then 'I should see last push widget' do
  126 + page.should have_content "You pushed to new_design"
  127 + page.should have_link "Create Merge Request"
  128 + end
  129 +
  130 + Given 'project "Forked Shop" has push event' do
  131 + @forked_project = Project.find_by_name("Forked Shop")
  132 +
  133 + data = {
  134 + before: "0000000000000000000000000000000000000000",
  135 + after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
  136 + ref: "refs/heads/new_design",
  137 + user_id: @user.id,
  138 + user_name: @user.name,
  139 + repository: {
  140 + name: @forked_project.name,
  141 + url: "localhost/rubinius",
  142 + description: "",
  143 + homepage: "localhost/rubinius",
  144 + private: true
  145 + }
  146 + }
  147 +
  148 + @event = Event.create(
  149 + project: @forked_project,
  150 + action: Event::PUSHED,
  151 + data: data,
  152 + author_id: @user.id
  153 + )
  154 + end
  155 +
  156 +
  157 + Then 'I click link edit "Merge Request On Forked Project"' do
  158 + find("#edit_merge_request").click
  159 + end
  160 +
  161 + Then 'I see the edit page prefilled for "Merge Request On Forked Project"' do
  162 + current_path.should == edit_project_merge_request_path(@project, @merge_request)
  163 + page.should have_content "Edit merge request #{@merge_request.id}"
  164 + find("#merge_request_title").value.should == "Merge Request On Forked Project"
  165 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  166 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  167 + find("#merge_request_source_branch").value.should have_content "master"
  168 + verify_commit_link(".mr_source_commit",@forked_project)
  169 + find("#merge_request_target_branch").value.should have_content "stable"
  170 + verify_commit_link(".mr_target_commit",@project)
  171 + end
  172 +
  173 + And 'I fill out an invalid "Merge Request On Forked Project" merge request' do
  174 + #If this isn't filled in the rest of the validations won't be triggered
  175 + fill_in "merge_request_title", with: "Merge Request On Forked Project"
  176 + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
  177 + find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
  178 + find(:select, "merge_request_source_branch", {}).value.should == ""
  179 + find(:select, "merge_request_target_branch", {}).value.should == ""
  180 + end
  181 +
  182 + Then 'I should see validation errors' do
  183 + page.should have_content "Source branch can't be blank"
  184 + page.should have_content "Target branch can't be blank"
  185 + page.should have_content "Branch conflict You can not use same project/branch for source and target"
  186 + end
  187 +
  188 + def project
  189 + @project ||= Project.find_by_name!("Shop")
  190 + end
  191 +
  192 + #Verify a link is generated against the correct project
  193 + def verify_commit_link(container_div, container_project)
  194 + #This should force a wait for the javascript to execute
  195 + find(:div,container_div).should have_css ".browse_code_link_holder"
  196 + find(:div,container_div).find(".commit_short_id")['href'].should have_content "#{container_project.path_with_namespace}/commit"
  197 + end
  198 +end
features/steps/project/project_merge_requests.rb
@@ -56,30 +56,41 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -56,30 +56,41 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
56 end 56 end
57 57
58 And 'I submit new merge request "Wiki Feature"' do 58 And 'I submit new merge request "Wiki Feature"' do
59 - fill_in "merge_request_title", with: "Wiki Feature"  
60 - select "bootstrap", from: "merge_request_source_branch"  
61 - select "master", from: "merge_request_target_branch" 59 + #this must come first, so that the target branch is set by the time the "select" for "notes_refactoring" is executed
  60 + select project.path_with_namespace, :from => "merge_request_target_project_id"
  61 + fill_in "merge_request_title", :with => "Wiki Feature"
  62 + select "master", :from => "merge_request_source_branch"
  63 + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
  64 + find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s
  65 +
  66 + #using "notes_refactoring" because "Bug NS-04" uses master/stable, this will fail merge_request validation if the branches are the same
  67 + find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring"
  68 + select "notes_refactoring", :from => "merge_request_target_branch"
  69 +
62 click_button "Submit merge request" 70 click_button "Submit merge request"
63 end 71 end
64 72
65 And 'project "Shop" have "Bug NS-04" open merge request' do 73 And 'project "Shop" have "Bug NS-04" open merge request' do
66 create(:merge_request, 74 create(:merge_request,
67 title: "Bug NS-04", 75 title: "Bug NS-04",
68 - project: project, 76 + source_project: project,
  77 + target_project: project,
69 author: project.users.first) 78 author: project.users.first)
70 end 79 end
71 80
72 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do 81 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
73 create(:merge_request_with_diffs, 82 create(:merge_request_with_diffs,
74 title: "Bug NS-05", 83 title: "Bug NS-05",
75 - project: project, 84 + source_project: project,
  85 + target_project: project,
76 author: project.users.first) 86 author: project.users.first)
77 end 87 end
78 88
79 And 'project "Shop" have "Feature NS-03" closed merge request' do 89 And 'project "Shop" have "Feature NS-03" closed merge request' do
80 create(:closed_merge_request, 90 create(:closed_merge_request,
81 title: "Feature NS-03", 91 title: "Feature NS-03",
82 - project: project, 92 + source_project: project,
  93 + target_project: project,
83 author: project.users.first) 94 author: project.users.first)
84 end 95 end
85 96
features/steps/shared/paths.rb
@@ -184,6 +184,10 @@ module SharedPaths @@ -184,6 +184,10 @@ module SharedPaths
184 visit project_path(project) 184 visit project_path(project)
185 end 185 end
186 186
  187 + step 'I visit project "Forked Shop" merge requests page' do
  188 + visit project_merge_requests_path(@forked_project)
  189 + end
  190 +
187 step 'I visit edit project "Shop" page' do 191 step 'I visit edit project "Shop" page' do
188 visit edit_project_path(project) 192 visit edit_project_path(project)
189 end 193 end
@@ -239,18 +243,22 @@ module SharedPaths @@ -239,18 +243,22 @@ module SharedPaths
239 243
240 step 'I visit merge request page "Bug NS-04"' do 244 step 'I visit merge request page "Bug NS-04"' do
241 mr = MergeRequest.find_by_title("Bug NS-04") 245 mr = MergeRequest.find_by_title("Bug NS-04")
242 - visit project_merge_request_path(mr.project, mr) 246 + visit project_merge_request_path(mr.target_project, mr)
243 end 247 end
244 248
245 step 'I visit merge request page "Bug NS-05"' do 249 step 'I visit merge request page "Bug NS-05"' do
246 mr = MergeRequest.find_by_title("Bug NS-05") 250 mr = MergeRequest.find_by_title("Bug NS-05")
247 - visit project_merge_request_path(mr.project, mr) 251 + visit project_merge_request_path(mr.target_project, mr)
248 end 252 end
249 253
250 step 'I visit project "Shop" merge requests page' do 254 step 'I visit project "Shop" merge requests page' do
251 visit project_merge_requests_path(project) 255 visit project_merge_requests_path(project)
252 end 256 end
253 257
  258 + step 'I visit forked project "Shop" merge requests page' do
  259 + visit project_merge_requests_path(project)
  260 + end
  261 +
254 step 'I visit project "Shop" milestones page' do 262 step 'I visit project "Shop" milestones page' do
255 visit project_milestones_path(project) 263 visit project_milestones_path(project)
256 end 264 end
features/support/env.rb
@@ -35,8 +35,7 @@ Capybara.ignore_hidden_elements = false @@ -35,8 +35,7 @@ Capybara.ignore_hidden_elements = false
35 DatabaseCleaner.strategy = :truncation 35 DatabaseCleaner.strategy = :truncation
36 36
37 Spinach.hooks.before_scenario do 37 Spinach.hooks.before_scenario do
38 - TestEnv.init(mailer: false)  
39 - 38 + TestEnv.setup_stubs
40 DatabaseCleaner.start 39 DatabaseCleaner.start
41 end 40 end
42 41
@@ -45,6 +44,7 @@ Spinach.hooks.after_scenario do @@ -45,6 +44,7 @@ Spinach.hooks.after_scenario do
45 end 44 end
46 45
47 Spinach.hooks.before_run do 46 Spinach.hooks.before_run do
  47 + TestEnv.init(mailer: false, init_repos: true, repos: false)
48 RSpec::Mocks::setup self 48 RSpec::Mocks::setup self
49 49
50 include FactoryGirl::Syntax::Methods 50 include FactoryGirl::Syntax::Methods
lib/api/merge_requests.rb
@@ -14,6 +14,14 @@ module API @@ -14,6 +14,14 @@ module API
14 end 14 end
15 not_found! 15 not_found!
16 end 16 end
  17 +
  18 + def not_fork?(target_project_id, user_project)
  19 + target_project_id.nil? || target_project_id == user_project.id.to_s
  20 + end
  21 +
  22 + def target_matches_fork(target_project_id,user_project)
  23 + user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
  24 + end
17 end 25 end
18 26
19 # List merge requests 27 # List merge requests
@@ -51,9 +59,10 @@ module API @@ -51,9 +59,10 @@ module API
51 # 59 #
52 # Parameters: 60 # Parameters:
53 # 61 #
54 - # id (required) - The ID of a project 62 + # id (required) - The ID of a project - this will be the source of the merge request
55 # source_branch (required) - The source branch 63 # source_branch (required) - The source branch
56 # target_branch (required) - The target branch 64 # target_branch (required) - The target branch
  65 + # target_project - The target project of the merge request defaults to the :id of the project
57 # assignee_id - Assignee user ID 66 # assignee_id - Assignee user ID
58 # title (required) - Title of MR 67 # title (required) - Title of MR
59 # 68 #
@@ -63,10 +72,20 @@ module API @@ -63,10 +72,20 @@ module API
63 post ":id/merge_requests" do 72 post ":id/merge_requests" do
64 authorize! :write_merge_request, user_project 73 authorize! :write_merge_request, user_project
65 required_attributes! [:source_branch, :target_branch, :title] 74 required_attributes! [:source_branch, :target_branch, :title]
66 -  
67 - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] 75 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
68 merge_request = user_project.merge_requests.new(attrs) 76 merge_request = user_project.merge_requests.new(attrs)
69 merge_request.author = current_user 77 merge_request.author = current_user
  78 + merge_request.source_project = user_project
  79 + target_project_id = attrs[:target_project_id]
  80 + if not_fork?(target_project_id, user_project)
  81 + merge_request.target_project = user_project
  82 + else
  83 + if target_matches_fork(target_project_id,user_project)
  84 + merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
  85 + else
  86 + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
  87 + end
  88 + end
70 89
71 if merge_request.save 90 if merge_request.save
72 merge_request.reload_code 91 merge_request.reload_code
lib/gitlab/satellite/action.rb
@@ -25,25 +25,31 @@ module Gitlab @@ -25,25 +25,31 @@ module Gitlab
25 end 25 end
26 end 26 end
27 rescue Errno::ENOMEM => ex 27 rescue Errno::ENOMEM => ex
28 - Gitlab::GitLogger.error(ex.message)  
29 - return false 28 + return handle_exception(ex)
30 rescue Grit::Git::GitTimeout => ex 29 rescue Grit::Git::GitTimeout => ex
31 - Gitlab::GitLogger.error(ex.message)  
32 - return false 30 + return handle_exception(ex)
33 ensure 31 ensure
34 Gitlab::ShellEnv.reset_env 32 Gitlab::ShellEnv.reset_env
35 end 33 end
36 34
37 - # * Clears the satellite  
38 - # * Updates the satellite from Gitolite 35 + # * Recreates the satellite
39 # * Sets up Git variables for the user 36 # * Sets up Git variables for the user
40 # 37 #
41 # Note: use this within #in_locked_and_timed_satellite 38 # Note: use this within #in_locked_and_timed_satellite
42 def prepare_satellite!(repo) 39 def prepare_satellite!(repo)
43 project.satellite.clear_and_update! 40 project.satellite.clear_and_update!
44 41
45 - repo.git.config({}, "user.name", user.name)  
46 - repo.git.config({}, "user.email", user.email) 42 + repo.config['user.name'] = user.name
  43 + repo.config['user.email'] = user.email
  44 + end
  45 +
  46 + def default_options(options = {})
  47 + {raise: true, timeout: true}.merge(options)
  48 + end
  49 +
  50 + def handle_exception(exception)
  51 + Gitlab::GitLogger.error(exception.message)
  52 + false
47 end 53 end
48 end 54 end
49 end 55 end
lib/gitlab/satellite/merge_action.rb
@@ -5,48 +5,118 @@ module Gitlab @@ -5,48 +5,118 @@ module Gitlab
5 attr_accessor :merge_request 5 attr_accessor :merge_request
6 6
7 def initialize(user, merge_request) 7 def initialize(user, merge_request)
8 - super user, merge_request.project 8 + super user, merge_request.target_project
9 @merge_request = merge_request 9 @merge_request = merge_request
10 end 10 end
11 11
12 # Checks if a merge request can be executed without user interaction 12 # Checks if a merge request can be executed without user interaction
13 def can_be_merged? 13 def can_be_merged?
14 in_locked_and_timed_satellite do |merge_repo| 14 in_locked_and_timed_satellite do |merge_repo|
  15 + prepare_satellite!(merge_repo)
15 merge_in_satellite!(merge_repo) 16 merge_in_satellite!(merge_repo)
16 end 17 end
17 end 18 end
18 19
19 # Merges the source branch into the target branch in the satellite and 20 # Merges the source branch into the target branch in the satellite and
20 - # pushes it back to Gitolite.  
21 - # It also removes the source branch if requested in the merge request. 21 + # pushes it back to the repository.
  22 + # It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
22 # 23 #
23 # Returns false if the merge produced conflicts 24 # Returns false if the merge produced conflicts
24 - # Returns false if pushing from the satellite to Gitolite failed or was rejected 25 + # Returns false if pushing from the satellite to the repository failed or was rejected
25 # Returns true otherwise 26 # Returns true otherwise
26 def merge! 27 def merge!
27 in_locked_and_timed_satellite do |merge_repo| 28 in_locked_and_timed_satellite do |merge_repo|
  29 + prepare_satellite!(merge_repo)
28 if merge_in_satellite!(merge_repo) 30 if merge_in_satellite!(merge_repo)
29 # push merge back to Gitolite 31 # push merge back to Gitolite
30 # will raise CommandFailed when push fails 32 # will raise CommandFailed when push fails
31 - merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)  
32 - 33 + merge_repo.git.push(default_options, :origin, merge_request.target_branch)
33 # remove source branch 34 # remove source branch
34 if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch) 35 if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
35 # will raise CommandFailed when push fails 36 # will raise CommandFailed when push fails
36 - merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}") 37 + merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
37 end 38 end
38 -  
39 # merge, push and branch removal successful 39 # merge, push and branch removal successful
40 true 40 true
41 end 41 end
42 end 42 end
43 rescue Grit::Git::CommandFailed => ex 43 rescue Grit::Git::CommandFailed => ex
44 - Gitlab::GitLogger.error(ex.message)  
45 - false 44 + handle_exception(ex)
46 end 45 end
47 46
48 - private 47 + # Get a raw diff of the source to the target
  48 + def diff_in_satellite
  49 + in_locked_and_timed_satellite do |merge_repo|
  50 + prepare_satellite!(merge_repo)
  51 +
  52 + update_satellite_source_and_target!(merge_repo)
  53 + if merge_request.for_fork?
  54 + diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  55 + else
  56 + diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
  57 +
  58 + end
  59 + return diff
  60 + end
  61 + rescue Grit::Git::CommandFailed => ex
  62 + handle_exception(ex)
  63 + end
  64 +
  65 + # Only show what is new in the source branch compared to the target branch, not the other way around.
  66 + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
  67 + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
  68 + def diffs_between_satellite
  69 + in_locked_and_timed_satellite do |merge_repo|
  70 + prepare_satellite!(merge_repo)
  71 + update_satellite_source_and_target!(merge_repo)
  72 + if merge_request.for_fork?
  73 + common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
  74 + #this method doesn't take default options
  75 + diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}")
  76 + else
  77 + raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
  78 + end
  79 + diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
  80 + return diffs
  81 + end
  82 + rescue Grit::Git::CommandFailed => ex
  83 + handle_exception(ex)
  84 + end
  85 +
  86 + # Get commit as an email patch
  87 + def format_patch
  88 + in_locked_and_timed_satellite do |merge_repo|
  89 + prepare_satellite!(merge_repo)
  90 + update_satellite_source_and_target!(merge_repo)
  91 + if (merge_request.for_fork?)
  92 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}...source/#{merge_request.source_branch}")
  93 + else
  94 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}...#{merge_request.source_branch}")
  95 + end
  96 + return patch
  97 + end
  98 + rescue Grit::Git::CommandFailed => ex
  99 + handle_exception(ex)
  100 + end
  101 +
  102 + # Retrieve an array of commits between the source and the target
  103 + def commits_between
  104 + in_locked_and_timed_satellite do |merge_repo|
  105 + prepare_satellite!(merge_repo)
  106 + update_satellite_source_and_target!(merge_repo)
  107 + if (merge_request.for_fork?)
  108 + commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  109 + else
  110 + raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
  111 + end
  112 + commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
  113 + return commits
  114 + end
  115 + rescue Grit::Git::CommandFailed => ex
  116 + handle_exception(ex)
  117 + end
49 118
  119 + private
50 # Merges the source_branch into the target_branch in the satellite. 120 # Merges the source_branch into the target_branch in the satellite.
51 # 121 #
52 # Note: it will clear out the satellite before doing anything 122 # Note: it will clear out the satellite before doing anything
@@ -54,18 +124,35 @@ module Gitlab @@ -54,18 +124,35 @@ module Gitlab
54 # Returns false if the merge produced conflicts 124 # Returns false if the merge produced conflicts
55 # Returns true otherwise 125 # Returns true otherwise
56 def merge_in_satellite!(repo) 126 def merge_in_satellite!(repo)
57 - prepare_satellite!(repo)  
58 -  
59 - # create target branch in satellite at the corresponding commit from Gitolite  
60 - repo.git.checkout({raise: true, timeout: true, b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}") 127 + update_satellite_source_and_target!(repo)
61 128
62 - # merge the source branch from Gitolite into the satellite 129 + # merge the source branch into the satellite
63 # will raise CommandFailed when merge fails 130 # will raise CommandFailed when merge fails
64 - repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch) 131 + if merge_request.for_fork?
  132 + repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
  133 + else
  134 + repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
  135 + end
65 rescue Grit::Git::CommandFailed => ex 136 rescue Grit::Git::CommandFailed => ex
66 - Gitlab::GitLogger.error(ex.message)  
67 - false 137 + handle_exception(ex)
68 end 138 end
  139 +
  140 + # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
  141 + def update_satellite_source_and_target!(repo)
  142 + if merge_request.for_fork?
  143 + repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
  144 + repo.remote_fetch('source')
  145 + repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
  146 + else
  147 + # We can't trust the input here being branch names, we can't always check it out because it could be a relative ref i.e. HEAD~3
  148 + # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared)
  149 + repo.git.checkout(default_options, "#{merge_request.source_branch}")
  150 + repo.git.checkout(default_options, "#{merge_request.target_branch}")
  151 + end
  152 + rescue Grit::Git::CommandFailed => ex
  153 + handle_exception(ex)
  154 + end
  155 +
69 end 156 end
70 end 157 end
71 end 158 end
lib/gitlab/satellite/satellite.rb
1 module Gitlab 1 module Gitlab
2 - class SatelliteNotExistError < StandardError; end 2 + class SatelliteNotExistError < StandardError; end
3 3
4 module Satellite 4 module Satellite
5 class Satellite 5 class Satellite
@@ -24,8 +24,11 @@ module Gitlab @@ -24,8 +24,11 @@ module Gitlab
24 def clear_and_update! 24 def clear_and_update!
25 raise_no_satellite unless exists? 25 raise_no_satellite unless exists?
26 26
  27 + File.exists? path
  28 + @repo = nil
27 clear_working_dir! 29 clear_working_dir!
28 delete_heads! 30 delete_heads!
  31 + remove_remotes!
29 update_from_source! 32 update_from_source!
30 end 33 end
31 34
@@ -55,10 +58,11 @@ module Gitlab @@ -55,10 +58,11 @@ module Gitlab
55 raise_no_satellite unless exists? 58 raise_no_satellite unless exists?
56 59
57 File.open(lock_file, "w+") do |f| 60 File.open(lock_file, "w+") do |f|
58 - f.flock(File::LOCK_EX)  
59 -  
60 - Dir.chdir(path) do  
61 - return yield 61 + begin
  62 + f.flock File::LOCK_EX
  63 + Dir.chdir(path) { return yield }
  64 + ensure
  65 + f.flock File::LOCK_UN
62 end 66 end
63 end 67 end
64 end 68 end
@@ -100,20 +104,34 @@ module Gitlab @@ -100,20 +104,34 @@ module Gitlab
100 if heads.include? PARKING_BRANCH 104 if heads.include? PARKING_BRANCH
101 repo.git.checkout({}, PARKING_BRANCH) 105 repo.git.checkout({}, PARKING_BRANCH)
102 else 106 else
103 - repo.git.checkout({b: true}, PARKING_BRANCH) 107 + repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
104 end 108 end
105 109
106 # remove the parking branch from the list of heads ... 110 # remove the parking branch from the list of heads ...
107 heads.delete(PARKING_BRANCH) 111 heads.delete(PARKING_BRANCH)
108 # ... and delete all others 112 # ... and delete all others
109 - heads.each { |head| repo.git.branch({D: true}, head) } 113 + heads.each { |head| repo.git.branch(default_options({D: true}), head) }
  114 + end
  115 +
  116 + # Deletes all remotes except origin
  117 + #
  118 + # This ensures we have no remote name clashes or issues updating branches when
  119 + # working with the satellite.
  120 + def remove_remotes!
  121 + remotes = repo.git.remote.split(' ')
  122 + remotes.delete('origin')
  123 + remotes.each { |name| repo.git.remote(default_options,'rm', name)}
110 end 124 end
111 125
112 # Updates the satellite from Gitolite 126 # Updates the satellite from Gitolite
113 # 127 #
114 # Note: this will only update remote branches (i.e. origin/*) 128 # Note: this will only update remote branches (i.e. origin/*)
115 def update_from_source! 129 def update_from_source!
116 - repo.git.fetch({timeout: true}, :origin) 130 + repo.git.fetch(default_options, :origin)
  131 + end
  132 +
  133 + def default_options(options = {})
  134 + {raise: true, timeout: true}.merge(options)
117 end 135 end
118 136
119 # Create directory for storing 137 # Create directory for storing
spec/contexts/filter_context_spec.rb 0 → 100644
@@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
  1 +require 'spec_helper'
  2 +
  3 +describe FilterContext do
  4 +
  5 + let(:user) { create :user }
  6 + let(:user2) { create :user }
  7 + let(:project1) { create(:project, creator_id: user.id) }
  8 + let(:project2) { create(:project, creator_id: user.id) }
  9 + let(:merge_request1) { create(:merge_request, author_id: user.id, source_project: project1, target_project: project2) }
  10 + let(:merge_request2) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project1) }
  11 + let(:merge_request3) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project2) }
  12 + let(:merge_request4) { create(:merge_request, author_id: user2.id, source_project: project2, target_project: project2, target_branch:"notes_refactoring") }
  13 + let(:issue1) { create(:issue, assignee_id: user.id, project: project1) }
  14 + let(:issue2) { create(:issue, assignee_id: user.id, project: project2) }
  15 + let(:issue3) { create(:issue, assignee_id: user2.id, project: project2) }
  16 +
  17 + describe 'merge requests' do
  18 + before :each do
  19 + merge_request1
  20 + merge_request2
  21 + merge_request3
  22 + merge_request4
  23 + end
  24 + it 'should by default filter properly' do
  25 + merge_requests = user.cared_merge_requests
  26 + params ={}
  27 + merge_requests = FilterContext.new(merge_requests, params).execute
  28 + merge_requests.size.should == 3
  29 + end
  30 + it 'should apply blocks passed in on creation to the filters' do
  31 + merge_requests = user.cared_merge_requests
  32 + params = {:project_id => project1.id}
  33 + merge_requests = FilterContext.new(merge_requests, params).execute
  34 + merge_requests.size.should == 2
  35 + end
  36 + end
  37 +
  38 + describe 'issues' do
  39 + before :each do
  40 + issue1
  41 + issue2
  42 + issue3
  43 + end
  44 + it 'should by default filter projects properly' do
  45 + issues = user.assigned_issues
  46 + params = {}
  47 + issues = FilterContext.new(issues, params).execute
  48 + issues.size.should == 2
  49 + end
  50 + it 'should apply blocks passed in on creation to the filters' do
  51 + issues = user.assigned_issues
  52 + params = {:project_id => project1.id}
  53 + issues = FilterContext.new(issues, params).execute
  54 + issues.size.should == 1
  55 + end
  56 + end
  57 +end
0 \ No newline at end of file 58 \ No newline at end of file
spec/controllers/commit_controller_spec.rb
@@ -2,12 +2,11 @@ require &#39;spec_helper&#39; @@ -2,12 +2,11 @@ require &#39;spec_helper&#39;
2 2
3 describe Projects::CommitController do 3 describe Projects::CommitController do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 - let(:user) { create(:user) }  
6 - let(:commit) { project.repository.last_commit_for("master") } 5 + let(:user) { create(:user) }
  6 + let(:commit) { project.repository.last_commit_for("master") }
7 7
8 before do 8 before do
9 sign_in(user) 9 sign_in(user)
10 -  
11 project.team << [user, :master] 10 project.team << [user, :master]
12 end 11 end
13 12
spec/controllers/commits_controller_spec.rb
@@ -2,7 +2,7 @@ require &#39;spec_helper&#39; @@ -2,7 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe Projects::CommitsController do 3 describe Projects::CommitsController do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 - let(:user) { create(:user) } 5 + let(:user) { create(:user) }
6 6
7 before do 7 before do
8 sign_in(user) 8 sign_in(user)
spec/controllers/merge_requests_controller_spec.rb
@@ -3,7 +3,7 @@ require &#39;spec_helper&#39; @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 describe Projects::MergeRequestsController do 3 describe Projects::MergeRequestsController do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 let(:user) { create(:user) } 5 let(:user) { create(:user) }
6 - let(:merge_request) { create(:merge_request_with_diffs, project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") } 6 + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") }
7 7
8 before do 8 before do
9 sign_in(user) 9 sign_in(user)
@@ -28,7 +28,7 @@ describe Projects::MergeRequestsController do @@ -28,7 +28,7 @@ describe Projects::MergeRequestsController do
28 it "should render it" do 28 it "should render it" do
29 get :show, project_id: project.code, id: merge_request.id, format: format 29 get :show, project_id: project.code, id: merge_request.id, format: format
30 30
31 - expect(response.body).to eq(merge_request.send(:"to_#{format}")) 31 + expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s)
32 end 32 end
33 33
34 it "should not escape Html" do 34 it "should not escape Html" do
spec/factories.rb
@@ -29,8 +29,19 @@ FactoryGirl.define do @@ -29,8 +29,19 @@ FactoryGirl.define do
29 sequence(:name) { |n| "project#{n}" } 29 sequence(:name) { |n| "project#{n}" }
30 path { name.downcase.gsub(/\s/, '_') } 30 path { name.downcase.gsub(/\s/, '_') }
31 creator 31 creator
  32 +
  33 + trait :source do
  34 + sequence(:name) { |n| "source project#{n}" }
  35 + end
  36 + trait :target do
  37 + sequence(:name) { |n| "target project#{n}" }
  38 + end
  39 +
  40 + factory :source_project, traits: [:source]
  41 + factory :target_project, traits: [:target]
32 end 42 end
33 43
  44 +
34 factory :redmine_project, parent: :project do 45 factory :redmine_project, parent: :project do
35 issues_tracker { "redmine" } 46 issues_tracker { "redmine" }
36 issues_tracker_id { "project_name_in_redmine" } 47 issues_tracker_id { "project_name_in_redmine" }
@@ -39,11 +50,20 @@ FactoryGirl.define do @@ -39,11 +50,20 @@ FactoryGirl.define do
39 factory :project_with_code, parent: :project do 50 factory :project_with_code, parent: :project do
40 path { 'gitlabhq' } 51 path { 'gitlabhq' }
41 52
  53 + trait :source_path do
  54 + path { 'source_gitlabhq' }
  55 + end
  56 +
  57 + trait :target_path do
  58 + path { 'target_gitlabhq' }
  59 + end
  60 +
  61 + factory :source_project_with_code, traits: [:source, :source_path]
  62 + factory :target_project_with_code, traits: [:target, :target_path]
  63 +
42 after :create do |project| 64 after :create do |project|
43 - repos_path = Rails.root.join('tmp', 'test-git-base-path')  
44 - seed_repo = Rails.root.join('tmp', 'repositories', 'gitlabhq')  
45 - target_repo = File.join(repos_path, project.path_with_namespace + '.git')  
46 - system("ln -s #{seed_repo} #{target_repo}") 65 + TestEnv.clear_repo_dir(project.namespace, project.path)
  66 + TestEnv.create_repo(project.namespace, project.path)
47 end 67 end
48 end 68 end
49 69
@@ -86,7 +106,8 @@ FactoryGirl.define do @@ -86,7 +106,8 @@ FactoryGirl.define do
86 factory :merge_request do 106 factory :merge_request do
87 title 107 title
88 author 108 author
89 - project factory: :project_with_code 109 + source_project factory: :source_project_with_code
  110 + target_project factory: :target_project_with_code
90 source_branch "master" 111 source_branch "master"
91 target_branch "stable" 112 target_branch "stable"
92 113
@@ -96,13 +117,13 @@ FactoryGirl.define do @@ -96,13 +117,13 @@ FactoryGirl.define do
96 source_branch "stable" # pretend bcf03b5d 117 source_branch "stable" # pretend bcf03b5d
97 st_commits do 118 st_commits do
98 [ 119 [
99 - project.repository.commit('bcf03b5d').to_hash,  
100 - project.repository.commit('bcf03b5d~1').to_hash,  
101 - project.repository.commit('bcf03b5d~2').to_hash 120 + source_project.repository.commit('bcf03b5d').to_hash,
  121 + source_project.repository.commit('bcf03b5d~1').to_hash,
  122 + source_project.repository.commit('bcf03b5d~2').to_hash
102 ] 123 ]
103 end 124 end
104 st_diffs do 125 st_diffs do
105 - project.repo.diff("bcf03b5d~3", "bcf03b5d") 126 + source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
106 end 127 end
107 end 128 end
108 129
@@ -133,7 +154,7 @@ FactoryGirl.define do @@ -133,7 +154,7 @@ FactoryGirl.define do
133 154
134 trait :on_commit do 155 trait :on_commit do
135 project factory: :project_with_code 156 project factory: :project_with_code
136 - commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" 157 + commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
137 noteable_type "Commit" 158 noteable_type "Commit"
138 end 159 end
139 160
@@ -143,12 +164,12 @@ FactoryGirl.define do @@ -143,12 +164,12 @@ FactoryGirl.define do
143 164
144 trait :on_merge_request do 165 trait :on_merge_request do
145 project factory: :project_with_code 166 project factory: :project_with_code
146 - noteable_id 1 167 + noteable_id 1
147 noteable_type "MergeRequest" 168 noteable_type "MergeRequest"
148 end 169 end
149 170
150 trait :on_issue do 171 trait :on_issue do
151 - noteable_id 1 172 + noteable_id 1
152 noteable_type "Issue" 173 noteable_type "Issue"
153 end 174 end
154 175
spec/factories_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 INVALID_FACTORIES = [ 3 INVALID_FACTORIES = [
4 - :key_with_a_space_in_the_middle,  
5 - :invalid_key, 4 + :key_with_a_space_in_the_middle,
  5 + :invalid_key,
6 ] 6 ]
7 7
8 FactoryGirl.factories.map(&:name).each do |factory_name| 8 FactoryGirl.factories.map(&:name).each do |factory_name|
spec/features/gitlab_flavored_markdown_spec.rb
@@ -3,11 +3,11 @@ require &#39;spec_helper&#39; @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 describe "GitLab Flavored Markdown" do 3 describe "GitLab Flavored Markdown" do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 let(:issue) { create(:issue, project: project) } 5 let(:issue) { create(:issue, project: project) }
6 - let(:merge_request) { create(:merge_request, project: project) } 6 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
7 let(:fred) do 7 let(:fred) do
8 - u = create(:user, name: "fred")  
9 - project.team << [u, :master]  
10 - u 8 + u = create(:user, name: "fred")
  9 + project.team << [u, :master]
  10 + u
11 end 11 end
12 12
13 before do 13 before do
@@ -83,9 +83,7 @@ describe &quot;GitLab Flavored Markdown&quot; do @@ -83,9 +83,7 @@ describe &quot;GitLab Flavored Markdown&quot; do
83 83
84 describe "for merge requests" do 84 describe "for merge requests" do
85 before do 85 before do
86 - @merge_request = create(:merge_request,  
87 - project: project,  
88 - title: "fix ##{issue.id}") 86 + @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.id}")
89 end 87 end
90 88
91 it "should render title in merge_requests#index" do 89 it "should render title in merge_requests#index" do
spec/features/notes_on_merge_requests_spec.rb
@@ -2,8 +2,8 @@ require &#39;spec_helper&#39; @@ -2,8 +2,8 @@ require &#39;spec_helper&#39;
2 2
3 describe "On a merge request", js: true do 3 describe "On a merge request", js: true do
4 let!(:project) { create(:project_with_code) } 4 let!(:project) { create(:project_with_code) }
5 - let!(:merge_request) { create(:merge_request, project: project) }  
6 - let!(:note) { create(:note_on_merge_request_with_attachment, project: project) } 5 + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
  6 + let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
7 7
8 before do 8 before do
9 login_as :user 9 login_as :user
@@ -62,7 +62,7 @@ describe &quot;On a merge request&quot;, js: true do @@ -62,7 +62,7 @@ describe &quot;On a merge request&quot;, js: true do
62 62
63 it 'should be added and form reset' do 63 it 'should be added and form reset' do
64 should have_content("This is awsome!") 64 should have_content("This is awsome!")
65 - within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } 65 + within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") }
66 within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } 66 within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
67 within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } 67 within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
68 end 68 end
@@ -135,8 +135,8 @@ describe &quot;On a merge request&quot;, js: true do @@ -135,8 +135,8 @@ describe &quot;On a merge request&quot;, js: true do
135 end 135 end
136 136
137 describe "On a merge request diff", js: true, focus: true do 137 describe "On a merge request diff", js: true, focus: true do
138 - let!(:project) { create(:project_with_code) }  
139 - let!(:merge_request) { create(:merge_request_with_diffs, project: project) } 138 + let!(:project) { create(:source_project_with_code) }
  139 + let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
140 140
141 before do 141 before do
142 login_as :user 142 login_as :user
@@ -144,6 +144,7 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -144,6 +144,7 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
144 visit diffs_project_merge_request_path(project, merge_request) 144 visit diffs_project_merge_request_path(project, merge_request)
145 end 145 end
146 146
  147 +
147 subject { page } 148 subject { page }
148 149
149 describe "when adding a note" do 150 describe "when adding a note" do
@@ -183,6 +184,9 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -183,6 +184,9 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
183 end 184 end
184 185
185 describe "with muliple note forms" do 186 describe "with muliple note forms" do
  187 + let!(:project) { create(:source_project_with_code) }
  188 + let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
  189 +
186 before do 190 before do
187 find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click 191 find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
188 find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click 192 find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click
@@ -205,13 +209,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -205,13 +209,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
205 209
206 # TODO: fix 210 # TODO: fix
207 #it 'should check if previews were rendered separately' do 211 #it 'should check if previews were rendered separately' do
208 - #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do  
209 - #should have_css(".js-note-preview", text: "One comment on line 185")  
210 - #end 212 + #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
  213 + #should have_css(".js-note-preview", text: "One comment on line 185")
  214 + #end
211 215
212 - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do  
213 - #should have_css(".js-note-preview", text: "Another comment on line 17")  
214 - #end 216 + #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
  217 + #should have_css(".js-note-preview", text: "Another comment on line 17")
  218 + #end
215 #end 219 #end
216 end 220 end
217 221
@@ -238,39 +242,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -238,39 +242,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
238 242
239 # TODO: fix 243 # TODO: fix
240 #it "should remove last note of a discussion" do 244 #it "should remove last note of a discussion" do
241 - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do  
242 - #find(".js-note-delete").click  
243 - #end  
244 -  
245 - #should_not have_css(".note_holder") 245 + # within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do
  246 + # find(".js-note-delete").click
  247 + # end
  248 + # should_not have_css(".note_holder")
246 #end 249 #end
247 end 250 end
248 end 251 end
249 252
250 # TODO: fix 253 # TODO: fix
251 #describe "when replying to a note" do 254 #describe "when replying to a note" do
252 - #before do  
253 - ## create first note  
254 - #find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click 255 + #before do
  256 + ## create first note
  257 + # find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click
255 258
256 - #within(".js-temp-notes-holder") do  
257 - #fill_in "note[note]", with: "One comment on line 184"  
258 - #click_button("Add Comment")  
259 - #end 259 + # within(".js-temp-notes-holder") do
  260 + # fill_in "note[note]", with: "One comment on line 184"
  261 + # click_button("Add Comment")
  262 + #end
260 263
261 - #within(".js-temp-notes-holder") do  
262 - #find(".js-discussion-reply-button").click  
263 - #fill_in "note[note]", with: "An additional comment in reply"  
264 - #click_button("Add Comment")  
265 - #end  
266 - #end  
267 -  
268 - #it 'should be inserted and form removed from reply' do  
269 - #should have_content("An additional comment in reply")  
270 - #within(".notes_holder") { should have_css(".note", count: 2) }  
271 - #within(".notes_holder") { should have_no_css("form") }  
272 - #within(".notes_holder") { should have_link("Reply") }  
273 - #end 264 + # within(".js-temp-notes-holder") do
  265 + # find(".js-discussion-reply-button").click
  266 + # fill_in "note[note]", with: "An additional comment in reply"
  267 + # click_button("Add Comment")
  268 + # end
  269 + #end
  270 +
  271 + #it 'should be inserted and form removed from reply' do
  272 + # should have_content("An additional comment in reply")
  273 + # within(".notes_holder") { should have_css(".note", count: 2) }
  274 + # within(".notes_holder") { should have_no_css("form") }
  275 + # within(".notes_holder") { should have_link("Reply") }
  276 + # end
274 #end 277 #end
275 end 278 end
276 279
spec/features/profile_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe "Profile account page" do 3 describe "Profile account page" do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 let(:user) { create(:user) } 6 let(:user) { create(:user) }
6 7
7 before do 8 before do
spec/features/projects_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe "Projects" do 3 describe "Projects" do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 before { login_as :user } 6 before { login_as :user }
6 7
7 describe "DELETE /projects/:id" do 8 describe "DELETE /projects/:id" do
spec/features/security/project_access_spec.rb
@@ -14,10 +14,10 @@ describe &quot;Application access&quot; do @@ -14,10 +14,10 @@ describe &quot;Application access&quot; do
14 end 14 end
15 15
16 describe "Project" do 16 describe "Project" do
17 - let(:project) { create(:project_with_code) } 17 + let(:project) { create(:project_with_code) }
18 18
19 - let(:master) { create(:user) }  
20 - let(:guest) { create(:user) } 19 + let(:master) { create(:user) }
  20 + let(:guest) { create(:user) }
21 let(:reporter) { create(:user) } 21 let(:reporter) { create(:user) }
22 22
23 before do 23 before do
@@ -108,7 +108,7 @@ describe &quot;Application access&quot; do @@ -108,7 +108,7 @@ describe &quot;Application access&quot; do
108 describe "GET /project_code/blob" do 108 describe "GET /project_code/blob" do
109 before do 109 before do
110 commit = project.repository.commit 110 commit = project.repository.commit
111 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 111 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
112 @blob_path = project_blob_path(project, File.join(commit.id, path)) 112 @blob_path = project_blob_path(project, File.join(commit.id, path))
113 end 113 end
114 114
@@ -232,13 +232,13 @@ describe &quot;Application access&quot; do @@ -232,13 +232,13 @@ describe &quot;Application access&quot; do
232 232
233 233
234 describe "PublicProject" do 234 describe "PublicProject" do
235 - let(:project) { create(:project_with_code) } 235 + let(:project) { create(:project_with_code) }
236 236
237 - let(:master) { create(:user) }  
238 - let(:guest) { create(:user) } 237 + let(:master) { create(:user) }
  238 + let(:guest) { create(:user) }
239 let(:reporter) { create(:user) } 239 let(:reporter) { create(:user) }
240 240
241 - let(:admin) { create(:user) } 241 + let(:admin) { create(:user) }
242 242
243 before do 243 before do
244 # public project 244 # public project
@@ -339,7 +339,7 @@ describe &quot;Application access&quot; do @@ -339,7 +339,7 @@ describe &quot;Application access&quot; do
339 describe "GET /project_code/blob" do 339 describe "GET /project_code/blob" do
340 before do 340 before do
341 commit = project.repository.commit 341 commit = project.repository.commit
342 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 342 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
343 @blob_path = project_blob_path(project, File.join(commit.id, path)) 343 @blob_path = project_blob_path(project, File.join(commit.id, path))
344 end 344 end
345 345
spec/helpers/gitlab_markdown_helper_spec.rb
@@ -9,7 +9,7 @@ describe GitlabMarkdownHelper do @@ -9,7 +9,7 @@ describe GitlabMarkdownHelper do
9 let(:user) { create(:user, username: 'gfm') } 9 let(:user) { create(:user, username: 'gfm') }
10 let(:commit) { project.repository.commit } 10 let(:commit) { project.repository.commit }
11 let(:issue) { create(:issue, project: project) } 11 let(:issue) { create(:issue, project: project) }
12 - let(:merge_request) { create(:merge_request, project: project) } 12 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
13 let(:snippet) { create(:project_snippet, project: project) } 13 let(:snippet) { create(:project_snippet, project: project) }
14 let(:member) { project.users_projects.where(user_id: user).first } 14 let(:member) { project.users_projects.where(user_id: user).first }
15 15
spec/lib/gitlab/satellite/action_spec.rb 0 → 100644
@@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::Action' do
  4 + let(:project) { create(:project_with_code) }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe '#prepare_satellite!' do
  8 +
  9 + it 'create a repository with a parking branch and one remote: origin' do
  10 + repo = project.satellite.repo
  11 +
  12 + #now lets dirty it up
  13 +
  14 + starting_remote_count = repo.git.list_remotes.size
  15 + starting_remote_count.should >= 1
  16 + #kind of hookey way to add a second remote
  17 + origin_uri = repo.git.remote({v: true}).split(" ")[1]
  18 + begin
  19 + repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
  20 + repo.git.branch({raise: true}, 'a-new-branch')
  21 +
  22 + repo.heads.size.should > (starting_remote_count)
  23 + repo.git.remote().split(" ").size.should > (starting_remote_count)
  24 + rescue
  25 + end
  26 +
  27 + repo.git.config({}, "user.name", "#{user.name} -- foo")
  28 + repo.git.config({}, "user.email", "#{user.email} -- foo")
  29 + repo.config['user.name'].should =="#{user.name} -- foo"
  30 + repo.config['user.email'].should =="#{user.email} -- foo"
  31 +
  32 +
  33 + #These must happen in the context of the satellite directory...
  34 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  35 + project.satellite.lock {
  36 + #Now clean it up, use send to get around prepare_satellite! being protected
  37 + satellite_action.send(:prepare_satellite!, repo)
  38 + }
  39 +
  40 + #verify it's clean
  41 + heads = repo.heads.map(&:name)
  42 + heads.size.should == 1
  43 + heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
  44 + remotes = repo.git.remote().split(' ')
  45 + remotes.size.should == 1
  46 + remotes.include?('origin').should == true
  47 + repo.config['user.name'].should ==user.name
  48 + repo.config['user.email'].should ==user.email
  49 + end
  50 + end
  51 +
  52 + describe '#in_locked_and_timed_satellite' do
  53 +
  54 + it 'should make use of a lockfile' do
  55 + repo = project.satellite.repo
  56 + called = false
  57 +
  58 + #set assumptions
  59 + File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file
  60 +
  61 + File.exists?(project.satellite.lock_file).should be_false
  62 +
  63 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  64 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  65 + repo.should == sat_repo
  66 + (File.exists? project.satellite.lock_file).should be_true
  67 + called = true
  68 + end
  69 +
  70 + called.should be_true
  71 +
  72 + end
  73 +
  74 + it 'should be able to use the satellite after locking' do
  75 + repo = project.satellite.repo
  76 + called = false
  77 +
  78 + # Set base assumptions
  79 + if File.exists? project.satellite.lock_file
  80 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  81 + end
  82 +
  83 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  84 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  85 + called = true
  86 + repo.should == sat_repo
  87 + (File.exists? project.satellite.lock_file).should be_true
  88 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true
  89 + end
  90 +
  91 + called.should be_true
  92 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  93 +
  94 + end
  95 +
  96 + class FileLockStatusChecker < File
  97 + def flocked? &block
  98 + status = flock LOCK_EX|LOCK_NB
  99 + case status
  100 + when false
  101 + return true
  102 + when 0
  103 + begin
  104 + block ? block.call : false
  105 + ensure
  106 + flock LOCK_UN
  107 + end
  108 + else
  109 + raise SystemCallError, status
  110 + end
  111 + end
  112 + end
  113 +
  114 + end
  115 +end
  116 +
spec/lib/gitlab/satellite/merge_action_spec.rb 0 → 100644
@@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::MergeAction' do
  4 + before(:each) do
  5 +# TestEnv.init(mailer: false, init_repos: true, repos: true)
  6 + @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a']
  7 + @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable
  8 + @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master
  9 + @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch
  10 +
  11 + #these commits are quite close together, itended to make string diffs/format patches small
  12 + @close_commit1 = ['2_3_notes_fix', '8470d70da67355c9c009e4401746b1d5410af2e3']
  13 + @close_commit2 = ['scss_refactoring', 'f0f14c8eaba69ebddd766498a9d0b0e79becd633']
  14 + end
  15 +
  16 + let(:project) { create(:project_with_code) }
  17 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
  18 + let(:merge_request_fork) { create(:merge_request) }
  19 + describe '#commits_between' do
  20 + def verify_commits(commits, first_commit_sha, last_commit_sha)
  21 + commits.each { |commit| commit.class.should == Gitlab::Git::Commit }
  22 + commits.first.id.should == first_commit_sha
  23 + commits.last.id.should == last_commit_sha
  24 + end
  25 +
  26 + context 'on fork' do
  27 + it 'should get proper commits between' do
  28 + merge_request_fork.target_branch = @one_after_stable[0]
  29 + merge_request_fork.source_branch = @master[0]
  30 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  31 + verify_commits(commits, @one_after_stable[1], @master[1])
  32 +
  33 + merge_request_fork.target_branch = @wiki_branch[0]
  34 + merge_request_fork.source_branch = @master[0]
  35 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  36 + verify_commits(commits, @wiki_branch[1], @master[1])
  37 + end
  38 + end
  39 +
  40 + context 'between branches' do
  41 + it 'should raise exception -- not expected to be used by non forks' do
  42 + merge_request.target_branch = @one_after_stable[0]
  43 + merge_request.source_branch = @master[0]
  44 + expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
  45 +
  46 + merge_request.target_branch = @wiki_branch[0]
  47 + merge_request.source_branch = @master[0]
  48 + expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
  49 + end
  50 + end
  51 + end
  52 +
  53 + describe '#format_patch' do
  54 + context 'on fork' do
  55 + it 'should build a format patch' do
  56 + merge_request_fork.target_branch = @close_commit1[0]
  57 + merge_request_fork.source_branch = @close_commit2[0]
  58 + patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
  59 + (patch.include? "From #{@close_commit2[1]}").should be_true
  60 + (patch.include? "From #{@close_commit1[1]}").should be_true
  61 + end
  62 + end
  63 +
  64 + context 'between branches' do
  65 + it 'should build a format patch' do
  66 + merge_request.target_branch = @close_commit1[0]
  67 + merge_request.source_branch = @close_commit2[0]
  68 + patch = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).format_patch
  69 + (patch.include? "From #{@close_commit2[1]}").should be_true
  70 + (patch.include? "From #{@close_commit1[1]}").should be_true
  71 + end
  72 + end
  73 + end
  74 +
  75 + describe '#diffs_between_satellite tested against diff_in_satellite' do
  76 +
  77 + def is_a_matching_diff(diff, diffs)
  78 + diff_count = diff.scan('diff --git').size
  79 + diff_count.should >= 1
  80 + diffs.size.should == diff_count
  81 + diffs.each do |a_diff|
  82 + a_diff.class.should == Gitlab::Git::Diff
  83 + (diff.include? a_diff.diff).should be_true
  84 + end
  85 + end
  86 +
  87 + context 'on fork' do
  88 + it 'should get proper diffs' do
  89 + merge_request_fork.target_branch = @close_commit1[0]
  90 + merge_request_fork.source_branch = @master[0]
  91 + diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
  92 +
  93 + merge_request_fork.target_branch = @close_commit1[0]
  94 + merge_request_fork.source_branch = @master[0]
  95 + diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diff_in_satellite
  96 +
  97 + is_a_matching_diff(diff, diffs)
  98 + end
  99 + end
  100 +
  101 + context 'between branches' do
  102 + it 'should get proper diffs' do
  103 + merge_request.target_branch = @close_commit1[0]
  104 + merge_request.source_branch = @master[0]
  105 + expect{Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite}.to raise_error
  106 + end
  107 + end
  108 + end
  109 +
  110 + describe '#can_be_merged?' do
  111 + context 'on fork' do
  112 + it 'return true or false depending on if something is mergable' do
  113 + merge_request_fork.target_branch = @one_after_stable[0]
  114 + merge_request_fork.source_branch = @master[0]
  115 + Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_true
  116 +
  117 + merge_request_fork.target_branch = @conflicting_metior[0]
  118 + merge_request_fork.source_branch = @wiki_branch[0]
  119 + Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_false
  120 + end
  121 + end
  122 +
  123 + context 'between branches' do
  124 + it 'return true or false depending on if something is mergable' do
  125 + merge_request.target_branch = @one_after_stable[0]
  126 + merge_request.source_branch = @master[0]
  127 + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_true
  128 +
  129 + merge_request.target_branch = @conflicting_metior[0]
  130 + merge_request.source_branch = @wiki_branch[0]
  131 + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_false
  132 + end
  133 + end
  134 + end
  135 +end
0 \ No newline at end of file 136 \ No newline at end of file
spec/mailers/notify_spec.rb
@@ -167,7 +167,7 @@ describe Notify do @@ -167,7 +167,7 @@ describe Notify do
167 end 167 end
168 168
169 context 'for merge requests' do 169 context 'for merge requests' do
170 - let(:merge_request) { create(:merge_request, assignee: assignee, project: project) } 170 + let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) }
171 171
172 describe 'that are new' do 172 describe 'that are new' do
173 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } 173 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -311,7 +311,7 @@ describe Notify do @@ -311,7 +311,7 @@ describe Notify do
311 end 311 end
312 312
313 describe 'on a merge request' do 313 describe 'on a merge request' do
314 - let(:merge_request) { create(:merge_request, project: project) } 314 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
315 let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } 315 let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
316 before(:each) { note.stub(:noteable).and_return(merge_request) } 316 before(:each) { note.stub(:noteable).and_return(merge_request) }
317 317
spec/models/commit_spec.rb
@@ -3,7 +3,6 @@ require &#39;spec_helper&#39; @@ -3,7 +3,6 @@ require &#39;spec_helper&#39;
3 describe Commit do 3 describe Commit do
4 let(:commit) { create(:project_with_code).repository.commit } 4 let(:commit) { create(:project_with_code).repository.commit }
5 5
6 -  
7 describe '#title' do 6 describe '#title' do
8 it "returns no_commit_message when safe_message is blank" do 7 it "returns no_commit_message when safe_message is blank" do
9 commit.stub(:safe_message).and_return('') 8 commit.stub(:safe_message).and_return('')
spec/models/forked_project_link_spec.rb
@@ -12,9 +12,9 @@ @@ -12,9 +12,9 @@
12 require 'spec_helper' 12 require 'spec_helper'
13 13
14 describe ForkedProjectLink, "add link on fork" do 14 describe ForkedProjectLink, "add link on fork" do
15 - let(:project_from) {create(:project)}  
16 - let(:namespace) {create(:namespace)}  
17 - let(:user) {create(:user, namespace: namespace)} 15 + let(:project_from) { create(:project) }
  16 + let(:namespace) { create(:namespace) }
  17 + let(:user) { create(:user, namespace: namespace) }
18 18
19 before do 19 before do
20 @project_to = fork_project(project_from, user) 20 @project_to = fork_project(project_from, user)
@@ -30,9 +30,9 @@ describe ForkedProjectLink, &quot;add link on fork&quot; do @@ -30,9 +30,9 @@ describe ForkedProjectLink, &quot;add link on fork&quot; do
30 end 30 end
31 31
32 describe :forked_from_project do 32 describe :forked_from_project do
33 - let(:forked_project_link) {build(:forked_project_link)}  
34 - let(:project_from) {create(:project)}  
35 - let(:project_to) {create(:project, forked_project_link: forked_project_link)} 33 + let(:forked_project_link) { build(:forked_project_link) }
  34 + let(:project_from) { create(:project) }
  35 + let(:project_to) { create(:project, forked_project_link: forked_project_link) }
36 36
37 37
38 before :each do 38 before :each do
spec/models/merge_request_spec.rb
@@ -41,15 +41,12 @@ describe MergeRequest do @@ -41,15 +41,12 @@ describe MergeRequest do
41 it { should include_module(Issuable) } 41 it { should include_module(Issuable) }
42 end 42 end
43 43
44 - describe "#mr_and_commit_notes" do  
45 -  
46 - end  
47 44
48 describe "#mr_and_commit_notes" do 45 describe "#mr_and_commit_notes" do
49 let!(:merge_request) { create(:merge_request) } 46 let!(:merge_request) { create(:merge_request) }
50 47
51 before do 48 before do
52 - merge_request.stub(:commits) { [merge_request.project.repository.commit] } 49 + merge_request.stub(:commits) { [merge_request.source_project.repository.commit] }
53 create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit') 50 create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit')
54 create(:note, noteable: merge_request) 51 create(:note, noteable: merge_request)
55 end 52 end
@@ -71,4 +68,38 @@ describe MergeRequest do @@ -71,4 +68,38 @@ describe MergeRequest do
71 subject.is_being_reassigned?.should be_false 68 subject.is_being_reassigned?.should be_false
72 end 69 end
73 end 70 end
  71 +
  72 + describe '#for_fork?' do
  73 + it 'returns true if the merge request is for a fork' do
  74 + subject.source_project = create(:source_project)
  75 + subject.target_project = create(:target_project)
  76 +
  77 + subject.for_fork?.should be_true
  78 + end
  79 + it 'returns false if is not for a fork' do
  80 + subject.source_project = create(:source_project)
  81 + subject.target_project = subject.source_project
  82 + subject.for_fork?.should be_false
  83 + end
  84 + end
  85 +
  86 + describe '#allow_source_branch_removal?' do
  87 + it 'should not allow removal when mr is a fork' do
  88 +
  89 + subject.disallow_source_branch_removal?.should be_true
  90 + end
  91 + it 'should not allow removal when the mr is not a fork, but the source branch is the root reference' do
  92 + subject.target_project = subject.source_project
  93 + subject.source_branch = subject.source_project.repository.root_ref
  94 + subject.disallow_source_branch_removal?.should be_true
  95 + end
  96 +
  97 + it 'should not disallow removal when the mr is not a fork, and but source branch is not the root reference' do
  98 + subject.target_project = subject.source_project
  99 + subject.source_branch = "Something Different #{subject.source_project.repository.root_ref}"
  100 + subject.for_fork?.should be_false
  101 + subject.disallow_source_branch_removal?.should be_false
  102 + end
  103 + end
  104 +
74 end 105 end
spec/models/note_spec.rb
@@ -144,12 +144,12 @@ describe Note do @@ -144,12 +144,12 @@ describe Note do
144 end 144 end
145 145
146 describe '#create_status_change_note' do 146 describe '#create_status_change_note' do
147 - let(:project) { create(:project) }  
148 - let(:thing) { create(:issue, project: project) }  
149 - let(:author) { create(:user) }  
150 - let(:status) { 'new_status' } 147 + let(:project) { create(:project) }
  148 + let(:thing) { create(:issue, project: project) }
  149 + let(:author) { create(:user) }
  150 + let(:status) { 'new_status' }
151 151
152 - subject { Note.create_status_change_note(thing, author, status) } 152 + subject { Note.create_status_change_note(thing, project, author, status) }
153 153
154 it 'creates and saves a Note' do 154 it 'creates and saves a Note' do
155 should be_a Note 155 should be_a Note
@@ -157,9 +157,9 @@ describe Note do @@ -157,9 +157,9 @@ describe Note do
157 end 157 end
158 158
159 its(:noteable) { should == thing } 159 its(:noteable) { should == thing }
160 - its(:project) { should == thing.project }  
161 - its(:author) { should == author }  
162 - its(:note) { should =~ /Status changed to #{status}/ } 160 + its(:project) { should == thing.project }
  161 + its(:author) { should == author }
  162 + its(:note) { should =~ /Status changed to #{status}/ }
163 end 163 end
164 164
165 describe :authorization do 165 describe :authorization do
spec/models/project_spec.rb
@@ -26,6 +26,9 @@ @@ -26,6 +26,9 @@
26 require 'spec_helper' 26 require 'spec_helper'
27 27
28 describe Project do 28 describe Project do
  29 + before(:each) { enable_observers }
  30 + after(:each) { disable_observers }
  31 +
29 describe "Associations" do 32 describe "Associations" do
30 it { should belong_to(:group) } 33 it { should belong_to(:group) }
31 it { should belong_to(:namespace) } 34 it { should belong_to(:namespace) }
@@ -95,12 +98,11 @@ describe Project do @@ -95,12 +98,11 @@ describe Project do
95 end 98 end
96 99
97 describe "last_activity methods" do 100 describe "last_activity methods" do
98 - before { enable_observers }  
99 - let(:project) { create(:project) } 101 + let(:project) { create(:project) }
100 let(:last_event) { double(created_at: Time.now) } 102 let(:last_event) { double(created_at: Time.now) }
101 103
102 describe "last_activity" do 104 describe "last_activity" do
103 - it "should alias last_activity to last_event"do 105 + it "should alias last_activity to last_event" do
104 project.stub(last_event: last_event) 106 project.stub(last_event: last_event)
105 project.last_activity.should == last_event 107 project.last_activity.should == last_event
106 end 108 end
@@ -122,7 +124,7 @@ describe Project do @@ -122,7 +124,7 @@ describe Project do
122 let(:project) { create(:project_with_code) } 124 let(:project) { create(:project_with_code) }
123 125
124 before do 126 before do
125 - @merge_request = create(:merge_request, project: project) 127 + @merge_request = create(:merge_request, source_project: project, target_project: project)
126 @key = create(:key, user_id: project.owner.id) 128 @key = create(:key, user_id: project.owner.id)
127 end 129 end
128 130
spec/observers/activity_observer_spec.rb
@@ -8,18 +8,6 @@ describe ActivityObserver do @@ -8,18 +8,6 @@ describe ActivityObserver do
8 it { @event.project.should == project } 8 it { @event.project.should == project }
9 end 9 end
10 10
11 - describe "Merge Request created" do  
12 - before do  
13 - MergeRequest.observers.enable :activity_observer do  
14 - @merge_request = create(:merge_request, project: project)  
15 - @event = Event.last  
16 - end  
17 - end  
18 -  
19 - it_should_be_valid_event  
20 - it { @event.action.should == Event::CREATED }  
21 - it { @event.target.should == @merge_request }  
22 - end  
23 11
24 describe "Issue created" do 12 describe "Issue created" do
25 before do 13 before do
spec/observers/issue_observer_spec.rb
@@ -26,14 +26,13 @@ describe IssueObserver do @@ -26,14 +26,13 @@ describe IssueObserver do
26 before { mock_issue.stub(state: 'closed') } 26 before { mock_issue.stub(state: 'closed') }
27 27
28 it 'note is created if the issue is being closed' do 28 it 'note is created if the issue is being closed' do
29 - Note.should_receive(:create_status_change_note).with(mock_issue, some_user, 'closed') 29 + Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed')
30 30
31 subject.after_close(mock_issue, nil) 31 subject.after_close(mock_issue, nil)
32 end 32 end
33 33
34 it 'trigger notification to send emails' do 34 it 'trigger notification to send emails' do
35 subject.notification.should_receive(:close_issue).with(mock_issue, some_user) 35 subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
36 -  
37 subject.after_close(mock_issue, nil) 36 subject.after_close(mock_issue, nil)
38 end 37 end
39 end 38 end
@@ -42,8 +41,7 @@ describe IssueObserver do @@ -42,8 +41,7 @@ describe IssueObserver do
42 before { mock_issue.stub(state: 'reopened') } 41 before { mock_issue.stub(state: 'reopened') }
43 42
44 it 'note is created if the issue is being reopened' do 43 it 'note is created if the issue is being reopened' do
45 - Note.should_receive(:create_status_change_note).with(mock_issue, some_user, 'reopened')  
46 - 44 + Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened')
47 subject.after_reopen(mock_issue, nil) 45 subject.after_reopen(mock_issue, nil)
48 end 46 end
49 end 47 end
spec/observers/merge_request_observer_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe MergeRequestObserver do 3 describe MergeRequestObserver do
4 - let(:some_user) { create :user }  
5 - let(:assignee) { create :user }  
6 - let(:author) { create :user }  
7 - let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }  
8 - let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) } 4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
9 let(:unassigned_mr) { create(:merge_request, author: author) } 9 let(:unassigned_mr) { create(:merge_request, author: author) }
10 - let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) } 10 + let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
11 let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) } 11 let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
12 12
13 before { subject.stub(:current_user).and_return(some_user) } 13 before { subject.stub(:current_user).and_return(some_user) }
14 before { subject.stub(notification: mock('NotificationService').as_null_object) } 14 before { subject.stub(notification: mock('NotificationService').as_null_object) }
  15 + before { mr_mock.stub(:author_id) }
  16 + before { mr_mock.stub(:target_project) }
15 before(:each) { enable_observers } 17 before(:each) { enable_observers }
16 - 18 + after(:each) { disable_observers }
17 19
18 subject { MergeRequestObserver.instance } 20 subject { MergeRequestObserver.instance }
19 21
@@ -30,7 +32,7 @@ describe MergeRequestObserver do @@ -30,7 +32,7 @@ describe MergeRequestObserver do
30 end 32 end
31 33
32 it 'is called when a merge request is changed' do 34 it 'is called when a merge request is changed' do
33 - changed = create(:merge_request, project: create(:project)) 35 + changed = create(:merge_request, source_project: create(:project))
34 subject.should_receive(:after_update) 36 subject.should_receive(:after_update)
35 37
36 MergeRequest.observers.enable :merge_request_observer do 38 MergeRequest.observers.enable :merge_request_observer do
@@ -59,13 +61,13 @@ describe MergeRequestObserver do @@ -59,13 +61,13 @@ describe MergeRequestObserver do
59 context '#after_close' do 61 context '#after_close' do
60 context 'a status "closed"' do 62 context 'a status "closed"' do
61 it 'note is created if the merge request is being closed' do 63 it 'note is created if the merge request is being closed' do
62 - Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed') 64 + Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed')
63 65
64 assigned_mr.close 66 assigned_mr.close
65 end 67 end
66 68
67 it 'notification is delivered only to author if the merge request is being closed' do 69 it 'notification is delivered only to author if the merge request is being closed' do
68 - Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed') 70 + Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed')
69 71
70 unassigned_mr.close 72 unassigned_mr.close
71 end 73 end
@@ -75,16 +77,41 @@ describe MergeRequestObserver do @@ -75,16 +77,41 @@ describe MergeRequestObserver do
75 context '#after_reopen' do 77 context '#after_reopen' do
76 context 'a status "reopened"' do 78 context 'a status "reopened"' do
77 it 'note is created if the merge request is being reopened' do 79 it 'note is created if the merge request is being reopened' do
78 - Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened') 80 + Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened')
79 81
80 closed_assigned_mr.reopen 82 closed_assigned_mr.reopen
81 end 83 end
82 84
83 it 'notification is delivered only to author if the merge request is being reopened' do 85 it 'notification is delivered only to author if the merge request is being reopened' do
84 - Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened') 86 + Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened')
85 87
86 closed_unassigned_mr.reopen 88 closed_unassigned_mr.reopen
87 end 89 end
88 end 90 end
89 end 91 end
  92 +
  93 + describe "Merge Request created" do
  94 + def self.it_should_be_valid_event
  95 + it { @event.should_not be_nil }
  96 + it { @event.should_not be_nil }
  97 + it { @event.project.should == project }
  98 + it { @event.project.should == project }
  99 + end
  100 +
  101 + let(:project) { create(:project) }
  102 + before do
  103 + TestEnv.enable_observers
  104 + @merge_request = create(:merge_request, source_project: project, target_project: project)
  105 + @event = Event.last
  106 + end
  107 +
  108 + after do
  109 + TestEnv.disable_observers
  110 + end
  111 +
  112 + it_should_be_valid_event
  113 + it { @event.action.should == Event::CREATED }
  114 + it { @event.target.should == @merge_request }
  115 + end
  116 +
90 end 117 end
spec/observers/user_observer_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe UserObserver do 3 describe UserObserver do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 subject { UserObserver.instance } 6 subject { UserObserver.instance }
6 before { subject.stub(notification: mock('NotificationService').as_null_object) } 7 before { subject.stub(notification: mock('NotificationService').as_null_object) }
7 8
spec/observers/users_project_observer_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe UsersProjectObserver do 3 describe UsersProjectObserver do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) { disable_observers }
5 6
6 let(:user) { create(:user) } 7 let(:user) { create(:user) }
7 let(:project) { create(:project) } 8 let(:project) { create(:project) }
spec/requests/api/merge_requests_spec.rb
@@ -3,10 +3,12 @@ require &quot;spec_helper&quot; @@ -3,10 +3,12 @@ require &quot;spec_helper&quot;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 5
6 - let(:user) { create(:user ) }  
7 - let!(:project) { create(:project_with_code, creator_id: user.id) }  
8 - let!(:merge_request) { create(:merge_request, author: user, assignee: user, project: project, title: "Test") }  
9 - before { project.team << [user, :reporters] } 6 + let(:user) { create(:user) }
  7 + let!(:project) {create(:project_with_code, creator_id: user.id) }
  8 + let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
  9 + before {
  10 + project.team << [user, :reporters]
  11 + }
10 12
11 describe "GET /projects/:id/merge_requests" do 13 describe "GET /projects/:id/merge_requests" do
12 context "when unauthenticated" do 14 context "when unauthenticated" do
@@ -40,35 +42,104 @@ describe API::API do @@ -40,35 +42,104 @@ describe API::API do
40 end 42 end
41 43
42 describe "POST /projects/:id/merge_requests" do 44 describe "POST /projects/:id/merge_requests" do
43 - it "should return merge_request" do  
44 - post api("/projects/#{project.id}/merge_requests", user),  
45 - title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user  
46 - response.status.should == 201  
47 - json_response['title'].should == 'Test merge_request'  
48 - end 45 + context 'between branches projects' do
  46 + it "should return merge_request" do
  47 + post api("/projects/#{project.id}/merge_requests", user),
  48 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user
  49 + response.status.should == 201
  50 + json_response['title'].should == 'Test merge_request'
  51 + end
49 52
50 - it "should return 422 when source_branch equals target_branch" do  
51 - post api("/projects/#{project.id}/merge_requests", user),  
52 - title: "Test merge_request", source_branch: "master", target_branch: "master", author: user  
53 - response.status.should == 422  
54 - end 53 + it "should return 422 when source_branch equals target_branch" do
  54 + post api("/projects/#{project.id}/merge_requests", user),
  55 + title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
  56 + response.status.should == 422
  57 + end
55 58
56 - it "should return 400 when source_branch is missing" do  
57 - post api("/projects/#{project.id}/merge_requests", user),  
58 - title: "Test merge_request", target_branch: "master", author: user  
59 - response.status.should == 400  
60 - end 59 + it "should return 400 when source_branch is missing" do
  60 + post api("/projects/#{project.id}/merge_requests", user),
  61 + title: "Test merge_request", target_branch: "master", author: user
  62 + response.status.should == 400
  63 + end
61 64
62 - it "should return 400 when target_branch is missing" do  
63 - post api("/projects/#{project.id}/merge_requests", user),  
64 - title: "Test merge_request", source_branch: "stable", author: user  
65 - response.status.should == 400 65 + it "should return 400 when target_branch is missing" do
  66 + post api("/projects/#{project.id}/merge_requests", user),
  67 + title: "Test merge_request", source_branch: "stable", author: user
  68 + response.status.should == 400
  69 + end
  70 +
  71 + it "should return 400 when title is missing" do
  72 + post api("/projects/#{project.id}/merge_requests", user),
  73 + target_branch: 'master', source_branch: 'stable'
  74 + response.status.should == 400
  75 + end
66 end 76 end
67 77
68 - it "should return 400 when title is missing" do  
69 - post api("/projects/#{project.id}/merge_requests", user),  
70 - target_branch: 'master', source_branch: 'stable'  
71 - response.status.should == 400 78 + context 'forked projects' do
  79 + let!(:user2) {create(:user)}
  80 + let!(:forked_project_link) { build(:forked_project_link) }
  81 + let!(:fork_project) { create(:source_project_with_code, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) }
  82 + let!(:unrelated_project) { create(:target_project_with_code, namespace: user2.namespace, creator_id: user2.id) }
  83 +
  84 + before :each do |each|
  85 + fork_project.team << [user2, :reporters]
  86 + forked_project_link.forked_from_project = project
  87 + forked_project_link.forked_to_project = fork_project
  88 + forked_project_link.save!
  89 + end
  90 +
  91 + it "should return merge_request" do
  92 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  93 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id
  94 + response.status.should == 201
  95 + json_response['title'].should == 'Test merge_request'
  96 + end
  97 +
  98 + it "should not return 422 when source_branch equals target_branch" do
  99 + project.id.should_not == fork_project.id
  100 + fork_project.forked?.should be_true
  101 + fork_project.forked_from_project.should == project
  102 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  103 + title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
  104 + response.status.should == 201
  105 + json_response['title'].should == 'Test merge_request'
  106 + end
  107 +
  108 + it "should return 400 when source_branch is missing" do
  109 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  110 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  111 + response.status.should == 400
  112 + end
  113 +
  114 + it "should return 400 when target_branch is missing" do
  115 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  116 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  117 + response.status.should == 400
  118 + end
  119 +
  120 + it "should return 400 when title is missing" do
  121 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  122 + target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
  123 + response.status.should == 400
  124 + end
  125 +
  126 + it "should return 400 when target_branch is specified and not a forked project" do
  127 + post api("/projects/#{project.id}/merge_requests", user),
  128 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
  129 + response.status.should == 400
  130 + end
  131 +
  132 + it "should return 400 when target_branch is specified and for a different fork" do
  133 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  134 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
  135 + response.status.should == 400
  136 + end
  137 +
  138 + it "should return 201 when target_branch is specified and for the same project" do
  139 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  140 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
  141 + response.status.should == 201
  142 + end
72 end 143 end
73 end 144 end
74 145
@@ -97,14 +168,14 @@ describe API::API do @@ -97,14 +168,14 @@ describe API::API do
97 168
98 it "should return 422 when source_branch and target_branch are renamed the same" do 169 it "should return 422 when source_branch and target_branch are renamed the same" do
99 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), 170 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
100 - source_branch: "master", target_branch: "master" 171 + source_branch: "master", target_branch: "master"
101 response.status.should == 422 172 response.status.should == 422
102 end 173 end
103 174
104 it "should return merge_request with renamed target_branch" do 175 it "should return merge_request with renamed target_branch" do
105 - put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test" 176 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
106 response.status.should == 200 177 response.status.should == 200
107 - json_response['target_branch'].should == 'test' 178 + json_response['target_branch'].should == 'wiki'
108 end 179 end
109 end 180 end
110 181
spec/requests/api/milestones_spec.rb
@@ -3,6 +3,7 @@ require &#39;spec_helper&#39; @@ -3,6 +3,7 @@ require &#39;spec_helper&#39;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 before(:each) { enable_observers } 5 before(:each) { enable_observers }
  6 + after(:each) {disable_observers}
6 7
7 let(:user) { create(:user) } 8 let(:user) { create(:user) }
8 let!(:project) { create(:project, namespace: user.namespace ) } 9 let!(:project) { create(:project, namespace: user.namespace ) }
spec/requests/api/notes_spec.rb
@@ -6,7 +6,7 @@ describe API::API do @@ -6,7 +6,7 @@ describe API::API do
6 let(:user) { create(:user) } 6 let(:user) { create(:user) }
7 let!(:project) { create(:project, namespace: user.namespace ) } 7 let!(:project) { create(:project, namespace: user.namespace ) }
8 let!(:issue) { create(:issue, project: project, author: user) } 8 let!(:issue) { create(:issue, project: project, author: user) }
9 - let!(:merge_request) { create(:merge_request, project: project, author: user) } 9 + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
10 let!(:snippet) { create(:project_snippet, project: project, author: user) } 10 let!(:snippet) { create(:project_snippet, project: project, author: user) }
11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } 11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
12 let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } 12 let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
spec/requests/api/projects_spec.rb
@@ -3,6 +3,7 @@ require &#39;spec_helper&#39; @@ -3,6 +3,7 @@ require &#39;spec_helper&#39;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 before(:each) { enable_observers } 5 before(:each) { enable_observers }
  6 + after(:each) { disable_observers }
6 7
7 let(:user) { create(:user) } 8 let(:user) { create(:user) }
8 let(:user2) { create(:user) } 9 let(:user2) { create(:user) }
@@ -475,7 +476,6 @@ describe API::API do @@ -475,7 +476,6 @@ describe API::API do
475 end 476 end
476 end 477 end
477 478
478 -  
479 describe "GET /projects/:id/snippets" do 479 describe "GET /projects/:id/snippets" do
480 it "should return an array of project snippets" do 480 it "should return an array of project snippets" do
481 get api("/projects/#{project.id}/snippets", user) 481 get api("/projects/#{project.id}/snippets", user)
spec/requests/api/repositories_spec.rb
@@ -3,6 +3,7 @@ require &#39;spec_helper&#39; @@ -3,6 +3,7 @@ require &#39;spec_helper&#39;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 before(:each) { enable_observers } 5 before(:each) { enable_observers }
  6 + after(:each) {disable_observers}
6 7
7 let(:user) { create(:user) } 8 let(:user) { create(:user) }
8 let(:user2) { create(:user) } 9 let(:user2) { create(:user) }
spec/services/notification_service_spec.rb
@@ -2,7 +2,6 @@ require &#39;spec_helper&#39; @@ -2,7 +2,6 @@ require &#39;spec_helper&#39;
2 2
3 describe NotificationService do 3 describe NotificationService do
4 let(:notification) { NotificationService.new } 4 let(:notification) { NotificationService.new }
5 -  
6 describe 'Keys' do 5 describe 'Keys' do
7 describe :new_key do 6 describe :new_key do
8 let(:key) { create(:personal_key) } 7 let(:key) { create(:personal_key) }
@@ -156,7 +155,8 @@ describe NotificationService do @@ -156,7 +155,8 @@ describe NotificationService do
156 let(:merge_request) { create :merge_request, assignee: create(:user) } 155 let(:merge_request) { create :merge_request, assignee: create(:user) }
157 156
158 before do 157 before do
159 - build_team(merge_request.project) 158 + build_team(merge_request.source_project)
  159 + build_team(merge_request.target_project)
160 end 160 end
161 161
162 describe :new_merge_request do 162 describe :new_merge_request do
@@ -232,11 +232,16 @@ describe NotificationService do @@ -232,11 +232,16 @@ describe NotificationService do
232 end 232 end
233 end 233 end
234 234
  235 + let(:u_watcher) { create(:user, notification_level: Notification::N_WATCH) }
  236 + let(:u_participating) { create(:user, notification_level: Notification::N_PARTICIPATING) }
  237 + let(:u_disabled) { create(:user, notification_level: Notification::N_DISABLED) }
  238 + let(:u_mentioned) { create(:user, username: 'mention', notification_level: Notification::N_WATCH) }
  239 +
235 def build_team(project) 240 def build_team(project)
236 - @u_watcher = create(:user, notification_level: Notification::N_WATCH)  
237 - @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)  
238 - @u_disabled = create(:user, notification_level: Notification::N_DISABLED)  
239 - @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_WATCH) 241 + @u_watcher = u_watcher
  242 + @u_participating = u_participating
  243 + @u_disabled = u_disabled
  244 + @u_mentioned = u_mentioned
240 245
241 project.team << [@u_watcher, :master] 246 project.team << [@u_watcher, :master]
242 project.team << [@u_participating, :master] 247 project.team << [@u_participating, :master]
spec/services/project_transfer_service_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe ProjectTransferService do 3 describe ProjectTransferService do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 6
6 context 'namespace -> namespace' do 7 context 'namespace -> namespace' do
7 let(:user) { create(:user) } 8 let(:user) { create(:user) }
@@ -24,6 +25,7 @@ describe ProjectTransferService do @@ -24,6 +25,7 @@ describe ProjectTransferService do
24 @result = service.transfer(project, nil) 25 @result = service.transfer(project, nil)
25 end 26 end
26 27
  28 +
27 it { @result.should be_true } 29 it { @result.should be_true }
28 it { project.namespace.should == nil } 30 it { project.namespace.should == nil }
29 end 31 end