Commit 3d7194f0112da12e8732df9ffe8b34fe7d0a9f6b

Authored by Izaak Alpert
Committed by Izaak Alpert
1 parent fd033671

Merge Request on forked projects

The good:

 - You can do a merge request for a forked commit and it will merge properly (i.e. it does work).
 - Push events take into account merge requests on forked projects
 - Tests around merge_actions now present, spinach, and other rspec tests
 - Satellites now clean themselves up rather then recreate

The questionable:

 - Events only know about target projects
 - Project's merge requests only hold on to MR's where they are the target
 - All operations performed in the satellite

The bad:

  -  Duplication between project's repositories and satellites (e.g. commits_between)

(for reference: http://feedback.gitlab.com/forums/176466-general/suggestions/3456722-merge-requests-between-projects-repos)

Fixes:

Make test repos/satellites only create when needed
-Spinach/Rspec now only initialize test directory, and setup stubs (things that are relatively cheap)
-project_with_code, source_project_with_code, and target_project_with_code now create/destroy their repos individually
-fixed remote removal
-How to merge renders properly
-Update emails to show project/branches
-Edit MR doesn't set target branch
-Fix some failures on editing/creating merge requests, added a test
-Added back a test around merge request observer
-Clean up project_transfer_spec, Remove duplicate enable/disable observers
-Ensure satellite lock files are cleaned up, Attempted to add some testing around these as well
-Signifant speed ups for tests
-Update formatting ordering in notes_on_merge_requests
-Remove wiki schema update
Fixes for search/search results
-Search results was using by_project for a list of projects, updated this to use in_projects
-updated search results to reference the correct (target) project
-udpated search results to print both sides of the merge request

Change-Id: I19407990a0950945cc95d62089cbcc6262dab1a8
Showing 97 changed files with 1584 additions and 476 deletions   Show diff stats
app/assets/stylesheets/common.scss
@@ -415,6 +415,17 @@ img.emoji { @@ -415,6 +415,17 @@ img.emoji {
415 @extend .light-well; 415 @extend .light-well;
416 @extend .light; 416 @extend .light;
417 margin-bottom: 10px; 417 margin-bottom: 10px;
  418 +
  419 +.label-project {
  420 + @include border-radius(4px);
  421 + padding: 2px 4px;
  422 + border: none;
  423 + font-size: 14px;
  424 + background: #474D57;
  425 + color: #fff;
  426 + font-family: $monospace_font;
  427 + text-shadow: 0 1px 1px #111;
  428 + font-weight: normal;
418 } 429 }
419 430
420 .group-name { 431 .group-name {
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?
@@ -20,12 +20,12 @@ class FilterContext @@ -20,12 +20,12 @@ class FilterContext
20 end 20 end
21 21
22 case params[:status] 22 case params[:status]
23 - when 'closed'  
24 - items.closed  
25 - when 'all'  
26 - items  
27 - else  
28 - items.opened 23 + when 'closed'
  24 + items.closed
  25 + when 'all'
  26 + items
  27 + else
  28 + items.opened
29 end 29 end
30 end 30 end
31 end 31 end
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,25 +33,39 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -33,25 +33,39 @@ 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 = @project.merge_requests.new(params[:merge_request])
  43 +
  44 + if params[:merge_request] && params[:merge_request][:source_project_id]
  45 + @merge_request.source_project = Project.find_by_id(params[:merge_request][:source_project_id])
  46 + else
  47 + @merge_request.source_project = @project
  48 + end
  49 + if params[:merge_request] && params[:merge_request][:target_project_id]
  50 + @merge_request.target_project = Project.find_by_id(params[:merge_request][:target_project_id])
  51 + end
  52 + @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
  53 + @merge_request
43 end 54 end
44 55
45 def edit 56 def edit
  57 + @target_branches = @merge_request.target_project.repository.branch_names
46 end 58 end
47 59
48 def create 60 def create
49 @merge_request = @project.merge_requests.new(params[:merge_request]) 61 @merge_request = @project.merge_requests.new(params[:merge_request])
50 @merge_request.author = current_user 62 @merge_request.author = current_user
51 - 63 + @merge_request.source_project_id = params[:merge_request][:source_project_id].to_i
  64 + @merge_request.target_project_id = params[:merge_request][:target_project_id].to_i
  65 + @target_branches ||= []
52 if @merge_request.save 66 if @merge_request.save
53 @merge_request.reload_code 67 @merge_request.reload_code
54 - redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.' 68 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
55 else 69 else
56 render "new" 70 render "new"
57 end 71 end
@@ -89,22 +103,36 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -89,22 +103,36 @@ class Projects::MergeRequestsController < Projects::ApplicationController
89 end 103 end
90 104
91 def branch_from 105 def branch_from
  106 + #This is always source
92 @commit = @repository.commit(params[:ref]) 107 @commit = @repository.commit(params[:ref])
93 end 108 end
94 109
95 def branch_to 110 def branch_to
96 - @commit = @repository.commit(params[:ref]) 111 + @target_project = selected_target_project
  112 + @commit = @target_project.repository.commit(params[:ref])
97 end 113 end
98 114
  115 + def update_branches
  116 + @target_project = selected_target_project
  117 + @target_branches = (@target_project.repository.branch_names).unshift("Select branch")
  118 + @target_branches
  119 + end
  120 +
  121 +
99 def ci_status 122 def ci_status
100 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) 123 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
101 - response = { status: status } 124 + response = {status: status}
102 125
103 render json: response 126 render json: response
104 end 127 end
105 128
106 protected 129 protected
107 130
  131 + def selected_target_project
  132 + ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
  133 + end
  134 +
  135 +
108 def merge_request 136 def merge_request
109 @merge_request ||= @project.merge_requests.find(params[:id]) 137 @merge_request ||= @project.merge_requests.find(params[:id])
110 end 138 end
@@ -123,11 +151,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @@ -123,11 +151,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
123 151
124 def validates_merge_request 152 def validates_merge_request
125 # Show git not found page if target branch doesn't exist 153 # 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) 154 + return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
127 155
128 # Show git not found page if source branch doesn't exist 156 # Show git not found page if source branch doesn't exist
129 # and there is no saved commits between source & target branch 157 # 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? 158 + return invalid_mr if !@merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
131 end 159 end
132 160
133 def define_show_vars 161 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
1 module MergeRequestsHelper 1 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,  
5 - merge_request: { 4 + event.project,
  5 + new_mr_from_push_event(event, event.project)
  6 + )
  7 + end
  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 +
  17 + def new_mr_from_push_event(event, target_project)
  18 + return :merge_request => {
  19 + source_project_id: event.project.id,
  20 + target_project_id: target_project.id,
6 source_branch: event.branch_name, 21 source_branch: event.branch_name,
7 - target_branch: event.project.repository.root_ref, 22 + target_branch: target_project.repository.root_ref,
8 title: event.branch_name.titleize 23 title: event.branch_name.titleize
9 - }  
10 - ) 24 + }
11 end 25 end
12 26
13 def mr_css_classes mr 27 def mr_css_classes mr
@@ -18,6 +32,6 @@ module MergeRequestsHelper @@ -18,6 +32,6 @@ module MergeRequestsHelper
18 end 32 end
19 33
20 def ci_build_details_path merge_request 34 def ci_build_details_path merge_request
21 - merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha) 35 + merge_request.source_project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
22 end 36 end
23 end 37 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: @merge_request.assignee_email, 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 +
  27 + # Over rides default behavour to show source/target
  28 + # Formats arguments into a String suitable for use as an email subject
  29 + #
  30 + # extra - Extra Strings to be inserted into the subject
  31 + #
  32 + # Examples
  33 + #
  34 + # >> subject('Lorem ipsum')
  35 + # => "GitLab Merge Request | Lorem ipsum"
  36 + #
  37 + # # Automatically inserts Project name:
  38 + # Forked MR
  39 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  40 + # => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
  41 + # => source branch => source
  42 + # => target branch => target
  43 + # >> subject('Lorem ipsum')
  44 + # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
  45 + #
  46 + # Non Forked MR
  47 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  48 + # => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  49 + # => source branch => source
  50 + # => target branch => target
  51 + # >> subject('Lorem ipsum')
  52 + # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
  53 + # # Accepts multiple arguments
  54 + # >> subject('Lorem ipsum', 'Dolor sit amet')
  55 + # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
  56 + def subject(*extra)
  57 + subject = "GitLab Merge Request |"
  58 + if @merge_request.for_fork?
  59 + subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
  60 + else
  61 + subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
  62 + end
  63 + subject << " | " + extra.join(' | ') if extra.present?
  64 + subject
  65 + end
29 end 66 end
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,37 @@ @@ -2,30 +2,37 @@
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 + BROKEN_DIFF = "--broken-diff"
  33 +
  34 + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id,:author_id_of_changes, :state_event
  35 +
29 36
30 attr_accessor :should_remove_source_branch 37 attr_accessor :should_remove_source_branch
31 38
@@ -74,22 +81,29 @@ class MergeRequest &lt; ActiveRecord::Base @@ -74,22 +81,29 @@ class MergeRequest &lt; ActiveRecord::Base
74 serialize :st_commits 81 serialize :st_commits
75 serialize :st_diffs 82 serialize :st_diffs
76 83
  84 + validates :source_project, presence: true
77 validates :source_branch, presence: true 85 validates :source_branch, presence: true
  86 + validates :target_project, presence: true
78 validates :target_branch, presence: true 87 validates :target_branch, presence: true
79 - validate :validate_branches 88 + validate :validate_branches
80 89
  90 + 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) }
  91 + 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) }
  92 + scope :opened, -> { with_state(:opened) }
  93 + scope :closed, -> { with_state(:closed) }
81 scope :merged, -> { with_state(:merged) } 94 scope :merged, -> { with_state(:merged) }
82 - scope :by_branch, ->(branch_name) { where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name) } 95 + 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) } 96 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
84 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } 97 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
85 - 98 + scope :by_project, ->(project_id) { where("source_project_id = :project_id OR target_project_id = :project_id", project_id: project_id) }
  99 + 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 100 # Closed scope for merge request should return
87 # both merged and closed mr's 101 # both merged and closed mr's
88 scope :closed, -> { with_states(:closed, :merged) } 102 scope :closed, -> { with_states(:closed, :merged) }
89 103
90 def validate_branches 104 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" 105 + if target_project==source_project && target_branch == source_branch
  106 + errors.add :branch_conflict, "You can not use same project/branch for source and target"
93 end 107 end
94 108
95 if opened? || reopened? 109 if opened? || reopened?
@@ -137,7 +151,14 @@ class MergeRequest &lt; ActiveRecord::Base @@ -137,7 +151,14 @@ class MergeRequest &lt; ActiveRecord::Base
137 end 151 end
138 152
139 def unmerged_diffs 153 def unmerged_diffs
140 - project.repository.diffs_between(source_branch, target_branch) 154 + #TODO:[IA-8] this needs to be handled better -- logged etc
  155 + diffs = Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite
  156 + if diffs
  157 + diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
  158 + else
  159 + diffs = []
  160 + end
  161 + diffs
