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 415 @extend .light-well;
416 416 @extend .light;
417 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 431 .group-name {
... ...
app/contexts/filter_context.rb
... ... @@ -12,7 +12,7 @@ class FilterContext
12 12  
13 13 def apply_filter items
14 14 if params[:project_id]
15   - items = items.where(project_id: params[:project_id])
  15 + items = items.by_project(params[:project_id])
16 16 end
17 17  
18 18 if params[:search].present?
... ... @@ -20,12 +20,12 @@ class FilterContext
20 20 end
21 21  
22 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 29 end
30 30 end
31 31 end
... ...
app/contexts/merge_requests_load_context.rb
... ... @@ -14,7 +14,7 @@ class MergeRequestsLoadContext < BaseContext
14 14 end
15 15  
16 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 19 # Filter by specific assignee_id (or lack thereof)?
20 20 if params[:assignee_id].present?
... ...
app/contexts/search_context.rb
... ... @@ -19,7 +19,7 @@ class SearchContext
19 19 if params[:search_code].present?
20 20 result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
21 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 23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(10)
24 24 result[:wiki_pages] = []
25 25 end
... ...
app/controllers/projects/merge_requests_controller.rb
... ... @@ -24,8 +24,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
24 24 format.html
25 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 29 end
30 30 end
31 31  
... ... @@ -33,25 +33,39 @@ class Projects::MergeRequestsController < Projects::ApplicationController
33 33 @commit = @merge_request.last_commit
34 34  
35 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 38 @line_notes = @merge_request.notes.where("line_code is not null")
39 39 end
40 40  
41 41 def new
42 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 54 end
44 55  
45 56 def edit
  57 + @target_branches = @merge_request.target_project.repository.branch_names
46 58 end
47 59  
48 60 def create
49 61 @merge_request = @project.merge_requests.new(params[:merge_request])
50 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 66 if @merge_request.save
53 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 69 else
56 70 render "new"
57 71 end
... ... @@ -89,22 +103,36 @@ class Projects::MergeRequestsController < Projects::ApplicationController
89 103 end
90 104  
91 105 def branch_from
  106 + #This is always source
92 107 @commit = @repository.commit(params[:ref])
93 108 end
94 109  
95 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 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 122 def ci_status
100 123 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
101   - response = { status: status }
  124 + response = {status: status}
102 125  
103 126 render json: response
104 127 end
105 128  
106 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 136 def merge_request
109 137 @merge_request ||= @project.merge_requests.find(params[:id])
110 138 end
... ... @@ -123,11 +151,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
123 151  
124 152 def validates_merge_request
125 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 156 # Show git not found page if source branch doesn't exist
129 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 159 end
132 160  
133 161 def define_show_vars
... ...
app/helpers/commits_helper.rb
... ... @@ -108,8 +108,8 @@ module CommitsHelper
108 108 end
109 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 113 end
114 114  
115 115 def diff_line_content(line)
... ...
app/helpers/merge_requests_helper.rb
1 1 module MergeRequestsHelper
2 2 def new_mr_path_from_push_event(event)
3 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 21 source_branch: event.branch_name,
7   - target_branch: event.project.repository.root_ref,
  22 + target_branch: target_project.repository.root_ref,
8 23 title: event.branch_name.titleize
9   - }
10   - )
  24 + }
11 25 end
12 26  
13 27 def mr_css_classes mr
... ... @@ -18,6 +32,6 @@ module MergeRequestsHelper
18 32 end
19 33  
20 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 36 end
23 37 end
... ...
app/mailers/emails/merge_requests.rb
... ... @@ -2,28 +2,65 @@ module Emails
2 2 module MergeRequests
3 3 def new_merge_request_email(recipient_id, merge_request_id)
4 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 6 end
8 7  
9 8 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
10 9 @merge_request = MergeRequest.find(merge_request_id)
11 10 @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
12   - @project = @merge_request.project
13 11 mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
14 12 end
15 13  
16 14 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
17 15 @merge_request = MergeRequest.find(merge_request_id)
18   - @project = @merge_request.project
19 16 @updated_by = User.find updated_by_user_id
20 17 mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title))
21 18 end
22 19  
23 20 def merged_merge_request_email(recipient_id, merge_request_id)
24 21 @merge_request = MergeRequest.find(merge_request_id)
25   - @project = @merge_request.project
26 22 mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title))
27 23 end
28 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 66 end
... ...
app/models/concerns/issuable.rb
... ... @@ -9,19 +9,14 @@ module Issuable
9 9 include Mentionable
10 10  
11 11 included do
12   - belongs_to :project
13 12 belongs_to :author, class_name: "User"
14 13 belongs_to :assignee, class_name: "User"
15 14 belongs_to :milestone
16 15 has_many :notes, as: :noteable, dependent: :destroy
17 16  
18   - validates :project, presence: true
19 17 validates :author, presence: true
20 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 20 scope :assigned_to, ->(u) { where(assignee_id: u.id)}
26 21 scope :recent, -> { order("created_at DESC") }
27 22 scope :assigned, -> { where("assignee_id IS NOT NULL") }
... ...
app/models/issue.rb
... ... @@ -17,8 +17,18 @@
17 17 #
18 18  
19 19 class Issue < ActiveRecord::Base
  20 +
20 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 32 attr_accessible :title, :assignee_id, :position, :description,
23 33 :milestone_id, :label_list, :author_id_of_changes,
24 34 :state_event
... ...
app/models/merge_request.rb
... ... @@ -2,30 +2,37 @@
2 2 #
3 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 22 require Rails.root.join("app/models/commit")
22 23 require Rails.root.join("lib/static_model")
23 24  
24 25 class MergeRequest < ActiveRecord::Base
  26 +
25 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 37 attr_accessor :should_remove_source_branch
31 38  
... ... @@ -74,22 +81,29 @@ class MergeRequest &lt; ActiveRecord::Base
74 81 serialize :st_commits
75 82 serialize :st_diffs
76 83  
  84 + validates :source_project, presence: true
77 85 validates :source_branch, presence: true
  86 + validates :target_project, presence: true
78 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 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 96 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
84 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 100 # Closed scope for merge request should return
87 101 # both merged and closed mr's
88 102 scope :closed, -> { with_states(:closed, :merged) }
89 103  
90 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 107 end
94 108  
95 109 if opened? || reopened?
... ... @@ -137,7 +151,14 @@ class MergeRequest &lt; ActiveRecord::Base
137 151 end
138 152  
139 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 162 end
142 163  
143 164 def last_commit
... ... @@ -145,11 +166,11 @@ class MergeRequest &lt; ActiveRecord::Base
145 166 end
146 167  
147 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 170 end
150 171  
151 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 174 end
154 175  
155 176 def commits
... ... @@ -158,24 +179,30 @@ class MergeRequest &lt; ActiveRecord::Base
158 179  
159 180 def probably_merged?
160 181 unmerged_commits.empty? &&
161   - commits.any? && opened?
  182 + commits.any? && opened?
