Commit 22ac0cc7ebc6411bdfd4df7e018ca0e1fd63c4c6
Exists in
master
and in
4 other branches
Merge branch 'feature/merge_requests'
Showing
29 changed files
with
531 additions
and
42 deletions
Show diff stats
app/assets/stylesheets/application.css
app/assets/stylesheets/issues.css.scss
... | ... | @@ -58,6 +58,8 @@ |
58 | 58 | padding: 0; |
59 | 59 | } |
60 | 60 | |
61 | +body.project-page .merge-request-form-holder table.no-borders tr, | |
62 | +body.project-page .merge-request-form-holder table.no-borders td, | |
61 | 63 | body.project-page .issue-form-holder table.no-borders tr, |
62 | 64 | body.project-page .issue-form-holder table.no-borders td, |
63 | 65 | body.project-page .new_snippet table tr, | ... | ... |
... | ... | @@ -0,0 +1,10 @@ |
1 | +// Place all the styles related to the MergeRequests controller here. | |
2 | +// They will automatically be included in application.css. | |
3 | +// You can use Sass (SCSS) here: http://sass-lang.com/ | |
4 | + | |
5 | + | |
6 | +.merge-request-form-holder { | |
7 | + select { | |
8 | + width:300px; | |
9 | + } | |
10 | +} | ... | ... |
app/assets/stylesheets/projects.css.scss
... | ... | @@ -155,6 +155,8 @@ input.ssh_project_url { |
155 | 155 | } |
156 | 156 | |
157 | 157 | /** FORM INPUTS **/ |
158 | +.new_merge_request, | |
159 | +.edit_merge_request, | |
158 | 160 | .user_new, |
159 | 161 | .new_key, |
160 | 162 | .new_issue, |
... | ... | @@ -326,12 +328,16 @@ body.project-page table .commit { |
326 | 328 | border:none; |
327 | 329 | text-shadow:none; |
328 | 330 | |
329 | - &.high { | |
331 | + &.inline { | |
332 | + display:inline; | |
333 | + } | |
334 | + | |
335 | + &.high, &.closed { | |
330 | 336 | background: #D12F19; |
331 | 337 | color:white; |
332 | 338 | } |
333 | 339 | |
334 | - &.today { | |
340 | + &.today, &.open { | |
335 | 341 | background: #44aa22; |
336 | 342 | color:white; |
337 | 343 | } |
... | ... | @@ -384,6 +390,32 @@ body.dashboard.project-page .news-feed .project-updates a.project-update span.up |
384 | 390 | body.dashboard.project-page .news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;} |
385 | 391 | /* eo Dashboard Page */ |
386 | 392 | |
393 | + | |
394 | +/** Merge requests */ | |
395 | +body.project-page .merge-request-commits {margin-bottom: 20px; display: block; width: 100%;} | |
396 | +body.project-page .merge-request-commits .data{ padding: 0} | |
397 | +body.project-page .merge-request-commits a.commit {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} | |
398 | +body.project-page .merge-request-commits a.commit:last-child{border-bottom: 0} | |
399 | +body.project-page .merge-request-commits a.commit img{float: left; margin-right: 10px;} | |
400 | +body.project-page .merge-request-commits a.commit span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} | |
401 | +body.project-page .merge-request-commits a.commit span.update-title{margin-bottom: 10px} | |
402 | +body.project-page .merge-request-commits a.commit span.update-author{color: #999; font-weight: normal; font-style: italic;} | |
403 | +body.project-page .merge-request-commits a.commit span.update-author strong{font-weight: bold; font-style: normal;} | |
404 | + | |
405 | + | |
406 | +/** Update entry **/ | |
407 | +.update-data { padding: 0 } | |
408 | +.update-data { width:100%; } | |
409 | +.update-data.ui-box .data { padding:0; } | |
410 | +a.update-item {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;} | |
411 | +a.update-item:last-child{border-bottom: 0} | |
412 | +a.update-item img{float: left; margin-right: 10px;} | |
413 | +a.update-item span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;} | |
414 | +a.update-item span.update-title{margin-bottom: 10px} | |
415 | +a.update-item span.update-author{color: #999; font-weight: normal; font-style: italic;} | |
416 | +a.update-item span.update-author strong{font-weight: bold; font-style: normal;} | |
417 | + | |
418 | + | |
387 | 419 | body.project-page .team_member_new .span-6, .team_member_edit .span-6{ padding:10px 0; } |
388 | 420 | |
389 | 421 | body.projects-page input.text.git-url.project_list_url { width:165px; } |
... | ... | @@ -394,3 +426,44 @@ body.project-page table.no-borders tr, |
394 | 426 | body.project-page table.no-borders td{ |
395 | 427 | border:none; |
396 | 428 | } |
429 | + | |
430 | +#gitlab-tabs { | |
431 | + .ui-tabs-nav { | |
432 | + border-bottom: 1px solid #DEDFE1; | |
433 | + | |
434 | + li { | |
435 | + background: none; | |
436 | + border:none; | |
437 | + font-size: 16px; | |
438 | + margin: 0; | |
439 | + padding: 0; | |
440 | + | |
441 | + a { | |
442 | + margin: 0; | |
443 | + padding: 10px 16px; | |
444 | + width:150px; | |
445 | + } | |
446 | + | |
447 | + &.ui-tabs-selected { | |
448 | + background-image: -webkit-gradient(linear, 0 0, 0 26, color-stop(0.076, #fefefe), to(#F6F7F8)); | |
449 | + background-image: -webkit-linear-gradient(#fefefe 7.6%, #F6F7F8); | |
450 | + background-image: -moz-linear-gradient(#fefefe 7.6%, #F6F7F8); | |
451 | + background-image: -o-linear-gradient(#fefefe 7.6%, #F6F7F8); | |
452 | + font-weight: bold; | |
453 | + border:1px solid #DEDFE1; | |
454 | + border-bottom: 1px solid #DEDFE1; | |
455 | + -webkit-border-top-left-radius: 5px; | |
456 | + -webkit-border-top-right-radius: 5px; | |
457 | + -moz-border-radius-topleft: 5px; | |
458 | + -moz-border-radius-topright: 5px; | |
459 | + border-top-left-radius: 5px; | |
460 | + border-top-right-radius: 5px; | |
461 | + } | |
462 | + } | |
463 | + } | |
464 | +} | |
465 | + | |
466 | +.ajax-tab-loading { | |
467 | + padding:40px; | |
468 | + display:none; | |
469 | +} | ... | ... |
... | ... | @@ -0,0 +1,90 @@ |
1 | +class MergeRequestsController < ApplicationController | |
2 | + before_filter :authenticate_user! | |
3 | + before_filter :project | |
4 | + before_filter :merge_request, :only => [:edit, :update, :destroy, :show, :commits, :diffs] | |
5 | + layout "project" | |
6 | + | |
7 | + # Authorize | |
8 | + before_filter :add_project_abilities | |
9 | + before_filter :authorize_read_project! | |
10 | + before_filter :authorize_write_project!, :only => [:new, :create, :edit, :update] | |
11 | + | |
12 | + def index | |
13 | + @merge_requests = @project.merge_requests | |
14 | + end | |
15 | + | |
16 | + def show | |
17 | + unless @project.repo.heads.map(&:name).include?(@merge_request.target_branch) && | |
18 | + @project.repo.heads.map(&:name).include?(@merge_request.source_branch) | |
19 | + head(404)and return | |
20 | + end | |
21 | + | |
22 | + @notes = @merge_request.notes.inc_author.order("created_at DESC").limit(20) | |
23 | + @note = @project.notes.new(:noteable => @merge_request) | |
24 | + | |
25 | + respond_to do |format| | |
26 | + format.html | |
27 | + format.js { respond_with_notes } | |
28 | + end | |
29 | + end | |
30 | + | |
31 | + def commits | |
32 | + @commits = @project.repo.commits_between(@merge_request.target_branch, @merge_request.source_branch).map {|c| Commit.new(c)} | |
33 | + render :template => "merge_requests/_commits", :layout => false | |
34 | + end | |
35 | + | |
36 | + def diffs | |
37 | + @commit = @project.commit(@merge_request.source_branch) | |
38 | + @diffs = @project.repo.diff(@merge_request.target_branch, @merge_request.source_branch) | |
39 | + render :template => "merge_requests/_diffs", :layout => false | |
40 | + end | |
41 | + | |
42 | + def new | |
43 | + @merge_request = @project.merge_requests.new | |
44 | + end | |
45 | + | |
46 | + def edit | |
47 | + end | |
48 | + | |
49 | + def create | |
50 | + @merge_request = @project.merge_requests.new(params[:merge_request]) | |
51 | + @merge_request.author = current_user | |
52 | + | |
53 | + respond_to do |format| | |
54 | + if @merge_request.save | |
55 | + format.html { redirect_to [@project, @merge_request], notice: 'Merge request was successfully created.' } | |
56 | + format.json { render json: @merge_request, status: :created, location: @merge_request } | |
57 | + else | |
58 | + format.html { render action: "new" } | |
59 | + format.json { render json: @merge_request.errors, status: :unprocessable_entity } | |
60 | + end | |
61 | + end | |
62 | + end | |
63 | + | |
64 | + def update | |
65 | + respond_to do |format| | |
66 | + if @merge_request.update_attributes(params[:merge_request]) | |
67 | + format.html { redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.' } | |
68 | + format.json { head :ok } | |
69 | + else | |
70 | + format.html { render action: "edit" } | |
71 | + format.json { render json: @merge_request.errors, status: :unprocessable_entity } | |
72 | + end | |
73 | + end | |
74 | + end | |
75 | + | |
76 | + def destroy | |
77 | + @merge_request.destroy | |
78 | + | |
79 | + respond_to do |format| | |
80 | + format.html { redirect_to project_merge_requests_url(@project) } | |
81 | + format.json { head :ok } | |
82 | + end | |
83 | + end | |
84 | + | |
85 | + protected | |
86 | + | |
87 | + def merge_request | |
88 | + @merge_request ||= @project.merge_requests.find(params[:id]) | |
89 | + end | |
90 | +end | ... | ... |
app/controllers/notes_controller.rb
... | ... | @@ -42,6 +42,8 @@ class NotesController < ApplicationController |
42 | 42 | Notify.note_commit_email(u, @note).deliver |
43 | 43 | when "Issue" then |
44 | 44 | Notify.note_issue_email(u, @note).deliver |
45 | + when "MergeRequest" | |
46 | + true # someone should write email notification | |
45 | 47 | when "Snippet" |
46 | 48 | true |
47 | 49 | else | ... | ... |
app/models/ability.rb
... | ... | @@ -17,6 +17,7 @@ class Ability |
17 | 17 | :read_issue, |
18 | 18 | :read_snippet, |
19 | 19 | :read_team_member, |
20 | + :read_merge_request, | |
20 | 21 | :read_note |
21 | 22 | ] if project.readers.include?(user) |
22 | 23 | |
... | ... | @@ -24,6 +25,7 @@ class Ability |
24 | 25 | :write_project, |
25 | 26 | :write_issue, |
26 | 27 | :write_snippet, |
28 | + :write_merge_request, | |
27 | 29 | :write_note |
28 | 30 | ] if project.writers.include?(user) |
29 | 31 | |
... | ... | @@ -32,6 +34,7 @@ class Ability |
32 | 34 | :admin_issue, |
33 | 35 | :admin_snippet, |
34 | 36 | :admin_team_member, |
37 | + :admin_merge_request, | |
35 | 38 | :admin_note |
36 | 39 | ] if project.admins.include?(user) |
37 | 40 | |
... | ... | @@ -39,7 +42,7 @@ class Ability |
39 | 42 | end |
40 | 43 | |
41 | 44 | class << self |
42 | - [:issue, :note, :snippet].each do |name| | |
45 | + [:issue, :note, :snippet, :merge_request].each do |name| | |
43 | 46 | define_method "#{name}_abilities" do |user, subject| |
44 | 47 | if subject.author == user |
45 | 48 | [ | ... | ... |
... | ... | @@ -0,0 +1,36 @@ |
1 | +class MergeRequest < ActiveRecord::Base | |
2 | + belongs_to :project | |
3 | + belongs_to :author, :class_name => "User" | |
4 | + belongs_to :assignee, :class_name => "User" | |
5 | + has_many :notes, :as => :noteable | |
6 | + | |
7 | + attr_protected :author, :author_id, :project, :project_id | |
8 | + | |
9 | + validates_presence_of :project_id | |
10 | + validates_presence_of :assignee_id | |
11 | + validates_presence_of :author_id | |
12 | + validates_presence_of :source_branch | |
13 | + validates_presence_of :target_branch | |
14 | + | |
15 | + delegate :name, | |
16 | + :email, | |
17 | + :to => :author, | |
18 | + :prefix => true | |
19 | + | |
20 | + delegate :name, | |
21 | + :email, | |
22 | + :to => :assignee, | |
23 | + :prefix => true | |
24 | + | |
25 | + validates :title, | |
26 | + :presence => true, | |
27 | + :length => { :within => 0..255 } | |
28 | + | |
29 | + scope :opened, where(:closed => false) | |
30 | + scope :closed, where(:closed => true) | |
31 | + scope :assigned, lambda { |u| where(:assignee_id => u.id)} | |
32 | + | |
33 | + def new? | |
34 | + today? && created_at == updated_at | |
35 | + end | |
36 | +end | ... | ... |
app/models/project.rb
... | ... | @@ -3,6 +3,7 @@ require "grit" |
3 | 3 | class Project < ActiveRecord::Base |
4 | 4 | belongs_to :owner, :class_name => "User" |
5 | 5 | |
6 | + has_many :merge_requests, :dependent => :destroy | |
6 | 7 | has_many :issues, :dependent => :destroy, :order => "position" |
7 | 8 | has_many :users_projects, :dependent => :destroy |
8 | 9 | has_many :users, :through => :users_projects | ... | ... |
app/views/issues/show.html.haml
... | ... | @@ -3,9 +3,9 @@ |
3 | 3 | = "Issue ##{@issue.id}" |
4 | 4 | .right |
5 | 5 | - if @issue.closed |
6 | - %span.tag.high Resolved | |
6 | + %span.tag.closed Closed | |
7 | 7 | - else |
8 | - %span.tag.today Open | |
8 | + %span.tag.open Open | |
9 | 9 | |
10 | 10 | .data |
11 | 11 | %p= @issue.title |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | - if @issue.closed |
29 | 29 | = link_to 'Reopen', project_issue_path(@project, @issue, :issue => {:closed => false }, :status_only => true), :method => :put, :class => "grey-button" |
30 | 30 | - else |
31 | - = link_to 'Resolve', project_issue_path(@project, @issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "grey-button" | |
31 | + = link_to 'Close', project_issue_path(@project, @issue, :issue => {:closed => true }, :status_only => true), :method => :put, :class => "grey-button" | |
32 | 32 | .right |
33 | 33 | = link_to 'Edit', edit_project_issue_path(@project, @issue), :class => "grey-button positive" |
34 | 34 | ... | ... |
app/views/layouts/project.html.haml
... | ... | @@ -39,6 +39,10 @@ |
39 | 39 | Wall |
40 | 40 | - if @project.common_notes.today.count > 0 |
41 | 41 | %span{ :class => "number" }= @project.common_notes.today.count |
42 | + = link_to project_merge_requests_path(@project), :class => (controller.controller_name == "merge_requests") ? "current" : nil do | |
43 | + Merge Requests | |
44 | + - if @project.merge_requests.opened.count > 0 | |
45 | + %span{ :class => "number" }= @project.merge_requests.opened.count | |
42 | 46 | = link_to project_snippets_path(@project), :class => (controller.controller_name == "snippets") ? "current" : nil do |
43 | 47 | Snippets |
44 | 48 | - if @project.snippets.non_expired.count > 0 | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +- if @commits.size > 0 | |
2 | + .merge-request-commits.ui-box.width-100p | |
3 | + - @commits.each do |commit| | |
4 | + %a{ :class => "commit", :href => project_commit_path(@project, :id => commit.id) } | |
5 | + - if commit.author_email | |
6 | + = image_tag gravatar_icon(commit.author_email), :class => "left", :width => 40, :style => "padding-right:5px;" | |
7 | + - else | |
8 | + = image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;" | |
9 | + %span.update-title | |
10 | + = truncate commit.safe_message, :length => 60 | |
11 | + %span.update-author | |
12 | + %strong= commit.author_name | |
13 | + authored | |
14 | + = time_ago_in_words(commit.created_at) | |
15 | + ago | |
16 | + .clear | |
17 | + | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +- @diffs.each do |diff| | |
2 | + - next if diff.diff.empty? | |
3 | + - file = (@commit.tree / diff.b_path) | |
4 | + - next unless file | |
5 | + .diff_file | |
6 | + .diff_file_header | |
7 | + - if diff.deleted_file | |
8 | + %strong{:id => "#{diff.b_path}"}= diff.a_path | |
9 | + - else | |
10 | + = link_to tree_file_project_ref_path(@project, @commit.id, diff.b_path) do | |
11 | + %strong{:id => "#{diff.b_path}"}= diff.b_path | |
12 | + %br/ | |
13 | + .diff_file_content | |
14 | + - if file.text? | |
15 | + = render :partial => "commits/text_file", :locals => { :diff => diff } | |
16 | + - elsif file.image? | |
17 | + .diff_file_content_image | |
18 | + %img{:src => "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
19 | + - else | |
20 | + %p | |
21 | + %center No preview for this file type | |
22 | + | ... | ... |
... | ... | @@ -0,0 +1,39 @@ |
1 | +%div.merge-request-form-holder | |
2 | + .ui-box.width-100p | |
3 | + %h3 | |
4 | + = @merge_request.new_record? ? "New Merge Request" : "Edit Merge Request ##{@merge_request.id}" | |
5 | + = form_for [@project, @merge_request] do |f| | |
6 | + .data | |
7 | + %table.no-borders | |
8 | + -if @merge_request.errors.any? | |
9 | + %tr | |
10 | + %td Errors | |
11 | + %td | |
12 | + #error_explanation | |
13 | + - @merge_request.errors.full_messages.each do |msg| | |
14 | + %span= msg | |
15 | + %br | |
16 | + | |
17 | + %tr | |
18 | + %td= f.label :title | |
19 | + %td= f.text_field :title | |
20 | + %tr | |
21 | + %td= f.label :source_branch, "From" | |
22 | + %td= f.select(:source_branch, @project.heads.map(&:name), { :include_blank => "Select branch" }) | |
23 | + %tr | |
24 | + %td= f.label :target_branch, "To" | |
25 | + %td= f.select(:target_branch, @project.heads.map(&:name), { :include_blank => "Select branch" }) | |
26 | + %tr | |
27 | + %td= f.label :assignee_id, "Assign to" | |
28 | + %td= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" }) | |
29 | + .buttons | |
30 | + = f.submit 'Save', :class => "grey-button" | |
31 | + .right= link_to 'Back', project_merge_requests_path(@project), :class => "grey-button" | |
32 | + | |
33 | +:javascript | |
34 | + $(function(){ | |
35 | + $('select#merge_request_assignee_id').chosen(); | |
36 | + $('select#merge_request_source_branch').chosen(); | |
37 | + $('select#merge_request_target_branch').chosen(); | |
38 | + }); | |
39 | + | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +%a.update-item{:href => project_merge_request_path(@project, merge_request)} | |
2 | + = image_tag gravatar_icon(merge_request.author_email), :class => "left", :width => 40 | |
3 | + %span.update-title | |
4 | + = merge_request.title | |
5 | + %span.update-author | |
6 | + %strong= merge_request.author_name | |
7 | + authored | |
8 | + = time_ago_in_words(merge_request.created_at) | |
9 | + ago | |
10 | + .right | |
11 | + %span.tag.commit= merge_request.source_branch | |
12 | + → | |
13 | + %span.tag.commit= merge_request.target_branch | |
14 | + | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | += render 'form' | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +- if @merge_requests.opened.count > 0 | |
2 | + %div{ :class => "update-data ui-box ui-box-small ui-box-big" } | |
3 | + %h3 | |
4 | + %span.tag.open Open | |
5 | + .data | |
6 | + = render @merge_requests.opened | |
7 | + | |
8 | + .clear | |
9 | + %br | |
10 | + | |
11 | +- if @merge_requests.closed.count > 0 | |
12 | + %div{ :class => "update-data ui-box ui-box-small ui-box-big" } | |
13 | + %h3 | |
14 | + %span.tag.closed Closed | |
15 | + .data | |
16 | + = render @merge_requests.closed | |
17 | + .clear | |
18 | + %br | |
19 | + | |
20 | += link_to 'New Merge request', new_project_merge_request_path(@project), :class => "grey-button" | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | += render 'form' | ... | ... |
... | ... | @@ -0,0 +1,59 @@ |
1 | +.merge-request-show-holder.ui-box.width-100p | |
2 | + %h3 | |
3 | + = "Merge Request ##{@merge_request.id}:" | |
4 | + | |
5 | + .tag.commit.inline= @merge_request.source_branch | |
6 | + → | |
7 | + .tag.commit.inline= @merge_request.target_branch | |
8 | + .right | |
9 | + - if @merge_request.closed | |
10 | + %span.tag.high Closed | |
11 | + - else | |
12 | + %span.tag.today Open | |
13 | + | |
14 | + .data | |
15 | + %p= @merge_request.title | |
16 | + | |
17 | + - if @merge_request.author == @merge_request.assignee | |
18 | + = image_tag gravatar_icon(@merge_request.assignee_email), :width => 20, :style => "padding:0 5px;" | |
19 | + = @merge_request.assignee_name | |
20 | + - else | |
21 | + = image_tag gravatar_icon(@merge_request.author_email), :width => 20, :style => "padding:0 5px;" | |
22 | + = @merge_request.author_name | |
23 | + → | |
24 | + = image_tag gravatar_icon(@merge_request.assignee_email), :width => 20, :style => "padding:0 5px;" | |
25 | + = @merge_request.assignee_name | |
26 | + .right | |
27 | + %cite.cgray= @merge_request.created_at.stamp("21 Aug 2011, 11:15pm") | |
28 | + .clear | |
29 | + | |
30 | + .buttons | |
31 | + - if can? current_user, :write_project, @project | |
32 | + - if @merge_request.closed | |
33 | + = link_to 'Reopen', project_merge_request_path(@project, @merge_request, :merge_request => {:closed => false }, :status_only => true), :method => :put, :class => "grey-button" | |
34 | + - else | |
35 | + = link_to 'Close', project_merge_request_path(@project, @merge_request, :merge_request => {:closed => true }, :status_only => true), :method => :put, :class => "grey-button" | |
36 | + .right | |
37 | + = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), :class => "grey-button positive" | |
38 | + | |
39 | +.clear | |
40 | +%br | |
41 | +%br | |
42 | + | |
43 | +#gitlab-tabs | |
44 | + %ul | |
45 | + %li= link_to "Notes", "#merge-notes" | |
46 | + %li= link_to "Commits", commits_project_merge_request_path(@project, @merge_request) | |
47 | + %li= link_to "Diff", diffs_project_merge_request_path(@project, @merge_request) | |
48 | + | |
49 | + #merge-notes | |
50 | + .issue_notes= render "notes/notes" | |
51 | + .loading{ :style => "display:none;"} | |
52 | + %center= image_tag "ajax-loader.gif" | |
53 | + .clear | |
54 | + | |
55 | + | |
56 | +:javascript | |
57 | + $(function(){ | |
58 | + $("#gitlab-tabs").tabs(); | |
59 | + }) | ... | ... |
config/routes.rb
... | ... | @@ -0,0 +1,15 @@ |
1 | +class CreateMergeRequests < ActiveRecord::Migration | |
2 | + def change | |
3 | + create_table :merge_requests do |t| | |
4 | + t.string :target_branch, :null => false | |
5 | + t.string :source_branch, :null => false | |
6 | + t.integer :project_id, :null => false | |
7 | + t.integer :author_id | |
8 | + t.integer :assignee_id | |
9 | + t.string :title | |
10 | + t.boolean :closed, :default => false, :null => false | |
11 | + | |
12 | + t.timestamps | |
13 | + end | |
14 | + end | |
15 | +end | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 12 | # It's strongly recommended to check this file into your version control system. |
13 | 13 | |
14 | -ActiveRecord::Schema.define(:version => 20111124115339) do | |
14 | +ActiveRecord::Schema.define(:version => 20111127155345) do | |
15 | 15 | |
16 | 16 | create_table "features", :force => true do |t| |
17 | 17 | t.string "name" |
... | ... | @@ -21,6 +21,8 @@ ActiveRecord::Schema.define(:version => 20111124115339) do |
21 | 21 | t.integer "project_id" |
22 | 22 | t.datetime "created_at" |
23 | 23 | t.datetime "updated_at" |
24 | + t.string "version" | |
25 | + t.integer "status", :default => 0, :null => false | |
24 | 26 | end |
25 | 27 | |
26 | 28 | create_table "issues", :force => true do |t| |
... | ... | @@ -45,6 +47,18 @@ ActiveRecord::Schema.define(:version => 20111124115339) do |
45 | 47 | t.string "identifier" |
46 | 48 | end |
47 | 49 | |
50 | + create_table "merge_requests", :force => true do |t| | |
51 | + t.string "target_branch", :null => false | |
52 | + t.string "source_branch", :null => false | |
53 | + t.integer "project_id", :null => false | |
54 | + t.integer "author_id" | |
55 | + t.integer "assignee_id" | |
56 | + t.string "title" | |
57 | + t.boolean "closed", :default => false, :null => false | |
58 | + t.datetime "created_at" | |
59 | + t.datetime "updated_at" | |
60 | + end | |
61 | + | |
48 | 62 | create_table "notes", :force => true do |t| |
49 | 63 | t.text "note" |
50 | 64 | t.string "noteable_id" | ... | ... |
spec/factories.rb
... | ... | @@ -34,6 +34,12 @@ Factory.add(:issue, Issue) do |obj| |
34 | 34 | obj.title = Faker::Lorem.sentence |
35 | 35 | end |
36 | 36 | |
37 | +Factory.add(:merge_request, MergeRequest) do |obj| | |
38 | + obj.title = Faker::Lorem.sentence | |
39 | + obj.source_branch = "master" | |
40 | + obj.target_branch = "master" | |
41 | +end | |
42 | + | |
37 | 43 | Factory.add(:snippet, Snippet) do |obj| |
38 | 44 | obj.title = Faker::Lorem.sentence |
39 | 45 | obj.file_name = Faker::Lorem.sentence | ... | ... |
spec/helpers/application_helper_spec.rb
... | ... | @@ -1,35 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe ApplicationHelper do | |
4 | - context ".gravatar_icon" do | |
5 | - context "over http" do | |
6 | - it "returns the correct URL to www.gravatar.com" do | |
7 | - expected = "http://www.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon" | |
8 | - | |
9 | - # Pretend we're running over HTTP | |
10 | - helper.stub(:request) do | |
11 | - request = double('request') | |
12 | - request.stub(:ssl?) { false } | |
13 | - request | |
14 | - end | |
15 | - | |
16 | - helper.gravatar_icon("admin@local.host").should == expected | |
17 | - end | |
18 | - end | |
19 | - | |
20 | - context "over https" do | |
21 | - it "returns the correct URL to secure.gravatar.com" do | |
22 | - expected = "https://secure.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon" | |
23 | - | |
24 | - # Pretend we're running over HTTPS | |
25 | - helper.stub(:request) do | |
26 | - request = double('request') | |
27 | - request.stub(:ssl?) { true } | |
28 | - request | |
29 | - end | |
30 | - | |
31 | - helper.gravatar_icon("admin@local.host").should == expected | |
32 | - end | |
33 | - end | |
34 | - end | |
35 | -end |
... | ... | @@ -0,0 +1,69 @@ |
1 | +require 'spec_helper' | |
2 | + | |
3 | +describe "MergeRequests" do | |
4 | + let(:project) { Factory :project } | |
5 | + | |
6 | + before do | |
7 | + login_as :user | |
8 | + project.add_access(@user, :read, :write) | |
9 | + @merge_request = Factory :merge_request, | |
10 | + :author => @user, | |
11 | + :assignee => @user, | |
12 | + :project => project | |
13 | + end | |
14 | + | |
15 | + describe "GET /merge_requests" do | |
16 | + before do | |
17 | + visit project_merge_requests_path(project) | |
18 | + end | |
19 | + | |
20 | + subject { page } | |
21 | + | |
22 | + it { should have_content(@merge_request.title) } | |
23 | + it { should have_content(@merge_request.target_branch) } | |
24 | + it { should have_content(@merge_request.source_branch) } | |
25 | + it { should have_content(@merge_request.assignee.name) } | |
26 | + end | |
27 | + | |
28 | + describe "GET /merge_request/:id" do | |
29 | + before do | |
30 | + visit project_merge_request_path(project, @merge_request) | |
31 | + end | |
32 | + | |
33 | + subject { page } | |
34 | + | |
35 | + it { should have_content(@merge_request.title) } | |
36 | + it { should have_content(@merge_request.target_branch) } | |
37 | + it { should have_content(@merge_request.source_branch) } | |
38 | + it { should have_content(@merge_request.assignee.name) } | |
39 | + | |
40 | + describe "Close merge request" do | |
41 | + before { click_link "Close" } | |
42 | + | |
43 | + it { should have_content(@merge_request.title) } | |
44 | + it "Show page should inform user that merge request closed" do | |
45 | + within ".merge-request-show-holder h3" do | |
46 | + page.should have_content "Closed" | |
47 | + end | |
48 | + end | |
49 | + end | |
50 | + end | |
51 | + | |
52 | + describe "GET /merge_requests/new" do | |
53 | + before do | |
54 | + visit new_project_merge_request_path(project) | |
55 | + fill_in "merge_request_title", :with => "Merge Request Title" | |
56 | + select "master", :from => "merge_request_source_branch" | |
57 | + select "master", :from => "merge_request_target_branch" | |
58 | + select @user.name, :from => "merge_request_assignee_id" | |
59 | + click_button "Save" | |
60 | + end | |
61 | + | |
62 | + it { current_path.should == project_merge_request_path(project, project.merge_requests.last) } | |
63 | + | |
64 | + it "should create merge request" do | |
65 | + page.should have_content "Open" | |
66 | + page.should have_content @user.name | |
67 | + end | |
68 | + end | |
69 | +end | ... | ... |
spec/requests/projects_security_spec.rb
... | ... | @@ -122,5 +122,14 @@ describe "Projects" do |
122 | 122 | it { project_snippets_path(@project).should be_denied_for :user } |
123 | 123 | it { project_snippets_path(@project).should be_denied_for :visitor } |
124 | 124 | end |
125 | + | |
126 | + describe "GET /project_code/merge_requests" do | |
127 | + it { project_merge_requests_path(@project).should be_allowed_for @u1 } | |
128 | + it { project_merge_requests_path(@project).should be_allowed_for @u3 } | |
129 | + it { project_merge_requests_path(@project).should be_denied_for :admin } | |
130 | + it { project_merge_requests_path(@project).should be_denied_for @u2 } | |
131 | + it { project_merge_requests_path(@project).should be_denied_for :user } | |
132 | + it { project_merge_requests_path(@project).should be_denied_for :visitor } | |
133 | + end | |
125 | 134 | end |
126 | 135 | end | ... | ... |