141 end 162 end
142 163
143 def last_commit 164 def last_commit
@@ -145,11 +166,11 @@ class MergeRequest &lt; ActiveRecord::Base @@ -145,11 +166,11 @@ class MergeRequest &lt; ActiveRecord::Base
145 end 166 end
146 167
147 def merge_event 168 def merge_event
148 - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last 169 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
149 end 170 end
150 171
151 def closed_event 172 def closed_event
152 - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last 173 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
153 end 174 end
154 175
155 def commits 176 def commits
@@ -158,24 +179,30 @@ class MergeRequest &lt; ActiveRecord::Base @@ -158,24 +179,30 @@ class MergeRequest &lt; ActiveRecord::Base
158 179
159 def probably_merged? 180 def probably_merged?
160 unmerged_commits.empty? && 181 unmerged_commits.empty? &&
161 - commits.any? && opened? 182 + commits.any? && opened?
162 end 183 end
163 184
164 def reloaded_commits 185 def reloaded_commits
165 if opened? && unmerged_commits.any? 186 if opened? && unmerged_commits.any?
166 self.st_commits = dump_commits(unmerged_commits) 187 self.st_commits = dump_commits(unmerged_commits)
167 save 188 save
  189 +
168 end 190 end
169 commits 191 commits
170 end 192 end
171 193
172 def unmerged_commits 194 def unmerged_commits
173 - self.project.repository.  
174 - commits_between(self.target_branch, self.source_branch).  
175 - sort_by(&:created_at).  
176 - reverse 195 + commits = Gitlab::Satellite::MergeAction.new(self.author,self).commits_between
  196 + commits = commits.map{ |commit| Gitlab::Git::Commit.new(commit, nil) }
  197 + if commits.present?
  198 + commits = Commit.decorate(commits).
  199 + sort_by(&:created_at).
  200 + reverse
  201 + end
  202 + commits
177 end 203 end
178 204
  205 +
179 def merge!(user_id) 206 def merge!(user_id)
180 self.author_id_of_changes = user_id 207 self.author_id_of_changes = user_id
181 self.merge 208 self.merge
@@ -195,25 +222,33 @@ class MergeRequest &lt; ActiveRecord::Base @@ -195,25 +222,33 @@ class MergeRequest &lt; ActiveRecord::Base
195 commit_ids = commits.map(&:id) 222 commit_ids = commits.map(&:id)
196 Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids) 223 Note.where("(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, commit_ids: commit_ids)
197 end 224 end
198 -  
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
  232 +
206 # Returns the commit as a series of email patches. 233 # Returns the commit as a series of email patches.
207 # 234 #
208 # see "git format-patch" 235 # see "git format-patch"
209 - def to_patch  
210 - project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}") 236 + def to_patch(current_user)
  237 + Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
211 end 238 end
212 239
213 def last_commit_short_sha 240 def last_commit_short_sha
214 @last_commit_short_sha ||= last_commit.sha[0..10] 241 @last_commit_short_sha ||= last_commit.sha[0..10]
215 end 242 end
216 243
  244 + def for_fork?
  245 + target_project != source_project
  246 + end
  247 +
  248 + def disallow_source_branch_removal?
  249 + (source_project.root_ref? source_branch) || for_fork?
  250 + end
  251 +
217 private 252 private
218 253
219 def dump_commits(commits) 254 def dump_commits(commits)
app/models/note.rb
@@ -32,8 +32,8 @@ class Note &lt; ActiveRecord::Base @@ -32,8 +32,8 @@ class Note &lt; ActiveRecord::Base
32 delegate :name, :email, to: :author, prefix: true 32 delegate :name, :email, to: :author, prefix: true
33 33
34 validates :note, :project, presence: true 34 validates :note, :project, presence: true
35 - validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true  
36 - validates :attachment, file_size: { maximum: 10.megabytes.to_i } 35 + validates :line_code, format: {with: /\A[a-z0-9]+_\d+_\d+\Z/}, allow_blank: true
  36 + validates :attachment, file_size: {maximum: 10.megabytes.to_i}
37 37
38 validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } 38 validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
39 validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } 39 validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
@@ -45,24 +45,24 @@ class Note &lt; ActiveRecord::Base @@ -45,24 +45,24 @@ class Note &lt; ActiveRecord::Base
45 scope :inline, -> { where("line_code IS NOT NULL") } 45 scope :inline, -> { where("line_code IS NOT NULL") }
46 scope :not_inline, -> { where(line_code: [nil, '']) } 46 scope :not_inline, -> { where(line_code: [nil, '']) }
47 47
48 - scope :common, ->{ where(noteable_type: ["", nil]) }  
49 - scope :fresh, ->{ order("created_at ASC, id ASC") }  
50 - scope :inc_author_project, ->{ includes(:project, :author) }  
51 - scope :inc_author, ->{ includes(:author) } 48 + scope :common, -> { where(noteable_type: ["", nil]) }
  49 + scope :fresh, -> { order("created_at ASC, id ASC") }
  50 + scope :inc_author_project, -> { includes(:project, :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,  
56 - project: noteable.project,  
57 - author: author,  
58 - note: "_Status changed to #{status}_"  
59 - }, without_protection: true) 55 + noteable: noteable,
  56 + project: project,
  57 + author: author,
  58 + note: "_Status changed to #{status}_"
  59 + }, without_protection: true)
60 end 60 end
61 61
62 def commit_author 62 def commit_author
63 @commit_author ||= 63 @commit_author ||=
64 - project.users.find_by_email(noteable.author_email) ||  
65 - project.users.find_by_name(noteable.author_name) 64 + project.users.find_by_email(noteable.author_email) ||
  65 + project.users.find_by_name(noteable.author_name)
66 rescue 66 rescue
67 nil 67 nil
68 end 68 end
@@ -97,8 +97,8 @@ class Note &lt; ActiveRecord::Base @@ -97,8 +97,8 @@ class Note &lt; ActiveRecord::Base
97 # otherwise false is returned 97 # otherwise false is returned
98 def downvote? 98 def downvote?
99 votable? && (note.start_with?('-1') || 99 votable? && (note.start_with?('-1') ||
100 - note.start_with?(':-1:')  
101 - ) 100 + note.start_with?(':-1:')
  101 + )
102 end 102 end
103 103
104 def for_commit? 104 def for_commit?
@@ -136,8 +136,8 @@ class Note &lt; ActiveRecord::Base @@ -136,8 +136,8 @@ class Note &lt; ActiveRecord::Base
136 else 136 else
137 super 137 super
138 end 138 end
139 - # Temp fix to prevent app crash  
140 - # if note commit id doesn't exist 139 + # Temp fix to prevent app crash
  140 + # if note commit id doesn't exist
141 rescue 141 rescue
142 nil 142 nil
143 end 143 end
@@ -146,8 +146,8 @@ class Note &lt; ActiveRecord::Base @@ -146,8 +146,8 @@ class Note &lt; ActiveRecord::Base
146 # otherwise false is returned 146 # otherwise false is returned
147 def upvote? 147 def upvote?
148 votable? && (note.start_with?('+1') || 148 votable? && (note.start_with?('+1') ||
149 - note.start_with?(':+1:')  
150 - ) 149 + note.start_with?(':+1:')
  150 + )
151 end 151 end
152 152
153 def votable? 153 def votable?
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 + cattr_accessor :current_user
  4 +
2 def after_create(merge_request) 5 def after_create(merge_request)
  6 + event_author_id = merge_request.author_id
  7 + if event_author_id
  8 + create_event(merge_request, Event.determine_action(merge_request))
  9 + end
  10 +
3 notification.new_merge_request(merge_request, current_user) 11 notification.new_merge_request(merge_request, current_user)
4 end 12 end
5 13
6 def after_close(merge_request, transition) 14 def after_close(merge_request, transition)
7 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 15 + create_event(merge_request, Event::CLOSED)
  16 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
8 17
9 notification.close_mr(merge_request, current_user) 18 notification.close_mr(merge_request, current_user)
10 end 19 end
11 20
12 def after_merge(merge_request, transition) 21 def after_merge(merge_request, transition)
13 notification.merge_mr(merge_request) 22 notification.merge_mr(merge_request)
  23 + # Since MR can be merged via sidekiq
  24 + # to prevent event duplication do this check
  25 + return true if merge_request.merge_event
  26 +
  27 + Event.create(
  28 + project: merge_request.target_project,
  29 + target_id: merge_request.id,
  30 + target_type: merge_request.class.name,
  31 + action: Event::MERGED,
  32 + author_id: merge_request.author_id_of_changes
  33 + )
14 end 34 end
15 35
16 def after_reopen(merge_request, transition) 36 def after_reopen(merge_request, transition)
17 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 37 + create_event(merge_request, Event::REOPENED)
  38 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
18 end 39 end
19 40
20 def after_update(merge_request) 41 def after_update(merge_request)
21 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? 42 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
22 end 43 end
  44 +
  45 +
  46 + def create_event(record, status)
  47 + Event.create(
  48 + project: record.target_project,
  49 + target_id: record.id,
  50 + target_type: record.class.name,
  51 + action: status,
  52 + author_id: record.author_id
  53 + )
  54 + end
  55 +
23 end 56 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,10 @@ class NotificationService @@ -80,8 +80,10 @@ 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.source_project)
  84 + recipients = recipients.concat(reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project))
  85 + recipients = recipients.concat(project_watchers(merge_request.source_project))
  86 + recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
85 87
86 recipients.each do |recipient| 88 recipients.each do |recipient|
87 mailer.merged_merge_request_email(recipient.id, merge_request.id) 89 mailer.merged_merge_request_email(recipient.id, merge_request.id)
@@ -102,7 +104,7 @@ class NotificationService @@ -102,7 +104,7 @@ class NotificationService
102 # ignore wall messages 104 # ignore wall messages
103 return true unless note.noteable_type.present? 105 return true unless note.noteable_type.present?
104 106
105 - opts = { noteable_type: note.noteable_type, project_id: note.project_id } 107 + opts = {noteable_type: note.noteable_type, project_id: note.project_id}
106 108
107 if note.commit_id.present? 109 if note.commit_id.present?
108 opts.merge!(commit_id: note.commit_id) 110 opts.merge!(commit_id: note.commit_id)
@@ -191,14 +193,14 @@ class NotificationService @@ -191,14 +193,14 @@ class NotificationService
191 end 193 end
192 end 194 end
193 195
194 - def new_resource_email(target, method) 196 + def new_resource_email(target, project, method)
195 if target.respond_to?(:participants) 197 if target.respond_to?(:participants)
196 recipients = target.participants 198 recipients = target.participants
197 else 199 else
198 recipients = [] 200 recipients = []
199 end 201 end
200 - recipients = reject_muted_users(recipients, target.project)  
201 - recipients = recipients.concat(project_watchers(target.project)).uniq 202 + recipients = reject_muted_users(recipients, project)
  203 + recipients = recipients.concat(project_watchers(project)).uniq
202 recipients.delete(target.author) 204 recipients.delete(target.author)
203 205
204 recipients.each do |recipient| 206 recipients.each do |recipient|
@@ -206,9 +208,9 @@ class NotificationService @@ -206,9 +208,9 @@ class NotificationService
206 end 208 end
207 end 209 end
208 210
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 211 + def close_resource_email(target, project, current_user, method)
  212 + recipients = reject_muted_users([target.author, target.assignee], project)
  213 + recipients = recipients.concat(project_watchers(project)).uniq