162 183 end
163 184  
164 185 def reloaded_commits
165 186 if opened? && unmerged_commits.any?
166 187 self.st_commits = dump_commits(unmerged_commits)
167 188 save
  189 +
168 190 end
169 191 commits
170 192 end
171 193  
172 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 203 end
178 204  
  205 +
179 206 def merge!(user_id)
180 207 self.author_id_of_changes = user_id
181 208 self.merge
... ... @@ -195,25 +222,33 @@ class MergeRequest &lt; ActiveRecord::Base
195 222 commit_ids = commits.map(&:id)
196 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 224 end
198   -
199 225 # Returns the raw diff for this merge request
200 226 #
201 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 230 end
205 231  
  232 +
206 233 # Returns the commit as a series of email patches.
207 234 #
208 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 238 end
212 239  
213 240 def last_commit_short_sha
214 241 @last_commit_short_sha ||= last_commit.sha[0..10]
215 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 252 private
218 253  
219 254 def dump_commits(commits)
... ...
app/models/note.rb
... ... @@ -32,8 +32,8 @@ class Note &lt; ActiveRecord::Base
32 32 delegate :name, :email, to: :author, prefix: true
33 33  
34 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 38 validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
39 39 validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
... ... @@ -45,24 +45,24 @@ class Note &lt; ActiveRecord::Base
45 45 scope :inline, -> { where("line_code IS NOT NULL") }
46 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 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 60 end
61 61  
62 62 def commit_author
63 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 66 rescue
67 67 nil
68 68 end
... ... @@ -97,8 +97,8 @@ class Note &lt; ActiveRecord::Base
97 97 # otherwise false is returned
98 98 def downvote?
99 99 votable? && (note.start_with?('-1') ||
100   - note.start_with?(':-1:')
101   - )
  100 + note.start_with?(':-1:')
  101 + )
102 102 end
103 103  
104 104 def for_commit?
... ... @@ -136,8 +136,8 @@ class Note &lt; ActiveRecord::Base
136 136 else
137 137 super
138 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 141 rescue
142 142 nil
143 143 end
... ... @@ -146,8 +146,8 @@ class Note &lt; ActiveRecord::Base
146 146 # otherwise false is returned
147 147 def upvote?
148 148 votable? && (note.start_with?('+1') ||
149   - note.start_with?(':+1:')
150   - )
  149 + note.start_with?(':+1:')
  150 + )
151 151 end
152 152  
153 153 def votable?
... ...
app/models/project.rb
... ... @@ -53,7 +53,7 @@ class Project &lt; ActiveRecord::Base
53 53  
54 54 has_many :services, dependent: :destroy
55 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 57 has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
58 58 has_many :milestones, dependent: :destroy
59 59 has_many :notes, dependent: :destroy
... ...
app/observers/activity_observer.rb
1 1 class ActivityObserver < BaseObserver
2   - observe :issue, :merge_request, :note, :milestone
  2 + observe :issue, :note, :milestone
3 3  
4 4 def after_create(record)
5 5 event_author_id = record.author_id
... ... @@ -13,47 +13,27 @@ class ActivityObserver &lt; BaseObserver
13 13 end
14 14  
15 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 17 end
24 18 end
25 19  
26 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 22 end
35 23  
36 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 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 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 38 end
59 39 end
... ...
app/observers/issue_observer.rb
... ... @@ -23,6 +23,6 @@ class IssueObserver &lt; BaseObserver
23 23  
24 24 # Create issue note with service comment like 'Status changed to closed'
25 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 27 end
28 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 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 11 notification.new_merge_request(merge_request, current_user)
4 12 end
5 13  
6 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 18 notification.close_mr(merge_request, current_user)
10 19 end
11 20  
12 21 def after_merge(merge_request, transition)
13 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 34 end
15 35  
16 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 39 end
19 40  
20 41 def after_update(merge_request)
21 42 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
22 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 56 end
... ...
app/services/notification_service.rb
... ... @@ -23,7 +23,7 @@ class NotificationService
23 23 # * project team members with notification level higher then Participating
24 24 #
25 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 27 end
28 28  
29 29 # When we close an issue we should send next emails:
... ... @@ -33,7 +33,7 @@ class NotificationService
33 33 # * project team members with notification level higher then Participating
34 34 #
35 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 37 end
38 38  
39 39 # When we reassign an issue we should send next emails:
... ... @@ -42,7 +42,7 @@ class NotificationService
42 42 # * issue new assignee if his notification level is not Disabled
43 43 #
44 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 46 end
47 47  
48 48  
... ... @@ -51,7 +51,7 @@ class NotificationService
51 51 # * mr assignee if his notification level is not Disabled
52 52 #
53 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 55 end
56 56  
57 57 # When we reassign a merge_request we should send next emails:
... ... @@ -60,7 +60,7 @@ class NotificationService
60 60 # * merge_request assignee if his notification level is not Disabled
61 61 #
62 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 64 end
65 65  
66 66 # When we close a merge request we should send next emails:
... ... @@ -70,7 +70,7 @@ class NotificationService
70 70 # * project team members with notification level higher then Participating
71 71 #
72 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 74 end
75 75  
76 76 # When we merge a merge request we should send next emails:
... ... @@ -80,8 +80,10 @@ class NotificationService
80 80 # * project team members with notification level higher then Participating
81 81 #
82 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 88 recipients.each do |recipient|
87 89 mailer.merged_merge_request_email(recipient.id, merge_request.id)
... ... @@ -102,7 +104,7 @@ class NotificationService
102 104 # ignore wall messages
103 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 109 if note.commit_id.present?
108 110 opts.merge!(commit_id: note.commit_id)
... ... @@ -191,14 +193,14 @@ class NotificationService
191 193 end
192 194 end
193 195  
194   - def new_resource_email(target, method)
  196 + def new_resource_email(target, project, method)
195 197 if target.respond_to?(:participants)
196 198 recipients = target.participants
197 199 else
198 200 recipients = []
199 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 204 recipients.delete(target.author)
203 205  
204 206 recipients.each do |recipient|
... ... @@ -206,9 +208,9 @@ class NotificationService
206 208 end
207 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 214 recipients.delete(current_user)
213 215  
214 216 recipients.each do |recipient|
... ... @@ -216,14 +218,14 @@ class NotificationService
216 218 end
217 219 end
218 220  
219   - def reassign_resource_email(target, current_user, method)
  221 + def reassign_resource_email(target, project, current_user, method)
220 222 recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
221 223  
222 224 # Add watchers to email list
223   - recipients = recipients.concat(project_watchers(target.project))
  225 + recipients = recipients.concat(project_watchers(project))
224 226  
225 227 # reject users with disabled notifications
226   - recipients = reject_muted_users(recipients, target.project)
  228 + recipients = reject_muted_users(recipients, project)
