Commit 66dacfc52a1a91783b68e503e0e1844e26f47a54

Authored by Dmitriy Zaporozhets
2 parents 1a83fea7 2388ca0f

Merge branch 'karlhungus-mr-on-fork' of /home/git/repositories/gitlab/gitlabhq

Showing 107 changed files with 1748 additions and 515 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 107 files displayed.

Gemfile
... ... @@ -129,7 +129,7 @@ group :assets do
129 129 gem 'turbolinks'
130 130 gem 'jquery-turbolinks'
131 131  
132   - gem 'chosen-rails', "0.9.8"
  132 + gem 'chosen-rails', "1.0.0"
133 133 gem 'select2-rails'
134 134 gem 'jquery-atwho-rails', "0.3.0"
135 135 gem "jquery-rails", "2.1.3"
... ...
Gemfile.lock
... ... @@ -72,9 +72,12 @@ GEM
72 72 charlock_holmes (0.6.9.4)
73 73 childprocess (0.3.9)
74 74 ffi (~> 1.0, >= 1.0.11)
75   - chosen-rails (0.9.8)
76   - railties (~> 3.0)
77   - thor (~> 0.14)
  75 + chosen-rails (1.0.0)
  76 + coffee-rails (>= 3.2)
  77 + compass-rails (>= 1.0)
  78 + railties (>= 3.0)
  79 + sass-rails (>= 3.2)
  80 + chunky_png (1.2.8)
78 81 code_analyzer (0.3.2)
79 82 sexp_processor
80 83 coderay (1.0.9)
... ... @@ -87,6 +90,12 @@ GEM
87 90 coffee-script-source (1.6.2)
88 91 colored (1.2)
89 92 colorize (0.5.8)
  93 + compass (0.12.2)
  94 + chunky_png (~> 1.2)
  95 + fssm (>= 0.2.7)
  96 + sass (~> 3.1)
  97 + compass-rails (1.0.3)
  98 + compass (>= 0.12.2, < 0.14)
90 99 connection_pool (1.1.0)
91 100 coveralls (0.6.7)
92 101 colorize
... ... @@ -149,6 +158,7 @@ GEM
149 158 dotenv (>= 0.7)
150 159 thor (>= 0.13.6)
151 160 formatador (0.2.4)
  161 + fssm (0.2.10)
152 162 gemoji (1.2.1)
153 163 gherkin-ruby (0.3.0)
154 164 github-linguist (2.3.4)
... ... @@ -548,7 +558,7 @@ DEPENDENCIES
548 558 bootstrap-sass
549 559 capybara
550 560 carrierwave
551   - chosen-rails (= 0.9.8)
  561 + chosen-rails (= 1.0.0)
552 562 coffee-rails
553 563 colored
554 564 coveralls
... ...
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
... ... @@ -17,6 +17,10 @@
17 17 border-radius: $radius;
18 18 }
19 19  
  20 +@mixin border-radius-left($radius) {
  21 + @include border-radius($radius 0 0 $radius)
  22 +}
  23 +