212 recipients.delete(current_user) 214 recipients.delete(current_user)
213 215
214 recipients.each do |recipient| 216 recipients.each do |recipient|
@@ -216,14 +218,14 @@ class NotificationService @@ -216,14 +218,14 @@ class NotificationService
216 end 218 end
217 end 219 end
218 220
219 - def reassign_resource_email(target, current_user, method) 221 + def reassign_resource_email(target, project, current_user, method)
220 recipients = User.where(id: [target.assignee_id, target.assignee_id_was]) 222 recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
221 223
222 # Add watchers to email list 224 # Add watchers to email list
223 - recipients = recipients.concat(project_watchers(target.project)) 225 + recipients = recipients.concat(project_watchers(project))
224 226
225 # reject users with disabled notifications 227 # reject users with disabled notifications
226 - recipients = reject_muted_users(recipients, target.project) 228 + recipients = reject_muted_users(recipients, project)
227 229
228 # Reject me from recipients if I reassign an item 230 # Reject me from recipients if I reassign an item
229 recipients.delete(current_user) 231 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/merge_requests/update_branches.js.haml 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  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 +
  6 +
  7 +
  8 +
app/views/notify/closed_merge_request_email.html.haml
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
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.project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + Projects:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} &rarr; #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}
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 +Project:Branches: #{@merge_request.source_project.path_with_namespace}/#{@merge_request.source_branch} - #{@merge_request.target_project.path_with_namespace}#{@merge_request.target_branch}
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 + Projects:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} &rarr; #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}
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 +Project:Branches: #{@merge_request.source_project.path_with_namespace}/#{@merge_request.source_branch} - #{@merge_request.target_project.path_with_namespace}#{@merge_request.target_branch}
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 + Project:Branches: #{@merge_request.source_project.path_with_namespace}/#{@merge_request.source_branch} - #{@merge_request.target_project.path_with_namespace}#{@merge_request.target_branch}
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 +From: <%= @merge_request.source_project.path_with_namespace%>:<%= @merge_request.source_branch %> to <%= @merge_request.target_project.path_with_namespace%>:<%= @merge_request.target_branch %>
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/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/compare/show.html.haml
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
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
app/views/projects/merge_requests/_form.html.haml
1 -= form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |f| 1 += form_for [@project, @merge_request], html: { class: "#{controller.action_name}-merge-request form-horizontal" } do |form_helper|
2 -if @merge_request.errors.any? 2 -if @merge_request.errors.any?
3 .alert.alert-error 3 .alert.alert-error
4 %ul 4 %ul
@@ -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= form_helper.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span4'})
  17 + .padded= form_helper.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= form_helper.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= form_helper.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,12 +49,11 @@ @@ -47,12 +49,11 @@
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 + = form_helper.submit 'Submit merge request', class: "btn btn-create"
54 -else 55 -else
55 - = f.submit 'Save changes', class: "btn btn-save" 56 + = form_helper.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(@project), class: "btn btn-cancel" do
58 Cancel 59 Cancel
@@ -63,16 +64,23 @@ @@ -63,16 +64,23 @@
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 - , target_branch = $("#merge_request_target_branch"); 67 + var source_branch = $("#merge_request_source_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(@project)}", {ref: source_branch.val() });
  72 + $.get("#{branch_to_project_merge_requests_path(@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 - }); 74 + target_project.live("change", function() {
  75 + $.get("#{update_branches_project_merge_requests_path(@project)}", {target_project_id: $(this).val() });
  76 + });
  77 + source_branch.live("change", function() {
  78 + $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: $(this).val() });
  79 + });
  80 + target_branch.live("change", function() {
  81 + $.get("#{branch_to_project_merge_requests_path(@project)}", {target_project_id: target_project.val(),ref: $(this).val() });
  82 + });
75 83
76 - target_branch.live("change", function() {  
77 - $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });  
78 }); 84 });
  85 +
  86 +
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 + = "#{merge_request.source_project.path_with_namespace}/#{merge_request.source_branch}"
11 %i.icon-angle-right 12 %i.icon-angle-right
12 - = merge_request.target_branch 13 + = "#{merge_request.target_project.path_with_namespace}/#{merge_request.target_branch}"
13 .merge-request-info 14 .merge-request-info
14 - if merge_request.author 15 - if merge_request.author
15 - authored by #{link_to_member(@project, merge_request.author)} 16 + authored by #{link_to_member(merge_request.source_project, merge_request.author)}
16 - if merge_request.votes_count > 0 17 - if merge_request.votes_count > 0
17 = render 'votes/votes_inline', votable: merge_request 18 = render 'votes/votes_inline', votable: merge_request
18 - if merge_request.notes.any? 19 - 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, @project)}");
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/_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-project= @merge_request.source_project.path_with_namespace
4 %span.label-branch= @merge_request.source_branch 5 %span.label-branch= @merge_request.source_branch
5 &rarr; 6 &rarr;
  7 + %span.label-project= @merge_request.target_project.path_with_namespace
6 %span.label-branch= @merge_request.target_branch 8 %span.label-branch= @merge_request.target_branch
7 9
8 %span.pull-right 10 %span.pull-right
app/views/search/_result.html.haml
@@ -22,11 +22,11 @@ @@ -22,11 +22,11 @@
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 + %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})
30 - @issues.each do |issue| 30 - @issues.each do |issue|
31 %li 31 %li
32 issue: 32 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
@@ -255,6 +255,7 @@ Gitlab::Application.routes.draw do @@ -255,6 +255,7 @@ Gitlab::Application.routes.draw do
255 collection do 255 collection do
256 get :branch_from 256 get :branch_from
257 get :branch_to 257 get :branch_to
  258 + get :update_branches
258 end 259 end
259 end 260 end
260 261
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,15 @@ FileUtils.cd(REPO_PATH) do @@ -19,5 +19,15 @@ 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..."
22 24
  25 +SATELLITE_PATH = Rails.root.join('tmp', 'satellite')
  26 +# Make directory
  27 +FileUtils.mkdir_p(SATELLITE_PATH)
  28 +# Chdir, clone from the seed
  29 +FileUtils.cd(SATELLITE_PATH) do
  30 + # Clone the satellite
  31 + `git clone --quiet #{REPO_PATH}/gitlabhq #{SATELLITE_PATH}/gitlabhq`
  32 +end
23 puts ' done.' 33 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.connection.execute("update merge_requests set 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,41 @@ @@ -0,0 +1,41 @@
  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 +
  8 + @javascript
  9 + Scenario: I can visit the target projects commit for a forked merge request
  10 + Given I visit project "Forked Shop" merge requests page
  11 + And I click link "New Merge Request"
  12 + And I fill out a "Merge Request On Forked Project" merge request
  13 + And I follow the target commit link
  14 + Then I should see the commit under the forked from project
  15 +
  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 +
  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 +
  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 prefilled "Merge Request On Forked Project"
  40 +
  41 +
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/deploy_keys.rb
@@ -3,10 +3,13 @@ class Spinach::Features::ProjectDeployKeys &lt; Spinach::FeatureSteps @@ -3,10 +3,13 @@ class Spinach::Features::ProjectDeployKeys &lt; Spinach::FeatureSteps
3 include SharedProject 3 include SharedProject
4 include SharedPaths 4 include SharedPaths
5 5
  6 +
  7 +
6 step 'project has deploy key' do 8 step 'project has deploy key' do
7 create(:deploy_keys_project, project: @project) 9 create(:deploy_keys_project, project: @project)
8 end 10 end
9 11
  12 +
10 step 'I should see project deploy keys' do 13 step 'I should see project deploy keys' do
11 within '.enabled-keys' do 14 within '.enabled-keys' do
12 page.should have_content deploy_key.title 15 page.should have_content deploy_key.title
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
@@ -14,12 +16,17 @@ class ForkProject &lt; Spinach::FeatureSteps @@ -14,12 +16,17 @@ class ForkProject &lt; Spinach::FeatureSteps
14 @project.team << [@user, :reporter] 16 @project.team << [@user, :reporter]
15 end 17 end
16 18
  19 +
17 step 'I should see the forked project page' do 20 step 'I should see the forked project page' do
18 page.should have_content "Project was successfully forked." 21 page.should have_content "Project was successfully forked."
19 current_path.should include current_user.namespace.path 22 current_path.should include current_user.namespace.path
  23 + @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
20 end 24 end
21 25
22 step 'I already have a project named "Shop" in my namespace' do 26 step 'I already have a project named "Shop" in my namespace' do
  27 + current_user.namespace ||= create(:namespace)
  28 + current_user.namespace.should_not be_nil
  29 + current_user.namespace.path.should_not be_nil
23 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace) 30 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
24 end 31 end
25 32
features/steps/project/project_forked_merge_requests.rb 0 → 100644
@@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
  1 +class ProjectForkedMergeRequests < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedNote
  5 + include SharedPaths
  6 +
  7 +
  8 + Given 'I am a member of project "Shop"' do
  9 + @project = Project.find_by_name "Shop"
  10 + @project ||= create(:project_with_code, name: "Shop")
  11 + @project.team << [@user, :reporter]
  12 + end
  13 +
  14 + And 'I have a project forked off of "Shop" called "Forked Shop"' do
  15 + @forking_user = @user
  16 + forked_project_link = build(:forked_project_link)
  17 + @forked_project = Project.find_by_name "Forked Shop"
  18 + @forked_project ||= create(:source_project_with_code, name: "Forked Shop", forked_project_link: forked_project_link, creator_id: @forking_user.id)
  19 + forked_project_link.forked_from_project = @project
  20 + forked_project_link.forked_to_project = @forked_project
  21 + forked_project_link.save!
  22 + end
  23 +
  24 +
  25 + Given 'I click link "New Merge Request"' do
  26 + click_link "New Merge Request"
  27 + end
  28 +
  29 +
  30 + Then 'I should see merge request "Merge Request On Forked Project"' do
  31 + page.should have_content "Merge Request On Forked Project"
  32 + @project.merge_requests.size.should >= 1
  33 + @merge_request = @project.merge_requests.last
  34 + current_path.should == project_merge_request_path(@project, @merge_request)
  35 + @merge_request.title.should == "Merge Request On Forked Project"
  36 + @merge_request.source_project.should == @forked_project
  37 + end
  38 +
  39 + And 'I fill out a "Merge Request On Forked Project" merge request' do
  40 + fill_in "merge_request_title", with: "Merge Request On Forked Project"
  41 + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
  42 + find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
  43 +
  44 + select @project.path_with_namespace, from: "merge_request_target_project_id"
  45 + find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
  46 +
  47 + select "master", from: "merge_request_source_branch"
  48 + find(:select, "merge_request_source_branch", {}).value.should == "master"
  49 + select "stable", from: "merge_request_target_branch"
  50 + find(:select, "merge_request_target_branch", {}).value.should == "stable"
  51 + end
  52 +
  53 + And 'I submit the merge request' do
  54 + click_button "Submit merge request"
  55 + end
  56 +
  57 + And 'I follow the target commit link' do
  58 + commit = @project.repository.commit
  59 + click_link commit.short_id(8)
  60 + end
  61 +
  62 + Then 'I should see the commit under the forked from project' do
  63 + commit = @project.repository.commit
  64 + page.should have_content(commit.message)
  65 + end
  66 +
  67 + And 'I click "Create Merge Request on fork" link' do
  68 + click_link "Create Merge Request on fork"
  69 + end
  70 +
  71 + Then 'I see prefilled new Merge Request page for the forked project' do
  72 + current_path.should == new_project_merge_request_path(@forked_project)
  73 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  74 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  75 + find("#merge_request_source_branch").value.should == "new_design"
  76 + find("#merge_request_target_branch").value.should == "master"
  77 + find("#merge_request_title").value.should == "New Design"
  78 + end
  79 +
  80 + Then 'I should see last push widget' do
  81 + page.should have_content "You pushed to new_design"
  82 + page.should have_link "Create Merge Request"
  83 + end
  84 +
  85 + Given 'project "Forked Shop" has push event' do
  86 + @forked_project = Project.find_by_name("Forked Shop")
  87 +
  88 + data = {
  89 + before: "0000000000000000000000000000000000000000",
  90 + after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
  91 + ref: "refs/heads/new_design",
  92 + user_id: @user.id,
  93 + user_name: @user.name,
  94 + repository: {
  95 + name: @forked_project.name,
  96 + url: "localhost/rubinius",
  97 + description: "",
  98 + homepage: "localhost/rubinius",
  99 + private: true
  100 + }
  101 + }
  102 +
  103 + @event = Event.create(
  104 + project: @forked_project,
  105 + action: Event::PUSHED,
  106 + data: data,
  107 + author_id: @user.id
  108 + )
  109 + end
  110 +
  111 +
  112 + Then 'I click link edit "Merge Request On Forked Project"' do
  113 + #there are other edit buttons in this page for replies
  114 +# links = page.all("a.btn.grouped")
  115 +# links.each {|e|puts e.inspect }
  116 + #TODO:[IA-08] there has got to be a better way to find this button -- there are multiple "Edit" buttons, so that won't work, maybe if we give it an explicit class in the haml
  117 + #click_link "Edit" # doesn't work, multiple "Edit" buttons
  118 + # find(:link, "a.btn:nth-child(3)").click
  119 + # find(:link, "/html/body/div[2]/div/div/h3/span[5]/a[2]").click
  120 + page.first(:xpath, "/html/body/div[2]/div/div/h3/span[5]/a[2]").click
  121 + end
  122 +
  123 + Then 'I see prefilled "Merge Request On Forked Project"' do
  124 + current_path.should == edit_project_merge_request_path(@project, @merge_request)
  125 + page.should have_content "Edit merge request #{@merge_request.id}"
  126 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  127 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  128 + find("#merge_request_source_branch").value.should == "master"
  129 + find("#merge_request_target_branch").value.should == "stable"
  130 + find("#merge_request_title").value.should == "Merge Request On Forked Project"
  131 + end
  132 +
  133 +
  134 + def project
  135 + @project ||= Project.find_by_name!("Shop")
  136 + end
  137 +
  138 +end