227 229  
228 230 # Reject me from recipients if I reassign an item
229 231 recipients.delete(current_user)
... ...
app/views/events/_event_last_push.html.haml
... ... @@ -9,6 +9,9 @@
9 9 = time_ago_in_words(event.created_at)
10 10 ago.
11 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 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 17 %hr
... ...
app/views/merge_requests/update_branches.js.haml 0 → 100644
... ... @@ -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 3 %p
4 4 = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request)
5 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 7 %p
8 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9  
... ...
app/views/notify/closed_merge_request_email.text.haml
1 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 7 Author: #{@merge_request.author_name}
8 8 Assignee: #{@merge_request.assignee_name}
... ...
app/views/notify/merged_merge_request_email.html.haml
1 1 %p
2 2 = "Merge Request #{@merge_request.id} was merged"
3 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 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 7 %p
8 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9  
... ...
app/views/notify/merged_merge_request_email.text.haml
1 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 7 Author: #{@merge_request.author_name}
8 8 Assignee: #{@merge_request.assignee_name}
... ...
app/views/notify/new_merge_request_email.html.haml
1 1 %p
2 2 = "New Merge Request !#{@merge_request.id}"
3 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 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 7 %p
8 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9  
... ...
app/views/notify/new_merge_request_email.text.erb
1 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 6 Author: <%= @merge_request.author_name %>
8 7 Asignee: <%= @merge_request.assignee_name %>
9 8  
... ...
app/views/notify/note_merge_request_email.html.haml
1 1 %p
2 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 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 6 for Merge Request ##{@merge_request.id}
7 7 %cite "#{truncate(@merge_request.title, length: 20)}"
8 8 = render 'note_message'
... ...
app/views/notify/note_merge_request_email.text.erb
1 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 6 <%= @note.author_name %>
... ...
app/views/notify/reassigned_merge_request_email.html.haml
1 1 %p
2 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 4 %p
5 5 Assignee changed
6 6 - if @previous_assignee
... ...
app/views/notify/reassigned_merge_request_email.text.erb
1 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 6 Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %>
... ...
app/views/projects/commits/_commit.html.haml
1 1 %li.commit
2 2 .browse_code_link_holder
3 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 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 7 = commit_author_link(commit, avatar: true, size: 24)
8 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 11 %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
12 12 = time_ago_in_words(commit.committed_date)
... ... @@ -14,7 +14,7 @@
14 14 &nbsp;
15 15  
16 16 %span.notes_count
17   - - notes = @project.notes.for_commit_id(commit.id)
  17 + - notes = project.notes.for_commit_id(commit.id)
18 18 - if notes.any?
19 19 %span.badge.badge-info
20 20 %i.icon-comment
... ...
app/views/projects/commits/_commits.html.haml
... ... @@ -3,7 +3,6 @@
3 3 .title
4 4 %i.icon-calendar
5 5 %span= day.stamp("28 Aug, 2010")
6   -
7 6 .pull-right
8 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 15 %div.ui-box
16 16 .title
17 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 20 - unless @diffs.empty?
21 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 2 -if @merge_request.errors.any?
3 3 .alert.alert-error
4 4 %ul
... ... @@ -12,18 +12,20 @@
12 12 .row
13 13 .span5
14 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 18 .mr_source_commit.prepend-top-10
18   -
19 19 .span2
20 20 %h1.merge-request-angle
21 21 %i.icon-angle-right
22 22 .span5
23 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 30 %hr
29 31  
... ... @@ -47,12 +49,11 @@
47 49 Milestone
48 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 52 .form-actions
52 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 55 -else
55   - = f.submit 'Save changes', class: "btn btn-save"
  56 + = form_helper.submit 'Save changes', class: "btn btn-save"
56 57 - if @merge_request.new_record?
57 58 = link_to project_merge_requests_path(@project), class: "btn btn-cancel" do
58 59 Cancel
... ... @@ -63,16 +64,23 @@
63 64 :javascript
64 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 1 %li{ class: mr_css_classes(merge_request) }
2 2 .merge-request-title
3 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 5 - if merge_request.merged?
6 6 %small.pull-right
7 7 %i.icon-ok
8 8 = "MERGED"
9 9 - else
10 10 %span.pull-right
  11 + = "#{merge_request.source_project.path_with_namespace}/#{merge_request.source_branch}"
11 12 %i.icon-angle-right
12   - = merge_request.target_branch
  13 + = "#{merge_request.target_project.path_with_namespace}/#{merge_request.target_branch}"
13 14 .merge-request-info
14 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 17 - if merge_request.votes_count > 0
17 18 = render 'votes/votes_inline', votable: merge_request
18 19 - if merge_request.notes.any?
... ...
app/views/projects/merge_requests/branch_from.js.haml
1 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 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 7 - if @commits.count > 8
8 8 %ul.first-commits.well-list
9 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 11 %li.bottom
12 12 8 of #{@commits.count} commits displayed.
13 13 %strong
14 14 %a.show-all-commits Click here to show all
15 15 %ul.all-commits.hide.well-list
16 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 19 - else
20 20 %ul.well-list
21 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 24 - else
25 25 %h4.nothing_here_message
... ...
app/views/projects/merge_requests/show/_how_to_merge.html.haml
... ... @@ -3,17 +3,49 @@
3 3 %a.close{href: "#"} ×
4 4 %h3 How To Merge
5 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 15 for instructions
16 16 .accept_group
17 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 19 .remove_branch_holder
20 20 = label_tag :should_remove_source_branch, class: "checkbox" do
21 21 = check_box_tag :should_remove_source_branch
... ...
app/views/projects/merge_requests/show/_mr_title.html.haml
1 1 %h3.page-title
2 2 = "Merge Request ##{@merge_request.id}:"
3 3 &nbsp;
  4 + %span.label-project= @merge_request.source_project.path_with_namespace
4 5 %span.label-branch= @merge_request.source_branch
5 6 &rarr;
  7 + %span.label-project= @merge_request.target_project.path_with_namespace
6 8 %span.label-branch= @merge_request.target_branch
7 9  
8 10 %span.pull-right
... ...
app/views/search/_result.html.haml
... ... @@ -22,11 +22,11 @@
22 22 - @merge_requests.each do |merge_request|
23 23 %li
24 24 merge request:
25   - = link_to [merge_request.project, merge_request] do
  25 + = link_to [merge_request.target_project, merge_request] do
26 26 %span ##{merge_request.id}
27 27 %strong.term
28 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 30 - @issues.each do |issue|
31 31 %li
32 32 issue:
... ...
app/views/shared/_merge_requests.html.haml
1 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 3 .ui-box
4 4 - project = group[0]
5 5 .title
... ...
config/routes.rb
... ... @@ -255,6 +255,7 @@ Gitlab::Application.routes.draw do
255 255 collection do
256 256 get :branch_from
257 257 get :branch_to
  258 + get :update_branches