20 24 @mixin linear-gradient($from, $to) {
21 25 background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
22 26 background-image: -webkit-linear-gradient($from, $to);
... ...
app/assets/stylesheets/sections/merge_requests.scss
... ... @@ -84,14 +84,23 @@
84 84  
85 85 .label-branch {
86 86 @include border-radius(4px);
87   - padding: 2px 4px;
  87 + padding: 3px 4px;
88 88 border: none;
89 89 font-size: 14px;
90 90 background: #474D57;
91 91 color: #fff;
92 92 font-family: $monospace_font;
93   - text-shadow: 0 1px 1px #111;
94 93 font-weight: normal;
  94 + overflow: hidden;
  95 +
  96 + .label-project {
  97 + @include border-radius-left(4px);
  98 + padding: 3px 4px;
  99 + background: #29A;
  100 + position: relative;
  101 + left: -4px;
  102 + letter-spacing: -1px;
  103 + }
95 104 }
96 105  
97 106 .mr-list {
... ...
app/assets/stylesheets/selects.scss
1 1 /* CHZN reset few styles */
2   -.chzn-container-single .chzn-single {
  2 +.chosen-container-single .chosen-single {
3 3 background: #FFF;
4 4 border: 1px solid #bbb;
5 5 box-shadow: none;
6 6 }
7   -.chzn-container-active .chzn-single {
  7 +.chosen-container-active .chosen-single {
8 8 background: #fff;
9 9 }
10 10  
... ... @@ -41,38 +41,38 @@
41 41 width: 120px;
42 42 }
43 43  
44   -.project-refs-form .chzn-container {
  44 +.project-refs-form .chosen-container {
45 45 position: relative;
46 46 top: 0;
47 47 left: 0;
48 48 margin-right: 10px;
49 49  
50   - .chzn-drop {
  50 + .chosen-drop {
51 51 min-width: 400px;
52   - .chzn-results {
  52 + .chosen-results {
53 53 max-height: 300px;
54 54 }
55   - .chzn-search input {
  55 + .chosen-search input {
56 56 min-width: 365px;
57 57 }
58 58 }
59 59 }
60 60  
61 61 /** Fix for Search Dropdown Border **/
62   -.chzn-container {
63   - .chzn-search {
  62 +.chosen-container {
  63 + .chosen-search {
64 64 input:focus {
65 65 @include box-shadow(none);
66 66 }
67 67 }
68 68  
69   - .chzn-drop {
  69 + .chosen-drop {
70 70 margin: 7px 0;
71 71 min-width: 200px;
72 72 border: 1px solid #bbb;
73 73 @include border-radius(0);
74 74  
75   - .chzn-results {
  75 + .chosen-results {
76 76 margin-top: 5px;
77 77 max-height: 300px;
78 78  
... ... @@ -95,7 +95,7 @@
95 95 }
96 96 }
97 97  
98   - .chzn-search {
  98 + .chosen-search {
99 99 @include bg-gray-gradient;
100 100 input {
101 101 min-width: 165px;
... ... @@ -103,18 +103,19 @@
103 103 }
104 104 }
105 105 }
  106 +}
106 107  
107   - .chzn-single {
108   - @include bg-light-gray-gradient;
  108 +.chosen-container .chosen-single,
  109 +.chosen-container.chosen-with-drop .chosen-single {
  110 + @include bg-light-gray-gradient;
109 111  
110   - div {
111   - background: transparent;
112   - border-left: none;
113   - }
  112 + div {
  113 + background: transparent;
  114 + border-left: none;
  115 + }
114 116  
115   - span {
116   - font-weight: normal;
117   - }
  117 + span {
  118 + font-weight: normal;
118 119 }
119 120 }
120 121  
... ... @@ -140,3 +141,11 @@
140 141 padding: 7px;
141 142 color: #666;
142 143 }
  144 +
  145 +.chosen-container .chosen-single div b {
  146 + background-position-y: 0px !important;
  147 +}
  148 +
  149 +.chosen-container .chosen-drop .chosen-search input {
  150 + background-position-y: -24px !important;
  151 +}
... ...
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].present?
15   - items = items.where(project_id: params[:project_id])
  15 + items = items.of_projects(params[:project_id])
16 16 end
17 17  
18 18 if params[:search].present?
... ...
app/contexts/merge_requests_load_context.rb
... ... @@ -14,7 +14,7 @@ class MergeRequestsLoadContext &lt; 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(20)
  22 + result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).limit(20)
23 23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(20)
24 24 result[:wiki_pages] = []
25 25 end
... ...
app/controllers/projects/merge_requests_controller.rb
... ... @@ -24,8 +24,8 @@ class Projects::MergeRequestsController &lt; 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,27 +33,37 @@ class Projects::MergeRequestsController &lt; 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   - @merge_request = @project.merge_requests.new(params[:merge_request])
  42 + @merge_request = MergeRequest.new(params[:merge_request])
  43 + @merge_request.source_project = @project unless @merge_request.source_project
  44 + @merge_request.target_project = @project unless @merge_request.target_project
  45 + @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
  46 + @source_project = @merge_request.source_project
  47 + @merge_request
43 48 end
44 49  
45 50 def edit
  51 + @source_project = @merge_request.source_project
  52 + @target_project = @merge_request.target_project
  53 + @target_branches = @merge_request.target_project.repository.branch_names
46 54 end
47 55  
48 56 def create
49   - @merge_request = @project.merge_requests.new(params[:merge_request])
  57 + @merge_request = MergeRequest.new(params[:merge_request])
50 58 @merge_request.author = current_user
51   -
  59 + @target_branches ||= []
52 60 if @merge_request.save
53 61 @merge_request.reload_code
54   - redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.'
  62 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
55 63 else
56   - render "new"
  64 + @source_project = @merge_request.source_project
  65 + @target_project = @merge_request.target_project
  66 + render action: "new"
57 67 end
58 68 end
59 69  
... ... @@ -61,7 +71,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
61 71 if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
62 72 @merge_request.reload_code
63 73 @merge_request.mark_as_unchecked
64   - redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.'
  74 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
65 75 else
66 76 render "edit"
67 77 end
... ... @@ -89,22 +99,35 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
89 99 end
90 100  
91 101 def branch_from
92   - @commit = @repository.commit(params[:ref])
  102 + #This is always source
  103 + @source_project = @merge_request.nil? ? @project : @merge_request.source_project
  104 + @commit = @repository.commit(params[:ref]) if params[:ref].present?
93 105 end
94 106  
95 107 def branch_to
96   - @commit = @repository.commit(params[:ref])
  108 + @target_project = selected_target_project
  109 + @commit = @target_project.repository.commit(params[:ref]) if params[:ref].present?
  110 + end
  111 +
  112 + def update_branches
  113 + @target_project = selected_target_project
  114 + @target_branches = @target_project.repository.branch_names
  115 + @target_branches
97 116 end
98 117  
99 118 def ci_status
100 119 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
101   - response = { status: status }
  120 + response = {status: status}
102 121  
103 122 render json: response
104 123 end
105 124  
106 125 protected
107 126  
  127 + def selected_target_project
  128 + ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
  129 + end
  130 +
108 131 def merge_request
109 132 @merge_request ||= @project.merge_requests.find(params[:id])
110 133 end
... ... @@ -123,11 +146,11 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
123 146  
124 147 def validates_merge_request
125 148 # Show git not found page if target branch doesn't exist
126   - return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch)
  149 + return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
127 150  
128 151 # Show git not found page if source branch doesn't exist
129 152 # and there is no saved commits between source & target branch
130   - return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
  153 + return invalid_mr if !@merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
131 154 end
132 155  
133 156 def define_show_vars
... ...
app/helpers/commits_helper.rb
... ... @@ -56,8 +56,8 @@ module CommitsHelper
56 56 end
57 57 end
58 58  
59   - def commit_to_html commit
60   - escape_javascript(render 'projects/commits/commit', commit: commit)
  59 + def commit_to_html commit, project
  60 + escape_javascript(render 'projects/commits/commit', commit: commit, project: project) unless commit.nil?
61 61 end
62 62  
63 63 def diff_line_content(line)
... ...
app/helpers/merge_requests_helper.rb
... ... @@ -2,14 +2,27 @@ module MergeRequestsHelper
2 2 def new_mr_path_from_push_event(event)
3 3 new_project_merge_request_path(
4 4 event.project,
5   - merge_request: {
6   - source_branch: event.branch_name,
7   - target_branch: event.project.repository.root_ref,
8   - title: event.branch_name.titleize
9   - }
  5 + new_mr_from_push_event(event, event.project)
10 6 )
11 7 end
12 8  
  9 + def new_mr_path_for_fork_from_push_event(event)
  10 + new_project_merge_request_path(
  11 + event.project,
  12 + new_mr_from_push_event(event, event.project.forked_from_project)
  13 + )
  14 + end
  15 +
  16 + def new_mr_from_push_event(event, target_project)
  17 + return :merge_request => {
  18 + source_project_id: event.project.id,
  19 + target_project_id: target_project.id,
  20 + source_branch: event.branch_name,
  21 + target_branch: target_project.repository.root_ref,
  22 + title: event.branch_name.titleize
  23 + }
  24 + end
  25 +
13 26 def mr_css_classes mr
14 27 classes = "merge-request"
15 28 classes << " closed" if mr.closed?
... ... @@ -18,6 +31,14 @@ module MergeRequestsHelper
18 31 end
19 32  
20 33 def ci_build_details_path merge_request
21   - merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
  34 + merge_request.source_project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
  35 + end
  36 +
  37 + def merge_path_description(merge_request, separator)
  38 + if merge_request.for_fork?
  39 + "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
  40 + else
  41 + "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
  42 + end
22 43 end
23 44 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 5 mail(to: recipient(recipient_id), 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 + # Over rides default behavour to show source/target
  27 + # Formats arguments into a String suitable for use as an email subject
  28 + #
  29 + # extra - Extra Strings to be inserted into the subject
  30 + #
  31 + # Examples
  32 + #
  33 + # >> subject('Lorem ipsum')
  34 + # => "GitLab Merge Request | Lorem ipsum"
  35 + #
  36 + # # Automatically inserts Project name:
  37 + # Forked MR
  38 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  39 + # => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
  40 + # => source branch => source
  41 + # => target branch => target
  42 + # >> subject('Lorem ipsum')
  43 + # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
  44 + #
  45 + # Non Forked MR
  46 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  47 + # => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  48 + # => source branch => source
  49 + # => target branch => target
  50 + # >> subject('Lorem ipsum')
  51 + # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
  52 + # # Accepts multiple arguments
  53 + # >> subject('Lorem ipsum', 'Dolor sit amet')
  54 + # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
  55 + def subject(*extra)
  56 + subject = "GitLab Merge Request |"
  57 + if @merge_request.for_fork?
  58 + subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
  59 + else
  60 + subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
  61 + end
  62 + subject << " | " + extra.join(' | ') if extra.present?
  63 + subject
  64 + end
  65 +
29 66 end
... ...
app/mailers/notify.rb
... ... @@ -6,6 +6,7 @@ class Notify &lt; ActionMailer::Base
6 6  
7 7 add_template_helper ApplicationHelper
8 8 add_template_helper GitlabMarkdownHelper
  9 + add_template_helper MergeRequestsHelper
9 10  
10 11 default_url_options[:host] = Gitlab.config.gitlab.host
11 12 default_url_options[:protocol] = Gitlab.config.gitlab.protocol
... ...
app/models/concerns/issuable.rb
... ... @@ -9,23 +9,19 @@ 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") }
28 23 scope :unassigned, -> { where("assignee_id IS NULL") }
  24 + scope :of_projects, ->(ids) { where(project_id: ids) }
29 25  
30 26 delegate :name,
31 27 :email,
... ...
app/models/issue.rb
... ... @@ -17,8 +17,17 @@
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 +
22 31 attr_accessible :title, :assignee_id, :position, :description,
23 32 :milestone_id, :label_list, :author_id_of_changes,
24 33 :state_event
... ...
app/models/merge_request.rb
... ... @@ -2,30 +2,35 @@
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 + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event
  33 +
29 34  
30 35 attr_accessor :should_remove_source_branch
31 36  
... ... @@ -74,30 +79,37 @@ class MergeRequest &lt; ActiveRecord::Base
74 79 serialize :st_commits
75 80 serialize :st_diffs
76 81  
  82 + validates :source_project, presence: true
77 83 validates :source_branch, presence: true
  84 + validates :target_project, presence: true
78 85 validates :target_branch, presence: true
79   - validate :validate_branches
  86 + validate :validate_branches
80 87  
  88 + scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
  89 + scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
  90 + scope :opened, -> { with_state(:opened) }
  91 + scope :closed, -> { with_state(:closed) }
81 92 scope :merged, -> { with_state(:merged) }
82   - scope :by_branch, ->(branch_name) { where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name) }
  93 + scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
83 94 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
84 95 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
85   -
  96 + scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
  97 + scope :of_projects, ->(ids) { where(target_project_id: ids) }
86 98 # Closed scope for merge request should return
87 99 # both merged and closed mr's
88 100 scope :closed, -> { with_states(:closed, :merged) }
89 101  
90 102 def validate_branches
91   - if target_branch == source_branch
92   - errors.add :branch_conflict, "You can not use same branch for source and target branches"
  103 + if target_project==source_project && target_branch == source_branch
  104 + errors.add :branch_conflict, "You can not use same project/branch for source and target"
93 105 end
94 106  
95 107 if opened? || reopened?
96   - similar_mrs = self.project.merge_requests.where(source_branch: source_branch, target_branch: target_branch).opened
  108 + similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
97 109 similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
98 110  
99 111 if similar_mrs.any?
100   - errors.add :base, "There is already an open merge request for this branches"
  112 + errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
101 113 end
102 114 end
103 115 end
... ... @@ -137,7 +149,14 @@ class MergeRequest &lt; ActiveRecord::Base
137 149 end
138 150  
139 151 def unmerged_diffs
140   - Gitlab::Git::Diff.between(project.repository, source_branch, target_branch)
  152 + diffs = if for_fork?
  153 + Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite
  154 + else
  155 + Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch)
  156 + end
  157 +
  158 + diffs ||= []
  159 + diffs
141 160 end
142 161  
143 162 def last_commit
... ... @@ -145,11 +164,11 @@ class MergeRequest &lt; ActiveRecord::Base
145 164 end
146 165  
147 166 def merge_event
148   - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
  167 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
149 168 end
150 169  
151 170 def closed_event
152   - self.project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
  171 + self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
153 172 end
154 173  
155 174 def commits
... ... @@ -165,15 +184,24 @@ class MergeRequest &lt; ActiveRecord::Base
165 184 if opened? && unmerged_commits.any?
166 185 self.st_commits = dump_commits(unmerged_commits)
167 186 save
  187 +
168 188 end
169 189 commits
170 190 end
171 191  
172 192 def unmerged_commits
173   - self.project.repository.
174   - commits_between(self.target_branch, self.source_branch).
  193 + if for_fork?
  194 + commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between
  195 + else
  196 + commits = target_project.repository.commits_between(self.target_branch, self.source_branch)
  197 + end
  198 +
  199 + if commits.present?
  200 + commits = Commit.decorate(commits).
175 201 sort_by(&:created_at).
176 202 reverse
  203 + end
  204 + commits
177 205 end
178 206  
179 207 def merge!(user_id)
... ... @@ -199,21 +227,29 @@ class MergeRequest &lt; ActiveRecord::Base
199 227 # Returns the raw diff for this merge request
200 228 #
201 229 # see "git diff"
202   - def to_diff
203   - project.repo.git.native(:diff, {timeout: 30, raise: true}, "#{target_branch}...#{source_branch}")
  230 + def to_diff(current_user)
  231 + Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
204 232 end
205 233  
206 234 # Returns the commit as a series of email patches.
207 235 #
208 236 # see "git format-patch"
209   - def to_patch
210   - project.repo.git.format_patch({timeout: 30, raise: true, stdout: true}, "#{target_branch}..#{source_branch}")
  237 + def to_patch(current_user)
  238 + Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
211 239 end
212 240  
213 241 def last_commit_short_sha
214 242 @last_commit_short_sha ||= last_commit.sha[0..10]
215 243 end
216 244  
  245 + def for_fork?
  246 + target_project != source_project
  247 + end
  248 +
  249 + def disallow_source_branch_removal?
  250 + (source_project.root_ref? source_branch) || for_fork?
  251 + end
  252 +
217 253 private
218 254  
219 255 def dump_commits(commits)
... ...
app/models/note.rb
... ... @@ -42,8 +42,8 @@ class Note &lt; ActiveRecord::Base
42 42  
43 43 # Scopes
44 44 scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
45   - scope :inline, -> { where("line_code IS NOT NULL") }
46   - scope :not_inline, -> { where(line_code: [nil, '']) }
  45 + scope :inline, ->{ where("line_code IS NOT NULL") }
  46 + scope :not_inline, ->{ where(line_code: [nil, '']) }
47 47  
48 48 scope :common, ->{ where(noteable_type: ["", nil]) }
49 49 scope :fresh, ->{ order("created_at ASC, id ASC") }
... ... @@ -53,10 +53,10 @@ class Note &lt; ActiveRecord::Base
53 53 serialize :st_diff
54 54 before_create :set_diff, if: ->(n) { n.line_code.present? }
55 55  
56   - def self.create_status_change_note(noteable, author, status)
  56 + def self.create_status_change_note(noteable, project, author, status)
57 57 create({
58 58 noteable: noteable,
59   - project: noteable.project,
  59 + project: project,
60 60 author: author,
61 61 note: "_Status changed to #{status}_"
62 62 }, without_protection: true)
... ... @@ -65,7 +65,7 @@ class Note &lt; ActiveRecord::Base
65 65 def commit_author
66 66 @commit_author ||=
67 67 project.users.find_by_email(noteable.author_email) ||
68   - project.users.find_by_name(noteable.author_name)
  68 + project.users.find_by_name(noteable.author_name)
69 69 rescue
70 70 nil
71 71 end
... ...
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 +
2 4 def after_create(merge_request)
  5 + if merge_request.author_id
  6 + create_event(merge_request, Event.determine_action(merge_request))
  7 + end
  8 +
3 9 notification.new_merge_request(merge_request, current_user)
4 10 end
5 11  
6 12 def after_close(merge_request, transition)
7   - Note.create_status_change_note(merge_request, current_user, merge_request.state)
  13 + create_event(merge_request, Event::CLOSED)
  14 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
8 15  
9 16 notification.close_mr(merge_request, current_user)
10 17 end
11 18  
12 19 def after_merge(merge_request, transition)
13 20 notification.merge_mr(merge_request)
  21 + # Since MR can be merged via sidekiq
  22 + # to prevent event duplication do this check
  23 + return true if merge_request.merge_event
  24 +
  25 + Event.create(
  26 + project: merge_request.target_project,
  27 + target_id: merge_request.id,
  28 + target_type: merge_request.class.name,
  29 + action: Event::MERGED,
  30 + author_id: merge_request.author_id_of_changes
  31 + )
14 32 end
15 33  
16 34 def after_reopen(merge_request, transition)
17   - Note.create_status_change_note(merge_request, current_user, merge_request.state)
  35 + create_event(merge_request, Event::REOPENED)
  36 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
18 37 end
19 38  
20 39 def after_update(merge_request)
21 40 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
22 41 end
  42 +
  43 + def create_event(record, status)
  44 + Event.create(
  45 + project: record.target_project,
  46 + target_id: record.id,
  47 + target_type: record.class.name,
  48 + action: status,
  49 + author_id: record.author_id
  50 + )
  51 + end
  52 +
23 53 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,8 @@ 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.target_project)
  84 + recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
85 85  
86 86 recipients.each do |recipient|
87 87 mailer.merged_merge_request_email(recipient.id, merge_request.id)
... ... @@ -194,14 +194,14 @@ class NotificationService
194 194 end
195 195 end
196 196  
197   - def new_resource_email(target, method)
  197 + def new_resource_email(target, project, method)
198 198 if target.respond_to?(:participants)
199 199 recipients = target.participants
200 200 else
201 201 recipients = []
202 202 end
203   - recipients = reject_muted_users(recipients, target.project)
204   - recipients = recipients.concat(project_watchers(target.project)).uniq
  203 + recipients = reject_muted_users(recipients, project)
  204 + recipients = recipients.concat(project_watchers(project)).uniq
205 205 recipients.delete(target.author)
206 206  
207 207 recipients.each do |recipient|
... ... @@ -209,9 +209,9 @@ class NotificationService
209 209 end
210 210 end
211 211  
212   - def close_resource_email(target, current_user, method)
213   - recipients = reject_muted_users([target.author, target.assignee], target.project)
214   - recipients = recipients.concat(project_watchers(target.project)).uniq
  212 + def close_resource_email(target, project, current_user, method)
  213 + recipients = reject_muted_users([target.author, target.assignee], project)
  214 + recipients = recipients.concat(project_watchers(project)).uniq
215 215 recipients.delete(current_user)
216 216  
217 217 recipients.each do |recipient|
... ... @@ -219,14 +219,14 @@ class NotificationService
219 219 end
220 220 end
221 221  
222   - def reassign_resource_email(target, current_user, method)
  222 + def reassign_resource_email(target, project, current_user, method)
223 223 recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
224 224  
225 225 # Add watchers to email list
226   - recipients = recipients.concat(project_watchers(target.project))
  226 + recipients = recipients.concat(project_watchers(project))
227 227  
228 228 # reject users with disabled notifications
229   - recipients = reject_muted_users(recipients, target.project)
  229 + recipients = reject_muted_users(recipients, project)
230 230  
231 231 # Reject me from recipients if I reassign an item
232 232 recipients.delete(current_user)
... ...
app/views/notify/closed_merge_request_email.html.haml
1 1 %p
2 2 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}"
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 + != merge_path_description(@merge_request, '&rarr;')
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 += merge_path_description(@merge_request, 'to')
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 + != merge_path_description(@merge_request, '&rarr;')
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 += merge_path_description(@merge_request, 'to')
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 + != merge_path_description(@merge_request, '&rarr;')
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 +<%= merge_path_description(@merge_request, 'to') %>
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/commit/show.html.haml
1 1 = render "commit_box"
2   -= render "projects/commits/diffs", diffs: @commit.diffs
  2 += render "projects/commits/diffs", diffs: @commit.diffs, project: @project
3 3 = render "projects/notes/notes_with_form"
... ...
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/commits/_diffs.html.haml
... ... @@ -5,7 +5,7 @@
5 5 %p To prevent performance issue we rejected diff information.
6 6 %p
7 7 But if you still want to see diff
8   - = link_to "click this link", project_commit_path(@project, @commit, force_show_diff: true), class: "underlined_link"
  8 + = link_to "click this link", project_commit_path(project, @commit, force_show_diff: true), class: "underlined_link"
9 9  
10 10 %p.commit-stat-summary
11 11 Showing
... ... @@ -23,8 +23,8 @@
23 23 - unless @suppress_diff
24 24 - diffs.each_with_index do |diff, i|
25 25 - next if diff.diff.empty?
26   - - file = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, diff.new_path)
27   - - file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) unless file.exists?
  26 + - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path)
  27 + - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists?
28 28 - next unless file.exists?
29 29 .file{id: "diff-#{i}"}
30 30 .header
... ... @@ -32,7 +32,7 @@
32 32 %span= diff.old_path
33 33  
34 34 - if @commit.parent_ids.present?
35   - = link_to project_blob_path(@project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
  35 + = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
36 36 View file @
37 37 %span.commit-short-id= @commit.short_id(6)
38 38 - else
... ... @@ -40,7 +40,7 @@
40 40 - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
41 41 %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
42 42  
43   - = link_to project_blob_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
  43 + = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
44 44 View file @
45 45 %span.commit-short-id= @commit.short_id(6)
46 46  
... ... @@ -50,7 +50,7 @@
50 50 - if file.text?
51 51 = render "projects/commits/text_file", diff: diff, index: i
52 52 - elsif file.image?
53   - - old_file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id
  53 + - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id
54 54 = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
55 55 - else
56 56 %p.nothing_here_message No preview for this file type
... ...
app/views/projects/compare/show.html.haml
... ... @@ -15,8 +15,8 @@
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
22   - = render "projects/commits/diffs", diffs: @diffs
  22 + = render "projects/commits/diffs", diffs: @diffs, project: @project
... ...
app/views/projects/merge_requests/_form.html.haml
... ... @@ -12,17 +12,27 @@
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
  17 + = f.select(:source_project_id,[[@merge_request.source_project.path_with_namespace,@merge_request.source_project.id]] , {}, {class: 'source_project chosen span4'})
  18 + .prepend-top-10
  19 + %i.icon-code-fork
  20 + &nbsp;
  21 + = f.select(:source_branch, @merge_request.source_project.repository.branch_names, { include_blank: "Select branch" }, {class: 'source_branch chosen span3'})
17 22 .mr_source_commit.prepend-top-10
18   -
19 23 .span2
20 24 %h1.merge-request-angle
21 25 %i.icon-angle-right
22 26 .span5
23 27 .light-well
24   - %h5.cgray To (Base Branch)
25   - = f.select(:target_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'})
  28 + %h5.cgray To
  29 + - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
  30 + .padded
  31 + = f.select(:target_project_id, projects.map { |proj| [proj.path_with_namespace,proj.id] }, {include_blank: "Select Target Project" }, {class: 'target_project chosen span4'})
  32 + .prepend-top-10
  33 + %i.icon-code-fork
  34 + &nbsp;
  35 + = f.select(:target_branch, @target_branches, { include_blank: "Select branch" }, {class: 'target_branch chosen span3'})
26 36 .mr_target_commit.prepend-top-10
27 37  
28 38 %hr
... ... @@ -47,32 +57,34 @@
47 57 Milestone
48 58 .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
49 59  
50   -
51 60 .form-actions
52 61 - if @merge_request.new_record?
53 62 = f.submit 'Submit merge request', class: "btn btn-create"
54 63 -else
55 64 = f.submit 'Save changes', class: "btn btn-save"
56 65 - if @merge_request.new_record?
57   - = link_to project_merge_requests_path(@project), class: "btn btn-cancel" do
  66 + = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do
58 67 Cancel
59 68 - else
60   - = link_to project_merge_request_path(@project, @merge_request), class: "btn btn-cancel" do
  69 + = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do
61 70 Cancel
62 71  
63 72 :javascript
64 73 disableButtonIfEmptyField("#merge_request_title", ".btn-save");
65 74  
66 75 var source_branch = $("#merge_request_source_branch")
67   - , target_branch = $("#merge_request_target_branch");
  76 + , target_branch = $("#merge_request_target_branch")
  77 + , target_project = $("#merge_request_target_project_id");
68 78  
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() });
  79 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
  80 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
71 81  
72   - source_branch.live("change", function() {
73   - $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: $(this).val() });
  82 + target_project.on("change", function() {
  83 + $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
74 84 });
75   -
76   - target_branch.live("change", function() {
77   - $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
  85 + source_branch.on("change", function() {
  86 + $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
  87 + });
  88 + target_branch.on("change", function() {
  89 + $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
78 90 });
... ...
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   - %i.icon-angle-right
12   - = merge_request.target_branch
  11 + - if merge_request.for_fork?
  12 + %span.light
  13 + = "#{merge_request.source_project.path_with_namespace}"
  14 + = "#{merge_request.source_branch}"
  15 + %i.icon-angle-right.light
  16 + = "#{merge_request.target_branch}"
  17 + - else
  18 + = "#{merge_request.source_branch}"
  19 + %i.icon-angle-right.light
  20 + = "#{merge_request.target_branch}"
13 21 .merge-request-info
14 22 - if merge_request.author
15   - authored by #{link_to_member(@project, merge_request.author)}
  23 + authored by #{link_to_member(merge_request.source_project, merge_request.author)}
16 24 - if merge_request.votes_count > 0
17 25 = render 'votes/votes_inline', votable: merge_request
18 26 - 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, @source_project)}");
3 3 var mrTitle = $('#merge_request_title');
4 4  
5   - if(mrTitle.is(":empty")) {
  5 + if(mrTitle.val().length == 0) {
6 6 mrTitle.val("#{params[:ref].titleize}");
7 7 }
... ...
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/_diffs.html.haml
1 1 - if @merge_request.valid_diffs?
2   - = render "projects/commits/diffs", diffs: @merge_request.diffs
  2 + = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
3 3 - elsif @merge_request.broken_diffs?
4 4 %h4.nothing_here_message
5 5 Can't load diff.
6 6 You can
7   - = link_to "download it", project_merge_request_path(@project, @merge_request, format: :diff), class: "vlink"
  7 + = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink"
8 8 instead.
9 9 - else
10 10 %h4.nothing_here_message Nothing to merge
... ...
app/views/projects/merge_requests/show/_how_to_merge.html.haml
... ... @@ -3,17 +3,49 @@
3 3 %a.close{href: "#", "data-dismiss" => "modal"} ×
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
... ... @@ -16,7 +16,7 @@
16 16 for instructions
17 17 .accept_group
18 18 = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
19   - - unless @project.root_ref? @merge_request.source_branch
  19 + - unless @merge_request.disallow_source_branch_removal?
20 20 .remove_branch_holder
21 21 = label_tag :should_remove_source_branch, class: "checkbox" do
22 22 = 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-branch= @merge_request.source_branch
5   - &rarr;
6   - %span.label-branch= @merge_request.target_branch
  4 + -if @merge_request.for_fork?
  5 + %span.label-branch
  6 + %span.label-project= truncate(@merge_request.source_project.path_with_namespace, length: 25)
  7 + #{@merge_request.source_branch}
  8 + &rarr;
  9 + %span.label-branch= @merge_request.target_branch
  10 + - else
  11 + %span.label-branch= @merge_request.source_branch
  12 + &rarr;
  13 + %span.label-branch= @merge_request.target_branch
7 14  
8 15 %span.pull-right
9 16 - if can?(current_user, :modify_merge_request, @merge_request)
... ... @@ -19,7 +26,7 @@
19 26  
20 27 = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
21 28  
22   - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do
  29 + = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do
23 30 %i.icon-edit
24 31 Edit
25 32  
... ...
app/views/projects/merge_requests/update_branches.js.haml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +:plain
  2 + $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
  3 + $(".target_branch").trigger("liszt:updated");
  4 + $(".mr_target_commit").html("");
  5 + $(".target_branch").trigger("change");
0 6 \ No newline at end of file
... ...
app/views/projects/notes/_discussion.html.haml
... ... @@ -23,7 +23,7 @@
23 23 discussion on this merge request diff
24 24 - elsif note.for_commit?
25 25 started a discussion on commit
26   - #{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)}
  26 + #{link_to note.noteable.short_id, project_commit_path(note.project, note.noteable)}
27 27 = link_to_commit_diff_line_note(note) if note.for_diff_line?
28 28 - else
29 29 %cite.cgray started a discussion
... ...
app/views/search/_result.html.haml
... ... @@ -22,11 +22,14 @@
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 + - if merge_request.for_fork?
  30 + %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} &rarr; #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
  31 + - else
  32 + %span.light (#{merge_request.source_branch} &rarr; #{merge_request.target_branch})
30 33 - @issues.each do |issue|
31 34 %li
32 35 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.small-box
4 4 - project = group[0]
5 5 .title
... ...
config/routes.rb
... ... @@ -262,6 +262,7 @@ Gitlab::Application.routes.draw do
262 262 collection do
263 263 get :branch_from
264 264 get :branch_to
  265 + get :update_branches
265 266 end
266 267 end
267 268  
... ...
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,18 @@ 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..."
  24 +
  25 +SATELLITE_PATH = Rails.root.join('tmp', 'satellite')
  26 +# Make directory
  27 +FileUtils.mkdir_p(SATELLITE_PATH)
  28 +# Clear any potential directory
  29 +FileUtils.rm_rf("#{SATELLITE_PATH}/gitlabhq")
  30 +# Chdir, clone from the seed
  31 +FileUtils.cd(SATELLITE_PATH) do
  32 + # Clone the satellite
22 33  
  34 + `git clone --quiet #{REPO_PATH}/gitlabhq #{SATELLITE_PATH}/gitlabhq`
  35 +end
23 36 puts ' done.'
... ...
db/migrate/20130419190306_allow_merges_for_forks.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class AllowMergesForForks < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :merge_requests, :target_project_id, :integer, :null => false
  4 + MergeRequest.update_all("target_project_id = project_id")
  5 + rename_column :merge_requests, :project_id, :source_project_id
  6 + end
  7 +
  8 + def self.down
  9 + remove_column :merge_requests, :target_project_id
  10 + rename_column :merge_requests, :source_project_id,:project_id
  11 + end
  12 +end
... ...
db/schema.rb
... ... @@ -55,8 +55,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
55 55 t.integer "assignee_id"
56 56 t.integer "author_id"
57 57 t.integer "project_id"
58   - t.datetime "created_at"
59   - t.datetime "updated_at"
  58 + t.datetime "created_at", :null => false
  59 + t.datetime "updated_at", :null => false
60 60 t.integer "position", :default => 0
61 61 t.string "branch_name"
62 62 t.text "description"
... ... @@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
73 73  
74 74 create_table "keys", :force => true do |t|
75 75 t.integer "user_id"
76   - t.datetime "created_at"
77   - t.datetime "updated_at"
  76 + t.datetime "created_at", :null => false
  77 + t.datetime "updated_at", :null => false
78 78 t.text "key"
79 79 t.string "title"
80 80 t.string "type"
... ... @@ -84,27 +84,28 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) 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"
93   - t.datetime "created_at"
94   - t.datetime "updated_at"
95   - t.text "st_commits", :limit => 2147483647
96   - t.text "st_diffs", :limit => 2147483647
  93 + t.datetime "created_at", :null => false
  94 + t.datetime "updated_at", :null => false
  95 + t.text "st_commits", :limit => 2147483647
  96 + t.text "st_diffs", :limit => 2147483647
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  
... ... @@ -140,8 +141,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
140 141 t.text "note"
141 142 t.string "noteable_type"
142 143 t.integer "author_id"
143   - t.datetime "created_at"
144   - t.datetime "updated_at"
  144 + t.datetime "created_at", :null => false
  145 + t.datetime "updated_at", :null => false
145 146 t.integer "project_id"
146 147 t.string "attachment"
147 148 t.string "line_code"
... ... @@ -162,8 +163,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
162 163 t.string "name"
163 164 t.string "path"
164 165 t.text "description"
165   - t.datetime "created_at"
166   - t.datetime "updated_at"
  166 + t.datetime "created_at", :null => false
  167 + t.datetime "updated_at", :null => false
167 168 t.integer "creator_id"
168 169 t.string "default_branch"
169 170 t.boolean "issues_enabled", :default => true, :null => false
... ... @@ -212,8 +213,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
212 213 t.text "content", :limit => 2147483647
213 214 t.integer "author_id", :null => false
214 215 t.integer "project_id"
215   - t.datetime "created_at"
216   - t.datetime "updated_at"
  216 + t.datetime "created_at", :null => false
  217 + t.datetime "updated_at", :null => false
217 218 t.string "file_name"
218 219 t.datetime "expires_at"
219 220 t.boolean "private", :default => true, :null => false
... ... @@ -235,6 +236,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
235 236 t.datetime "created_at"
236 237 end
237 238  
  239 + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
  240 + add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"
  241 +
238 242 create_table "tags", :force => true do |t|
239 243 t.string "name"
240 244 end
... ... @@ -266,37 +270,37 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
266 270 end
267 271  
268 272 create_table "users", :force => true do |t|
269   - t.string "email", :default => "", :null => false
270   - t.string "encrypted_password", :limit => 128, :default => "", :null => false
  273 + t.string "email", :default => "", :null => false
  274 + t.string "encrypted_password", :default => "", :null => false
271 275 t.string "reset_password_token"
272 276 t.datetime "reset_password_sent_at"
273 277 t.datetime "remember_created_at"
274   - t.integer "sign_in_count", :default => 0
  278 + t.integer "sign_in_count", :default => 0
275 279 t.datetime "current_sign_in_at"
276 280 t.datetime "last_sign_in_at"
277 281 t.string "current_sign_in_ip"
278 282 t.string "last_sign_in_ip"
279   - t.datetime "created_at"
280   - t.datetime "updated_at"
  283 + t.datetime "created_at", :null => false
  284 + t.datetime "updated_at", :null => false
281 285 t.string "name"
282   - t.boolean "admin", :default => false, :null => false
283   - t.integer "projects_limit", :default => 10
284   - t.string "skype", :default => "", :null => false
285   - t.string "linkedin", :default => "", :null => false
286   - t.string "twitter", :default => "", :null => false
  286 + t.boolean "admin", :default => false, :null => false
  287 + t.integer "projects_limit", :default => 10
  288 + t.string "skype", :default => "", :null => false
  289 + t.string "linkedin", :default => "", :null => false
  290 + t.string "twitter", :default => "", :null => false
287 291 t.string "authentication_token"
288   - t.integer "theme_id", :default => 1, :null => false
  292 + t.integer "theme_id", :default => 1, :null => false
289 293 t.string "bio"
290   - t.integer "failed_attempts", :default => 0
  294 + t.integer "failed_attempts", :default => 0
291 295 t.datetime "locked_at"
292 296 t.string "extern_uid"
293 297 t.string "provider"
294 298 t.string "username"
295   - t.boolean "can_create_group", :default => true, :null => false
296   - t.boolean "can_create_team", :default => true, :null => false
  299 + t.boolean "can_create_group", :default => true, :null => false
  300 + t.boolean "can_create_team", :default => true, :null => false
297 301 t.string "state"
298   - t.integer "color_scheme_id", :default => 1, :null => false
299   - t.integer "notification_level", :default => 1, :null => false
  302 + t.integer "color_scheme_id", :default => 1, :null => false
  303 + t.integer "notification_level", :default => 1, :null => false
300 304 t.datetime "password_expires_at"
301 305 t.integer "created_by_id"
302 306 end
... ... @@ -304,6 +308,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
304 308 add_index "users", ["admin"], :name => "index_users_on_admin"
305 309 add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
306 310 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
  311 + add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true
307 312 add_index "users", ["name"], :name => "index_users_on_name"
308 313 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
309 314 add_index "users", ["username"], :name => "index_users_on_username"
... ... @@ -322,8 +327,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
322 327 create_table "users_projects", :force => true do |t|
323 328 t.integer "user_id", :null => false
324 329 t.integer "project_id", :null => false
325   - t.datetime "created_at"
326   - t.datetime "updated_at"
  330 + t.datetime "created_at", :null => false
  331 + t.datetime "updated_at", :null => false
327 332 t.integer "project_access", :default => 0, :null => false
328 333 t.integer "notification_level", :default => 3, :null => false
329 334 end
... ... @@ -335,8 +340,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
335 340 create_table "web_hooks", :force => true do |t|
336 341 t.string "url"
337 342 t.integer "project_id"
338   - t.datetime "created_at"
339   - t.datetime "updated_at"
  343 + t.datetime "created_at", :null => false
  344 + t.datetime "updated_at", :null => false
340 345 t.string "type", :default => "ProjectHook"
341 346 t.integer "service_id"
342 347 end
... ...
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,43 @@
  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 + # TODO: fix
  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 + @javascript
  17 + Scenario: I submit new unassigned merge request to a forked project
  18 + Given I visit project "Forked Shop" merge requests page
  19 + And I click link "New Merge Request"
  20 + And I fill out a "Merge Request On Forked Project" merge request
  21 + And I submit the merge request
  22 + Then I should see merge request "Merge Request On Forked Project"
  23 +
  24 + @javascript
  25 + Scenario: I can edit a forked merge request
  26 + Given I visit project "Forked Shop" merge requests page
  27 + And I click link "New Merge Request"
  28 + And I fill out a "Merge Request On Forked Project" merge request
  29 + And I submit the merge request
  30 + And I should see merge request "Merge Request On Forked Project"
  31 + And I click link edit "Merge Request On Forked Project"
  32 + Then I see the edit page prefilled for "Merge Request On Forked Project"
  33 + And I update the merge request title
  34 + And I save the merge request
  35 + Then I should see the edited merge request
  36 +
  37 + @javascript
  38 + Scenario: I cannot submit an invalid merge request
  39 + Given I visit project "Forked Shop" merge requests page
  40 + And I click link "New Merge Request"
  41 + And I fill out an invalid "Merge Request On Forked Project" merge request
  42 + And I submit the merge request
  43 + Then I should see validation errors
... ...
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/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
... ... @@ -17,9 +19,13 @@ class ForkProject &lt; Spinach::FeatureSteps
17 19 step 'I should see the forked project page' do
18 20 page.should have_content "Project was successfully forked."
19 21 current_path.should include current_user.namespace.path
  22 + @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
20 23 end
21 24  
22 25 step 'I already have a project named "Shop" in my namespace' do
  26 + current_user.namespace ||= create(:namespace)
  27 + current_user.namespace.should_not be_nil
  28 + current_user.namespace.path.should_not be_nil
23 29 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
24 30 end
25 31  
... ...
features/steps/project/project_forked_merge_requests.rb 0 → 100644
... ... @@ -0,0 +1,184 @@
  1 +class ProjectForkedMergeRequests < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedNote
  5 + include SharedPaths
  6 + include ChosenHelper
  7 +
  8 + step '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 + step '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 , namespace: @forking_user.namespace)
  19 +
  20 + forked_project_link.forked_from_project = @project
  21 + forked_project_link.forked_to_project = @forked_project
  22 + @forked_project.team << [@forking_user , :master]
  23 + forked_project_link.save!
  24 + end
  25 +
  26 + step 'I click link "New Merge Request"' do
  27 + click_link "New Merge Request"
  28 + end
  29 +
  30 + step 'I should see merge request "Merge Request On Forked Project"' do
  31 + @project.merge_requests.size.should >= 1
  32 + @merge_request = @project.merge_requests.last
  33 + current_path.should == project_merge_request_path(@project, @merge_request)
  34 + @merge_request.title.should == "Merge Request On Forked Project"
  35 + @merge_request.source_project.should == @forked_project
  36 + @merge_request.source_branch.should == "master"
  37 + @merge_request.target_branch.should == "stable"
  38 + page.should have_content @forked_project.path_with_namespace
  39 + page.should have_content @project.path_with_namespace
  40 + page.should have_content @merge_request.source_branch
  41 + page.should have_content @merge_request.target_branch
  42 + end
  43 +
  44 + step 'I fill out a "Merge Request On Forked Project" merge request' do
  45 + chosen @forked_project.id, from: "#merge_request_source_project_id"
  46 + chosen @project.id, from: "#merge_request_target_project_id"
  47 +
  48 + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
  49 + find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
  50 +
  51 + chosen "master", from: "#merge_request_source_branch"
  52 + chosen "stable", from: "#merge_request_target_branch"
  53 +
  54 + find(:select, "merge_request_source_branch", {}).value.should == 'master'
  55 + find(:select, "merge_request_target_branch", {}).value.should == 'stable'
  56 +
  57 + fill_in "merge_request_title", with: "Merge Request On Forked Project"
  58 + end
  59 +
  60 + step 'I submit the merge request' do
  61 + click_button "Submit merge request"
  62 + end
  63 +
  64 + step 'I follow the target commit link' do
  65 + commit = @project.repository.commit
  66 + click_link commit.short_id(8)
  67 + end
  68 +
  69 + step 'I should see the commit under the forked from project' do
  70 + commit = @project.repository.commit
  71 + page.should have_content(commit.message)
  72 + end
  73 +
  74 + step 'I click "Create Merge Request on fork" link' do
  75 + click_link "Create Merge Request on fork"
  76 + end
  77 +
  78 + step 'I see prefilled new Merge Request page for the forked project' do
  79 + current_path.should == new_project_merge_request_path(@forked_project)
  80 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  81 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  82 + find("#merge_request_source_branch").value.should have_content "new_design"
  83 + find("#merge_request_target_branch").value.should have_content "master"
  84 + find("#merge_request_title").value.should == "New Design"
  85 + verify_commit_link(".mr_target_commit",@project)
  86 + verify_commit_link(".mr_source_commit",@forked_project)
  87 + end
  88 +
  89 + step 'I update the merge request title' do
  90 + fill_in "merge_request_title", with: "An Edited Forked Merge Request"
  91 + end
  92 +
  93 + step 'I save the merge request' do
  94 + click_button "Save changes"
  95 + end
  96 +
  97 + step 'I should see the edited merge request' do
  98 + page.should have_content "An Edited Forked Merge Request"
  99 + @project.merge_requests.size.should >= 1
  100 + @merge_request = @project.merge_requests.last
  101 + current_path.should == project_merge_request_path(@project, @merge_request)
  102 + @merge_request.source_project.should == @forked_project
  103 + @merge_request.source_branch.should == "master"
  104 + @merge_request.target_branch.should == "stable"
  105 + page.should have_content @forked_project.path_with_namespace
  106 + page.should have_content @project.path_with_namespace
  107 + page.should have_content @merge_request.source_branch
  108 + page.should have_content @merge_request.target_branch
  109 + end
  110 +
  111 + step 'I should see last push widget' do
  112 + page.should have_content "You pushed to new_design"
  113 + page.should have_link "Create Merge Request"
  114 + end
  115 +
  116 + step 'project "Forked Shop" has push event' do
  117 + @forked_project = Project.find_by_name("Forked Shop")
  118 +
  119 + data = {
  120 + before: "0000000000000000000000000000000000000000",
  121 + after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
  122 + ref: "refs/heads/new_design",
  123 + user_id: @user.id,
  124 + user_name: @user.name,
  125 + repository: {
  126 + name: @forked_project.name,
  127 + url: "localhost/rubinius",
  128 + description: "",
  129 + homepage: "localhost/rubinius",
  130 + private: true
  131 + }
  132 + }
  133 +
  134 + @event = Event.create(
  135 + project: @forked_project,
  136 + action: Event::PUSHED,
  137 + data: data,
  138 + author_id: @user.id
  139 + )
  140 + end
  141 +
  142 +
  143 + step 'I click link edit "Merge Request On Forked Project"' do
  144 + find("#edit_merge_request").click
  145 + end
  146 +
  147 + step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
  148 + current_path.should == edit_project_merge_request_path(@project, @merge_request)
  149 + page.should have_content "Edit merge request ##{@merge_request.id}"
  150 + find("#merge_request_title").value.should == "Merge Request On Forked Project"
  151 + find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
  152 + find("#merge_request_target_project_id").value.should == @project.id.to_s
  153 + find("#merge_request_source_branch").value.should have_content "master"
  154 + verify_commit_link(".mr_source_commit",@forked_project)
  155 + find("#merge_request_target_branch").value.should have_content "stable"
  156 + verify_commit_link(".mr_target_commit",@project)
  157 + end
  158 +
  159 + step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
  160 + #If this isn't filled in the rest of the validations won't be triggered
  161 + fill_in "merge_request_title", with: "Merge Request On Forked Project"
  162 + find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
  163 + find(:select, "merge_request_target_project_id", {}).value.should == @forked_project.id.to_s
  164 + find(:select, "merge_request_source_branch", {}).value.should == ""
  165 + find(:select, "merge_request_target_branch", {}).value.should == ""
  166 + end
  167 +
  168 + step 'I should see validation errors' do
  169 + page.should have_content "Source branch can't be blank"
  170 + page.should have_content "Target branch can't be blank"
  171 + page.should have_content "Branch conflict You can not use same project/branch for source and target"
  172 + end
  173 +
  174 + def project
  175 + @project ||= Project.find_by_name!("Shop")
  176 + end
  177 +
  178 + #Verify a link is generated against the correct project
  179 + def verify_commit_link(container_div, container_project)
  180 + #This should force a wait for the javascript to execute
  181 + find(:div,container_div).should have_css ".browse_code_link_holder"
  182 + find(:div,container_div).find(".commit_short_id")['href'].should have_content "#{container_project.path_with_namespace}/commit"
  183 + end
  184 +end
... ...
features/steps/project/project_merge_requests.rb
... ... @@ -56,30 +56,41 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
56 56 end
57 57  
58 58 And 'I submit new merge request "Wiki Feature"' do
59   - fill_in "merge_request_title", with: "Wiki Feature"
60   - select "bootstrap", from: "merge_request_source_branch"
61   - select "master", from: "merge_request_target_branch"
  59 + #this must come first, so that the target branch is set by the time the "select" for "notes_refactoring" is executed
  60 + select project.path_with_namespace, :from => "merge_request_target_project_id"
  61 + fill_in "merge_request_title", :with => "Wiki Feature"
  62 + select "master", :from => "merge_request_source_branch"
  63 + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
  64 + find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s
  65 +
  66 + #using "notes_refactoring" because "Bug NS-04" uses master/stable, this will fail merge_request validation if the branches are the same
  67 + find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring"
  68 + select "notes_refactoring", :from => "merge_request_target_branch"
  69 +
62 70 click_button "Submit merge request"
63 71 end
64 72  
65 73 And 'project "Shop" have "Bug NS-04" open merge request' do
66 74 create(:merge_request,
67 75 title: "Bug NS-04",
68   - project: project,
  76 + source_project: project,
  77 + target_project: project,
69 78 author: project.users.first)
70 79 end
71 80  
72 81 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
73 82 create(:merge_request_with_diffs,
74 83 title: "Bug NS-05",
75   - project: project,
  84 + source_project: project,
  85 + target_project: project,
76 86 author: project.users.first)
77 87 end
78 88  
79 89 And 'project "Shop" have "Feature NS-03" closed merge request' do
80 90 create(:closed_merge_request,
81 91 title: "Feature NS-03",
82   - project: project,
  92 + source_project: project,
  93 + target_project: project,
83 94 author: project.users.first)
84 95 end
85 96  
... ...
features/steps/project/project_network_graph.rb
... ... @@ -15,11 +15,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps
15 15 end
16 16  
17 17 And 'page should select "master" in select box' do
18   - page.should have_selector '#ref_chzn span', text: "master"
  18 + page.should have_selector '.chosen-single span', text: "master"
19 19 end
20 20  
21 21 And 'page should select "v2.1.0" in select box' do
22   - page.should have_selector '#ref_chzn span', text: "v2.1.0"
  22 + page.should have_selector '.chosen-single span', text: "v2.1.0"
23 23 end
24 24  
25 25 And 'page should have "master" on graph' do
... ... @@ -61,11 +61,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps
61 61 end
62 62  
63 63 And 'page should select "stable" in select box' do
64   - page.should have_selector '#ref_chzn span', text: "stable"
  64 + page.should have_selector '.chosen-single span', text: "stable"
65 65 end
66 66  
67 67 And 'page should select "v2.1.0" in select box' do
68   - page.should have_selector '#ref_chzn span', text: "v2.1.0"
  68 + page.should have_selector '.chosen-single span', text: "v2.1.0"
69 69 end
70 70  
71 71 And 'page should have "stable" on graph' do
... ...
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
... ... @@ -14,7 +14,7 @@ require &#39;spinach/capybara&#39;
14 14 require 'sidekiq/testing/inline'
15 15  
16 16  
17   -%w(valid_commit select2_helper test_env).each do |f|
  17 +%w(valid_commit select2_helper chosen_helper test_env).each do |f|
18 18 require Rails.root.join('spec', 'support', f)
19 19 end
20 20  
... ... @@ -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
... ... @@ -14,6 +14,14 @@ module API
14 14 end
15 15 not_found!
16 16 end
  17 +
  18 + def not_fork?(target_project_id, user_project)
  19 + target_project_id.nil? || target_project_id == user_project.id.to_s
  20 + end
  21 +
  22 + def target_matches_fork(target_project_id,user_project)
  23 + user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
  24 + end
17 25 end
18 26  
19 27 # List merge requests
... ... @@ -51,9 +59,10 @@ module API
51 59 #
52 60 # Parameters:
53 61 #
54   - # id (required) - The ID of a project
  62 + # id (required) - The ID of a project - this will be the source of the merge request
55 63 # source_branch (required) - The source branch
56 64 # target_branch (required) - The target branch
  65 + # target_project - The target project of the merge request defaults to the :id of the project
57 66 # assignee_id - Assignee user ID
58 67 # title (required) - Title of MR
59 68 #
... ... @@ -63,10 +72,20 @@ module API
63 72 post ":id/merge_requests" do
64 73 authorize! :write_merge_request, user_project
65 74 required_attributes! [:source_branch, :target_branch, :title]
66   -
67   - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
  75 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
68 76 merge_request = user_project.merge_requests.new(attrs)
69 77 merge_request.author = current_user
  78 + merge_request.source_project = user_project
  79 + target_project_id = attrs[:target_project_id]
  80 + if not_fork?(target_project_id, user_project)
  81 + merge_request.target_project = user_project
  82 + else
  83 + if target_matches_fork(target_project_id,user_project)
  84 + merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
  85 + else
  86 + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
  87 + end
  88 + end
70 89  
71 90 if merge_request.save
72 91 merge_request.reload_code
... ...
lib/gitlab/satellite/action.rb
... ... @@ -25,25 +25,31 @@ module Gitlab
25 25 end
26 26 end
27 27 rescue Errno::ENOMEM => ex
28   - Gitlab::GitLogger.error(ex.message)
29   - return false
  28 + return handle_exception(ex)
30 29 rescue Grit::Git::GitTimeout => ex
31   - Gitlab::GitLogger.error(ex.message)
32   - return false
  30 + return handle_exception(ex)
33 31 ensure
34 32 Gitlab::ShellEnv.reset_env
35 33 end
36 34  
37   - # * Clears the satellite
38   - # * Updates the satellite from Gitolite
  35 + # * Recreates the satellite
39 36 # * Sets up Git variables for the user
40 37 #
41 38 # Note: use this within #in_locked_and_timed_satellite
42 39 def prepare_satellite!(repo)
43 40 project.satellite.clear_and_update!
44 41  
45   - repo.git.config({}, "user.name", user.name)
46   - repo.git.config({}, "user.email", user.email)
  42 + repo.config['user.name'] = user.name
  43 + repo.config['user.email'] = user.email
  44 + end
  45 +
  46 + def default_options(options = {})
  47 + {raise: true, timeout: true}.merge(options)
  48 + end
  49 +
  50 + def handle_exception(exception)
  51 + Gitlab::GitLogger.error(exception.message)
  52 + false
47 53 end
48 54 end
49 55 end
... ...
lib/gitlab/satellite/merge_action.rb
... ... @@ -5,48 +5,120 @@ 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
42 42 end
43 43 rescue Grit::Git::CommandFailed => ex
44   - Gitlab::GitLogger.error(ex.message)
45   - false
  44 + handle_exception(ex)
46 45 end
47 46  
48   - private
  47 + # Get a raw diff of the source to the target
  48 + def diff_in_satellite
  49 + in_locked_and_timed_satellite do |merge_repo|
  50 + prepare_satellite!(merge_repo)
  51 + update_satellite_source_and_target!(merge_repo)
  52 +
  53 + if merge_request.for_fork?
  54 + diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  55 + else
  56 + diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
  57 + end
  58 +
  59 + return diff
  60 + end
  61 + rescue Grit::Git::CommandFailed => ex
  62 + handle_exception(ex)
  63 + end
  64 +
  65 + # Only show what is new in the source branch compared to the target branch, not the other way around.
  66 + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
  67 + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
  68 + def diffs_between_satellite
  69 + in_locked_and_timed_satellite do |merge_repo|
  70 + prepare_satellite!(merge_repo)
  71 + update_satellite_source_and_target!(merge_repo)
  72 + if merge_request.for_fork?
  73 + common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
  74 + #this method doesn't take default options
  75 + diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}")
  76 + else
  77 + raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
  78 + end
  79 + diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
  80 + return diffs
  81 + end
  82 + rescue Grit::Git::CommandFailed => ex
  83 + handle_exception(ex)
  84 + end
  85 +
  86 + # Get commit as an email patch
  87 + def format_patch
  88 + in_locked_and_timed_satellite do |merge_repo|
  89 + prepare_satellite!(merge_repo)
  90 + update_satellite_source_and_target!(merge_repo)
  91 +
  92 + if (merge_request.for_fork?)
  93 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
  94 + else
  95 + patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}..#{merge_request.source_branch}")
  96 + end
  97 +
  98 + return patch
  99 + end
  100 + rescue Grit::Git::CommandFailed => ex
  101 + handle_exception(ex)
  102 + end
