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.

@@ -129,7 +129,7 @@ group :assets do @@ -129,7 +129,7 @@ group :assets do
129 gem 'turbolinks' 129 gem 'turbolinks'
130 gem 'jquery-turbolinks' 130 gem 'jquery-turbolinks'
131 131
132 - gem 'chosen-rails', "0.9.8" 132 + gem 'chosen-rails', "1.0.0"
133 gem 'select2-rails' 133 gem 'select2-rails'
134 gem 'jquery-atwho-rails', "0.3.0" 134 gem 'jquery-atwho-rails', "0.3.0"
135 gem "jquery-rails", "2.1.3" 135 gem "jquery-rails", "2.1.3"
@@ -72,9 +72,12 @@ GEM @@ -72,9 +72,12 @@ GEM
72 charlock_holmes (0.6.9.4) 72 charlock_holmes (0.6.9.4)
73 childprocess (0.3.9) 73 childprocess (0.3.9)
74 ffi (~> 1.0, >= 1.0.11) 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 code_analyzer (0.3.2) 81 code_analyzer (0.3.2)
79 sexp_processor 82 sexp_processor
80 coderay (1.0.9) 83 coderay (1.0.9)
@@ -87,6 +90,12 @@ GEM @@ -87,6 +90,12 @@ GEM
87 coffee-script-source (1.6.2) 90 coffee-script-source (1.6.2)
88 colored (1.2) 91 colored (1.2)
89 colorize (0.5.8) 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 connection_pool (1.1.0) 99 connection_pool (1.1.0)
91 coveralls (0.6.7) 100 coveralls (0.6.7)
92 colorize 101 colorize
@@ -149,6 +158,7 @@ GEM @@ -149,6 +158,7 @@ GEM
149 dotenv (>= 0.7) 158 dotenv (>= 0.7)
150 thor (>= 0.13.6) 159 thor (>= 0.13.6)
151 formatador (0.2.4) 160 formatador (0.2.4)
  161 + fssm (0.2.10)
152 gemoji (1.2.1) 162 gemoji (1.2.1)
153 gherkin-ruby (0.3.0) 163 gherkin-ruby (0.3.0)
154 github-linguist (2.3.4) 164 github-linguist (2.3.4)
@@ -548,7 +558,7 @@ DEPENDENCIES @@ -548,7 +558,7 @@ DEPENDENCIES
548 bootstrap-sass 558 bootstrap-sass
549 capybara 559 capybara
550 carrierwave 560 carrierwave
551 - chosen-rails (= 0.9.8) 561 + chosen-rails (= 1.0.0)
552 coffee-rails 562 coffee-rails
553 colored 563 colored
554 coveralls 564 coveralls
app/assets/stylesheets/gitlab_bootstrap/mixins.scss
@@ -17,6 +17,10 @@ @@ -17,6 +17,10 @@
17 border-radius: $radius; 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 @mixin linear-gradient($from, $to) { 24 @mixin linear-gradient($from, $to) {
21 background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to)); 25 background-image: -webkit-gradient(linear, 0 0, 0 100%, from($from), to($to));
22 background-image: -webkit-linear-gradient($from, $to); 26 background-image: -webkit-linear-gradient($from, $to);
app/assets/stylesheets/sections/merge_requests.scss
@@ -84,14 +84,23 @@ @@ -84,14 +84,23 @@
84 84
85 .label-branch { 85 .label-branch {
86 @include border-radius(4px); 86 @include border-radius(4px);
87 - padding: 2px 4px; 87 + padding: 3px 4px;
88 border: none; 88 border: none;
89 font-size: 14px; 89 font-size: 14px;
90 background: #474D57; 90 background: #474D57;
91 color: #fff; 91 color: #fff;
92 font-family: $monospace_font; 92 font-family: $monospace_font;
93 - text-shadow: 0 1px 1px #111;  
94 font-weight: normal; 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 .mr-list { 106 .mr-list {
app/assets/stylesheets/selects.scss
1 /* CHZN reset few styles */ 1 /* CHZN reset few styles */
2 -.chzn-container-single .chzn-single { 2 +.chosen-container-single .chosen-single {
3 background: #FFF; 3 background: #FFF;
4 border: 1px solid #bbb; 4 border: 1px solid #bbb;
5 box-shadow: none; 5 box-shadow: none;
6 } 6 }
7 -.chzn-container-active .chzn-single { 7 +.chosen-container-active .chosen-single {
8 background: #fff; 8 background: #fff;
9 } 9 }
10 10
@@ -41,38 +41,38 @@ @@ -41,38 +41,38 @@
41 width: 120px; 41 width: 120px;
42 } 42 }
43 43
44 -.project-refs-form .chzn-container { 44 +.project-refs-form .chosen-container {
45 position: relative; 45 position: relative;
46 top: 0; 46 top: 0;
47 left: 0; 47 left: 0;
48 margin-right: 10px; 48 margin-right: 10px;
49 49
50 - .chzn-drop { 50 + .chosen-drop {
51 min-width: 400px; 51 min-width: 400px;
52 - .chzn-results { 52 + .chosen-results {
53 max-height: 300px; 53 max-height: 300px;
54 } 54 }
55 - .chzn-search input { 55 + .chosen-search input {
56 min-width: 365px; 56 min-width: 365px;
57 } 57 }
58 } 58 }
59 } 59 }
60 60
61 /** Fix for Search Dropdown Border **/ 61 /** Fix for Search Dropdown Border **/
62 -.chzn-container {  
63 - .chzn-search { 62 +.chosen-container {
  63 + .chosen-search {
64 input:focus { 64 input:focus {
65 @include box-shadow(none); 65 @include box-shadow(none);
66 } 66 }
67 } 67 }
68 68
69 - .chzn-drop { 69 + .chosen-drop {
70 margin: 7px 0; 70 margin: 7px 0;
71 min-width: 200px; 71 min-width: 200px;
72 border: 1px solid #bbb; 72 border: 1px solid #bbb;
73 @include border-radius(0); 73 @include border-radius(0);
74 74
75 - .chzn-results { 75 + .chosen-results {
76 margin-top: 5px; 76 margin-top: 5px;
77 max-height: 300px; 77 max-height: 300px;
78 78
@@ -95,7 +95,7 @@ @@ -95,7 +95,7 @@
95 } 95 }
96 } 96 }
97 97
98 - .chzn-search { 98 + .chosen-search {
99 @include bg-gray-gradient; 99 @include bg-gray-gradient;
100 input { 100 input {
101 min-width: 165px; 101 min-width: 165px;
@@ -103,18 +103,19 @@ @@ -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,3 +141,11 @@
140 padding: 7px; 141 padding: 7px;
141 color: #666; 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,7 +12,7 @@ class FilterContext
12 12
13 def apply_filter items 13 def apply_filter items
14 if params[:project_id].present? 14 if params[:project_id].present?
15 - items = items.where(project_id: params[:project_id]) 15 + items = items.of_projects(params[:project_id])
16 end 16 end
17 17
18 if params[:search].present? 18 if params[:search].present?
app/contexts/merge_requests_load_context.rb
@@ -14,7 +14,7 @@ class MergeRequestsLoadContext &lt; BaseContext @@ -14,7 +14,7 @@ class MergeRequestsLoadContext &lt; BaseContext
14 end 14 end
15 15
16 merge_requests = merge_requests.page(params[:page]).per(20) 16 merge_requests = merge_requests.page(params[:page]).per(20)
17 - merge_requests = merge_requests.includes(:author, :project).order("created_at desc") 17 + merge_requests = merge_requests.includes(:author, :source_project, :target_project).order("created_at desc")
18 18
19 # Filter by specific assignee_id (or lack thereof)? 19 # Filter by specific assignee_id (or lack thereof)?
20 if params[:assignee_id].present? 20 if params[:assignee_id].present?
app/contexts/search_context.rb
@@ -19,7 +19,7 @@ class SearchContext @@ -19,7 +19,7 @@ class SearchContext
19 if params[:search_code].present? 19 if params[:search_code].present?
20 result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? 20 result[:blobs] = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo?
21 else 21 else
22 - result[:merge_requests] = MergeRequest.where(project_id: project_ids).search(query).limit(20) 22 + result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).limit(20)
23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(20) 23 result[:issues] = Issue.where(project_id: project_ids).search(query).limit(20)
24 result[:wiki_pages] = [] 24 result[:wiki_pages] = []
25 end 25 end
app/controllers/projects/merge_requests_controller.rb
@@ -24,8 +24,8 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -24,8 +24,8 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
24 format.html 24 format.html
25 format.js 25 format.js
26 26
27 - format.diff { render text: @merge_request.to_diff }  
28 - format.patch { render text: @merge_request.to_patch } 27 + format.diff { render text: @merge_request.to_diff(current_user) }
  28 + format.patch { render text: @merge_request.to_patch(current_user) }
29 end 29 end
30 end 30 end
31 31
@@ -33,27 +33,37 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -33,27 +33,37 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
33 @commit = @merge_request.last_commit 33 @commit = @merge_request.last_commit
34 34
35 @comments_allowed = @reply_allowed = true 35 @comments_allowed = @reply_allowed = true
36 - @comments_target = { noteable_type: 'MergeRequest',  
37 - noteable_id: @merge_request.id } 36 + @comments_target = {noteable_type: 'MergeRequest',
  37 + noteable_id: @merge_request.id}
38 @line_notes = @merge_request.notes.where("line_code is not null") 38 @line_notes = @merge_request.notes.where("line_code is not null")
39 end 39 end
40 40
41 def new 41 def new
42 - @merge_request = @project.merge_requests.new(params[:merge_request]) 42 + @merge_request = MergeRequest.new(params[:merge_request])
  43 + @merge_request.source_project = @project unless @merge_request.source_project
  44 + @merge_request.target_project = @project unless @merge_request.target_project
  45 + @target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
  46 + @source_project = @merge_request.source_project
  47 + @merge_request
43 end 48 end
44 49
45 def edit 50 def edit
  51 + @source_project = @merge_request.source_project
  52 + @target_project = @merge_request.target_project
  53 + @target_branches = @merge_request.target_project.repository.branch_names
46 end 54 end
47 55
48 def create 56 def create
49 - @merge_request = @project.merge_requests.new(params[:merge_request]) 57 + @merge_request = MergeRequest.new(params[:merge_request])
50 @merge_request.author = current_user 58 @merge_request.author = current_user
51 - 59 + @target_branches ||= []
52 if @merge_request.save 60 if @merge_request.save
53 @merge_request.reload_code 61 @merge_request.reload_code
54 - redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.' 62 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
55 else 63 else
56 - render "new" 64 + @source_project = @merge_request.source_project
  65 + @target_project = @merge_request.target_project
  66 + render action: "new"
57 end 67 end
58 end 68 end
59 69
@@ -61,7 +71,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -61,7 +71,7 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
61 if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id)) 71 if @merge_request.update_attributes(params[:merge_request].merge(author_id_of_changes: current_user.id))
62 @merge_request.reload_code 72 @merge_request.reload_code
63 @merge_request.mark_as_unchecked 73 @merge_request.mark_as_unchecked
64 - redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.' 74 + redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
65 else 75 else
66 render "edit" 76 render "edit"
67 end 77 end
@@ -89,22 +99,35 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -89,22 +99,35 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
89 end 99 end
90 100
91 def branch_from 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 end 105 end
94 106
95 def branch_to 107 def branch_to
96 - @commit = @repository.commit(params[:ref]) 108 + @target_project = selected_target_project
  109 + @commit = @target_project.repository.commit(params[:ref]) 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 end 116 end
98 117
99 def ci_status 118 def ci_status
100 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha) 119 status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
101 - response = { status: status } 120 + response = {status: status}
102 121
103 render json: response 122 render json: response
104 end 123 end
105 124
106 protected 125 protected
107 126
  127 + def selected_target_project
  128 + ((@project.id.to_s == params[:target_project_id]) || @project.forked_project_link.nil?) ? @project : @project.forked_project_link.forked_from_project
  129 + end
  130 +
108 def merge_request 131 def merge_request
109 @merge_request ||= @project.merge_requests.find(params[:id]) 132 @merge_request ||= @project.merge_requests.find(params[:id])
110 end 133 end
@@ -123,11 +146,11 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController @@ -123,11 +146,11 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
123 146
124 def validates_merge_request 147 def validates_merge_request
125 # Show git not found page if target branch doesn't exist 148 # Show git not found page if target branch doesn't exist
126 - return invalid_mr unless @project.repository.branch_names.include?(@merge_request.target_branch) 149 + return invalid_mr unless @merge_request.target_project.repository.branch_names.include?(@merge_request.target_branch)
127 150
128 # Show git not found page if source branch doesn't exist 151 # Show git not found page if source branch doesn't exist
129 # and there is no saved commits between source & target branch 152 # and there is no saved commits between source & target branch
130 - return invalid_mr if !@project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank? 153 + return invalid_mr if !@merge_request.source_project.repository.branch_names.include?(@merge_request.source_branch) && @merge_request.commits.blank?
131 end 154 end
132 155
133 def define_show_vars 156 def define_show_vars
app/helpers/commits_helper.rb
@@ -56,8 +56,8 @@ module CommitsHelper @@ -56,8 +56,8 @@ module CommitsHelper
56 end 56 end
57 end 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 end 61 end
62 62
63 def diff_line_content(line) 63 def diff_line_content(line)
app/helpers/merge_requests_helper.rb
@@ -2,14 +2,27 @@ module MergeRequestsHelper @@ -2,14 +2,27 @@ module MergeRequestsHelper
2 def new_mr_path_from_push_event(event) 2 def new_mr_path_from_push_event(event)
3 new_project_merge_request_path( 3 new_project_merge_request_path(
4 event.project, 4 event.project,
5 - merge_request: {  
6 - source_branch: event.branch_name,  
7 - target_branch: event.project.repository.root_ref,  
8 - title: event.branch_name.titleize  
9 - } 5 + new_mr_from_push_event(event, event.project)
10 ) 6 )
11 end 7 end
12 8
  9 + def new_mr_path_for_fork_from_push_event(event)
  10 + new_project_merge_request_path(
  11 + event.project,
  12 + new_mr_from_push_event(event, event.project.forked_from_project)
  13 + )
  14 + end
  15 +
  16 + def new_mr_from_push_event(event, target_project)
  17 + return :merge_request => {
  18 + source_project_id: event.project.id,
  19 + target_project_id: target_project.id,
  20 + source_branch: event.branch_name,
  21 + target_branch: target_project.repository.root_ref,
  22 + title: event.branch_name.titleize
  23 + }
  24 + end
  25 +
13 def mr_css_classes mr 26 def mr_css_classes mr
14 classes = "merge-request" 27 classes = "merge-request"
15 classes << " closed" if mr.closed? 28 classes << " closed" if mr.closed?
@@ -18,6 +31,14 @@ module MergeRequestsHelper @@ -18,6 +31,14 @@ module MergeRequestsHelper
18 end 31 end
19 32
20 def ci_build_details_path merge_request 33 def ci_build_details_path merge_request
21 - merge_request.project.gitlab_ci_service.build_page(merge_request.last_commit.sha) 34 + merge_request.source_project.gitlab_ci_service.build_page(merge_request.last_commit.sha)
  35 + end
  36 +
  37 + def merge_path_description(merge_request, separator)
  38 + if merge_request.for_fork?
  39 + "Project:Branches: #{@merge_request.source_project.path_with_namespace}:#{@merge_request.source_branch} #{separator} #{@merge_request.target_project.path_with_namespace}:#{@merge_request.target_branch}"
  40 + else
  41 + "Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
  42 + end
22 end 43 end
23 end 44 end
app/mailers/emails/merge_requests.rb
@@ -2,28 +2,65 @@ module Emails @@ -2,28 +2,65 @@ module Emails
2 module MergeRequests 2 module MergeRequests
3 def new_merge_request_email(recipient_id, merge_request_id) 3 def new_merge_request_email(recipient_id, merge_request_id)
4 @merge_request = MergeRequest.find(merge_request_id) 4 @merge_request = MergeRequest.find(merge_request_id)
5 - @project = @merge_request.project  
6 mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title)) 5 mail(to: recipient(recipient_id), subject: subject("new merge request !#{@merge_request.id}", @merge_request.title))
7 end 6 end
8 7
9 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id) 8 def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
10 @merge_request = MergeRequest.find(merge_request_id) 9 @merge_request = MergeRequest.find(merge_request_id)
11 @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id 10 @previous_assignee = User.find_by_id(previous_assignee_id) if previous_assignee_id
12 - @project = @merge_request.project  
13 mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title)) 11 mail(to: recipient(recipient_id), subject: subject("changed merge request !#{@merge_request.id}", @merge_request.title))
14 end 12 end
15 13
16 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) 14 def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
17 @merge_request = MergeRequest.find(merge_request_id) 15 @merge_request = MergeRequest.find(merge_request_id)
18 - @project = @merge_request.project  
19 @updated_by = User.find updated_by_user_id 16 @updated_by = User.find updated_by_user_id
20 mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title)) 17 mail(to: recipient(recipient_id), subject: subject("Closed merge request !#{@merge_request.id}", @merge_request.title))
21 end 18 end
22 19
23 def merged_merge_request_email(recipient_id, merge_request_id) 20 def merged_merge_request_email(recipient_id, merge_request_id)
24 @merge_request = MergeRequest.find(merge_request_id) 21 @merge_request = MergeRequest.find(merge_request_id)
25 - @project = @merge_request.project  
26 mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title)) 22 mail(to: recipient(recipient_id), subject: subject("Accepted merge request !#{@merge_request.id}", @merge_request.title))
27 end 23 end
28 end 24 end
  25 +
  26 + # Over rides default behavour to show source/target
  27 + # Formats arguments into a String suitable for use as an email subject
  28 + #
  29 + # extra - Extra Strings to be inserted into the subject
  30 + #
  31 + # Examples
  32 + #
  33 + # >> subject('Lorem ipsum')
  34 + # => "GitLab Merge Request | Lorem ipsum"
  35 + #
  36 + # # Automatically inserts Project name:
  37 + # Forked MR
  38 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  39 + # => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
  40 + # => source branch => source
  41 + # => target branch => target
  42 + # >> subject('Lorem ipsum')
  43 + # => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
  44 + #
  45 + # Non Forked MR
  46 + # => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  47 + # => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
  48 + # => source branch => source
  49 + # => target branch => target
  50 + # >> subject('Lorem ipsum')
  51 + # => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
  52 + # # Accepts multiple arguments
  53 + # >> subject('Lorem ipsum', 'Dolor sit amet')
  54 + # => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
  55 + def subject(*extra)
  56 + subject = "GitLab Merge Request |"
  57 + if @merge_request.for_fork?
  58 + subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
  59 + else
  60 + subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
  61 + end
  62 + subject << " | " + extra.join(' | ') if extra.present?
  63 + subject
  64 + end
  65 +