258 259 end
259 260 end
260 261  
... ...
db/fixtures/development/10_merge_requests.rb
... ... @@ -23,7 +23,8 @@ Gitlab::Seeder.quiet do
23 23 id: i,
24 24 source_branch: branches.first,
25 25 target_branch: branches.last,
26   - project_id: project.id,
  26 + source_project_id: project.id,
  27 + target_project_id: project.id,
27 28 author_id: user_id,
28 29 assignee_id: user_id,
29 30 milestone: project.milestones.sample,
... ...
db/fixtures/test/001_repo.rb
... ... @@ -19,5 +19,15 @@ FileUtils.cd(REPO_PATH) do
19 19 # Remove the copy
20 20 FileUtils.rm(SEED_REPO)
21 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 33 puts ' done.'
... ...
db/migrate/20130419190306_allow_merges_for_forks.rb 0 → 100644
... ... @@ -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
... ...
db/schema.rb
... ... @@ -84,9 +84,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do
84 84 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
85 85  
86 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 90 t.integer "author_id"
91 91 t.integer "assignee_id"
92 92 t.string "title"
... ... @@ -97,14 +97,15 @@ ActiveRecord::Schema.define(:version =&gt; 20130624162710) do
97 97 t.integer "milestone_id"
98 98 t.string "state"
99 99 t.string "merge_status"
  100 + t.integer "target_project_id", :null => false
100 101 end
101 102  
102 103 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
103 104 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
104 105 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
105 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 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 109 add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch"
109 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 16 And I visit dashboard page
17 17 Then I should see groups list
18 18  
  19 + @javascript
19 20 Scenario: I should see last push widget
20 21 Then I should see last push widget
21 22 And I click "Create Merge Request" link
... ...
features/project/forked_merge_requests.feature 0 → 100644
... ... @@ -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 29 And I click link "Close"
30 30 Then I should see closed merge request "Bug NS-04"
31 31  
  32 + @javascript
32 33 Scenario: I submit new unassigned merge request
33 34 Given I click link "New Merge Request"
34 35 And I submit new merge request "Wiki Feature"
... ...
features/steps/dashboard/dashboard.rb
... ... @@ -22,6 +22,7 @@ class Dashboard &lt; Spinach::FeatureSteps
22 22  
23 23 Then 'I see prefilled new Merge Request page' do
24 24 current_path.should == new_project_merge_request_path(@project)
  25 + find("#merge_request_target_project_id").value.should == @project.id.to_s
25 26 find("#merge_request_source_branch").value.should == "new_design"
26 27 find("#merge_request_target_branch").value.should == "master"
27 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 61 end
62 62  
63 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 65 Event.create(
66 66 project: @project,
67 67 action: Event::MERGED,
... ...
features/steps/dashboard/dashboard_merge_requests.rb
... ... @@ -6,18 +6,24 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps
6 6 merge_requests = @user.merge_requests
7 7 merge_requests.each do |mr|
8 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 11 end
11 12 end
12 13  
13 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 28 end
23 29 end
... ...
features/steps/group/group.rb
... ... @@ -60,7 +60,8 @@ class Groups &lt; Spinach::FeatureSteps
60 60  
61 61 Given 'project from group has merge requests assigned to me' do
62 62 create :merge_request,
63   - project: project,
  63 + source_project: project,
  64 + target_project: project,
64 65 assignee: current_user,
65 66 author: current_user
66 67 end
... ...
features/steps/project/deploy_keys.rb
... ... @@ -3,10 +3,13 @@ class Spinach::Features::ProjectDeployKeys &lt; Spinach::FeatureSteps
3 3 include SharedProject
4 4 include SharedPaths
5 5  
  6 +
  7 +
6 8 step 'project has deploy key' do
7 9 create(:deploy_keys_project, project: @project)
8 10 end
9 11  
  12 +
10 13 step 'I should see project deploy keys' do
11 14 within '.enabled-keys' do
12 15 page.should have_content deploy_key.title
... ...
features/steps/project/project_fork.rb
... ... @@ -4,6 +4,8 @@ class ForkProject &lt; Spinach::FeatureSteps
4 4 include SharedProject
5 5  
6 6 step 'I click link "Fork"' do
  7 + page.should have_content "Shop"
  8 + page.should have_content "Fork"
7 9 Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true)
8 10 click_link "Fork"
9 11 end
... ... @@ -14,12 +16,17 @@ class ForkProject &lt; Spinach::FeatureSteps
14 16 @project.team << [@user, :reporter]
15 17 end
16 18  
  19 +
17 20 step 'I should see the forked project page' do
18 21 page.should have_content "Project was successfully forked."
19 22 current_path.should include current_user.namespace.path
  23 + @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
20 24 end
21 25  
22 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 30 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
24 31 end
25 32  
... ...
features/steps/project/project_forked_merge_requests.rb 0 → 100644
... ... @@ -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 24 page.should have_content "Wiki Feature"
25 25 end
26 26  
  27 +
27 28 Then 'I should see closed merge request "Bug NS-04"' do
28 29 merge_request = MergeRequest.find_by_title!("Bug NS-04")
29 30 merge_request.closed?.should be_true
... ... @@ -56,30 +57,35 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
56 57 end
57 58  
58 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 65 click_button "Submit merge request"
63 66 end
64 67  
65 68 And 'project "Shop" have "Bug NS-04" open merge request' do
66 69 create(:merge_request,
67 70 title: "Bug NS-04",
68   - project: project,
  71 + source_project: project,
  72 + target_project: project,
69 73 author: project.users.first)
70 74 end
71 75  
72 76 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
73 77 create(:merge_request_with_diffs,
74 78 title: "Bug NS-05",
75   - project: project,
  79 + source_project: project,
  80 + target_project: project,
76 81 author: project.users.first)
77 82 end
78 83  
79 84 And 'project "Shop" have "Feature NS-03" closed merge request' do
80 85 create(:closed_merge_request,
81 86 title: "Feature NS-03",
82   - project: project,
  87 + source_project: project,
  88 + target_project: project,
83 89 author: project.users.first)
84 90 end
85 91  
... ...
features/steps/shared/paths.rb
... ... @@ -184,6 +184,10 @@ module SharedPaths
184 184 visit project_path(project)
185 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 191 step 'I visit edit project "Shop" page' do
188 192 visit edit_project_path(project)
189 193 end
... ... @@ -239,18 +243,22 @@ module SharedPaths
239 243  
240 244 step 'I visit merge request page "Bug NS-04"' do
241 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 247 end
244 248  
245 249 step 'I visit merge request page "Bug NS-05"' do
246 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 252 end
249 253  
250 254 step 'I visit project "Shop" merge requests page' do
251 255 visit project_merge_requests_path(project)
252 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 262 step 'I visit project "Shop" milestones page' do
255 263 visit project_milestones_path(project)
256 264 end
... ...
features/support/env.rb
... ... @@ -35,8 +35,7 @@ Capybara.ignore_hidden_elements = false
35 35 DatabaseCleaner.strategy = :truncation
36 36  
37 37 Spinach.hooks.before_scenario do
38   - TestEnv.init(mailer: false)
39   -
  38 + TestEnv.setup_stubs