49 103  
  104 + # Retrieve an array of commits between the source and the target
  105 + def commits_between
  106 + in_locked_and_timed_satellite do |merge_repo|
  107 + prepare_satellite!(merge_repo)
  108 + update_satellite_source_and_target!(merge_repo)
  109 + if (merge_request.for_fork?)
  110 + commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
  111 + else
  112 + raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
  113 + end
  114 + commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
  115 + return commits
  116 + end
  117 + rescue Grit::Git::CommandFailed => ex
  118 + handle_exception(ex)
  119 + end
  120 +
  121 + private
50 122 # Merges the source_branch into the target_branch in the satellite.
51 123 #
52 124 # Note: it will clear out the satellite before doing anything
... ... @@ -54,18 +126,35 @@ module Gitlab
54 126 # Returns false if the merge produced conflicts
55 127 # Returns true otherwise
56 128 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}")
  129 + update_satellite_source_and_target!(repo)
61 130  
62   - # merge the source branch from Gitolite into the satellite
  131 + # merge the source branch into the satellite
63 132 # will raise CommandFailed when merge fails
64   - repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch)
  133 + if merge_request.for_fork?
  134 + repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
  135 + else
  136 + repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
  137 + end
65 138 rescue Grit::Git::CommandFailed => ex
66   - Gitlab::GitLogger.error(ex.message)
67   - false
  139 + handle_exception(ex)