features/steps/project/project_merge_requests.rb
@@ -24,6 +24,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -24,6 +24,7 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
24 page.should have_content "Wiki Feature" 24 page.should have_content "Wiki Feature"
25 end 25 end
26 26
  27 +
27 Then 'I should see closed merge request "Bug NS-04"' do 28 Then 'I should see closed merge request "Bug NS-04"' do
28 merge_request = MergeRequest.find_by_title!("Bug NS-04") 29 merge_request = MergeRequest.find_by_title!("Bug NS-04")
29 merge_request.closed?.should be_true 30 merge_request.closed?.should be_true
@@ -56,30 +57,35 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps @@ -56,30 +57,35 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
56 end 57 end
57 58
58 And 'I submit new merge request "Wiki Feature"' do 59 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" 60 + fill_in "merge_request_title", :with => "Wiki Feature"
  61 + select project.path_with_namespace, :from => "merge_request_target_project_id"
  62 + select "master", :from => "merge_request_source_branch"
  63 + select "stable", :from => "merge_request_target_branch"
  64 + find(:select, "merge_request_target_branch", {}).find(:option, "stable", {}).value.should == "stable"
62 click_button "Submit merge request" 65 click_button "Submit merge request"
63 end 66 end
64 67
65 And 'project "Shop" have "Bug NS-04" open merge request' do 68 And 'project "Shop" have "Bug NS-04" open merge request' do
66 create(:merge_request, 69 create(:merge_request,
67 title: "Bug NS-04", 70 title: "Bug NS-04",
68 - project: project, 71 + source_project: project,
  72 + target_project: project,
69 author: project.users.first) 73 author: project.users.first)
70 end 74 end
71 75
72 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do 76 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
73 create(:merge_request_with_diffs, 77 create(:merge_request_with_diffs,
74 title: "Bug NS-05", 78 title: "Bug NS-05",
75 - project: project, 79 + source_project: project,
  80 + target_project: project,
76 author: project.users.first) 81 author: project.users.first)
77 end 82 end
78 83
79 And 'project "Shop" have "Feature NS-03" closed merge request' do 84 And 'project "Shop" have "Feature NS-03" closed merge request' do
80 create(:closed_merge_request, 85 create(:closed_merge_request,
81 title: "Feature NS-03", 86 title: "Feature NS-03",
82 - project: project, 87 + source_project: project,
  88 + target_project: project,
83 author: project.users.first) 89 author: project.users.first)
84 end 90 end
85 91
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
@@ -51,9 +51,10 @@ module API @@ -51,9 +51,10 @@ module API
51 # 51 #
52 # Parameters: 52 # Parameters:
53 # 53 #
54 - # id (required) - The ID of a project 54 + # id (required) - The ID of a project - this will be the source of the merge request
55 # source_branch (required) - The source branch 55 # source_branch (required) - The source branch
56 # target_branch (required) - The target branch 56 # target_branch (required) - The target branch
  57 + # target_project - The target project of the merge request defaults to the :id of the project
57 # assignee_id - Assignee user ID 58 # assignee_id - Assignee user ID
58 # title (required) - Title of MR 59 # title (required) - Title of MR
59 # 60 #
@@ -63,11 +64,15 @@ module API @@ -63,11 +64,15 @@ module API
63 post ":id/merge_requests" do 64 post ":id/merge_requests" do
64 authorize! :write_merge_request, user_project 65 authorize! :write_merge_request, user_project
65 required_attributes! [:source_branch, :target_branch, :title] 66 required_attributes! [:source_branch, :target_branch, :title]
66 -  
67 - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] 67 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
68 merge_request = user_project.merge_requests.new(attrs) 68 merge_request = user_project.merge_requests.new(attrs)
69 merge_request.author = current_user 69 merge_request.author = current_user
70 - 70 + merge_request.source_project = user_project
  71 + if !attrs[:target_project_id].nil? && user_project.forked? && user_project.forked_from_project.id.to_s == attrs[:target_project_id]
  72 + merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
  73 + elsif attrs[:target_project].nil?
  74 + merge_request.target_project = user_project
  75 + end
71 if merge_request.save 76 if merge_request.save
72 merge_request.reload_code 77 merge_request.reload_code
73 present merge_request, with: Entities::MergeRequest 78 present merge_request, with: Entities::MergeRequest
lib/gitlab/satellite/action.rb
1 module Gitlab 1 module Gitlab
2 module Satellite 2 module Satellite
3 class Action 3 class Action
4 - DEFAULT_OPTIONS = { git_timeout: 30.seconds } 4 + DEFAULT_OPTIONS = { git_timeout: 30.seconds}
5 5
6 attr_accessor :options, :project, :user 6 attr_accessor :options, :project, :user
7 7
@@ -34,16 +34,19 @@ module Gitlab @@ -34,16 +34,19 @@ module Gitlab
34 Gitlab::ShellEnv.reset_env 34 Gitlab::ShellEnv.reset_env
35 end 35 end
36 36
37 - # * Clears the satellite  
38 - # * Updates the satellite from Gitolite 37 + # * Recreates the satellite
39 # * Sets up Git variables for the user 38 # * Sets up Git variables for the user
40 # 39 #
41 # Note: use this within #in_locked_and_timed_satellite 40 # Note: use this within #in_locked_and_timed_satellite
42 def prepare_satellite!(repo) 41 def prepare_satellite!(repo)
43 project.satellite.clear_and_update! 42 project.satellite.clear_and_update!
44 43
45 - repo.git.config({}, "user.name", user.name)  
46 - repo.git.config({}, "user.email", user.email) 44 + repo.config['user.name']=user.name
  45 + repo.config['user.email']=user.email
  46 + end
  47 +
  48 + def default_options(options = {})
  49 + {raise: true, timeout: true}.merge(options)
47 end 50 end
48 end 51 end
49 end 52 end
lib/gitlab/satellite/merge_action.rb
@@ -5,37 +5,37 @@ module Gitlab @@ -5,37 +5,37 @@ 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
@@ -45,6 +45,82 @@ module Gitlab @@ -45,6 +45,82 @@ module Gitlab
45 false 45 false
46 end 46 end
47 47
  48 +
  49 + # Get a raw diff of the source to the target
  50 + def diff_in_satellite
  51 + in_locked_and_timed_satellite do |merge_repo|
  52 + prepare_satellite!(merge_repo)
  53 +
  54 + update_satellite_source_and_target!(merge_repo)
  55 + if merge_request.for_fork?
  56 + diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  57 + else
  58 + diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
  59 +
  60 + end
  61 + return diff
  62 + end
  63 + rescue Grit::Git::CommandFailed => ex
  64 + Gitlab::GitLogger.error(ex.message)
  65 + false
  66 + end
  67 +
  68 + # Only show what is new in the source branch compared to the target branch, not the other way around.
  69 + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
  70 + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
  71 + def diffs_between_satellite
  72 + in_locked_and_timed_satellite do |merge_repo|
  73 + prepare_satellite!(merge_repo)
  74 + update_satellite_source_and_target!(merge_repo)
  75 + if merge_request.for_fork?
  76 + common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
  77 + diffs = merge_repo.diff(default_options, common_commit, "source/#{merge_request.source_branch}")
  78 + else
  79 + common_commit = merge_repo.git.native(:merge_base, default_options, ["#{merge_request.target_branch}", "#{merge_request.source_branch}"]).strip
  80 + diffs = merge_repo.diff(default_options, common_commit, "#{merge_request.source_branch}")
  81 + end
  82 + return diffs
  83 + end
  84 + rescue Grit::Git::CommandFailed => ex
  85 + Gitlab::GitLogger.error(ex.message)
  86 + false
  87 + end
  88 +
  89 + # Get commit as an email patch
  90 + def format_patch
  91 + in_locked_and_timed_satellite do |merge_repo|
  92 + prepare_satellite!(merge_repo)
  93 + update_satellite_source_and_target!(merge_repo)
  94 + if (merge_request.for_fork?)
  95 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}...source/#{merge_request.source_branch}")
  96 + else
  97 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}...#{merge_request.source_branch}")
  98 + end
  99 + return patch
  100 + end
  101 + rescue Grit::Git::CommandFailed => ex
  102 + Gitlab::GitLogger.error(ex.message)
  103 + false
  104 + end
  105 +
  106 + # Retrieve an array of commits between the source and the target
  107 + def commits_between
  108 + in_locked_and_timed_satellite do |merge_repo|
  109 + prepare_satellite!(merge_repo)
  110 + update_satellite_source_and_target!(merge_repo)
  111 + if (merge_request.for_fork?)
  112 + commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  113 + else
  114 + commits = merge_repo.commits_between("#{merge_request.target_branch}", "#{merge_request.source_branch}")
  115 + end
  116 + return commits
  117 +
  118 + end
  119 + rescue Grit::Git::CommandFailed => ex
  120 + Gitlab::GitLogger.error(ex.message)
  121 + false
  122 + end
  123 +