29 end 66 end
app/mailers/notify.rb
@@ -6,6 +6,7 @@ class Notify &lt; ActionMailer::Base @@ -6,6 +6,7 @@ class Notify &lt; ActionMailer::Base
6 6
7 add_template_helper ApplicationHelper 7 add_template_helper ApplicationHelper
8 add_template_helper GitlabMarkdownHelper 8 add_template_helper GitlabMarkdownHelper
  9 + add_template_helper MergeRequestsHelper
9 10
10 default_url_options[:host] = Gitlab.config.gitlab.host 11 default_url_options[:host] = Gitlab.config.gitlab.host
11 default_url_options[:protocol] = Gitlab.config.gitlab.protocol 12 default_url_options[:protocol] = Gitlab.config.gitlab.protocol
app/models/concerns/issuable.rb
@@ -9,23 +9,19 @@ module Issuable @@ -9,23 +9,19 @@ module Issuable
9 include Mentionable 9 include Mentionable
10 10
11 included do 11 included do
12 - belongs_to :project  
13 belongs_to :author, class_name: "User" 12 belongs_to :author, class_name: "User"
14 belongs_to :assignee, class_name: "User" 13 belongs_to :assignee, class_name: "User"
15 belongs_to :milestone 14 belongs_to :milestone
16 has_many :notes, as: :noteable, dependent: :destroy 15 has_many :notes, as: :noteable, dependent: :destroy
17 16
18 - validates :project, presence: true  
19 validates :author, presence: true 17 validates :author, presence: true
20 validates :title, presence: true, length: { within: 0..255 } 18 validates :title, presence: true, length: { within: 0..255 }
21 19
22 - scope :opened, -> { with_state(:opened) }  
23 - scope :closed, -> { with_state(:closed) }  
24 - scope :of_group, ->(group) { where(project_id: group.project_ids) }  
25 scope :assigned_to, ->(u) { where(assignee_id: u.id)} 20 scope :assigned_to, ->(u) { where(assignee_id: u.id)}
26 scope :recent, -> { order("created_at DESC") } 21 scope :recent, -> { order("created_at DESC") }
27 scope :assigned, -> { where("assignee_id IS NOT NULL") } 22 scope :assigned, -> { where("assignee_id IS NOT NULL") }
28 scope :unassigned, -> { where("assignee_id IS NULL") } 23 scope :unassigned, -> { where("assignee_id IS NULL") }
  24 + scope :of_projects, ->(ids) { where(project_id: ids) }
29 25
30 delegate :name, 26 delegate :name,
31 :email, 27 :email,
app/models/issue.rb
@@ -17,8 +17,17 @@ @@ -17,8 +17,17 @@
17 # 17 #
18 18
19 class Issue < ActiveRecord::Base 19 class Issue < ActiveRecord::Base
  20 +
20 include Issuable 21 include Issuable
21 22
  23 + belongs_to :project
  24 + validates :project, presence: true
  25 +
  26 + scope :of_group, ->(group) { where(project_id: group.project_ids) }
  27 + scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
  28 + scope :opened, -> { with_state(:opened) }
  29 + scope :closed, -> { with_state(:closed) }
  30 +
22 attr_accessible :title, :assignee_id, :position, :description, 31 attr_accessible :title, :assignee_id, :position, :description,
23 :milestone_id, :label_list, :author_id_of_changes, 32 :milestone_id, :label_list, :author_id_of_changes,
24 :state_event 33 :state_event
app/models/merge_request.rb
@@ -2,30 +2,35 @@ @@ -2,30 +2,35 @@
2 # 2 #
3 # Table name: merge_requests 3 # Table name: merge_requests
4 # 4 #
5 -# id :integer not null, primary key  
6 -# target_branch :string(255) not null  
7 -# source_branch :string(255) not null  
8 -# project_id :integer not null  
9 -# author_id :integer  
10 -# assignee_id :integer  
11 -# title :string(255)  
12 -# created_at :datetime  
13 -# updated_at :datetime  
14 -# st_commits :text(2147483647)  
15 -# st_diffs :text(2147483647)  
16 -# milestone_id :integer  
17 -# state :string(255)  
18 -# merge_status :string(255) 5 +# id :integer not null, primary key
  6 +# target_project_id :integer not null
  7 +# target_branch :string(255) not null
  8 +# source_project_id :integer not null
  9 +# source_branch :string(255) not null
  10 +# author_id :integer
  11 +# assignee_id :integer
  12 +# title :string(255)
  13 +# created_at :datetime
  14 +# updated_at :datetime
  15 +# st_commits :text(2147483647)
  16 +# st_diffs :text(2147483647)
  17 +# milestone_id :integer
  18 +# state :string(255)
  19 +# merge_status :string(255)
19 # 20 #
20 21
21 require Rails.root.join("app/models/commit") 22 require Rails.root.join("app/models/commit")
22 require Rails.root.join("lib/static_model") 23 require Rails.root.join("lib/static_model")
23 24
24 class MergeRequest < ActiveRecord::Base 25 class MergeRequest < ActiveRecord::Base
  26 +
25 include Issuable 27 include Issuable
26 28
27 - attr_accessible :title, :assignee_id, :target_branch, :source_branch, :milestone_id,  
28 - :author_id_of_changes, :state_event 29 + belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
  30 + belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
  31 +
  32 + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event
  33 +
29 34
30 attr_accessor :should_remove_source_branch 35 attr_accessor :should_remove_source_branch
31 36
@@ -74,30 +79,37 @@ class MergeRequest &lt; ActiveRecord::Base @@ -74,30 +79,37 @@ class MergeRequest &lt; ActiveRecord::Base
74 serialize :st_commits 79 serialize :st_commits
75 serialize :st_diffs 80 serialize :st_diffs
76 81
  82 + validates :source_project, presence: true
77 validates :source_branch, presence: true 83 validates :source_branch, presence: true
  84 + validates :target_project, presence: true
78 validates :target_branch, presence: true 85 validates :target_branch, presence: true
79 - validate :validate_branches 86 + validate :validate_branches
80 87
  88 + scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
  89 + scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
  90 + scope :opened, -> { with_state(:opened) }
  91 + scope :closed, -> { with_state(:closed) }
81 scope :merged, -> { with_state(:merged) } 92 scope :merged, -> { with_state(:merged) }
82 - scope :by_branch, ->(branch_name) { where("source_branch LIKE :branch OR target_branch LIKE :branch", branch: branch_name) } 93 + scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
83 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } 94 scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
84 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } 95 scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
85 - 96 + scope :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 # Closed scope for merge request should return 98 # Closed scope for merge request should return
87 # both merged and closed mr's 99 # both merged and closed mr's
88 scope :closed, -> { with_states(:closed, :merged) } 100 scope :closed, -> { with_states(:closed, :merged) }
89 101
90 def validate_branches 102 def validate_branches
91 - if target_branch == source_branch  
92 - errors.add :branch_conflict, "You can not use same branch for source and target branches" 103 + if target_project==source_project && target_branch == source_branch
  104 + errors.add :branch_conflict, "You can not use same project/branch for source and target"
93 end 105 end
94 106
95 if opened? || reopened? 107 if opened? || reopened?
96 - similar_mrs = self.project.merge_requests.where(source_branch: source_branch, target_branch: target_branch).opened 108 + similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
97 similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id 109 similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
98 110
99 if similar_mrs.any? 111 if similar_mrs.any?
100 - errors.add :base, "There is already an open merge request for this branches" 112 + errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
101 end 113 end
102 end 114 end
103 end 115 end
@@ -137,7 +149,14 @@ class MergeRequest &lt; ActiveRecord::Base @@ -137,7 +149,14 @@ class MergeRequest &lt; ActiveRecord::Base
137 end 149 end
138 150
139 def unmerged_diffs 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 end 160 end
142 161
143 def last_commit 162 def last_commit
@@ -145,11 +164,11 @@ class MergeRequest &lt; ActiveRecord::Base @@ -145,11 +164,11 @@ class MergeRequest &lt; ActiveRecord::Base
145 end 164 end
146 165
147 def merge_event 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 end 168 end
150 169
151 def closed_event 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 end 172 end
154 173
155 def commits 174 def commits
@@ -165,15 +184,24 @@ class MergeRequest &lt; ActiveRecord::Base @@ -165,15 +184,24 @@ class MergeRequest &lt; ActiveRecord::Base
165 if opened? && unmerged_commits.any? 184 if opened? && unmerged_commits.any?
166 self.st_commits = dump_commits(unmerged_commits) 185 self.st_commits = dump_commits(unmerged_commits)
167 save 186 save
  187 +
168 end 188 end
169 commits 189 commits
170 end 190 end
171 191
172 def unmerged_commits 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 sort_by(&:created_at). 201 sort_by(&:created_at).
176 reverse 202 reverse
  203 + end
  204 + commits
177 end 205 end
178 206
179 def merge!(user_id) 207 def merge!(user_id)
@@ -199,21 +227,29 @@ class MergeRequest &lt; ActiveRecord::Base @@ -199,21 +227,29 @@ class MergeRequest &lt; ActiveRecord::Base
199 # Returns the raw diff for this merge request 227 # Returns the raw diff for this merge request
200 # 228 #
201 # see "git diff" 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 end 232 end
205 233
206 # Returns the commit as a series of email patches. 234 # Returns the commit as a series of email patches.
207 # 235 #
208 # see "git format-patch" 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 end 239 end
212 240
213 def last_commit_short_sha 241 def last_commit_short_sha
214 @last_commit_short_sha ||= last_commit.sha[0..10] 242 @last_commit_short_sha ||= last_commit.sha[0..10]
215 end 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 private 253 private
218 254
219 def dump_commits(commits) 255 def dump_commits(commits)
app/models/note.rb
@@ -42,8 +42,8 @@ class Note &lt; ActiveRecord::Base @@ -42,8 +42,8 @@ class Note &lt; ActiveRecord::Base
42 42
43 # Scopes 43 # Scopes
44 scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } 44 scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
45 - scope :inline, -> { where("line_code IS NOT NULL") }  
46 - scope :not_inline, -> { where(line_code: [nil, '']) } 45 + scope :inline, ->{ where("line_code IS NOT NULL") }
  46 + scope :not_inline, ->{ where(line_code: [nil, '']) }
47 47
48 scope :common, ->{ where(noteable_type: ["", nil]) } 48 scope :common, ->{ where(noteable_type: ["", nil]) }
49 scope :fresh, ->{ order("created_at ASC, id ASC") } 49 scope :fresh, ->{ order("created_at ASC, id ASC") }
@@ -53,10 +53,10 @@ class Note &lt; ActiveRecord::Base @@ -53,10 +53,10 @@ class Note &lt; ActiveRecord::Base
53 serialize :st_diff 53 serialize :st_diff
54 before_create :set_diff, if: ->(n) { n.line_code.present? } 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 create({ 57 create({
58 noteable: noteable, 58 noteable: noteable,
59 - project: noteable.project, 59 + project: project,
60 author: author, 60 author: author,
61 note: "_Status changed to #{status}_" 61 note: "_Status changed to #{status}_"
62 }, without_protection: true) 62 }, without_protection: true)
@@ -65,7 +65,7 @@ class Note &lt; ActiveRecord::Base @@ -65,7 +65,7 @@ class Note &lt; ActiveRecord::Base
65 def commit_author 65 def commit_author
66 @commit_author ||= 66 @commit_author ||=
67 project.users.find_by_email(noteable.author_email) || 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 rescue 69 rescue
70 nil 70 nil
71 end 71 end
app/models/project.rb
@@ -53,7 +53,7 @@ class Project &lt; ActiveRecord::Base @@ -53,7 +53,7 @@ class Project &lt; ActiveRecord::Base
53 53
54 has_many :services, dependent: :destroy 54 has_many :services, dependent: :destroy
55 has_many :events, dependent: :destroy 55 has_many :events, dependent: :destroy
56 - has_many :merge_requests, dependent: :destroy 56 + has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
57 has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC" 57 has_many :issues, dependent: :destroy, order: "state DESC, created_at DESC"
58 has_many :milestones, dependent: :destroy 58 has_many :milestones, dependent: :destroy
59 has_many :notes, dependent: :destroy 59 has_many :notes, dependent: :destroy
app/observers/activity_observer.rb
1 class ActivityObserver < BaseObserver 1 class ActivityObserver < BaseObserver
2 - observe :issue, :merge_request, :note, :milestone 2 + observe :issue, :note, :milestone
3 3
4 def after_create(record) 4 def after_create(record)
5 event_author_id = record.author_id 5 event_author_id = record.author_id
@@ -13,47 +13,27 @@ class ActivityObserver &lt; BaseObserver @@ -13,47 +13,27 @@ class ActivityObserver &lt; BaseObserver
13 end 13 end
14 14
15 if event_author_id 15 if event_author_id
16 - Event.create(  
17 - project: record.project,  
18 - target_id: record.id,  
19 - target_type: record.class.name,  
20 - action: Event.determine_action(record),  
21 - author_id: event_author_id  
22 - ) 16 + create_event(record, Event.determine_action(record))
23 end 17 end
24 end 18 end
25 19
26 def after_close(record, transition) 20 def after_close(record, transition)
27 - Event.create(  
28 - project: record.project,  
29 - target_id: record.id,  
30 - target_type: record.class.name,  
31 - action: Event::CLOSED,  
32 - author_id: record.author_id_of_changes  
33 - ) 21 + create_event(record, Event::CLOSED)
34 end 22 end
35 23
36 def after_reopen(record, transition) 24 def after_reopen(record, transition)
37 - Event.create(  
38 - project: record.project,  
39 - target_id: record.id,  
40 - target_type: record.class.name,  
41 - action: Event::REOPENED,  
42 - author_id: record.author_id_of_changes  
43 - ) 25 + create_event(record, Event::REOPENED)
44 end 26 end
45 27
46 - def after_merge(record, transition)  
47 - # Since MR can be merged via sidekiq  
48 - # to prevent event duplication do this check  
49 - return true if record.merge_event 28 + protected
50 29
  30 + def create_event(record, status)
51 Event.create( 31 Event.create(
52 - project: record.project,  
53 - target_id: record.id,  
54 - target_type: record.class.name,  
55 - action: Event::MERGED,  
56 - author_id: record.author_id_of_changes 32 + project: record.project,
  33 + target_id: record.id,
  34 + target_type: record.class.name,
  35 + action: status,
  36 + author_id: record.author_id
57 ) 37 )
58 end 38 end
59 end 39 end
app/observers/issue_observer.rb
@@ -23,6 +23,6 @@ class IssueObserver &lt; BaseObserver @@ -23,6 +23,6 @@ class IssueObserver &lt; BaseObserver
23 23
24 # Create issue note with service comment like 'Status changed to closed' 24 # Create issue note with service comment like 'Status changed to closed'
25 def create_note(issue) 25 def create_note(issue)
26 - Note.create_status_change_note(issue, current_user, issue.state) 26 + Note.create_status_change_note(issue, issue.project, current_user, issue.state)
27 end 27 end
28 end 28 end
app/observers/merge_request_observer.rb
1 -class MergeRequestObserver < BaseObserver 1 +class MergeRequestObserver < ActivityObserver
  2 + observe :merge_request
  3 +
2 def after_create(merge_request) 4 def after_create(merge_request)
  5 + if merge_request.author_id
  6 + create_event(merge_request, Event.determine_action(merge_request))
  7 + end
  8 +
3 notification.new_merge_request(merge_request, current_user) 9 notification.new_merge_request(merge_request, current_user)
4 end 10 end
5 11
6 def after_close(merge_request, transition) 12 def after_close(merge_request, transition)
7 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 13 + create_event(merge_request, Event::CLOSED)
  14 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
8 15
9 notification.close_mr(merge_request, current_user) 16 notification.close_mr(merge_request, current_user)
10 end 17 end
11 18
12 def after_merge(merge_request, transition) 19 def after_merge(merge_request, transition)
13 notification.merge_mr(merge_request) 20 notification.merge_mr(merge_request)
  21 + # Since MR can be merged via sidekiq
  22 + # to prevent event duplication do this check
  23 + return true if merge_request.merge_event
  24 +
  25 + Event.create(
  26 + project: merge_request.target_project,
  27 + target_id: merge_request.id,
  28 + target_type: merge_request.class.name,
  29 + action: Event::MERGED,
  30 + author_id: merge_request.author_id_of_changes
  31 + )
14 end 32 end
15 33
16 def after_reopen(merge_request, transition) 34 def after_reopen(merge_request, transition)
17 - Note.create_status_change_note(merge_request, current_user, merge_request.state) 35 + create_event(merge_request, Event::REOPENED)
  36 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state)
18 end 37 end
19 38
20 def after_update(merge_request) 39 def after_update(merge_request)
21 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned? 40 notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
22 end 41 end
  42 +
  43 + def create_event(record, status)
  44 + Event.create(
  45 + project: record.target_project,
  46 + target_id: record.id,
  47 + target_type: record.class.name,
  48 + action: status,
  49 + author_id: record.author_id
  50 + )
  51 + end
  52 +