40 39 DatabaseCleaner.start
41 40 end
42 41  
... ... @@ -45,6 +44,7 @@ Spinach.hooks.after_scenario do
45 44 end
46 45  
47 46 Spinach.hooks.before_run do
  47 + TestEnv.init(mailer: false, init_repos: true, repos: false)
48 48 RSpec::Mocks::setup self
49 49  
50 50 include FactoryGirl::Syntax::Methods
... ...
lib/api/merge_requests.rb
... ... @@ -51,9 +51,10 @@ module API
51 51 #
52 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 55 # source_branch (required) - The source branch
56 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 58 # assignee_id - Assignee user ID
58 59 # title (required) - Title of MR
59 60 #
... ... @@ -63,11 +64,15 @@ module API
63 64 post ":id/merge_requests" do
64 65 authorize! :write_merge_request, user_project
65 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 68 merge_request = user_project.merge_requests.new(attrs)
69 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 76 if merge_request.save
72 77 merge_request.reload_code
73 78 present merge_request, with: Entities::MergeRequest
... ...
lib/gitlab/satellite/action.rb
1 1 module Gitlab
2 2 module Satellite
3 3 class Action
4   - DEFAULT_OPTIONS = { git_timeout: 30.seconds }
  4 + DEFAULT_OPTIONS = { git_timeout: 30.seconds}
5 5  
6 6 attr_accessor :options, :project, :user
7 7  
... ... @@ -34,16 +34,19 @@ module Gitlab
34 34 Gitlab::ShellEnv.reset_env
35 35 end
36 36  
37   - # * Clears the satellite
38   - # * Updates the satellite from Gitolite
  37 + # * Recreates the satellite
39 38 # * Sets up Git variables for the user
40 39 #
41 40 # Note: use this within #in_locked_and_timed_satellite
42 41 def prepare_satellite!(repo)
43 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 50 end
48 51 end
49 52 end
... ...
lib/gitlab/satellite/merge_action.rb
... ... @@ -5,37 +5,37 @@ module Gitlab
5 5 attr_accessor :merge_request
6 6  
7 7 def initialize(user, merge_request)
8   - super user, merge_request.project
  8 + super user, merge_request.target_project
9 9 @merge_request = merge_request
10 10 end
11 11  
12 12 # Checks if a merge request can be executed without user interaction
13 13 def can_be_merged?
14 14 in_locked_and_timed_satellite do |merge_repo|
  15 + prepare_satellite!(merge_repo)
15 16 merge_in_satellite!(merge_repo)
16 17 end
17 18 end
18 19  
19 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 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 26 # Returns true otherwise
26 27 def merge!
27 28 in_locked_and_timed_satellite do |merge_repo|
  29 + prepare_satellite!(merge_repo)
28 30 if merge_in_satellite!(merge_repo)
29 31 # push merge back to Gitolite
30 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 34 # remove source branch
34 35 if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
35 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 38 end
38   -
39 39 # merge, push and branch removal successful
40 40 true
41 41 end
... ... @@ -45,6 +45,82 @@ module Gitlab
45 45 false
46 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 124 private
49 125  
50 126 # Merges the source_branch into the target_branch in the satellite.
... ... @@ -54,18 +130,38 @@ module Gitlab
54 130 # Returns false if the merge produced conflicts
55 131 # Returns true otherwise
56 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 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 159 rescue Grit::Git::CommandFailed => ex
66 160 Gitlab::GitLogger.error(ex.message)
67 161 false
68 162 end
  163 +
  164 +
69 165 end
70 166 end
71 167 end
... ...
lib/gitlab/satellite/satellite.rb
1 1 module Gitlab
2   - class SatelliteNotExistError < StandardError; end
  2 + class SatelliteNotExistError < StandardError;
  3 + end
3 4  
4 5 module Satellite
5 6 class Satellite
... ... @@ -21,11 +22,14 @@ module Gitlab
21 22 raise SatelliteNotExistError.new("Satellite doesn't exist")
22 23 end
23 24  
  25 +
24 26 def clear_and_update!
25 27 raise_no_satellite unless exists?
26   -
  28 + File.exists? path
  29 + @repo = nil
27 30 clear_working_dir!
28 31 delete_heads!
  32 + remove_remotes!
29 33 update_from_source!
30 34 end
31 35  
... ... @@ -55,14 +59,16 @@ module Gitlab
55 59 raise_no_satellite unless exists?
56 60  
57 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 67 end
63 68 end
64 69 end
65 70  
  71 +
66 72 def lock_file
67 73 create_locks_dir unless File.exists?(lock_files_dir)
68 74 File.join(lock_files_dir, "satellite_#{project.id}.lock")
... ... @@ -100,20 +106,35 @@ module Gitlab
100 106 if heads.include? PARKING_BRANCH
101 107 repo.git.checkout({}, PARKING_BRANCH)
102 108 else
103   - repo.git.checkout({b: true}, PARKING_BRANCH)
  109 + repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
104 110 end
105 111  
106 112 # remove the parking branch from the list of heads ...
107 113 heads.delete(PARKING_BRANCH)
108 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 126 end
111 127  
112 128 # Updates the satellite from Gitolite
113 129 #
114 130 # Note: this will only update remote branches (i.e. origin/*)
115 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 138 end
118 139  
119 140 # Create directory for stroing
... ...
spec/contexts/filter_context_spec.rb 0 → 100644
... ... @@ -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 58 \ No newline at end of file
... ...
spec/controllers/commit_controller_spec.rb
... ... @@ -2,12 +2,11 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe Projects::CommitController do
4 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 8 before do
9 9 sign_in(user)
10   -
11 10 project.team << [user, :master]
12 11 end
13 12  
... ...
spec/controllers/commits_controller_spec.rb
... ... @@ -2,7 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe Projects::CommitsController do
4 4 let(:project) { create(:project_with_code) }
5   - let(:user) { create(:user) }
  5 + let(:user) { create(:user) }
6 6  
7 7 before do
8 8 sign_in(user)
... ...
spec/controllers/merge_requests_controller_spec.rb
... ... @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 3 describe Projects::MergeRequestsController do
4 4 let(:project) { create(:project_with_code) }
5 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 8 before do
9 9 sign_in(user)
... ... @@ -28,7 +28,7 @@ describe Projects::MergeRequestsController do
28 28 it "should render it" do
29 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 32 end
33 33  
34 34 it "should not escape Html" do
... ...
spec/factories.rb
  1 +<<<<<<< HEAD
1 2 include ActionDispatch::TestProcess
  3 +=======
  4 +require Rails.root.join('spec', 'support', 'test_env.rb')
  5 +>>>>>>> Merge Request on forked projects