48 private 124 private
49 125
50 # Merges the source_branch into the target_branch in the satellite. 126 # Merges the source_branch into the target_branch in the satellite.
@@ -54,18 +130,38 @@ module Gitlab @@ -54,18 +130,38 @@ module Gitlab
54 # Returns false if the merge produced conflicts 130 # Returns false if the merge produced conflicts
55 # Returns true otherwise 131 # Returns true otherwise
56 def merge_in_satellite!(repo) 132 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}") 133 + update_satellite_source_and_target!(repo)
61 134
62 - # merge the source branch from Gitolite into the satellite 135 + # merge the source branch into the satellite
63 # will raise CommandFailed when merge fails 136 # will raise CommandFailed when merge fails
64 - repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch) 137 + if merge_request.for_fork?
  138 + repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
  139 + else
  140 + repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
  141 + end
  142 + rescue Grit::Git::CommandFailed => ex
  143 + Gitlab::GitLogger.error(ex.message)
  144 + false
  145 + end
  146 +
  147 + # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
  148 + def update_satellite_source_and_target!(repo)
  149 + if merge_request.for_fork?
  150 + repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
  151 + repo.remote_fetch('source')
  152 + repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
  153 + else
  154 + # 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
  155 + # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared)
  156 + repo.git.checkout(default_options, "#{merge_request.source_branch}")
  157 + repo.git.checkout(default_options, "#{merge_request.target_branch}")
  158 + end
65 rescue Grit::Git::CommandFailed => ex 159 rescue Grit::Git::CommandFailed => ex
66 Gitlab::GitLogger.error(ex.message) 160 Gitlab::GitLogger.error(ex.message)
67 false 161 false
68 end 162 end
  163 +
  164 +
69 end 165 end
70 end 166 end
71 end 167 end
lib/gitlab/satellite/satellite.rb
1 module Gitlab 1 module Gitlab
2 - class SatelliteNotExistError < StandardError; end 2 + class SatelliteNotExistError < StandardError;
  3 + end
3 4
4 module Satellite 5 module Satellite
5 class Satellite 6 class Satellite
@@ -21,11 +22,14 @@ module Gitlab @@ -21,11 +22,14 @@ module Gitlab
21 raise SatelliteNotExistError.new("Satellite doesn't exist") 22 raise SatelliteNotExistError.new("Satellite doesn't exist")
22 end 23 end
23 24
  25 +
24 def clear_and_update! 26 def clear_and_update!
25 raise_no_satellite unless exists? 27 raise_no_satellite unless exists?
26 - 28 + File.exists? path
  29 + @repo = nil
27 clear_working_dir! 30 clear_working_dir!
28 delete_heads! 31 delete_heads!
  32 + remove_remotes!
29 update_from_source! 33 update_from_source!
30 end 34 end
31 35
@@ -55,14 +59,16 @@ module Gitlab @@ -55,14 +59,16 @@ module Gitlab
55 raise_no_satellite unless exists? 59 raise_no_satellite unless exists?
56 60
57 File.open(lock_file, "w+") do |f| 61 File.open(lock_file, "w+") do |f|
58 - f.flock(File::LOCK_EX)  
59 -  
60 - Dir.chdir(path) do  
61 - return yield 62 + begin
  63 + f.flock File::LOCK_EX
  64 + Dir.chdir(path) { return yield }
  65 + ensure
  66 + f.flock File::LOCK_UN
62 end 67 end
63 end 68 end
64 end 69 end
65 70
  71 +
66 def lock_file 72 def lock_file
67 create_locks_dir unless File.exists?(lock_files_dir) 73 create_locks_dir unless File.exists?(lock_files_dir)
68 File.join(lock_files_dir, "satellite_#{project.id}.lock") 74 File.join(lock_files_dir, "satellite_#{project.id}.lock")
@@ -100,20 +106,35 @@ module Gitlab @@ -100,20 +106,35 @@ module Gitlab
100 if heads.include? PARKING_BRANCH 106 if heads.include? PARKING_BRANCH
101 repo.git.checkout({}, PARKING_BRANCH) 107 repo.git.checkout({}, PARKING_BRANCH)
102 else 108 else
103 - repo.git.checkout({b: true}, PARKING_BRANCH) 109 + repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
104 end 110 end
105 111
106 # remove the parking branch from the list of heads ... 112 # remove the parking branch from the list of heads ...
107 heads.delete(PARKING_BRANCH) 113 heads.delete(PARKING_BRANCH)
108 # ... and delete all others 114 # ... and delete all others
109 - heads.each { |head| repo.git.branch({D: true}, head) } 115 + heads.each { |head| repo.git.branch(default_options({D: true}), head) }
  116 + end
  117 +
  118 + # Deletes all remotes except origin
  119 + #
  120 + # This ensures we have no remote name clashes or issues updating branches when
  121 + # working with the satellite.
  122 + def remove_remotes!
  123 + remotes = repo.git.remote.split(' ')
  124 + remotes.delete('origin')
  125 + remotes.each { |name| repo.git.remote(default_options,'rm', name)}
110 end 126 end
111 127
112 # Updates the satellite from Gitolite 128 # Updates the satellite from Gitolite
113 # 129 #
114 # Note: this will only update remote branches (i.e. origin/*) 130 # Note: this will only update remote branches (i.e. origin/*)
115 def update_from_source! 131 def update_from_source!
116 - repo.git.fetch({timeout: true}, :origin) 132 + repo.git.fetch(default_options, :origin)
  133 + end
  134 +
  135 +
  136 + def default_options(options = {})
  137 + {raise: true, timeout: true}.merge(options)
117 end 138 end
118 139
119 # Create directory for stroing 140 # Create directory for stroing
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) }
  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
  1 +<<<<<<< HEAD
1 include ActionDispatch::TestProcess 2 include ActionDispatch::TestProcess
  3 +=======
  4 +require Rails.root.join('spec', 'support', 'test_env.rb')
  5 +>>>>>>> Merge Request on forked projects
2 6
3 FactoryGirl.define do 7 FactoryGirl.define do
4 sequence :sentence, aliases: [:title, :content] do 8 sequence :sentence, aliases: [:title, :content] do
@@ -29,8 +33,19 @@ FactoryGirl.define do @@ -29,8 +33,19 @@ FactoryGirl.define do
29 sequence(:name) { |n| "project#{n}" } 33 sequence(:name) { |n| "project#{n}" }
30 path { name.downcase.gsub(/\s/, '_') } 34 path { name.downcase.gsub(/\s/, '_') }
31 creator 35 creator
  36 +
  37 + trait :source do
  38 + sequence(:name) { |n| "source project#{n}" }
  39 + end
  40 + trait :target do
  41 + sequence(:name) { |n| "target project#{n}" }
  42 + end
  43 +
  44 + factory :source_project, traits: [:source]
  45 + factory :target_project, traits: [:target]
32 end 46 end
33 47
  48 +
34 factory :redmine_project, parent: :project do 49 factory :redmine_project, parent: :project do
35 issues_tracker { "redmine" } 50 issues_tracker { "redmine" }
36 issues_tracker_id { "project_name_in_redmine" } 51 issues_tracker_id { "project_name_in_redmine" }
@@ -39,14 +54,24 @@ FactoryGirl.define do @@ -39,14 +54,24 @@ FactoryGirl.define do
39 factory :project_with_code, parent: :project do 54 factory :project_with_code, parent: :project do
40 path { 'gitlabhq' } 55 path { 'gitlabhq' }
41 56
  57 + trait :source_path do
  58 + path { 'source_gitlabhq' }
  59 + end
  60 +
  61 + trait :target_path do
  62 + path { 'target_gitlabhq' }
  63 + end
  64 +
  65 + factory :source_project_with_code, traits: [:source, :source_path]
  66 + factory :target_project_with_code, traits: [:target, :target_path]
  67 +
42 after :create do |project| 68 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}") 69 + TestEnv.clear_repo_dir(project.namespace, project.path)
  70 + TestEnv.create_repo(project.namespace, project.path)
47 end 71 end
48 end 72 end
49 73
  74 +
50 factory :group do 75 factory :group do
51 sequence(:name) { |n| "group#{n}" } 76 sequence(:name) { |n| "group#{n}" }
52 path { name.downcase.gsub(/\s/, '_') } 77 path { name.downcase.gsub(/\s/, '_') }
@@ -86,7 +111,8 @@ FactoryGirl.define do @@ -86,7 +111,8 @@ FactoryGirl.define do
86 factory :merge_request do 111 factory :merge_request do
87 title 112 title
88 author 113 author
89 - project factory: :project_with_code 114 + source_project factory: :source_project_with_code
  115 + target_project factory: :target_project_with_code
90 source_branch "master" 116 source_branch "master"
91 target_branch "stable" 117 target_branch "stable"
92 118
@@ -96,13 +122,13 @@ FactoryGirl.define do @@ -96,13 +122,13 @@ FactoryGirl.define do
96 source_branch "stable" # pretend bcf03b5d 122 source_branch "stable" # pretend bcf03b5d
97 st_commits do 123 st_commits do
98 [ 124 [
99 - project.repository.commit('bcf03b5d').to_hash,  
100 - project.repository.commit('bcf03b5d~1').to_hash,  
101 - project.repository.commit('bcf03b5d~2').to_hash 125 + source_project.repository.commit('bcf03b5d').to_hash,
  126 + source_project.repository.commit('bcf03b5d~1').to_hash,
  127 + source_project.repository.commit('bcf03b5d~2').to_hash
102 ] 128 ]
103 end 129 end
104 st_diffs do 130 st_diffs do
105 - project.repo.diff("bcf03b5d~3", "bcf03b5d") 131 + source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
106 end 132 end
107 end 133 end
108 134
@@ -133,7 +159,7 @@ FactoryGirl.define do @@ -133,7 +159,7 @@ FactoryGirl.define do
133 159
134 trait :on_commit do 160 trait :on_commit do
135 project factory: :project_with_code 161 project factory: :project_with_code
136 - commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" 162 + commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
137 noteable_type "Commit" 163 noteable_type "Commit"
138 end 164 end
139 165
@@ -143,12 +169,12 @@ FactoryGirl.define do @@ -143,12 +169,12 @@ FactoryGirl.define do
143 169
144 trait :on_merge_request do 170 trait :on_merge_request do
145 project factory: :project_with_code 171 project factory: :project_with_code
146 - noteable_id 1 172 + noteable_id 1
147 noteable_type "MergeRequest" 173 noteable_type "MergeRequest"
148 end 174 end
149 175
150 trait :on_issue do 176 trait :on_issue do
151 - noteable_id 1 177 + noteable_id 1
152 noteable_type "Issue" 178 noteable_type "Issue"
153 end 179 end
154 180
spec/factories_spec.rb
@@ -5,8 +5,10 @@ INVALID_FACTORIES = [ @@ -5,8 +5,10 @@ INVALID_FACTORIES = [
5 :invalid_key, 5 :invalid_key,
6 ] 6 ]
7 7
  8 +
8 FactoryGirl.factories.map(&:name).each do |factory_name| 9 FactoryGirl.factories.map(&:name).each do |factory_name|
9 next if INVALID_FACTORIES.include?(factory_name) 10 next if INVALID_FACTORIES.include?(factory_name)
  11 +
10 describe "#{factory_name} factory" do 12 describe "#{factory_name} factory" do
11 it 'should be valid' do 13 it 'should be valid' do
12 build(factory_name).should be_valid 14 build(factory_name).should be_valid
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, source_project: project, target_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
@@ -205,13 +206,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -205,13 +206,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
205 206
206 # TODO: fix 207 # TODO: fix
207 #it 'should check if previews were rendered separately' do 208 #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 209 + #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
  210 + #should have_css(".js-note-preview", text: "One comment on line 185")
  211 + #end