23 end 53 end
app/services/notification_service.rb
@@ -23,7 +23,7 @@ class NotificationService @@ -23,7 +23,7 @@ class NotificationService
23 # * project team members with notification level higher then Participating 23 # * project team members with notification level higher then Participating
24 # 24 #
25 def new_issue(issue, current_user) 25 def new_issue(issue, current_user)
26 - new_resource_email(issue, 'new_issue_email') 26 + new_resource_email(issue, issue.project, 'new_issue_email')
27 end 27 end
28 28
29 # When we close an issue we should send next emails: 29 # When we close an issue we should send next emails:
@@ -33,7 +33,7 @@ class NotificationService @@ -33,7 +33,7 @@ class NotificationService
33 # * project team members with notification level higher then Participating 33 # * project team members with notification level higher then Participating
34 # 34 #
35 def close_issue(issue, current_user) 35 def close_issue(issue, current_user)
36 - close_resource_email(issue, current_user, 'closed_issue_email') 36 + close_resource_email(issue, issue.project, current_user, 'closed_issue_email')
37 end 37 end
38 38
39 # When we reassign an issue we should send next emails: 39 # When we reassign an issue we should send next emails:
@@ -42,7 +42,7 @@ class NotificationService @@ -42,7 +42,7 @@ class NotificationService
42 # * issue new assignee if his notification level is not Disabled 42 # * issue new assignee if his notification level is not Disabled
43 # 43 #
44 def reassigned_issue(issue, current_user) 44 def reassigned_issue(issue, current_user)
45 - reassign_resource_email(issue, current_user, 'reassigned_issue_email') 45 + reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email')
46 end 46 end
47 47
48 48
@@ -51,7 +51,7 @@ class NotificationService @@ -51,7 +51,7 @@ class NotificationService
51 # * mr assignee if his notification level is not Disabled 51 # * mr assignee if his notification level is not Disabled
52 # 52 #
53 def new_merge_request(merge_request, current_user) 53 def new_merge_request(merge_request, current_user)
54 - new_resource_email(merge_request, 'new_merge_request_email') 54 + new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email')
55 end 55 end
56 56
57 # When we reassign a merge_request we should send next emails: 57 # When we reassign a merge_request we should send next emails:
@@ -60,7 +60,7 @@ class NotificationService @@ -60,7 +60,7 @@ class NotificationService
60 # * merge_request assignee if his notification level is not Disabled 60 # * merge_request assignee if his notification level is not Disabled
61 # 61 #
62 def reassigned_merge_request(merge_request, current_user) 62 def reassigned_merge_request(merge_request, current_user)
63 - reassign_resource_email(merge_request, current_user, 'reassigned_merge_request_email') 63 + reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email')
64 end 64 end
65 65
66 # When we close a merge request we should send next emails: 66 # When we close a merge request we should send next emails:
@@ -70,7 +70,7 @@ class NotificationService @@ -70,7 +70,7 @@ class NotificationService
70 # * project team members with notification level higher then Participating 70 # * project team members with notification level higher then Participating
71 # 71 #
72 def close_mr(merge_request, current_user) 72 def close_mr(merge_request, current_user)
73 - close_resource_email(merge_request, current_user, 'closed_merge_request_email') 73 + close_resource_email(merge_request, merge_request.target_project, current_user, 'closed_merge_request_email')
74 end 74 end
75 75
76 # When we merge a merge request we should send next emails: 76 # When we merge a merge request we should send next emails:
@@ -80,8 +80,8 @@ class NotificationService @@ -80,8 +80,8 @@ class NotificationService
80 # * project team members with notification level higher then Participating 80 # * project team members with notification level higher then Participating
81 # 81 #
82 def merge_mr(merge_request) 82 def merge_mr(merge_request)
83 - recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.project)  
84 - recipients = recipients.concat(project_watchers(merge_request.project)).uniq 83 + recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
  84 + recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
85 85
86 recipients.each do |recipient| 86 recipients.each do |recipient|
87 mailer.merged_merge_request_email(recipient.id, merge_request.id) 87 mailer.merged_merge_request_email(recipient.id, merge_request.id)
@@ -194,14 +194,14 @@ class NotificationService @@ -194,14 +194,14 @@ class NotificationService
194 end 194 end
195 end 195 end
196 196
197 - def new_resource_email(target, method) 197 + def new_resource_email(target, project, method)
198 if target.respond_to?(:participants) 198 if target.respond_to?(:participants)
199 recipients = target.participants 199 recipients = target.participants
200 else 200 else
201 recipients = [] 201 recipients = []
202 end 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 recipients.delete(target.author) 205 recipients.delete(target.author)
206 206
207 recipients.each do |recipient| 207 recipients.each do |recipient|
@@ -209,9 +209,9 @@ class NotificationService @@ -209,9 +209,9 @@ class NotificationService
209 end 209 end
210 end 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 recipients.delete(current_user) 215 recipients.delete(current_user)
216 216
217 recipients.each do |recipient| 217 recipients.each do |recipient|
@@ -219,14 +219,14 @@ class NotificationService @@ -219,14 +219,14 @@ class NotificationService
219 end 219 end
220 end 220 end
221 221
222 - def reassign_resource_email(target, current_user, method) 222 + def reassign_resource_email(target, project, current_user, method)
223 recipients = User.where(id: [target.assignee_id, target.assignee_id_was]) 223 recipients = User.where(id: [target.assignee_id, target.assignee_id_was])
224 224
225 # Add watchers to email list 225 # Add watchers to email list
226 - recipients = recipients.concat(project_watchers(target.project)) 226 + recipients = recipients.concat(project_watchers(project))
227 227
228 # reject users with disabled notifications 228 # reject users with disabled notifications
229 - recipients = reject_muted_users(recipients, target.project) 229 + recipients = reject_muted_users(recipients, project)
230 230
231 # Reject me from recipients if I reassign an item 231 # Reject me from recipients if I reassign an item
232 recipients.delete(current_user) 232 recipients.delete(current_user)
app/views/notify/closed_merge_request_email.html.haml
1 %p 1 %p
2 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" 2 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/closed_merge_request_email.text.haml
1 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}" 1 = "Merge Request #{@merge_request.id} was closed by #{@updated_by.name}"
2 2
3 -Merge Request url: #{project_merge_request_url(@merge_request.project, @merge_request)} 3 +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
5 -Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} 5 += merge_path_description(@merge_request, 'to')
6 6
7 Author: #{@merge_request.author_name} 7 Author: #{@merge_request.author_name}
8 Assignee: #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.assignee_name}
app/views/notify/merged_merge_request_email.html.haml
1 %p 1 %p
2 = "Merge Request #{@merge_request.id} was merged" 2 = "Merge Request #{@merge_request.id} was merged"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/merged_merge_request_email.text.haml
1 = "Merge Request #{@merge_request.id} was merged" 1 = "Merge Request #{@merge_request.id} was merged"
2 2
3 -Merge Request Url: #{project_merge_request_url(@merge_request.project, @merge_request)} 3 +Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
4 4
5 -Branches: #{@merge_request.source_branch} - #{@merge_request.target_branch} 5 += merge_path_description(@merge_request, 'to')
6 6
7 Author: #{@merge_request.author_name} 7 Author: #{@merge_request.author_name}
8 Assignee: #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.assignee_name}
app/views/notify/new_merge_request_email.html.haml
1 %p 1 %p
2 = "New Merge Request !#{@merge_request.id}" 2 = "New Merge Request !#{@merge_request.id}"
3 %p 3 %p
4 - = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.project, @merge_request) 4 + = link_to_gfm truncate(@merge_request.title, length: 40), project_merge_request_url(@merge_request.target_project, @merge_request)
5 %p 5 %p
6 - Branches: #{@merge_request.source_branch} &rarr; #{@merge_request.target_branch} 6 + != merge_path_description(@merge_request, '&rarr;')
7 %p 7 %p
8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} 8 Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
9 9
app/views/notify/new_merge_request_email.text.erb
1 New Merge Request <%= @merge_request.id %> 1 New Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %>  
4 - 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
5 4
6 -Branches: <%= @merge_request.source_branch %> to <%= @merge_request.target_branch %> 5 +<%= merge_path_description(@merge_request, 'to') %>
7 Author: <%= @merge_request.author_name %> 6 Author: <%= @merge_request.author_name %>
8 Asignee: <%= @merge_request.assignee_name %> 7 Asignee: <%= @merge_request.assignee_name %>
9 8
app/views/notify/note_merge_request_email.html.haml
1 %p 1 %p
2 - if @note.for_diff_line? 2 - if @note.for_diff_line?
3 - = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}") 3 + = link_to "New comment on diff", diffs_project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
4 - else 4 - else
5 - = link_to "New comment", project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}") 5 + = link_to "New comment", project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")
6 for Merge Request ##{@merge_request.id} 6 for Merge Request ##{@merge_request.id}
7 %cite "#{truncate(@merge_request.title, length: 20)}" 7 %cite "#{truncate(@merge_request.title, length: 20)}"
8 = render 'note_message' 8 = render 'note_message'
app/views/notify/note_merge_request_email.text.erb
1 New comment for Merge Request <%= @merge_request.id %> 1 New comment for Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request, anchor: "note_#{@note.id}")) %> 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
4 4
5 5
6 <%= @note.author_name %> 6 <%= @note.author_name %>
app/views/notify/reassigned_merge_request_email.html.haml
1 %p 1 %p
2 = "Reassigned Merge Request !#{@merge_request.id}" 2 = "Reassigned Merge Request !#{@merge_request.id}"
3 - = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.project, @merge_request) 3 + = link_to_gfm truncate(@merge_request.title, length: 30), project_merge_request_url(@merge_request.target_project, @merge_request)
4 %p 4 %p
5 Assignee changed 5 Assignee changed
6 - if @previous_assignee 6 - if @previous_assignee
app/views/notify/reassigned_merge_request_email.text.erb
1 Reassigned Merge Request <%= @merge_request.id %> 1 Reassigned Merge Request <%= @merge_request.id %>
2 2
3 -<%= url_for(project_merge_request_url(@merge_request.project, @merge_request)) %> 3 +<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
4 4
5 5
6 Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %> 6 Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %>
app/views/projects/commit/show.html.haml
1 = render "commit_box" 1 = render "commit_box"
2 -= render "projects/commits/diffs", diffs: @commit.diffs 2 += render "projects/commits/diffs", diffs: @commit.diffs, project: @project
3 = render "projects/notes/notes_with_form" 3 = render "projects/notes/notes_with_form"
app/views/projects/commits/_commit.html.haml
1 %li.commit 1 %li.commit
2 .browse_code_link_holder 2 .browse_code_link_holder
3 %p 3 %p
4 - %strong= link_to "Browse Code »", project_tree_path(@project, commit), class: "right" 4 + %strong= link_to "Browse Code »", project_tree_path(project, commit), class: "right"
5 %p 5 %p
6 - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" 6 + = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
7 = commit_author_link(commit, avatar: true, size: 24) 7 = commit_author_link(commit, avatar: true, size: 24)
8 &nbsp; 8 &nbsp;
9 - = link_to_gfm truncate(commit.title, length: 70), project_commit_path(@project, commit.id), class: "row_title" 9 + = link_to_gfm truncate(commit.title, length: 70), project_commit_path(project, commit.id), class: "row_title"
10 10
11 %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") } 11 %time.committed_ago{ datetime: commit.committed_date, title: commit.committed_date.stamp("Aug 21, 2011 9:23pm") }
12 = time_ago_in_words(commit.committed_date) 12 = time_ago_in_words(commit.committed_date)
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 &nbsp; 14 &nbsp;
15 15
16 %span.notes_count 16 %span.notes_count
17 - - notes = @project.notes.for_commit_id(commit.id) 17 + - notes = project.notes.for_commit_id(commit.id)
18 - if notes.any? 18 - if notes.any?
19 %span.badge.badge-info 19 %span.badge.badge-info
20 %i.icon-comment 20 %i.icon-comment
app/views/projects/commits/_commits.html.haml
@@ -3,7 +3,6 @@ @@ -3,7 +3,6 @@
3 .title 3 .title
4 %i.icon-calendar 4 %i.icon-calendar
5 %span= day.stamp("28 Aug, 2010") 5 %span= day.stamp("28 Aug, 2010")
6 -  
7 .pull-right 6 .pull-right
8 %small= pluralize(commits.count, 'commit') 7 %small= pluralize(commits.count, 'commit')
9 - %ul.well-list= render commits 8 + %ul.well-list= render commits, project: @project
app/views/projects/commits/_diffs.html.haml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 %p To prevent performance issue we rejected diff information. 5 %p To prevent performance issue we rejected diff information.
6 %p 6 %p
7 But if you still want to see diff 7 But if you still want to see diff
8 - = link_to "click this link", project_commit_path(@project, @commit, force_show_diff: true), class: "underlined_link" 8 + = link_to "click this link", project_commit_path(project, @commit, force_show_diff: true), class: "underlined_link"
9 9
10 %p.commit-stat-summary 10 %p.commit-stat-summary
11 Showing 11 Showing
@@ -23,8 +23,8 @@ @@ -23,8 +23,8 @@
23 - unless @suppress_diff 23 - unless @suppress_diff
24 - diffs.each_with_index do |diff, i| 24 - diffs.each_with_index do |diff, i|
25 - next if diff.diff.empty? 25 - next if diff.diff.empty?
26 - - file = Gitlab::Git::Blob.new(@repository, @commit.id, @ref, diff.new_path)  
27 - - file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) unless file.exists? 26 + - file = Gitlab::Git::Blob.new(project.repository, @commit.id, @ref, diff.new_path)
  27 + - file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) unless file.exists?