2 6  
3 7 FactoryGirl.define do
4 8 sequence :sentence, aliases: [:title, :content] do
... ... @@ -29,8 +33,19 @@ FactoryGirl.define do
29 33 sequence(:name) { |n| "project#{n}" }
30 34 path { name.downcase.gsub(/\s/, '_') }
31 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 46 end
33 47  
  48 +
34 49 factory :redmine_project, parent: :project do
35 50 issues_tracker { "redmine" }
36 51 issues_tracker_id { "project_name_in_redmine" }
... ... @@ -39,14 +54,24 @@ FactoryGirl.define do
39 54 factory :project_with_code, parent: :project do
40 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 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 71 end
48 72 end
49 73  
  74 +
50 75 factory :group do
51 76 sequence(:name) { |n| "group#{n}" }
52 77 path { name.downcase.gsub(/\s/, '_') }
... ... @@ -86,7 +111,8 @@ FactoryGirl.define do
86 111 factory :merge_request do
87 112 title
88 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 116 source_branch "master"
91 117 target_branch "stable"
92 118  
... ... @@ -96,13 +122,13 @@ FactoryGirl.define do
96 122 source_branch "stable" # pretend bcf03b5d
97 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 129 end
104 130 st_diffs do
105   - project.repo.diff("bcf03b5d~3", "bcf03b5d")
  131 + source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
106 132 end
107 133 end
108 134  
... ... @@ -133,7 +159,7 @@ FactoryGirl.define do
133 159  
134 160 trait :on_commit do
135 161 project factory: :project_with_code
136   - commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
  162 + commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
137 163 noteable_type "Commit"
138 164 end
139 165  
... ... @@ -143,12 +169,12 @@ FactoryGirl.define do
143 169  
144 170 trait :on_merge_request do
145 171 project factory: :project_with_code
146   - noteable_id 1
  172 + noteable_id 1
147 173 noteable_type "MergeRequest"
148 174 end
149 175  
150 176 trait :on_issue do
151   - noteable_id 1
  177 + noteable_id 1
152 178 noteable_type "Issue"
153 179 end
154 180  
... ...
spec/factories_spec.rb
... ... @@ -5,8 +5,10 @@ INVALID_FACTORIES = [
5 5 :invalid_key,
6 6 ]
7 7  
  8 +
8 9 FactoryGirl.factories.map(&:name).each do |factory_name|
9 10 next if INVALID_FACTORIES.include?(factory_name)
  11 +
10 12 describe "#{factory_name} factory" do
11 13 it 'should be valid' do
12 14 build(factory_name).should be_valid
... ...
spec/features/gitlab_flavored_markdown_spec.rb
... ... @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 3 describe "GitLab Flavored Markdown" do
4 4 let(:project) { create(:project_with_code) }
5 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 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 11 end
12 12  
13 13 before do
... ... @@ -83,9 +83,7 @@ describe &quot;GitLab Flavored Markdown&quot; do
83 83  
84 84 describe "for merge requests" do
85 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 87 end
90 88  
91 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 2  
3 3 describe "On a merge request", js: true do
4 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 8 before do
9 9 login_as :user
... ... @@ -62,7 +62,7 @@ describe &quot;On a merge request&quot;, js: true do
62 62  
63 63 it 'should be added and form reset' do
64 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 66 within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
67 67 within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
68 68 end
... ... @@ -135,8 +135,8 @@ describe &quot;On a merge request&quot;, js: true do
135 135 end
136 136  
137 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 141 before do
142 142 login_as :user
... ... @@ -144,6 +144,7 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
144 144 visit diffs_project_merge_request_path(project, merge_request)
145 145 end
146 146  
  147 +
147 148 subject { page }
148 149  
149 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 206  
206 207 # TODO: fix
207 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 216 #end
216 217 end
217 218  
... ... @@ -238,39 +239,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
238 239  
239 240 # TODO: fix
240 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 246 #end
247 247 end
248 248 end
249 249  
250 250 # TODO: fix
251 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 274 #end
275 275 end
276 276  
... ...
spec/features/profile_spec.rb
... ... @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe "Profile account page" do
4 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 6 let(:user) { create(:user) }
6 7  
7 8 before do
... ...
spec/features/projects_spec.rb
... ... @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe "Projects" do
4 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 6 before { login_as :user }
6 7  
7 8 describe "DELETE /projects/:id" do
... ...
spec/features/security/project_access_spec.rb
... ... @@ -14,13 +14,14 @@ describe &quot;Application access&quot; do
14 14 end
15 15  
16 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 21 let(:reporter) { create(:user) }
22 22  
23 23 before do
  24 +
24 25 # full access
25 26 project.team << [master, :master]
26 27  
... ... @@ -108,7 +109,7 @@ describe &quot;Application access&quot; do
108 109 describe "GET /project_code/blob" do
109 110 before do
110 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 113 @blob_path = project_blob_path(project, File.join(commit.id, path))
113 114 end
114 115  
... ... @@ -232,13 +233,13 @@ describe &quot;Application access&quot; do
232 233  
233 234  
234 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 240 let(:reporter) { create(:user) }
240 241  
241   - let(:admin) { create(:user) }
  242 + let(:admin) { create(:user) }
242 243  
243 244 before do
244 245 # public project
... ... @@ -339,7 +340,7 @@ describe &quot;Application access&quot; do
339 340 describe "GET /project_code/blob" do
340 341 before do
341 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 344 @blob_path = project_blob_path(project, File.join(commit.id, path))
344 345 end
345 346  
... ...
spec/helpers/gitlab_markdown_helper_spec.rb
... ... @@ -9,7 +9,7 @@ describe GitlabMarkdownHelper do
9 9 let(:user) { create(:user, username: 'gfm') }
10 10 let(:commit) { project.repository.commit }
11 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 13 let(:snippet) { create(:project_snippet, project: project) }
14 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 @@
  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 @@
  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 137 \ No newline at end of file