211 212
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 213 + #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
  214 + #should have_css(".js-note-preview", text: "Another comment on line 17")
  215 + #end
215 #end 216 #end
216 end 217 end
217 218
@@ -238,39 +239,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -238,39 +239,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
238 239
239 # TODO: fix 240 # TODO: fix
240 #it "should remove last note of a discussion" do 241 #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") 242 + # within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do
  243 + # find(".js-note-delete").click
  244 + # end
  245 + # should_not have_css(".note_holder")
246 #end 246 #end
247 end 247 end
248 end 248 end
249 249
250 # TODO: fix 250 # TODO: fix
251 #describe "when replying to a note" do 251 #describe "when replying to a note" do
252 - #before do  
253 - ## create first note  
254 - #find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click 252 + #before do
  253 + ## create first note
  254 + # find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click
255 255
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 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
260 260
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 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
274 #end 274 #end
275 end 275 end
276 276
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,13 +14,14 @@ describe &quot;Application access&quot; do @@ -14,13 +14,14 @@ 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
  24 +
24 # full access 25 # full access
25 project.team << [master, :master] 26 project.team << [master, :master]
26 27
@@ -108,7 +109,7 @@ describe &quot;Application access&quot; do @@ -108,7 +109,7 @@ describe &quot;Application access&quot; do
108 describe "GET /project_code/blob" do 109 describe "GET /project_code/blob" do
109 before do 110 before do
110 commit = project.repository.commit 111 commit = project.repository.commit
111 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 112 + 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)) 113 @blob_path = project_blob_path(project, File.join(commit.id, path))
113 end 114 end
114 115
@@ -232,13 +233,13 @@ describe &quot;Application access&quot; do @@ -232,13 +233,13 @@ describe &quot;Application access&quot; do
232 233
233 234
234 describe "PublicProject" do 235 describe "PublicProject" do
235 - let(:project) { create(:project_with_code) } 236 + let(:project) { create(:project_with_code) }
236 237
237 - let(:master) { create(:user) }  
238 - let(:guest) { create(:user) } 238 + let(:master) { create(:user) }
  239 + let(:guest) { create(:user) }
239 let(:reporter) { create(:user) } 240 let(:reporter) { create(:user) }
240 241
241 - let(:admin) { create(:user) } 242 + let(:admin) { create(:user) }
242 243
243 before do 244 before do
244 # public project 245 # public project
@@ -339,7 +340,7 @@ describe &quot;Application access&quot; do @@ -339,7 +340,7 @@ describe &quot;Application access&quot; do
339 describe "GET /project_code/blob" do 340 describe "GET /project_code/blob" do
340 before do 341 before do
341 commit = project.repository.commit 342 commit = project.repository.commit
342 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 343 + 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)) 344 @blob_path = project_blob_path(project, File.join(commit.id, path))
344 end 345 end
345 346
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,131 @@ @@ -0,0 +1,131 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::Action' do
  4 +
  5 +
  6 + let(:project) { create(:project_with_code) }
  7 + let(:user) { create(:user) }
  8 +
  9 +
  10 + describe '#prepare_satellite!' do
  11 +
  12 + it 'create a repository with a parking branch and one remote: origin' do
  13 + repo = project.satellite.repo
  14 +
  15 + #now lets dirty it up
  16 +
  17 + starting_remote_count = repo.git.list_remotes.size
  18 + starting_remote_count.should >= 1
  19 + #kind of hookey way to add a second remote
  20 + origin_uri = repo.git.remote({v: true}).split(" ")[1]
  21 + begin
  22 + repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
  23 + repo.git.branch({raise: true}, 'a-new-branch')
  24 +
  25 + repo.heads.size.should > (starting_remote_count)
  26 + repo.git.remote().split(" ").size.should > (starting_remote_count)
  27 + rescue
  28 + end
  29 +
  30 + repo.git.config({}, "user.name", "#{user.name} -- foo")
  31 + repo.git.config({}, "user.email", "#{user.email} -- foo")
  32 + repo.config['user.name'].should =="#{user.name} -- foo"
  33 + repo.config['user.email'].should =="#{user.email} -- foo"
  34 +
  35 +
  36 + #These must happen in the context of the satellite directory...
  37 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  38 + project.satellite.lock {
  39 + #Now clean it up, use send to get around prepare_satellite! being protected
  40 + satellite_action.send(:prepare_satellite!, repo)
  41 + }
  42 +
  43 + #verify it's clean
  44 + heads = repo.heads.map(&:name)
  45 + heads.size.should == 1
  46 + heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
  47 + remotes = repo.git.remote().split(' ')
  48 + remotes.size.should == 1
  49 + remotes.include?('origin').should == true
  50 + repo.config['user.name'].should ==user.name
  51 + repo.config['user.email'].should ==user.email
  52 + end
  53 +
  54 +
  55 + end
  56 +
  57 +
  58 + describe '#in_locked_and_timed_satellite' do
  59 +
  60 + it 'should make use of a lockfile' do
  61 + repo = project.satellite.repo
  62 + called = false
  63 +
  64 + #set assumptions
  65 + File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file
  66 +
  67 + File.exists?(project.satellite.lock_file).should be_false
  68 +
  69 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  70 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  71 + repo.should == sat_repo
  72 + (File.exists? project.satellite.lock_file).should be_true
  73 + called = true
  74 + end
  75 +
  76 + called.should be_true
  77 +
  78 + end
  79 +
  80 + it 'should be able to use the satellite after locking' do
  81 + pending "can't test this, doesn't seem to be a way to the the flock status on a file, throwing piles of processes at it seems lousy too"
  82 + repo = project.satellite.repo
  83 + first_call = false
  84 +
  85 + (File.exists? project.satellite.lock_file).should be_false
  86 +
  87 + test_file = ->(called) {
  88 + File.exists?(project.satellite.lock_file).should be_true
  89 + called.should be_true
  90 + File.readlines.should == "some test code"
  91 + File.truncate(project.satellite.lock, 0)
  92 + File.readlines.should == ""
  93 + }
  94 +
  95 + write_file = ->(called, checker) {
  96 + if (File.exists?(project.satellite.lock_file))
  97 + file = File.open(project.satellite.lock, '+w')
  98 + file.write("some test code")
  99 + file.close
  100 + checker.call(called)
  101 + end
  102 + }
  103 +
  104 +
  105 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  106 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  107 + write_file.call(first_call, test_file)
  108 + first_call = true
  109 + repo.should == sat_repo
  110 + (File.exists? project.satellite.lock_file).should be_true
  111 +
  112 + end
  113 +
  114 + first_call.should be_true
  115 + puts File.stat(project.satellite.lock_file).inspect
  116 +
  117 + second_call = false
  118 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  119 + write_file.call(second_call, test_file)
  120 + second_call = true
  121 + repo.should == sat_repo
  122 + (File.exists? project.satellite.lock_file).should be_true
  123 + end
  124 +
  125 + second_call.should be_true
  126 + (File.exists? project.satellite.lock_file).should be_true
  127 + end
  128 +
  129 + end
  130 +end
  131 +
spec/lib/gitlab/satellite/merge_action_spec.rb 0 → 100644
@@ -0,0 +1,136 @@ @@ -0,0 +1,136 @@
  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 +
  15 + end
  16 +
  17 + let(:project) { create(:project_with_code) }
  18 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
  19 + let(:merge_request_fork) { create(:merge_request) }
  20 + describe '#commits_between' do
  21 + context 'on fork' do
  22 + it 'should get proper commits between' do
  23 + merge_request_fork.target_branch = @one_after_stable[0]
  24 + merge_request_fork.source_branch = @master[0]
  25 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  26 + commits.first.id.should == @one_after_stable[1]
  27 + commits.last.id.should == @master[1]
  28 +
  29 + merge_request_fork.target_branch = @wiki_branch[0]
  30 + merge_request_fork.source_branch = @master[0]
  31 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  32 + commits.first.id.should == @wiki_branch[1]
  33 + commits.last.id.should == @master[1]
  34 + end
  35 + end
  36 +
  37 + context 'between branches' do
  38 + it 'should get proper commits between' do
  39 + merge_request.target_branch = @one_after_stable[0]
  40 + merge_request.source_branch = @master[0]
  41 + commits = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between
  42 + commits.first.id.should == @one_after_stable[1]
  43 + commits.last.id.should == @master[1]
  44 +
  45 + merge_request.target_branch = @wiki_branch[0]
  46 + merge_request.source_branch = @master[0]
  47 + commits = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between
  48 + commits.first.id.should == @wiki_branch[1]
  49 + commits.last.id.should == @master[1]
  50 + end
  51 + end
  52 + end
  53 +
  54 +
  55 + describe '#format_patch' do
  56 + context 'on fork' do
  57 + it 'should build a format patch' do
  58 + merge_request_fork.target_branch = @close_commit1[0]
  59 + merge_request_fork.source_branch = @close_commit2[0]
  60 + patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
  61 + (patch.include? "From #{@close_commit2[1]}").should be_true
  62 + (patch.include? "From #{@close_commit1[1]}").should be_true
  63 + end
  64 + end
  65 +
  66 + context 'between branches' do
  67 + it 'should build a format patch' do
  68 + merge_request.target_branch = @close_commit1[0]
  69 + merge_request.source_branch = @close_commit2[0]
  70 + patch = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).format_patch
  71 + (patch.include? "From #{@close_commit2[1]}").should be_true
  72 + (patch.include? "From #{@close_commit1[1]}").should be_true
  73 + end
  74 + end
  75 + end
  76 +
  77 +
  78 + describe '#diffs_between_satellite tested against diff_in_satellite' do
  79 + context 'on fork' do
  80 + it 'should get proper diffs' do
  81 + merge_request_fork.target_branch = @close_commit1[0]
  82 + merge_request_fork.source_branch = @master[0]
  83 + diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
  84 +
  85 + merge_request_fork.target_branch = @close_commit1[0]
  86 + merge_request_fork.source_branch = @master[0]
  87 + diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diffs_between_satellite
  88 +
  89 + diffs.each {|a_diff| (diff.include? a_diff.diff).should be_true}
  90 + end
  91 + end
  92 +
  93 + context 'between branches' do
  94 + it 'should get proper diffs' do
  95 + merge_request.target_branch = @close_commit1[0]
  96 + merge_request.source_branch = @wiki_branch[0]
  97 + diffs = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite
  98 +
  99 +
  100 + merge_request.target_branch = @close_commit1[0]
  101 + merge_request.source_branch = @master[0]
  102 + diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite
  103 +
  104 + diffs.each {|a_diff| (diff.include? a_diff.diff).should be_true}
  105 + end
  106 + end
  107 + end
  108 +
  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 +
  136 +end