28 - next unless file.exists? 28 - next unless file.exists?
29 .file{id: "diff-#{i}"} 29 .file{id: "diff-#{i}"}
30 .header 30 .header
@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 %span= diff.old_path 32 %span= diff.old_path
33 33
34 - if @commit.parent_ids.present? 34 - if @commit.parent_ids.present?
35 - = link_to project_blob_path(@project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do 35 + = link_to project_blob_path(project, tree_join(@commit.parent_id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
36 View file @ 36 View file @
37 %span.commit-short-id= @commit.short_id(6) 37 %span.commit-short-id= @commit.short_id(6)
38 - else 38 - else
@@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
40 - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode 40 - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
41 %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" 41 %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}"
42 42
43 - = link_to project_blob_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do 43 + = link_to project_blob_path(project, tree_join(@commit.id, diff.new_path)), {:class => 'btn btn-tiny pull-right view-file'} do
44 View file @ 44 View file @
45 %span.commit-short-id= @commit.short_id(6) 45 %span.commit-short-id= @commit.short_id(6)
46 46
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 - if file.text? 50 - if file.text?
51 = render "projects/commits/text_file", diff: diff, index: i 51 = render "projects/commits/text_file", diff: diff, index: i
52 - elsif file.image? 52 - elsif file.image?
53 - - old_file = Gitlab::Git::Blob.new(@repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id 53 + - old_file = Gitlab::Git::Blob.new(project.repository, @commit.parent_id, @ref, diff.old_path) if @commit.parent_id
54 = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i 54 = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i
55 - else 55 - else
56 %p.nothing_here_message No preview for this file type 56 %p.nothing_here_message No preview for this file type
app/views/projects/compare/show.html.haml
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 %div.ui-box 15 %div.ui-box
16 .title 16 .title
17 Commits (#{@commits.count}) 17 Commits (#{@commits.count})
18 - %ul.well-list= render Commit.decorate(@commits) 18 + %ul.well-list= render Commit.decorate(@commits), project: @project
19 19
20 - unless @diffs.empty? 20 - unless @diffs.empty?
21 %h4 Diff 21 %h4 Diff
22 - = render "projects/commits/diffs", diffs: @diffs 22 + = render "projects/commits/diffs", diffs: @diffs, project: @project
app/views/projects/merge_requests/_form.html.haml
@@ -12,17 +12,27 @@ @@ -12,17 +12,27 @@
12 .row 12 .row
13 .span5 13 .span5
14 .light-well 14 .light-well
15 - %h5.cgray From (Head Branch)  
16 - = f.select(:source_branch, @repository.branch_names, { include_blank: "Select branch" }, {class: 'chosen span4'}) 15 + %h5.cgray From
  16 + .padded
  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 .mr_source_commit.prepend-top-10 22 .mr_source_commit.prepend-top-10
18 -  
19 .span2 23 .span2
20 %h1.merge-request-angle 24 %h1.merge-request-angle
21 %i.icon-angle-right 25 %i.icon-angle-right
22 .span5 26 .span5
23 .light-well 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 .mr_target_commit.prepend-top-10 36 .mr_target_commit.prepend-top-10
27 37
28 %hr 38 %hr
@@ -47,32 +57,34 @@ @@ -47,32 +57,34 @@
47 Milestone 57 Milestone
48 .controls= f.select(:milestone_id, @project.milestones.active.all.map {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'}) 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 .form-actions 60 .form-actions
52 - if @merge_request.new_record? 61 - if @merge_request.new_record?
53 = f.submit 'Submit merge request', class: "btn btn-create" 62 = f.submit 'Submit merge request', class: "btn btn-create"
54 -else 63 -else
55 = f.submit 'Save changes', class: "btn btn-save" 64 = f.submit 'Save changes', class: "btn btn-save"
56 - if @merge_request.new_record? 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 Cancel 67 Cancel
59 - else 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 Cancel 70 Cancel
62 71
63 :javascript 72 :javascript
64 disableButtonIfEmptyField("#merge_request_title", ".btn-save"); 73 disableButtonIfEmptyField("#merge_request_title", ".btn-save");
65 74
66 var source_branch = $("#merge_request_source_branch") 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 %li{ class: mr_css_classes(merge_request) } 1 %li{ class: mr_css_classes(merge_request) }
2 .merge-request-title 2 .merge-request-title
3 %span.light= "##{merge_request.id}" 3 %span.light= "##{merge_request.id}"
4 - = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.project, merge_request), class: "row_title" 4 + = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
5 - if merge_request.merged? 5 - if merge_request.merged?
6 %small.pull-right 6 %small.pull-right
7 %i.icon-ok 7 %i.icon-ok
8 = "MERGED" 8 = "MERGED"
9 - else 9 - else
10 %span.pull-right 10 %span.pull-right
11 - %i.icon-angle-right  
12 - = merge_request.target_branch 11 + - if merge_request.for_fork?
  12 + %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 .merge-request-info 21 .merge-request-info
14 - if merge_request.author 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 - if merge_request.votes_count > 0 24 - if merge_request.votes_count > 0
17 = render 'votes/votes_inline', votable: merge_request 25 = render 'votes/votes_inline', votable: merge_request
18 - if merge_request.notes.any? 26 - if merge_request.notes.any?
app/views/projects/merge_requests/branch_from.js.haml
1 :plain 1 :plain
2 - $(".mr_source_commit").html("#{commit_to_html(@commit)}"); 2 + $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project)}");
3 var mrTitle = $('#merge_request_title'); 3 var mrTitle = $('#merge_request_title');
4 4
5 - if(mrTitle.is(":empty")) { 5 + if(mrTitle.val().length == 0) {
6 mrTitle.val("#{params[:ref].titleize}"); 6 mrTitle.val("#{params[:ref].titleize}");
7 } 7 }
app/views/projects/merge_requests/branch_to.js.haml
1 :plain 1 :plain
2 - $(".mr_target_commit").html("#{commit_to_html(@commit)}"); 2 + $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project)}");
app/views/projects/merge_requests/show/_commits.html.haml
@@ -7,19 +7,19 @@ @@ -7,19 +7,19 @@
7 - if @commits.count > 8 7 - if @commits.count > 8
8 %ul.first-commits.well-list 8 %ul.first-commits.well-list
9 - @commits.first(8).each do |commit| 9 - @commits.first(8).each do |commit|
10 - = render "projects/commits/commit", commit: commit 10 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
11 %li.bottom 11 %li.bottom
12 8 of #{@commits.count} commits displayed. 12 8 of #{@commits.count} commits displayed.
13 %strong 13 %strong
14 %a.show-all-commits Click here to show all 14 %a.show-all-commits Click here to show all
15 %ul.all-commits.hide.well-list 15 %ul.all-commits.hide.well-list
16 - @commits.each do |commit| 16 - @commits.each do |commit|
17 - = render "projects/commits/commit", commit: commit 17 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
18 18
19 - else 19 - else
20 %ul.well-list 20 %ul.well-list
21 - @commits.each do |commit| 21 - @commits.each do |commit|
22 - = render "projects/commits/commit", commit: commit 22 + = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
23 23
24 - else 24 - else
25 %h4.nothing_here_message 25 %h4.nothing_here_message
app/views/projects/merge_requests/show/_diffs.html.haml
1 - if @merge_request.valid_diffs? 1 - if @merge_request.valid_diffs?
2 - = render "projects/commits/diffs", diffs: @merge_request.diffs 2 + = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project
3 - elsif @merge_request.broken_diffs? 3 - elsif @merge_request.broken_diffs?
4 %h4.nothing_here_message 4 %h4.nothing_here_message
5 Can't load diff. 5 Can't load diff.
6 You can 6 You can
7 - = link_to "download it", project_merge_request_path(@project, @merge_request, format: :diff), class: "vlink" 7 + = link_to "download it", project_merge_request_path(@merge_request.source_project, @merge_request), format: :diff, class: "vlink"
8 instead. 8 instead.
9 - else 9 - else
10 %h4.nothing_here_message Nothing to merge 10 %h4.nothing_here_message Nothing to merge
app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -3,17 +3,49 @@ @@ -3,17 +3,49 @@
3 %a.close{href: "#", "data-dismiss" => "modal"} × 3 %a.close{href: "#", "data-dismiss" => "modal"} ×
4 %h3 How to merge 4 %h3 How to merge
5 .modal-body 5 .modal-body
6 - %p  
7 - %strong Step 1.  
8 - Checkout target branch and get recent objects from GitLab  
9 - %pre.dark  
10 - :preserve  
11 - git checkout #{@merge_request.target_branch}  
12 - git fetch origin  
13 - %p  
14 - %strong Step 2.  
15 - Merge source branch into target branch and push changes to GitLab  
16 - %pre.dark  
17 - :preserve  
18 - git merge origin/#{@merge_request.source_branch}  
19 - git push origin #{@merge_request.target_branch} 6 + - if @merge_request.for_fork?
  7 + - source_remote = @merge_request.source_project.namespace.nil? ? "source" :@merge_request.source_project.namespace.path
  8 + - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
  9 + %p
  10 + %strong Step 1.
  11 + Checkout target branch and get recent objects from GitLab
  12 + Assuming remote for #{@merge_request.target_project.path_with_namespace} is called #{target_remote}
  13 + remote for #{@merge_request.source_project.path_with_namespace} is called #{source_remote}
  14 + %pre.dark
  15 + :preserve
  16 + git checkout #{target_remote} #{@merge_request.target_branch}
  17 + git fetch #{source_remote}
  18 + %p
  19 + %strong Step 2.
  20 + Merge source branch into target branch and push changes to GitLab
  21 + %pre.dark
  22 + :preserve
  23 + git merge #{source_remote}/#{@merge_request.source_branch}
  24 + git push #{target_remote} #{@merge_request.target_branch}
  25 + - else
  26 + %p
  27 + %strong Step 1.
  28 + Checkout target branch and get recent objects from GitLab
  29 + %pre.dark
  30 + :preserve
  31 + git checkout #{@merge_request.target_branch}
  32 + git fetch origin
  33 + %p
  34 + %strong Step 2.
  35 + Merge source branch into target branch and push changes to GitLab
  36 + %pre.dark
  37 + :preserve
  38 + git merge origin/#{@merge_request.source_branch}
  39 + git push origin #{@merge_request.target_branch}
  40 +
  41 +
  42 +:javascript
  43 + $(function(){
  44 + var modal = $('#modal_merge_info').modal({modal: true, show:false});
  45 + $('.how_to_merge_link').bind("click", function(){
  46 + modal.show();
  47 + });
  48 + $('.modal-header .close').bind("click", function(){
  49 + modal.hide();
  50 + })
  51 + })
app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 for instructions 16 for instructions
17 .accept_group 17 .accept_group
18 = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" 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 .remove_branch_holder 20 .remove_branch_holder
21 = label_tag :should_remove_source_branch, class: "checkbox" do 21 = label_tag :should_remove_source_branch, class: "checkbox" do
22 = check_box_tag :should_remove_source_branch 22 = check_box_tag :should_remove_source_branch
app/views/projects/merge_requests/show/_mr_title.html.haml
1 %h3.page-title 1 %h3.page-title
2 = "Merge Request ##{@merge_request.id}:" 2 = "Merge Request ##{@merge_request.id}:"
3 &nbsp; 3 &nbsp;
4 - %span.label-branch= @merge_request.source_branch  
5 - &rarr;  
6 - %span.label-branch= @merge_request.target_branch 4 + -if @merge_request.for_fork?
  5 + %span.label-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 %span.pull-right 15 %span.pull-right
9 - if can?(current_user, :modify_merge_request, @merge_request) 16 - if can?(current_user, :modify_merge_request, @merge_request)
@@ -19,7 +26,7 @@ @@ -19,7 +26,7 @@
19 26
20 = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request" 27 = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn grouped btn-close", title: "Close merge request"
21 28
22 - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped" do 29 + = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn grouped", id:"edit_merge_request" do
23 %i.icon-edit 30 %i.icon-edit
24 Edit 31 Edit
25 32
app/views/projects/merge_requests/update_branches.js.haml 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +:plain
  2 + $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
  3 + $(".target_branch").trigger("liszt:updated");
  4 + $(".mr_target_commit").html("");
  5 + $(".target_branch").trigger("change");
0 \ No newline at end of file 6 \ No newline at end of file
app/views/projects/notes/_discussion.html.haml
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 discussion on this merge request diff 23 discussion on this merge request diff
24 - elsif note.for_commit? 24 - elsif note.for_commit?
25 started a discussion on commit 25 started a discussion on commit
26 - #{link_to note.noteable.short_id, project_commit_path(@project, note.noteable)} 26 + #{link_to note.noteable.short_id, project_commit_path(note.project, note.noteable)}
27 = link_to_commit_diff_line_note(note) if note.for_diff_line? 27 = link_to_commit_diff_line_note(note) if note.for_diff_line?
28 - else 28 - else
29 %cite.cgray started a discussion 29 %cite.cgray started a discussion
app/views/search/_result.html.haml
@@ -22,11 +22,14 @@ @@ -22,11 +22,14 @@
22 - @merge_requests.each do |merge_request| 22 - @merge_requests.each do |merge_request|
23 %li 23 %li
24 merge request: 24 merge request:
25 - = link_to [merge_request.project, merge_request] do 25 + = link_to [merge_request.target_project, merge_request] do
26 %span ##{merge_request.id} 26 %span ##{merge_request.id}
27 %strong.term 27 %strong.term
28 = truncate merge_request.title, length: 50 28 = truncate merge_request.title, length: 50
29 - %span.light (#{merge_request.project.name_with_namespace}) 29 + - if merge_request.for_fork?
  30 + %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} &rarr; #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch})
  31 + - else
  32 + %span.light (#{merge_request.source_branch} &rarr; #{merge_request.target_branch})
30 - @issues.each do |issue| 33 - @issues.each do |issue|
31 %li 34 %li
32 issue: 35 issue:
app/views/shared/_merge_requests.html.haml
1 - if @merge_requests.any? 1 - if @merge_requests.any?
2 - - @merge_requests.group_by(&:project).each do |group| 2 + - @merge_requests.group_by(&:target_project).each do |group|
3 .ui-box.small-box 3 .ui-box.small-box
4 - project = group[0] 4 - project = group[0]
5 .title 5 .title
config/routes.rb
@@ -262,6 +262,7 @@ Gitlab::Application.routes.draw do @@ -262,6 +262,7 @@ Gitlab::Application.routes.draw do
262 collection do 262 collection do
263 get :branch_from 263 get :branch_from
264 get :branch_to 264 get :branch_to
  265 + get :update_branches
265 end 266 end
266 end 267 end
267 268
db/fixtures/development/10_merge_requests.rb
@@ -23,7 +23,8 @@ Gitlab::Seeder.quiet do @@ -23,7 +23,8 @@ Gitlab::Seeder.quiet do
23 id: i, 23 id: i,
24 source_branch: branches.first, 24 source_branch: branches.first,
25 target_branch: branches.last, 25 target_branch: branches.last,
26 - project_id: project.id, 26 + source_project_id: project.id,
  27 + target_project_id: project.id,
27 author_id: user_id, 28 author_id: user_id,
28 assignee_id: user_id, 29 assignee_id: user_id,
29 milestone: project.milestones.sample, 30 milestone: project.milestones.sample,
db/fixtures/test/001_repo.rb
@@ -19,5 +19,18 @@ FileUtils.cd(REPO_PATH) do @@ -19,5 +19,18 @@ FileUtils.cd(REPO_PATH) do
19 # Remove the copy 19 # Remove the copy
20 FileUtils.rm(SEED_REPO) 20 FileUtils.rm(SEED_REPO)
21 end 21 end
  22 +puts ' done.'
  23 +print "Creating seed satellite..."
  24 +
  25 +SATELLITE_PATH = Rails.root.join('tmp', 'satellite')
  26 +# Make directory
  27 +FileUtils.mkdir_p(SATELLITE_PATH)
  28 +# Clear any potential directory
  29 +FileUtils.rm_rf("#{SATELLITE_PATH}/gitlabhq")
  30 +# Chdir, clone from the seed
  31 +FileUtils.cd(SATELLITE_PATH) do
  32 + # Clone the satellite
22 33
  34 + `git clone --quiet #{REPO_PATH}/gitlabhq #{SATELLITE_PATH}/gitlabhq`
  35 +end
23 puts ' done.' 36 puts ' done.'
db/migrate/20130419190306_allow_merges_for_forks.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -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
@@ -55,8 +55,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -55,8 +55,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
55 t.integer "assignee_id" 55 t.integer "assignee_id"
56 t.integer "author_id" 56 t.integer "author_id"
57 t.integer "project_id" 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 t.integer "position", :default => 0 60 t.integer "position", :default => 0
61 t.string "branch_name" 61 t.string "branch_name"
62 t.text "description" 62 t.text "description"
@@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
73 73
74 create_table "keys", :force => true do |t| 74 create_table "keys", :force => true do |t|
75 t.integer "user_id" 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 t.text "key" 78 t.text "key"
79 t.string "title" 79 t.string "title"
80 t.string "type" 80 t.string "type"
@@ -84,27 +84,28 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -84,27 +84,28 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
84 add_index "keys", ["user_id"], :name => "index_keys_on_user_id" 84 add_index "keys", ["user_id"], :name => "index_keys_on_user_id"
85 85
86 create_table "merge_requests", :force => true do |t| 86 create_table "merge_requests", :force => true do |t|
87 - t.string "target_branch", :null => false  
88 - t.string "source_branch", :null => false  
89 - t.integer "project_id", :null => false 87 + t.string "target_branch", :null => false
  88 + t.string "source_branch", :null => false
  89 + t.integer "source_project_id", :null => false
90 t.integer "author_id" 90 t.integer "author_id"
91 t.integer "assignee_id" 91 t.integer "assignee_id"
92 t.string "title" 92 t.string "title"
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 t.integer "milestone_id" 97 t.integer "milestone_id"
98 t.string "state" 98 t.string "state"
99 t.string "merge_status" 99 t.string "merge_status"
  100 + t.integer "target_project_id", :null => false
100 end 101 end
101 102
102 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id" 103 add_index "merge_requests", ["assignee_id"], :name => "index_merge_requests_on_assignee_id"
103 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id" 104 add_index "merge_requests", ["author_id"], :name => "index_merge_requests_on_author_id"
104 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at" 105 add_index "merge_requests", ["created_at"], :name => "index_merge_requests_on_created_at"
105 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id" 106 add_index "merge_requests", ["milestone_id"], :name => "index_merge_requests_on_milestone_id"
106 - add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id"  
107 add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch" 107 add_index "merge_requests", ["source_branch"], :name => "index_merge_requests_on_source_branch"
  108 + add_index "merge_requests", ["source_project_id"], :name => "index_merge_requests_on_project_id"
108 add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch" 109 add_index "merge_requests", ["target_branch"], :name => "index_merge_requests_on_target_branch"
109 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title" 110 add_index "merge_requests", ["title"], :name => "index_merge_requests_on_title"
110 111
@@ -140,8 +141,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -140,8 +141,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
140 t.text "note" 141 t.text "note"
141 t.string "noteable_type" 142 t.string "noteable_type"
142 t.integer "author_id" 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 t.integer "project_id" 146 t.integer "project_id"
146 t.string "attachment" 147 t.string "attachment"
147 t.string "line_code" 148 t.string "line_code"
@@ -162,8 +163,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -162,8 +163,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
162 t.string "name" 163 t.string "name"
163 t.string "path" 164 t.string "path"
164 t.text "description" 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 t.integer "creator_id" 168 t.integer "creator_id"
168 t.string "default_branch" 169 t.string "default_branch"
169 t.boolean "issues_enabled", :default => true, :null => false 170 t.boolean "issues_enabled", :default => true, :null => false
@@ -212,8 +213,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -212,8 +213,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
212 t.text "content", :limit => 2147483647 213 t.text "content", :limit => 2147483647
213 t.integer "author_id", :null => false 214 t.integer "author_id", :null => false
214 t.integer "project_id" 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 t.string "file_name" 218 t.string "file_name"
218 t.datetime "expires_at" 219 t.datetime "expires_at"
219 t.boolean "private", :default => true, :null => false 220 t.boolean "private", :default => true, :null => false
@@ -235,6 +236,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -235,6 +236,9 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
235 t.datetime "created_at" 236 t.datetime "created_at"
236 end 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 create_table "tags", :force => true do |t| 242 create_table "tags", :force => true do |t|
239 t.string "name" 243 t.string "name"
240 end 244 end
@@ -266,37 +270,37 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -266,37 +270,37 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
266 end 270 end
267 271
268 create_table "users", :force => true do |t| 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 t.string "reset_password_token" 275 t.string "reset_password_token"
272 t.datetime "reset_password_sent_at" 276 t.datetime "reset_password_sent_at"
273 t.datetime "remember_created_at" 277 t.datetime "remember_created_at"
274 - t.integer "sign_in_count", :default => 0 278 + t.integer "sign_in_count", :default => 0
275 t.datetime "current_sign_in_at" 279 t.datetime "current_sign_in_at"
276 t.datetime "last_sign_in_at" 280 t.datetime "last_sign_in_at"
277 t.string "current_sign_in_ip" 281 t.string "current_sign_in_ip"
278 t.string "last_sign_in_ip" 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 t.string "name" 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 t.string "authentication_token" 291 t.string "authentication_token"
288 - t.integer "theme_id", :default => 1, :null => false 292 + t.integer "theme_id", :default => 1, :null => false
289 t.string "bio" 293 t.string "bio"
290 - t.integer "failed_attempts", :default => 0 294 + t.integer "failed_attempts", :default => 0
291 t.datetime "locked_at" 295 t.datetime "locked_at"
292 t.string "extern_uid" 296 t.string "extern_uid"
293 t.string "provider" 297 t.string "provider"
294 t.string "username" 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 t.string "state" 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 t.datetime "password_expires_at" 304 t.datetime "password_expires_at"
301 t.integer "created_by_id" 305 t.integer "created_by_id"
302 end 306 end
@@ -304,6 +308,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -304,6 +308,7 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
304 add_index "users", ["admin"], :name => "index_users_on_admin" 308 add_index "users", ["admin"], :name => "index_users_on_admin"
305 add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true 309 add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
306 add_index "users", ["email"], :name => "index_users_on_email", :unique => true 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 add_index "users", ["name"], :name => "index_users_on_name" 312 add_index "users", ["name"], :name => "index_users_on_name"
308 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true 313 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
309 add_index "users", ["username"], :name => "index_users_on_username" 314 add_index "users", ["username"], :name => "index_users_on_username"
@@ -322,8 +327,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -322,8 +327,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
322 create_table "users_projects", :force => true do |t| 327 create_table "users_projects", :force => true do |t|
323 t.integer "user_id", :null => false 328 t.integer "user_id", :null => false
324 t.integer "project_id", :null => false 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 t.integer "project_access", :default => 0, :null => false 332 t.integer "project_access", :default => 0, :null => false
328 t.integer "notification_level", :default => 3, :null => false 333 t.integer "notification_level", :default => 3, :null => false
329 end 334 end
@@ -335,8 +340,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do @@ -335,8 +340,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130804151314) do
335 create_table "web_hooks", :force => true do |t| 340 create_table "web_hooks", :force => true do |t|
336 t.string "url" 341 t.string "url"
337 t.integer "project_id" 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 t.string "type", :default => "ProjectHook" 345 t.string "type", :default => "ProjectHook"
341 t.integer "service_id" 346 t.integer "service_id"
342 end 347 end
features/dashboard/dashboard.feature
@@ -16,6 +16,7 @@ Feature: Dashboard @@ -16,6 +16,7 @@ Feature: Dashboard
16 And I visit dashboard page 16 And I visit dashboard page
17 Then I should see groups list 17 Then I should see groups list
18 18
  19 + @javascript
19 Scenario: I should see last push widget 20 Scenario: I should see last push widget
20 Then I should see last push widget 21 Then I should see last push widget
21 And I click "Create Merge Request" link 22 And I click "Create Merge Request" link
features/project/forked_merge_requests.feature 0 → 100644
@@ -0,0 +1,43 @@ @@ -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,6 +29,7 @@ Feature: Project Merge Requests
29 And I click link "Close" 29 And I click link "Close"
30 Then I should see closed merge request "Bug NS-04" 30 Then I should see closed merge request "Bug NS-04"
31 31
  32 + @javascript
32 Scenario: I submit new unassigned merge request 33 Scenario: I submit new unassigned merge request
33 Given I click link "New Merge Request" 34 Given I click link "New Merge Request"
34 And I submit new merge request "Wiki Feature" 35 And I submit new merge request "Wiki Feature"
features/steps/dashboard/dashboard.rb
@@ -22,6 +22,7 @@ class Dashboard &lt; Spinach::FeatureSteps @@ -22,6 +22,7 @@ class Dashboard &lt; Spinach::FeatureSteps
22 22
23 Then 'I see prefilled new Merge Request page' do 23 Then 'I see prefilled new Merge Request page' do
24 current_path.should == new_project_merge_request_path(@project) 24 current_path.should == new_project_merge_request_path(@project)
  25 + find("#merge_request_target_project_id").value.should == @project.id.to_s
25 find("#merge_request_source_branch").value.should == "new_design" 26 find("#merge_request_source_branch").value.should == "new_design"
26 find("#merge_request_target_branch").value.should == "master" 27 find("#merge_request_target_branch").value.should == "master"
27 find("#merge_request_title").value.should == "New Design" 28 find("#merge_request_title").value.should == "New Design"
features/steps/dashboard/dashboard_event_filters.rb
@@ -61,7 +61,7 @@ class EventFilters &lt; Spinach::FeatureSteps @@ -61,7 +61,7 @@ class EventFilters &lt; Spinach::FeatureSteps
61 end 61 end
62 62
63 And 'this project has merge request event' do 63 And 'this project has merge request event' do
64 - merge_request = create :merge_request, author: @user, project: @project 64 + merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
65 Event.create( 65 Event.create(
66 project: @project, 66 project: @project,
67 action: Event::MERGED, 67 action: Event::MERGED,
features/steps/dashboard/dashboard_merge_requests.rb
@@ -6,18 +6,24 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps @@ -6,18 +6,24 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps
6 merge_requests = @user.merge_requests 6 merge_requests = @user.merge_requests
7 merge_requests.each do |mr| 7 merge_requests.each do |mr|
8 page.should have_content(mr.title[0..10]) 8 page.should have_content(mr.title[0..10])
9 - page.should have_content(mr.project.name) 9 + page.should have_content(mr.target_project.name)
  10 + page.should have_content(mr.source_project.name)
10 end 11 end
11 end 12 end
12 13
13 And 'I have authored merge requests' do 14 And 'I have authored merge requests' do
14 - project1 = create :project  
15 - project2 = create :project 15 + project1_source = create :project
  16 + project1_target= create :project
  17 + project2_source = create :project
  18 + project2_target = create :project
16 19
17 - project1.team << [@user, :master]  
18 - project2.team << [@user, :master]  
19 20
20 - merge_request1 = create :merge_request, author: @user, project: project1  
21 - merge_request2 = create :merge_request, author: @user, project: project2 21 + project1_source.team << [@user, :master]
  22 + project1_target.team << [@user, :master]
  23 + project2_source.team << [@user, :master]
  24 + project2_target.team << [@user, :master]
  25 +
  26 + merge_request1 = create :merge_request, author: @user, source_project: project1_source, target_project: project1_target
  27 + merge_request2 = create :merge_request, author: @user, source_project: project2_source, target_project: project2_target
22 end 28 end
23 end 29 end
features/steps/group/group.rb
@@ -60,7 +60,8 @@ class Groups &lt; Spinach::FeatureSteps @@ -60,7 +60,8 @@ class Groups &lt; Spinach::FeatureSteps
60 60
61 Given 'project from group has merge requests assigned to me' do 61 Given 'project from group has merge requests assigned to me' do
62 create :merge_request, 62 create :merge_request,
63 - project: project, 63 + source_project: project,
  64 + target_project: project,
64 assignee: current_user, 65 assignee: current_user,
65 author: current_user 66 author: current_user
66 end 67 end
features/steps/project/project_fork.rb
@@ -4,6 +4,8 @@ class ForkProject &lt; Spinach::FeatureSteps @@ -4,6 +4,8 @@ class ForkProject &lt; Spinach::FeatureSteps
4 include SharedProject 4 include SharedProject
5 5
6 step 'I click link "Fork"' do 6 step 'I click link "Fork"' do
  7 + page.should have_content "Shop"
  8 + page.should have_content "Fork"
7 Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true) 9 Gitlab::Shell.any_instance.stub(:fork_repository).and_return(true)
8 click_link "Fork" 10 click_link "Fork"
9 end 11 end
@@ -17,9 +19,13 @@ class ForkProject &lt; Spinach::FeatureSteps @@ -17,9 +19,13 @@ class ForkProject &lt; Spinach::FeatureSteps
17 step 'I should see the forked project page' do 19 step 'I should see the forked project page' do
18 page.should have_content "Project was successfully forked." 20 page.should have_content "Project was successfully forked."
19 current_path.should include current_user.namespace.path 21 current_path.should include current_user.namespace.path
  22 + @forked_project = Project.find_by_namespace_id(current_user.namespace.path)
20 end 23 end
21 24
22 step 'I already have a project named "Shop" in my namespace' do 25 step 'I already have a project named "Shop" in my namespace' do
  26 + current_user.namespace ||= create(:namespace)
  27 + current_user.namespace.should_not be_nil
  28 + current_user.namespace.path.should_not be_nil
23 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace) 29 @my_project = create(:project_with_code, name: "Shop", namespace: current_user.namespace)
24 end 30 end
25 31
features/steps/project/project_forked_merge_requests.rb 0 → 100644
@@ -0,0 +1,184 @@ @@ -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,30 +56,41 @@ class ProjectMergeRequests &lt; Spinach::FeatureSteps
56 end 56 end
57 57
58 And 'I submit new merge request "Wiki Feature"' do 58 And 'I submit new merge request "Wiki Feature"' do
59 - fill_in "merge_request_title", with: "Wiki Feature"  
60 - select "bootstrap", from: "merge_request_source_branch"  
61 - select "master", from: "merge_request_target_branch" 59 + #this must come first, so that the target branch is set by the time the "select" for "notes_refactoring" is executed
  60 + select project.path_with_namespace, :from => "merge_request_target_project_id"
  61 + fill_in "merge_request_title", :with => "Wiki Feature"
  62 + select "master", :from => "merge_request_source_branch"
  63 + find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
  64 + find(:select, "merge_request_source_project_id", {}).value.should == project.id.to_s
  65 +
  66 + #using "notes_refactoring" because "Bug NS-04" uses master/stable, this will fail merge_request validation if the branches are the same
  67 + find(:select, "merge_request_target_branch", {}).find(:option, "notes_refactoring", {}).value.should == "notes_refactoring"
  68 + select "notes_refactoring", :from => "merge_request_target_branch"
  69 +
62 click_button "Submit merge request" 70 click_button "Submit merge request"
63 end 71 end
64 72
65 And 'project "Shop" have "Bug NS-04" open merge request' do 73 And 'project "Shop" have "Bug NS-04" open merge request' do
66 create(:merge_request, 74 create(:merge_request,
67 title: "Bug NS-04", 75 title: "Bug NS-04",
68 - project: project, 76 + source_project: project,
  77 + target_project: project,
69 author: project.users.first) 78 author: project.users.first)
70 end 79 end
71 80
72 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do 81 And 'project "Shop" have "Bug NS-05" open merge request with diffs inside' do
73 create(:merge_request_with_diffs, 82 create(:merge_request_with_diffs,
74 title: "Bug NS-05", 83 title: "Bug NS-05",
75 - project: project, 84 + source_project: project,
  85 + target_project: project,
76 author: project.users.first) 86 author: project.users.first)
77 end 87 end
78 88
79 And 'project "Shop" have "Feature NS-03" closed merge request' do 89 And 'project "Shop" have "Feature NS-03" closed merge request' do
80 create(:closed_merge_request, 90 create(:closed_merge_request,
81 title: "Feature NS-03", 91 title: "Feature NS-03",
82 - project: project, 92 + source_project: project,
  93 + target_project: project,
83 author: project.users.first) 94 author: project.users.first)
84 end 95 end
85 96
features/steps/project/project_network_graph.rb
@@ -15,11 +15,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps @@ -15,11 +15,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps
15 end 15 end
16 16
17 And 'page should select "master" in select box' do 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 end 19 end
20 20
21 And 'page should select "v2.1.0" in select box' do 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 end 23 end
24 24
25 And 'page should have "master" on graph' do 25 And 'page should have "master" on graph' do
@@ -61,11 +61,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps @@ -61,11 +61,11 @@ class ProjectNetworkGraph &lt; Spinach::FeatureSteps
61 end 61 end
62 62
63 And 'page should select "stable" in select box' do 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 end 65 end
66 66
67 And 'page should select "v2.1.0" in select box' do 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 end 69 end
70 70
71 And 'page should have "stable" on graph' do 71 And 'page should have "stable" on graph' do
features/steps/shared/paths.rb
@@ -184,6 +184,10 @@ module SharedPaths @@ -184,6 +184,10 @@ module SharedPaths
184 visit project_path(project) 184 visit project_path(project)
185 end 185 end
186 186
  187 + step 'I visit project "Forked Shop" merge requests page' do
  188 + visit project_merge_requests_path(@forked_project)
  189 + end
  190 +
187 step 'I visit edit project "Shop" page' do 191 step 'I visit edit project "Shop" page' do
188 visit edit_project_path(project) 192 visit edit_project_path(project)
189 end 193 end
@@ -239,18 +243,22 @@ module SharedPaths @@ -239,18 +243,22 @@ module SharedPaths
239 243
240 step 'I visit merge request page "Bug NS-04"' do 244 step 'I visit merge request page "Bug NS-04"' do
241 mr = MergeRequest.find_by_title("Bug NS-04") 245 mr = MergeRequest.find_by_title("Bug NS-04")
242 - visit project_merge_request_path(mr.project, mr) 246 + visit project_merge_request_path(mr.target_project, mr)
243 end 247 end
244 248
245 step 'I visit merge request page "Bug NS-05"' do 249 step 'I visit merge request page "Bug NS-05"' do
246 mr = MergeRequest.find_by_title("Bug NS-05") 250 mr = MergeRequest.find_by_title("Bug NS-05")
247 - visit project_merge_request_path(mr.project, mr) 251 + visit project_merge_request_path(mr.target_project, mr)
248 end 252 end
249 253
250 step 'I visit project "Shop" merge requests page' do 254 step 'I visit project "Shop" merge requests page' do
251 visit project_merge_requests_path(project) 255 visit project_merge_requests_path(project)
252 end 256 end
253 257
  258 + step 'I visit forked project "Shop" merge requests page' do
  259 + visit project_merge_requests_path(project)
  260 + end
  261 +
254 step 'I visit project "Shop" milestones page' do 262 step 'I visit project "Shop" milestones page' do
255 visit project_milestones_path(project) 263 visit project_milestones_path(project)
256 end 264 end
features/support/env.rb
@@ -14,7 +14,7 @@ require &#39;spinach/capybara&#39; @@ -14,7 +14,7 @@ require &#39;spinach/capybara&#39;
14 require 'sidekiq/testing/inline' 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 require Rails.root.join('spec', 'support', f) 18 require Rails.root.join('spec', 'support', f)
19 end 19 end
20 20
@@ -35,8 +35,7 @@ Capybara.ignore_hidden_elements = false @@ -35,8 +35,7 @@ Capybara.ignore_hidden_elements = false
35 DatabaseCleaner.strategy = :truncation 35 DatabaseCleaner.strategy = :truncation
36 36
37 Spinach.hooks.before_scenario do 37 Spinach.hooks.before_scenario do
38 - TestEnv.init(mailer: false)  
39 - 38 + TestEnv.setup_stubs
40 DatabaseCleaner.start 39 DatabaseCleaner.start
41 end 40 end
42 41
@@ -45,6 +44,7 @@ Spinach.hooks.after_scenario do @@ -45,6 +44,7 @@ Spinach.hooks.after_scenario do
45 end 44 end
46 45
47 Spinach.hooks.before_run do 46 Spinach.hooks.before_run do
  47 + TestEnv.init(mailer: false, init_repos: true, repos: false)
48 RSpec::Mocks::setup self 48 RSpec::Mocks::setup self
49 49
50 include FactoryGirl::Syntax::Methods 50 include FactoryGirl::Syntax::Methods
lib/api/merge_requests.rb
@@ -14,6 +14,14 @@ module API @@ -14,6 +14,14 @@ module API
14 end 14 end
15 not_found! 15 not_found!
16 end 16 end
  17 +
  18 + def not_fork?(target_project_id, user_project)
  19 + target_project_id.nil? || target_project_id == user_project.id.to_s
  20 + end
  21 +
  22 + def target_matches_fork(target_project_id,user_project)
  23 + user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
  24 + end
17 end 25 end
18 26
19 # List merge requests 27 # List merge requests
@@ -51,9 +59,10 @@ module API @@ -51,9 +59,10 @@ module API
51 # 59 #
52 # Parameters: 60 # Parameters:
53 # 61 #
54 - # id (required) - The ID of a project 62 + # id (required) - The ID of a project - this will be the source of the merge request
55 # source_branch (required) - The source branch 63 # source_branch (required) - The source branch
56 # target_branch (required) - The target branch 64 # target_branch (required) - The target branch
  65 + # target_project - The target project of the merge request defaults to the :id of the project
57 # assignee_id - Assignee user ID 66 # assignee_id - Assignee user ID
58 # title (required) - Title of MR 67 # title (required) - Title of MR
59 # 68 #
@@ -63,10 +72,20 @@ module API @@ -63,10 +72,20 @@ module API
63 post ":id/merge_requests" do 72 post ":id/merge_requests" do
64 authorize! :write_merge_request, user_project 73 authorize! :write_merge_request, user_project
65 required_attributes! [:source_branch, :target_branch, :title] 74 required_attributes! [:source_branch, :target_branch, :title]
66 -  
67 - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title] 75 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
68 merge_request = user_project.merge_requests.new(attrs) 76 merge_request = user_project.merge_requests.new(attrs)
69 merge_request.author = current_user 77 merge_request.author = current_user
  78 + merge_request.source_project = user_project
  79 + target_project_id = attrs[:target_project_id]
  80 + if not_fork?(target_project_id, user_project)
  81 + merge_request.target_project = user_project
  82 + else
  83 + if target_matches_fork(target_project_id,user_project)
  84 + merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
  85 + else
  86 + render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
  87 + end
  88 + end
70 89
71 if merge_request.save 90 if merge_request.save
72 merge_request.reload_code 91 merge_request.reload_code
lib/gitlab/satellite/action.rb
@@ -25,25 +25,31 @@ module Gitlab @@ -25,25 +25,31 @@ module Gitlab
25 end 25 end
26 end 26 end
27 rescue Errno::ENOMEM => ex 27 rescue Errno::ENOMEM => ex
28 - Gitlab::GitLogger.error(ex.message)  
29 - return false 28 + return handle_exception(ex)
30 rescue Grit::Git::GitTimeout => ex 29 rescue Grit::Git::GitTimeout => ex
31 - Gitlab::GitLogger.error(ex.message)  
32 - return false 30 + return handle_exception(ex)
33 ensure 31 ensure
34 Gitlab::ShellEnv.reset_env 32 Gitlab::ShellEnv.reset_env
35 end 33 end
36 34
37 - # * Clears the satellite  
38 - # * Updates the satellite from Gitolite 35 + # * Recreates the satellite
39 # * Sets up Git variables for the user 36 # * Sets up Git variables for the user
40 # 37 #
41 # Note: use this within #in_locked_and_timed_satellite 38 # Note: use this within #in_locked_and_timed_satellite
42 def prepare_satellite!(repo) 39 def prepare_satellite!(repo)
43 project.satellite.clear_and_update! 40 project.satellite.clear_and_update!
44 41
45 - repo.git.config({}, "user.name", user.name)  
46 - repo.git.config({}, "user.email", user.email) 42 + repo.config['user.name'] = user.name
  43 + repo.config['user.email'] = user.email
  44 + end
  45 +
  46 + def default_options(options = {})
  47 + {raise: true, timeout: true}.merge(options)
  48 + end
  49 +
  50 + def handle_exception(exception)
  51 + Gitlab::GitLogger.error(exception.message)
  52 + false
47 end 53 end
48 end 54 end
49 end 55 end
lib/gitlab/satellite/merge_action.rb
@@ -5,48 +5,120 @@ module Gitlab @@ -5,48 +5,120 @@ module Gitlab
5 attr_accessor :merge_request 5 attr_accessor :merge_request
6 6
7 def initialize(user, merge_request) 7 def initialize(user, merge_request)
8 - super user, merge_request.project 8 + super user, merge_request.target_project
9 @merge_request = merge_request 9 @merge_request = merge_request
10 end 10 end
11 11
12 # Checks if a merge request can be executed without user interaction 12 # Checks if a merge request can be executed without user interaction
13 def can_be_merged? 13 def can_be_merged?
14 in_locked_and_timed_satellite do |merge_repo| 14 in_locked_and_timed_satellite do |merge_repo|
  15 + prepare_satellite!(merge_repo)
15 merge_in_satellite!(merge_repo) 16 merge_in_satellite!(merge_repo)
16 end 17 end
17 end 18 end
18 19
19 # Merges the source branch into the target branch in the satellite and 20 # Merges the source branch into the target branch in the satellite and
20 - # pushes it back to Gitolite.  
21 - # It also removes the source branch if requested in the merge request. 21 + # pushes it back to the repository.
  22 + # It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
22 # 23 #
23 # Returns false if the merge produced conflicts 24 # Returns false if the merge produced conflicts
24 - # Returns false if pushing from the satellite to Gitolite failed or was rejected 25 + # Returns false if pushing from the satellite to the repository failed or was rejected
25 # Returns true otherwise 26 # Returns true otherwise
26 def merge! 27 def merge!
27 in_locked_and_timed_satellite do |merge_repo| 28 in_locked_and_timed_satellite do |merge_repo|
  29 + prepare_satellite!(merge_repo)
28 if merge_in_satellite!(merge_repo) 30 if merge_in_satellite!(merge_repo)
29 # push merge back to Gitolite 31 # push merge back to Gitolite
30 # will raise CommandFailed when push fails 32 # will raise CommandFailed when push fails
31 - merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)  
32 - 33 + merge_repo.git.push(default_options, :origin, merge_request.target_branch)
33 # remove source branch 34 # remove source branch
34 if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch) 35 if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
35 # will raise CommandFailed when push fails 36 # will raise CommandFailed when push fails
36 - merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}") 37 + merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
37 end 38 end
38 -  
39 # merge, push and branch removal successful 39 # merge, push and branch removal successful
40 true 40 true
41 end 41 end
42 end 42 end
43 rescue Grit::Git::CommandFailed => ex 43 rescue Grit::Git::CommandFailed => ex
44 - Gitlab::GitLogger.error(ex.message)  
45 - false 44 + handle_exception(ex)
46 end 45 end
47 46
48 - private 47 + # Get a raw diff of the source to the target
  48 + def diff_in_satellite
  49 + in_locked_and_timed_satellite do |merge_repo|
  50 + prepare_satellite!(merge_repo)
  51 + 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 # Merges the source_branch into the target_branch in the satellite. 122 # Merges the source_branch into the target_branch in the satellite.