68 140 end
  141 +
  142 + # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
  143 + def update_satellite_source_and_target!(repo)
  144 + if merge_request.for_fork?
  145 + repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
  146 + repo.remote_fetch('source')
  147 + repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
  148 + else
  149 + # 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
  150 + # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared)
  151 + repo.git.checkout(default_options, "#{merge_request.source_branch}")
  152 + repo.git.checkout(default_options, "#{merge_request.target_branch}")
  153 + end
  154 + rescue Grit::Git::CommandFailed => ex
  155 + handle_exception(ex)
  156 + end
  157 +
69 158 end
70 159 end
71 160 end
... ...
lib/gitlab/satellite/satellite.rb
1 1 module Gitlab
2   - class SatelliteNotExistError < StandardError; end
  2 + class SatelliteNotExistError < StandardError; end
3 3  
4 4 module Satellite
5 5 class Satellite
... ... @@ -24,8 +24,11 @@ module Gitlab
24 24 def clear_and_update!
25 25 raise_no_satellite unless exists?
26 26  
  27 + File.exists? path
  28 + @repo = nil
27 29 clear_working_dir!
28 30 delete_heads!
  31 + remove_remotes!
29 32 update_from_source!
30 33 end
31 34  
... ... @@ -55,10 +58,11 @@ module Gitlab
55 58 raise_no_satellite unless exists?
56 59  
57 60 File.open(lock_file, "w+") do |f|
58   - f.flock(File::LOCK_EX)
59   -
60   - Dir.chdir(path) do
61   - return yield
  61 + begin
  62 + f.flock File::LOCK_EX
  63 + Dir.chdir(path) { return yield }
  64 + ensure
  65 + f.flock File::LOCK_UN