0 \ No newline at end of file 137 \ 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,39 @@ describe MergeRequest do @@ -71,4 +68,39 @@ 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 +
  87 + describe '#allow_source_branch_removal?' do
  88 + it 'should not allow removal when mr is a fork' do
  89 +
  90 + subject.disallow_source_branch_removal?.should be_true
  91 + end
  92 + it 'should not allow removal when the mr is not a fork, but the source branch is the root reference' do
  93 + subject.target_project = subject.source_project
  94 + subject.source_branch = subject.source_project.repository.root_ref
  95 + subject.disallow_source_branch_removal?.should be_true
  96 + end
  97 +
  98 + it 'should not disallow removal when the mr is not a fork, and but source branch is not the root reference' do
  99 + subject.target_project = subject.source_project
  100 + subject.source_branch = "Something Different #{subject.source_project.repository.root_ref}"
  101 + subject.for_fork?.should be_false
  102 + subject.disallow_source_branch_removal?.should be_false
  103 + end
  104 + end
  105 +
74 end 106 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,42 @@ describe MergeRequestObserver do @@ -75,16 +77,42 @@ 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 +
  94 + describe "Merge Request created" do
  95 + def self.it_should_be_valid_event
  96 + it { @event.should_not be_nil }
  97 + it { @event.should_not be_nil }
  98 + it { @event.project.should == project }
  99 + it { @event.project.should == project }
  100 + end
  101 +
  102 + let(:project) { create(:project) }
  103 + before do
  104 + TestEnv.enable_observers
  105 + @merge_request = create(:merge_request, source_project: project, target_project: project)
  106 + @event = Event.last
  107 + end
  108 +
  109 + after do
  110 + TestEnv.enable_observers
  111 + end
  112 +
  113 + it_should_be_valid_event
  114 + it { @event.action.should == Event::CREATED }
  115 + it { @event.target.should == @merge_request }
  116 + end
  117 +
90 end 118 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,91 @@ describe API::API do @@ -40,35 +42,91 @@ 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 +
  83 + before :each do |each|
  84 + fork_project.team << [user2, :reporters]
  85 + forked_project_link.forked_from_project = project
  86 + forked_project_link.forked_to_project = fork_project
  87 + forked_project_link.save!
  88 + end
  89 +
  90 + it "should return merge_request" do
  91 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  92 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id
  93 + response.status.should == 201
  94 + json_response['title'].should == 'Test merge_request'
  95 + end
  96 +
  97 + it "should not return 422 when source_branch equals target_branch" do
  98 + project.id.should_not == fork_project.id
  99 + fork_project.forked?.should be_true
  100 + fork_project.forked_from_project.should == project
  101 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  102 + title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
  103 + response.status.should == 201
  104 + json_response['title'].should == 'Test merge_request'
  105 + end
  106 +
  107 + it "should return 400 when source_branch is missing" do
  108 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  109 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  110 + response.status.should == 400
  111 + end
  112 +
  113 + it "should return 400 when target_branch is missing" do
  114 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  115 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  116 + response.status.should == 400
  117 + end
  118 +
  119 + it "should return 400 when title is missing" do
  120 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  121 + target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
  122 + response.status.should == 400
  123 + end
  124 +
  125 + it "should return 400 when target_branch is specified and not a forked project" do
  126 + post api("/projects/#{project.id}/merge_requests", user),
  127 + target_branch: 'master', source_branch: 'stable', author: user, target_project: fork_project.id
  128 + response.status.should == 400
  129 + end
72 end 130 end
73 end 131 end
74 132
@@ -97,14 +155,14 @@ describe API::API do @@ -97,14 +155,14 @@ describe API::API do
97 155
98 it "should return 422 when source_branch and target_branch are renamed the same" do 156 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), 157 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
100 - source_branch: "master", target_branch: "master" 158 + source_branch: "master", target_branch: "master"
101 response.status.should == 422 159 response.status.should == 422
102 end 160 end
103 161
104 it "should return merge_request with renamed target_branch" do 162 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" 163 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
106 response.status.should == 200 164 response.status.should == 200
107 - json_response['target_branch'].should == 'test' 165 + json_response['target_branch'].should == 'wiki'
108 end 166 end
109 end 167 end
110 168
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) }
@@ -14,7 +15,8 @@ describe API::API do @@ -14,7 +15,8 @@ describe API::API do
14 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } 15 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } 16 let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
16 17
17 - before { project.team << [user, :reporter] } 18 + before {
  19 + project.team << [user, :reporter] }
18 20
19 describe "GET /projects" do 21 describe "GET /projects" do
20 context "when unauthenticated" do 22 context "when unauthenticated" do
@@ -46,16 +48,16 @@ describe API::API do @@ -46,16 +48,16 @@ describe API::API do
46 it "should not create new project" do 48 it "should not create new project" do
47 expect { 49 expect {
48 post api("/projects", user2), name: 'foo' 50 post api("/projects", user2), name: 'foo'
49 - }.to change {Project.count}.by(0) 51 + }.to change { Project.count }.by(0)
50 end 52 end
51 end 53 end
52 54
53 it "should create new project without path" do 55 it "should create new project without path" do
54 - expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) 56 + expect { post api("/projects", user), name: 'foo' }.to change { Project.count }.by(1)
55 end 57 end
56 58
57 it "should not create new project without name" do 59 it "should not create new project without name" do
58 - expect { post api("/projects", user) }.to_not change {Project.count} 60 + expect { post api("/projects", user) }.to_not change { Project.count }
59 end 61 end
60 62
61 it "should return a 400 error if name not given" do 63 it "should return a 400 error if name not given" do
@@ -89,17 +91,17 @@ describe API::API do @@ -89,17 +91,17 @@ describe API::API do
89 91
90 it "should assign attributes to project" do 92 it "should assign attributes to project" do
91 project = attributes_for(:project, { 93 project = attributes_for(:project, {
92 - description: Faker::Lorem.sentence,  
93 - default_branch: 'stable',  
94 - issues_enabled: false,  
95 - wall_enabled: false,  
96 - merge_requests_enabled: false,  
97 - wiki_enabled: false 94 + description: Faker::Lorem.sentence,
  95 + default_branch: 'stable',
  96 + issues_enabled: false,
  97 + wall_enabled: false,
  98 + merge_requests_enabled: false,
  99 + wiki_enabled: false
98 }) 100 })
99 101
100 post api("/projects", user), project 102 post api("/projects", user), project
101 103
102 - project.each_pair do |k,v| 104 + project.each_pair do |k, v|
103 next if k == :path 105 next if k == :path
104 json_response[k.to_s].should == v 106 json_response[k.to_s].should == v
105 end 107 end
@@ -125,11 +127,11 @@ describe API::API do @@ -125,11 +127,11 @@ describe API::API do
125 before { admin } 127 before { admin }
126 128
127 it "should create new project without path" do 129 it "should create new project without path" do
128 - expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) 130 + expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change { Project.count }.by(1)
129 end 131 end
130 132
131 it "should not create new project without name" do 133 it "should not create new project without name" do
132 - expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count} 134 + expect { post api("/projects/user/#{user.id}", admin) }.to_not change { Project.count }
133 end 135 end
134 136
135 it "should respond with 201 on success" do 137 it "should respond with 201 on success" do
@@ -144,17 +146,17 @@ describe API::API do @@ -144,17 +146,17 @@ describe API::API do
144 146
145 it "should assign attributes to project" do 147 it "should assign attributes to project" do
146 project = attributes_for(:project, { 148 project = attributes_for(:project, {
147 - description: Faker::Lorem.sentence,  
148 - default_branch: 'stable',  
149 - issues_enabled: false,  
150 - wall_enabled: false,  
151 - merge_requests_enabled: false,  
152 - wiki_enabled: false 149 + description: Faker::Lorem.sentence,
  150 + default_branch: 'stable',
  151 + issues_enabled: false,
  152 + wall_enabled: false,
  153 + merge_requests_enabled: false,
  154 + wiki_enabled: false
153 }) 155 })
154 156
155 post api("/projects/user/#{user.id}", admin), project 157 post api("/projects/user/#{user.id}", admin), project
156 158
157 - project.each_pair do |k,v| 159 + project.each_pair do |k, v|
158 next if k == :path 160 next if k == :path
159 json_response[k.to_s].should == v 161 json_response[k.to_s].should == v
160 end 162 end
@@ -267,7 +269,7 @@ describe API::API do @@ -267,7 +269,7 @@ describe API::API do
267 it "should add user to project team" do 269 it "should add user to project team" do
268 expect { 270 expect {
269 post api("/projects/#{project.id}/members", user), user_id: user2.id, 271 post api("/projects/#{project.id}/members", user), user_id: user2.id,
270 - access_level: UsersProject::DEVELOPER 272 + access_level: UsersProject::DEVELOPER
271 }.to change { UsersProject.count }.by(1) 273 }.to change { UsersProject.count }.by(1)
272 274
273 response.status.should == 201 275 response.status.should == 201
@@ -277,10 +279,10 @@ describe API::API do @@ -277,10 +279,10 @@ describe API::API do
277 279
278 it "should return a 201 status if user is already project member" do 280 it "should return a 201 status if user is already project member" do
279 post api("/projects/#{project.id}/members", user), user_id: user2.id, 281 post api("/projects/#{project.id}/members", user), user_id: user2.id,
280 - access_level: UsersProject::DEVELOPER 282 + access_level: UsersProject::DEVELOPER
281 expect { 283 expect {
282 post api("/projects/#{project.id}/members", user), user_id: user2.id, 284 post api("/projects/#{project.id}/members", user), user_id: user2.id,
283 - access_level: UsersProject::DEVELOPER 285 + access_level: UsersProject::DEVELOPER
284 }.not_to change { UsersProject.count }.by(1) 286 }.not_to change { UsersProject.count }.by(1)
285 287
286 response.status.should == 201 288 response.status.should == 201
@@ -411,8 +413,8 @@ describe API::API do @@ -411,8 +413,8 @@ describe API::API do
411 it "should add hook to project" do 413 it "should add hook to project" do
412 expect { 414 expect {
413 post api("/projects/#{project.id}/hooks", user), 415 post api("/projects/#{project.id}/hooks", user),
414 - url: "http://example.com"  
415 - }.to change {project.hooks.count}.by(1) 416 + url: "http://example.com"
  417 + }.to change { project.hooks.count }.by(1)
416 response.status.should == 201 418 response.status.should == 201
417 end 419 end
418 420
@@ -430,7 +432,7 @@ describe API::API do @@ -430,7 +432,7 @@ describe API::API do
430 describe "PUT /projects/:id/hooks/:hook_id" do 432 describe "PUT /projects/:id/hooks/:hook_id" do
431 it "should update an existing project hook" do 433 it "should update an existing project hook" do
432 put api("/projects/#{project.id}/hooks/#{hook.id}", user), 434 put api("/projects/#{project.id}/hooks/#{hook.id}", user),
433 - url: 'http://example.org' 435 + url: 'http://example.org'
434 response.status.should == 200 436 response.status.should == 200
435 json_response['url'].should == 'http://example.org' 437 json_response['url'].should == 'http://example.org'
436 end 438 end
@@ -455,7 +457,7 @@ describe API::API do @@ -455,7 +457,7 @@ describe API::API do
455 it "should delete hook from project" do 457 it "should delete hook from project" do
456 expect { 458 expect {
457 delete api("/projects/#{project.id}/hooks/#{hook.id}", user) 459 delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
458 - }.to change {project.hooks.count}.by(-1) 460 + }.to change { project.hooks.count }.by(-1)
459 response.status.should == 200 461 response.status.should == 200
460 end 462 end
461 463
@@ -501,26 +503,26 @@ describe API::API do @@ -501,26 +503,26 @@ describe API::API do
501 describe "POST /projects/:id/snippets" do 503 describe "POST /projects/:id/snippets" do
502 it "should create a new project snippet" do 504 it "should create a new project snippet" do
503 post api("/projects/#{project.id}/snippets", user), 505 post api("/projects/#{project.id}/snippets", user),
504 - title: 'api test', file_name: 'sample.rb', code: 'test' 506 + title: 'api test', file_name: 'sample.rb', code: 'test'
505 response.status.should == 201 507 response.status.should == 201
506 json_response['title'].should == 'api test' 508 json_response['title'].should == 'api test'
507 end 509 end
508 510
509 it "should return a 400 error if title is not given" do 511 it "should return a 400 error if title is not given" do
510 post api("/projects/#{project.id}/snippets", user), 512 post api("/projects/#{project.id}/snippets", user),
511 - file_name: 'sample.rb', code: 'test' 513 + file_name: 'sample.rb', code: 'test'
512 response.status.should == 400 514 response.status.should == 400
513 end 515 end
514 516
515 it "should return a 400 error if file_name not given" do 517 it "should return a 400 error if file_name not given" do
516 post api("/projects/#{project.id}/snippets", user), 518 post api("/projects/#{project.id}/snippets", user),
517 - title: 'api test', code: 'test' 519 + title: 'api test', code: 'test'
518 response.status.should == 400 520 response.status.should == 400
519 end 521 end
520 522
521 it "should return a 400 error if code not given" do 523 it "should return a 400 error if code not given" do
522 post api("/projects/#{project.id}/snippets", user), 524 post api("/projects/#{project.id}/snippets", user),
523 - title: 'api test', file_name: 'sample.rb' 525 + title: 'api test', file_name: 'sample.rb'
524 response.status.should == 400 526 response.status.should == 400
525 end 527 end
526 end 528 end
@@ -528,7 +530,7 @@ describe API::API do @@ -528,7 +530,7 @@ describe API::API do
528 describe "PUT /projects/:id/snippets/:shippet_id" do 530 describe "PUT /projects/:id/snippets/:shippet_id" do
529 it "should update an existing project snippet" do 531 it "should update an existing project snippet" do
530 put api("/projects/#{project.id}/snippets/#{snippet.id}", user), 532 put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
531 - code: 'updated code' 533 + code: 'updated code'
532 response.status.should == 200 534 response.status.should == 200
533 json_response['title'].should == 'example' 535 json_response['title'].should == 'example'
534 snippet.reload.content.should == 'updated code' 536 snippet.reload.content.should == 'updated code'
@@ -536,7 +538,7 @@ describe API::API do @@ -536,7 +538,7 @@ describe API::API do
536 538
537 it "should update an existing project snippet with new title" do 539 it "should update an existing project snippet with new title" do
538 put api("/projects/#{project.id}/snippets/#{snippet.id}", user), 540 put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
539 - title: 'other api test' 541 + title: 'other api test'
540 response.status.should == 200 542 response.status.should == 200
541 json_response['title'].should == 'other api test' 543 json_response['title'].should == 'other api test'
542 end 544 end
@@ -598,7 +600,7 @@ describe API::API do @@ -598,7 +600,7 @@ describe API::API do
598 600
599 describe "POST /projects/:id/keys" do 601 describe "POST /projects/:id/keys" do
600 it "should not create an invalid ssh key" do 602 it "should not create an invalid ssh key" do
601 - post api("/projects/#{project.id}/keys", user), { title: "invalid key" } 603 + post api("/projects/#{project.id}/keys", user), {title: "invalid key"}
602 response.status.should == 404 604 response.status.should == 404
603 end 605 end
604 606
@@ -606,7 +608,7 @@ describe API::API do @@ -606,7 +608,7 @@ describe API::API do
606 key_attrs = attributes_for :key 608 key_attrs = attributes_for :key
607 expect { 609 expect {
608 post api("/projects/#{project.id}/keys", user), key_attrs 610 post api("/projects/#{project.id}/keys", user), key_attrs
609 - }.to change{ project.deploy_keys.count }.by(1) 611 + }.to change { project.deploy_keys.count }.by(1)
610 end 612 end
611 end 613 end
612 614
@@ -616,7 +618,7 @@ describe API::API do @@ -616,7 +618,7 @@ describe API::API do
616 it "should delete existing key" do 618 it "should delete existing key" do
617 expect { 619 expect {
618 delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user) 620 delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
619 - }.to change{ project.deploy_keys.count }.by(-1) 621 + }.to change { project.deploy_keys.count }.by(-1)
620 end 622 end
621 623
622 it "should return 404 Not Found with invalid ID" do 624 it "should return 404 Not Found with invalid ID" do
spec/services/notification_service_spec.rb
@@ -156,7 +156,8 @@ describe NotificationService do @@ -156,7 +156,8 @@ describe NotificationService do
156 let(:merge_request) { create :merge_request, assignee: create(:user) } 156 let(:merge_request) { create :merge_request, assignee: create(:user) }
157 157
158 before do 158 before do
159 - build_team(merge_request.project) 159 + build_team(merge_request.source_project)
  160 + build_team(merge_request.target_project)
160 end 161 end
161 162
162 describe :new_merge_request do 163 describe :new_merge_request do
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
spec/spec_helper.rb
@@ -48,8 +48,11 @@ Spork.prefork do @@ -48,8 +48,11 @@ Spork.prefork do
48 # instead of true. 48 # instead of true.
49 config.use_transactional_fixtures = false 49 config.use_transactional_fixtures = false
50 50
51 - config.before do  
52 - TestEnv.init(observers: false) 51 + config.before(:suite) do
  52 + TestEnv.init(observers: false, init_repos: true, repos: false)
  53 + end
  54 + config.before(:each) do
  55 + TestEnv.setup_stubs
53 end 56 end
54 end 57 end
55 end 58 end
spec/support/test_env.rb
@@ -27,17 +27,65 @@ module TestEnv @@ -27,17 +27,65 @@ module TestEnv
27 27
28 # Disable mailer for spinach tests 28 # Disable mailer for spinach tests
29 disable_mailer if opts[:mailer] == false 29 disable_mailer if opts[:mailer] == false
  30 + setup_stubs
30 31
31 32
32 - # Use tmp dir for FS manipulations  
33 - repos_path = Rails.root.join('tmp', 'test-git-base-path')  
34 - Gitlab.config.gitlab_shell.stub(repos_path: repos_path)  
35 - Gitlab::Git::Repository.stub(repos_path: repos_path) 33 + clear_test_repo_dir if opts[:init_repos] == true
  34 + setup_test_repos(opts) if opts[:repos] == true
  35 + end
  36 +
  37 + def testing_path
  38 + Rails.root.join('tmp', 'test-git-base-path')
  39 + end
  40 +
  41 + def seed_repo_path
  42 + Rails.root.join('tmp', 'repositories', 'gitlabhq')
  43 + end
  44 +
  45 + def seed_satellite_path
  46 + Rails.root.join('tmp', 'satellite', 'gitlabhq')
  47 + end
  48 +
  49 + def satellite_path
  50 + "#{testing_path()}/satellite"
  51 + end
  52 +
  53 + def repo(namespace, name)
  54 + unless (namespace.nil? || namespace.path.nil? || namespace.path.strip.empty?)
  55 + repo = File.join(testing_path(), "#{namespace.path}/#{name}.git")
  56 + else
  57 + repo = File.join(testing_path(), "#{name}.git")
  58 + end
  59 + end
  60 +
  61 + def satellite(namespace, name)
  62 + unless (namespace.nil? || namespace.path.nil? || namespace.path.strip.empty?)
  63 + satellite_repo = File.join(satellite_path, namespace.path, name)
  64 + else
  65 + satellite_repo = File.join(satellite_path, name)
  66 + end
  67 + end
  68 +
36 69
  70 + def setup_test_repos(opts ={})
  71 + create_repo(nil, 'gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('')
  72 + create_repo(nil, 'source_gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('source_')
  73 + create_repo(nil, 'target_gitlabhq') #unless opts[:repo].nil? || !opts[:repo].include?('target_')
  74 + end
  75 +
  76 + def setup_stubs()
  77 + # Use tmp dir for FS manipulations
  78 + repos_path = testing_path()
37 GollumWiki.any_instance.stub(:init_repo) do |path| 79 GollumWiki.any_instance.stub(:init_repo) do |path|
38 create_temp_repo(File.join(repos_path, "#{path}.git")) 80 create_temp_repo(File.join(repos_path, "#{path}.git"))
39 end 81 end
40 82
  83 + Gitlab.config.gitlab_shell.stub(repos_path: repos_path)
  84 +
  85 + Gitlab.config.satellites.stub(path: satellite_path)
  86 +
  87 + Gitlab::Git::Repository.stub(repos_path: repos_path)
  88 +
41 Gitlab::Shell.any_instance.stub( 89 Gitlab::Shell.any_instance.stub(
42 add_repository: true, 90 add_repository: true,
43 mv_repository: true, 91 mv_repository: true,
@@ -48,24 +96,54 @@ module TestEnv @@ -48,24 +96,54 @@ module TestEnv
48 ) 96 )
49 97
50 Gitlab::Satellite::Satellite.any_instance.stub( 98 Gitlab::Satellite::Satellite.any_instance.stub(
51 - exists?: true,  
52 - destroy: true,  
53 - create: true 99 + exists?: true,
  100 + destroy: true,
  101 + create: true,
  102 + lock_files_dir: repos_path
54 ) 103 )
55 104
56 MergeRequest.any_instance.stub( 105 MergeRequest.any_instance.stub(
57 - check_if_can_be_merged: true 106 + check_if_can_be_merged: true
58 ) 107 )
59 -  
60 Repository.any_instance.stub( 108 Repository.any_instance.stub(
61 - size: 12.45 109 + size: 12.45
62 ) 110 )
  111 + end
63 112
  113 + def clear_test_repo_dir
  114 + setup_stubs
  115 + # Use tmp dir for FS manipulations
  116 + repos_path = testing_path()
64 # Remove tmp/test-git-base-path 117 # Remove tmp/test-git-base-path
65 FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path 118 FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
66 119
67 # Recreate tmp/test-git-base-path 120 # Recreate tmp/test-git-base-path
68 FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path 121 FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path
  122 + #Since much more is happening in satellites
  123 + FileUtils.mkdir_p Gitlab.config.satellites.path
  124 + end
  125 +
  126 + def clear_repo_dir(namespace, name)
  127 + setup_stubs
  128 + #Clean any .wiki.git that may have been created
  129 + FileUtils.rm_rf File.join(testing_path(), "#{name}.wiki.git")
  130 + end
  131 +
  132 + #Create a repo and it's satellite
  133 + def create_repo(namespace, name)
  134 + setup_stubs
  135 + repo = repo(namespace, name)
  136 +
  137 + # Symlink tmp/repositories/gitlabhq to tmp/test-git-base-path/gitlabhq
  138 + system("ln -s -f #{seed_repo_path()} #{repo}")
  139 + create_satellite(repo, namespace, name)
  140 + end
  141 +
  142 + # Create a testing satellite, and clone the source repo into it
  143 + def create_satellite(source_repo, namespace, satellite_name)
  144 + satellite_repo = satellite(namespace, satellite_name)
  145 + # Symlink tmp/satellite/gitlabhq to tmp/test-git-base-path/satellite/gitlabhq
  146 + system("ln -s -f #{seed_satellite_path()} #{satellite_repo}")
69 end 147 end
70 148
71 def create_temp_repo(path) 149 def create_temp_repo(path)