51 # 123 #
52 # Note: it will clear out the satellite before doing anything 124 # Note: it will clear out the satellite before doing anything
@@ -54,18 +126,35 @@ module Gitlab @@ -54,18 +126,35 @@ module Gitlab
54 # Returns false if the merge produced conflicts 126 # Returns false if the merge produced conflicts
55 # Returns true otherwise 127 # Returns true otherwise
56 def merge_in_satellite!(repo) 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 # will raise CommandFailed when merge fails 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 rescue Grit::Git::CommandFailed => ex 138 rescue Grit::Git::CommandFailed => ex
66 - Gitlab::GitLogger.error(ex.message)  
67 - false 139 + handle_exception(ex)
68 end 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 end 158 end
70 end 159 end
71 end 160 end
lib/gitlab/satellite/satellite.rb
1 module Gitlab 1 module Gitlab
2 - class SatelliteNotExistError < StandardError; end 2 + class SatelliteNotExistError < StandardError; end
3 3
4 module Satellite 4 module Satellite
5 class Satellite 5 class Satellite
@@ -24,8 +24,11 @@ module Gitlab @@ -24,8 +24,11 @@ module Gitlab
24 def clear_and_update! 24 def clear_and_update!
25 raise_no_satellite unless exists? 25 raise_no_satellite unless exists?
26 26
  27 + File.exists? path
  28 + @repo = nil