62 66 end
63 67 end
64 68 end
... ... @@ -100,20 +104,34 @@ module Gitlab
100 104 if heads.include? PARKING_BRANCH
101 105 repo.git.checkout({}, PARKING_BRANCH)
102 106 else
103   - repo.git.checkout({b: true}, PARKING_BRANCH)
  107 + repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
104 108 end
105 109  
106 110 # remove the parking branch from the list of heads ...
107 111 heads.delete(PARKING_BRANCH)
108 112 # ... and delete all others
109   - heads.each { |head| repo.git.branch({D: true}, head) }
  113 + heads.each { |head| repo.git.branch(default_options({D: true}), head) }
  114 + end
  115 +
  116 + # Deletes all remotes except origin
  117 + #
  118 + # This ensures we have no remote name clashes or issues updating branches when
  119 + # working with the satellite.
  120 + def remove_remotes!
  121 + remotes = repo.git.remote.split(' ')
  122 + remotes.delete('origin')
  123 + remotes.each { |name| repo.git.remote(default_options,'rm', name)}
110 124 end
111 125  
112 126 # Updates the satellite from Gitolite
113 127 #
114 128 # Note: this will only update remote branches (i.e. origin/*)
115 129 def update_from_source!
116   - repo.git.fetch({timeout: true}, :origin)
  130 + repo.git.fetch(default_options, :origin)
  131 + end
  132 +
  133 + def default_options(options = {})
  134 + {raise: true, timeout: true}.merge(options)
117 135 end
118 136  
119 137 # Create directory for storing
... ...
spec/contexts/filter_context_spec.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +require 'spec_helper'
  2 +
  3 +describe FilterContext do
  4 +
  5 + let(:user) { create :user }
  6 + let(:user2) { create :user }
  7 + let(:project1) { create(:project, creator_id: user.id) }
  8 + let(:project2) { create(:project, creator_id: user.id) }
  9 + let(:merge_request1) { create(:merge_request, author_id: user.id, source_project: project1, target_project: project2) }
  10 + let(:merge_request2) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project1) }
  11 + let(:merge_request3) { create(:merge_request, author_id: user.id, source_project: project2, target_project: project2) }
  12 + let(:merge_request4) { create(:merge_request, author_id: user2.id, source_project: project2, target_project: project2, target_branch:"notes_refactoring") }
  13 + let(:issue1) { create(:issue, assignee_id: user.id, project: project1) }
  14 + let(:issue2) { create(:issue, assignee_id: user.id, project: project2) }
  15 + let(:issue3) { create(:issue, assignee_id: user2.id, project: project2) }
  16 +
  17 + describe 'merge requests' do
  18 + before :each do
  19 + merge_request1
  20 + merge_request2
  21 + merge_request3
  22 + merge_request4
  23 + end
  24 +
  25 + it 'should by default filter properly' do
  26 + merge_requests = user.cared_merge_requests
  27 + params ={}
  28 + merge_requests = FilterContext.new(merge_requests, params).execute
  29 + merge_requests.size.should == 3
  30 + end
  31 +
  32 + it 'should apply blocks passed in on creation to the filters' do
  33 + merge_requests = user.cared_merge_requests
  34 + params = {:project_id => project1.id}
  35 + merge_requests = FilterContext.new(merge_requests, params).execute
  36 + merge_requests.size.should == 1
  37 + end
  38 + end
  39 +
  40 + describe 'issues' do
  41 + before :each do
  42 + issue1
  43 + issue2
  44 + issue3
  45 + end
  46 + it 'should by default filter projects properly' do
  47 + issues = user.assigned_issues
  48 + params = {}
  49 + issues = FilterContext.new(issues, params).execute
  50 + issues.size.should == 2
  51 + end
  52 + it 'should apply blocks passed in on creation to the filters' do
  53 + issues = user.assigned_issues
  54 + params = {:project_id => project1.id}
  55 + issues = FilterContext.new(issues, params).execute
  56 + issues.size.should == 1
  57 + end
  58 + end
  59 +end