... ...
spec/mailers/notify_spec.rb
... ... @@ -167,7 +167,7 @@ describe Notify do
167 167 end
168 168  
169 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 172 describe 'that are new' do
173 173 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
... ... @@ -311,7 +311,7 @@ describe Notify do
311 311 end
312 312  
313 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 315 let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
316 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 3 describe Commit do
4 4 let(:commit) { create(:project_with_code).repository.commit }
5 5  
6   -
7 6 describe '#title' do
8 7 it "returns no_commit_message when safe_message is blank" do
9 8 commit.stub(:safe_message).and_return('')
... ...
spec/models/forked_project_link_spec.rb
... ... @@ -12,9 +12,9 @@
12 12 require 'spec_helper'
13 13  
14 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 19 before do
20 20 @project_to = fork_project(project_from, user)
... ... @@ -30,9 +30,9 @@ describe ForkedProjectLink, &quot;add link on fork&quot; do
30 30 end
31 31  
32 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 38 before :each do
... ...
spec/models/merge_request_spec.rb
... ... @@ -41,15 +41,12 @@ describe MergeRequest do
41 41 it { should include_module(Issuable) }
42 42 end
43 43  
44   - describe "#mr_and_commit_notes" do
45   -
46   - end
47 44  
48 45 describe "#mr_and_commit_notes" do
49 46 let!(:merge_request) { create(:merge_request) }
50 47  
51 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 50 create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit')
54 51 create(:note, noteable: merge_request)
55 52 end
... ... @@ -71,4 +68,39 @@ describe MergeRequest do
71 68 subject.is_being_reassigned?.should be_false
72 69 end
73 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 106 end
... ...
spec/models/note_spec.rb
... ... @@ -144,12 +144,12 @@ describe Note do
144 144 end
145 145  
146 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 154 it 'creates and saves a Note' do
155 155 should be_a Note
... ... @@ -157,9 +157,9 @@ describe Note do
157 157 end
158 158  
159 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 163 end
164 164  
165 165 describe :authorization do
... ...
spec/models/project_spec.rb
... ... @@ -26,6 +26,9 @@
26 26 require 'spec_helper'
27 27  
28 28 describe Project do
  29 + before(:each) { enable_observers }
  30 + after(:each) { disable_observers }
  31 +
29 32 describe "Associations" do
30 33 it { should belong_to(:group) }
31 34 it { should belong_to(:namespace) }
... ... @@ -95,12 +98,11 @@ describe Project do
95 98 end
96 99  
97 100 describe "last_activity methods" do
98   - before { enable_observers }
99   - let(:project) { create(:project) }
  101 + let(:project) { create(:project) }
100 102 let(:last_event) { double(created_at: Time.now) }
101 103  
102 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 106 project.stub(last_event: last_event)
105 107 project.last_activity.should == last_event
106 108 end
... ... @@ -122,7 +124,7 @@ describe Project do
122 124 let(:project) { create(:project_with_code) }
123 125  
124 126 before do
125   - @merge_request = create(:merge_request, project: project)
  127 + @merge_request = create(:merge_request, source_project: project, target_project: project)
126 128 @key = create(:key, user_id: project.owner.id)
127 129 end
128 130  
... ...
spec/observers/activity_observer_spec.rb
... ... @@ -8,18 +8,6 @@ describe ActivityObserver do
8 8 it { @event.project.should == project }
9 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 12 describe "Issue created" do
25 13 before do
... ...
spec/observers/issue_observer_spec.rb
... ... @@ -26,14 +26,13 @@ describe IssueObserver do
26 26 before { mock_issue.stub(state: 'closed') }
27 27  
28 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 31 subject.after_close(mock_issue, nil)
32 32 end
33 33  
34 34 it 'trigger notification to send emails' do
35 35 subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
36   -
37 36 subject.after_close(mock_issue, nil)
38 37 end
39 38 end
... ... @@ -42,8 +41,7 @@ describe IssueObserver do
42 41 before { mock_issue.stub(state: 'reopened') }
43 42  
44 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 45 subject.after_reopen(mock_issue, nil)
48 46 end
49 47 end
... ...
spec/observers/merge_request_observer_spec.rb
1 1 require 'spec_helper'
2 2  
3 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 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 11 let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
12 12  
13 13 before { subject.stub(:current_user).and_return(some_user) }
14 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 17 before(:each) { enable_observers }
16   -
  18 + after(:each) { disable_observers }
17 19  
18 20 subject { MergeRequestObserver.instance }
19 21  
... ... @@ -30,7 +32,7 @@ describe MergeRequestObserver do
30 32 end
31 33  
32 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 36 subject.should_receive(:after_update)
35 37  
36 38 MergeRequest.observers.enable :merge_request_observer do
... ... @@ -59,13 +61,13 @@ describe MergeRequestObserver do
59 61 context '#after_close' do
60 62 context 'a status "closed"' do
61 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 66 assigned_mr.close
65 67 end
66 68  
67 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 72 unassigned_mr.close
71 73 end
... ... @@ -75,16 +77,42 @@ describe MergeRequestObserver do
75 77 context '#after_reopen' do
76 78 context 'a status "reopened"' do
77 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 82 closed_assigned_mr.reopen
81 83 end
82 84  
83 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 88 closed_unassigned_mr.reopen
87 89 end
88 90 end
89 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 118 end
... ...
spec/observers/user_observer_spec.rb
... ... @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe UserObserver do
4 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 6 subject { UserObserver.instance }
6 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 2  
3 3 describe UsersProjectObserver do
4 4 before(:each) { enable_observers }
  5 + after(:each) { disable_observers }
5 6  
6 7 let(:user) { create(:user) }
7 8 let(:project) { create(:project) }
... ...
spec/requests/api/merge_requests_spec.rb
... ... @@ -3,10 +3,12 @@ require &quot;spec_helper&quot;
3 3 describe API::API do
4 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 13 describe "GET /projects/:id/merge_requests" do
12 14 context "when unauthenticated" do
... ... @@ -40,35 +42,91 @@ describe API::API do
40 42 end
41 43  
42 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 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 130 end
73 131 end
74 132  
... ... @@ -97,14 +155,14 @@ describe API::API do
97 155  
98 156 it "should return 422 when source_branch and target_branch are renamed the same" do
99 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 159 response.status.should == 422
102 160 end
103 161  
104 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 164 response.status.should == 200
107   - json_response['target_branch'].should == 'test'
  165 + json_response['target_branch'].should == 'wiki'
108 166 end
109 167 end
110 168  
... ...
spec/requests/api/milestones_spec.rb
... ... @@ -3,6 +3,7 @@ require &#39;spec_helper&#39;
3 3 describe API::API do
4 4 include ApiHelpers
5 5 before(:each) { enable_observers }
  6 + after(:each) {disable_observers}
6 7  
7 8 let(:user) { create(:user) }
8 9 let!(:project) { create(:project, namespace: user.namespace ) }
... ...
spec/requests/api/notes_spec.rb
... ... @@ -6,7 +6,7 @@ describe API::API do
6 6 let(:user) { create(:user) }
7 7 let!(:project) { create(:project, namespace: user.namespace ) }
8 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 10 let!(:snippet) { create(:project_snippet, project: project, author: user) }
11 11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
12 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 3 describe API::API do
4 4 include ApiHelpers
5 5 before(:each) { enable_observers }
  6 + after(:each) { disable_observers }
6 7  
7 8 let(:user) { create(:user) }
8 9 let(:user2) { create(:user) }
... ... @@ -14,7 +15,8 @@ describe API::API do
14 15 let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
15 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 21 describe "GET /projects" do
20 22 context "when unauthenticated" do
... ... @@ -46,16 +48,16 @@ describe API::API do
46 48 it "should not create new project" do
47 49 expect {
48 50 post api("/projects", user2), name: 'foo'
49   - }.to change {Project.count}.by(0)
  51 + }.to change { Project.count }.by(0)