27 clear_working_dir! 29 clear_working_dir!
28 delete_heads! 30 delete_heads!
  31 + remove_remotes!
29 update_from_source! 32 update_from_source!
30 end 33 end
31 34
@@ -55,10 +58,11 @@ module Gitlab @@ -55,10 +58,11 @@ module Gitlab
55 raise_no_satellite unless exists? 58 raise_no_satellite unless exists?
56 59
57 File.open(lock_file, "w+") do |f| 60 File.open(lock_file, "w+") do |f|
58 - f.flock(File::LOCK_EX)  
59 -  
60 - Dir.chdir(path) do  
61 - return yield 61 + begin
  62 + f.flock File::LOCK_EX
  63 + Dir.chdir(path) { return yield }
  64 + ensure
  65 + f.flock File::LOCK_UN
62 end 66 end
63 end 67 end
64 end 68 end
@@ -100,20 +104,34 @@ module Gitlab @@ -100,20 +104,34 @@ module Gitlab
100 if heads.include? PARKING_BRANCH 104 if heads.include? PARKING_BRANCH
101 repo.git.checkout({}, PARKING_BRANCH) 105 repo.git.checkout({}, PARKING_BRANCH)
102 else 106 else
103 - repo.git.checkout({b: true}, PARKING_BRANCH) 107 + repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
104 end 108 end
105 109
106 # remove the parking branch from the list of heads ... 110 # remove the parking branch from the list of heads ...
107 heads.delete(PARKING_BRANCH) 111 heads.delete(PARKING_BRANCH)
108 # ... and delete all others 112 # ... and delete all others
109 - heads.each { |head| repo.git.branch({D: true}, head) } 113 + heads.each { |head| repo.git.branch(default_options({D: true}), head) }
  114 + end
  115 +
  116 + # Deletes all remotes except origin
  117 + #
  118 + # This ensures we have no remote name clashes or issues updating branches when
  119 + # working with the satellite.
  120 + def remove_remotes!
  121 + remotes = repo.git.remote.split(' ')
  122 + remotes.delete('origin')
  123 + remotes.each { |name| repo.git.remote(default_options,'rm', name)}
110 end 124 end
111 125
112 # Updates the satellite from Gitolite 126 # Updates the satellite from Gitolite
113 # 127 #
114 # Note: this will only update remote branches (i.e. origin/*) 128 # Note: this will only update remote branches (i.e. origin/*)
115 def update_from_source! 129 def update_from_source!
116 - repo.git.fetch({timeout: true}, :origin) 130 + repo.git.fetch(default_options, :origin)
  131 + end
  132 +
  133 + def default_options(options = {})
  134 + {raise: true, timeout: true}.merge(options)
117 end 135 end
118 136
119 # Create directory for storing 137 # Create directory for storing
spec/contexts/filter_context_spec.rb 0 → 100644
@@ -0,0 +1,59 @@ @@ -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 +7,6 @@ describe Projects::CommitController do
7 7
8 before do 8 before do
9 sign_in(user) 9 sign_in(user)
10 -  
11 project.team << [user, :master] 10 project.team << [user, :master]
12 end 11 end
13 12
spec/controllers/commits_controller_spec.rb
@@ -2,7 +2,7 @@ require &#39;spec_helper&#39; @@ -2,7 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe Projects::CommitsController do 3 describe Projects::CommitsController do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 - let(:user) { create(:user) } 5 + let(:user) { create(:user) }
6 6
7 before do 7 before do
8 sign_in(user) 8 sign_in(user)
spec/controllers/merge_requests_controller_spec.rb
@@ -3,7 +3,7 @@ require &#39;spec_helper&#39; @@ -3,7 +3,7 @@ require &#39;spec_helper&#39;
3 describe Projects::MergeRequestsController do 3 describe Projects::MergeRequestsController do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 let(:user) { create(:user) } 5 let(:user) { create(:user) }
6 - let(:merge_request) { create(:merge_request_with_diffs, project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") } 6 + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project, target_branch: "bcf03b5d~3", source_branch: "bcf03b5d") }
7 7
8 before do 8 before do
9 sign_in(user) 9 sign_in(user)
@@ -28,7 +28,7 @@ describe Projects::MergeRequestsController do @@ -28,7 +28,7 @@ describe Projects::MergeRequestsController do
28 it "should render it" do 28 it "should render it" do
29 get :show, project_id: project.code, id: merge_request.id, format: format 29 get :show, project_id: project.code, id: merge_request.id, format: format
30 30
31 - expect(response.body).to eq(merge_request.send(:"to_#{format}")) 31 + expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s)
32 end 32 end
33 33
34 it "should not escape Html" do 34 it "should not escape Html" do
spec/factories.rb
@@ -29,8 +29,19 @@ FactoryGirl.define do @@ -29,8 +29,19 @@ FactoryGirl.define do
29 sequence(:name) { |n| "project#{n}" } 29 sequence(:name) { |n| "project#{n}" }
30 path { name.downcase.gsub(/\s/, '_') } 30 path { name.downcase.gsub(/\s/, '_') }
31 creator 31 creator
  32 +
  33 + trait :source do
  34 + sequence(:name) { |n| "source project#{n}" }
  35 + end
  36 + trait :target do
  37 + sequence(:name) { |n| "target project#{n}" }
  38 + end
  39 +
  40 + factory :source_project, traits: [:source]
  41 + factory :target_project, traits: [:target]
32 end 42 end
33 43
  44 +
34 factory :redmine_project, parent: :project do 45 factory :redmine_project, parent: :project do
35 issues_tracker { "redmine" } 46 issues_tracker { "redmine" }
36 issues_tracker_id { "project_name_in_redmine" } 47 issues_tracker_id { "project_name_in_redmine" }
@@ -39,11 +50,20 @@ FactoryGirl.define do @@ -39,11 +50,20 @@ FactoryGirl.define do
39 factory :project_with_code, parent: :project do 50 factory :project_with_code, parent: :project do
40 path { 'gitlabhq' } 51 path { 'gitlabhq' }
41 52
  53 + trait :source_path do
  54 + path { 'source_gitlabhq' }
  55 + end
  56 +
  57 + trait :target_path do
  58 + path { 'target_gitlabhq' }
  59 + end
  60 +
  61 + factory :source_project_with_code, traits: [:source, :source_path]
  62 + factory :target_project_with_code, traits: [:target, :target_path]
  63 +
42 after :create do |project| 64 after :create do |project|
43 - repos_path = Rails.root.join('tmp', 'test-git-base-path')  
44 - seed_repo = Rails.root.join('tmp', 'repositories', 'gitlabhq')  
45 - target_repo = File.join(repos_path, project.path_with_namespace + '.git')  
46 - system("ln -s #{seed_repo} #{target_repo}") 65 + TestEnv.clear_repo_dir(project.namespace, project.path)
  66 + TestEnv.create_repo(project.namespace, project.path)
47 end 67 end
48 end 68 end
49 69
@@ -86,7 +106,8 @@ FactoryGirl.define do @@ -86,7 +106,8 @@ FactoryGirl.define do
86 factory :merge_request do 106 factory :merge_request do
87 title 107 title
88 author 108 author
89 - project factory: :project_with_code 109 + source_project factory: :source_project_with_code
  110 + target_project factory: :target_project_with_code
90 source_branch "master" 111 source_branch "master"
91 target_branch "stable" 112 target_branch "stable"
92 113
@@ -96,13 +117,13 @@ FactoryGirl.define do @@ -96,13 +117,13 @@ FactoryGirl.define do
96 source_branch "stable" # pretend bcf03b5d 117 source_branch "stable" # pretend bcf03b5d
97 st_commits do 118 st_commits do
98 [ 119 [
99 - project.repository.commit('bcf03b5d').to_hash,  
100 - project.repository.commit('bcf03b5d~1').to_hash,  
101 - project.repository.commit('bcf03b5d~2').to_hash 120 + source_project.repository.commit('bcf03b5d').to_hash,
  121 + source_project.repository.commit('bcf03b5d~1').to_hash,
  122 + source_project.repository.commit('bcf03b5d~2').to_hash
102 ] 123 ]
103 end 124 end
104 st_diffs do 125 st_diffs do
105 - project.repo.diff("bcf03b5d~3", "bcf03b5d") 126 + source_project.repo.diff("bcf03b5d~3", "bcf03b5d")
106 end 127 end
107 end 128 end
108 129
@@ -133,7 +154,7 @@ FactoryGirl.define do @@ -133,7 +154,7 @@ FactoryGirl.define do
133 154
134 trait :on_commit do 155 trait :on_commit do
135 project factory: :project_with_code 156 project factory: :project_with_code
136 - commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a" 157 + commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
137 noteable_type "Commit" 158 noteable_type "Commit"
138 end 159 end
139 160
@@ -143,12 +164,12 @@ FactoryGirl.define do @@ -143,12 +164,12 @@ FactoryGirl.define do
143 164
144 trait :on_merge_request do 165 trait :on_merge_request do
145 project factory: :project_with_code 166 project factory: :project_with_code
146 - noteable_id 1 167 + noteable_id 1
147 noteable_type "MergeRequest" 168 noteable_type "MergeRequest"
148 end 169 end
149 170
150 trait :on_issue do 171 trait :on_issue do
151 - noteable_id 1 172 + noteable_id 1
152 noteable_type "Issue" 173 noteable_type "Issue"
153 end 174 end
154 175
spec/factories_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 INVALID_FACTORIES = [ 3 INVALID_FACTORIES = [
4 - :key_with_a_space_in_the_middle,  
5 - :invalid_key, 4 + :key_with_a_space_in_the_middle,
  5 + :invalid_key,
6 ] 6 ]
7 7
8 FactoryGirl.factories.map(&:name).each do |factory_name| 8 FactoryGirl.factories.map(&:name).each do |factory_name|
spec/features/gitlab_flavored_markdown_spec.rb
@@ -3,11 +3,11 @@ require &#39;spec_helper&#39; @@ -3,11 +3,11 @@ require &#39;spec_helper&#39;
3 describe "GitLab Flavored Markdown" do 3 describe "GitLab Flavored Markdown" do
4 let(:project) { create(:project_with_code) } 4 let(:project) { create(:project_with_code) }
5 let(:issue) { create(:issue, project: project) } 5 let(:issue) { create(:issue, project: project) }
6 - let(:merge_request) { create(:merge_request, project: project) } 6 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
7 let(:fred) do 7 let(:fred) do
8 - u = create(:user, name: "fred")  
9 - project.team << [u, :master]  
10 - u 8 + u = create(:user, name: "fred")
  9 + project.team << [u, :master]
  10 + u
11 end 11 end
12 12
13 before do 13 before do
@@ -83,9 +83,7 @@ describe &quot;GitLab Flavored Markdown&quot; do @@ -83,9 +83,7 @@ describe &quot;GitLab Flavored Markdown&quot; do
83 83
84 describe "for merge requests" do 84 describe "for merge requests" do
85 before do 85 before do
86 - @merge_request = create(:merge_request,  
87 - project: project,  
88 - title: "fix ##{issue.id}") 86 + @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.id}")
89 end 87 end
90 88
91 it "should render title in merge_requests#index" do 89 it "should render title in merge_requests#index" do
spec/features/notes_on_merge_requests_spec.rb
@@ -2,8 +2,8 @@ require &#39;spec_helper&#39; @@ -2,8 +2,8 @@ require &#39;spec_helper&#39;
2 2
3 describe "On a merge request", js: true do 3 describe "On a merge request", js: true do
4 let!(:project) { create(:project_with_code) } 4 let!(:project) { create(:project_with_code) }
5 - let!(:merge_request) { create(:merge_request, project: project) }  
6 - let!(:note) { create(:note_on_merge_request_with_attachment, project: project) } 5 + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
  6 + let!(:note) { create(:note_on_merge_request_with_attachment, project: project) }
7 7
8 before do 8 before do
9 login_as :user 9 login_as :user
@@ -62,7 +62,7 @@ describe &quot;On a merge request&quot;, js: true do @@ -62,7 +62,7 @@ describe &quot;On a merge request&quot;, js: true do
62 62
63 it 'should be added and form reset' do 63 it 'should be added and form reset' do
64 should have_content("This is awsome!") 64 should have_content("This is awsome!")
65 - within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } 65 + within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") }
66 within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } 66 within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
67 within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } 67 within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
68 end 68 end
@@ -135,8 +135,8 @@ describe &quot;On a merge request&quot;, js: true do @@ -135,8 +135,8 @@ describe &quot;On a merge request&quot;, js: true do
135 end 135 end
136 136
137 describe "On a merge request diff", js: true, focus: true do 137 describe "On a merge request diff", js: true, focus: true do
138 - let!(:project) { create(:project_with_code) }  
139 - let!(:merge_request) { create(:merge_request_with_diffs, project: project) } 138 + let!(:project) { create(:source_project_with_code) }
  139 + let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
140 140
141 before do 141 before do
142 login_as :user 142 login_as :user
@@ -144,6 +144,7 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -144,6 +144,7 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
144 visit diffs_project_merge_request_path(project, merge_request) 144 visit diffs_project_merge_request_path(project, merge_request)
145 end 145 end
146 146
  147 +
