Commit 08994f3f6034eb214de4cda304d8797a51b2397a
Exists in
master
and in
4 other branches
Merge remote-tracking branch 'origin/merge_button'
Conflicts: app/assets/javascripts/merge_requests.js db/schema.rb
Showing
13 changed files
with
223 additions
and
7 deletions
Show diff stats
app/assets/javascripts/merge_requests.js
| 1 | var MergeRequest = { | 1 | var MergeRequest = { |
| 2 | diffs_loaded: false, | 2 | diffs_loaded: false, |
| 3 | commits_loaded: false, | 3 | commits_loaded: false, |
| 4 | + opts: false, | ||
| 4 | 5 | ||
| 5 | init: | 6 | init: |
| 6 | - function() { | 7 | + function(opts) { |
| 8 | + this.opts = opts; | ||
| 9 | + | ||
| 10 | + if($(".automerge_widget").length){ | ||
| 11 | + $.get(opts.url_to_automerge_check, function(data){ | ||
| 12 | + $(".automerge_widget").hide(); | ||
| 13 | + $(".automerge_widget." + data.state).show(); | ||
| 14 | + }, "json"); | ||
| 15 | + } | ||
| 16 | + | ||
| 7 | $(".nav-tabs a").live("click", function() { | 17 | $(".nav-tabs a").live("click", function() { |
| 8 | $(".nav-tabs a").parent().removeClass("active"); | 18 | $(".nav-tabs a").parent().removeClass("active"); |
| 9 | $(this).parent().addClass("active"); | 19 | $(this).parent().addClass("active"); |
| @@ -44,5 +54,11 @@ var MergeRequest = { | @@ -44,5 +54,11 @@ var MergeRequest = { | ||
| 44 | function() { | 54 | function() { |
| 45 | $(".first_mr_commits").remove(); | 55 | $(".first_mr_commits").remove(); |
| 46 | $(".all_mr_commits").removeClass("hide"); | 56 | $(".all_mr_commits").removeClass("hide"); |
| 57 | + }, | ||
| 58 | + | ||
| 59 | + already_cannot_be_merged: | ||
| 60 | + function(){ | ||
| 61 | + $(".automerge_widget").hide(); | ||
| 62 | + $(".automerge_widget.already_cannot_be_merged").show(); | ||
| 47 | } | 63 | } |
| 48 | } | 64 | } |
app/controllers/merge_requests_controller.rb
| @@ -2,7 +2,7 @@ class MergeRequestsController < ApplicationController | @@ -2,7 +2,7 @@ class MergeRequestsController < ApplicationController | ||
| 2 | before_filter :authenticate_user! | 2 | before_filter :authenticate_user! |
| 3 | before_filter :project | 3 | before_filter :project |
| 4 | before_filter :module_enabled | 4 | before_filter :module_enabled |
| 5 | - before_filter :merge_request, :only => [:edit, :update, :destroy, :show, :commits, :diffs] | 5 | + before_filter :merge_request, :only => [:edit, :update, :destroy, :show, :commits, :diffs, :automerge, :automerge_check] |
| 6 | layout "project" | 6 | layout "project" |
| 7 | 7 | ||
| 8 | # Authorize | 8 | # Authorize |
| @@ -89,6 +89,7 @@ class MergeRequestsController < ApplicationController | @@ -89,6 +89,7 @@ class MergeRequestsController < ApplicationController | ||
| 89 | respond_to do |format| | 89 | respond_to do |format| |
| 90 | if @merge_request.update_attributes(params[:merge_request].merge(:author_id_of_changes => current_user.id)) | 90 | if @merge_request.update_attributes(params[:merge_request].merge(:author_id_of_changes => current_user.id)) |
| 91 | @merge_request.reload_code | 91 | @merge_request.reload_code |
| 92 | + @merge_request.mark_as_unchecked | ||
| 92 | format.html { redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.' } | 93 | format.html { redirect_to [@project, @merge_request], notice: 'Merge request was successfully updated.' } |
| 93 | format.json { head :ok } | 94 | format.json { head :ok } |
| 94 | else | 95 | else |
| @@ -98,6 +99,23 @@ class MergeRequestsController < ApplicationController | @@ -98,6 +99,23 @@ class MergeRequestsController < ApplicationController | ||
| 98 | end | 99 | end |
| 99 | end | 100 | end |
| 100 | 101 | ||
| 102 | + def automerge_check | ||
| 103 | + if @merge_request.unchecked? | ||
| 104 | + @merge_request.check_if_can_be_merged | ||
| 105 | + end | ||
| 106 | + render :json => {:state => @merge_request.human_state} | ||
| 107 | + end | ||
| 108 | + | ||
| 109 | + def automerge | ||
| 110 | + return access_denied! unless can?(current_user, :accept_mr, @project) | ||
| 111 | + if @merge_request.open? && @merge_request.can_be_merged? | ||
| 112 | + @merge_request.automerge!(current_user) | ||
| 113 | + @status = true | ||
| 114 | + else | ||
| 115 | + @status = false | ||
| 116 | + end | ||
| 117 | + end | ||
| 118 | + | ||
| 101 | def destroy | 119 | def destroy |
| 102 | @merge_request.destroy | 120 | @merge_request.destroy |
| 103 | 121 |
app/models/ability.rb
| @@ -48,6 +48,7 @@ class Ability | @@ -48,6 +48,7 @@ class Ability | ||
| 48 | :admin_team_member, | 48 | :admin_team_member, |
| 49 | :admin_merge_request, | 49 | :admin_merge_request, |
| 50 | :admin_note, | 50 | :admin_note, |
| 51 | + :accept_mr, | ||
| 51 | :admin_wiki | 52 | :admin_wiki |
| 52 | ] if project.master_access_for?(user) || project.owner == user | 53 | ] if project.master_access_for?(user) || project.owner == user |
| 53 | 54 |
app/models/merge_request.rb
| 1 | require File.join(Rails.root, "app/models/commit") | 1 | require File.join(Rails.root, "app/models/commit") |
| 2 | 2 | ||
| 3 | class MergeRequest < ActiveRecord::Base | 3 | class MergeRequest < ActiveRecord::Base |
| 4 | + UNCHECKED = 1 | ||
| 5 | + CAN_BE_MERGED = 2 | ||
| 6 | + CANNOT_BE_MERGED = 3 | ||
| 7 | + | ||
| 4 | belongs_to :project | 8 | belongs_to :project |
| 5 | belongs_to :author, :class_name => "User" | 9 | belongs_to :author, :class_name => "User" |
| 6 | belongs_to :assignee, :class_name => "User" | 10 | belongs_to :assignee, :class_name => "User" |
| @@ -45,6 +49,15 @@ class MergeRequest < ActiveRecord::Base | @@ -45,6 +49,15 @@ class MergeRequest < ActiveRecord::Base | ||
| 45 | where("source_branch like :branch or target_branch like :branch", :branch => branch_name) | 49 | where("source_branch like :branch or target_branch like :branch", :branch => branch_name) |
| 46 | end | 50 | end |
| 47 | 51 | ||
| 52 | + def human_state | ||
| 53 | + states = { | ||
| 54 | + CAN_BE_MERGED => "can_be_merged", | ||
| 55 | + CANNOT_BE_MERGED => "cannot_be_merged", | ||
| 56 | + UNCHECKED => "unchecked" | ||
| 57 | + } | ||
| 58 | + states[self.state] | ||
| 59 | + end | ||
| 60 | + | ||
| 48 | def validate_branches | 61 | def validate_branches |
| 49 | if target_branch == source_branch | 62 | if target_branch == source_branch |
| 50 | errors.add :base, "You can not use same branch for source and target branches" | 63 | errors.add :base, "You can not use same branch for source and target branches" |
| @@ -56,6 +69,27 @@ class MergeRequest < ActiveRecord::Base | @@ -56,6 +69,27 @@ class MergeRequest < ActiveRecord::Base | ||
| 56 | self.reloaded_diffs | 69 | self.reloaded_diffs |
| 57 | end | 70 | end |
| 58 | 71 | ||
| 72 | + def unchecked? | ||
| 73 | + state == UNCHECKED | ||
| 74 | + end | ||
| 75 | + | ||
| 76 | + def mark_as_unchecked | ||
| 77 | + self.update_attributes(:state => UNCHECKED) | ||
| 78 | + end | ||
| 79 | + | ||
| 80 | + def can_be_merged? | ||
| 81 | + state == CAN_BE_MERGED | ||
| 82 | + end | ||
| 83 | + | ||
| 84 | + def check_if_can_be_merged | ||
| 85 | + self.state = if GitlabMerge.new(self, self.author).can_be_merged? | ||
| 86 | + CAN_BE_MERGED | ||
| 87 | + else | ||
| 88 | + CANNOT_BE_MERGED | ||
| 89 | + end | ||
| 90 | + self.save | ||
| 91 | + end | ||
| 92 | + | ||
| 59 | def new? | 93 | def new? |
| 60 | today? && created_at == updated_at | 94 | today? && created_at == updated_at |
| 61 | end | 95 | end |
| @@ -118,6 +152,10 @@ class MergeRequest < ActiveRecord::Base | @@ -118,6 +152,10 @@ class MergeRequest < ActiveRecord::Base | ||
| 118 | save | 152 | save |
| 119 | end | 153 | end |
| 120 | 154 | ||
| 155 | + def mark_as_unmergable | ||
| 156 | + self.update_attributes :state => CANNOT_BE_MERGED | ||
| 157 | + end | ||
| 158 | + | ||
| 121 | def reloaded_commits | 159 | def reloaded_commits |
| 122 | if open? && unmerged_commits.any? | 160 | if open? && unmerged_commits.any? |
| 123 | self.st_commits = unmerged_commits | 161 | self.st_commits = unmerged_commits |
| @@ -144,6 +182,16 @@ class MergeRequest < ActiveRecord::Base | @@ -144,6 +182,16 @@ class MergeRequest < ActiveRecord::Base | ||
| 144 | :author_id => user_id | 182 | :author_id => user_id |
| 145 | ) | 183 | ) |
| 146 | end | 184 | end |
| 185 | + | ||
| 186 | + def automerge!(current_user) | ||
| 187 | + if GitlabMerge.new(self, current_user).merge | ||
| 188 | + self.merge!(current_user.id) | ||
| 189 | + true | ||
| 190 | + end | ||
| 191 | + rescue | ||
| 192 | + self.mark_as_unmergable | ||
| 193 | + false | ||
| 194 | + end | ||
| 147 | end | 195 | end |
| 148 | # == Schema Information | 196 | # == Schema Information |
| 149 | # | 197 | # |
app/models/project/hooks_trait.rb
| @@ -18,7 +18,7 @@ module Project::HooksTrait | @@ -18,7 +18,7 @@ module Project::HooksTrait | ||
| 18 | 18 | ||
| 19 | # Update code for merge requests | 19 | # Update code for merge requests |
| 20 | mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all | 20 | mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all |
| 21 | - mrs.each { |merge_request| merge_request.reload_code } | 21 | + mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } |
| 22 | 22 | ||
| 23 | # Close merge requests | 23 | # Close merge requests |
| 24 | mrs = self.merge_requests.opened.where(:target_branch => branch_name).all | 24 | mrs = self.merge_requests.opened.where(:target_branch => branch_name).all |
app/views/merge_requests/show.html.haml
| @@ -8,7 +8,6 @@ | @@ -8,7 +8,6 @@ | ||
| 8 | %span.right | 8 | %span.right |
| 9 | - if can?(current_user, :modify_merge_request, @merge_request) | 9 | - if can?(current_user, :modify_merge_request, @merge_request) |
| 10 | - if @merge_request.open? | 10 | - if @merge_request.open? |
| 11 | - = link_to "Show how to merge", "#", :class => "how_to_merge_link btn small padded", :title => "How To Merge" | ||
| 12 | = link_to 'Close', project_merge_request_path(@project, @merge_request, :merge_request => {:closed => true }, :status_only => true), :method => :put, :class => "btn small padded", :title => "Close merge request" | 11 | = link_to 'Close', project_merge_request_path(@project, @merge_request, :merge_request => {:closed => true }, :status_only => true), :method => :put, :class => "btn small padded", :title => "Close merge request" |
| 13 | = link_to edit_project_merge_request_path(@project, @merge_request), :class => "btn small padded" do | 12 | = link_to edit_project_merge_request_path(@project, @merge_request), :class => "btn small padded" do |
| 14 | Edit | 13 | Edit |
| @@ -53,6 +52,34 @@ | @@ -53,6 +52,34 @@ | ||
| 53 | Closed by #{@merge_request.closed_event.author_name} | 52 | Closed by #{@merge_request.closed_event.author_name} |
| 54 | %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. | 53 | %small #{time_ago_in_words(@merge_request.closed_event.created_at)} ago. |
| 55 | 54 | ||
| 55 | + | ||
| 56 | +- if @merge_request.open? && @commits.any? && can?(current_user, :accept_mr, @project) | ||
| 57 | + .automerge_widget.can_be_merged{:style => "display:none"} | ||
| 58 | + .ui-box.padded | ||
| 59 | + %p | ||
| 60 | + You can accept this request automatically. If you still want to do it manually - #{link_to "click here", "#", :class => "how_to_merge_link vlink", :title => "How To Merge"} for instructions | ||
| 61 | + = link_to "Accept Merge Request", automerge_project_merge_request_path(@project, @merge_request), :class => "btn small info accept_merge_request", :remote => true | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + .automerge_widget.cannot_be_merged{:style => "display:none"} | ||
| 65 | + .alert-message | ||
| 66 | + %p | ||
| 67 | + %strong This request cant be merged with GitLab. You should do it manually | ||
| 68 | + = link_to "Show how to merge", "#", :class => "how_to_merge_link btn small padded", :title => "How To Merge" | ||
| 69 | + | ||
| 70 | + .automerge_widget.unchecked | ||
| 71 | + .alert-message | ||
| 72 | + %p | ||
| 73 | + %strong Checking for ability to automatically merge… | ||
| 74 | + | ||
| 75 | + .automerge_widget.already_cannot_be_merged{:style => "display:none"} | ||
| 76 | + .alert-message | ||
| 77 | + %p | ||
| 78 | + %strong This merge request already can not be merged | ||
| 79 | + | ||
| 80 | + | ||
| 81 | + | ||
| 82 | + | ||
| 56 | = render "merge_requests/commits" | 83 | = render "merge_requests/commits" |
| 57 | 84 | ||
| 58 | - unless @commits.empty? | 85 | - unless @commits.empty? |
| @@ -72,7 +99,13 @@ | @@ -72,7 +99,13 @@ | ||
| 72 | 99 | ||
| 73 | :javascript | 100 | :javascript |
| 74 | $(function(){ | 101 | $(function(){ |
| 75 | - MergeRequest.init(); | 102 | + MergeRequest.init({ |
| 103 | + url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", | ||
| 104 | + }); | ||
| 105 | + | ||
| 106 | + $(".accept_merge_request").live("ajax:beforeSend", function() { | ||
| 107 | + $(this).replaceWith('#{image_tag "ajax_loader.gif"}'); | ||
| 108 | + }) | ||
| 76 | }) | 109 | }) |
| 77 | 110 | ||
| 78 | = render "notes/per_line_form" | 111 | = render "notes/per_line_form" |
config/routes.rb
| @@ -100,6 +100,8 @@ Gitlab::Application.routes.draw do | @@ -100,6 +100,8 @@ Gitlab::Application.routes.draw do | ||
| 100 | resources :merge_requests do | 100 | resources :merge_requests do |
| 101 | member do | 101 | member do |
| 102 | get :diffs | 102 | get :diffs |
| 103 | + get :automerge | ||
| 104 | + get :automerge_check | ||
| 103 | end | 105 | end |
| 104 | 106 | ||
| 105 | collection do | 107 | collection do |
db/migrate/20120329170745_add_automerge_to_merge_request.rb
0 → 100644
db/schema.rb
| @@ -11,8 +11,7 @@ | @@ -11,8 +11,7 @@ | ||
| 11 | # | 11 | # |
| 12 | # It's strongly recommended to check this file into your version control system. | 12 | # It's strongly recommended to check this file into your version control system. |
| 13 | 13 | ||
| 14 | -ActiveRecord::Schema.define(:version => 20120413135904) do | ||
| 15 | - | 14 | +ActiveRecord::Schema.define(:version => 20120329170745) do |
| 16 | create_table "events", :force => true do |t| | 15 | create_table "events", :force => true do |t| |
| 17 | t.string "target_type" | 16 | t.string "target_type" |
| 18 | t.integer "target_id" | 17 | t.integer "target_id" |
| @@ -65,6 +64,7 @@ ActiveRecord::Schema.define(:version => 20120413135904) do | @@ -65,6 +64,7 @@ ActiveRecord::Schema.define(:version => 20120413135904) do | ||
| 65 | t.text "st_commits", :limit => 2147483647 | 64 | t.text "st_commits", :limit => 2147483647 |
| 66 | t.text "st_diffs", :limit => 2147483647 | 65 | t.text "st_diffs", :limit => 2147483647 |
| 67 | t.boolean "merged", :default => false, :null => false | 66 | t.boolean "merged", :default => false, :null => false |
| 67 | + t.boolean "auto_merge", :default => true, :null => false | ||
| 68 | end | 68 | end |
| 69 | 69 | ||
| 70 | add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" | 70 | add_index "merge_requests", ["project_id"], :name => "index_merge_requests_on_project_id" |
| @@ -0,0 +1,48 @@ | @@ -0,0 +1,48 @@ | ||
| 1 | +class GitlabMerge | ||
| 2 | + attr_accessor :project, :merge_path, :merge_request, :user | ||
| 3 | + | ||
| 4 | + def initialize(merge_request, user) | ||
| 5 | + self.user = user | ||
| 6 | + self.merge_request = merge_request | ||
| 7 | + self.project = merge_request.project | ||
| 8 | + self.merge_path = File.join(Rails.root, "tmp", "merge_repo", project.path, merge_request.id.to_s) | ||
| 9 | + FileUtils.rm_rf(merge_path) | ||
| 10 | + FileUtils.mkdir_p merge_path | ||
| 11 | + end | ||
| 12 | + | ||
| 13 | + def can_be_merged? | ||
| 14 | + pull do |repo, output| | ||
| 15 | + !(output =~ /Automatic merge failed/) | ||
| 16 | + end | ||
| 17 | + end | ||
| 18 | + | ||
| 19 | + def merge | ||
| 20 | + pull do |repo, output| | ||
| 21 | + if output =~ /Automatic merge failed/ | ||
| 22 | + false | ||
| 23 | + else | ||
| 24 | + repo.git.push({}, "origin", merge_request.target_branch) | ||
| 25 | + true | ||
| 26 | + end | ||
| 27 | + end | ||
| 28 | + end | ||
| 29 | + | ||
| 30 | + def pull | ||
| 31 | + File.open(File.join(Rails.root, "tmp", "merge_repo", "#{project.path}.lock"), "w+") do |f| | ||
| 32 | + f.flock(File::LOCK_EX) | ||
| 33 | + | ||
| 34 | + self.project.repo.git.clone({:branch => merge_request.target_branch}, project.url_to_repo, merge_path) | ||
| 35 | + unless File.exist?(self.merge_path) | ||
| 36 | + raise "Gitlab user do not have access to repo. You should run: rake gitlab_enable_automerge" | ||
| 37 | + end | ||
| 38 | + Dir.chdir(merge_path) do | ||
| 39 | + merge_repo = Grit::Repo.new('.') | ||
| 40 | + merge_repo.git.sh "git config user.name \"#{user.name}\"" | ||
| 41 | + merge_repo.git.sh "git config user.email \"#{user.email}\"" | ||
| 42 | + output = merge_repo.git.pull({}, "--no-ff", "origin", merge_request.source_branch) | ||
| 43 | + yield(merge_repo, output) | ||
| 44 | + end | ||
| 45 | + | ||
| 46 | + end | ||
| 47 | + end | ||
| 48 | +end |
lib/gitlabhq/gitolite.rb
| @@ -123,5 +123,34 @@ module Gitlabhq | @@ -123,5 +123,34 @@ module Gitlabhq | ||
| 123 | 123 | ||
| 124 | repo | 124 | repo |
| 125 | end | 125 | end |
| 126 | + | ||
| 127 | + def admin_all_repo | ||
| 128 | + ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite')) | ||
| 129 | + conf = ga_repo.config | ||
| 130 | + owner_name = "" | ||
| 131 | + | ||
| 132 | + # Read gitolite-admin user | ||
| 133 | + # | ||
| 134 | + begin | ||
| 135 | + repo = conf.get_repo("gitolite-admin") | ||
| 136 | + owner_name = repo.permissions[0]["RW+"][""][0] | ||
| 137 | + raise StandardError if owner_name.blank? | ||
| 138 | + rescue => ex | ||
| 139 | + puts "Cant determine gitolite-admin owner".red | ||
| 140 | + raise StandardError | ||
| 141 | + end | ||
| 142 | + | ||
| 143 | + # @ALL repos premission for gitolite owner | ||
| 144 | + repo_name = "@all" | ||
| 145 | + repo = if conf.has_repo?(repo_name) | ||
| 146 | + conf.get_repo(repo_name) | ||
| 147 | + else | ||
| 148 | + ::Gitolite::Config::Repo.new(repo_name) | ||
| 149 | + end | ||
| 150 | + | ||
| 151 | + repo.add_permission("RW+", "", owner_name) | ||
| 152 | + conf.add_repo(repo, true) | ||
| 153 | + ga_repo.save | ||
| 154 | + end | ||
| 126 | end | 155 | end |
| 127 | end | 156 | end |