... ...
spec/controllers/commit_controller_spec.rb
... ... @@ -7,7 +7,6 @@ describe Projects::CommitController do
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
... ... @@ -29,8 +29,19 @@ FactoryGirl.define do
29 29 sequence(:name) { |n| "project#{n}" }
30 30 path { name.downcase.gsub(/\s/, '_') }
31 31 creator
  32 +
  33 + trait :source do
  34 + sequence(:name) { |n| "source project#{n}" }
  35 + end
  36 + trait :target do
  37 + sequence(:name) { |n| "target project#{n}" }
  38 + end
  39 +
  40 + factory :source_project, traits: [:source]
  41 + factory :target_project, traits: [:target]
32 42 end
33 43  
  44 +
34 45 factory :redmine_project, parent: :project do
35 46 issues_tracker { "redmine" }
36 47 issues_tracker_id { "project_name_in_redmine" }
... ... @@ -39,11 +50,20 @@ FactoryGirl.define do
39 50 factory :project_with_code, parent: :project do
40 51 path { 'gitlabhq' }
41 52  
  53 + trait :source_path do
  54 + path { 'source_gitlabhq' }
  55 + end
  56 +
  57 + trait :target_path do
  58 + path { 'target_gitlabhq' }
  59 + end
  60 +
  61 + factory :source_project_with_code, traits: [:source, :source_path]
  62 + factory :target_project_with_code, traits: [:target, :target_path]
  63 +
42 64 after :create do |project|
43   - repos_path = Rails.root.join('tmp', 'test-git-base-path')
44   - seed_repo = Rails.root.join('tmp', 'repositories', 'gitlabhq')
45   - target_repo = File.join(repos_path, project.path_with_namespace + '.git')
46   - system("ln -s #{seed_repo} #{target_repo}")
  65 + TestEnv.clear_repo_dir(project.namespace, project.path)
  66 + TestEnv.create_repo(project.namespace, project.path)
47 67 end
48 68 end
49 69  
... ... @@ -86,7 +106,8 @@ FactoryGirl.define do
86 106 factory :merge_request do
87 107 title
88 108 author
89   - project factory: :project_with_code
  109 + source_project factory: :source_project_with_code
  110 + target_project factory: :target_project_with_code
90 111 source_branch "master"
91 112 target_branch "stable"
92 113  
... ... @@ -96,13 +117,13 @@ FactoryGirl.define do
96 117 source_branch "stable" # pretend bcf03b5d
97 118 st_commits do
98 119 [
99   - project.repository.commit('bcf03b5d').to_hash,
100   - project.repository.commit('bcf03b5d~1').to_hash,
101   - project.repository.commit('bcf03b5d~2').to_hash
  120 + source_project.repository.commit('bcf03b5d').to_hash,
  121 + source_project.repository.commit('bcf03b5d~1').to_hash,
  122 + source_project.repository.commit('bcf03b5d~2').to_hash
102 123 ]
103 124 end
104 125 st_diffs do
105   - project.repo.diff("bcf03b5d~3", "bcf03b5d")
  126 + source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
106 127 end
107 128 end
108 129  
... ... @@ -133,7 +154,7 @@ FactoryGirl.define do
133 154  
134 155 trait :on_commit do
135 156 project factory: :project_with_code
136   - commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
  157 + commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
137 158 noteable_type "Commit"
138 159 end
139 160  
... ... @@ -143,12 +164,12 @@ FactoryGirl.define do
143 164  
144 165 trait :on_merge_request do
145 166 project factory: :project_with_code
146   - noteable_id 1
  167 + noteable_id 1
147 168 noteable_type "MergeRequest"
148 169 end
149 170  
150 171 trait :on_issue do
151   - noteable_id 1
  172 + noteable_id 1
152 173 noteable_type "Issue"
153 174 end
154 175  
... ...
spec/factories_spec.rb
1 1 require 'spec_helper'
2 2  
3 3 INVALID_FACTORIES = [
4   - :key_with_a_space_in_the_middle,
5   - :invalid_key,
  4 + :key_with_a_space_in_the_middle,
  5 + :invalid_key,
6 6 ]
7 7  
8 8 FactoryGirl.factories.map(&:name).each do |factory_name|
... ...
spec/features/gitlab_flavored_markdown_spec.rb
... ... @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 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, 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
... ... @@ -183,6 +184,9 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
183 184 end
184 185  
185 186 describe "with muliple note forms" do
  187 + let!(:project) { create(:source_project_with_code) }
  188 + let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
  189 +
186 190 before do
187 191 find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
188 192 find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click
... ... @@ -205,13 +209,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
205 209  
206 210 # TODO: fix
207 211 #it 'should check if previews were rendered separately' do
208   - #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
209   - #should have_css(".js-note-preview", text: "One comment on line 185")
210   - #end
  212 + #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
  213 + #should have_css(".js-note-preview", text: "One comment on line 185")
  214 + #end
211 215  
212   - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
213   - #should have_css(".js-note-preview", text: "Another comment on line 17")
214   - #end
  216 + #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
  217 + #should have_css(".js-note-preview", text: "Another comment on line 17")
  218 + #end
215 219 #end
216 220 end
217 221  
... ... @@ -238,39 +242,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
238 242  
239 243 # TODO: fix
240 244 #it "should remove last note of a discussion" do
241   - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do
242   - #find(".js-note-delete").click
243   - #end
244   -
245   - #should_not have_css(".note_holder")
  245 + # within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do
  246 + # find(".js-note-delete").click
  247 + # end
  248 + # should_not have_css(".note_holder")
246 249 #end
247 250 end
248 251 end
249 252  
250 253 # TODO: fix
251 254 #describe "when replying to a note" do
252   - #before do
253   - ## create first note
254   - #find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click
  255 + #before do
  256 + ## create first note
  257 + # find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click
255 258  
256   - #within(".js-temp-notes-holder") do
257   - #fill_in "note[note]", with: "One comment on line 184"
258   - #click_button("Add Comment")
259   - #end
  259 + # within(".js-temp-notes-holder") do
  260 + # fill_in "note[note]", with: "One comment on line 184"
  261 + # click_button("Add Comment")
  262 + #end
260 263  
261   - #within(".js-temp-notes-holder") do
262   - #find(".js-discussion-reply-button").click
263   - #fill_in "note[note]", with: "An additional comment in reply"
264   - #click_button("Add Comment")
265   - #end
266   - #end
267   -
268   - #it 'should be inserted and form removed from reply' do
269   - #should have_content("An additional comment in reply")
270   - #within(".notes_holder") { should have_css(".note", count: 2) }
271   - #within(".notes_holder") { should have_no_css("form") }
272   - #within(".notes_holder") { should have_link("Reply") }
273   - #end
  264 + # within(".js-temp-notes-holder") do
  265 + # find(".js-discussion-reply-button").click
  266 + # fill_in "note[note]", with: "An additional comment in reply"
  267 + # click_button("Add Comment")
  268 + # end
  269 + #end
  270 +
  271 + #it 'should be inserted and form removed from reply' do
  272 + # should have_content("An additional comment in reply")
  273 + # within(".notes_holder") { should have_css(".note", count: 2) }
  274 + # within(".notes_holder") { should have_no_css("form") }
  275 + # within(".notes_holder") { should have_link("Reply") }
  276 + # end
274 277 #end
275 278 end
276 279  
... ...
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,10 +14,10 @@ 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
... ... @@ -108,7 +108,7 @@ describe &quot;Application access&quot; do
108 108 describe "GET /project_code/blob" do
109 109 before do
110 110 commit = project.repository.commit
111   - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
  111 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
112 112 @blob_path = project_blob_path(project, File.join(commit.id, path))
113 113 end
114 114  
... ... @@ -232,13 +232,13 @@ describe &quot;Application access&quot; do
232 232  
233 233  
234 234 describe "PublicProject" do
235   - let(:project) { create(:project_with_code) }
  235 + let(:project) { create(:project_with_code) }
236 236  
237   - let(:master) { create(:user) }
238   - let(:guest) { create(:user) }
  237 + let(:master) { create(:user) }
  238 + let(:guest) { create(:user) }
239 239 let(:reporter) { create(:user) }
240 240  
241   - let(:admin) { create(:user) }
  241 + let(:admin) { create(:user) }
242 242  
243 243 before do
244 244 # public project
... ... @@ -339,7 +339,7 @@ describe &quot;Application access&quot; do
339 339 describe "GET /project_code/blob" do
340 340 before do
341 341 commit = project.repository.commit
342   - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
  342 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
343 343 @blob_path = project_blob_path(project, File.join(commit.id, path))
344 344 end
345 345  
... ...
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,116 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::Action' do
  4 + let(:project) { create(:project_with_code) }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe '#prepare_satellite!' do
  8 +
  9 + it 'create a repository with a parking branch and one remote: origin' do
  10 + repo = project.satellite.repo
  11 +
  12 + #now lets dirty it up
  13 +
  14 + starting_remote_count = repo.git.list_remotes.size
  15 + starting_remote_count.should >= 1
  16 + #kind of hookey way to add a second remote
  17 + origin_uri = repo.git.remote({v: true}).split(" ")[1]
  18 + begin
  19 + repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
  20 + repo.git.branch({raise: true}, 'a-new-branch')
  21 +
  22 + repo.heads.size.should > (starting_remote_count)
  23 + repo.git.remote().split(" ").size.should > (starting_remote_count)
  24 + rescue
  25 + end
  26 +
  27 + repo.git.config({}, "user.name", "#{user.name} -- foo")
  28 + repo.git.config({}, "user.email", "#{user.email} -- foo")
  29 + repo.config['user.name'].should =="#{user.name} -- foo"
  30 + repo.config['user.email'].should =="#{user.email} -- foo"
  31 +
  32 +
  33 + #These must happen in the context of the satellite directory...
  34 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  35 + project.satellite.lock {
  36 + #Now clean it up, use send to get around prepare_satellite! being protected
  37 + satellite_action.send(:prepare_satellite!, repo)
  38 + }
  39 +
  40 + #verify it's clean
  41 + heads = repo.heads.map(&:name)
  42 + heads.size.should == 1
  43 + heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
  44 + remotes = repo.git.remote().split(' ')
  45 + remotes.size.should == 1
  46 + remotes.include?('origin').should == true
  47 + repo.config['user.name'].should ==user.name
  48 + repo.config['user.email'].should ==user.email
  49 + end
  50 + end
  51 +
  52 + describe '#in_locked_and_timed_satellite' do
  53 +
  54 + it 'should make use of a lockfile' do
  55 + repo = project.satellite.repo
  56 + called = false
  57 +
  58 + #set assumptions
  59 + File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file
  60 +
  61 + File.exists?(project.satellite.lock_file).should be_false
  62 +
  63 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  64 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  65 + repo.should == sat_repo
  66 + (File.exists? project.satellite.lock_file).should be_true
  67 + called = true
  68 + end
  69 +
  70 + called.should be_true
  71 +
  72 + end
  73 +
  74 + it 'should be able to use the satellite after locking' do
  75 + repo = project.satellite.repo
  76 + called = false
  77 +
  78 + # Set base assumptions
  79 + if File.exists? project.satellite.lock_file
  80 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  81 + end
  82 +
  83 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  84 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  85 + called = true
  86 + repo.should == sat_repo
  87 + (File.exists? project.satellite.lock_file).should be_true
  88 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true
  89 + end
  90 +
  91 + called.should be_true
  92 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  93 +
  94 + end
  95 +
  96 + class FileLockStatusChecker < File
  97 + def flocked? &block
  98 + status = flock LOCK_EX|LOCK_NB
  99 + case status
  100 + when false
  101 + return true
  102 + when 0
  103 + begin
  104 + block ? block.call : false
  105 + ensure
  106 + flock LOCK_UN
  107 + end
  108 + else
  109 + raise SystemCallError, status
  110 + end
  111 + end
  112 + end
  113 +
  114 + end
  115 +end
  116 +