147 subject { page } 148 subject { page }
148 149
149 describe "when adding a note" do 150 describe "when adding a note" do
@@ -183,6 +184,9 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -183,6 +184,9 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
183 end 184 end
184 185
185 describe "with muliple note forms" do 186 describe "with muliple note forms" do
  187 + let!(:project) { create(:source_project_with_code) }
  188 + let!(:merge_request) { create(:merge_request_with_diffs, source_project: project, target_project: project) }
  189 +
186 before do 190 before do
187 find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click 191 find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185"]').click
188 find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click 192 find('a[data-line-code="342e16cbbd482ac2047dc679b2749d248cc1428f_18_17"]').click
@@ -205,13 +209,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -205,13 +209,13 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
205 209
206 # TODO: fix 210 # TODO: fix
207 #it 'should check if previews were rendered separately' do 211 #it 'should check if previews were rendered separately' do
208 - #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do  
209 - #should have_css(".js-note-preview", text: "One comment on line 185")  
210 - #end 212 + #within("tr[id='4735dfc552ad7bf15ca468adc3cad9d05b624490_185_185'] + .js-temp-notes-holder") do
  213 + #should have_css(".js-note-preview", text: "One comment on line 185")
  214 + #end
211 215
212 - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do  
213 - #should have_css(".js-note-preview", text: "Another comment on line 17")  
214 - #end 216 + #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .js-temp-notes-holder") do
  217 + #should have_css(".js-note-preview", text: "Another comment on line 17")
  218 + #end
215 #end 219 #end
216 end 220 end
217 221
@@ -238,39 +242,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do @@ -238,39 +242,38 @@ describe &quot;On a merge request diff&quot;, js: true, focus: true do
238 242
239 # TODO: fix 243 # TODO: fix
240 #it "should remove last note of a discussion" do 244 #it "should remove last note of a discussion" do
241 - #within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do  
242 - #find(".js-note-delete").click  
243 - #end  
244 -  
245 - #should_not have_css(".note_holder") 245 + # within("tr[id='342e16cbbd482ac2047dc679b2749d248cc1428f_18_17'] + .notes-holder") do
  246 + # find(".js-note-delete").click
  247 + # end
  248 + # should_not have_css(".note_holder")
246 #end 249 #end
247 end 250 end
248 end 251 end
249 252
250 # TODO: fix 253 # TODO: fix
251 #describe "when replying to a note" do 254 #describe "when replying to a note" do
252 - #before do  
253 - ## create first note  
254 - #find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click 255 + #before do
  256 + ## create first note
  257 + # find('a[data-line-code="4735dfc552ad7bf15ca468adc3cad9d05b624490_184_184"]').click
255 258
256 - #within(".js-temp-notes-holder") do  
257 - #fill_in "note[note]", with: "One comment on line 184"  
258 - #click_button("Add Comment")  
259 - #end 259 + # within(".js-temp-notes-holder") do
  260 + # fill_in "note[note]", with: "One comment on line 184"
  261 + # click_button("Add Comment")
  262 + #end
260 263
261 - #within(".js-temp-notes-holder") do  
262 - #find(".js-discussion-reply-button").click  
263 - #fill_in "note[note]", with: "An additional comment in reply"  
264 - #click_button("Add Comment")  
265 - #end  
266 - #end  
267 -  
268 - #it 'should be inserted and form removed from reply' do  
269 - #should have_content("An additional comment in reply")  
270 - #within(".notes_holder") { should have_css(".note", count: 2) }  
271 - #within(".notes_holder") { should have_no_css("form") }  
272 - #within(".notes_holder") { should have_link("Reply") }  
273 - #end 264 + # within(".js-temp-notes-holder") do
  265 + # find(".js-discussion-reply-button").click
  266 + # fill_in "note[note]", with: "An additional comment in reply"
  267 + # click_button("Add Comment")
  268 + # end
  269 + #end
  270 +
  271 + #it 'should be inserted and form removed from reply' do
  272 + # should have_content("An additional comment in reply")
  273 + # within(".notes_holder") { should have_css(".note", count: 2) }
  274 + # within(".notes_holder") { should have_no_css("form") }
  275 + # within(".notes_holder") { should have_link("Reply") }
  276 + # end
274 #end 277 #end
275 end 278 end
276 279
spec/features/profile_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe "Profile account page" do 3 describe "Profile account page" do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 let(:user) { create(:user) } 6 let(:user) { create(:user) }
6 7
7 before do 8 before do
spec/features/projects_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe "Projects" do 3 describe "Projects" do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 before { login_as :user } 6 before { login_as :user }
6 7
7 describe "DELETE /projects/:id" do 8 describe "DELETE /projects/:id" do
spec/features/security/project_access_spec.rb
@@ -14,10 +14,10 @@ describe &quot;Application access&quot; do @@ -14,10 +14,10 @@ describe &quot;Application access&quot; do
14 end 14 end
15 15
16 describe "Project" do 16 describe "Project" do
17 - let(:project) { create(:project_with_code) } 17 + let(:project) { create(:project_with_code) }
18 18
19 - let(:master) { create(:user) }  
20 - let(:guest) { create(:user) } 19 + let(:master) { create(:user) }
  20 + let(:guest) { create(:user) }
21 let(:reporter) { create(:user) } 21 let(:reporter) { create(:user) }
22 22
23 before do 23 before do
@@ -108,7 +108,7 @@ describe &quot;Application access&quot; do @@ -108,7 +108,7 @@ describe &quot;Application access&quot; do
108 describe "GET /project_code/blob" do 108 describe "GET /project_code/blob" do
109 before do 109 before do
110 commit = project.repository.commit 110 commit = project.repository.commit
111 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 111 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
112 @blob_path = project_blob_path(project, File.join(commit.id, path)) 112 @blob_path = project_blob_path(project, File.join(commit.id, path))
113 end 113 end
114 114
@@ -232,13 +232,13 @@ describe &quot;Application access&quot; do @@ -232,13 +232,13 @@ describe &quot;Application access&quot; do
232 232
233 233
234 describe "PublicProject" do 234 describe "PublicProject" do
235 - let(:project) { create(:project_with_code) } 235 + let(:project) { create(:project_with_code) }
236 236
237 - let(:master) { create(:user) }  
238 - let(:guest) { create(:user) } 237 + let(:master) { create(:user) }
  238 + let(:guest) { create(:user) }
239 let(:reporter) { create(:user) } 239 let(:reporter) { create(:user) }
240 240
241 - let(:admin) { create(:user) } 241 + let(:admin) { create(:user) }
242 242
243 before do 243 before do
244 # public project 244 # public project
@@ -339,7 +339,7 @@ describe &quot;Application access&quot; do @@ -339,7 +339,7 @@ describe &quot;Application access&quot; do
339 describe "GET /project_code/blob" do 339 describe "GET /project_code/blob" do
340 before do 340 before do
341 commit = project.repository.commit 341 commit = project.repository.commit
342 - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name 342 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
343 @blob_path = project_blob_path(project, File.join(commit.id, path)) 343 @blob_path = project_blob_path(project, File.join(commit.id, path))
344 end 344 end
345 345
spec/helpers/gitlab_markdown_helper_spec.rb
@@ -9,7 +9,7 @@ describe GitlabMarkdownHelper do @@ -9,7 +9,7 @@ describe GitlabMarkdownHelper do
9 let(:user) { create(:user, username: 'gfm') } 9 let(:user) { create(:user, username: 'gfm') }
10 let(:commit) { project.repository.commit } 10 let(:commit) { project.repository.commit }
11 let(:issue) { create(:issue, project: project) } 11 let(:issue) { create(:issue, project: project) }
12 - let(:merge_request) { create(:merge_request, project: project) } 12 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
13 let(:snippet) { create(:project_snippet, project: project) } 13 let(:snippet) { create(:project_snippet, project: project) }
14 let(:member) { project.users_projects.where(user_id: user).first } 14 let(:member) { project.users_projects.where(user_id: user).first }
15 15
spec/lib/gitlab/satellite/action_spec.rb 0 → 100644
@@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
  1 +require 'spec_helper'
  2 +
  3 +describe 'Gitlab::Satellite::Action' do
  4 + let(:project) { create(:project_with_code) }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe '#prepare_satellite!' do
  8 +
  9 + it 'create a repository with a parking branch and one remote: origin' do
  10 + repo = project.satellite.repo
  11 +
  12 + #now lets dirty it up
  13 +
  14 + starting_remote_count = repo.git.list_remotes.size
  15 + starting_remote_count.should >= 1
  16 + #kind of hookey way to add a second remote
  17 + origin_uri = repo.git.remote({v: true}).split(" ")[1]
  18 + begin
  19 + repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
  20 + repo.git.branch({raise: true}, 'a-new-branch')
  21 +
  22 + repo.heads.size.should > (starting_remote_count)
  23 + repo.git.remote().split(" ").size.should > (starting_remote_count)
  24 + rescue
  25 + end
  26 +
  27 + repo.git.config({}, "user.name", "#{user.name} -- foo")
  28 + repo.git.config({}, "user.email", "#{user.email} -- foo")
  29 + repo.config['user.name'].should =="#{user.name} -- foo"
  30 + repo.config['user.email'].should =="#{user.email} -- foo"
  31 +
  32 +
  33 + #These must happen in the context of the satellite directory...
  34 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  35 + project.satellite.lock {
  36 + #Now clean it up, use send to get around prepare_satellite! being protected
  37 + satellite_action.send(:prepare_satellite!, repo)
  38 + }
  39 +
  40 + #verify it's clean
  41 + heads = repo.heads.map(&:name)
  42 + heads.size.should == 1
  43 + heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
  44 + remotes = repo.git.remote().split(' ')
  45 + remotes.size.should == 1
  46 + remotes.include?('origin').should == true
  47 + repo.config['user.name'].should ==user.name
  48 + repo.config['user.email'].should ==user.email
  49 + end
  50 + end
  51 +
  52 + describe '#in_locked_and_timed_satellite' do
  53 +
  54 + it 'should make use of a lockfile' do
  55 + repo = project.satellite.repo
  56 + called = false
  57 +
  58 + #set assumptions
  59 + File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file
  60 +
  61 + File.exists?(project.satellite.lock_file).should be_false
  62 +
  63 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  64 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  65 + repo.should == sat_repo
  66 + (File.exists? project.satellite.lock_file).should be_true
  67 + called = true
  68 + end
  69 +
  70 + called.should be_true
  71 +
  72 + end
  73 +
  74 + it 'should be able to use the satellite after locking' do
  75 + repo = project.satellite.repo
  76 + called = false
  77 +
  78 + # Set base assumptions
  79 + if File.exists? project.satellite.lock_file
  80 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  81 + end
  82 +
  83 + satellite_action = Gitlab::Satellite::Action.new(user, project)
  84 + satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
  85 + called = true
  86 + repo.should == sat_repo
  87 + (File.exists? project.satellite.lock_file).should be_true
  88 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true
  89 + end
  90 +
  91 + called.should be_true
  92 + FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
  93 +
  94 + end
  95 +
  96 + class FileLockStatusChecker < File
  97 + def flocked? &block
  98 + status = flock LOCK_EX|LOCK_NB
  99 + case status
  100 + when false
  101 + return true
  102 + when 0
  103 + begin
  104 + block ? block.call : false
  105 + ensure
  106 + flock LOCK_UN
  107 + end
  108 + else
  109 + raise SystemCallError, status
  110 + end
  111 + end
  112 + end
  113 +
  114 + end
  115 +end
  116 +
spec/lib/gitlab/satellite/merge_action_spec.rb 0 → 100644
@@ -0,0 +1,148 @@ @@ -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 \ No newline at end of file 149 \ No newline at end of file
spec/mailers/notify_spec.rb
@@ -167,7 +167,7 @@ describe Notify do @@ -167,7 +167,7 @@ describe Notify do
167 end 167 end
168 168
169 context 'for merge requests' do 169 context 'for merge requests' do
170 - let(:merge_request) { create(:merge_request, assignee: assignee, project: project) } 170 + let(:merge_request) { create(:merge_request, assignee: assignee, source_project: project, target_project: project) }
171 171
172 describe 'that are new' do 172 describe 'that are new' do
173 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } 173 subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -311,7 +311,7 @@ describe Notify do @@ -311,7 +311,7 @@ describe Notify do
311 end 311 end
312 312
313 describe 'on a merge request' do 313 describe 'on a merge request' do
314 - let(:merge_request) { create(:merge_request, project: project) } 314 + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
315 let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } 315 let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
316 before(:each) { note.stub(:noteable).and_return(merge_request) } 316 before(:each) { note.stub(:noteable).and_return(merge_request) }
317 317
spec/models/commit_spec.rb
@@ -3,7 +3,6 @@ require &#39;spec_helper&#39; @@ -3,7 +3,6 @@ require &#39;spec_helper&#39;
3 describe Commit do 3 describe Commit do
4 let(:commit) { create(:project_with_code).repository.commit } 4 let(:commit) { create(:project_with_code).repository.commit }
5 5
6 -  
7 describe '#title' do 6 describe '#title' do
8 it "returns no_commit_message when safe_message is blank" do 7 it "returns no_commit_message when safe_message is blank" do
9 commit.stub(:safe_message).and_return('') 8 commit.stub(:safe_message).and_return('')
spec/models/forked_project_link_spec.rb
@@ -12,9 +12,9 @@ @@ -12,9 +12,9 @@
12 require 'spec_helper' 12 require 'spec_helper'
13 13
14 describe ForkedProjectLink, "add link on fork" do 14 describe ForkedProjectLink, "add link on fork" do
15 - let(:project_from) {create(:project)}  
16 - let(:namespace) {create(:namespace)}  
17 - let(:user) {create(:user, namespace: namespace)} 15 + let(:project_from) { create(:project) }
  16 + let(:namespace) { create(:namespace) }
  17 + let(:user) { create(:user, namespace: namespace) }
18 18
19 before do 19 before do
20 @project_to = fork_project(project_from, user) 20 @project_to = fork_project(project_from, user)
@@ -30,9 +30,9 @@ describe ForkedProjectLink, &quot;add link on fork&quot; do @@ -30,9 +30,9 @@ describe ForkedProjectLink, &quot;add link on fork&quot; do
30 end 30 end
31 31
32 describe :forked_from_project do 32 describe :forked_from_project do
33 - let(:forked_project_link) {build(:forked_project_link)}  
34 - let(:project_from) {create(:project)}  
35 - let(:project_to) {create(:project, forked_project_link: forked_project_link)} 33 + let(:forked_project_link) { build(:forked_project_link) }
  34 + let(:project_from) { create(:project) }
  35 + let(:project_to) { create(:project, forked_project_link: forked_project_link) }
36 36
37 37
38 before :each do 38 before :each do
spec/models/merge_request_spec.rb
@@ -41,15 +41,12 @@ describe MergeRequest do @@ -41,15 +41,12 @@ describe MergeRequest do
41 it { should include_module(Issuable) } 41 it { should include_module(Issuable) }
42 end 42 end
43 43
44 - describe "#mr_and_commit_notes" do  
45 -  
46 - end  
47 44
48 describe "#mr_and_commit_notes" do 45 describe "#mr_and_commit_notes" do
49 let!(:merge_request) { create(:merge_request) } 46 let!(:merge_request) { create(:merge_request) }
50 47
51 before do 48 before do
52 - merge_request.stub(:commits) { [merge_request.project.repository.commit] } 49 + merge_request.stub(:commits) { [merge_request.source_project.repository.commit] }
53 create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit') 50 create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit')
54 create(:note, noteable: merge_request) 51 create(:note, noteable: merge_request)
55 end 52 end
@@ -71,4 +68,38 @@ describe MergeRequest do @@ -71,4 +68,38 @@ describe MergeRequest do
71 subject.is_being_reassigned?.should be_false 68 subject.is_being_reassigned?.should be_false
72 end 69 end
73 end 70 end
  71 +
  72 + describe '#for_fork?' do
  73 + it 'returns true if the merge request is for a fork' do
  74 + subject.source_project = create(:source_project)
  75 + subject.target_project = create(:target_project)
  76 +
  77 + subject.for_fork?.should be_true
  78 + end
  79 + it 'returns false if is not for a fork' do
  80 + subject.source_project = create(:source_project)
  81 + subject.target_project = subject.source_project
  82 + subject.for_fork?.should be_false
  83 + end
  84 + end
  85 +
  86 + describe '#allow_source_branch_removal?' do
  87 + it 'should not allow removal when mr is a fork' do
  88 +
  89 + subject.disallow_source_branch_removal?.should be_true
  90 + end
  91 + it 'should not allow removal when the mr is not a fork, but the source branch is the root reference' do
  92 + subject.target_project = subject.source_project
  93 + subject.source_branch = subject.source_project.repository.root_ref
  94 + subject.disallow_source_branch_removal?.should be_true
  95 + end
  96 +
  97 + it 'should not disallow removal when the mr is not a fork, and but source branch is not the root reference' do
  98 + subject.target_project = subject.source_project
  99 + subject.source_branch = "Something Different #{subject.source_project.repository.root_ref}"
  100 + subject.for_fork?.should be_false
  101 + subject.disallow_source_branch_removal?.should be_false
  102 + end
  103 + end
  104 +
74 end 105 end
spec/models/note_spec.rb
@@ -144,12 +144,12 @@ describe Note do @@ -144,12 +144,12 @@ describe Note do
144 end 144 end
145 145
146 describe '#create_status_change_note' do 146 describe '#create_status_change_note' do
147 - let(:project) { create(:project) }  
148 - let(:thing) { create(:issue, project: project) }  
149 - let(:author) { create(:user) }  
150 - let(:status) { 'new_status' } 147 + let(:project) { create(:project) }
  148 + let(:thing) { create(:issue, project: project) }
  149 + let(:author) { create(:user) }
  150 + let(:status) { 'new_status' }
