From 79021e674bab1d34609228343fbf3403d9bd9bc6 Mon Sep 17 00:00:00 2001
From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Date: Fri, 7 Sep 2012 08:16:29 +0300
Subject: [PATCH] Split gitolite backend. Use gitolite_config methods moved to
 separate class

---
 app/controllers/application_controller.rb  |   4 -
 app/views/errors/invalid_ssh_key.html.haml |   3 -
 lib/gitlab/backend/gitolite.rb             | 189 ++-------------------
 lib/gitlab/backend/gitolite_config.rb      | 168 ++++++++++++++++++
 spec/lib/gitolite_config_spec.rb           |  16 ++
 spec/lib/gitolite_spec.rb                  |  25 +++
 spec/support/gitolite_stub.rb              |  22 ++-
 7 files changed, 241 insertions(+), 186 deletions(-)
 delete mode 100644 app/views/errors/invalid_ssh_key.html.haml
 create mode 100644 lib/gitlab/backend/gitolite_config.rb
 create mode 100644 spec/lib/gitolite_config_spec.rb
 create mode 100644 spec/lib/gitolite_spec.rb

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7e53b8fe5ff..2fe2a97460f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -14,10 +14,6 @@ class ApplicationController < ActionController::Base
     render "errors/gitolite", layout: "error"
   end
 
-  rescue_from Gitlab::Gitolite::InvalidKey do |exception|
-    render "errors/invalid_ssh_key", layout: "error"
-  end
-
   rescue_from Encoding::CompatibilityError do |exception|
     render "errors/encoding", layout: "error", status: 404
   end
diff --git a/app/views/errors/invalid_ssh_key.html.haml b/app/views/errors/invalid_ssh_key.html.haml
deleted file mode 100644
index fb7922b0ea3..00000000000
--- a/app/views/errors/invalid_ssh_key.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%h1 Git Error
-%hr
-%p Seems like SSH Key you provided is not a valid SSH key.
diff --git a/lib/gitlab/backend/gitolite.rb b/lib/gitlab/backend/gitolite.rb
index 3dfb574c4d5..bc9e1f1d867 100644
--- a/lib/gitlab/backend/gitolite.rb
+++ b/lib/gitlab/backend/gitolite.rb
@@ -1,202 +1,43 @@
-require 'gitolite'
-require 'timeout'
-require 'fileutils'
+require_relative 'gitolite_config'
 
-# TODO: refactor & cleanup 
 module Gitlab
   class Gitolite
     class AccessDenied < StandardError; end
-    class InvalidKey < StandardError; end
+
+    def config
+      @config ||= Gitlab::GitoliteConfig.new
+    end
 
     def set_key key_id, key_content, projects
-      configure do |c|
-        c.update_keys(key_id, key_content)
-        c.update_projects(projects)
+      config.apply do |config|
+        config.write_key(key_id, key_content)
+        config.update_projects(projects)
       end
     end
 
     def remove_key key_id, projects
-      configure do |c|
-        c.delete_key(key_id)
-        c.update_projects(projects)
+      config.apply do |config|
+        config.rm_key(key_id)
+        config.update_projects(projects)
       end
     end
 
     def update_repository project
-      configure do |c|
-        c.update_project(project.path, project)
-      end
+      config.update_project!(project.path, project)
     end
 
-    alias_method :create_repository, :update_repository
-
     def remove_repository project
-      configure do |c|
-        c.destroy_project(project)
-      end
+      config.destroy_project!(project)
     end
 
     def url_to_repo path
       Gitlab.config.ssh_path + "#{path}.git"
     end
 
-    def initialize
-      # create tmp dir
-      @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
-    end
-
     def enable_automerge
-      configure do |git|
-        git.admin_all_repo
-      end
-    end
-
-    protected
-
-    def destroy_project(project)
-      FileUtils.rm_rf(project.path_to_repo)
-
-      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
-      conf = ga_repo.config
-      conf.rm_repo(project.path)
-      ga_repo.save
-    end
-
-    #update or create
-    def update_keys(user, key)
-      File.open(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
-    end
-
-    def delete_key(user)
-      File.unlink(File.join(@local_dir, 'gitolite/keydir',"#{user}.pub"))
-      `cd #{File.join(@local_dir,'gitolite')} ; git rm keydir/#{user}.pub`
-    end
-
-    # update or create
-    def update_project(repo_name, project)
-      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
-      conf = ga_repo.config
-      repo = update_project_config(project, conf)
-      conf.add_repo(repo, true)
-      
-      ga_repo.save
-    end
-
-    # Updates many projects and uses project.path as the repo path
-    # An order of magnitude faster than update_project
-    def update_projects(projects)
-      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
-      conf = ga_repo.config
-
-      projects.each do |project|
-        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.strip + "$ ", 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
-
-    def admin_all_repo
-      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(@local_dir,'gitolite'))
-      conf = ga_repo.config
-      owner_name = ""
-
-      # Read gitolite-admin user
-      #
-      begin 
-        repo = conf.get_repo("gitolite-admin")
-        owner_name = repo.permissions[0]["RW+"][""][0]
-        raise StandardError if owner_name.blank?
-      rescue => ex
-        puts "Can't determine gitolite-admin owner".red
-        raise StandardError
-      end
-
-      # @ALL repos premission for gitolite owner
-      repo_name = "@all"
-      repo = if conf.has_repo?(repo_name)
-               conf.get_repo(repo_name)
-             else 
-               ::Gitolite::Config::Repo.new(repo_name)
-             end
-
-      repo.add_permission("RW+", "", owner_name)
-      conf.add_repo(repo, true)
-      ga_repo.save
+      config.admin_all_repo!(project)
     end
 
-    private
-
-    def pull
-      # create tmp dir
-      @local_dir = File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
-      Dir.mkdir @local_dir
-
-      `git clone #{Gitlab.config.gitolite_admin_uri} #{@local_dir}/gitolite`
-    end
-
-    def push
-      Dir.chdir(File.join(@local_dir, "gitolite"))
-      `git add -A`
-      `git commit -am "GitLab"`
-      `git push`
-      Dir.chdir(Rails.root)
-
-      FileUtils.rm_rf(@local_dir)
-    end
-
-    def configure
-      Timeout::timeout(30) do
-        File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
-          begin 
-            f.flock(File::LOCK_EX)
-            pull
-            yield(self)
-            push
-          ensure
-            f.flock(File::LOCK_UN)
-          end
-        end
-      end
-    rescue Exception => ex
-      if ex.message =~ /is not a valid SSH key string/
-        raise Gitolite::InvalidKey.new("ssh key is not valid")
-      else
-        Gitlab::Logger.error(ex.message)
-        raise Gitolite::AccessDenied.new("gitolite timeout")
-      end
-    end
+    alias_method :create_repository, :update_repository
   end
 end
diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb
new file mode 100644
index 00000000000..5cf491211b6
--- /dev/null
+++ b/lib/gitlab/backend/gitolite_config.rb
@@ -0,0 +1,168 @@
+require 'gitolite'
+require 'timeout'
+require 'fileutils'
+
+module Gitlab
+  class GitoliteConfig
+    def config_tmp_dir
+      @config_tmp_dir ||= File.join(Rails.root, 'tmp',"gitlabhq-gitolite-#{Time.now.to_i}")
+    end
+
+    def apply
+      Timeout::timeout(30) do
+        File.open(File.join(Rails.root, 'tmp', "gitlabhq-gitolite.lock"), "w+") do |f|
+          begin
+            f.flock(File::LOCK_EX)
+            pull
+            yield(self)
+            push
+          ensure
+            f.flock(File::LOCK_UN)
+          end
+        end
+      end
+    rescue Exception => ex
+      Gitlab::Logger.error(ex.message)
+      raise Gitolite::AccessDenied.new("gitolite timeout")
+    end
+
+    def destroy_project(project)
+      FileUtils.rm_rf(project.path_to_repo)
+
+      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
+      conf = ga_repo.config
+      conf.rm_repo(project.path)
+      ga_repo.save
+    end
+
+    def destroy_project!(project)
+      apply do |config|
+        config.destroy_project(project)
+      end
+    end
+
+    def write_key(id, key)
+      File.open(File.join(config_tmp_dir, 'gitolite/keydir',"#{id}.pub"), 'w') do |f|
+        f.write(key.gsub(/\n/,''))
+      end
+    end
+
+    def rm_key(user)
+      File.unlink(File.join(config_tmp_dir, 'gitolite/keydir',"#{user}.pub"))
+      `cd #{File.join(config_tmp_dir,'gitolite')} ; git rm keydir/#{user}.pub`
+    end
+
+    # update or create
+    def update_project(repo_name, project)
+      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
+      conf = ga_repo.config
+      repo = update_project_config(project, conf)
+      conf.add_repo(repo, true)
+
+      ga_repo.save
+    end
+
+    def update_project!(repo_name, project)
+      apply do |config|
+        config.update_project(repo_name, project)
+      end
+    end
+
+    # Updates many projects and uses project.path as the repo path
+    # An order of magnitude faster than update_project
+    def update_projects(projects)
+      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
+      conf = ga_repo.config
+
+      projects.each do |project|
+        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.strip + "$ ", 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
+
+    def admin_all_repo
+      ga_repo = ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
+      conf = ga_repo.config
+      owner_name = ""
+
+      # Read gitolite-admin user
+      #
+      begin
+        repo = conf.get_repo("gitolite-admin")
+        owner_name = repo.permissions[0]["RW+"][""][0]
+        raise StandardError if owner_name.blank?
+      rescue => ex
+        puts "Can't determine gitolite-admin owner".red
+        raise StandardError
+      end
+
+      # @ALL repos premission for gitolite owner
+      repo_name = "@all"
+      repo = if conf.has_repo?(repo_name)
+               conf.get_repo(repo_name)
+             else
+               ::Gitolite::Config::Repo.new(repo_name)
+             end
+
+      repo.add_permission("RW+", "", owner_name)
+      conf.add_repo(repo, true)
+      ga_repo.save
+    end
+
+    def admin_all_repo!
+      apply { |config| config.admin_all_repo }
+    end
+
+    private
+
+    def pull
+      Dir.mkdir config_tmp_dir
+      `git clone #{Gitlab.config.gitolite_admin_uri} #{config_tmp_dir}/gitolite`
+    end
+
+    def push
+      Dir.chdir(File.join(config_tmp_dir, "gitolite"))
+      `git add -A`
+      `git commit -am "GitLab"`
+      `git push`
+      Dir.chdir(Rails.root)
+
+      FileUtils.rm_rf(config_tmp_dir)
+    end
+  end
+end
+
diff --git a/spec/lib/gitolite_config_spec.rb b/spec/lib/gitolite_config_spec.rb
new file mode 100644
index 00000000000..c3ce0db569a
--- /dev/null
+++ b/spec/lib/gitolite_config_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::GitoliteConfig do
+  let(:gitolite) { Gitlab::GitoliteConfig.new }
+
+  it { should respond_to :write_key }
+  it { should respond_to :rm_key }
+  it { should respond_to :update_project }
+  it { should respond_to :update_project! }
+  it { should respond_to :update_projects }
+  it { should respond_to :destroy_project }
+  it { should respond_to :destroy_project! }
+  it { should respond_to :apply }
+  it { should respond_to :admin_all_repo }
+  it { should respond_to :admin_all_repo! }
+end
diff --git a/spec/lib/gitolite_spec.rb b/spec/lib/gitolite_spec.rb
new file mode 100644
index 00000000000..cc8ce8b2cce
--- /dev/null
+++ b/spec/lib/gitolite_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::Gitolite do
+  let(:project) { double('Project', path: 'diaspora') }
+  let(:gitolite_config) { double('Gitlab::GitoliteConfig') }
+  let(:gitolite) { Gitlab::Gitolite.new }
+
+  before do
+    gitolite.stub(config: gitolite_config)
+  end
+
+  it { should respond_to :set_key }
+  it { should respond_to :remove_key }
+
+  it { should respond_to :update_repository }
+  it { should respond_to :create_repository }
+  it { should respond_to :remove_repository }
+
+  it { gitolite.url_to_repo('diaspora').should == Gitlab.config.ssh_path + "diaspora.git" }
+
+  it "should call config update" do
+    gitolite_config.should_receive(:update_project!)
+    gitolite.update_repository project
+  end
+end
diff --git a/spec/support/gitolite_stub.rb b/spec/support/gitolite_stub.rb
index 2a907f99bc8..037b09cd555 100644
--- a/spec/support/gitolite_stub.rb
+++ b/spec/support/gitolite_stub.rb
@@ -17,7 +17,7 @@ module GitoliteStub
     )
 
     gitolite_admin = double(
-      'Gitolite::GitoliteAdmin', 
+      'Gitolite::GitoliteAdmin',
       config: gitolite_config,
       save: true,
     )
@@ -27,9 +27,21 @@ module GitoliteStub
   end
 
   def stub_gitlab_gitolite
-    gitlab_gitolite = Gitlab::Gitolite.new
-    Gitlab::Gitolite.stub(new: gitlab_gitolite)
-    gitlab_gitolite.stub(configure: ->() { yield(self) })
-    gitlab_gitolite.stub(update_keys: true)
+    gitolite_config = double('Gitlab::GitoliteConfig')
+    gitolite_config.stub(
+      apply: ->() { yield(self) },
+      write_key: true,
+      rm_key: true,
+      update_projects: true,
+      update_project: true,
+      update_project!: true,
+      destroy_project: true,
+      destroy_project!: true,
+      admin_all_repo: true,
+      admin_all_repo!: true,
+
+    )
+
+    Gitlab::GitoliteConfig.stub(new: gitolite_config)
   end
 end
-- 
2.30.9