... ...
spec/lib/gitlab/satellite/merge_action_spec.rb 0 → 100644
... ... @@ -0,0 +1,148 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::MergeAction' do
  4 + before(:each) do
  5 +# TestEnv.init(mailer: false, init_repos: true, repos: true)
  6 + @master = ['master', 'bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a']
  7 + @one_after_stable = ['stable', '6ea87c47f0f8a24ae031c3fff17bc913889ecd00'] #this commit sha is one after stable
  8 + @wiki_branch = ['wiki', '635d3e09b72232b6e92a38de6cc184147e5bcb41'] #this is the commit sha where the wiki branch goes off from master
  9 + @conflicting_metior = ['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f'] #this branch conflicts with the wiki branch
  10 +
  11 + #these commits are quite close together, itended to make string diffs/format patches small
  12 + @close_commit1 = ['2_3_notes_fix', '8470d70da67355c9c009e4401746b1d5410af2e3']
  13 + @close_commit2 = ['scss_refactoring', 'f0f14c8eaba69ebddd766498a9d0b0e79becd633']
  14 + end
  15 +
  16 + let(:project) { create(:project_with_code) }
  17 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
  18 + let(:merge_request_fork) { create(:merge_request) }
  19 + describe '#commits_between' do
  20 + def verify_commits(commits, first_commit_sha, last_commit_sha)
  21 + commits.each { |commit| commit.class.should == Gitlab::Git::Commit }
  22 + commits.first.id.should == first_commit_sha
  23 + commits.last.id.should == last_commit_sha
  24 + end
  25 +
  26 + context 'on fork' do
  27 + it 'should get proper commits between' do
  28 + merge_request_fork.target_branch = @one_after_stable[0]
  29 + merge_request_fork.source_branch = @master[0]
  30 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  31 + verify_commits(commits, @one_after_stable[1], @master[1])
  32 +
  33 + merge_request_fork.target_branch = @wiki_branch[0]
  34 + merge_request_fork.source_branch = @master[0]
  35 + commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
  36 + verify_commits(commits, @wiki_branch[1], @master[1])
  37 + end
  38 + end
  39 +
  40 + context 'between branches' do
  41 + it 'should raise exception -- not expected to be used by non forks' do
  42 + merge_request.target_branch = @one_after_stable[0]
  43 + merge_request.source_branch = @master[0]
  44 + expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
  45 +
  46 + merge_request.target_branch = @wiki_branch[0]
  47 + merge_request.source_branch = @master[0]
  48 + expect {Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between}.to raise_error
  49 + end
  50 + end
  51 + end
  52 +
  53 + describe '#format_patch' do
  54 + let(:target_commit) {['artiom-config-examples','9edbac5ac88ffa1ec9dad0097226b51e29ebc9ac']}
  55 + let(:source_commit) {['metior', '313d96e42b313a0af5ab50fa233bf43e27118b3f']}
  56 +
  57 + def verify_content(patch)
  58 + (patch.include? source_commit[1]).should be_true
  59 + (patch.include? '635d3e09b72232b6e92a38de6cc184147e5bcb41').should be_true
  60 + (patch.include? '2bb2dee057327c81978ed0aa99904bd7ff5e6105').should be_true
  61 + (patch.include? '2e83de1924ad3429b812d17498b009a8b924795d').should be_true
  62 + (patch.include? 'ee45a49c57a362305431cbf004e4590b713c910e').should be_true
  63 + (patch.include? 'a6870dd08f8f274d9a6b899f638c0c26fefaa690').should be_true
  64 +
  65 + (patch.include? 'e74fae147abc7d2ffbf93d363dbbe45b87751f6f').should be_false
  66 + (patch.include? '86f76b11c670425bbab465087f25172378d76147').should be_false
  67 + end
  68 +
  69 + context 'on fork' do
  70 + it 'should build a format patch' do
  71 + merge_request_fork.target_branch = target_commit[0]
  72 + merge_request_fork.source_branch = source_commit[0]
  73 + patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
  74 + verify_content(patch)
  75 + end
  76 + end
  77 +
  78 + context 'between branches' do
  79 + it 'should build a format patch' do
  80 + merge_request.target_branch = target_commit[0]
  81 + merge_request.source_branch = source_commit[0]
  82 + patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request).format_patch
  83 + verify_content(patch)
  84 + end
  85 + end
  86 + end
  87 +
  88 + describe '#diffs_between_satellite tested against diff_in_satellite' do
  89 +
  90 + def is_a_matching_diff(diff, diffs)
  91 + diff_count = diff.scan('diff --git').size
  92 + diff_count.should >= 1
  93 + diffs.size.should == diff_count
  94 + diffs.each do |a_diff|
  95 + a_diff.class.should == Gitlab::Git::Diff
  96 + (diff.include? a_diff.diff).should be_true
  97 + end
  98 + end
  99 +
  100 + context 'on fork' do
  101 + it 'should get proper diffs' do
  102 + merge_request_fork.target_branch = @close_commit1[0]
  103 + merge_request_fork.source_branch = @master[0]
  104 + diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
  105 +
  106 + merge_request_fork.target_branch = @close_commit1[0]
  107 + merge_request_fork.source_branch = @master[0]
  108 + diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diff_in_satellite
  109 +
  110 + is_a_matching_diff(diff, diffs)
  111 + end
  112 + end
  113 +
  114 + context 'between branches' do
  115 + it 'should get proper diffs' do
  116 + merge_request.target_branch = @close_commit1[0]
  117 + merge_request.source_branch = @master[0]
  118 + expect{Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite}.to raise_error
  119 + end
  120 + end
  121 + end
  122 +
  123 + describe '#can_be_merged?' do
  124 + context 'on fork' do
  125 + it 'return true or false depending on if something is mergable' do
  126 + merge_request_fork.target_branch = @one_after_stable[0]
  127 + merge_request_fork.source_branch = @master[0]
  128 + Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_true
  129 +
  130 + merge_request_fork.target_branch = @conflicting_metior[0]
  131 + merge_request_fork.source_branch = @wiki_branch[0]
  132 + Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?.should be_false
  133 + end
  134 + end
  135 +
  136 + context 'between branches' do
  137 + it 'return true or false depending on if something is mergable' do
  138 + merge_request.target_branch = @one_after_stable[0]
  139 + merge_request.source_branch = @master[0]
  140 + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_true
  141 +
  142 + merge_request.target_branch = @conflicting_metior[0]
  143 + merge_request.source_branch = @wiki_branch[0]
  144 + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?.should be_false
  145 + end
  146 + end
  147 + end
  148 +end
0 149 \ 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,38 @@ 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 + describe '#allow_source_branch_removal?' do
  87 + it 'should not allow removal when mr is a fork' do
  88 +
  89 + subject.disallow_source_branch_removal?.should be_true
  90 + end
  91 + it 'should not allow removal when the mr is not a fork, but the source branch is the root reference' do
  92 + subject.target_project = subject.source_project
  93 + subject.source_branch = subject.source_project.repository.root_ref
  94 + subject.disallow_source_branch_removal?.should be_true
  95 + end
  96 +
  97 + it 'should not disallow removal when the mr is not a fork, and but source branch is not the root reference' do
  98 + subject.target_project = subject.source_project
  99 + subject.source_branch = "Something Different #{subject.source_project.repository.root_ref}"
  100 + subject.for_fork?.should be_false
  101 + subject.disallow_source_branch_removal?.should be_false
  102 + end
  103 + end
  104 +
74 105 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,41 @@ 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 + describe "Merge Request created" do
  94 + def self.it_should_be_valid_event
  95 + it { @event.should_not be_nil }
  96 + it { @event.should_not be_nil }
  97 + it { @event.project.should == project }
  98 + it { @event.project.should == project }
  99 + end
  100 +
  101 + let(:project) { create(:project) }
  102 + before do
  103 + TestEnv.enable_observers
  104 + @merge_request = create(:merge_request, source_project: project, target_project: project)
  105 + @event = Event.last
  106 + end
  107 +
  108 + after do
  109 + TestEnv.disable_observers
  110 + end
  111 +
  112 + it_should_be_valid_event
  113 + it { @event.action.should == Event::CREATED }
  114 + it { @event.target.should == @merge_request }
  115 + end
  116 +
90 117 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,104 @@ 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 + let!(:unrelated_project) { create(:target_project_with_code, namespace: user2.namespace, creator_id: user2.id) }
  83 +
  84 + before :each do |each|
  85 + fork_project.team << [user2, :reporters]
  86 + forked_project_link.forked_from_project = project
  87 + forked_project_link.forked_to_project = fork_project
  88 + forked_project_link.save!
  89 + end
  90 +
  91 + it "should return merge_request" do
  92 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  93 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id
  94 + response.status.should == 201
  95 + json_response['title'].should == 'Test merge_request'
  96 + end
  97 +
  98 + it "should not return 422 when source_branch equals target_branch" do
  99 + project.id.should_not == fork_project.id
  100 + fork_project.forked?.should be_true
  101 + fork_project.forked_from_project.should == project
  102 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  103 + title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
  104 + response.status.should == 201
  105 + json_response['title'].should == 'Test merge_request'
  106 + end
  107 +
  108 + it "should return 400 when source_branch is missing" do
  109 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  110 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  111 + response.status.should == 400
  112 + end
  113 +
  114 + it "should return 400 when target_branch is missing" do
  115 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  116 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  117 + response.status.should == 400
  118 + end
  119 +
  120 + it "should return 400 when title is missing" do
  121 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  122 + target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
  123 + response.status.should == 400
  124 + end
  125 +
  126 + it "should return 400 when target_branch is specified and not a forked project" do
  127 + post api("/projects/#{project.id}/merge_requests", user),
  128 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
  129 + response.status.should == 400
  130 + end
  131 +
  132 + it "should return 400 when target_branch is specified and for a different fork" do
  133 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  134 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
  135 + response.status.should == 400
  136 + end
  137 +
  138 + it "should return 201 when target_branch is specified and for the same project" do
  139 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  140 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
  141 + response.status.should == 201
  142 + end
72 143 end
73 144 end
74 145  
... ... @@ -97,14 +168,14 @@ describe API::API do
97 168  
98 169 it "should return 422 when source_branch and target_branch are renamed the same" do
99 170 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
100   - source_branch: "master", target_branch: "master"
  171 + source_branch: "master", target_branch: "master"
101 172 response.status.should == 422
102 173 end
103 174  
104 175 it "should return merge_request with renamed target_branch" do
105   - put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test"
  176 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
106 177 response.status.should == 200
107   - json_response['target_branch'].should == 'test'
  178 + json_response['target_branch'].should == 'wiki'
108 179 end
109 180 end
110 181  
... ...
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) }
... ...