151 151
152 - subject { Note.create_status_change_note(thing, author, status) } 152 + subject { Note.create_status_change_note(thing, project, author, status) }
153 153
154 it 'creates and saves a Note' do 154 it 'creates and saves a Note' do
155 should be_a Note 155 should be_a Note
@@ -157,9 +157,9 @@ describe Note do @@ -157,9 +157,9 @@ describe Note do
157 end 157 end
158 158
159 its(:noteable) { should == thing } 159 its(:noteable) { should == thing }
160 - its(:project) { should == thing.project }  
161 - its(:author) { should == author }  
162 - its(:note) { should =~ /Status changed to #{status}/ } 160 + its(:project) { should == thing.project }
  161 + its(:author) { should == author }
  162 + its(:note) { should =~ /Status changed to #{status}/ }
163 end 163 end
164 164
165 describe :authorization do 165 describe :authorization do
spec/models/project_spec.rb
@@ -26,6 +26,9 @@ @@ -26,6 +26,9 @@
26 require 'spec_helper' 26 require 'spec_helper'
27 27
28 describe Project do 28 describe Project do
  29 + before(:each) { enable_observers }
  30 + after(:each) { disable_observers }
  31 +
29 describe "Associations" do 32 describe "Associations" do
30 it { should belong_to(:group) } 33 it { should belong_to(:group) }
31 it { should belong_to(:namespace) } 34 it { should belong_to(:namespace) }
@@ -95,12 +98,11 @@ describe Project do @@ -95,12 +98,11 @@ describe Project do
95 end 98 end
96 99
97 describe "last_activity methods" do 100 describe "last_activity methods" do
98 - before { enable_observers }  
99 - let(:project) { create(:project) } 101 + let(:project) { create(:project) }
100 let(:last_event) { double(created_at: Time.now) } 102 let(:last_event) { double(created_at: Time.now) }
101 103
102 describe "last_activity" do 104 describe "last_activity" do
103 - it "should alias last_activity to last_event"do 105 + it "should alias last_activity to last_event" do
104 project.stub(last_event: last_event) 106 project.stub(last_event: last_event)
105 project.last_activity.should == last_event 107 project.last_activity.should == last_event
106 end 108 end
@@ -122,7 +124,7 @@ describe Project do @@ -122,7 +124,7 @@ describe Project do
122 let(:project) { create(:project_with_code) } 124 let(:project) { create(:project_with_code) }
123 125
124 before do 126 before do
125 - @merge_request = create(:merge_request, project: project) 127 + @merge_request = create(:merge_request, source_project: project, target_project: project)
126 @key = create(:key, user_id: project.owner.id) 128 @key = create(:key, user_id: project.owner.id)
127 end 129 end
128 130
spec/observers/activity_observer_spec.rb
@@ -8,18 +8,6 @@ describe ActivityObserver do @@ -8,18 +8,6 @@ describe ActivityObserver do
8 it { @event.project.should == project } 8 it { @event.project.should == project }
9 end 9 end
10 10
11 - describe "Merge Request created" do  
12 - before do  
13 - MergeRequest.observers.enable :activity_observer do  
14 - @merge_request = create(:merge_request, project: project)  
15 - @event = Event.last  
16 - end  
17 - end  
18 -  
19 - it_should_be_valid_event  
20 - it { @event.action.should == Event::CREATED }  
21 - it { @event.target.should == @merge_request }  
22 - end  
23 11
24 describe "Issue created" do 12 describe "Issue created" do
25 before do 13 before do
spec/observers/issue_observer_spec.rb
@@ -26,14 +26,13 @@ describe IssueObserver do @@ -26,14 +26,13 @@ describe IssueObserver do
26 before { mock_issue.stub(state: 'closed') } 26 before { mock_issue.stub(state: 'closed') }
27 27
28 it 'note is created if the issue is being closed' do 28 it 'note is created if the issue is being closed' do
29 - Note.should_receive(:create_status_change_note).with(mock_issue, some_user, 'closed') 29 + Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed')
30 30
31 subject.after_close(mock_issue, nil) 31 subject.after_close(mock_issue, nil)
32 end 32 end
33 33
34 it 'trigger notification to send emails' do 34 it 'trigger notification to send emails' do
35 subject.notification.should_receive(:close_issue).with(mock_issue, some_user) 35 subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
36 -  
37 subject.after_close(mock_issue, nil) 36 subject.after_close(mock_issue, nil)
38 end 37 end
39 end 38 end
@@ -42,8 +41,7 @@ describe IssueObserver do @@ -42,8 +41,7 @@ describe IssueObserver do
42 before { mock_issue.stub(state: 'reopened') } 41 before { mock_issue.stub(state: 'reopened') }
43 42
44 it 'note is created if the issue is being reopened' do 43 it 'note is created if the issue is being reopened' do
45 - Note.should_receive(:create_status_change_note).with(mock_issue, some_user, 'reopened')  
46 - 44 + Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened')
47 subject.after_reopen(mock_issue, nil) 45 subject.after_reopen(mock_issue, nil)
48 end 46 end
49 end 47 end
spec/observers/merge_request_observer_spec.rb
1 require 'spec_helper' 1 require 'spec_helper'
2 2
3 describe MergeRequestObserver do 3 describe MergeRequestObserver do
4 - let(:some_user) { create :user }  
5 - let(:assignee) { create :user }  
6 - let(:author) { create :user }  
7 - let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }  
8 - let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) } 4 + let(:some_user) { create :user }
  5 + let(:assignee) { create :user }
  6 + let(:author) { create :user }
  7 + let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author) }
  8 + let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author) }
9 let(:unassigned_mr) { create(:merge_request, author: author) } 9 let(:unassigned_mr) { create(:merge_request, author: author) }
10 - let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) } 10 + let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author) }
11 let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) } 11 let(:closed_unassigned_mr) { create(:closed_merge_request, author: author) }
12 12
13 before { subject.stub(:current_user).and_return(some_user) } 13 before { subject.stub(:current_user).and_return(some_user) }
14 before { subject.stub(notification: mock('NotificationService').as_null_object) } 14 before { subject.stub(notification: mock('NotificationService').as_null_object) }
  15 + before { mr_mock.stub(:author_id) }
  16 + before { mr_mock.stub(:target_project) }
15 before(:each) { enable_observers } 17 before(:each) { enable_observers }
16 - 18 + after(:each) { disable_observers }
17 19
18 subject { MergeRequestObserver.instance } 20 subject { MergeRequestObserver.instance }
19 21
@@ -30,7 +32,7 @@ describe MergeRequestObserver do @@ -30,7 +32,7 @@ describe MergeRequestObserver do
30 end 32 end
31 33
32 it 'is called when a merge request is changed' do 34 it 'is called when a merge request is changed' do
33 - changed = create(:merge_request, project: create(:project)) 35 + changed = create(:merge_request, source_project: create(:project))
34 subject.should_receive(:after_update) 36 subject.should_receive(:after_update)
35 37
36 MergeRequest.observers.enable :merge_request_observer do 38 MergeRequest.observers.enable :merge_request_observer do
@@ -59,13 +61,13 @@ describe MergeRequestObserver do @@ -59,13 +61,13 @@ describe MergeRequestObserver do
59 context '#after_close' do 61 context '#after_close' do
60 context 'a status "closed"' do 62 context 'a status "closed"' do
61 it 'note is created if the merge request is being closed' do 63 it 'note is created if the merge request is being closed' do
62 - Note.should_receive(:create_status_change_note).with(assigned_mr, some_user, 'closed') 64 + Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.target_project, some_user, 'closed')
63 65
64 assigned_mr.close 66 assigned_mr.close
65 end 67 end
66 68
67 it 'notification is delivered only to author if the merge request is being closed' do 69 it 'notification is delivered only to author if the merge request is being closed' do
68 - Note.should_receive(:create_status_change_note).with(unassigned_mr, some_user, 'closed') 70 + Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.target_project, some_user, 'closed')
69 71
70 unassigned_mr.close 72 unassigned_mr.close
71 end 73 end
@@ -75,16 +77,41 @@ describe MergeRequestObserver do @@ -75,16 +77,41 @@ describe MergeRequestObserver do
75 context '#after_reopen' do 77 context '#after_reopen' do
76 context 'a status "reopened"' do 78 context 'a status "reopened"' do
77 it 'note is created if the merge request is being reopened' do 79 it 'note is created if the merge request is being reopened' do
78 - Note.should_receive(:create_status_change_note).with(closed_assigned_mr, some_user, 'reopened') 80 + Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.target_project, some_user, 'reopened')
79 81
80 closed_assigned_mr.reopen 82 closed_assigned_mr.reopen
81 end 83 end
82 84
83 it 'notification is delivered only to author if the merge request is being reopened' do 85 it 'notification is delivered only to author if the merge request is being reopened' do
84 - Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, some_user, 'reopened') 86 + Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.target_project, some_user, 'reopened')
85 87
86 closed_unassigned_mr.reopen 88 closed_unassigned_mr.reopen
87 end 89 end
88 end 90 end
89 end 91 end
  92 +
  93 + describe "Merge Request created" do
  94 + def self.it_should_be_valid_event
  95 + it { @event.should_not be_nil }
  96 + it { @event.should_not be_nil }
  97 + it { @event.project.should == project }
  98 + it { @event.project.should == project }
  99 + end
  100 +
  101 + let(:project) { create(:project) }
  102 + before do
  103 + TestEnv.enable_observers
  104 + @merge_request = create(:merge_request, source_project: project, target_project: project)
  105 + @event = Event.last
  106 + end
  107 +
  108 + after do
  109 + TestEnv.disable_observers
  110 + end
  111 +
  112 + it_should_be_valid_event
  113 + it { @event.action.should == Event::CREATED }
  114 + it { @event.target.should == @merge_request }
  115 + end
  116 +
90 end 117 end
spec/observers/user_observer_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe UserObserver do 3 describe UserObserver do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) {disable_observers}
5 subject { UserObserver.instance } 6 subject { UserObserver.instance }
6 before { subject.stub(notification: mock('NotificationService').as_null_object) } 7 before { subject.stub(notification: mock('NotificationService').as_null_object) }
7 8
spec/observers/users_project_observer_spec.rb
@@ -2,6 +2,7 @@ require &#39;spec_helper&#39; @@ -2,6 +2,7 @@ require &#39;spec_helper&#39;
2 2
3 describe UsersProjectObserver do 3 describe UsersProjectObserver do
4 before(:each) { enable_observers } 4 before(:each) { enable_observers }
  5 + after(:each) { disable_observers }
5 6
6 let(:user) { create(:user) } 7 let(:user) { create(:user) }
7 let(:project) { create(:project) } 8 let(:project) { create(:project) }
spec/requests/api/merge_requests_spec.rb
@@ -3,10 +3,12 @@ require &quot;spec_helper&quot; @@ -3,10 +3,12 @@ require &quot;spec_helper&quot;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 5
6 - let(:user) { create(:user ) }  
7 - let!(:project) { create(:project_with_code, creator_id: user.id) }  
8 - let!(:merge_request) { create(:merge_request, author: user, assignee: user, project: project, title: "Test") }  
9 - before { project.team << [user, :reporters] } 6 + let(:user) { create(:user) }
  7 + let!(:project) {create(:project_with_code, creator_id: user.id) }
  8 + let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
  9 + before {
  10 + project.team << [user, :reporters]
  11 + }
10 12
11 describe "GET /projects/:id/merge_requests" do 13 describe "GET /projects/:id/merge_requests" do
12 context "when unauthenticated" do 14 context "when unauthenticated" do
@@ -40,35 +42,104 @@ describe API::API do @@ -40,35 +42,104 @@ describe API::API do
40 end 42 end
41 43
42 describe "POST /projects/:id/merge_requests" do 44 describe "POST /projects/:id/merge_requests" do
43 - it "should return merge_request" do  
44 - post api("/projects/#{project.id}/merge_requests", user),  
45 - title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user  
46 - response.status.should == 201  
47 - json_response['title'].should == 'Test merge_request'  
48 - end 45 + context 'between branches projects' do
  46 + it "should return merge_request" do
  47 + post api("/projects/#{project.id}/merge_requests", user),
  48 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user
  49 + response.status.should == 201
  50 + json_response['title'].should == 'Test merge_request'
  51 + end
49 52
50 - it "should return 422 when source_branch equals target_branch" do  
51 - post api("/projects/#{project.id}/merge_requests", user),  
52 - title: "Test merge_request", source_branch: "master", target_branch: "master", author: user  
53 - response.status.should == 422  
54 - end 53 + it "should return 422 when source_branch equals target_branch" do
  54 + post api("/projects/#{project.id}/merge_requests", user),
  55 + title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
  56 + response.status.should == 422
  57 + end
55 58
56 - it "should return 400 when source_branch is missing" do  
57 - post api("/projects/#{project.id}/merge_requests", user),  
58 - title: "Test merge_request", target_branch: "master", author: user  
59 - response.status.should == 400  
60 - end 59 + it "should return 400 when source_branch is missing" do
  60 + post api("/projects/#{project.id}/merge_requests", user),
  61 + title: "Test merge_request", target_branch: "master", author: user
  62 + response.status.should == 400
  63 + end
61 64
62 - it "should return 400 when target_branch is missing" do  
63 - post api("/projects/#{project.id}/merge_requests", user),  
64 - title: "Test merge_request", source_branch: "stable", author: user  
65 - response.status.should == 400 65 + it "should return 400 when target_branch is missing" do
  66 + post api("/projects/#{project.id}/merge_requests", user),
  67 + title: "Test merge_request", source_branch: "stable", author: user
  68 + response.status.should == 400
  69 + end
  70 +
  71 + it "should return 400 when title is missing" do
  72 + post api("/projects/#{project.id}/merge_requests", user),
  73 + target_branch: 'master', source_branch: 'stable'
  74 + response.status.should == 400
  75 + end
66 end 76 end
67 77
68 - it "should return 400 when title is missing" do  
69 - post api("/projects/#{project.id}/merge_requests", user),  
70 - target_branch: 'master', source_branch: 'stable'  
71 - response.status.should == 400 78 + context 'forked projects' do
  79 + let!(:user2) {create(:user)}
  80 + let!(:forked_project_link) { build(:forked_project_link) }
  81 + let!(:fork_project) { create(:source_project_with_code, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) }
  82 + let!(:unrelated_project) { create(:target_project_with_code, namespace: user2.namespace, creator_id: user2.id) }
  83 +
  84 + before :each do |each|
  85 + fork_project.team << [user2, :reporters]
  86 + forked_project_link.forked_from_project = project
  87 + forked_project_link.forked_to_project = fork_project
  88 + forked_project_link.save!
  89 + end
  90 +
  91 + it "should return merge_request" do
  92 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  93 + title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id
  94 + response.status.should == 201
  95 + json_response['title'].should == 'Test merge_request'
  96 + end
  97 +
  98 + it "should not return 422 when source_branch equals target_branch" do
  99 + project.id.should_not == fork_project.id
  100 + fork_project.forked?.should be_true
  101 + fork_project.forked_from_project.should == project
  102 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  103 + title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
  104 + response.status.should == 201
  105 + json_response['title'].should == 'Test merge_request'
  106 + end
  107 +
  108 + it "should return 400 when source_branch is missing" do
  109 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  110 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  111 + response.status.should == 400
  112 + end
  113 +
  114 + it "should return 400 when target_branch is missing" do
  115 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  116 + title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
  117 + response.status.should == 400
  118 + end
  119 +
  120 + it "should return 400 when title is missing" do
  121 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  122 + target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
  123 + response.status.should == 400
  124 + end
  125 +
  126 + it "should return 400 when target_branch is specified and not a forked project" do
  127 + post api("/projects/#{project.id}/merge_requests", user),
  128 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
  129 + response.status.should == 400
  130 + end
  131 +
  132 + it "should return 400 when target_branch is specified and for a different fork" do
  133 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  134 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
  135 + response.status.should == 400
  136 + end
  137 +
  138 + it "should return 201 when target_branch is specified and for the same project" do
  139 + post api("/projects/#{fork_project.id}/merge_requests", user2),
  140 + title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
  141 + response.status.should == 201
  142 + end
72 end 143 end
73 end 144 end
74 145
@@ -97,14 +168,14 @@ describe API::API do @@ -97,14 +168,14 @@ describe API::API do
97 168
98 it "should return 422 when source_branch and target_branch are renamed the same" do 169 it "should return 422 when source_branch and target_branch are renamed the same" do
99 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), 170 put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
100 - source_branch: "master", target_branch: "master" 171 + source_branch: "master", target_branch: "master"
101 response.status.should == 422 172 response.status.should == 422
102 end 173 end
103 174
104 it "should return merge_request with renamed target_branch" do 175 it "should return merge_request with renamed target_branch" do
105 - put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "test" 176 + put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
106 response.status.should == 200 177 response.status.should == 200
107 - json_response['target_branch'].should == 'test' 178 + json_response['target_branch'].should == 'wiki'
108 end 179 end
109 end 180 end
110 181
spec/requests/api/milestones_spec.rb
@@ -3,6 +3,7 @@ require &#39;spec_helper&#39; @@ -3,6 +3,7 @@ require &#39;spec_helper&#39;
3 describe API::API do 3 describe API::API do
4 include ApiHelpers 4 include ApiHelpers
5 before(:each) { enable_observers } 5 before(:each) { enable_observers }
  6 + after(:each) {disable_observers}
6 7
7 let(:user) { create(:user) } 8 let(:user) { create(:user) }
8 let!(:project) { create(:project, namespace: user.namespace ) } 9 let!(:project) { create(:project, namespace: user.namespace ) }
spec/requests/api/notes_spec.rb
@@ -6,7 +6,7 @@ describe API::API do @@ -6,7 +6,7 @@ describe API::API do
6 let(:user) { create(:user) } 6 let(:user) { create(:user) }
7 let!(:project) { create(:project, namespace: user.namespace ) } 7 let!(:project) { create(:project, namespace: user.namespace ) }
8 let!(:issue) { create(:issue, project: project, author: user) } 8 let!(:issue) { create(:issue, project: project, author: user) }
9 - let!(:merge_request) { create(:merge_request, project: project, author: user) } 9 + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
10 let!(:snippet) { create(:project_snippet, project: project, author: user) } 10 let!(:snippet) { create(:project_snippet, project: project, author: user) }
11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } 11 let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
12 let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } 12 let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }