From 37224dc9c1ee80ba9030b616e2bc87bd96919e09 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Date: Wed, 15 Feb 2012 22:02:33 +0200
Subject: [PATCH] ProtectedBranches model, Master permission for repo\n Allow
 push to protected branch for masters only

---
 .../protected_branches_controller.rb          | 23 +++++++
 app/models/project.rb                         | 41 ++++++++----
 app/models/protected_branch.rb                | 29 +++++++++
 app/models/repository.rb                      |  4 +-
 app/views/protected_branches/index.html.haml  | 39 ++++++++++++
 .../repositories/_branches_head.html.haml     |  9 +++
 app/views/repositories/_head.html.haml        |  2 +-
 app/views/repositories/branches.html.haml     |  2 +-
 config/routes.rb                              |  1 +
 ...0120215182305_create_protected_branches.rb | 10 +++
 db/schema.rb                                  | 21 ++++++-
 lib/gitlabhq/gitolite.rb                      | 62 ++++++++++---------
 spec/models/project_spec.rb                   | 23 ++++---
 spec/models/protected_branch_spec.rb          | 16 +++++
 14 files changed, 229 insertions(+), 53 deletions(-)
 create mode 100644 app/controllers/protected_branches_controller.rb
 create mode 100644 app/models/protected_branch.rb
 create mode 100644 app/views/protected_branches/index.html.haml
 create mode 100644 app/views/repositories/_branches_head.html.haml
 create mode 100644 db/migrate/20120215182305_create_protected_branches.rb
 create mode 100644 spec/models/protected_branch_spec.rb

diff --git a/app/controllers/protected_branches_controller.rb b/app/controllers/protected_branches_controller.rb
new file mode 100644
index 00000000000..c91bd903645
--- /dev/null
+++ b/app/controllers/protected_branches_controller.rb
@@ -0,0 +1,23 @@
+class ProtectedBranchesController < ApplicationController
+  before_filter :project
+
+  # Authorize
+  before_filter :add_project_abilities
+  before_filter :authorize_read_project!
+  before_filter :require_non_empty_project
+
+  layout "project"
+
+  def index
+    @branches = @project.protected_branches.all
+    @protected_branch = @project.protected_branches.new
+  end
+
+  def create
+    @project.protected_branches.create(params[:protected_branch])
+    redirect_to project_protected_branches_path(@project)
+  end
+
+  def destroy
+  end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 49311daaea7..ac70eedb009 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -16,6 +16,7 @@ class Project < ActiveRecord::Base
   has_many :snippets, :dependent => :destroy
   has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
   has_many :web_hooks, :dependent => :destroy
+  has_many :protected_branches, :dependent => :destroy
 
   acts_as_taggable
 
@@ -138,6 +139,15 @@ class Project < ActiveRecord::Base
     data
   end
 
+  def open_branches
+    if protected_branches.empty?
+      self.repo.heads
+    else
+      pnames = protected_branches.map(&:name)
+      self.repo.heads.reject { |h| pnames.include?(h.name) }
+    end.sort_by(&:name)
+  end
+
   def team_member_by_name_or_email(email = nil, name = nil)
     user = users.where("email like ? or name like ?", email, name).first
     users_projects.find_by_user_id(user.id) if user
@@ -210,6 +220,12 @@ class Project < ActiveRecord::Base
     keys.map(&:identifier)
   end
 
+  def repository_masters
+    keys = Key.joins({:user => :users_projects}).
+      where("users_projects.project_id = ? AND users_projects.repo_access = ?", id, Repository::REPO_MASTER)
+    keys.map(&:identifier)
+  end
+
   def readers
     @readers ||= users_projects.includes(:user).where(:project_access => [PROJECT_R, PROJECT_RW, PROJECT_RWA]).map(&:user)
   end
@@ -235,7 +251,7 @@ class Project < ActiveRecord::Base
   end
 
   def allow_pull_for?(user)
-    !users_projects.where(:user_id => user.id, :repo_access => [Repository::REPO_R, Repository::REPO_RW]).empty?
+    !users_projects.where(:user_id => user.id, :repo_access => [Repository::REPO_R, Repository::REPO_RW, Repository::REPO_MASTER]).empty?
   end
 
   def root_ref 
@@ -340,15 +356,18 @@ end
 #
 # Table name: projects
 #
-#  id             :integer         not null, primary key
-#  name           :string(255)
-#  path           :string(255)
-#  description    :text
-#  created_at     :datetime
-#  updated_at     :datetime
-#  private_flag   :boolean         default(TRUE), not null
-#  code           :string(255)
-#  owner_id       :integer
-#  default_branch :string(255)     default("master"), not null
+#  id                     :integer         not null, primary key
+#  name                   :string(255)
+#  path                   :string(255)
+#  description            :text
+#  created_at             :datetime
+#  updated_at             :datetime
+#  private_flag           :boolean         default(TRUE), not null
+#  code                   :string(255)
+#  owner_id               :integer
+#  default_branch         :string(255)     default("master"), not null
+#  issues_enabled         :boolean         default(TRUE), not null
+#  wall_enabled           :boolean         default(TRUE), not null
+#  merge_requests_enabled :boolean         default(TRUE), not null
 #
 
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
new file mode 100644
index 00000000000..9c2d391d0c1
--- /dev/null
+++ b/app/models/protected_branch.rb
@@ -0,0 +1,29 @@
+class ProtectedBranch < ActiveRecord::Base
+  belongs_to :project
+  validates_presence_of :project_id
+  validates_presence_of :name
+
+  after_save :update_repository
+  after_destroy :update_repository
+
+  def update_repository
+    Gitlabhq::GitHost.system.new.configure do |c|
+      c.update_project(project.path, project)
+    end
+  end
+
+  def commit
+    project.commit(self.name)
+  end
+end
+# == Schema Information
+#
+# Table name: protected_branches
+#
+#  id         :integer         not null, primary key
+#  project_id :integer         not null
+#  name       :string(255)     not null
+#  created_at :datetime        not null
+#  updated_at :datetime        not null
+#
+
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8f9f9746257..3d9ad262390 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -4,6 +4,7 @@ class Repository
   REPO_N = 0
   REPO_R = 1
   REPO_RW = 2
+  REPO_MASTER = 3
 
   attr_accessor :project
 
@@ -15,7 +16,8 @@ class Repository
     {
       "Denied"      => REPO_N,
       "Pull"        => REPO_R,
-      "Pull & Push" => REPO_RW
+      "Pull & Push" => REPO_RW,
+      "Master"      => REPO_MASTER
     }
   end
 
diff --git a/app/views/protected_branches/index.html.haml b/app/views/protected_branches/index.html.haml
new file mode 100644
index 00000000000..055d9369091
--- /dev/null
+++ b/app/views/protected_branches/index.html.haml
@@ -0,0 +1,39 @@
+= render "repositories/branches_head"
+
+= form_for [@project, @protected_branch] do |f|
+  -if @protected_branch.errors.any?
+    .alert-message.block-message.error
+      %ul
+        - @protected_branch.errors.full_messages.each do |msg|
+          %li= msg
+
+  .clearfix
+    = f.label :name
+    .input= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , { :include_blank => "Select branch" }, { :style => "width:300px" })
+  .actions
+    = f.submit 'Add', :class => "primary btn"
+
+
+- unless @branches.empty?
+  %table
+    %thead
+      %tr
+        %th Name
+        %th Last commit
+    %tbody
+      - @branches.each do |branch|
+        %tr
+          %td
+            = link_to project_commits_path(@project, :ref => branch.name) do
+              %strong= branch.name
+              - if branch.name == @project.root_ref
+                %span.label default
+          %td
+            = link_to project_commits_path(@project, branch.commit.id) do
+              = truncate branch.commit.id.to_s, :length => 10
+            = time_ago_in_words(branch.commit.committed_date)
+            ago
+
+
+:javascript
+  $('select#protected_branch_name').chosen();
diff --git a/app/views/repositories/_branches_head.html.haml b/app/views/repositories/_branches_head.html.haml
new file mode 100644
index 00000000000..d98dedc23eb
--- /dev/null
+++ b/app/views/repositories/_branches_head.html.haml
@@ -0,0 +1,9 @@
+= render "repositories/head"
+%ul.pills
+  %li{:class => ("active" if current_page?(branches_project_repository_path(@project)))}
+    = link_to branches_project_repository_path(@project) do 
+      All
+  %li{:class => ("active" if current_page?(project_protected_branches_path(@project)))}
+    = link_to project_protected_branches_path(@project) do 
+      Protected
+
diff --git a/app/views/repositories/_head.html.haml b/app/views/repositories/_head.html.haml
index 37360d48a62..2d43d008e2a 100644
--- a/app/views/repositories/_head.html.haml
+++ b/app/views/repositories/_head.html.haml
@@ -3,7 +3,7 @@
     = link_to project_repository_path(@project) do 
       %span
       Activities
-  %li{:class => "#{'active' if current_page?(branches_project_repository_path(@project)) }"}
+  %li{:class => "#{'active' if current_page?(branches_project_repository_path(@project)) || current_page?(project_protected_branches_path(@project)) }"}
     = link_to branches_project_repository_path(@project) do 
       %span
       Branches
diff --git a/app/views/repositories/branches.html.haml b/app/views/repositories/branches.html.haml
index d817ab5147f..91a97207791 100644
--- a/app/views/repositories/branches.html.haml
+++ b/app/views/repositories/branches.html.haml
@@ -1,4 +1,4 @@
-= render "head"
+= render "repositories/branches_head"
 - unless @branches.empty?
   %table
     %thead
diff --git a/config/routes.rb b/config/routes.rb
index 81bf93c7f23..e7a96fb4afb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -62,6 +62,7 @@ Gitlab::Application.routes.draw do
     end
 
     resources :deploy_keys
+    resources :protected_branches, :only => [:index, :create, :destroy]
 
     resources :refs, :only => [], :path => "/" do 
       collection do 
diff --git a/db/migrate/20120215182305_create_protected_branches.rb b/db/migrate/20120215182305_create_protected_branches.rb
new file mode 100644
index 00000000000..841d08c33d5
--- /dev/null
+++ b/db/migrate/20120215182305_create_protected_branches.rb
@@ -0,0 +1,10 @@
+class CreateProtectedBranches < ActiveRecord::Migration
+  def change
+    create_table :protected_branches do |t|
+      t.integer :project_id, :null => false
+      t.string :name, :null => false
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bb9f638764a..f7006c4c9c2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,19 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20120206170141) do
+ActiveRecord::Schema.define(:version => 20120215182305) do
+
+  create_table "features", :force => true do |t|
+    t.string   "name"
+    t.string   "branch_name"
+    t.integer  "assignee_id"
+    t.integer  "author_id"
+    t.integer  "project_id"
+    t.datetime "created_at"
+    t.datetime "updated_at"
+    t.string   "version"
+    t.integer  "status",      :default => 0, :null => false
+  end
 
   create_table "issues", :force => true do |t|
     t.string   "title"
@@ -82,6 +94,13 @@ ActiveRecord::Schema.define(:version => 20120206170141) do
     t.boolean  "merge_requests_enabled", :default => true,     :null => false
   end
 
+  create_table "protected_branches", :force => true do |t|
+    t.integer  "project_id", :null => false
+    t.string   "name",       :null => false
+    t.datetime "created_at", :null => false
+    t.datetime "updated_at", :null => false
+  end
+
   create_table "snippets", :force => true do |t|
     t.string   "title"
     t.text     "content"
diff --git a/lib/gitlabhq/gitolite.rb b/lib/gitlabhq/gitolite.rb
index e6eb8e5144b..4f911113ea0 100644
--- a/lib/gitlabhq/gitolite.rb
+++ b/lib/gitlabhq/gitolite.rb
@@ -64,21 +64,9 @@ module Gitlabhq
     def update_project(repo_name, project)
       ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
       conf = ga_repo.config
-
-      repo = if conf.has_repo?(repo_name)
-               conf.get_repo(repo_name)
-             else 
-               ::Gitolite::Config::Repo.new(repo_name)
-             end
-
-      name_readers = project.repository_readers
-      name_writers = project.repository_writers
-
-      repo.clean_permissions
-      repo.add_permission("R", "", name_readers) unless name_readers.blank?
-      repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
+      repo = update_project_config(project, conf)
       conf.add_repo(repo, true)
-
+      
       ga_repo.save
     end
 
@@ -89,25 +77,43 @@ module Gitlabhq
       conf = ga_repo.config
 
       projects.each do |project|
-        repo_name = project.path
-
-        repo = if conf.has_repo?(repo_name)
-                 conf.get_repo(repo_name)
-               else 
-                 ::Gitolite::Config::Repo.new(repo_name)
-               end
-
-        name_readers = project.repository_readers
-        name_writers = project.repository_writers
-
-        repo.clean_permissions
-        repo.add_permission("R", "", name_readers) unless name_readers.blank?
-        repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
+        repo = update_project_config(project, conf)
         conf.add_repo(repo, true)
       end
 
       ga_repo.save
     end
 
+    def update_project_config(project, conf)
+      repo_name = project.path
+
+      repo = if conf.has_repo?(repo_name)
+               conf.get_repo(repo_name)
+             else 
+               ::Gitolite::Config::Repo.new(repo_name)
+             end
+
+      name_readers = project.repository_readers
+      name_writers = project.repository_writers
+      name_masters = project.repository_masters
+
+      pr_br = project.protected_branches.map(&:name).join(" ")
+
+      repo.clean_permissions
+
+      # Deny access to protected branches for writers
+      unless name_writers.blank? || pr_br.blank?
+        repo.add_permission("-", pr_br, name_writers)
+      end
+
+      # Add read permissions
+      repo.add_permission("R", "", name_readers) unless name_readers.blank?
+
+      # Add write permissions
+      repo.add_permission("RW+", "", name_writers) unless name_writers.blank?
+      repo.add_permission("RW+", "", name_masters) unless name_masters.blank?
+
+      repo
+    end
   end
 end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 68bc82de898..437b139752a 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -290,15 +290,18 @@ end
 #
 # Table name: projects
 #
-#  id             :integer         not null, primary key
-#  name           :string(255)
-#  path           :string(255)
-#  description    :text
-#  created_at     :datetime
-#  updated_at     :datetime
-#  private_flag   :boolean         default(TRUE), not null
-#  code           :string(255)
-#  owner_id       :integer
-#  default_branch :string(255)     default("master"), not null
+#  id                     :integer         not null, primary key
+#  name                   :string(255)
+#  path                   :string(255)
+#  description            :text
+#  created_at             :datetime
+#  updated_at             :datetime
+#  private_flag           :boolean         default(TRUE), not null
+#  code                   :string(255)
+#  owner_id               :integer
+#  default_branch         :string(255)     default("master"), not null
+#  issues_enabled         :boolean         default(TRUE), not null
+#  wall_enabled           :boolean         default(TRUE), not null
+#  merge_requests_enabled :boolean         default(TRUE), not null
 #
 
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
new file mode 100644
index 00000000000..a0b0032a558
--- /dev/null
+++ b/spec/models/protected_branch_spec.rb
@@ -0,0 +1,16 @@
+# == Schema Information
+#
+# Table name: protected_branches
+#
+#  id         :integer         not null, primary key
+#  project_id :integer         not null
+#  name       :string(255)     not null
+#  created_at :datetime        not null
+#  updated_at :datetime        not null
+#
+
+require 'spec_helper'
+
+describe ProtectedBranch do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
-- 
GitLab