50 52 end
51 53 end
52 54  
53 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 57 end
56 58  
57 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 61 end
60 62  
61 63 it "should return a 400 error if name not given" do
... ... @@ -89,17 +91,17 @@ describe API::API do
89 91  
90 92 it "should assign attributes to project" do
91 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 102 post api("/projects", user), project
101 103  
102   - project.each_pair do |k,v|
  104 + project.each_pair do |k, v|
103 105 next if k == :path
104 106 json_response[k.to_s].should == v
105 107 end
... ... @@ -125,11 +127,11 @@ describe API::API do
125 127 before { admin }
126 128  
127 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 131 end
130 132  
131 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 135 end
134 136  
135 137 it "should respond with 201 on success" do
... ... @@ -144,17 +146,17 @@ describe API::API do
144 146  
145 147 it "should assign attributes to project" do
146 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 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 160 next if k == :path
159 161 json_response[k.to_s].should == v
160 162 end
... ... @@ -267,7 +269,7 @@ describe API::API do
267 269 it "should add user to project team" do
268 270 expect {
269 271 post api("/projects/#{project.id}/members", user), user_id: user2.id,
270   - access_level: UsersProject::DEVELOPER
  272 + access_level: UsersProject::DEVELOPER
271 273 }.to change { UsersProject.count }.by(1)
272 274  
273 275 response.status.should == 201
... ... @@ -277,10 +279,10 @@ describe API::API do
277 279  
278 280 it "should return a 201 status if user is already project member" do
279 281 post api("/projects/#{project.id}/members", user), user_id: user2.id,
280   - access_level: UsersProject::DEVELOPER
  282 + access_level: UsersProject::DEVELOPER
281 283 expect {
282 284 post api("/projects/#{project.id}/members", user), user_id: user2.id,
283   - access_level: UsersProject::DEVELOPER
  285 + access_level: UsersProject::DEVELOPER
284 286 }.not_to change { UsersProject.count }.by(1)
285 287  
286 288 response.status.should == 201
... ... @@ -411,8 +413,8 @@ describe API::API do
411 413 it "should add hook to project" do
412 414 expect {
413 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 418 response.status.should == 201
417 419 end
418 420  
... ... @@ -430,7 +432,7 @@ describe API::API do
430 432 describe "PUT /projects/:id/hooks/:hook_id" do
431 433 it "should update an existing project hook" do
432 434 put api("/projects/#{project.id}/hooks/#{hook.id}", user),
433   - url: 'http://example.org'
  435 + url: 'http://example.org'
434 436 response.status.should == 200
435 437 json_response['url'].should == 'http://example.org'
436 438 end
... ... @@ -455,7 +457,7 @@ describe API::API do
455 457 it "should delete hook from project" do
456 458 expect {
457 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 461 response.status.should == 200
460 462 end
461 463  
... ... @@ -501,26 +503,26 @@ describe API::API do
501 503 describe "POST /projects/:id/snippets" do
502 504 it "should create a new project snippet" do
503 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 507 response.status.should == 201
506 508 json_response['title'].should == 'api test'
507 509 end
508 510  
509 511 it "should return a 400 error if title is not given" do
510 512 post api("/projects/#{project.id}/snippets", user),
511   - file_name: 'sample.rb', code: 'test'
  513 + file_name: 'sample.rb', code: 'test'
512 514 response.status.should == 400
513 515 end
514 516  
515 517 it "should return a 400 error if file_name not given" do
516 518 post api("/projects/#{project.id}/snippets", user),
517   - title: 'api test', code: 'test'
  519 + title: 'api test', code: 'test'
518 520 response.status.should == 400
519 521 end
520 522  
521 523 it "should return a 400 error if code not given" do
522 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 526 response.status.should == 400
525 527 end
526 528 end
... ... @@ -528,7 +530,7 @@ describe API::API do
528 530 describe "PUT /projects/:id/snippets/:shippet_id" do
529 531 it "should update an existing project snippet" do
530 532 put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
531   - code: 'updated code'
  533 + code: 'updated code'
532 534 response.status.should == 200
533 535 json_response['title'].should == 'example'
534 536 snippet.reload.content.should == 'updated code'
... ... @@ -536,7 +538,7 @@ describe API::API do
536 538  
537 539 it "should update an existing project snippet with new title" do
538 540 put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
539   - title: 'other api test'
  541 + title: 'other api test'
540 542 response.status.should == 200
541 543 json_response['title'].should == 'other api test'
542 544 end
... ... @@ -598,7 +600,7 @@ describe API::API do
598 600  
599 601 describe "POST /projects/:id/keys" do
600 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 604 response.status.should == 404
603 605 end
604 606  
... ... @@ -606,7 +608,7 @@ describe API::API do
606 608 key_attrs = attributes_for :key
607 609 expect {
608 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 612 end
611 613 end
612 614  
... ... @@ -616,7 +618,7 @@ describe API::API do
616 618 it "should delete existing key" do
617 619 expect {
618 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 622 end
621 623  
622 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 156 let(:merge_request) { create :merge_request, assignee: create(:user) }
157 157  
158 158 before do
159   - build_team(merge_request.project)
  159 + build_team(merge_request.source_project)
  160 + build_team(merge_request.target_project)
160 161 end
161 162  
162 163 describe :new_merge_request do
... ...
spec/services/project_transfer_service_spec.rb
... ... @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2  
3 3 describe ProjectTransferService do
4 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 6  
6 7 context 'namespace -> namespace' do
7 8 let(:user) { create(:user) }
... ... @@ -24,6 +25,7 @@ describe ProjectTransferService do
24 25 @result = service.transfer(project, nil)
25 26 end
26 27  
  28 +
27 29 it { @result.should be_true }
28 30 it { project.namespace.should == nil }
29 31 end
... ...
spec/spec_helper.rb
... ... @@ -48,8 +48,11 @@ Spork.prefork do
48 48 # instead of true.
49 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 56 end
54 57 end
55 58 end
... ...
spec/support/test_env.rb
... ... @@ -27,17 +27,65 @@ module TestEnv
27 27  
28 28 # Disable mailer for spinach tests
29 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 79 GollumWiki.any_instance.stub(:init_repo) do |path|
38 80 create_temp_repo(File.join(repos_path, "#{path}.git"))
39 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 89 Gitlab::Shell.any_instance.stub(
42 90 add_repository: true,
43 91 mv_repository: true,
... ... @@ -48,24 +96,54 @@ module TestEnv
48 96 )
49 97  
50 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 105 MergeRequest.any_instance.stub(
57   - check_if_can_be_merged: true
  106 + check_if_can_be_merged: true
58 107 )
59   -
60 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 117 # Remove tmp/test-git-base-path
65 118 FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path
66 119  
67 120 # Recreate tmp/test-git-base-path
68 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 147 end
70 148  
71 149 def create_temp_repo(path)
... ...