Commit 08994f3f6034eb214de4cda304d8797a51b2397a

Authored by Dmitriy Zaporozhets
2 parents 9af14e4b 3a6694b5

Merge remote-tracking branch 'origin/merge_button'

Conflicts:
	app/assets/javascripts/merge_requests.js
	db/schema.rb
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 &lt; ActiveRecord::Base @@ -45,6 +49,15 @@ class MergeRequest &lt; 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 &lt; ActiveRecord::Base @@ -56,6 +69,27 @@ class MergeRequest &lt; 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 &lt; ActiveRecord::Base @@ -118,6 +152,10 @@ class MergeRequest &lt; 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 &lt; ActiveRecord::Base @@ -144,6 +182,16 @@ class MergeRequest &lt; 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/automerge.js.haml 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +-if @status
  2 + :plain
  3 + location.reload();
  4 +-else
  5 + :plain
  6 + MergeRequest.already_cannot_be_merged()
  7 +
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 + &nbsp;
  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 &nbsp;
  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
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddAutomergeToMergeRequest < ActiveRecord::Migration
  2 + def change
  3 + add_column :merge_requests, :state, :integer, :null => false, :default => 1
  4 + end
  5 +end
@@ -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 =&gt; 20120413135904) do @@ -65,6 +64,7 @@ ActiveRecord::Schema.define(:version =&gt; 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"
lib/gitlab_merge.rb 0 → 100644
@@ -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
lib/tasks/gitlab_enable_automerge.rake 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +desc "Give gitlab user full access to every repo"
  2 +task :gitlab_enable_automerge => :environment do
  3 +
  4 + Gitlabhq::GitHost.system.new.configure do |git|
  5 + git.admin_all_repo
  6 + end
  7 +
  8 + puts "Done!".green
  9 +end