diff --git a/app/controllers/repository_protected_branches_controller.rb b/app/controllers/repository_protected_branches_controller.rb
new file mode 100644
index 0000000..7c1aa6c
--- /dev/null
+++ b/app/controllers/repository_protected_branches_controller.rb
@@ -0,0 +1,129 @@
+class RepositoryProtectedBranchesController < RedmineGitHostingController
+  unloadable
+
+  before_filter :set_current_tab
+  before_filter :can_view_protected_branches,   :only => [:index]
+  before_filter :can_create_protected_branches, :only => [:new, :create]
+  before_filter :can_edit_protected_branches,   :only => [:edit, :update, :destroy]
+
+  before_filter :find_repository_protected_branch, :except => [:index, :new, :create, :sort]
+
+
+  def index
+    @repository_protected_branches = RepositoryProtectedBranche.find_all_by_repository_id(@repository.id)
+
+    respond_to do |format|
+      format.html { render :layout => 'popup' }
+      format.js
+    end
+  end
+
+
+  def new
+    @protected_branch = RepositoryProtectedBranche.new()
+    @protected_branch.repository = @repository
+    @protected_branch.user_list  = []
+  end
+
+
+  def create
+    @protected_branch = RepositoryProtectedBranche.new(params[:repository_protected_branche])
+    @protected_branch.repository = @repository
+
+    respond_to do |format|
+      if @protected_branch.save
+        flash[:notice] = l(:notice_protected_branch_created)
+
+        format.html { redirect_to success_url }
+        format.js   { render :js => "window.location = #{success_url.to_json};" }
+      else
+        format.html {
+          flash[:error] = l(:notice_protected_branch_create_failed)
+          render :action => "create"
+        }
+        format.js { render "form_error", :layout => false }
+      end
+    end
+  end
+
+
+  def update
+    respond_to do |format|
+      if @protected_branch.update_attributes(params[:repository_protected_branche])
+        flash[:notice] = l(:notice_protected_branch_updated)
+
+        format.html { redirect_to success_url }
+        format.js   { render :js => "window.location = #{success_url.to_json};" }
+      else
+        format.html {
+          flash[:error] = l(:notice_protected_branch_update_failed)
+          render :action => "edit"
+        }
+        format.js { render "form_error", :layout => false }
+      end
+    end
+  end
+
+
+  def destroy
+    respond_to do |format|
+      if @protected_branch.destroy
+        flash[:notice] = l(:notice_protected_branch_deleted)
+        format.js { render :js => "window.location = #{success_url.to_json};" }
+      else
+        format.js { render :layout => false }
+      end
+    end
+  end
+
+
+  def clone
+    @protected_branch = RepositoryProtectedBranche.clone_from(params[:id])
+    render "new"
+  end
+
+
+  def sort
+    params[:repository_protected_branche].each_with_index do |id, index|
+      RepositoryProtectedBranche.update_all({position: index + 1}, {id: id})
+    end
+    render :nothing => true
+  end
+
+
+  private
+
+
+  def can_view_protected_branches
+    render_403 unless view_context.user_allowed_to(:view_repository_protected_branches, @project)
+  end
+
+
+  def can_create_protected_branches
+    render_403 unless view_context.user_allowed_to(:create_repository_protected_branches, @project)
+  end
+
+
+  def can_edit_protected_branches
+    render_403 unless view_context.user_allowed_to(:edit_repository_protected_branches, @project)
+  end
+
+
+  def find_repository_protected_branch
+    protected_branch = RepositoryProtectedBranche.find_by_id(params[:id])
+
+    if protected_branch && protected_branch.repository_id == @repository.id
+      @protected_branch = protected_branch
+    elsif protected_branch
+      render_403
+    else
+      render_404
+    end
+  end
+
+
+  def set_current_tab
+    @tab = 'repository_protected_branches'
+  end
+
+end
diff --git a/app/models/repository_protected_branche.rb b/app/models/repository_protected_branche.rb
new file mode 100644
index 0000000..31167e0
--- /dev/null
+++ b/app/models/repository_protected_branche.rb
@@ -0,0 +1,71 @@
+class RepositoryProtectedBranche < ActiveRecord::Base
+  unloadable
+
+  VALID_PERMS  = [ "RW+", "RW" ]
+  DEFAULT_PERM = "RW+"
+
+  acts_as_list
+
+  ## Attributes
+  attr_accessible :path, :permissions, :position, :user_list
+
+  ## Relations
+  belongs_to :repository
+
+  ## Validations
+  validates :repository_id, :presence => true
+  validates :path,          :presence => true, :uniqueness => { :scope => :permissions }
+  validates :permissions,   :presence => true, :inclusion => { :in => VALID_PERMS }
+  validates :user_list,     :presence => true
+
+  ## Serializations
+  serialize :user_list, Array
+
+  ## Callbacks
+  before_validation :remove_blank_items
+
+  after_commit ->(obj) { obj.update_permissions }, :on => :create
+  after_commit ->(obj) { obj.update_permissions }, :on => :update
+  after_commit ->(obj) { obj.update_permissions }, :on => :destroy
+
+  ## Scopes
+  default_scope order('position ASC')
+
+
+  def self.clone_from(parent)
+    parent = find_by_id(parent) unless parent.kind_of? RepositoryProtectedBranche
+    copy = self.new
+    copy.attributes = parent.attributes
+    copy.repository = parent.repository
+
+    copy
+  end
+
+
+  def available_users
+    repository.project.member_principals.map(&:user).compact.uniq.map{ |user| user.login }.sort
+  end
+
+
+  def allowed_users
+    self.user_list.map{ |user| User.find_by_login(user).gitolite_identifier }.sort
+  end
+
+
+  protected
+
+
+  def update_permissions
+    RedmineGitolite::GitHosting.logger.info { "Update branch permissions for repository : '#{repository.gitolite_repository_name}'" }
+    RedmineGitolite::GitHosting.resync_gitolite(:update_repository, repository.id)
+  end
+
+
+  private
+
+
+  def remove_blank_items
+    self.user_list = user_list.select{ |user| !user.blank? } rescue []
+  end
+
+end
diff --git a/app/services/download_git_revision.rb b/app/services/download_git_revision.rb
new file mode 100644
index 0000000..a546fd6
--- /dev/null
+++ b/app/services/download_git_revision.rb
@@ -0,0 +1,101 @@
+class DownloadGitRevision
+  unloadable
+
+  attr_reader :commit_valid
+  attr_reader :content_type
+  attr_reader :filename
+
+
+  def initialize(repository, revision, format)
+    @repository = repository
+    @revision   = revision
+    @format     = format
+    @project    = repository.project
+
+    @commit_valid  = false
+    @commit_id     = ''
+    @content_type  = ''
+    @filename      = ''
+    @cmd_args      = []
+
+    validate_revision
+    fill_data
+  end
+
+
+  def content
+    RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{@repository.gitolite_repository_path}", 'archive', *@cmd_args, @commit_id)
+  end
+
+
+  private
+
+
+  def validate_revision
+    commit_id = nil
+
+    # is the revision a branch?
+    @repository.branches.each do |x|
+      if x.to_s == @revision
+        commit_id = x.revision
+        break
+      end
+    end
+
+    # is the revision a tag?
+    if commit_id.nil?
+      tags = RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{@repository.gitolite_repository_path}", 'tag').split
+      tags.each do |x|
+        if x == @revision
+          commit_id = RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{@repository.gitolite_repository_path}", 'rev-list', @revision).split[0]
+          break
+        end
+      end
+    end
+
+    # well, let check if this is a valid commit_id
+    if commit_id.nil?
+      commit_id = @revision
+    end
+
+    valid_commit = RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{@repository.gitolite_repository_path}", 'rev-parse', '--quiet', '--verify', commit_id).chomp.strip
+
+    if valid_commit == ''
+      @commit_valid = false
+    else
+      @commit_valid = true
+      @commit_id = valid_commit
+    end
+  end
+
+
+  def fill_data
+    project_name = @project.to_s.parameterize.to_s
+
+    if project_name.length == 0
+      project_name = "tarball"
+    end
+
+    case @format
+      when 'tar' then
+        extension     = 'tar'
+        @content_type = 'application/x-tar'
+        @cmd_args << "--format=tar"
+
+      when 'tar.gz' then
+        extension     = 'tar.gz'
+        @content_type = 'application/x-gzip'
+        @cmd_args << "--format=tar.gz"
+        @cmd_args << "-7"
+
+      when 'zip' then
+        extension     = 'zip'
+        @content_type = 'application/x-zip'
+        @cmd_args << "--format=zip"
+        @cmd_args << "-7"
+    end
+
+    @filename = "#{project_name}-#{@revision}.#{extension}"
+  end
+
+end
diff --git a/app/services/git_notifier.rb b/app/services/git_notifier.rb
new file mode 100644
index 0000000..921f48b
--- /dev/null
+++ b/app/services/git_notifier.rb
@@ -0,0 +1,108 @@
+class GitNotifier
+  unloadable
+
+
+  attr_reader :email_prefix
+  attr_reader :sender_address
+  attr_reader :default_list
+  attr_reader :mail_mapping
+
+
+  def initialize(repository)
+    @repository     = repository
+    @project        = repository.project
+
+    @email_prefix   = ''
+    @sender_address = ''
+    @default_list   = []
+    @mail_mapping   = {}
+
+    build_notifier
+  end
+
+
+  def mailing_list
+    @mail_mapping.keys
+  end
+
+
+  private
+
+
+  def build_notifier
+    set_email_prefix
+    set_sender_address
+    set_default_list
+    set_mail_mapping
+  end
+
+
+  def set_email_prefix
+    if !@repository.git_notification.nil? && !@repository.git_notification.prefix.empty?
+      @email_prefix = @repository.git_notification.prefix
+    else
+      @email_prefix = RedmineGitolite::Config.get_setting(:gitolite_notify_global_prefix)
+    end
+  end
+
+
+  def set_sender_address
+    if !@repository.git_notification.nil? && !@repository.git_notification.sender_address.empty?
+      @sender_address = @repository.git_notification.sender_address
+    else
+      @sender_address = RedmineGitolite::Config.get_setting(:gitolite_notify_global_sender_address)
+    end
+  end
+
+
+  def set_default_list
+    @default_list = @project.member_principals.map(&:user).compact.uniq
+                                                          .select{|user| user.allowed_to?(:receive_git_notifications, @project)}
+                                                          .map(&:mail).uniq.sort
+  end
+
+
+  def set_mail_mapping
+    mail_mapping = {}
+
+    # First collect all project users
+    default_users = @default_list.map{ |mail| mail_mapping[mail] = :project }
+
+    # Then add global include list
+    RedmineGitolite::Config.get_setting(:gitolite_notify_global_include).sort.map{ |mail| mail_mapping[mail] = :global }
+
+    # Then filter
+    mail_mapping = filter_list(mail_mapping)
+
+    # Then add local include list
+    if !@repository.git_notification.nil? && !@repository.git_notification.include_list.empty?
+      @repository.git_notification.include_list.sort.map{ |mail| mail_mapping[mail] = :local }
+    end
+
+    @mail_mapping = mail_mapping
+  end
+
+
+  def filter_list(merged_map)
+    mail_mapping = {}
+    exclude_list = []
+
+    # Build exclusion list
+    if !RedmineGitolite::Config.get_setting(:gitolite_notify_global_exclude).empty?
+      exclude_list = RedmineGitolite::Config.get_setting(:gitolite_notify_global_exclude)
+    end
+
+    if !@repository.git_notification.nil? && !@repository.git_notification.exclude_list.empty?
+      exclude_list = exclude_list + @repository.git_notification.exclude_list
+    end
+
+    exclude_list = exclude_list.uniq.sort
+
+    merged_map.each do |mail, from|
+      mail_mapping[mail] = from unless exclude_list.include?(mail)
+    end
+
+    return mail_mapping
+  end
+
+end
diff --git a/app/services/github_payload.rb b/app/services/github_payload.rb
new file mode 100644
index 0000000..8c8f34d
--- /dev/null
+++ b/app/services/github_payload.rb
@@ -0,0 +1,129 @@
+class GithubPayload
+  unloadable
+
+
+  attr_reader :payload
+
+
+  def initialize(repository, payload)
+    @repository = repository
+    @project    = repository.project
+    @payload    = build_payload(payload)
+  end
+
+
+  private
+
+
+  def logger
+    RedmineGitolite::Log.get_logger(:git_hooks)
+  end
+
+
+  # Returns an array of GitHub post-receive hook style hashes
+  # http://help.github.com/post-receive-hooks/
+  def build_payload(refs)
+    payload = []
+
+    refs.each do |ref|
+
+      oldhead, newhead, refname = ref.split(',')
+
+      # Only pay attention to branch updates
+      next if !refname.match(/refs\/heads\//)
+
+      branch = refname.gsub('refs/heads/', '')
+
+      if newhead.match(/^0{40}$/)
+        # Deleting a branch
+        logger.info { "Deleting branch '#{branch}'" }
+        next
+      elsif oldhead.match(/^0{40}$/)
+        # Creating a branch
+        logger.info { "Creating branch '#{branch}'" }
+        range = newhead
+      else
+        range = "#{oldhead}..#{newhead}"
+      end
+
+      # Grab the repository path
+      revisions_in_range = RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{@repository.gitolite_repository_path}", 'rev-list', '--reverse', range)
+      logger.debug { "Revisions in range : #{revisions_in_range.split().join(' ')}" }
+
+      commits = []
+
+      revisions_in_range.split().each do |rev|
+        logger.debug { "Revision : '#{rev.strip}'" }
+        revision = @repository.find_changeset_by_name(rev.strip)
+        next if revision.nil?
+
+        revision_url = Rails.application.routes.url_helpers.url_for(
+                         :controller => 'repositories', :action => 'revision',
+                         :id => @project, :repository_id => @repository.identifier_param, :rev => rev,
+                         :only_path => false, :host => Setting['host_name'], :protocol => Setting['protocol']
+                       )
+
+        commit = {
+          :id        => revision.revision,
+          :message   => revision.comments,
+          :timestamp => revision.committed_on,
+          :added     => [],
+          :modified  => [],
+          :removed   => [],
+          :url       => revision_url,
+          :author    => {
+            :name  => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'),
+            :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1')
+          }
+        }
+
+        revision.filechanges.each do |change|
+          if change.action == "M"
+            commit[:modified] << change.path
+          elsif change.action == "A"
+            commit[:added] << change.path
+          elsif change.action == "D"
+            commit[:removed] << change.path
+          end
+        end
+
+        commits << commit
+      end
+
+      repository_url = Rails.application.routes.url_helpers.url_for(
+                         :controller => 'repositories', :action => 'show',
+                         :id => @project, :repository_id => @repository.identifier_param,
+                         :only_path => false, :host => Setting['host_name'], :protocol => Setting['protocol']
+                       )
+
+      payload << {
+        :before     => oldhead,
+        :after      => newhead,
+        :ref        => refname,
+        :commits    => commits,
+        :pusher     => {
+          :name  => Setting["app_title"],
+          :email => Setting["mail_from"]
+        },
+        :repository => {
+          :description => @project.description,
+          :fork        => false,
+          :forks       => 0,
+          :homepage    => @project.homepage,
+          :name        => @repository.redmine_name,
+          :open_issues => @project.issues.open.length,
+          :watchers    => 0,
+          :private     => !@project.is_public,
+          :url         => repository_url,
+          :owner       => {
+            :name  => Setting["app_title"],
+            :email => Setting["mail_from"]
+          }
+        }
+      }
+    end
+
+    return payload
+  end
+
+end
diff --git a/app/services/hooks/git_mirrors.rb b/app/services/hooks/git_mirrors.rb
new file mode 100644
index 0000000..b25bdbb
--- /dev/null
+++ b/app/services/hooks/git_mirrors.rb
@@ -0,0 +1,58 @@
+module Hooks
+  class GitMirrors
+    unloadable
+
+
+    def initialize(repository, payload)
+      @repository = repository
+      @project    = repository.project
+      @payload    = payload
+    end
+
+
+    def execute
+      call_mirrors
+    end
+
+
+    private
+
+
+    def logger
+      RedmineGitolite::Log.get_logger(:git_hooks)
+    end
+
+
+    def call_mirrors
+      y = ""
+
+      ## Push to each mirror
+      if @repository.mirrors.active.any?
+
+        logger.info { "Notifying mirrors about changes to this repository :" }
+        y << "\nNotifying mirrors about changes to this repository :\n"
+
+        @repository.mirrors.active.each do |mirror|
+          if mirror.needs_push(@payload)
+            logger.info { "Pushing changes to #{mirror.url} ... " }
+            y << "  - Pushing changes to #{mirror.url} ... "
+
+            push_failed, push_message = mirror.push
+
+            if push_failed
+              logger.error { "Failed!" }
+              logger.error { "#{push_message}" }
+              y << " [failure]\n"
+            else
+              logger.info { "Succeeded!" }
+              y << " [success]\n"
+            end
+          end
+        end
+      end
+
+      return y
+    end
+
+  end
+end
diff --git a/app/services/hooks/github_issues_sync.rb b/app/services/hooks/github_issues_sync.rb
new file mode 100644
index 0000000..55496eb
--- /dev/null
+++ b/app/services/hooks/github_issues_sync.rb
@@ -0,0 +1,147 @@
+require 'json'
+
+module Hooks
+  class GithubIssuesSync
+    unloadable
+
+    include HttpHelper
+
+
+    def initialize(project, params)
+      @project = project
+      @params  = params
+    end
+
+
+    def execute
+      sync_with_github
+    end
+
+
+    private
+
+
+    def logger
+      RedmineGitolite::Log.get_logger(:git_hooks)
+    end
+
+
+    def sync_with_github
+      github_issue = GithubIssue.find_by_github_id(@params[:issue][:id])
+      redmine_issue = Issue.find_by_subject(@params[:issue][:title])
+      create_relation = false
+
+      ## We don't have stored relation
+      if github_issue.nil?
+
+        ## And we don't have issue in Redmine
+        if redmine_issue.nil?
+          create_relation = true
+          redmine_issue = create_redmine_issue
+        else
+          ## Create relation and update issue
+          create_relation = true
+          redmine_issue = update_redmine_issue(redmine_issue)
+        end
+      else
+        ## We have one relation, update issue
+        redmine_issue = update_redmine_issue(github_issue.issue)
+      end
+
+      if create_relation
+        github_issue = GithubIssue.new
+        github_issue.github_id = @params[:issue][:id]
+        github_issue.issue_id = redmine_issue.id
+        github_issue.save!
+      end
+
+      if @params.has_key?(:comment)
+        issue_journal = GithubComment.find_by_github_id(@params[:comment][:id])
+
+        if issue_journal.nil?
+          issue_journal = create_issue_journal(github_issue.issue)
+
+          github_comment = GithubComment.new
+          github_comment.github_id = @params[:comment][:id]
+          github_comment.journal_id = issue_journal.id
+          github_comment.save!
+        end
+      end
+    end
+
+
+    def create_redmine_issue
+      logger.info { "Github Issues Sync : create new issue" }
+
+      issue = Issue.new
+      issue.project_id = @project.id
+      issue.tracker_id = @project.trackers.find(:first).try(:id)
+      issue.subject = @params[:issue][:title].chomp[0, 255]
+      issue.description = @params[:issue][:body]
+      issue.updated_on = @params[:issue][:updated_at]
+      issue.created_on = @params[:issue][:created_at]
+
+      ## Get user mail
+      user = find_user(@params[:issue][:user][:url])
+      issue.author = user
+
+      issue.save!
+      return issue
+    end
+
+
+    def create_issue_journal(issue)
+      logger.info { "Github Issues Sync : create new journal for issue '##{issue.id}'" }
+
+      journal = Journal.new
+      journal.journalized_id = issue.id
+      journal.journalized_type = 'Issue'
+      journal.notes = @params[:comment][:body]
+      journal.created_on = @params[:comment][:created_at]
+
+      ## Get user mail
+      user = find_user(@params[:comment][:user][:url])
+      journal.user_id = user.id
+
+      journal.save!
+      return journal
+    end
+
+
+    def update_redmine_issue(issue)
+      logger.info { "Github Issues Sync : update issue '##{issue.id}'" }
+
+      if @params[:issue][:state] == 'closed'
+        issue.status_id = 5
+      else
+        issue.status_id = 1
+      end
+
+      issue.subject = @params[:issue][:title].chomp[0, 255]
+      issue.description = @params[:issue][:body]
+      issue.updated_on = @params[:issue][:updated_at]
+
+      issue.save!
+      return issue
+    end
+
+
+    def find_user(url)
+      post_failed, user_data = post_data(url, "", :method => :get)
+      user_data = JSON.parse(user_data)
+
+      user = User.find_by_mail(user_data['email'])
+
+      if user.nil?
+        logger.info { "Github Issues Sync : cannot find user '#{user_data['email']}' in Redmine, use anonymous" }
+        user = User.anonymous
+        user.mail = user_data['email']
+        user.firstname = user_data['name']
+        user.lastname  = user_data['login']
+      end
+
+      return user
+    end
+
+  end
+end
diff --git a/app/services/hooks/http_helper.rb b/app/services/hooks/http_helper.rb
new file mode 100644
index 0000000..9a4aa4a
--- /dev/null
+++ b/app/services/hooks/http_helper.rb
@@ -0,0 +1,45 @@
+require 'net/http'
+require 'net/https'
+require 'uri'
+
+module Hooks
+  module HttpHelper
+    unloadable
+
+    def post_data(url, payload, opts={})
+      uri  = URI(url)
+      http = Net::HTTP.new(uri.host, uri.port)
+
+      if uri.scheme == 'https'
+        http.use_ssl = true
+        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+      end
+
+      if opts[:method] == :post
+        request = Net::HTTP::Post.new(uri.request_uri)
+        request.set_form_data({"payload" => payload.to_json})
+      else
+        request = Net::HTTP::Get.new(uri.request_uri)
+      end
+
+      message = ""
+
+      begin
+        res = http.start {|openhttp| openhttp.request request}
+        if !res.is_a?(Net::HTTPSuccess)
+          message = "Return code : #{res.code} (#{res.message})."
+          failed = true
+        else
+          message = res.body
+          failed = false
+        end
+      rescue => e
+        message = "Exception : #{e.message}"
+        failed = true
+      end
+
+      return failed, message
+    end
+
+  end
+end
diff --git a/app/services/hooks/redmine.rb b/app/services/hooks/redmine.rb
new file mode 100644
index 0000000..bb4de70
--- /dev/null
+++ b/app/services/hooks/redmine.rb
@@ -0,0 +1,49 @@
+module Hooks
+  class Redmine
+    unloadable
+
+
+    def initialize(repository)
+      @repository = repository
+    end
+
+
+    def execute
+      fetch_changesets
+    end
+
+
+    private
+
+
+    def logger
+      RedmineGitolite::Log.get_logger(:git_hooks)
+    end
+
+
+    def fetch_changesets
+      ## Fetch commits from the repository
+      y = ""
+
+      logger.info { "Fetching changesets for '#{@repository.redmine_name}' repository ... " }
+      y << "  - Fetching changesets for '#{@repository.redmine_name}' repository ... "
+
+      begin
+        @repository.fetch_changesets
+        logger.info { "Succeeded!" }
+        y << " [success]\n"
+      rescue ::Redmine::Scm::Adapters::CommandFailed => e
+        logger.error { "Failed!" }
+        logger.error { "Error during fetching changesets : #{e.message}" }
+        y << " [failure]\n"
+      rescue => e
+        logger.error { "Failed!" }
+        logger.error { "Error after fetching changesets : #{e.message}" }
+        y << " [failure]\n"
+      end
+
+      return y
+    end
+
+  end
+end
diff --git a/app/services/hooks/webservices.rb b/app/services/hooks/webservices.rb
new file mode 100644
index 0000000..552b88c
--- /dev/null
+++ b/app/services/hooks/webservices.rb
@@ -0,0 +1,81 @@
+module Hooks
+  class Webservices
+    unloadable
+
+    include HttpHelper
+
+
+    def initialize(repository, payload)
+      @repository = repository
+      @project    = repository.project
+      @payload    = payload
+    end
+
+
+    def execute
+      call_webservices
+    end
+
+
+    private
+
+
+    def logger
+      RedmineGitolite::Log.get_logger(:git_hooks)
+    end
+
+
+    def call_webservices
+      y = ""
+
+      ## Post to each post-receive URL
+      if @repository.post_receive_urls.active.any?
+
+        logger.info { "Notifying post receive urls about changes to this repository :" }
+        y << "\nNotifying post receive urls about changes to this repository :\n"
+
+        @repository.post_receive_urls.active.each do |post_receive_url|
+          if payloads = post_receive_url.needs_push(@payload)
+
+            method = (post_receive_url.mode == :github) ? :post : :get
+
+            if method == :post && post_receive_url.split_payloads?
+              payloads.each do |payload|
+                logger.info { "Notifying #{post_receive_url.url} for '#{payload[:ref]}' ... " }
+                y << "  - Notifying #{post_receive_url.url} for '#{payload[:ref]}' ... "
+
+                post_failed, post_message = post_data(post_receive_url.url, payload, :method => method)
+
+                if post_failed
+                  logger.error { "Failed!" }
+                  logger.error { "#{post_message}" }
+                  y << " [failure]\n"
+                else
+                  logger.info { "Succeeded!" }
+                  y << " [success]\n"
+                end
+              end
+            else
+              logger.info { "Notifying #{post_receive_url.url} ... " }
+              y << "  - Notifying #{post_receive_url.url} ... "
+
+              post_failed, post_message = post_data(post_receive_url.url, @payload, :method => method)
+
+              if post_failed
+                logger.error { "Failed!" }
+                logger.error { "#{post_message}" }
+                y << " [failure]\n"
+              else
+                logger.info { "Succeeded!" }
+                y << " [success]\n"
+              end
+            end
+          end
+        end
+      end
+
+      return y
+    end
+
+  end
+end
diff --git a/app/views/gitolite_public_keys/_form.html.erb b/app/views/gitolite_public_keys/_form.html.erb
new file mode 100644
index 0000000..d6e8345
--- /dev/null
+++ b/app/views/gitolite_public_keys/_form.html.erb
@@ -0,0 +1,43 @@
+<div id="validation_messages"><%= error_messages_for 'gitolite_public_key' %></div>
+
+<%= labelled_form_for :gitolite_public_key, gitolite_public_key,
+                      :url  => {:controller => 'gitolite_public_keys', :action => 'create', :user_id => params[:id], :tab => params[:id]&&'keys'},
+                      :html => {:method => :post} do |f| %>
+
+  <p><%= f.text_field :title, :label => :label_identifier_can_be_arbitrary, :required => true, :style => 'width: 97%;' %></p>
+
+  <% if can_create_deployment_keys_for_some_project(@user) %>
+    <p>
+      <%= f.select :key_type, options_for_select(
+                                [
+                                  [ l(:label_user_key), 0 ],
+                                  [ l(:label_deploy_key), 1 ]
+                                ],
+                              ),
+                              { :required => true, :label => :label_key_type },
+                              { :class    => 'select_key_type' } %>
+
+
+    </p>
+
+    <div id="key_type_options" style="display: none;">
+      <p>
+        <%= f.check_box :delete_when_unused, :required => true, :label => :label_deployment_credential_delete_when_unused %>
+      </p>
+    </div>
+  <% end %>
+
+  <p>
+    <%= f.text_area :key, :label => :label_public_key, :required => true,
+          :style => "width: 97%; height: 200px; overflow: auto;",
+          :cols => nil,
+          :rows => nil %>
+  </p>
+
+
+
+  <em><%= l(:label_cut_and_paste) %></em><br /><br />
+  <%= submit_tag l(:button_create), :name => 'create_button' %>
+  <%= submit_tag l(:button_cancel), :name => 'cancel_button' %>
+
+<% end %>
diff --git a/app/views/gitolite_public_keys/index.html.erb b/app/views/gitolite_public_keys/index.html.erb
new file mode 100644
index 0000000..ad06034
--- /dev/null
+++ b/app/views/gitolite_public_keys/index.html.erb
@@ -0,0 +1 @@
+<%= render :partial => 'gitolite_public_keys/view' %>
diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb
old mode 100644
new mode 100755
diff --git a/app/views/repositories/_edit_bottom.html.erb b/app/views/repositories/_edit_bottom.html.erb
new file mode 100644
index 0000000..5cfd020
--- /dev/null
+++ b/app/views/repositories/_edit_bottom.html.erb
@@ -0,0 +1,125 @@
+<% if @repository.is_a?(Repository::Git) %>
+
+  <div id="repository-tabs">
+    <ul>
+      <% if user_allowed_to(:view_deployment_keys, @repository.project) %>
+        <li id="tab-repository_deployment_credentials">
+          <%= link_to label_with_icon(l(:label_deployment_credentials), 'fa-ticket'),
+                      repository_deployment_credentials_path(@repository) %>
+        </li>
+      <% end %>
+
+      <% if user_allowed_to(:view_repository_git_notifications, @repository.project) %>
+        <li id="tab-repository_git_notifications">
+          <%= link_to label_with_icon(l(:label_git_notifications), 'fa-envelope'),
+                      repository_git_notifications_path(@repository) %>
+        </li>
+      <% end %>
+
+      <% if user_allowed_to(:view_repository_mirrors, @repository.project) %>
+        <li id="tab-repository_mirrors">
+          <%= link_to label_with_icon(l(:label_repository_mirrors), 'fa-sitemap'),
+                      repository_mirrors_path(@repository) %>
+        </li>
+      <% end %>
+
+      <% if user_allowed_to(:view_repository_post_receive_urls, @repository.project) %>
+        <li id="tab-repository_post_receive_urls">
+          <%= link_to label_with_icon(l(:label_post_receive_urls), 'fa-external-link'),
+                      repository_post_receive_urls_path(@repository) %>
+        </li>
+      <% end %>
+
+      <% if user_allowed_to(:view_repository_git_config_keys, @repository.project) %>
+        <li id="tab-repository_git_config_keys">
+          <%= link_to label_with_icon(l(:label_git_config_keys), 'fa-th-list'),
+                      repository_git_config_keys_path(@repository) %>
+        </li>
+      <% end %>
+
+      <% if user_allowed_to(:view_repository_protected_branches, @repository.project) %>
+        <li id="tab-repository_protected_branches">
+          <%= link_to "<i class=\"fa fa-lock\"></i>".html_safe + l(:label_protected_branches),
+                      repository_protected_branches_path(@repository) %>
+        </li>
+      <% end %>
+    </ul>
+  </div>
+
+  <div id="modal-box"></div>
+  <div id="modal-box-close-only"></div>
+
+  <% content_for :header_tags do %>
+    <%= stylesheet_link_tag 'font_awesome',                     :plugin => 'redmine_bootstrap_kit' %>
+    <%= stylesheet_link_tag 'jquery_tag_it',                    :plugin => 'redmine_bootstrap_kit' %>
+    <%= stylesheet_link_tag 'bootstrap/bootstrap_custom',       :plugin => 'redmine_bootstrap_kit' %>
+    <%= stylesheet_link_tag 'bootstrap/bootstrap_label',        :plugin => 'redmine_bootstrap_kit' %>
+    <%= stylesheet_link_tag 'bootstrap/bootstrap_tables',       :plugin => 'redmine_bootstrap_kit' %>
+
+    <%= javascript_include_tag 'jquery_tag_it',                 :plugin => 'redmine_bootstrap_kit' %>
+    <%= javascript_include_tag 'bootstrap',                     :plugin => 'redmine_bootstrap_kit' %>
+
+    <%= javascript_tag do %>
+
+      function post_mode_change(elem) {
+        if (!elem) return;
+        var idx = elem.selectedIndex;
+        if (idx == 0) {
+          $('#payload_options').show();
+        } else {
+          $('#payload_options').hide();
+        }
+      }
+
+      function push_mode_change(elem) {
+        if (!elem) return;
+        var idx = elem.selectedIndex;
+        if (idx == 0) {
+          $('#ref_spec_options').hide();
+        } else {
+          $('#ref_spec_options').show();
+        }
+      }
+
+      function key_mode_change(elem) {
+        if (!elem) return;
+        var idx = elem.selectedIndex;
+        if (idx == 0) {
+          $('#new_key_window').show();
+        } else {
+          $('#new_key_window').hide();
+        }
+      }
+
+      var modals           = Object();
+      modals.label_save    = '<%= l(:button_save) %>';
+      modals.label_cancel  = '<%= l(:button_cancel) %>';
+      modals.label_ok      = '<%= l(:button_ok) %>';
+      modals.modal_list    = [];
+      modals.modal_list[0] = {'source': '.modal-box', 'target': '#modal-box', 'mode': 'standard'};
+      modals.modal_list[1] = {'source': '.modal-box-close-only', 'target': '#modal-box-close-only', 'mode': 'close-only'};
+
+      $(document).ready(function() {
+        var all_tabs = $("#repository-tabs li");
+        var active_tab = '';
+
+        all_tabs.each(function(){
+          if ($(this).attr('id').replace('tab-', '') == '<%= @tab %>') {
+            active_tab = all_tabs.index(this);
+          }
+        });
+
+        $("#repository-tabs").tabs({
+          active: active_tab,
+          activate: function(event, ui) {
+            var new_tab_name = $(ui.newTab).attr('id').replace('tab-', '');
+            if ("replaceState" in window.history) {
+              window.history.replaceState(null, document.title, 'edit?tab=' + new_tab_name);
+            }
+          }
+        });
+      });
+    <% end %>
+  <% end %>
+
+<% end %>
diff --git a/app/views/repositories/_edit_top.html.erb b/app/views/repositories/_edit_top.html.erb
new file mode 100644
index 0000000..07ab5a1
--- /dev/null
+++ b/app/views/repositories/_edit_top.html.erb
@@ -0,0 +1,6 @@
+<% if @repository.is_a?(Repository::Git) %>
+  <div class="git_hosting box">
+    <h3><%= l(:label_repository_access_url) %></h3>
+    <%= render :partial => 'common/git_urls', :locals => {:repository => @repository} %>
+  </div>
+<% end %>
diff --git a/app/views/repositories/_git_form.html.erb b/app/views/repositories/_git_form.html.erb
new file mode 100644
index 0000000..f134fe0
--- /dev/null
+++ b/app/views/repositories/_git_form.html.erb
@@ -0,0 +1,174 @@
+<% content_for :header_tags do %>
+  <%= stylesheet_link_tag    'bootstrap/bootstrap_switch', :plugin => 'redmine_bootstrap_kit' %>
+  <%= javascript_include_tag 'plugins/bootstrap_switch',   :plugin => 'redmine_bootstrap_kit' %>
+  <%= javascript_include_tag 'bootstrap',                  :plugin => 'redmine_bootstrap_kit' %>
+<% end %>
+
+<%= javascript_tag do %>
+
+  $(document).ready(function() {
+
+    // Actually this takes the parent <p> mark
+    var default_checkbox = $('#repository_is_default').parent();
+
+    <% if @project.repository.nil? %>
+      // The default repository is not set, nothing to do...
+
+    <% elsif @project.repository == @repository %>
+      var content = $('<%= content_tag :p do -%>
+        <%= hidden_field_tag "repository[is_default]", "1" -%>
+        <%= content_tag :label, l(:field_repository_is_default) -%>
+        <%= content_tag :span,  l(:label_yes), :class => 'label label-success' -%>
+      <% end -%>');
+
+      content.insertAfter(default_checkbox);
+      default_checkbox.remove();
+      $('#repository_identifier').parent().remove();
+    <% else %>
+      var content = $('<%= content_tag :p do -%>
+        <%= hidden_field_tag "repository[is_default]", "0" -%>
+        <%= content_tag :label, l(:field_repository_is_default) -%>
+        <%= content_tag :span,  l(:label_no), :class => 'label' -%>
+      <% end -%>');
+
+      content.insertAfter(default_checkbox);
+      default_checkbox.remove();
+    <% end %>
+
+  });
+
+<% end %>
+
+<% if @repository.is_a?(Repository::Git) %>
+
+  <%= javascript_tag do %>
+
+    function moveGitOptionsDiv(){
+      if ( $('#git_hosting_settings').length == 0 ) {
+        // Set selectors
+        var original_div = $('#repository_git_options').parent();
+        var new_div      = $('#repository_git_options');
+
+        // Wrap the original_div in a new div
+        original_div.wrap('<div id="git_hosting_settings" class="box tabular">');
+
+        // Remove original css class
+        original_div.removeClass('box tabular');
+
+        // Add new css class
+        original_div.addClass('split');
+
+        // Insert our div after the original one
+        new_div.insertAfter(original_div);
+
+        $('<div style="display: block; clear: both;"></div>').insertAfter(new_div);
+
+        new_div.show();
+      }
+    }
+
+    $(document).ready(function() {
+      $('#repository_url').parent().remove();
+      moveGitOptionsDiv();
+
+      $('.bootstrap-switch').each(function(index, element) {
+        if (!$(element).children('div').length > 0){
+          installBootstrapSwitch(element);
+        }
+      });
+    });
+  <% end %>
+
+  <div id="repository_git_options" class="split" style="display: none;">
+
+    <h3><%= l(:label_repository_options) %></h3>
+
+    <%
+      selected = "false"
+      if @repository.extra[:git_daemon]
+        selected = "true"
+      elsif @project.is_public && RedmineGitolite::Config.get_setting(:gitolite_daemon_by_default, true)
+        selected = "true"
+      end
+    %>
+
+    <p>
+      <label><%= l(:label_enable_git_daemon) %></label>
+      <span class="bootstrap-switch switch-small" data-on="primary" data-off="default" data-on-label="<%= l(:label_yes) %>" data-off-label="<%= l(:label_no) %>">
+        <%= hidden_field_tag "extra[git_daemon]", "false" %>
+        <%= check_box_tag "extra[git_daemon]", selected, (selected == "true" ? true : false), disabled: !@project.is_public %>
+      </span>
+    </p>
+
+    <% if user_allowed_to(:create_repository_git_notifications, @repository.project) %>
+      <p>
+        <label><%= l(:label_enable_git_notify) %></label>
+        <span class="bootstrap-switch switch-small" data-on="primary" data-off="default" data-on-label="<%= l(:label_yes) %>" data-off-label="<%= l(:label_no) %>">
+          <%= hidden_field_tag "extra[git_notify]", "false" %>
+          <%= check_box_tag "extra[git_notify]", @repository.extra[:git_notify], @repository.extra[:git_notify] %>
+        </span>
+      </p>
+    <% end %>
+
+    <% if user_allowed_to(:create_repository_protected_branches, @repository.project) %>
+      <p>
+        <label><%= l(:label_enable_protected_branches) %></label>
+        <span class="bootstrap-switch switch-small" data-on="primary" data-off="default" data-on-label="<%= l(:label_yes) %>" data-off-label="<%= l(:label_no) %>">
+          <%= hidden_field_tag "extra[protected_branch]", "false" %>
+          <%= check_box_tag "extra[protected_branch]", @repository.extra[:protected_branch], @repository.extra[:protected_branch] %>
+        </span>
+      </p>
+    <% end %>
+
+    <p>
+      <%= label_tag  "extra[git_http]", l(:label_enable_smart_http) %>
+      <%= select_tag "extra[git_http]", options_for_select([
+                                                              [l(:label_disabled), "0"],
+                                                              [l(:label_http_only), "3"],
+                                                              [l(:label_https_only), "1"],
+                                                              [l(:label_https_and_http), "2"]
+                                                            ], :selected => @repository.extra[:git_http].to_s) %>
+    </p>
+
+    <p>
+      <%= label_tag  "", l(:label_repository_default_branch) %>
+      <% if !@repository.new_record? && !@repository.branches.empty? %>
+        <%= select_tag "extra[default_branch]", options_for_select(@repository.branches.collect{ |b| [b.to_s, b.to_s] }, :selected => @repository.branches.find{ |b| b.is_default}.to_s) %>
+      <% else %>
+        <span class="label label-info"><%= @repository.extra[:default_branch] %></span>
+      <% end %>
+    </p>
+
+    <p>
+      <%= label_tag  "", l(:label_mirroring_keys_installed) %>
+      <%= image_tag (RedmineGitolite::GitoliteWrapper.mirroring_keys_installed? ? 'true.png' : 'exclamation.png') %>
+    </p>
+
+    <% if !@repository.new_record? %>
+      <p>
+        <%= label_tag  "", l(:label_repository_exists_in_gitolite) %>
+        <%= image_tag (@repository.exists_in_gitolite? ? 'true.png' : 'exclamation.png') %>
+      </p>
+      <p>
+        <label><%= l(:label_browse_repository) %></label>
+        <%= link_to h(@repository.url), {:controller => 'repositories', :action => 'show', :id => @project, :repository_id => @repository.identifier_param} %>
+      </p>
+    <% else %>
+      <%= javascript_tag do %>
+        $(document).ready(function() {
+          var content = $('<%= content_tag :p do -%>
+            <%= hidden_field_tag "repository[create_readme]", "false" -%>
+            <%= content_tag :label, l(:label_init_repo_with_readme) -%>
+            <%= check_box_tag "repository[create_readme]", "true", RedmineGitolite::Config.get_setting(:init_repositories_on_create, true) -%>
+          <% end -%>');
+
+          if ($('#repository_create_readme').length == 0) {
+            content.insertAfter($('#repository_identifier').parent());
+          }
+        });
+      <% end %>
+    <% end %>
+
+  </div>
+
+<% end %>
diff --git a/app/views/repositories/_sidebar.html.erb b/app/views/repositories/_sidebar.html.erb
new file mode 100644
index 0000000..ad3d7bd
--- /dev/null
+++ b/app/views/repositories/_sidebar.html.erb
@@ -0,0 +1,24 @@
+<%= javascript_tag do %>
+  $(document).ready(function() {
+    $('#sidebar p').remove();
+  });
+<% end %>
+
+
+
+<ul class="repository git">
+  <% @repositories.sort.each do |repo| %>
+      <li class="repository git"><%= link_to h(repo.name), {:controller => 'repositories', :action => 'show', :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil},
+                                    :class => [ 'repository', (repo == @repository ? 'selected' : ''), @repository.type.split('::')[1].downcase ].join(' ') %>
+      </li>
+  <% end %>
+</ul>
+
+<% if @repository.is_a?(Repository::Git) && RedmineGitolite::Config.get_setting(:show_repositories_url, true) %>
+  <div class="git_hosting_urls">
+    <h3><%= l(:label_repository_access_url) %></h3>
+    <%= render :partial => 'common/git_urls', :locals => {:repository => @repository} %>
+  </div>
+<% end %>
+
+<%= render :partial => 'repositories/download_revision' %>
diff --git a/app/views/repository_protected_branches/_form.html.erb b/app/views/repository_protected_branches/_form.html.erb
new file mode 100644
index 0000000..c60e200
--- /dev/null
+++ b/app/views/repository_protected_branches/_form.html.erb
@@ -0,0 +1,55 @@
+<div id="validation_messages_protected_branch"><%= error_messages_for 'protected_branch' %></div>
+
+<div class="box">
+  <p><%= f.text_field :path,        :required => true, :size => 65, :label => l(:label_branch_path) %></p>
+  <p><%= f.select     :permissions, options_for_select(RepositoryProtectedBranche::VALID_PERMS, @protected_branch.permissions),
+                      :required => true,
+                      :label    => :label_permissions %>
+  </p>
+
+  <%= hidden_field_tag "repository_protected_branche[user_list][]", "" %>
+
+  <p><label for="repository_protected_branche[user_list]"><%= l(:label_user_list) %> :</label></p>
+  <ul id="user_list">
+    <% if @protected_branch.user_list.any? %>
+      <% @protected_branch.user_list.each do |item| %>
+        <li><%= item %></li>
+      <% end %>
+    <% end %>
+  </ul>
+</div>
+
+<%= javascript_tag do %>
+  var user_list = <%= raw @protected_branch.available_users.to_json %>;
+
+  function loadTagIt(target){
+
+    $('#' + target).gtagit({
+      autocomplete: {source: function(request, resolve) {
+          // fetch new values with request.resolve
+          resolve(user_list);
+        }
+      },
+      afterTagAdded: function(event, ui) {
+        var value = ui.tag.children('input:hidden').val();
+        user_list = user_list.filter(function(v) { return v != value;});
+        $(".ui-dialog-content").dialog("option", "position", ['center', 'center']).animate('slow');
+      },
+      afterTagRemoved: function(event, ui) {
+        var value = ui.tag.children('input:hidden').val();
+        user_list.push(value);
+        $(".ui-dialog-content").dialog("option", "position", ['center', 'center']).animate('slow');
+      },
+      showAutocompleteOnFocus: true,
+      placeholderText: '+ add user',
+      allowDuplicates: false,
+      caseSensitive: false,
+      fieldName: 'repository_protected_branche[' + target + '][]',
+    });
+
+  }
+
+  $(document).ready(function() {
+    loadTagIt('user_list');
+  });
+<% end %>
diff --git a/app/views/repository_protected_branches/edit.html.erb b/app/views/repository_protected_branches/edit.html.erb
new file mode 100644
index 0000000..6ff1514
--- /dev/null
+++ b/app/views/repository_protected_branches/edit.html.erb
@@ -0,0 +1,15 @@
+<% if !@is_xhr %>
+  <h2><%= l(:label_protected_branch_edit) %></h2>
+<% end %>
+
+<%= labelled_form_for :repository_protected_branche, @protected_branch,
+                      :url  => repository_protected_branch_path(@repository, @protected_branch),
+                      :html => { :method => :put, :class => 'tabular', :remote => @is_xhr } do |f| %>
+
+  <%= render :partial => 'form', :locals => { :f => f } %>
+
+  <% if !@is_xhr %>
+    <%= submit_tag l(:button_save), :disable_with => l(:label_backup_in_progress) %>
+    <%= link_to l(:button_cancel), url_for(:controller => 'repositories', :action => 'edit', :id => @repository.id) %>
+  <% end %>
+<% end %>
diff --git a/app/views/repository_protected_branches/form_error.js.erb b/app/views/repository_protected_branches/form_error.js.erb
new file mode 100644
index 0000000..c00afe0
--- /dev/null
+++ b/app/views/repository_protected_branches/form_error.js.erb
@@ -0,0 +1 @@
+$('#validation_messages_protected_branch').html("<%= escape_javascript(error_messages_for 'protected_branch') %>");
diff --git a/app/views/repository_protected_branches/index.html.erb b/app/views/repository_protected_branches/index.html.erb
new file mode 100644
index 0000000..ff5eb2f
--- /dev/null
+++ b/app/views/repository_protected_branches/index.html.erb
@@ -0,0 +1,86 @@
+<div>
+
+  <% if user_allowed_to(:create_repository_protected_branches, @project) %>
+    <div class="contextual">
+      <%= link_to(l(:label_protected_branch_add), new_repository_protected_branch_path(@repository),
+                                                  :class => 'icon icon-add modal-box') %>
+
+    </div>
+  <% end %>
+
+  <h3><%= l(:label_protected_branches) %></h3>
+
+  <% if @repository_protected_branches.any? %>
+
+    <table id="protected_branches" class="table table-hover" data-update-url="<%= sort_repository_protected_branches_url %>">
+      <thead>
+        <tr>
+          <th></th>
+          <th><%= l(:label_branch_path) %></th>
+          <th><%= l(:label_permissions) %></th>
+          <th><%= l(:label_user_list) %></th>
+          <th></th>
+        </tr>
+      </thead>
+
+      <tbody>
+        <% @repository_protected_branches.each do |protected_branch| %>
+          <%= content_tag_for(:tr, protected_branch) do %>
+            <td><span class="handle">[drag]</span></td>
+            <td style="font-family: Consolas;" ><%= protected_branch.path %></td>
+            <td>
+              <% color = protected_branch.permissions == '-' ? 'important' : 'success' %>
+              <span class="label label-<%= color %>"><%= protected_branch.permissions %></span>
+            </td>
+            <td>
+              <% protected_branch.user_list.each do |user| %>
+                <span class="label label-info"><%= user %></span>
+              <% end %>
+            </td>
+            <td class="buttons">
+              <% if user_allowed_to(:edit_repository_protected_branches, @project) %>
+
+                <%= link_to l(:button_edit), edit_repository_protected_branch_path(@repository, protected_branch),
+                                             :class => 'icon icon-edit modal-box' %>
+
+                <%= link_to l(:button_clone), clone_repository_protected_branch_path(@repository, protected_branch),
+                                              :class => 'icon icon-clone modal-box' %>
+
+                <%= link_to l(:button_delete), repository_protected_branch_path(@repository, protected_branch),
+                                               :remote  => true,
+                                               :method  => :delete,
+                                               :confirm => l(:text_are_you_sure),
+                                               :class   => 'icon icon-del' %>
+              <% end %>
+            </td>
+          <% end %>
+        <% end %>
+      </tbody>
+    </table>
+
+  <% else %>
+    <p class="nodata"><%= l(:label_no_data) %></p>
+  <% end %>
+
+</div>
+
+<%= javascript_tag do %>
+  // Return a helper with preserved width of cells
+  var fixHelper = function(e, ui) {
+    ui.children().each(function() {
+      $(this).width($(this).width());
+    });
+    return ui;
+  };
+
+  $(document).ready(function() {
+    initModalBoxes(modals);
+    $('#protected_branches tbody').sortable({
+      helper: fixHelper,
+      axis: 'y',
+      update: function(event, ui) {
+        $.post($('#protected_branches').data('update-url'), $(this).sortable('serialize'));
+      }
+    });
+  });
+<% end %>
diff --git a/app/views/repository_protected_branches/new.html.erb b/app/views/repository_protected_branches/new.html.erb
new file mode 100644
index 0000000..4aa4887
--- /dev/null
+++ b/app/views/repository_protected_branches/new.html.erb
@@ -0,0 +1,15 @@
+<% if !@is_xhr %>
+  <h2><%= l(:label_protected_branch_create) %></h2>
+<% end %>
+
+<%= labelled_form_for :repository_protected_branche, @protected_branch,
+                      :url  => repository_protected_branches_path(@repository),
+                      :html => { :method => :post, :class => 'tabular', :remote => @is_xhr } do |f| %>
+
+  <%= render :partial => 'form', :locals => { :f => f } %>
+
+  <% if !@is_xhr %>
+    <%= submit_tag l(:button_save), :disable_with => l(:label_backup_in_progress) %>
+    <%= link_to l(:button_cancel), url_for(:controller => 'repositories', :action => 'edit', :id => @repository.id) %>
+  <% end %>
+<% end %>
diff --git a/assets/fonts/FontAwesome.otf b/assets/fonts/FontAwesome.otf
old mode 100644
new mode 100755
diff --git a/assets/images/clone.png b/assets/images/clone.png
new file mode 100644
index 0000000..057f4a5
Binary files /dev/null and b/assets/images/clone.png differ
diff --git a/assets/images/locked.png b/assets/images/locked.png
new file mode 100644
index 0000000..5c46f15
Binary files /dev/null and b/assets/images/locked.png differ
diff --git a/assets/javascripts/bootstrap.js b/assets/javascripts/bootstrap.js
old mode 100644
new mode 100755
diff --git a/assets/javascripts/plugins/ZeroClipboard.min.map b/assets/javascripts/plugins/ZeroClipboard.min.map
new file mode 100644
index 0000000..854758f
--- /dev/null
+++ b/assets/javascripts/plugins/ZeroClipboard.min.map
@@ -0,0 +1 @@
+{"version":3,"file":"ZeroClipboard.min.js","sources":["ZeroClipboard.js"],"names":["window","undefined","_currentElement","_copyTarget","_window","_document","document","_navigator","navigator","_setTimeout","setTimeout","_encodeURIComponent","encodeURIComponent","_ActiveXObject","ActiveXObject","_Error","Error","_parseInt","Number","parseInt","_parseFloat","parseFloat","_isNaN","isNaN","_round","Math","round","_now","Date","now","_keys","Object","keys","_defineProperty","defineProperty","_hasOwn","prototype","hasOwnProperty","_slice","Array","slice","_unwrap","unwrapper","el","wrap","unwrap","div","createElement","unwrappedDiv","nodeType","e","_args","argumentsObj","call","_extend","i","len","arg","prop","src","copy","args","arguments","target","length","_deepCopy","source","_pick","obj","newObj","_omit","indexOf","_deleteOwnProperties","_containedBy","ancestorEl","ownerDocument","parentNode","_getDirPathOfUrl","url","dir","split","lastIndexOf","_getCurrentScriptUrlFromErrorStack","stack","matches","match","_getCurrentScriptUrlFromError","err","sourceURL","fileName","_getCurrentScriptUrl","jsPath","scripts","currentScript","getElementsByTagName","readyState","_getUnanimousScriptParentDir","jsDir","_getDefaultSwfPath","_flashState","bridge","version","pluginType","disabled","outdated","unavailable","deactivated","overdue","ready","_minimumFlashVersion","_handlers","_clipData","_clipDataFormatMap","_eventMessages","error","flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue","_globalConfig","swfPath","trustedDomains","location","host","cacheBust","forceEnhancedClipboard","flashLoadTimeout","autoActivate","bubbleEvents","containerId","containerClass","swfObjectId","hoverClass","activeClass","forceHandCursor","title","zIndex","_config","options","test","_isValidHtml4Id","_state","browser","flash","zeroclipboard","ZeroClipboard","config","_isFlashUnusable","_on","eventType","listener","events","added","toLowerCase","on","replace","push","emit","type","errorTypes","name","_off","foundIndex","perEventHandlers","off","splice","_listeners","_emit","event","eventCopy","returnVal","tmp","_createEvent","_preprocessEvent","_dispatchCallbacks","this","_mapClipDataToFlash","data","formatMap","_create","isFlashUnusable","maxWait","_embedSwf","_destroy","clearData","blur","_unembedSwf","_setData","format","dataObj","dataFormat","_clearData","_getData","_focus","element","_removeClass","_addClass","newTitle","getAttribute","htmlBridge","_getHtmlBridge","setAttribute","useHandCursor","_getStyle","_setHandCursor","_reposition","_blur","removeAttribute","style","left","top","width","_activeElement","id","relatedTarget","currentTarget","timeStamp","msg","message","minimumVersion","clipboardData","setData","_mapClipResultsFromFlash","_getRelatedTarget","_addMouseData","targetEl","relatedTargetId","getElementById","srcElement","fromElement","toElement","pos","_getDOMObjectPosition","screenLeft","screenX","screenTop","screenY","scrollLeft","body","documentElement","scrollTop","pageX","_stageX","pageY","_stageY","clientX","clientY","moveX","movementX","moveY","movementY","x","y","offsetX","offsetY","layerX","layerY","_shouldPerformAsync","_dispatchCallback","func","context","async","apply","wildcardTypeHandlers","specificTypeHandlers","handlers","concat","originalContext","handleEvent","sourceIsSwf","_source","flashErrorNames","wasDeactivated","textContent","htmlContent","value","outerHTML","innerHTML","innerText","_safeActiveElement","focus","_fireMouseEvent","bubbles","cancelable","doc","defaults","view","defaultView","canBubble","detail","button","which","createEvent","dispatchEvent","ctrlKey","altKey","shiftKey","metaKey","initMouseEvent","_createHtmlBridge","container","className","position","height","_getSafeZIndex","flashBridge","nodeName","allowScriptAccess","_determineScriptAccess","allowNetworking","flashvars","_vars","swfUrl","_cacheBust","divToBeReplaced","appendChild","tmpDiv","oldIE","firstChild","replaceChild","display","removeSwfFromIE","removeChild","clipData","newClipData","text","html","rtf","clipResults","newResults","tmpHash","path","domain","domains","str","trustedOriginsExpanded","_extractDomain","protocol","join","originOrUrl","protocolIndex","pathIndex","_extractAllDomains","origins","resultsArray","currentDomain","configOptions","swfDomain","activeElement","classList","contains","add","classNames","setClass","c","cl","remove","getComputedStyle","getPropertyValue","_getZoomFactor","rect","physicalWidth","logicalWidth","zoomFactor","getBoundingClientRect","right","offsetWidth","info","pageXOffset","pageYOffset","leftBorderWidth","clientLeft","topBorderWidth","clientTop","bottom","enabled","setHandCursor","val","_detectFlashSupport","parseFlashVersion","desc","isPepperFlash","flashPlayerFileName","inspectPlugin","plugin","hasFlash","flashVersion","description","filename","isPPAPI","ax","mimeType","isActiveX","plugins","mimeTypes","enabledPlugin","GetVariable","e1","e2","e3","_createClient","writable","configurable","enumerable","state","create","destroy","getData","activate","deactivate","_clientIdCounter","_clientMeta","_elementIdCounter","_elementMeta","_mouseHandlers","_clientConstructor","elements","client","instance","clip","_clientOn","_clientOff","_clientListeners","_clientEmit","_clientShouldEmit","_clientDispatchCallbacks","_clientClip","_prepClip","zcClippingId","_addMouseHandlers","clippedElements","_clientUnclip","meta","arrayIndex","clientIds","_removeMouseHandlers","_clientElements","_clientDestroy","unclip","clippedEls","hasClippedEls","goodTarget","goodRelTarget","goodClient","_suppressMouseEvents","stopImmediatePropagation","preventDefault","_elementMouseOver","addEventListener","mouseover","mouseout","mouseenter","mouseleave","mousemove","mouseHandlers","key","mouseEvents","removeEventListener","setText","setHtml","setRichText","richText","define","amd","module","exports"],"mappings":"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","sourcesContent":["/*!\n * ZeroClipboard\n * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface.\n * Copyright (c) 2014 Jon Rohan, James M. Greene\n * Licensed MIT\n * http://zeroclipboard.org/\n * v2.1.6\n */\n(function(window, undefined) {\n  \"use strict\";\n  /**\n * Store references to critically important global functions that may be\n * overridden on certain web pages.\n */\n  var _window = window, _document = _window.document, _navigator = _window.navigator, _setTimeout = _window.setTimeout, _encodeURIComponent = _window.encodeURIComponent, _ActiveXObject = _window.ActiveXObject, _Error = _window.Error, _parseInt = _window.Number.parseInt || _window.parseInt, _parseFloat = _window.Number.parseFloat || _window.parseFloat, _isNaN = _window.Number.isNaN || _window.isNaN, _round = _window.Math.round, _now = _window.Date.now, _keys = _window.Object.keys, _defineProperty = _window.Object.defineProperty, _hasOwn = _window.Object.prototype.hasOwnProperty, _slice = _window.Array.prototype.slice, _unwrap = function() {\n    var unwrapper = function(el) {\n      return el;\n    };\n    if (typeof _window.wrap === \"function\" && typeof _window.unwrap === \"function\") {\n      try {\n        var div = _document.createElement(\"div\");\n        var unwrappedDiv = _window.unwrap(div);\n        if (div.nodeType === 1 && unwrappedDiv && unwrappedDiv.nodeType === 1) {\n          unwrapper = _window.unwrap;\n        }\n      } catch (e) {}\n    }\n    return unwrapper;\n  }();\n  /**\n * Convert an `arguments` object into an Array.\n *\n * @returns The arguments as an Array\n * @private\n */\n  var _args = function(argumentsObj) {\n    return _slice.call(argumentsObj, 0);\n  };\n  /**\n * Shallow-copy the owned, enumerable properties of one object over to another, similar to jQuery's `$.extend`.\n *\n * @returns The target object, augmented\n * @private\n */\n  var _extend = function() {\n    var i, len, arg, prop, src, copy, args = _args(arguments), target = args[0] || {};\n    for (i = 1, len = args.length; i < len; i++) {\n      if ((arg = args[i]) != null) {\n        for (prop in arg) {\n          if (_hasOwn.call(arg, prop)) {\n            src = target[prop];\n            copy = arg[prop];\n            if (target !== copy && copy !== undefined) {\n              target[prop] = copy;\n            }\n          }\n        }\n      }\n    }\n    return target;\n  };\n  /**\n * Return a deep copy of the source object or array.\n *\n * @returns Object or Array\n * @private\n */\n  var _deepCopy = function(source) {\n    var copy, i, len, prop;\n    if (typeof source !== \"object\" || source == null) {\n      copy = source;\n    } else if (typeof source.length === \"number\") {\n      copy = [];\n      for (i = 0, len = source.length; i < len; i++) {\n        if (_hasOwn.call(source, i)) {\n          copy[i] = _deepCopy(source[i]);\n        }\n      }\n    } else {\n      copy = {};\n      for (prop in source) {\n        if (_hasOwn.call(source, prop)) {\n          copy[prop] = _deepCopy(source[prop]);\n        }\n      }\n    }\n    return copy;\n  };\n  /**\n * Makes a shallow copy of `obj` (like `_extend`) but filters its properties based on a list of `keys` to keep.\n * The inverse of `_omit`, mostly. The big difference is that these properties do NOT need to be enumerable to\n * be kept.\n *\n * @returns A new filtered object.\n * @private\n */\n  var _pick = function(obj, keys) {\n    var newObj = {};\n    for (var i = 0, len = keys.length; i < len; i++) {\n      if (keys[i] in obj) {\n        newObj[keys[i]] = obj[keys[i]];\n      }\n    }\n    return newObj;\n  };\n  /**\n * Makes a shallow copy of `obj` (like `_extend`) but filters its properties based on a list of `keys` to omit.\n * The inverse of `_pick`.\n *\n * @returns A new filtered object.\n * @private\n */\n  var _omit = function(obj, keys) {\n    var newObj = {};\n    for (var prop in obj) {\n      if (keys.indexOf(prop) === -1) {\n        newObj[prop] = obj[prop];\n      }\n    }\n    return newObj;\n  };\n  /**\n * Remove all owned, enumerable properties from an object.\n *\n * @returns The original object without its owned, enumerable properties.\n * @private\n */\n  var _deleteOwnProperties = function(obj) {\n    if (obj) {\n      for (var prop in obj) {\n        if (_hasOwn.call(obj, prop)) {\n          delete obj[prop];\n        }\n      }\n    }\n    return obj;\n  };\n  /**\n * Determine if an element is contained within another element.\n *\n * @returns Boolean\n * @private\n */\n  var _containedBy = function(el, ancestorEl) {\n    if (el && el.nodeType === 1 && el.ownerDocument && ancestorEl && (ancestorEl.nodeType === 1 && ancestorEl.ownerDocument && ancestorEl.ownerDocument === el.ownerDocument || ancestorEl.nodeType === 9 && !ancestorEl.ownerDocument && ancestorEl === el.ownerDocument)) {\n      do {\n        if (el === ancestorEl) {\n          return true;\n        }\n        el = el.parentNode;\n      } while (el);\n    }\n    return false;\n  };\n  /**\n * Get the URL path's parent directory.\n *\n * @returns String or `undefined`\n * @private\n */\n  var _getDirPathOfUrl = function(url) {\n    var dir;\n    if (typeof url === \"string\" && url) {\n      dir = url.split(\"#\")[0].split(\"?\")[0];\n      dir = url.slice(0, url.lastIndexOf(\"/\") + 1);\n    }\n    return dir;\n  };\n  /**\n * Get the current script's URL by throwing an `Error` and analyzing it.\n *\n * @returns String or `undefined`\n * @private\n */\n  var _getCurrentScriptUrlFromErrorStack = function(stack) {\n    var url, matches;\n    if (typeof stack === \"string\" && stack) {\n      matches = stack.match(/^(?:|[^:@]*@|.+\\)@(?=http[s]?|file)|.+?\\s+(?: at |@)(?:[^:\\(]+ )*[\\(]?)((?:http[s]?|file):\\/\\/[\\/]?.+?\\/[^:\\)]*?)(?::\\d+)(?::\\d+)?/);\n      if (matches && matches[1]) {\n        url = matches[1];\n      } else {\n        matches = stack.match(/\\)@((?:http[s]?|file):\\/\\/[\\/]?.+?\\/[^:\\)]*?)(?::\\d+)(?::\\d+)?/);\n        if (matches && matches[1]) {\n          url = matches[1];\n        }\n      }\n    }\n    return url;\n  };\n  /**\n * Get the current script's URL by throwing an `Error` and analyzing it.\n *\n * @returns String or `undefined`\n * @private\n */\n  var _getCurrentScriptUrlFromError = function() {\n    var url, err;\n    try {\n      throw new _Error();\n    } catch (e) {\n      err = e;\n    }\n    if (err) {\n      url = err.sourceURL || err.fileName || _getCurrentScriptUrlFromErrorStack(err.stack);\n    }\n    return url;\n  };\n  /**\n * Get the current script's URL.\n *\n * @returns String or `undefined`\n * @private\n */\n  var _getCurrentScriptUrl = function() {\n    var jsPath, scripts, i;\n    if (_document.currentScript && (jsPath = _document.currentScript.src)) {\n      return jsPath;\n    }\n    scripts = _document.getElementsByTagName(\"script\");\n    if (scripts.length === 1) {\n      return scripts[0].src || undefined;\n    }\n    if (\"readyState\" in scripts[0]) {\n      for (i = scripts.length; i--; ) {\n        if (scripts[i].readyState === \"interactive\" && (jsPath = scripts[i].src)) {\n          return jsPath;\n        }\n      }\n    }\n    if (_document.readyState === \"loading\" && (jsPath = scripts[scripts.length - 1].src)) {\n      return jsPath;\n    }\n    if (jsPath = _getCurrentScriptUrlFromError()) {\n      return jsPath;\n    }\n    return undefined;\n  };\n  /**\n * Get the unanimous parent directory of ALL script tags.\n * If any script tags are either (a) inline or (b) from differing parent\n * directories, this method must return `undefined`.\n *\n * @returns String or `undefined`\n * @private\n */\n  var _getUnanimousScriptParentDir = function() {\n    var i, jsDir, jsPath, scripts = _document.getElementsByTagName(\"script\");\n    for (i = scripts.length; i--; ) {\n      if (!(jsPath = scripts[i].src)) {\n        jsDir = null;\n        break;\n      }\n      jsPath = _getDirPathOfUrl(jsPath);\n      if (jsDir == null) {\n        jsDir = jsPath;\n      } else if (jsDir !== jsPath) {\n        jsDir = null;\n        break;\n      }\n    }\n    return jsDir || undefined;\n  };\n  /**\n * Get the presumed location of the \"ZeroClipboard.swf\" file, based on the location\n * of the executing JavaScript file (e.g. \"ZeroClipboard.js\", etc.).\n *\n * @returns String\n * @private\n */\n  var _getDefaultSwfPath = function() {\n    var jsDir = _getDirPathOfUrl(_getCurrentScriptUrl()) || _getUnanimousScriptParentDir() || \"\";\n    return jsDir + \"ZeroClipboard.swf\";\n  };\n  /**\n * Keep track of the state of the Flash object.\n * @private\n */\n  var _flashState = {\n    bridge: null,\n    version: \"0.0.0\",\n    pluginType: \"unknown\",\n    disabled: null,\n    outdated: null,\n    unavailable: null,\n    deactivated: null,\n    overdue: null,\n    ready: null\n  };\n  /**\n * The minimum Flash Player version required to use ZeroClipboard completely.\n * @readonly\n * @private\n */\n  var _minimumFlashVersion = \"11.0.0\";\n  /**\n * Keep track of all event listener registrations.\n * @private\n */\n  var _handlers = {};\n  /**\n * Keep track of the currently activated element.\n * @private\n */\n  var _currentElement;\n  /**\n * Keep track of the element that was activated when a `copy` process started.\n * @private\n */\n  var _copyTarget;\n  /**\n * Keep track of data for the pending clipboard transaction.\n * @private\n */\n  var _clipData = {};\n  /**\n * Keep track of data formats for the pending clipboard transaction.\n * @private\n */\n  var _clipDataFormatMap = null;\n  /**\n * The `message` store for events\n * @private\n */\n  var _eventMessages = {\n    ready: \"Flash communication is established\",\n    error: {\n      \"flash-disabled\": \"Flash is disabled or not installed\",\n      \"flash-outdated\": \"Flash is too outdated to support ZeroClipboard\",\n      \"flash-unavailable\": \"Flash is unable to communicate bidirectionally with JavaScript\",\n      \"flash-deactivated\": \"Flash is too outdated for your browser and/or is configured as click-to-activate\",\n      \"flash-overdue\": \"Flash communication was established but NOT within the acceptable time limit\"\n    }\n  };\n  /**\n * ZeroClipboard configuration defaults for the Core module.\n * @private\n */\n  var _globalConfig = {\n    swfPath: _getDefaultSwfPath(),\n    trustedDomains: window.location.host ? [ window.location.host ] : [],\n    cacheBust: true,\n    forceEnhancedClipboard: false,\n    flashLoadTimeout: 3e4,\n    autoActivate: true,\n    bubbleEvents: true,\n    containerId: \"global-zeroclipboard-html-bridge\",\n    containerClass: \"global-zeroclipboard-container\",\n    swfObjectId: \"global-zeroclipboard-flash-bridge\",\n    hoverClass: \"zeroclipboard-is-hover\",\n    activeClass: \"zeroclipboard-is-active\",\n    forceHandCursor: false,\n    title: null,\n    zIndex: 999999999\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.config`.\n * @private\n */\n  var _config = function(options) {\n    if (typeof options === \"object\" && options !== null) {\n      for (var prop in options) {\n        if (_hasOwn.call(options, prop)) {\n          if (/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(prop)) {\n            _globalConfig[prop] = options[prop];\n          } else if (_flashState.bridge == null) {\n            if (prop === \"containerId\" || prop === \"swfObjectId\") {\n              if (_isValidHtml4Id(options[prop])) {\n                _globalConfig[prop] = options[prop];\n              } else {\n                throw new Error(\"The specified `\" + prop + \"` value is not valid as an HTML4 Element ID\");\n              }\n            } else {\n              _globalConfig[prop] = options[prop];\n            }\n          }\n        }\n      }\n    }\n    if (typeof options === \"string\" && options) {\n      if (_hasOwn.call(_globalConfig, options)) {\n        return _globalConfig[options];\n      }\n      return;\n    }\n    return _deepCopy(_globalConfig);\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.state`.\n * @private\n */\n  var _state = function() {\n    return {\n      browser: _pick(_navigator, [ \"userAgent\", \"platform\", \"appName\" ]),\n      flash: _omit(_flashState, [ \"bridge\" ]),\n      zeroclipboard: {\n        version: ZeroClipboard.version,\n        config: ZeroClipboard.config()\n      }\n    };\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.isFlashUnusable`.\n * @private\n */\n  var _isFlashUnusable = function() {\n    return !!(_flashState.disabled || _flashState.outdated || _flashState.unavailable || _flashState.deactivated);\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.on`.\n * @private\n */\n  var _on = function(eventType, listener) {\n    var i, len, events, added = {};\n    if (typeof eventType === \"string\" && eventType) {\n      events = eventType.toLowerCase().split(/\\s+/);\n    } else if (typeof eventType === \"object\" && eventType && typeof listener === \"undefined\") {\n      for (i in eventType) {\n        if (_hasOwn.call(eventType, i) && typeof i === \"string\" && i && typeof eventType[i] === \"function\") {\n          ZeroClipboard.on(i, eventType[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventType = events[i].replace(/^on/, \"\");\n        added[eventType] = true;\n        if (!_handlers[eventType]) {\n          _handlers[eventType] = [];\n        }\n        _handlers[eventType].push(listener);\n      }\n      if (added.ready && _flashState.ready) {\n        ZeroClipboard.emit({\n          type: \"ready\"\n        });\n      }\n      if (added.error) {\n        var errorTypes = [ \"disabled\", \"outdated\", \"unavailable\", \"deactivated\", \"overdue\" ];\n        for (i = 0, len = errorTypes.length; i < len; i++) {\n          if (_flashState[errorTypes[i]] === true) {\n            ZeroClipboard.emit({\n              type: \"error\",\n              name: \"flash-\" + errorTypes[i]\n            });\n            break;\n          }\n        }\n      }\n    }\n    return ZeroClipboard;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.off`.\n * @private\n */\n  var _off = function(eventType, listener) {\n    var i, len, foundIndex, events, perEventHandlers;\n    if (arguments.length === 0) {\n      events = _keys(_handlers);\n    } else if (typeof eventType === \"string\" && eventType) {\n      events = eventType.split(/\\s+/);\n    } else if (typeof eventType === \"object\" && eventType && typeof listener === \"undefined\") {\n      for (i in eventType) {\n        if (_hasOwn.call(eventType, i) && typeof i === \"string\" && i && typeof eventType[i] === \"function\") {\n          ZeroClipboard.off(i, eventType[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventType = events[i].toLowerCase().replace(/^on/, \"\");\n        perEventHandlers = _handlers[eventType];\n        if (perEventHandlers && perEventHandlers.length) {\n          if (listener) {\n            foundIndex = perEventHandlers.indexOf(listener);\n            while (foundIndex !== -1) {\n              perEventHandlers.splice(foundIndex, 1);\n              foundIndex = perEventHandlers.indexOf(listener, foundIndex);\n            }\n          } else {\n            perEventHandlers.length = 0;\n          }\n        }\n      }\n    }\n    return ZeroClipboard;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.handlers`.\n * @private\n */\n  var _listeners = function(eventType) {\n    var copy;\n    if (typeof eventType === \"string\" && eventType) {\n      copy = _deepCopy(_handlers[eventType]) || null;\n    } else {\n      copy = _deepCopy(_handlers);\n    }\n    return copy;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.emit`.\n * @private\n */\n  var _emit = function(event) {\n    var eventCopy, returnVal, tmp;\n    event = _createEvent(event);\n    if (!event) {\n      return;\n    }\n    if (_preprocessEvent(event)) {\n      return;\n    }\n    if (event.type === \"ready\" && _flashState.overdue === true) {\n      return ZeroClipboard.emit({\n        type: \"error\",\n        name: \"flash-overdue\"\n      });\n    }\n    eventCopy = _extend({}, event);\n    _dispatchCallbacks.call(this, eventCopy);\n    if (event.type === \"copy\") {\n      tmp = _mapClipDataToFlash(_clipData);\n      returnVal = tmp.data;\n      _clipDataFormatMap = tmp.formatMap;\n    }\n    return returnVal;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.create`.\n * @private\n */\n  var _create = function() {\n    if (typeof _flashState.ready !== \"boolean\") {\n      _flashState.ready = false;\n    }\n    if (!ZeroClipboard.isFlashUnusable() && _flashState.bridge === null) {\n      var maxWait = _globalConfig.flashLoadTimeout;\n      if (typeof maxWait === \"number\" && maxWait >= 0) {\n        _setTimeout(function() {\n          if (typeof _flashState.deactivated !== \"boolean\") {\n            _flashState.deactivated = true;\n          }\n          if (_flashState.deactivated === true) {\n            ZeroClipboard.emit({\n              type: \"error\",\n              name: \"flash-deactivated\"\n            });\n          }\n        }, maxWait);\n      }\n      _flashState.overdue = false;\n      _embedSwf();\n    }\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.destroy`.\n * @private\n */\n  var _destroy = function() {\n    ZeroClipboard.clearData();\n    ZeroClipboard.blur();\n    ZeroClipboard.emit(\"destroy\");\n    _unembedSwf();\n    ZeroClipboard.off();\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.setData`.\n * @private\n */\n  var _setData = function(format, data) {\n    var dataObj;\n    if (typeof format === \"object\" && format && typeof data === \"undefined\") {\n      dataObj = format;\n      ZeroClipboard.clearData();\n    } else if (typeof format === \"string\" && format) {\n      dataObj = {};\n      dataObj[format] = data;\n    } else {\n      return;\n    }\n    for (var dataFormat in dataObj) {\n      if (typeof dataFormat === \"string\" && dataFormat && _hasOwn.call(dataObj, dataFormat) && typeof dataObj[dataFormat] === \"string\" && dataObj[dataFormat]) {\n        _clipData[dataFormat] = dataObj[dataFormat];\n      }\n    }\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.clearData`.\n * @private\n */\n  var _clearData = function(format) {\n    if (typeof format === \"undefined\") {\n      _deleteOwnProperties(_clipData);\n      _clipDataFormatMap = null;\n    } else if (typeof format === \"string\" && _hasOwn.call(_clipData, format)) {\n      delete _clipData[format];\n    }\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.getData`.\n * @private\n */\n  var _getData = function(format) {\n    if (typeof format === \"undefined\") {\n      return _deepCopy(_clipData);\n    } else if (typeof format === \"string\" && _hasOwn.call(_clipData, format)) {\n      return _clipData[format];\n    }\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.focus`/`ZeroClipboard.activate`.\n * @private\n */\n  var _focus = function(element) {\n    if (!(element && element.nodeType === 1)) {\n      return;\n    }\n    if (_currentElement) {\n      _removeClass(_currentElement, _globalConfig.activeClass);\n      if (_currentElement !== element) {\n        _removeClass(_currentElement, _globalConfig.hoverClass);\n      }\n    }\n    _currentElement = element;\n    _addClass(element, _globalConfig.hoverClass);\n    var newTitle = element.getAttribute(\"title\") || _globalConfig.title;\n    if (typeof newTitle === \"string\" && newTitle) {\n      var htmlBridge = _getHtmlBridge(_flashState.bridge);\n      if (htmlBridge) {\n        htmlBridge.setAttribute(\"title\", newTitle);\n      }\n    }\n    var useHandCursor = _globalConfig.forceHandCursor === true || _getStyle(element, \"cursor\") === \"pointer\";\n    _setHandCursor(useHandCursor);\n    _reposition();\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.blur`/`ZeroClipboard.deactivate`.\n * @private\n */\n  var _blur = function() {\n    var htmlBridge = _getHtmlBridge(_flashState.bridge);\n    if (htmlBridge) {\n      htmlBridge.removeAttribute(\"title\");\n      htmlBridge.style.left = \"0px\";\n      htmlBridge.style.top = \"-9999px\";\n      htmlBridge.style.width = \"1px\";\n      htmlBridge.style.top = \"1px\";\n    }\n    if (_currentElement) {\n      _removeClass(_currentElement, _globalConfig.hoverClass);\n      _removeClass(_currentElement, _globalConfig.activeClass);\n      _currentElement = null;\n    }\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.activeElement`.\n * @private\n */\n  var _activeElement = function() {\n    return _currentElement || null;\n  };\n  /**\n * Check if a value is a valid HTML4 `ID` or `Name` token.\n * @private\n */\n  var _isValidHtml4Id = function(id) {\n    return typeof id === \"string\" && id && /^[A-Za-z][A-Za-z0-9_:\\-\\.]*$/.test(id);\n  };\n  /**\n * Create or update an `event` object, based on the `eventType`.\n * @private\n */\n  var _createEvent = function(event) {\n    var eventType;\n    if (typeof event === \"string\" && event) {\n      eventType = event;\n      event = {};\n    } else if (typeof event === \"object\" && event && typeof event.type === \"string\" && event.type) {\n      eventType = event.type;\n    }\n    if (!eventType) {\n      return;\n    }\n    if (!event.target && /^(copy|aftercopy|_click)$/.test(eventType.toLowerCase())) {\n      event.target = _copyTarget;\n    }\n    _extend(event, {\n      type: eventType.toLowerCase(),\n      target: event.target || _currentElement || null,\n      relatedTarget: event.relatedTarget || null,\n      currentTarget: _flashState && _flashState.bridge || null,\n      timeStamp: event.timeStamp || _now() || null\n    });\n    var msg = _eventMessages[event.type];\n    if (event.type === \"error\" && event.name && msg) {\n      msg = msg[event.name];\n    }\n    if (msg) {\n      event.message = msg;\n    }\n    if (event.type === \"ready\") {\n      _extend(event, {\n        target: null,\n        version: _flashState.version\n      });\n    }\n    if (event.type === \"error\") {\n      if (/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(event.name)) {\n        _extend(event, {\n          target: null,\n          minimumVersion: _minimumFlashVersion\n        });\n      }\n      if (/^flash-(outdated|unavailable|deactivated|overdue)$/.test(event.name)) {\n        _extend(event, {\n          version: _flashState.version\n        });\n      }\n    }\n    if (event.type === \"copy\") {\n      event.clipboardData = {\n        setData: ZeroClipboard.setData,\n        clearData: ZeroClipboard.clearData\n      };\n    }\n    if (event.type === \"aftercopy\") {\n      event = _mapClipResultsFromFlash(event, _clipDataFormatMap);\n    }\n    if (event.target && !event.relatedTarget) {\n      event.relatedTarget = _getRelatedTarget(event.target);\n    }\n    event = _addMouseData(event);\n    return event;\n  };\n  /**\n * Get a relatedTarget from the target's `data-clipboard-target` attribute\n * @private\n */\n  var _getRelatedTarget = function(targetEl) {\n    var relatedTargetId = targetEl && targetEl.getAttribute && targetEl.getAttribute(\"data-clipboard-target\");\n    return relatedTargetId ? _document.getElementById(relatedTargetId) : null;\n  };\n  /**\n * Add element and position data to `MouseEvent` instances\n * @private\n */\n  var _addMouseData = function(event) {\n    if (event && /^_(?:click|mouse(?:over|out|down|up|move))$/.test(event.type)) {\n      var srcElement = event.target;\n      var fromElement = event.type === \"_mouseover\" && event.relatedTarget ? event.relatedTarget : undefined;\n      var toElement = event.type === \"_mouseout\" && event.relatedTarget ? event.relatedTarget : undefined;\n      var pos = _getDOMObjectPosition(srcElement);\n      var screenLeft = _window.screenLeft || _window.screenX || 0;\n      var screenTop = _window.screenTop || _window.screenY || 0;\n      var scrollLeft = _document.body.scrollLeft + _document.documentElement.scrollLeft;\n      var scrollTop = _document.body.scrollTop + _document.documentElement.scrollTop;\n      var pageX = pos.left + (typeof event._stageX === \"number\" ? event._stageX : 0);\n      var pageY = pos.top + (typeof event._stageY === \"number\" ? event._stageY : 0);\n      var clientX = pageX - scrollLeft;\n      var clientY = pageY - scrollTop;\n      var screenX = screenLeft + clientX;\n      var screenY = screenTop + clientY;\n      var moveX = typeof event.movementX === \"number\" ? event.movementX : 0;\n      var moveY = typeof event.movementY === \"number\" ? event.movementY : 0;\n      delete event._stageX;\n      delete event._stageY;\n      _extend(event, {\n        srcElement: srcElement,\n        fromElement: fromElement,\n        toElement: toElement,\n        screenX: screenX,\n        screenY: screenY,\n        pageX: pageX,\n        pageY: pageY,\n        clientX: clientX,\n        clientY: clientY,\n        x: clientX,\n        y: clientY,\n        movementX: moveX,\n        movementY: moveY,\n        offsetX: 0,\n        offsetY: 0,\n        layerX: 0,\n        layerY: 0\n      });\n    }\n    return event;\n  };\n  /**\n * Determine if an event's registered handlers should be execute synchronously or asynchronously.\n *\n * @returns {boolean}\n * @private\n */\n  var _shouldPerformAsync = function(event) {\n    var eventType = event && typeof event.type === \"string\" && event.type || \"\";\n    return !/^(?:(?:before)?copy|destroy)$/.test(eventType);\n  };\n  /**\n * Control if a callback should be executed asynchronously or not.\n *\n * @returns `undefined`\n * @private\n */\n  var _dispatchCallback = function(func, context, args, async) {\n    if (async) {\n      _setTimeout(function() {\n        func.apply(context, args);\n      }, 0);\n    } else {\n      func.apply(context, args);\n    }\n  };\n  /**\n * Handle the actual dispatching of events to client instances.\n *\n * @returns `undefined`\n * @private\n */\n  var _dispatchCallbacks = function(event) {\n    if (!(typeof event === \"object\" && event && event.type)) {\n      return;\n    }\n    var async = _shouldPerformAsync(event);\n    var wildcardTypeHandlers = _handlers[\"*\"] || [];\n    var specificTypeHandlers = _handlers[event.type] || [];\n    var handlers = wildcardTypeHandlers.concat(specificTypeHandlers);\n    if (handlers && handlers.length) {\n      var i, len, func, context, eventCopy, originalContext = this;\n      for (i = 0, len = handlers.length; i < len; i++) {\n        func = handlers[i];\n        context = originalContext;\n        if (typeof func === \"string\" && typeof _window[func] === \"function\") {\n          func = _window[func];\n        }\n        if (typeof func === \"object\" && func && typeof func.handleEvent === \"function\") {\n          context = func;\n          func = func.handleEvent;\n        }\n        if (typeof func === \"function\") {\n          eventCopy = _extend({}, event);\n          _dispatchCallback(func, context, [ eventCopy ], async);\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * Preprocess any special behaviors, reactions, or state changes after receiving this event.\n * Executes only once per event emitted, NOT once per client.\n * @private\n */\n  var _preprocessEvent = function(event) {\n    var element = event.target || _currentElement || null;\n    var sourceIsSwf = event._source === \"swf\";\n    delete event._source;\n    var flashErrorNames = [ \"flash-disabled\", \"flash-outdated\", \"flash-unavailable\", \"flash-deactivated\", \"flash-overdue\" ];\n    switch (event.type) {\n     case \"error\":\n      if (flashErrorNames.indexOf(event.name) !== -1) {\n        _extend(_flashState, {\n          disabled: event.name === \"flash-disabled\",\n          outdated: event.name === \"flash-outdated\",\n          unavailable: event.name === \"flash-unavailable\",\n          deactivated: event.name === \"flash-deactivated\",\n          overdue: event.name === \"flash-overdue\",\n          ready: false\n        });\n      }\n      break;\n\n     case \"ready\":\n      var wasDeactivated = _flashState.deactivated === true;\n      _extend(_flashState, {\n        disabled: false,\n        outdated: false,\n        unavailable: false,\n        deactivated: false,\n        overdue: wasDeactivated,\n        ready: !wasDeactivated\n      });\n      break;\n\n     case \"beforecopy\":\n      _copyTarget = element;\n      break;\n\n     case \"copy\":\n      var textContent, htmlContent, targetEl = event.relatedTarget;\n      if (!(_clipData[\"text/html\"] || _clipData[\"text/plain\"]) && targetEl && (htmlContent = targetEl.value || targetEl.outerHTML || targetEl.innerHTML) && (textContent = targetEl.value || targetEl.textContent || targetEl.innerText)) {\n        event.clipboardData.clearData();\n        event.clipboardData.setData(\"text/plain\", textContent);\n        if (htmlContent !== textContent) {\n          event.clipboardData.setData(\"text/html\", htmlContent);\n        }\n      } else if (!_clipData[\"text/plain\"] && event.target && (textContent = event.target.getAttribute(\"data-clipboard-text\"))) {\n        event.clipboardData.clearData();\n        event.clipboardData.setData(\"text/plain\", textContent);\n      }\n      break;\n\n     case \"aftercopy\":\n      ZeroClipboard.clearData();\n      if (element && element !== _safeActiveElement() && element.focus) {\n        element.focus();\n      }\n      break;\n\n     case \"_mouseover\":\n      ZeroClipboard.focus(element);\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        if (element && element !== event.relatedTarget && !_containedBy(event.relatedTarget, element)) {\n          _fireMouseEvent(_extend({}, event, {\n            type: \"mouseenter\",\n            bubbles: false,\n            cancelable: false\n          }));\n        }\n        _fireMouseEvent(_extend({}, event, {\n          type: \"mouseover\"\n        }));\n      }\n      break;\n\n     case \"_mouseout\":\n      ZeroClipboard.blur();\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        if (element && element !== event.relatedTarget && !_containedBy(event.relatedTarget, element)) {\n          _fireMouseEvent(_extend({}, event, {\n            type: \"mouseleave\",\n            bubbles: false,\n            cancelable: false\n          }));\n        }\n        _fireMouseEvent(_extend({}, event, {\n          type: \"mouseout\"\n        }));\n      }\n      break;\n\n     case \"_mousedown\":\n      _addClass(element, _globalConfig.activeClass);\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        _fireMouseEvent(_extend({}, event, {\n          type: event.type.slice(1)\n        }));\n      }\n      break;\n\n     case \"_mouseup\":\n      _removeClass(element, _globalConfig.activeClass);\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        _fireMouseEvent(_extend({}, event, {\n          type: event.type.slice(1)\n        }));\n      }\n      break;\n\n     case \"_click\":\n      _copyTarget = null;\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        _fireMouseEvent(_extend({}, event, {\n          type: event.type.slice(1)\n        }));\n      }\n      break;\n\n     case \"_mousemove\":\n      if (_globalConfig.bubbleEvents === true && sourceIsSwf) {\n        _fireMouseEvent(_extend({}, event, {\n          type: event.type.slice(1)\n        }));\n      }\n      break;\n    }\n    if (/^_(?:click|mouse(?:over|out|down|up|move))$/.test(event.type)) {\n      return true;\n    }\n  };\n  /**\n * Dispatch a synthetic MouseEvent.\n *\n * @returns `undefined`\n * @private\n */\n  var _fireMouseEvent = function(event) {\n    if (!(event && typeof event.type === \"string\" && event)) {\n      return;\n    }\n    var e, target = event.target || null, doc = target && target.ownerDocument || _document, defaults = {\n      view: doc.defaultView || _window,\n      canBubble: true,\n      cancelable: true,\n      detail: event.type === \"click\" ? 1 : 0,\n      button: typeof event.which === \"number\" ? event.which - 1 : typeof event.button === \"number\" ? event.button : doc.createEvent ? 0 : 1\n    }, args = _extend(defaults, event);\n    if (!target) {\n      return;\n    }\n    if (doc.createEvent && target.dispatchEvent) {\n      args = [ args.type, args.canBubble, args.cancelable, args.view, args.detail, args.screenX, args.screenY, args.clientX, args.clientY, args.ctrlKey, args.altKey, args.shiftKey, args.metaKey, args.button, args.relatedTarget ];\n      e = doc.createEvent(\"MouseEvents\");\n      if (e.initMouseEvent) {\n        e.initMouseEvent.apply(e, args);\n        e._source = \"js\";\n        target.dispatchEvent(e);\n      }\n    }\n  };\n  /**\n * Create the HTML bridge element to embed the Flash object into.\n * @private\n */\n  var _createHtmlBridge = function() {\n    var container = _document.createElement(\"div\");\n    container.id = _globalConfig.containerId;\n    container.className = _globalConfig.containerClass;\n    container.style.position = \"absolute\";\n    container.style.left = \"0px\";\n    container.style.top = \"-9999px\";\n    container.style.width = \"1px\";\n    container.style.height = \"1px\";\n    container.style.zIndex = \"\" + _getSafeZIndex(_globalConfig.zIndex);\n    return container;\n  };\n  /**\n * Get the HTML element container that wraps the Flash bridge object/element.\n * @private\n */\n  var _getHtmlBridge = function(flashBridge) {\n    var htmlBridge = flashBridge && flashBridge.parentNode;\n    while (htmlBridge && htmlBridge.nodeName === \"OBJECT\" && htmlBridge.parentNode) {\n      htmlBridge = htmlBridge.parentNode;\n    }\n    return htmlBridge || null;\n  };\n  /**\n * Create the SWF object.\n *\n * @returns The SWF object reference.\n * @private\n */\n  var _embedSwf = function() {\n    var len, flashBridge = _flashState.bridge, container = _getHtmlBridge(flashBridge);\n    if (!flashBridge) {\n      var allowScriptAccess = _determineScriptAccess(_window.location.host, _globalConfig);\n      var allowNetworking = allowScriptAccess === \"never\" ? \"none\" : \"all\";\n      var flashvars = _vars(_globalConfig);\n      var swfUrl = _globalConfig.swfPath + _cacheBust(_globalConfig.swfPath, _globalConfig);\n      container = _createHtmlBridge();\n      var divToBeReplaced = _document.createElement(\"div\");\n      container.appendChild(divToBeReplaced);\n      _document.body.appendChild(container);\n      var tmpDiv = _document.createElement(\"div\");\n      var oldIE = _flashState.pluginType === \"activex\";\n      tmpDiv.innerHTML = '<object id=\"' + _globalConfig.swfObjectId + '\" name=\"' + _globalConfig.swfObjectId + '\" ' + 'width=\"100%\" height=\"100%\" ' + (oldIE ? 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\"' : 'type=\"application/x-shockwave-flash\" data=\"' + swfUrl + '\"') + \">\" + (oldIE ? '<param name=\"movie\" value=\"' + swfUrl + '\"/>' : \"\") + '<param name=\"allowScriptAccess\" value=\"' + allowScriptAccess + '\"/>' + '<param name=\"allowNetworking\" value=\"' + allowNetworking + '\"/>' + '<param name=\"menu\" value=\"false\"/>' + '<param name=\"wmode\" value=\"transparent\"/>' + '<param name=\"flashvars\" value=\"' + flashvars + '\"/>' + \"</object>\";\n      flashBridge = tmpDiv.firstChild;\n      tmpDiv = null;\n      _unwrap(flashBridge).ZeroClipboard = ZeroClipboard;\n      container.replaceChild(flashBridge, divToBeReplaced);\n    }\n    if (!flashBridge) {\n      flashBridge = _document[_globalConfig.swfObjectId];\n      if (flashBridge && (len = flashBridge.length)) {\n        flashBridge = flashBridge[len - 1];\n      }\n      if (!flashBridge && container) {\n        flashBridge = container.firstChild;\n      }\n    }\n    _flashState.bridge = flashBridge || null;\n    return flashBridge;\n  };\n  /**\n * Destroy the SWF object.\n * @private\n */\n  var _unembedSwf = function() {\n    var flashBridge = _flashState.bridge;\n    if (flashBridge) {\n      var htmlBridge = _getHtmlBridge(flashBridge);\n      if (htmlBridge) {\n        if (_flashState.pluginType === \"activex\" && \"readyState\" in flashBridge) {\n          flashBridge.style.display = \"none\";\n          (function removeSwfFromIE() {\n            if (flashBridge.readyState === 4) {\n              for (var prop in flashBridge) {\n                if (typeof flashBridge[prop] === \"function\") {\n                  flashBridge[prop] = null;\n                }\n              }\n              if (flashBridge.parentNode) {\n                flashBridge.parentNode.removeChild(flashBridge);\n              }\n              if (htmlBridge.parentNode) {\n                htmlBridge.parentNode.removeChild(htmlBridge);\n              }\n            } else {\n              _setTimeout(removeSwfFromIE, 10);\n            }\n          })();\n        } else {\n          if (flashBridge.parentNode) {\n            flashBridge.parentNode.removeChild(flashBridge);\n          }\n          if (htmlBridge.parentNode) {\n            htmlBridge.parentNode.removeChild(htmlBridge);\n          }\n        }\n      }\n      _flashState.ready = null;\n      _flashState.bridge = null;\n      _flashState.deactivated = null;\n    }\n  };\n  /**\n * Map the data format names of the \"clipData\" to Flash-friendly names.\n *\n * @returns A new transformed object.\n * @private\n */\n  var _mapClipDataToFlash = function(clipData) {\n    var newClipData = {}, formatMap = {};\n    if (!(typeof clipData === \"object\" && clipData)) {\n      return;\n    }\n    for (var dataFormat in clipData) {\n      if (dataFormat && _hasOwn.call(clipData, dataFormat) && typeof clipData[dataFormat] === \"string\" && clipData[dataFormat]) {\n        switch (dataFormat.toLowerCase()) {\n         case \"text/plain\":\n         case \"text\":\n         case \"air:text\":\n         case \"flash:text\":\n          newClipData.text = clipData[dataFormat];\n          formatMap.text = dataFormat;\n          break;\n\n         case \"text/html\":\n         case \"html\":\n         case \"air:html\":\n         case \"flash:html\":\n          newClipData.html = clipData[dataFormat];\n          formatMap.html = dataFormat;\n          break;\n\n         case \"application/rtf\":\n         case \"text/rtf\":\n         case \"rtf\":\n         case \"richtext\":\n         case \"air:rtf\":\n         case \"flash:rtf\":\n          newClipData.rtf = clipData[dataFormat];\n          formatMap.rtf = dataFormat;\n          break;\n\n         default:\n          break;\n        }\n      }\n    }\n    return {\n      data: newClipData,\n      formatMap: formatMap\n    };\n  };\n  /**\n * Map the data format names from Flash-friendly names back to their original \"clipData\" names (via a format mapping).\n *\n * @returns A new transformed object.\n * @private\n */\n  var _mapClipResultsFromFlash = function(clipResults, formatMap) {\n    if (!(typeof clipResults === \"object\" && clipResults && typeof formatMap === \"object\" && formatMap)) {\n      return clipResults;\n    }\n    var newResults = {};\n    for (var prop in clipResults) {\n      if (_hasOwn.call(clipResults, prop)) {\n        if (prop !== \"success\" && prop !== \"data\") {\n          newResults[prop] = clipResults[prop];\n          continue;\n        }\n        newResults[prop] = {};\n        var tmpHash = clipResults[prop];\n        for (var dataFormat in tmpHash) {\n          if (dataFormat && _hasOwn.call(tmpHash, dataFormat) && _hasOwn.call(formatMap, dataFormat)) {\n            newResults[prop][formatMap[dataFormat]] = tmpHash[dataFormat];\n          }\n        }\n      }\n    }\n    return newResults;\n  };\n  /**\n * Will look at a path, and will create a \"?noCache={time}\" or \"&noCache={time}\"\n * query param string to return. Does NOT append that string to the original path.\n * This is useful because ExternalInterface often breaks when a Flash SWF is cached.\n *\n * @returns The `noCache` query param with necessary \"?\"/\"&\" prefix.\n * @private\n */\n  var _cacheBust = function(path, options) {\n    var cacheBust = options == null || options && options.cacheBust === true;\n    if (cacheBust) {\n      return (path.indexOf(\"?\") === -1 ? \"?\" : \"&\") + \"noCache=\" + _now();\n    } else {\n      return \"\";\n    }\n  };\n  /**\n * Creates a query string for the FlashVars param.\n * Does NOT include the cache-busting query param.\n *\n * @returns FlashVars query string\n * @private\n */\n  var _vars = function(options) {\n    var i, len, domain, domains, str = \"\", trustedOriginsExpanded = [];\n    if (options.trustedDomains) {\n      if (typeof options.trustedDomains === \"string\") {\n        domains = [ options.trustedDomains ];\n      } else if (typeof options.trustedDomains === \"object\" && \"length\" in options.trustedDomains) {\n        domains = options.trustedDomains;\n      }\n    }\n    if (domains && domains.length) {\n      for (i = 0, len = domains.length; i < len; i++) {\n        if (_hasOwn.call(domains, i) && domains[i] && typeof domains[i] === \"string\") {\n          domain = _extractDomain(domains[i]);\n          if (!domain) {\n            continue;\n          }\n          if (domain === \"*\") {\n            trustedOriginsExpanded.length = 0;\n            trustedOriginsExpanded.push(domain);\n            break;\n          }\n          trustedOriginsExpanded.push.apply(trustedOriginsExpanded, [ domain, \"//\" + domain, _window.location.protocol + \"//\" + domain ]);\n        }\n      }\n    }\n    if (trustedOriginsExpanded.length) {\n      str += \"trustedOrigins=\" + _encodeURIComponent(trustedOriginsExpanded.join(\",\"));\n    }\n    if (options.forceEnhancedClipboard === true) {\n      str += (str ? \"&\" : \"\") + \"forceEnhancedClipboard=true\";\n    }\n    if (typeof options.swfObjectId === \"string\" && options.swfObjectId) {\n      str += (str ? \"&\" : \"\") + \"swfObjectId=\" + _encodeURIComponent(options.swfObjectId);\n    }\n    return str;\n  };\n  /**\n * Extract the domain (e.g. \"github.com\") from an origin (e.g. \"https://github.com\") or\n * URL (e.g. \"https://github.com/zeroclipboard/zeroclipboard/\").\n *\n * @returns the domain\n * @private\n */\n  var _extractDomain = function(originOrUrl) {\n    if (originOrUrl == null || originOrUrl === \"\") {\n      return null;\n    }\n    originOrUrl = originOrUrl.replace(/^\\s+|\\s+$/g, \"\");\n    if (originOrUrl === \"\") {\n      return null;\n    }\n    var protocolIndex = originOrUrl.indexOf(\"//\");\n    originOrUrl = protocolIndex === -1 ? originOrUrl : originOrUrl.slice(protocolIndex + 2);\n    var pathIndex = originOrUrl.indexOf(\"/\");\n    originOrUrl = pathIndex === -1 ? originOrUrl : protocolIndex === -1 || pathIndex === 0 ? null : originOrUrl.slice(0, pathIndex);\n    if (originOrUrl && originOrUrl.slice(-4).toLowerCase() === \".swf\") {\n      return null;\n    }\n    return originOrUrl || null;\n  };\n  /**\n * Set `allowScriptAccess` based on `trustedDomains` and `window.location.host` vs. `swfPath`.\n *\n * @returns The appropriate script access level.\n * @private\n */\n  var _determineScriptAccess = function() {\n    var _extractAllDomains = function(origins) {\n      var i, len, tmp, resultsArray = [];\n      if (typeof origins === \"string\") {\n        origins = [ origins ];\n      }\n      if (!(typeof origins === \"object\" && origins && typeof origins.length === \"number\")) {\n        return resultsArray;\n      }\n      for (i = 0, len = origins.length; i < len; i++) {\n        if (_hasOwn.call(origins, i) && (tmp = _extractDomain(origins[i]))) {\n          if (tmp === \"*\") {\n            resultsArray.length = 0;\n            resultsArray.push(\"*\");\n            break;\n          }\n          if (resultsArray.indexOf(tmp) === -1) {\n            resultsArray.push(tmp);\n          }\n        }\n      }\n      return resultsArray;\n    };\n    return function(currentDomain, configOptions) {\n      var swfDomain = _extractDomain(configOptions.swfPath);\n      if (swfDomain === null) {\n        swfDomain = currentDomain;\n      }\n      var trustedDomains = _extractAllDomains(configOptions.trustedDomains);\n      var len = trustedDomains.length;\n      if (len > 0) {\n        if (len === 1 && trustedDomains[0] === \"*\") {\n          return \"always\";\n        }\n        if (trustedDomains.indexOf(currentDomain) !== -1) {\n          if (len === 1 && currentDomain === swfDomain) {\n            return \"sameDomain\";\n          }\n          return \"always\";\n        }\n      }\n      return \"never\";\n    };\n  }();\n  /**\n * Get the currently active/focused DOM element.\n *\n * @returns the currently active/focused element, or `null`\n * @private\n */\n  var _safeActiveElement = function() {\n    try {\n      return _document.activeElement;\n    } catch (err) {\n      return null;\n    }\n  };\n  /**\n * Add a class to an element, if it doesn't already have it.\n *\n * @returns The element, with its new class added.\n * @private\n */\n  var _addClass = function(element, value) {\n    if (!element || element.nodeType !== 1) {\n      return element;\n    }\n    if (element.classList) {\n      if (!element.classList.contains(value)) {\n        element.classList.add(value);\n      }\n      return element;\n    }\n    if (value && typeof value === \"string\") {\n      var classNames = (value || \"\").split(/\\s+/);\n      if (element.nodeType === 1) {\n        if (!element.className) {\n          element.className = value;\n        } else {\n          var className = \" \" + element.className + \" \", setClass = element.className;\n          for (var c = 0, cl = classNames.length; c < cl; c++) {\n            if (className.indexOf(\" \" + classNames[c] + \" \") < 0) {\n              setClass += \" \" + classNames[c];\n            }\n          }\n          element.className = setClass.replace(/^\\s+|\\s+$/g, \"\");\n        }\n      }\n    }\n    return element;\n  };\n  /**\n * Remove a class from an element, if it has it.\n *\n * @returns The element, with its class removed.\n * @private\n */\n  var _removeClass = function(element, value) {\n    if (!element || element.nodeType !== 1) {\n      return element;\n    }\n    if (element.classList) {\n      if (element.classList.contains(value)) {\n        element.classList.remove(value);\n      }\n      return element;\n    }\n    if (typeof value === \"string\" && value) {\n      var classNames = value.split(/\\s+/);\n      if (element.nodeType === 1 && element.className) {\n        var className = (\" \" + element.className + \" \").replace(/[\\n\\t]/g, \" \");\n        for (var c = 0, cl = classNames.length; c < cl; c++) {\n          className = className.replace(\" \" + classNames[c] + \" \", \" \");\n        }\n        element.className = className.replace(/^\\s+|\\s+$/g, \"\");\n      }\n    }\n    return element;\n  };\n  /**\n * Attempt to interpret the element's CSS styling. If `prop` is `\"cursor\"`,\n * then we assume that it should be a hand (\"pointer\") cursor if the element\n * is an anchor element (\"a\" tag).\n *\n * @returns The computed style property.\n * @private\n */\n  var _getStyle = function(el, prop) {\n    var value = _window.getComputedStyle(el, null).getPropertyValue(prop);\n    if (prop === \"cursor\") {\n      if (!value || value === \"auto\") {\n        if (el.nodeName === \"A\") {\n          return \"pointer\";\n        }\n      }\n    }\n    return value;\n  };\n  /**\n * Get the zoom factor of the browser. Always returns `1.0`, except at\n * non-default zoom levels in IE<8 and some older versions of WebKit.\n *\n * @returns Floating unit percentage of the zoom factor (e.g. 150% = `1.5`).\n * @private\n */\n  var _getZoomFactor = function() {\n    var rect, physicalWidth, logicalWidth, zoomFactor = 1;\n    if (typeof _document.body.getBoundingClientRect === \"function\") {\n      rect = _document.body.getBoundingClientRect();\n      physicalWidth = rect.right - rect.left;\n      logicalWidth = _document.body.offsetWidth;\n      zoomFactor = _round(physicalWidth / logicalWidth * 100) / 100;\n    }\n    return zoomFactor;\n  };\n  /**\n * Get the DOM positioning info of an element.\n *\n * @returns Object containing the element's position, width, and height.\n * @private\n */\n  var _getDOMObjectPosition = function(obj) {\n    var info = {\n      left: 0,\n      top: 0,\n      width: 0,\n      height: 0\n    };\n    if (obj.getBoundingClientRect) {\n      var rect = obj.getBoundingClientRect();\n      var pageXOffset, pageYOffset, zoomFactor;\n      if (\"pageXOffset\" in _window && \"pageYOffset\" in _window) {\n        pageXOffset = _window.pageXOffset;\n        pageYOffset = _window.pageYOffset;\n      } else {\n        zoomFactor = _getZoomFactor();\n        pageXOffset = _round(_document.documentElement.scrollLeft / zoomFactor);\n        pageYOffset = _round(_document.documentElement.scrollTop / zoomFactor);\n      }\n      var leftBorderWidth = _document.documentElement.clientLeft || 0;\n      var topBorderWidth = _document.documentElement.clientTop || 0;\n      info.left = rect.left + pageXOffset - leftBorderWidth;\n      info.top = rect.top + pageYOffset - topBorderWidth;\n      info.width = \"width\" in rect ? rect.width : rect.right - rect.left;\n      info.height = \"height\" in rect ? rect.height : rect.bottom - rect.top;\n    }\n    return info;\n  };\n  /**\n * Reposition the Flash object to cover the currently activated element.\n *\n * @returns `undefined`\n * @private\n */\n  var _reposition = function() {\n    var htmlBridge;\n    if (_currentElement && (htmlBridge = _getHtmlBridge(_flashState.bridge))) {\n      var pos = _getDOMObjectPosition(_currentElement);\n      _extend(htmlBridge.style, {\n        width: pos.width + \"px\",\n        height: pos.height + \"px\",\n        top: pos.top + \"px\",\n        left: pos.left + \"px\",\n        zIndex: \"\" + _getSafeZIndex(_globalConfig.zIndex)\n      });\n    }\n  };\n  /**\n * Sends a signal to the Flash object to display the hand cursor if `true`.\n *\n * @returns `undefined`\n * @private\n */\n  var _setHandCursor = function(enabled) {\n    if (_flashState.ready === true) {\n      if (_flashState.bridge && typeof _flashState.bridge.setHandCursor === \"function\") {\n        _flashState.bridge.setHandCursor(enabled);\n      } else {\n        _flashState.ready = false;\n      }\n    }\n  };\n  /**\n * Get a safe value for `zIndex`\n *\n * @returns an integer, or \"auto\"\n * @private\n */\n  var _getSafeZIndex = function(val) {\n    if (/^(?:auto|inherit)$/.test(val)) {\n      return val;\n    }\n    var zIndex;\n    if (typeof val === \"number\" && !_isNaN(val)) {\n      zIndex = val;\n    } else if (typeof val === \"string\") {\n      zIndex = _getSafeZIndex(_parseInt(val, 10));\n    }\n    return typeof zIndex === \"number\" ? zIndex : \"auto\";\n  };\n  /**\n * Detect the Flash Player status, version, and plugin type.\n *\n * @see {@link https://code.google.com/p/doctype-mirror/wiki/ArticleDetectFlash#The_code}\n * @see {@link http://stackoverflow.com/questions/12866060/detecting-pepper-ppapi-flash-with-javascript}\n *\n * @returns `undefined`\n * @private\n */\n  var _detectFlashSupport = function(ActiveXObject) {\n    var plugin, ax, mimeType, hasFlash = false, isActiveX = false, isPPAPI = false, flashVersion = \"\";\n    /**\n   * Derived from Apple's suggested sniffer.\n   * @param {String} desc e.g. \"Shockwave Flash 7.0 r61\"\n   * @returns {String} \"7.0.61\"\n   * @private\n   */\n    function parseFlashVersion(desc) {\n      var matches = desc.match(/[\\d]+/g);\n      matches.length = 3;\n      return matches.join(\".\");\n    }\n    function isPepperFlash(flashPlayerFileName) {\n      return !!flashPlayerFileName && (flashPlayerFileName = flashPlayerFileName.toLowerCase()) && (/^(pepflashplayer\\.dll|libpepflashplayer\\.so|pepperflashplayer\\.plugin)$/.test(flashPlayerFileName) || flashPlayerFileName.slice(-13) === \"chrome.plugin\");\n    }\n    function inspectPlugin(plugin) {\n      if (plugin) {\n        hasFlash = true;\n        if (plugin.version) {\n          flashVersion = parseFlashVersion(plugin.version);\n        }\n        if (!flashVersion && plugin.description) {\n          flashVersion = parseFlashVersion(plugin.description);\n        }\n        if (plugin.filename) {\n          isPPAPI = isPepperFlash(plugin.filename);\n        }\n      }\n    }\n    if (_navigator.plugins && _navigator.plugins.length) {\n      plugin = _navigator.plugins[\"Shockwave Flash\"];\n      inspectPlugin(plugin);\n      if (_navigator.plugins[\"Shockwave Flash 2.0\"]) {\n        hasFlash = true;\n        flashVersion = \"2.0.0.11\";\n      }\n    } else if (_navigator.mimeTypes && _navigator.mimeTypes.length) {\n      mimeType = _navigator.mimeTypes[\"application/x-shockwave-flash\"];\n      plugin = mimeType && mimeType.enabledPlugin;\n      inspectPlugin(plugin);\n    } else if (typeof ActiveXObject !== \"undefined\") {\n      isActiveX = true;\n      try {\n        ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.7\");\n        hasFlash = true;\n        flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\n      } catch (e1) {\n        try {\n          ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.6\");\n          hasFlash = true;\n          flashVersion = \"6.0.21\";\n        } catch (e2) {\n          try {\n            ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\");\n            hasFlash = true;\n            flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\n          } catch (e3) {\n            isActiveX = false;\n          }\n        }\n      }\n    }\n    _flashState.disabled = hasFlash !== true;\n    _flashState.outdated = flashVersion && _parseFloat(flashVersion) < _parseFloat(_minimumFlashVersion);\n    _flashState.version = flashVersion || \"0.0.0\";\n    _flashState.pluginType = isPPAPI ? \"pepper\" : isActiveX ? \"activex\" : hasFlash ? \"netscape\" : \"unknown\";\n  };\n  /**\n * Invoke the Flash detection algorithms immediately upon inclusion so we're not waiting later.\n */\n  _detectFlashSupport(_ActiveXObject);\n  /**\n * A shell constructor for `ZeroClipboard` client instances.\n *\n * @constructor\n */\n  var ZeroClipboard = function() {\n    if (!(this instanceof ZeroClipboard)) {\n      return new ZeroClipboard();\n    }\n    if (typeof ZeroClipboard._createClient === \"function\") {\n      ZeroClipboard._createClient.apply(this, _args(arguments));\n    }\n  };\n  /**\n * The ZeroClipboard library's version number.\n *\n * @static\n * @readonly\n * @property {string}\n */\n  _defineProperty(ZeroClipboard, \"version\", {\n    value: \"2.1.6\",\n    writable: false,\n    configurable: true,\n    enumerable: true\n  });\n  /**\n * Update or get a copy of the ZeroClipboard global configuration.\n * Returns a copy of the current/updated configuration.\n *\n * @returns Object\n * @static\n */\n  ZeroClipboard.config = function() {\n    return _config.apply(this, _args(arguments));\n  };\n  /**\n * Diagnostic method that describes the state of the browser, Flash Player, and ZeroClipboard.\n *\n * @returns Object\n * @static\n */\n  ZeroClipboard.state = function() {\n    return _state.apply(this, _args(arguments));\n  };\n  /**\n * Check if Flash is unusable for any reason: disabled, outdated, deactivated, etc.\n *\n * @returns Boolean\n * @static\n */\n  ZeroClipboard.isFlashUnusable = function() {\n    return _isFlashUnusable.apply(this, _args(arguments));\n  };\n  /**\n * Register an event listener.\n *\n * @returns `ZeroClipboard`\n * @static\n */\n  ZeroClipboard.on = function() {\n    return _on.apply(this, _args(arguments));\n  };\n  /**\n * Unregister an event listener.\n * If no `listener` function/object is provided, it will unregister all listeners for the provided `eventType`.\n * If no `eventType` is provided, it will unregister all listeners for every event type.\n *\n * @returns `ZeroClipboard`\n * @static\n */\n  ZeroClipboard.off = function() {\n    return _off.apply(this, _args(arguments));\n  };\n  /**\n * Retrieve event listeners for an `eventType`.\n * If no `eventType` is provided, it will retrieve all listeners for every event type.\n *\n * @returns array of listeners for the `eventType`; if no `eventType`, then a map/hash object of listeners for all event types; or `null`\n */\n  ZeroClipboard.handlers = function() {\n    return _listeners.apply(this, _args(arguments));\n  };\n  /**\n * Event emission receiver from the Flash object, forwarding to any registered JavaScript event listeners.\n *\n * @returns For the \"copy\" event, returns the Flash-friendly \"clipData\" object; otherwise `undefined`.\n * @static\n */\n  ZeroClipboard.emit = function() {\n    return _emit.apply(this, _args(arguments));\n  };\n  /**\n * Create and embed the Flash object.\n *\n * @returns The Flash object\n * @static\n */\n  ZeroClipboard.create = function() {\n    return _create.apply(this, _args(arguments));\n  };\n  /**\n * Self-destruct and clean up everything, including the embedded Flash object.\n *\n * @returns `undefined`\n * @static\n */\n  ZeroClipboard.destroy = function() {\n    return _destroy.apply(this, _args(arguments));\n  };\n  /**\n * Set the pending data for clipboard injection.\n *\n * @returns `undefined`\n * @static\n */\n  ZeroClipboard.setData = function() {\n    return _setData.apply(this, _args(arguments));\n  };\n  /**\n * Clear the pending data for clipboard injection.\n * If no `format` is provided, all pending data formats will be cleared.\n *\n * @returns `undefined`\n * @static\n */\n  ZeroClipboard.clearData = function() {\n    return _clearData.apply(this, _args(arguments));\n  };\n  /**\n * Get a copy of the pending data for clipboard injection.\n * If no `format` is provided, a copy of ALL pending data formats will be returned.\n *\n * @returns `String` or `Object`\n * @static\n */\n  ZeroClipboard.getData = function() {\n    return _getData.apply(this, _args(arguments));\n  };\n  /**\n * Sets the current HTML object that the Flash object should overlay. This will put the global\n * Flash object on top of the current element; depending on the setup, this may also set the\n * pending clipboard text data as well as the Flash object's wrapping element's title attribute\n * based on the underlying HTML element and ZeroClipboard configuration.\n *\n * @returns `undefined`\n * @static\n */\n  ZeroClipboard.focus = ZeroClipboard.activate = function() {\n    return _focus.apply(this, _args(arguments));\n  };\n  /**\n * Un-overlays the Flash object. This will put the global Flash object off-screen; depending on\n * the setup, this may also unset the Flash object's wrapping element's title attribute based on\n * the underlying HTML element and ZeroClipboard configuration.\n *\n * @returns `undefined`\n * @static\n */\n  ZeroClipboard.blur = ZeroClipboard.deactivate = function() {\n    return _blur.apply(this, _args(arguments));\n  };\n  /**\n * Returns the currently focused/\"activated\" HTML element that the Flash object is wrapping.\n *\n * @returns `HTMLElement` or `null`\n * @static\n */\n  ZeroClipboard.activeElement = function() {\n    return _activeElement.apply(this, _args(arguments));\n  };\n  /**\n * Keep track of the ZeroClipboard client instance counter.\n */\n  var _clientIdCounter = 0;\n  /**\n * Keep track of the state of the client instances.\n *\n * Entry structure:\n *   _clientMeta[client.id] = {\n *     instance: client,\n *     elements: [],\n *     handlers: {}\n *   };\n */\n  var _clientMeta = {};\n  /**\n * Keep track of the ZeroClipboard clipped elements counter.\n */\n  var _elementIdCounter = 0;\n  /**\n * Keep track of the state of the clipped element relationships to clients.\n *\n * Entry structure:\n *   _elementMeta[element.zcClippingId] = [client1.id, client2.id];\n */\n  var _elementMeta = {};\n  /**\n * Keep track of the state of the mouse event handlers for clipped elements.\n *\n * Entry structure:\n *   _mouseHandlers[element.zcClippingId] = {\n *     mouseover:  function(event) {},\n *     mouseout:   function(event) {},\n *     mouseenter: function(event) {},\n *     mouseleave: function(event) {},\n *     mousemove:  function(event) {}\n *   };\n */\n  var _mouseHandlers = {};\n  /**\n * Extending the ZeroClipboard configuration defaults for the Client module.\n */\n  _extend(_globalConfig, {\n    autoActivate: true\n  });\n  /**\n * The real constructor for `ZeroClipboard` client instances.\n * @private\n */\n  var _clientConstructor = function(elements) {\n    var client = this;\n    client.id = \"\" + _clientIdCounter++;\n    _clientMeta[client.id] = {\n      instance: client,\n      elements: [],\n      handlers: {}\n    };\n    if (elements) {\n      client.clip(elements);\n    }\n    ZeroClipboard.on(\"*\", function(event) {\n      return client.emit(event);\n    });\n    ZeroClipboard.on(\"destroy\", function() {\n      client.destroy();\n    });\n    ZeroClipboard.create();\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.on`.\n * @private\n */\n  var _clientOn = function(eventType, listener) {\n    var i, len, events, added = {}, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (typeof eventType === \"string\" && eventType) {\n      events = eventType.toLowerCase().split(/\\s+/);\n    } else if (typeof eventType === \"object\" && eventType && typeof listener === \"undefined\") {\n      for (i in eventType) {\n        if (_hasOwn.call(eventType, i) && typeof i === \"string\" && i && typeof eventType[i] === \"function\") {\n          this.on(i, eventType[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventType = events[i].replace(/^on/, \"\");\n        added[eventType] = true;\n        if (!handlers[eventType]) {\n          handlers[eventType] = [];\n        }\n        handlers[eventType].push(listener);\n      }\n      if (added.ready && _flashState.ready) {\n        this.emit({\n          type: \"ready\",\n          client: this\n        });\n      }\n      if (added.error) {\n        var errorTypes = [ \"disabled\", \"outdated\", \"unavailable\", \"deactivated\", \"overdue\" ];\n        for (i = 0, len = errorTypes.length; i < len; i++) {\n          if (_flashState[errorTypes[i]]) {\n            this.emit({\n              type: \"error\",\n              name: \"flash-\" + errorTypes[i],\n              client: this\n            });\n            break;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.off`.\n * @private\n */\n  var _clientOff = function(eventType, listener) {\n    var i, len, foundIndex, events, perEventHandlers, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (arguments.length === 0) {\n      events = _keys(handlers);\n    } else if (typeof eventType === \"string\" && eventType) {\n      events = eventType.split(/\\s+/);\n    } else if (typeof eventType === \"object\" && eventType && typeof listener === \"undefined\") {\n      for (i in eventType) {\n        if (_hasOwn.call(eventType, i) && typeof i === \"string\" && i && typeof eventType[i] === \"function\") {\n          this.off(i, eventType[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventType = events[i].toLowerCase().replace(/^on/, \"\");\n        perEventHandlers = handlers[eventType];\n        if (perEventHandlers && perEventHandlers.length) {\n          if (listener) {\n            foundIndex = perEventHandlers.indexOf(listener);\n            while (foundIndex !== -1) {\n              perEventHandlers.splice(foundIndex, 1);\n              foundIndex = perEventHandlers.indexOf(listener, foundIndex);\n            }\n          } else {\n            perEventHandlers.length = 0;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.handlers`.\n * @private\n */\n  var _clientListeners = function(eventType) {\n    var copy = null, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (handlers) {\n      if (typeof eventType === \"string\" && eventType) {\n        copy = handlers[eventType] ? handlers[eventType].slice(0) : [];\n      } else {\n        copy = _deepCopy(handlers);\n      }\n    }\n    return copy;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.emit`.\n * @private\n */\n  var _clientEmit = function(event) {\n    if (_clientShouldEmit.call(this, event)) {\n      if (typeof event === \"object\" && event && typeof event.type === \"string\" && event.type) {\n        event = _extend({}, event);\n      }\n      var eventCopy = _extend({}, _createEvent(event), {\n        client: this\n      });\n      _clientDispatchCallbacks.call(this, eventCopy);\n    }\n    return this;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.clip`.\n * @private\n */\n  var _clientClip = function(elements) {\n    elements = _prepClip(elements);\n    for (var i = 0; i < elements.length; i++) {\n      if (_hasOwn.call(elements, i) && elements[i] && elements[i].nodeType === 1) {\n        if (!elements[i].zcClippingId) {\n          elements[i].zcClippingId = \"zcClippingId_\" + _elementIdCounter++;\n          _elementMeta[elements[i].zcClippingId] = [ this.id ];\n          if (_globalConfig.autoActivate === true) {\n            _addMouseHandlers(elements[i]);\n          }\n        } else if (_elementMeta[elements[i].zcClippingId].indexOf(this.id) === -1) {\n          _elementMeta[elements[i].zcClippingId].push(this.id);\n        }\n        var clippedElements = _clientMeta[this.id] && _clientMeta[this.id].elements;\n        if (clippedElements.indexOf(elements[i]) === -1) {\n          clippedElements.push(elements[i]);\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.unclip`.\n * @private\n */\n  var _clientUnclip = function(elements) {\n    var meta = _clientMeta[this.id];\n    if (!meta) {\n      return this;\n    }\n    var clippedElements = meta.elements;\n    var arrayIndex;\n    if (typeof elements === \"undefined\") {\n      elements = clippedElements.slice(0);\n    } else {\n      elements = _prepClip(elements);\n    }\n    for (var i = elements.length; i--; ) {\n      if (_hasOwn.call(elements, i) && elements[i] && elements[i].nodeType === 1) {\n        arrayIndex = 0;\n        while ((arrayIndex = clippedElements.indexOf(elements[i], arrayIndex)) !== -1) {\n          clippedElements.splice(arrayIndex, 1);\n        }\n        var clientIds = _elementMeta[elements[i].zcClippingId];\n        if (clientIds) {\n          arrayIndex = 0;\n          while ((arrayIndex = clientIds.indexOf(this.id, arrayIndex)) !== -1) {\n            clientIds.splice(arrayIndex, 1);\n          }\n          if (clientIds.length === 0) {\n            if (_globalConfig.autoActivate === true) {\n              _removeMouseHandlers(elements[i]);\n            }\n            delete elements[i].zcClippingId;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.elements`.\n * @private\n */\n  var _clientElements = function() {\n    var meta = _clientMeta[this.id];\n    return meta && meta.elements ? meta.elements.slice(0) : [];\n  };\n  /**\n * The underlying implementation of `ZeroClipboard.Client.prototype.destroy`.\n * @private\n */\n  var _clientDestroy = function() {\n    this.unclip();\n    this.off();\n    delete _clientMeta[this.id];\n  };\n  /**\n * Inspect an Event to see if the Client (`this`) should honor it for emission.\n * @private\n */\n  var _clientShouldEmit = function(event) {\n    if (!(event && event.type)) {\n      return false;\n    }\n    if (event.client && event.client !== this) {\n      return false;\n    }\n    var clippedEls = _clientMeta[this.id] && _clientMeta[this.id].elements;\n    var hasClippedEls = !!clippedEls && clippedEls.length > 0;\n    var goodTarget = !event.target || hasClippedEls && clippedEls.indexOf(event.target) !== -1;\n    var goodRelTarget = event.relatedTarget && hasClippedEls && clippedEls.indexOf(event.relatedTarget) !== -1;\n    var goodClient = event.client && event.client === this;\n    if (!(goodTarget || goodRelTarget || goodClient)) {\n      return false;\n    }\n    return true;\n  };\n  /**\n * Handle the actual dispatching of events to a client instance.\n *\n * @returns `this`\n * @private\n */\n  var _clientDispatchCallbacks = function(event) {\n    if (!(typeof event === \"object\" && event && event.type)) {\n      return;\n    }\n    var async = _shouldPerformAsync(event);\n    var wildcardTypeHandlers = _clientMeta[this.id] && _clientMeta[this.id].handlers[\"*\"] || [];\n    var specificTypeHandlers = _clientMeta[this.id] && _clientMeta[this.id].handlers[event.type] || [];\n    var handlers = wildcardTypeHandlers.concat(specificTypeHandlers);\n    if (handlers && handlers.length) {\n      var i, len, func, context, eventCopy, originalContext = this;\n      for (i = 0, len = handlers.length; i < len; i++) {\n        func = handlers[i];\n        context = originalContext;\n        if (typeof func === \"string\" && typeof _window[func] === \"function\") {\n          func = _window[func];\n        }\n        if (typeof func === \"object\" && func && typeof func.handleEvent === \"function\") {\n          context = func;\n          func = func.handleEvent;\n        }\n        if (typeof func === \"function\") {\n          eventCopy = _extend({}, event);\n          _dispatchCallback(func, context, [ eventCopy ], async);\n        }\n      }\n    }\n    return this;\n  };\n  /**\n * Prepares the elements for clipping/unclipping.\n *\n * @returns An Array of elements.\n * @private\n */\n  var _prepClip = function(elements) {\n    if (typeof elements === \"string\") {\n      elements = [];\n    }\n    return typeof elements.length !== \"number\" ? [ elements ] : elements;\n  };\n  /**\n * Add a `mouseover` handler function for a clipped element.\n *\n * @returns `undefined`\n * @private\n */\n  var _addMouseHandlers = function(element) {\n    if (!(element && element.nodeType === 1)) {\n      return;\n    }\n    var _suppressMouseEvents = function(event) {\n      if (!(event || (event = _window.event))) {\n        return;\n      }\n      if (event._source !== \"js\") {\n        event.stopImmediatePropagation();\n        event.preventDefault();\n      }\n      delete event._source;\n    };\n    var _elementMouseOver = function(event) {\n      if (!(event || (event = _window.event))) {\n        return;\n      }\n      _suppressMouseEvents(event);\n      ZeroClipboard.focus(element);\n    };\n    element.addEventListener(\"mouseover\", _elementMouseOver, false);\n    element.addEventListener(\"mouseout\", _suppressMouseEvents, false);\n    element.addEventListener(\"mouseenter\", _suppressMouseEvents, false);\n    element.addEventListener(\"mouseleave\", _suppressMouseEvents, false);\n    element.addEventListener(\"mousemove\", _suppressMouseEvents, false);\n    _mouseHandlers[element.zcClippingId] = {\n      mouseover: _elementMouseOver,\n      mouseout: _suppressMouseEvents,\n      mouseenter: _suppressMouseEvents,\n      mouseleave: _suppressMouseEvents,\n      mousemove: _suppressMouseEvents\n    };\n  };\n  /**\n * Remove a `mouseover` handler function for a clipped element.\n *\n * @returns `undefined`\n * @private\n */\n  var _removeMouseHandlers = function(element) {\n    if (!(element && element.nodeType === 1)) {\n      return;\n    }\n    var mouseHandlers = _mouseHandlers[element.zcClippingId];\n    if (!(typeof mouseHandlers === \"object\" && mouseHandlers)) {\n      return;\n    }\n    var key, val, mouseEvents = [ \"move\", \"leave\", \"enter\", \"out\", \"over\" ];\n    for (var i = 0, len = mouseEvents.length; i < len; i++) {\n      key = \"mouse\" + mouseEvents[i];\n      val = mouseHandlers[key];\n      if (typeof val === \"function\") {\n        element.removeEventListener(key, val, false);\n      }\n    }\n    delete _mouseHandlers[element.zcClippingId];\n  };\n  /**\n * Creates a new ZeroClipboard client instance.\n * Optionally, auto-`clip` an element or collection of elements.\n *\n * @constructor\n */\n  ZeroClipboard._createClient = function() {\n    _clientConstructor.apply(this, _args(arguments));\n  };\n  /**\n * Register an event listener to the client.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.on = function() {\n    return _clientOn.apply(this, _args(arguments));\n  };\n  /**\n * Unregister an event handler from the client.\n * If no `listener` function/object is provided, it will unregister all handlers for the provided `eventType`.\n * If no `eventType` is provided, it will unregister all handlers for every event type.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.off = function() {\n    return _clientOff.apply(this, _args(arguments));\n  };\n  /**\n * Retrieve event listeners for an `eventType` from the client.\n * If no `eventType` is provided, it will retrieve all listeners for every event type.\n *\n * @returns array of listeners for the `eventType`; if no `eventType`, then a map/hash object of listeners for all event types; or `null`\n */\n  ZeroClipboard.prototype.handlers = function() {\n    return _clientListeners.apply(this, _args(arguments));\n  };\n  /**\n * Event emission receiver from the Flash object for this client's registered JavaScript event listeners.\n *\n * @returns For the \"copy\" event, returns the Flash-friendly \"clipData\" object; otherwise `undefined`.\n */\n  ZeroClipboard.prototype.emit = function() {\n    return _clientEmit.apply(this, _args(arguments));\n  };\n  /**\n * Register clipboard actions for new element(s) to the client.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.clip = function() {\n    return _clientClip.apply(this, _args(arguments));\n  };\n  /**\n * Unregister the clipboard actions of previously registered element(s) on the page.\n * If no elements are provided, ALL registered elements will be unregistered.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.unclip = function() {\n    return _clientUnclip.apply(this, _args(arguments));\n  };\n  /**\n * Get all of the elements to which this client is clipped.\n *\n * @returns array of clipped elements\n */\n  ZeroClipboard.prototype.elements = function() {\n    return _clientElements.apply(this, _args(arguments));\n  };\n  /**\n * Self-destruct and clean up everything for a single client.\n * This will NOT destroy the embedded Flash object.\n *\n * @returns `undefined`\n */\n  ZeroClipboard.prototype.destroy = function() {\n    return _clientDestroy.apply(this, _args(arguments));\n  };\n  /**\n * Stores the pending plain text to inject into the clipboard.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.setText = function(text) {\n    ZeroClipboard.setData(\"text/plain\", text);\n    return this;\n  };\n  /**\n * Stores the pending HTML text to inject into the clipboard.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.setHtml = function(html) {\n    ZeroClipboard.setData(\"text/html\", html);\n    return this;\n  };\n  /**\n * Stores the pending rich text (RTF) to inject into the clipboard.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.setRichText = function(richText) {\n    ZeroClipboard.setData(\"application/rtf\", richText);\n    return this;\n  };\n  /**\n * Stores the pending data to inject into the clipboard.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.setData = function() {\n    ZeroClipboard.setData.apply(this, _args(arguments));\n    return this;\n  };\n  /**\n * Clears the pending data to inject into the clipboard.\n * If no `format` is provided, all pending data formats will be cleared.\n *\n * @returns `this`\n */\n  ZeroClipboard.prototype.clearData = function() {\n    ZeroClipboard.clearData.apply(this, _args(arguments));\n    return this;\n  };\n  /**\n * Gets a copy of the pending data to inject into the clipboard.\n * If no `format` is provided, a copy of ALL pending data formats will be returned.\n *\n * @returns `String` or `Object`\n */\n  ZeroClipboard.prototype.getData = function() {\n    return ZeroClipboard.getData.apply(this, _args(arguments));\n  };\n  if (typeof define === \"function\" && define.amd) {\n    define(function() {\n      return ZeroClipboard;\n    });\n  } else if (typeof module === \"object\" && module && typeof module.exports === \"object\" && module.exports) {\n    module.exports = ZeroClipboard;\n  } else {\n    window.ZeroClipboard = ZeroClipboard;\n  }\n})(function() {\n  return this || window;\n}());"]}
\ No newline at end of file
diff --git a/assets/javascripts/plugins/bootstrap_switch.js b/assets/javascripts/plugins/bootstrap_switch.js
old mode 100644
new mode 100755
diff --git a/assets/javascripts/plugins/bootstrap_tooltip.js b/assets/javascripts/plugins/bootstrap_tooltip.js
old mode 100644
new mode 100755
diff --git a/assets/javascripts/plugins/bootstrap_transitions.js b/assets/javascripts/plugins/bootstrap_transitions.js
old mode 100644
new mode 100755
diff --git a/assets/javascripts/plugins/tag_it.js b/assets/javascripts/plugins/tag_it.js
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_alert.css b/assets/stylesheets/bootstrap/bootstrap_alert.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_custom.css b/assets/stylesheets/bootstrap/bootstrap_custom.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_label.css b/assets/stylesheets/bootstrap/bootstrap_label.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_switch.css b/assets/stylesheets/bootstrap/bootstrap_switch.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_tables.css b/assets/stylesheets/bootstrap/bootstrap_tables.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/bootstrap_tooltip.css b/assets/stylesheets/bootstrap/bootstrap_tooltip.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_flat_0_aaaaaa_40x100.png b/assets/stylesheets/bootstrap/images/ui-bg_flat_0_aaaaaa_40x100.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_glass_55_fbf9ee_1x400.png b/assets/stylesheets/bootstrap/images/ui-bg_glass_55_fbf9ee_1x400.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_glass_65_ffffff_1x400.png b/assets/stylesheets/bootstrap/images/ui-bg_glass_65_ffffff_1x400.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_glass_75_dadada_1x400.png b/assets/stylesheets/bootstrap/images/ui-bg_glass_75_dadada_1x400.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_glass_75_e6e6e6_1x400.png b/assets/stylesheets/bootstrap/images/ui-bg_glass_75_e6e6e6_1x400.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_glass_75_ffffff_1x400.png b/assets/stylesheets/bootstrap/images/ui-bg_glass_75_ffffff_1x400.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/assets/stylesheets/bootstrap/images/ui-bg_highlight-soft_75_cccccc_1x100.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-bg_inset-soft_95_fef1ec_1x100.png b/assets/stylesheets/bootstrap/images/ui-bg_inset-soft_95_fef1ec_1x100.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_222222_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_222222_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_2e83ff_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_2e83ff_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_454545_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_454545_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_888888_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_888888_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_cd0a0a_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_cd0a0a_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/bootstrap/images/ui-icons_f6cf3b_256x240.png b/assets/stylesheets/bootstrap/images/ui-icons_f6cf3b_256x240.png
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/font_awesome.css b/assets/stylesheets/font_awesome.css
old mode 100644
new mode 100755
diff --git a/assets/stylesheets/tag_it/jquery_tagit.css b/assets/stylesheets/tag_it/jquery_tagit.css
old mode 100644
new mode 100755
diff --git a/bin/.placeholder b/bin/.placeholder
deleted file mode 100644
index e69de29..0000000
diff --git a/bin/gitolite_admin_ssh b/bin/gitolite_admin_ssh
new file mode 100755
index 0000000..dea6fc2
--- /dev/null
+++ b/bin/gitolite_admin_ssh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec ssh -T -o BatchMode=yes -o StrictHostKeyChecking=no -p 22 -i /opt/redmine/plugins/redmine_git_hosting/ssh_keys/redmine_gitolite_admin_id_rsa "$@"
diff --git a/bin/run_git_cmd_as_gitolite_user b/bin/run_git_cmd_as_gitolite_user
new file mode 100755
index 0000000..b5dc15b
--- /dev/null
+++ b/bin/run_git_cmd_as_gitolite_user
@@ -0,0 +1,9 @@
+#!/bin/sh
+if [ "$(whoami)" = "git" ] ; then
+  cmd=$(printf "\"%s\" " "$@")
+  cd ~
+  eval "git $cmd"
+else
+  cmd=$(printf "\"%s\" " "$@")
+  sudo -n -u git -i eval "git $cmd"
+fi
diff --git a/bin/run_shell_cmd_as_gitolite_user b/bin/run_shell_cmd_as_gitolite_user
new file mode 100755
index 0000000..0f26889
--- /dev/null
+++ b/bin/run_shell_cmd_as_gitolite_user
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+my $command = join(" ", @ARGV);
+
+my $user = `whoami`;
+chomp $user;
+if ($user eq "git")
+{
+  exec("cd ~ ; $command");
+}
+else
+{
+  $command =~ s/\\/\\\\/g;
+  $command =~ s/"/\\"/g;
+  exec("sudo -n -u git -i eval \"$command\"");
+}
diff --git a/config/sidekiq-worker-dev.yml b/config/sidekiq-worker-dev.yml
old mode 100644
new mode 100755
diff --git a/config/sidekiq-worker.yml b/config/sidekiq-worker.yml
old mode 100644
new mode 100755
diff --git a/contrib/scripts/puma.rb b/contrib/scripts/puma.rb
new file mode 100644
index 0000000..00f01dd
--- /dev/null
+++ b/contrib/scripts/puma.rb
@@ -0,0 +1,7 @@
+stdout_redirect '/home/redmine/redmine/log/puma.stderr.log', '/home/redmine/redmine/log/puma.stdout.log'
+
+on_worker_boot do
+  ActiveSupport.on_load(:active_record) do
+    ActiveRecord::Base.establish_connection
+  end
+end
diff --git a/contrib/scripts/redmine b/contrib/scripts/redmine
new file mode 100644
index 0000000..e84b712
--- /dev/null
+++ b/contrib/scripts/redmine
@@ -0,0 +1,36 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          redmine
+# Required-Start:    $local_fs $remote_fs $network $mysql $named
+# Required-Stop:     $local_fs $remote_fs $network $mysql $named
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Redmine projects manager
+# Description:       This file should be used to start and stop Redmine.
+### END INIT INFO
+
+[ -f /etc/default/rcS ] && . /etc/default/rcS
+. /lib/lsb/init-functions
+
+REDMINE_USER="redmine"
+
+WEBSERVER="server_puma"
+WORKER1="sidekiq_git_hosting"
+
+case "$1" in
+  start)
+    su - $REDMINE_USER -c "${WEBSERVER}.sh start"
+    su - $REDMINE_USER -c "${WORKER1}.sh start"
+  ;;
+  stop)
+    su - $REDMINE_USER -c "${WEBSERVER}.sh stop"
+    su - $REDMINE_USER -c "${WORKER1}.sh stop"
+  ;;
+  restart)
+    su - $REDMINE_USER -c "${WEBSERVER}.sh restart"
+    su - $REDMINE_USER -c "${WORKER1}.sh restart"
+  ;;
+  *)
+    echo "Usage : /etc/init.d/redmine {start|stop|restart}"
+  ;;
+esac
diff --git a/contrib/scripts/server_puma.sh b/contrib/scripts/server_puma.sh
new file mode 100755
index 0000000..3b6a6c9
--- /dev/null
+++ b/contrib/scripts/server_puma.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+# You should place this script in user's home bin dir like :
+# /home/redmine/bin/server_puma.sh
+#
+# Normally the user's bin directory should be in the PATH.
+# If not, add this in /home/redmine/.profile :
+#
+# ------------------>8
+# #set PATH so it includes user's private bin if it exists
+# if [ -d "$HOME/bin" ] ; then
+#   PATH="$HOME/bin:$PATH"
+# fi
+# ------------------>8
+#
+#
+# This script *must* be run by the Redmine user so
+# switch user *before* running the script :
+# root$ su - redmine
+#
+# Then :
+# redmine$ server_puma.sh start
+# redmine$ server_puma.sh stop
+# redmine$ server_puma.sh restart
+
+SERVER_NAME="redmine"
+
+RAILS_ENV="production"
+
+REDMINE_PATH="$HOME/redmine"
+CONFIG_FILE="$HOME/etc/puma.rb"
+
+PID_FILE="$REDMINE_PATH/tmp/pids/puma.pid"
+SOCK_FILE="$REDMINE_PATH/tmp/sockets/redmine.sock"
+
+BIND_URI="unix://$SOCK_FILE"
+
+THREADS="0:8"
+WORKERS=2
+
+function start () {
+  echo "Start Puma Server..."
+  puma --daemon --preload --bind $BIND_URI \
+       --environment $RAILS_ENV --dir $REDMINE_PATH \
+       --workers $WORKERS --threads $THREADS \
+       --pidfile $PID_FILE --tag $SERVER_NAME \
+       --config $CONFIG_FILE
+  echo "Done"
+}
+
+function stop () {
+  echo "Stop Puma Server..."
+  if [ -f $PID_FILE ] ; then
+    kill $(cat $PID_FILE) 2>/dev/null
+    rm -f $PID_FILE
+    rm -f $SOCK_FILE
+  fi
+  echo "Done"
+}
+
+case "$1" in
+  start)
+    start
+  ;;
+  stop)
+    stop
+  ;;
+  restart)
+    stop
+    start
+  ;;
+  *)
+    echo "Usage : server_puma.sh {start|stop|restart}"
+  ;;
+esac
diff --git a/contrib/scripts/sidekiq_git_hosting.sh b/contrib/scripts/sidekiq_git_hosting.sh
new file mode 100755
index 0000000..c0be9e2
--- /dev/null
+++ b/contrib/scripts/sidekiq_git_hosting.sh
@@ -0,0 +1,169 @@
+#!/bin/bash
+
+# You should place this script in user's home bin dir like :
+# /home/redmine/bin/sidekiq_git_hosting.sh
+#
+# Normally the user's bin directory should be in the PATH.
+# If not, add this in /home/redmine/.profile :
+#
+# ------------------>8
+# #set PATH so it includes user's private bin if it exists
+# if [ -d "$HOME/bin" ] ; then
+#   PATH="$HOME/bin:$PATH"
+# fi
+# ------------------>8
+#
+#
+# This script *must* be run by the Redmine user so
+# switch user *before* running the script :
+# root$ su - redmine
+#
+# Then :
+# redmine$ sidekiq_git_hosting.sh start
+# redmine$ sidekiq_git_hosting.sh stop
+# redmine$ sidekiq_git_hosting.sh restart
+
+# WORKER_NAME is used to identify the worker among the processus list
+# Example : sidekiq 3.2.1 redmine_git_hosting [0 of 1 busy]
+WORKER_NAME="redmine_git_hosting"
+
+# The Rails environment, default : production
+RAILS_ENV=${RAILS_ENV:-production}
+
+# The absolute path to Redmine
+REDMINE_PATH=${REDMINE_PATH:-$HOME/redmine}
+
+# The start detection timeout
+TIMEOUT=${TIMEOUT:-15}
+
+DESC="Sidekiq worker '$WORKER_NAME'"
+
+LOG_DIR="$REDMINE_PATH/log"
+PID_DIR="$REDMINE_PATH/tmp/pids"
+
+LOG_FILE="$LOG_DIR/worker_${WORKER_NAME}.log"
+PID_FILE="$PID_DIR/worker_${WORKER_NAME}.pid"
+
+# Do not change these values !
+# See here for more details :
+# https://github.com/jbox-web/redmine_git_hosting/wiki/Configuration-notes#sidekiq--concurrency
+CONCURRENCY=1
+QUEUE="redmine_git_hosting,1"
+
+if [ "$RAILS_ENV" = "production" ] ; then
+  DAEMON_OPTS="--daemon --logfile $LOG_FILE --pidfile $PID_FILE"
+else
+  DAEMON_OPTS=
+fi
+
+if [ ! -d $PID_DIR ] ; then
+  mkdir $PID_DIR
+fi
+
+
+RETVAL=0
+
+
+################################
+success() {
+  echo -e "\t\t[ \e[32mOK\e[0m ]"
+}
+
+
+failure() {
+  echo -e "\t\t[ \e[31mFailure\e[0m ]"
+}
+
+
+start () {
+  pid=$(get_pid)
+  if [ $pid -gt 1 ] ; then
+    echo "$DESC is already running (pid $pid)"
+    RETVAL=1
+    return $RETVAL
+  fi
+
+  echo -n "Starting $DESC ..."
+
+  sidekiq $DAEMON_OPTS --verbose --concurrency $CONCURRENCY \
+          --environment $RAILS_ENV --require $REDMINE_PATH \
+          --queue $QUEUE --tag $WORKER_NAME
+
+  if [ ! -z "$DAEMON_OPTS" ] ; then
+    for ((i=1; i<=TIMEOUT; i++)) ; do
+      pid=$(get_pid)
+      if [ $pid -gt 1 ] ; then
+        break
+      fi
+      echo -n '.' && sleep 1
+    done
+    echo -n " "
+
+    pid=$(get_pid)
+    if [ $pid -gt 1 ] ; then
+      success
+      RETVAL=0
+    else
+      failure
+      RETVAL=1
+    fi
+  fi
+}
+
+
+stop () {
+  echo -n "Shutting down $DESC ..."
+  kill $(cat $PID_FILE 2>/dev/null) >/dev/null 2>&1
+  RETVAL=$?
+  [ $RETVAL -eq 0 ] && success || failure
+  rm -f $PID_FILE >/dev/null 2>&1
+}
+
+
+status () {
+  # show status
+  pid=$(get_pid)
+  if [ $pid -gt 1 ] ; then
+    echo "$DESC is running (pid $pid)"
+  else
+    echo "$DESC is stopped"
+  fi
+  RETVAL=0
+}
+
+
+get_pid () {
+  # get status
+  pid=$(ps axo pid,command | grep sidekiq | grep $WORKER_NAME | awk '{print $1}')
+  if [ -z $pid ] ; then
+    rc=1
+  else
+    rc=$pid
+  fi
+  echo $rc
+}
+
+################################
+
+case "$1" in
+  start)
+    start
+  ;;
+  stop)
+    stop
+  ;;
+  restart)
+    stop
+    sleep 1
+    start
+  ;;
+  status)
+    status
+  ;;
+  *)
+    echo "Usage: $0 {start|stop|restart|status}"
+    exit 1
+  ;;
+esac
+
+exit $RETVAL
diff --git a/contrib/selinux/README b/contrib/selinux/README
old mode 100644
new mode 100755
diff --git a/contrib/selinux/redmine_git.fc b/contrib/selinux/redmine_git.fc
old mode 100644
new mode 100755
diff --git a/contrib/selinux/redmine_git.if b/contrib/selinux/redmine_git.if
old mode 100644
new mode 100755
diff --git a/contrib/selinux/redmine_git.pp b/contrib/selinux/redmine_git.pp
old mode 100644
new mode 100755
diff --git a/contrib/selinux/redmine_git.te b/contrib/selinux/redmine_git.te
old mode 100644
new mode 100755
diff --git a/contrib/travis/install_redmine.sh b/contrib/travis/install_redmine.sh
new file mode 100755
index 0000000..0cdb685
--- /dev/null
+++ b/contrib/travis/install_redmine.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+
+REDMINE_SOURCE_URL="http://www.redmine.org/releases"
+
+REDMINE_NAME="redmine-${REDMINE_VERSION}"
+REDMINE_PACKAGE="${REDMINE_NAME}.tar.gz"
+REDMINE_URL="${REDMINE_SOURCE_URL}/${REDMINE_PACKAGE}"
+
+GITHUB_USER=${GITHUB_USER:-jbox-web}
+GITHUB_PROJECT=${GITHUB_PROJECT:-redmine_git_hosting}
+GITHUB_SOURCE="${GITHUB_USER}/${GITHUB_PROJECT}"
+
+PLUGIN_PATH=${PLUGIN_PATH:-$GITHUB_SOURCE}
+PLUGIN_NAME=${PLUGIN_NAME:-$GITHUB_PROJECT}
+
+INSTALL_GITOLITE=${INSTALL_GITOLITE:-true}
+
+REDMINE_SIDEKIQ_PLUGIN="https://github.com/ogom/redmine_sidekiq.git"
+REDMINE_BOOTSTRAP_PLUGIN="https://github.com/jbox-web/redmine_bootstrap_kit.git"
+
+CURRENT_DIR=$(pwd)
+
+echo ""
+echo "######################"
+echo "REDMINE INSTALLATION SCRIPT"
+echo ""
+echo "REDMINE_VERSION : ${REDMINE_VERSION}"
+echo "REDMINE_URL     : ${REDMINE_URL}"
+echo "CURRENT_DIR     : ${CURRENT_DIR}"
+echo "GITHUB_SOURCE   : ${GITHUB_SOURCE}"
+echo "PLUGIN_PATH     : ${PLUGIN_PATH}"
+echo ""
+
+echo "#### GET TARBALL"
+wget "${REDMINE_URL}"
+echo "Done !"
+echo ""
+
+echo "#### EXTRACT IT"
+tar xf "${REDMINE_PACKAGE}"
+echo "Done !"
+echo ""
+
+echo "#### MOVE PLUGIN"
+mv "${PLUGIN_PATH}" "${REDMINE_NAME}/plugins"
+rmdir "${PLUGIN_PATH}"
+echo "Done !"
+echo ""
+
+echo "#### CREATE SYMLINK"
+ln -s "${REDMINE_NAME}" "redmine"
+echo "Done !"
+echo ""
+
+echo "#### INSTALL DATABASE FILE"
+if [ "$DATABASE_ADAPTER" == "mysql" ] ; then
+  echo "Type : mysql"
+  cp "redmine/plugins/${PLUGIN_NAME}/spec/database_mysql.yml" "redmine/config/database.yml"
+else
+  echo "Type : postgres"
+  cp "redmine/plugins/${PLUGIN_NAME}/spec/database_postgres.yml" "redmine/config/database.yml"
+fi
+
+echo "Done !"
+echo ""
+
+echo "#### INSTALL RSPEC FILE"
+mkdir "redmine/spec"
+cp "redmine/plugins/${PLUGIN_NAME}/spec/root_spec_helper.rb" "redmine/spec/spec_helper.rb"
+echo "Done !"
+echo ""
+
+echo "#### UPDATE GEMFILE"
+echo "Update shoulda to 3.5.0"
+sed -i 's/gem "shoulda", "~> 3.3.2"/gem "shoulda", "~> 3.5.0"/' "redmine/Gemfile"
+echo "Done !"
+echo ""
+
+echo "Update capybara to 2.2.0"
+sed -i 's/gem "capybara", "~> 2.1.0"/gem "capybara", "~> 2.2.0"/' "redmine/Gemfile"
+echo "Done !"
+echo ""
+
+echo "#### INSTALL ADMIN SSH KEY"
+ssh-keygen -N '' -f "redmine/plugins/${PLUGIN_NAME}/ssh_keys/redmine_gitolite_admin_id_rsa"
+echo "Done !"
+echo ""
+
+if [ "$TRAVIS_RUBY_VERSION" != "1.9.3" ] ; then
+  echo "#### INSTALL REDMINE SIDEKIQ PLUGIN"
+  git clone "${REDMINE_SIDEKIQ_PLUGIN}" "redmine/plugins/redmine_sidekiq"
+  echo "Done !"
+  echo ""
+fi
+
+echo "#### INSTALL REDMINE BOOTSTRAP PLUGIN"
+git clone "${REDMINE_BOOTSTRAP_PLUGIN}" "redmine/plugins/redmine_bootstrap_kit"
+echo "Done !"
+echo ""
+
+echo "######################"
+echo "CURRENT DIRECTORY LISTING"
+echo ""
+
+ls -l "${CURRENT_DIR}"
+echo ""
+
+echo "######################"
+echo "REDMINE PLUGIN DIRECTORY LISTING"
+echo ""
+
+ls -l "${REDMINE_NAME}/plugins"
+echo ""
+
+if [ "$INSTALL_GITOLITE" == "true" ] ; then
+  echo "######################"
+  echo "INSTALL GITOLITE V3"
+  echo ""
+
+  sudo useradd --create-home git
+  sudo -n -u git -i git clone https://github.com/sitaramc/gitolite.git
+  sudo -n -u git -i mkdir bin
+  sudo -n -u git -i gitolite/install -to /home/git/bin
+  sudo cp "redmine/plugins/${PLUGIN_NAME}/ssh_keys/redmine_gitolite_admin_id_rsa.pub" /home/git/
+  sudo chown git.git /home/git/redmine_gitolite_admin_id_rsa.pub
+  sudo -n -u git -i gitolite setup -pk redmine_gitolite_admin_id_rsa.pub
+fi
diff --git a/db/migrate/20110726000000_extend_changesets_notified_cia.rb b/db/migrate/20110726000000_extend_changesets_notified_cia.rb
new file mode 100644
index 0000000..8873782
--- /dev/null
+++ b/db/migrate/20110726000000_extend_changesets_notified_cia.rb
@@ -0,0 +1,14 @@
+class ExtendChangesetsNotifiedCia < ActiveRecord::Migration
+  def self.up
+    add_column :changesets, :notified_cia, :integer, :default=>0
+  end
+
+  def self.down
+    # Deal with fact that one of next migrations doesn't restore :notified_cia
+    remove_column :changesets, :notified_cia if self.column_exists?(:changesets, :notified_cia)
+  end
+
+  def self.column_exists?(table_name, column_name)
+    columns(table_name).any?{ |c| c.name == column_name.to_s }
+  end
+end
diff --git a/db/migrate/2011072600000_extend_changesets_notified_cia.rb b/db/migrate/2011072600000_extend_changesets_notified_cia.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20110807000000_create_repository_mirrors.rb b/db/migrate/20110807000000_create_repository_mirrors.rb
new file mode 100644
index 0000000..149ad4b
--- /dev/null
+++ b/db/migrate/20110807000000_create_repository_mirrors.rb
@@ -0,0 +1,15 @@
+class CreateRepositoryMirrors < ActiveRecord::Migration
+  def self.up
+    create_table :repository_mirrors do |t|
+      t.column :project_id, :integer
+      t.column :active, :integer, :default => 1
+      t.column :url, :string
+      t.references :project
+      t.timestamps
+    end
+  end
+
+  def self.down
+    drop_table :repository_mirrors
+  end
+end
diff --git a/db/migrate/2011080700000_create_repository_mirrors.rb b/db/migrate/2011080700000_create_repository_mirrors.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20110813000000_create_git_repository_extras.rb b/db/migrate/20110813000000_create_git_repository_extras.rb
new file mode 100644
index 0000000..7b3370e
--- /dev/null
+++ b/db/migrate/20110813000000_create_git_repository_extras.rb
@@ -0,0 +1,42 @@
+class CreateGitRepositoryExtras < ActiveRecord::Migration
+
+  def self.up
+    drop_table :git_repository_extras if self.table_exists?("git_repository_extras")
+
+    create_table :git_repository_extras do |t|
+      t.column :repository_id, :integer
+      # from repository extra columns
+      t.column :git_daemon, :integer, :default => 1
+      t.column :git_http,   :integer, :default => 1
+      t.column :notify_cia, :integer, :default => 0
+      # from Hooks Keys table
+      t.column :key, :string
+    end
+
+    if self.table_exists?("git_hook_keys")
+      drop_table :git_hook_keys
+    end
+
+    if self.column_exists?(:repositories, :git_daemon)
+      remove_column :repositories, :git_daemon
+    end
+
+    if self.column_exists?(:repositories, :git_http)
+      remove_column :repositories, :git_http
+    end
+
+  end
+
+  def self.down
+    drop_table :git_repository_extras
+  end
+
+  def self.table_exists?(name)
+    ActiveRecord::Base.connection.tables.include?(name)
+  end
+
+  def self.column_exists?(table_name, column_name)
+    columns(table_name).any?{ |c| c.name == column_name.to_s }
+  end
+
+end
diff --git a/db/migrate/2011081300000_create_git_repository_extras.rb b/db/migrate/2011081300000_create_git_repository_extras.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20110817000000_move_notified_cia_to_git_cia_notifications.rb b/db/migrate/20110817000000_move_notified_cia_to_git_cia_notifications.rb
new file mode 100644
index 0000000..66da395
--- /dev/null
+++ b/db/migrate/20110817000000_move_notified_cia_to_git_cia_notifications.rb
@@ -0,0 +1,30 @@
+class MoveNotifiedCiaToGitCiaNotifications < ActiveRecord::Migration
+  def self.up
+    drop_table :git_cia_notifications if self.table_exists?("git_cia_notifications")
+
+    create_table :git_cia_notifications do |t|
+      t.column :repository_id, :integer
+      t.column :scmid, :string
+    end
+
+    # Speed up searches
+    add_index(:git_cia_notifications, :scmid)
+    # Make sure uniqueness of the two columns, :scmid, :repository_id
+    add_index(:git_cia_notifications, [:scmid, :repository_id], :unique => true)
+
+    remove_column :changesets, :notified_cia if self.column_exists?(:changesets, :notified_cia)
+  end
+
+  def self.down
+    drop_table :git_cia_notifications
+  end
+
+  def self.table_exists?(name)
+    ActiveRecord::Base.connection.tables.include?(name)
+  end
+
+  def self.column_exists?(table_name, column_name)
+    columns(table_name).any?{ |c| c.name == column_name.to_s }
+  end
+
+end
diff --git a/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb b/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20120521000000_create_repository_post_receive_urls.rb b/db/migrate/20120521000000_create_repository_post_receive_urls.rb
new file mode 100644
index 0000000..ead700a
--- /dev/null
+++ b/db/migrate/20120521000000_create_repository_post_receive_urls.rb
@@ -0,0 +1,15 @@
+class CreateRepositoryPostReceiveUrls < ActiveRecord::Migration
+  def self.up
+    create_table :repository_post_receive_urls do |t|
+      t.column :project_id, :integer
+      t.column :active, :integer, :default => 1
+      t.column :url, :string
+      t.references :project
+      t.timestamps
+    end
+  end
+
+  def self.down
+    drop_table :repository_post_receive_urls
+  end
+end
diff --git a/db/migrate/2012052100000_create_repository_post_receive_urls.rb b/db/migrate/2012052100000_create_repository_post_receive_urls.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20120521000010_set_post_receive_url_role_permissions.rb b/db/migrate/20120521000010_set_post_receive_url_role_permissions.rb
new file mode 100644
index 0000000..7c19739
--- /dev/null
+++ b/db/migrate/20120521000010_set_post_receive_url_role_permissions.rb
@@ -0,0 +1,55 @@
+class SetPostReceiveUrlRolePermissions < ActiveRecord::Migration
+
+  def self.up
+    manager_role_name = I18n.t(:default_role_manager, {:locale => Setting.default_language})
+    puts "Updating role : '#{manager_role_name}'..."
+    manager_role = Role.find_by_name(manager_role_name)
+    if !manager_role.nil?
+      manager_role.add_permission! :view_repository_post_receive_urls
+      manager_role.add_permission! :create_repository_post_receive_urls
+      manager_role.add_permission! :edit_repository_post_receive_urls
+      manager_role.save
+      puts "done !"
+    else
+      puts "Role '#{manager_role_name}' not found, exit !"
+    end
+
+    developer_role_name = I18n.t(:default_role_developer, {:locale => Setting.default_language})
+    puts "Updating role : '#{developer_role_name}'..."
+    developer_role = Role.find_by_name(developer_role_name)
+    if !developer_role.nil?
+      developer_role.add_permission! :view_repository_post_receive_urls
+      developer_role.save
+      puts "done !"
+    else
+      puts "Role '#{developer_role_name}' not found, exit !"
+    end
+  end
+
+  def self.down
+    manager_role_name = I18n.t(:default_role_manager, {:locale => Setting.default_language})
+    puts "Updating role : '#{manager_role_name}'..."
+    manager_role = Role.find_by_name(manager_role_name)
+    if !manager_role.nil?
+      manager_role.remove_permission! :view_repository_post_receive_urls
+      manager_role.remove_permission! :create_repository_post_receive_urls
+      manager_role.remove_permission! :edit_repository_post_receive_urls
+      manager_role.save
+      puts "done !"
+    else
+      puts "Role '#{manager_role_name}' not found, exit !"
+    end
+
+    developer_role_name = I18n.t(:default_role_developer, {:locale => Setting.default_language})
+    puts "Updating role : '#{developer_role_name}'..."
+    developer_role = Role.find_by_name(developer_role_name)
+    if !developer_role.nil?
+      developer_role.remove_permission! :view_repository_post_receive_urls
+      developer_role.save
+      puts "done !"
+    else
+      puts "Role '#{developer_role_name}' not found, exit !"
+    end
+  end
+
+end
diff --git a/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb b/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20120522000000_add_post_receive_url_modes.rb b/db/migrate/20120522000000_add_post_receive_url_modes.rb
new file mode 100644
index 0000000..97b8e34
--- /dev/null
+++ b/db/migrate/20120522000000_add_post_receive_url_modes.rb
@@ -0,0 +1,9 @@
+class AddPostReceiveUrlModes < ActiveRecord::Migration
+  def self.up
+    add_column :repository_post_receive_urls, :mode, :string, :default => "github"
+  end
+
+  def self.down
+    remove_column :repository_post_receive_urls, :mode
+  end
+end
diff --git a/db/migrate/2012052200000_add_post_receive_url_modes.rb b/db/migrate/2012052200000_add_post_receive_url_modes.rb
old mode 100644
new mode 100755
diff --git a/db/migrate/20140417004100_enforce_models_constraints.rb b/db/migrate/20140417004100_enforce_models_constraints.rb
new file mode 100644
index 0000000..5a0d5b0
--- /dev/null
+++ b/db/migrate/20140417004100_enforce_models_constraints.rb
@@ -0,0 +1,41 @@
+class EnforceModelsConstraints < ActiveRecord::Migration
+
+  def self.up
+
+    change_column :git_caches, :repo_identifier, :string, :null => false, :after => :id
+    change_column :git_caches, :command,         :text,   :null => false
+    change_column :git_caches, :command_output,  :binary, :null => false, :limit => 16777216
+
+    change_column :gitolite_public_keys, :user_id,    :integer, :null => false, :after => :id
+    change_column :gitolite_public_keys, :key_type,   :integer, :null => false, :after => :user_id
+    change_column :gitolite_public_keys, :title,      :string, :null => false
+    change_column :gitolite_public_keys, :identifier, :string, :null => false
+    change_column :gitolite_public_keys, :key,        :text,   :null => false
+
+    change_column :repository_deployment_credentials, :repository_id, :integer, :null => false
+    change_column :repository_deployment_credentials, :gitolite_public_key_id, :integer, :null => false
+    change_column :repository_deployment_credentials, :user_id, :integer, :null => false
+
+    change_column :repository_git_config_keys, :repository_id, :integer, :null => false
+    change_column :repository_git_config_keys, :key,           :string,  :null => false
+    change_column :repository_git_config_keys, :value,         :string,  :null => false
+
+    change_column :repository_git_extras, :repository_id, :integer, :null => false
+    change_column :repository_git_extras, :key,           :string,  :null => false
+
+    change_column :repository_git_notifications, :repository_id, :integer, :null => false
+
+    change_column :repository_mirrors, :repository_id, :integer, :null => false, :after => :id
+    change_column :repository_mirrors, :url,           :string,  :null => false, :after => :repository_id
+    change_column :repository_mirrors, :push_mode,     :integer, :null => false
+    remove_column :repository_mirrors, :created_at
+    remove_column :repository_mirrors, :updated_at
+
+    change_column :repository_post_receive_urls, :repository_id, :integer, :null => false, :after => :id
+    change_column :repository_post_receive_urls, :url,           :string,  :null => false, :after => :repository_id
+    change_column :repository_post_receive_urls, :mode,          :string,  :null => false, :after => :url
+    remove_column :repository_post_receive_urls, :created_at
+    remove_column :repository_post_receive_urls, :updated_at
+  end
+
+end
diff --git a/db/migrate/20140423224900_convert_boolean.rb b/db/migrate/20140423224900_convert_boolean.rb
new file mode 100644
index 0000000..18a1d4c
--- /dev/null
+++ b/db/migrate/20140423224900_convert_boolean.rb
@@ -0,0 +1,116 @@
+class ConvertBoolean < ActiveRecord::Migration
+
+  def self.up
+
+    ## GitolitePublicKey
+    add_column :gitolite_public_keys, :active_temp, :boolean, :default => true, :after => :key
+    GitolitePublicKey.reset_column_information
+    GitolitePublicKey.all.each do |p|
+      if p.active == 1
+        p.active_temp = true
+      else
+        p.active_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :gitolite_public_keys, :active
+    rename_column :gitolite_public_keys, :active_temp, :active
+
+
+    add_column :gitolite_public_keys, :delete_when_unused_temp, :boolean, :default => true, :after => :active
+    GitolitePublicKey.reset_column_information
+    GitolitePublicKey.all.each do |p|
+      if p.delete_when_unused == 1
+        p.delete_when_unused_temp = true
+      else
+        p.delete_when_unused_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :gitolite_public_keys, :delete_when_unused
+    rename_column :gitolite_public_keys, :delete_when_unused_temp, :delete_when_unused
+
+
+    ## RepositoryGitExtra
+    add_column :repository_git_extras, :git_daemon_temp, :boolean, :default => true, :after => :git_daemon
+    RepositoryGitExtra.reset_column_information
+    RepositoryGitExtra.all.each do |p|
+      if p.git_daemon == 1
+        p.git_daemon_temp = true
+      else
+        p.git_daemon_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :repository_git_extras, :git_daemon
+    rename_column :repository_git_extras, :git_daemon_temp, :git_daemon
+
+
+    add_column :repository_git_extras, :git_notify_temp, :boolean, :default => true, :after => :git_notify
+    RepositoryGitExtra.reset_column_information
+    RepositoryGitExtra.all.each do |p|
+      if p.git_notify == 1
+        p.git_notify_temp = true
+      else
+        p.git_notify_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :repository_git_extras, :git_notify
+    rename_column :repository_git_extras, :git_notify_temp, :git_notify
+
+
+    ## RepositoryDeploymentCredential
+    add_column :repository_deployment_credentials, :active_temp, :boolean, :default => true, :after => :active
+    RepositoryDeploymentCredential.reset_column_information
+    RepositoryDeploymentCredential.all.each do |p|
+      if p.active == 1
+        p.active_temp = true
+      else
+        p.active_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :repository_deployment_credentials, :active
+    rename_column :repository_deployment_credentials, :active_temp, :active
+
+
+    ## RepositoryMirror
+    add_column :repository_mirrors, :active_temp, :boolean, :default => true, :after => :active
+    RepositoryMirror.reset_column_information
+    RepositoryMirror.all.each do |p|
+      if p.active == 1
+        p.active_temp = true
+      else
+        p.active_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :repository_mirrors, :active
+    rename_column :repository_mirrors, :active_temp, :active
+
+
+    ## RepositoryPostReceiveUrl
+    add_column :repository_post_receive_urls, :active_temp, :boolean, :default => true, :after => :active
+    RepositoryPostReceiveUrl.reset_column_information
+    RepositoryPostReceiveUrl.all.each do |p|
+      if p.active == 1
+        p.active_temp = true
+      else
+        p.active_temp = false
+      end
+      puts "Update!"
+      p.save(:validate => false)
+    end
+    remove_column :repository_post_receive_urls, :active
+    rename_column :repository_post_receive_urls, :active_temp, :active
+
+  end
+
+end
diff --git a/db/migrate/20140516224900_add_trigger_to_post_receive.rb b/db/migrate/20140516224900_add_trigger_to_post_receive.rb
new file mode 100644
index 0000000..fc0ceff
--- /dev/null
+++ b/db/migrate/20140516224900_add_trigger_to_post_receive.rb
@@ -0,0 +1,13 @@
+class AddTriggerToPostReceive < ActiveRecord::Migration
+
+  def self.up
+    add_column :repository_post_receive_urls, :use_triggers, :boolean, :default => false
+    add_column :repository_post_receive_urls, :triggers,     :text
+  end
+
+  def self.down
+    remove_column :repository_post_receive_urls, :use_triggers
+    remove_column :repository_post_receive_urls, :triggers
+  end
+
+end
diff --git a/db/migrate/20140523013000_add_split_payloads_to_post_receive.rb b/db/migrate/20140523013000_add_split_payloads_to_post_receive.rb
new file mode 100644
index 0000000..c119507
--- /dev/null
+++ b/db/migrate/20140523013000_add_split_payloads_to_post_receive.rb
@@ -0,0 +1,11 @@
+class AddSplitPayloadsToPostReceive < ActiveRecord::Migration
+
+  def self.up
+    add_column :repository_post_receive_urls, :split_payloads, :boolean, :default => false
+  end
+
+  def self.down
+    remove_column :repository_post_receive_urls, :split_payloads
+  end
+
+end
diff --git a/db/migrate/20140618224954_add_fingerprint_to_gitolite_public_keys.rb b/db/migrate/20140618224954_add_fingerprint_to_gitolite_public_keys.rb
new file mode 100644
index 0000000..0ff66b0
--- /dev/null
+++ b/db/migrate/20140618224954_add_fingerprint_to_gitolite_public_keys.rb
@@ -0,0 +1,14 @@
+class AddFingerprintToGitolitePublicKeys < ActiveRecord::Migration
+
+  def self.up
+    add_column :gitolite_public_keys, :fingerprint, :string, :after => 'key'
+    GitolitePublicKey.update_all("fingerprint = ''")
+    change_column :gitolite_public_keys, :fingerprint, :string, :null => false
+  end
+
+
+  def self.down
+    remove_column :gitolite_public_keys, :fingerprint
+  end
+
+end
diff --git a/db/migrate/20140618231512_create_unique_indexes.rb b/db/migrate/20140618231512_create_unique_indexes.rb
new file mode 100644
index 0000000..a30214e
--- /dev/null
+++ b/db/migrate/20140618231512_create_unique_indexes.rb
@@ -0,0 +1,33 @@
+class CreateUniqueIndexes < ActiveRecord::Migration
+
+  def self.up
+    add_index :repository_git_extras, :repository_id, :unique => true
+
+    add_index :repository_git_config_keys,   [ :key, :repository_id ], :unique => true
+    add_index :repository_post_receive_urls, [ :url, :repository_id ], :unique => true
+    add_index :repository_mirrors,           [ :url, :repository_id ], :unique => true
+
+    add_index :repository_deployment_credentials, [ :repository_id, :gitolite_public_key_id ], :unique => true, :name => 'index_deployment_credentials_on_repository_id_and_public_key_id'
+
+    add_index :gitolite_public_keys, [ :title, :user_id ], :unique => true
+
+    add_index :github_comments, [ :github_id, :journal_id ], :unique => true
+    add_index :github_issues, [ :github_id, :issue_id ], :unique => true
+  end
+
+  def self.down
+    remove_index :repository_git_extras, :repository_id
+
+    remove_index :repository_git_config_keys,   [ :key, :repository_id ]
+    remove_index :repository_post_receive_urls, [ :url, :repository_id ]
+    remove_index :repository_mirrors,           [ :url, :repository_id ]
+
+    remove_index :repository_deployment_credentials, :name => 'index_deployment_credentials_on_repository_id_and_public_key_id'
+
+    remove_index :gitolite_public_keys, [ :title, :user_id ]
+
+    remove_index :github_comments, [ :github_id, :journal_id ]
+    remove_index :github_issues, [ :github_id, :issue_id ]
+  end
+
+end
diff --git a/db/migrate/20140621004200_create_repository_protected_branches.rb b/db/migrate/20140621004200_create_repository_protected_branches.rb
new file mode 100644
index 0000000..ab2b22d
--- /dev/null
+++ b/db/migrate/20140621004200_create_repository_protected_branches.rb
@@ -0,0 +1,15 @@
+class CreateRepositoryProtectedBranches < ActiveRecord::Migration
+  def self.up
+    create_table :repository_protected_branches do |t|
+      t.column :repository_id, :integer, :null => false
+      t.column :path,          :string,  :null => false
+      t.column :permissions,   :string,  :null => false
+      t.column :user_list,     :text,    :null => false
+      t.column :position,      :integer
+    end
+  end
+
+  def self.down
+    drop_table :repository_protected_branches
+  end
+end
diff --git a/db/migrate/20140621004300_add_protected_branch_to_repository_git_extra.rb b/db/migrate/20140621004300_add_protected_branch_to_repository_git_extra.rb
new file mode 100644
index 0000000..ef9dd31
--- /dev/null
+++ b/db/migrate/20140621004300_add_protected_branch_to_repository_git_extra.rb
@@ -0,0 +1,11 @@
+class AddProtectedBranchToRepositoryGitExtra < ActiveRecord::Migration
+
+  def self.up
+    add_column :repository_git_extras, :protected_branch, :boolean, :default => false, :after => :default_branch
+  end
+
+  def self.down
+    remove_column :repository_git_extras, :protected_branch
+  end
+
+end
diff --git a/db/migrate/20140624150200_remove_gitolite_public_keys_active_column.rb b/db/migrate/20140624150200_remove_gitolite_public_keys_active_column.rb
new file mode 100644
index 0000000..17103af
--- /dev/null
+++ b/db/migrate/20140624150200_remove_gitolite_public_keys_active_column.rb
@@ -0,0 +1,12 @@
+class RemoveGitolitePublicKeysActiveColumn < ActiveRecord::Migration
+
+  def self.up
+    remove_column :gitolite_public_keys, :active
+  end
+
+
+  def self.down
+    add_column :gitolite_public_keys, :active, :boolean, :default => true, :after => :fingerprint
+  end
+
+end
diff --git a/lib/redmine_git_hosting/hooks/add_public_keys_link.rb b/lib/redmine_git_hosting/hooks/add_public_keys_link.rb
new file mode 100644
index 0000000..309f2d0
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/add_public_keys_link.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class AddPublicKeysLink < Redmine::Hook::ViewListener
+      render_on :view_my_account_contextual, :inline => "| <%= link_to l(:label_my_public_keys), public_keys_path, :class => 'icon icon-passwd' %>"
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/display_git_urls_on_project.rb b/lib/redmine_git_hosting/hooks/display_git_urls_on_project.rb
new file mode 100644
index 0000000..dc4a009
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/display_git_urls_on_project.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class ShowGitUrlsOnProject < Redmine::Hook::ViewListener
+      render_on :view_projects_show_left, :partial => 'projects/git_urls'
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/display_git_urls_on_repository_edit.rb b/lib/redmine_git_hosting/hooks/display_git_urls_on_repository_edit.rb
new file mode 100644
index 0000000..fe9fb6f
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/display_git_urls_on_repository_edit.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class ShowGitUrlsOnRepositoryEdit < Redmine::Hook::ViewListener
+      render_on :view_repository_edit_top, :partial => 'repositories/edit_top'
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/display_repository_extras.rb b/lib/redmine_git_hosting/hooks/display_repository_extras.rb
new file mode 100644
index 0000000..4687db0
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/display_repository_extras.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class DisplayRepositoryExtras < Redmine::Hook::ViewListener
+      render_on :view_repository_edit_bottom, :partial => 'repositories/edit_bottom'
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/display_repository_options.rb b/lib/redmine_git_hosting/hooks/display_repository_options.rb
new file mode 100644
index 0000000..521416c
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/display_repository_options.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class DisplayRepositoryOptions < Redmine::Hook::ViewListener
+      render_on :view_repository_form, :partial => 'repositories/git_form'
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/display_repository_sidebar.rb b/lib/redmine_git_hosting/hooks/display_repository_sidebar.rb
new file mode 100644
index 0000000..635a3d3
--- /dev/null
+++ b/lib/redmine_git_hosting/hooks/display_repository_sidebar.rb
@@ -0,0 +1,7 @@
+module RedmineGitHosting
+  module Hooks
+    class DisplayRepositorySidebar < Redmine::Hook::ViewListener
+      render_on :view_repositories_show_sidebar, :partial => 'repositories/sidebar'
+    end
+  end
+end
diff --git a/lib/redmine_git_hosting/hooks/show_git_urls_on_project.rb b/lib/redmine_git_hosting/hooks/show_git_urls_on_project.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_git_hosting/patches/my_controller_patch.rb b/lib/redmine_git_hosting/patches/my_controller_patch.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_git_hosting/patches/repositories_controller_patch.rb b/lib/redmine_git_hosting/patches/repositories_controller_patch.rb
index 9cb7e87..58afc0c 100644
--- a/lib/redmine_git_hosting/patches/repositories_controller_patch.rb
+++ b/lib/redmine_git_hosting/patches/repositories_controller_patch.rb
@@ -21,9 +21,10 @@ module RedmineGitHosting
       module InstanceMethods
 
         def show_with_git_hosting(&block)
+          Repository.fetch_changesets
           if @repository.is_a?(Repository::Git) && @repository.empty?
             # Fake list of repos
-            @repositories = @project.gitolite_repos
+            @repositories = @project.repositories
             render :action => 'git_instructions'
           else
             show_without_git_hosting(&block)
diff --git a/lib/redmine_git_hosting/patches/repositories_controller_patch.rb~ b/lib/redmine_git_hosting/patches/repositories_controller_patch.rb~
new file mode 100755
index 0000000..93271f3
--- /dev/null
+++ b/lib/redmine_git_hosting/patches/repositories_controller_patch.rb~
@@ -0,0 +1,135 @@
+module RedmineGitHosting
+  module Patches
+    module RepositoriesControllerPatch
+
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+
+          alias_method_chain :show,    :git_hosting
+          alias_method_chain :create,  :git_hosting
+          alias_method_chain :update,  :git_hosting
+          alias_method_chain :destroy, :git_hosting
+
+          before_filter :set_current_tab, :only => :edit
+
+          helper :git_hosting
+        end
+      end
+
+      module InstanceMethods
+
+        def show_with_git_hosting(&block)
+          Repository.fetch_changesets
+          if @repository.is_a?(Repository::Git) && @repository.empty?
+            # Fake list of repos
+            @repositories = @project.gitolite_repos
+            render :action => 'git_instructions'
+          else
+            show_without_git_hosting(&block)
+          end
+        end
+
+
+        def create_with_git_hosting(&block)
+          create_without_git_hosting(&block)
+
+          if @repository.is_a?(Repository::Git)
+            if !@repository.errors.any?
+
+              if params[:extra][:git_daemon] == 'true'
+                params[:extra][:git_daemon] = 1
+              else
+                params[:extra][:git_daemon] = 0
+              end
+
+              if params[:extra][:git_notify] == 'true'
+                params[:extra][:git_notify] = 1
+              else
+                params[:extra][:git_notify] = 0
+              end
+
+              @repository.extra.update_attributes(params[:extra])
+
+              options = params[:repository][:create_readme] == 'true' ? {:create_readme_file => true} : {:create_readme_file => false}
+
+              RedmineGitolite::GitHosting.logger.info { "User '#{User.current.login}' created a new repository '#{@repository.gitolite_repository_name}'" }
+              RedmineGitolite::GitHosting.resync_gitolite({ :command => :add_repository, :object => @repository.id, :options => options })
+            end
+          end
+        end
+
+
+        def update_with_git_hosting(&block)
+          update_without_git_hosting(&block)
+
+          if @repository.is_a?(Repository::Git)
+            if !@repository.errors.any?
+
+              if params[:extra][:git_daemon] == 'true'
+                params[:extra][:git_daemon] = 1
+              else
+                params[:extra][:git_daemon] = 0
+              end
+
+              if params[:extra][:git_notify] == 'true'
+                params[:extra][:git_notify] = 1
+              else
+                params[:extra][:git_notify] = 0
+              end
+
+              update_default_branch = false
+
+              if params[:extra].has_key?(:default_branch) && @repository.extra[:default_branch] != params[:extra][:default_branch]
+                update_default_branch = true
+              end
+
+              ## Update attributes
+              @repository.extra.update_attributes(params[:extra])
+
+              ## Update repository
+              RedmineGitolite::GitHosting.logger.info { "User '#{User.current.login}' has modified repository '#{@repository.gitolite_repository_name}'" }
+              RedmineGitolite::GitHosting.resync_gitolite({ :command => :update_repository, :object => @repository.id })
+
+              ## Update repository default branch
+              if update_default_branch
+                RedmineGitolite::GitHosting.logger.info { "User '#{User.current.login}' has modified default_branch of '#{@repository.gitolite_repository_name}' ('#{@repository.extra[:default_branch]}')" }
+                RedmineGitolite::GitHosting.resync_gitolite({ :command => :update_repository_default_branch, :object => @repository.id })
+              end
+            end
+          end
+        end
+
+
+        def destroy_with_git_hosting(&block)
+          destroy_without_git_hosting(&block)
+
+          if @repository.is_a?(Repository::Git)
+            if !@repository.errors.any?
+              RedmineGitolite::GitHosting.logger.info { "User '#{User.current.login}' has removed repository '#{@repository.gitolite_repository_name}'" }
+              repository_data = {}
+              repository_data['repo_name'] = @repository.gitolite_repository_name
+              repository_data['repo_path'] = @repository.gitolite_repository_path
+              RedmineGitolite::GitHosting.resync_gitolite({ :command => :delete_repositories, :object => [repository_data] })
+            end
+          end
+        end
+
+
+        private
+
+
+        def set_current_tab
+          @tab = params[:tab] || ""
+        end
+
+      end
+
+    end
+  end
+end
+
+unless RepositoriesController.included_modules.include?(RedmineGitHosting::Patches::RepositoriesControllerPatch)
+  RepositoriesController.send(:include, RedmineGitHosting::Patches::RepositoriesControllerPatch)
+end
diff --git a/lib/redmine_git_hosting/patches/sys_controller_patch.rb b/lib/redmine_git_hosting/patches/sys_controller_patch.rb
new file mode 100644
index 0000000..e5a5ff4
--- /dev/null
+++ b/lib/redmine_git_hosting/patches/sys_controller_patch.rb
@@ -0,0 +1,36 @@
+require_dependency 'sys_controller'
+
+module RedmineGitHosting
+  module Patches
+    module SysControllerPatch
+
+      def self.included(base)
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+
+          alias_method_chain :fetch_changesets, :git_hosting
+        end
+      end
+
+
+      module InstanceMethods
+
+        def fetch_changesets_with_git_hosting(&block)
+          # Previous routine
+          fetch_changesets_without_git_hosting(&block)
+
+          RedmineGitolite::GitHosting.logger.info { "Purging Recycle Bin from fetch_changesets" }
+          RedmineGitolite::Recycle.new().delete_expired_files
+        end
+
+      end
+
+
+    end
+  end
+end
+
+unless SysController.included_modules.include?(RedmineGitHosting::Patches::SysControllerPatch)
+  SysController.send(:include, RedmineGitHosting::Patches::SysControllerPatch)
+end
diff --git a/lib/redmine_gitolite/admin.rb b/lib/redmine_gitolite/admin.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/admin_projects.rb b/lib/redmine_gitolite/admin_projects.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/admin_repositories.rb b/lib/redmine_gitolite/admin_repositories.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/admin_repositories_helper.rb b/lib/redmine_gitolite/admin_repositories_helper.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/admin_users.rb b/lib/redmine_gitolite/admin_users.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/admin_users_helper.rb b/lib/redmine_gitolite/admin_users_helper.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/config_redmine.rb b/lib/redmine_gitolite/config_redmine.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/extra_loading.rb b/lib/redmine_gitolite/extra_loading.rb
new file mode 100644
index 0000000..cfcba6b
--- /dev/null
+++ b/lib/redmine_gitolite/extra_loading.rb
@@ -0,0 +1,15 @@
+module RedmineGitolite
+  module ExtraLoading
+    # Adds plugin locales if any
+    # YAML translation files should be found under <plugin>/config/locales/
+    ::I18n.load_path += Dir.glob(File.join(Rails.root, 'plugins', 'redmine_git_hosting', 'config', 'locales', '**', '*.yml'))
+
+    # Load Forms and Concerns objects
+    services = File.join(Rails.root, 'plugins', 'redmine_git_hosting', 'app', 'services')
+
+    if File.directory?(services)
+      ActiveSupport::Dependencies.autoload_paths += [services]
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_modules/gitolite_config.rb b/lib/redmine_gitolite/gitolite_modules/gitolite_config.rb
new file mode 100644
index 0000000..e9785a8
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_modules/gitolite_config.rb
@@ -0,0 +1,184 @@
+module RedmineGitolite::GitoliteModules
+
+  module GitoliteConfig
+
+    class << self
+      def included(receiver)
+        receiver.send(:extend, ClassMethods)
+      end
+    end
+
+
+    module ClassMethods
+
+      def logger
+        RedmineGitolite::Log.get_logger(:global)
+      end
+
+      # Puts Redmine user in cache as it should not change
+      @@redmine_user = nil
+      def redmine_user
+        @@redmine_user = (%x[whoami]).chomp.strip if @@redmine_user.nil?
+        @@redmine_user
+      end
+
+
+      def gitolite_user
+        RedmineGitolite::Config.get_setting(:gitolite_user)
+      end
+
+
+      def gitolite_server_port
+        RedmineGitolite::Config.get_setting(:gitolite_server_port)
+      end
+
+
+      def gitolite_ssh_private_key
+        RedmineGitolite::Config.get_setting(:gitolite_ssh_private_key)
+      end
+
+
+      def gitolite_ssh_public_key
+        RedmineGitolite::Config.get_setting(:gitolite_ssh_public_key)
+      end
+
+
+      def gitolite_config_file
+        RedmineGitolite::Config.get_setting(:gitolite_config_file)
+      end
+
+
+      def gitolite_key_subdir
+        'redmine_git_hosting'
+      end
+
+
+      def git_config_username
+        RedmineGitolite::Config.get_setting(:git_config_username)
+      end
+
+
+      def git_config_email
+        RedmineGitolite::Config.get_setting(:git_config_email)
+      end
+
+
+      def gitolite_temp_dir
+        RedmineGitolite::Config.get_setting(:gitolite_temp_dir)
+      end
+
+
+      def http_server_domain
+        RedmineGitolite::Config.get_setting(:http_server_domain)
+      end
+
+
+      def https_server_domain
+        RedmineGitolite::Config.get_setting(:https_server_domain)
+      end
+
+
+      def gitolite_url
+        [gitolite_user, '@localhost'].join
+      end
+
+
+      def gitolite_hooks_url
+        [Setting.protocol, '://', Setting.host_name, '/githooks/post-receive/redmine'].join
+      end
+
+
+      def gitolite_admin_dir
+        File.join(gitolite_temp_dir, gitolite_user, 'gitolite-admin.git')
+      end
+
+
+      def http_root_url
+        my_root_url(false)
+      end
+
+
+      def https_root_url
+        my_root_url(true)
+      end
+
+
+      def my_root_url(ssl = false)
+        # Remove any path from httpServer in case they are leftover from previous installations.
+        # No trailing /.
+        my_root_path = Redmine::Utils::relative_url_root
+
+        if ssl && https_server_domain != ''
+          server_domain = https_server_domain
+        else
+          server_domain = http_server_domain
+        end
+
+        my_root_url = File.join(server_domain[/^[^\/]*/], my_root_path, "/")[0..-2]
+
+        return my_root_url
+      end
+
+
+      ###############################
+      ##                           ##
+      ##         TEMP DIR          ##
+      ##                           ##
+      ###############################
+
+      @@temp_dir_path = nil
+      @@previous_temp_dir_path = nil
+
+      def create_temp_dir
+        if (@@previous_temp_dir_path != gitolite_temp_dir)
+          @@previous_temp_dir_path = gitolite_temp_dir
+          @@temp_dir_path = gitolite_admin_dir
+        end
+
+        if !File.directory?(@@temp_dir_path)
+          logger.info { "Create tmp directory : '#{@@temp_dir_path}'" }
+
+          begin
+            FileUtils.mkdir_p @@temp_dir_path
+            FileUtils.chmod 0700, @@temp_dir_path
+          rescue => e
+            logger.error { "Cannot create tmp directory : '#{@@temp_dir_path}'" }
+          end
+
+        end
+
+        return @@temp_dir_path
+      end
+
+
+      @@temp_dir_writeable = false
+
+      def temp_dir_writeable?(opts = {})
+        @@temp_dir_writeable = false if opts.has_key?(:reset) && opts[:reset] == true
+
+        if !@@temp_dir_writeable
+
+          logger.debug { "Testing if temp directory '#{create_temp_dir}' is writeable ..." }
+
+          mytestfile = File.join(create_temp_dir, "writecheck")
+
+          if !File.directory?(create_temp_dir)
+            @@temp_dir_writeable = false
+          else
+            begin
+              FileUtils.touch mytestfile
+              FileUtils.rm mytestfile
+              @@temp_dir_writeable = true
+            rescue => e
+              @@temp_dir_writeable = false
+            end
+          end
+        end
+
+        return @@temp_dir_writeable
+      end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_modules/gitolite_infos.rb b/lib/redmine_gitolite/gitolite_modules/gitolite_infos.rb
new file mode 100644
index 0000000..04cbce9
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_modules/gitolite_infos.rb
@@ -0,0 +1,101 @@
+module RedmineGitolite::GitoliteModules
+
+  module GitoliteInfos
+
+    ##########################
+    #                        #
+    #     GITOLITE INFOS     #
+    #                        #
+    ##########################
+
+    class << self
+      def included(receiver)
+        receiver.send(:extend, ClassMethods)
+      end
+    end
+
+
+    module ClassMethods
+
+      @@gitolite_infos_cached = nil
+      @@gitolite_info_stamp   = nil
+
+      def gitolite_infos
+        if !@@gitolite_infos_cached.nil? && (Time.new - @@gitolite_info_stamp <= 1)
+          return @@gitolite_infos_cached
+        end
+        begin
+          @@gitolite_infos_cached = ssh_shell('info')[0]
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Error while getting Gitolite version" }
+          @@gitolite_infos_cached = ''
+        end
+        @@gitolite_info_stamp = Time.new
+        return @@gitolite_infos_cached
+      end
+
+
+      def gitolite_version
+        logger.debug { "Getting Gitolite version..." }
+        find_version(gitolite_infos)
+      end
+
+
+      def gitolite_banner
+        logger.debug { "Getting Gitolite banner..." }
+        gitolite_infos
+      end
+
+
+      def find_version(output)
+        return 0 if output.blank?
+
+        version = nil
+
+        line = output.split("\n")[0]
+
+        if line =~ /gitolite[ -]v?2./
+          version = 2
+        elsif line.include?('running gitolite3')
+          version = 3
+        else
+          version = 0
+        end
+
+        return version
+      end
+
+
+      def gitolite_command
+        if gitolite_version == 2
+          gitolite_command = ['gl-setup']
+        elsif gitolite_version == 3
+          gitolite_command = ['gitolite', 'setup']
+        else
+          gitolite_command = nil
+        end
+        return gitolite_command
+      end
+
+
+      def gitolite_repository_count
+        if gitolite_version == 3
+          logger.debug { "Getting Gitolite physical repositories list..." }
+
+          begin
+            count = sudo_capture('gitolite', 'list-phy-repos').split("\n").length
+          rescue RedmineGitolite::GitHosting::GitHostingException => e
+            logger.error { "Error while getting Gitolite physical repositories list" }
+            count = 0
+          end
+
+          return count
+        else
+          return 'This is Gitolite v2, not implemented...'
+        end
+      end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_modules/mirroring.rb b/lib/redmine_gitolite/gitolite_modules/mirroring.rb
new file mode 100644
index 0000000..e11f30d
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_modules/mirroring.rb
@@ -0,0 +1,99 @@
+module RedmineGitolite::GitoliteModules
+
+  module Mirroring
+
+    ###############################
+    ##                           ##
+    ##          MIRRORS          ##
+    ##                           ##
+    ###############################
+
+    GITOLITE_MIRRORING_KEYS_NAME   = "redmine_gitolite_admin_id_rsa_mirroring"
+
+    class << self
+      def included(receiver)
+        receiver.send(:extend, ClassMethods)
+      end
+    end
+
+
+    module ClassMethods
+
+      def gitolite_ssh_private_key_dest_path
+        File.join('$HOME', '.ssh', GITOLITE_MIRRORING_KEYS_NAME)
+      end
+
+
+      def gitolite_ssh_public_key_dest_path
+        File.join('$HOME', '.ssh', "#{GITOLITE_MIRRORING_KEYS_NAME}.pub")
+      end
+
+
+      def gitolite_mirroring_script_dest_path
+        File.join('$HOME', '.ssh', 'run_gitolite_admin_ssh')
+      end
+
+
+      @@mirroring_public_key = nil
+
+      def mirroring_public_key
+        if @@mirroring_public_key.nil?
+          begin
+            public_key = File.read(gitolite_ssh_public_key).chomp.strip
+            @@mirroring_public_key = public_key.split(/[\t ]+/)[0].to_s + " " + public_key.split(/[\t ]+/)[1].to_s
+          rescue => e
+            logger.error { "Error while loading mirroring public key : #{e.output}" }
+            @@mirroring_public_key = nil
+          end
+        end
+
+        return @@mirroring_public_key
+      end
+
+
+      @@mirroring_keys_installed = false
+
+      def mirroring_keys_installed?(opts = {})
+        @@mirroring_keys_installed = false if opts.has_key?(:reset) && opts[:reset] == true
+
+        if !@@mirroring_keys_installed
+          logger.info { "Installing Redmine Gitolite mirroring SSH keys ..." }
+
+          if (install_private_key && install_public_key && install_mirroring_script)
+            logger.info { "Done !" }
+            @@mirroring_keys_installed = true
+          else
+            logger.error { "Failed to install Redmine Gitolite mirroring SSH keys !" }
+            @@mirroring_keys_installed = false
+          end
+        end
+
+        return @@mirroring_keys_installed
+      end
+
+
+      def install_private_key
+        sudo_install_file(File.read(gitolite_ssh_private_key), gitolite_ssh_private_key_dest_path, '600')
+      rescue
+        false
+      end
+
+
+      def install_public_key
+        sudo_install_file(File.read(gitolite_ssh_public_key), gitolite_ssh_public_key_dest_path, '644')
+      rescue
+        false
+      end
+
+
+      def install_mirroring_script
+        command = ['#!/bin/sh', "\n", 'exec', 'ssh', '-T', '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=no', '-i', gitolite_ssh_private_key_dest_path, '"$@"', "\n"].join(' ')
+        sudo_install_file(command, gitolite_mirroring_script_dest_path, '700')
+      rescue
+        false
+      end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_modules/ssh_wrapper.rb b/lib/redmine_gitolite/gitolite_modules/ssh_wrapper.rb
new file mode 100644
index 0000000..a149367
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_modules/ssh_wrapper.rb
@@ -0,0 +1,49 @@
+module RedmineGitolite::GitoliteModules
+
+  module SshWrapper
+
+    ##########################
+    #                        #
+    #       SSH Wrapper      #
+    #                        #
+    ##########################
+
+    class << self
+      def included(receiver)
+        receiver.send(:extend, ClassMethods)
+      end
+    end
+
+
+    module ClassMethods
+
+      # Execute a command in the gitolite forced environment through this user
+      # i.e., executes 'ssh git@localhost <command>'
+      #
+      # Returns stdout, stderr and the exit code
+      def ssh_shell(*params)
+        RedmineGitolite::GitHosting.execute('ssh', ssh_shell_params.concat(params))
+      end
+
+
+      # Return only the output from the ssh command and checks
+      def ssh_capture(*params)
+        RedmineGitolite::GitHosting.capture('ssh', ssh_shell_params.concat(params))
+      end
+
+      # Returns the ssh prefix arguments for all ssh_* commands
+      #
+      # These are as follows:
+      # * (-T) Never request tty
+      # * (-i <gitolite_ssh_private_key>) Use the SSH keys given in Settings
+      # * (-p <gitolite_server_port>) Use port from settings
+      # * (-o BatchMode=yes) Never ask for a password
+      # * <gitolite_user>@localhost (see +gitolite_url+)
+      def ssh_shell_params
+        ['-T', '-o', 'BatchMode=yes', '-p', gitolite_server_port, '-i', gitolite_ssh_private_key, gitolite_url]
+      end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_modules/sudo_wrapper.rb b/lib/redmine_gitolite/gitolite_modules/sudo_wrapper.rb
new file mode 100644
index 0000000..058a27e
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_modules/sudo_wrapper.rb
@@ -0,0 +1,242 @@
+module RedmineGitolite::GitoliteModules
+
+  module SudoWrapper
+
+    ##########################
+    #                        #
+    #   SUDO Shell Wrapper   #
+    #                        #
+    ##########################
+
+    class << self
+      def included(receiver)
+        receiver.send(:extend, ClassMethods)
+      end
+    end
+
+
+    module ClassMethods
+
+      # Returns the sudo prefix to all sudo_* commands
+      #
+      # These are as follows:
+      # * (-i) login as `gitolite_user` (setting ENV['HOME')
+      # * (-n) non-interactive
+      # * (-u `gitolite_user`) target user
+      def sudo_shell_params
+        ['-n', '-u', gitolite_user, '-i']
+      end
+
+
+      # Execute a command as the gitolite user defined in +GitoliteWrapper.gitolite_user+.
+      #
+      # Will shell out to +sudo -n -u <gitolite_user> params+
+      #
+      def sudo_shell(*params)
+        RedmineGitolite::GitHosting.execute('sudo', sudo_shell_params.concat(params))
+      end
+
+
+      # Return only the output of the shell command
+      # Throws an exception if the shell command does not exit with code 0.
+      def sudo_capture(*params)
+        RedmineGitolite::GitHosting.capture('sudo', sudo_shell_params.concat(params))
+      end
+
+
+      def sudo_pipe_capture(*params, stdin)
+        RedmineGitolite::GitHosting.capture('sudo', sudo_shell_params.concat(params), {stdin_data: stdin, binmode: true})
+      end
+
+
+      # Pipe file content via sudo to dest_file.
+      # Expect file content to end with EOL (\n)
+      def sudo_install_file(content, dest_file, filemode)
+        if([ 'cat', '<<\EOF', '>' + dest_file, "\n" + content.to_s + "EOF" ].respond_to?("join"))
+          stdin = [ 'cat', '<<\EOF', '>' + dest_file, "\n" + content.to_s + "EOF" ].join(' ')
+        else
+          stdin =  'cat' + ' <<\EOF', '> ' + dest_file, " \n " + content.to_s + " EOF"
+        end
+
+        begin
+          sudo_pipe_capture('sh', stdin)
+          sudo_chmod(filemode, dest_file)
+          return true
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { e.output }
+          return false
+        end
+      end
+
+
+      # Test if a file exists with size > 0
+      def sudo_file_exists?(filename)
+        sudo_test(filename, '-s')
+      end
+
+
+      # Test if a directory exists
+      def sudo_dir_exists?(dirname)
+        sudo_test(dirname, '-r')
+      end
+
+
+      def sudo_update_gitolite!
+        if(gitolite_command.respond_to?("join"))
+          logger.info { "Running '#{gitolite_command.join(' ')}' on the Gitolite install ..." }
+        end
+        begin
+          sudo_shell(*gitolite_command)
+          return true
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { e.output }
+          return false
+        end
+      end
+
+
+      # Test properties of a path from the git user.
+      #
+      # e.g., Test if a directory exists: sudo_test('~/somedir', '-d')
+      def sudo_test(path, *testarg)
+        out, _ , code = sudo_shell('eval', 'test', *testarg, path)
+        return code == 0
+      rescue => e
+        logger.debug("File check for #{path} failed : #{e.message}")
+        false
+      end
+
+
+      # Calls mkdir with the given arguments on the git user's side.
+      #
+      # e.g., sudo_mkdir('-p', '/some/path')
+      #
+      def sudo_mkdir(*args)
+        sudo_shell('eval', 'mkdir', *args)
+      end
+
+
+      # Calls chmod with the given arguments on the git user's side.
+      #
+      # e.g., sudo_chmod('755', '/some/path')
+      #
+      def sudo_chmod(mode, file)
+        sudo_shell('eval', 'chmod', mode, file)
+      end
+
+
+      # Removes a directory and all subdirectories below gitolite_user's $HOME.
+      #
+      # Assumes a relative path.
+      #
+      # If force=true, it will delete using 'rm -rf <path>', otherwise
+      # it uses rmdir
+      #
+      def sudo_rmdir(path, force = false)
+        if force
+          sudo_shell('eval', 'rm', '-rf', path)
+        else
+          sudo_shell('eval', 'rmdir', path)
+        end
+      end
+
+
+      # Moves a file/directory to a new target.
+      #
+      def sudo_move(old_path, new_path)
+        sudo_shell('eval', 'mv', old_path, new_path)
+      end
+
+
+      # Test if repository is empty on Gitolite side
+      #
+      def sudo_repository_empty?(path)
+        empty_repo = false
+        
+        if(File.respond_to?("join"))
+          path = File.join('$HOME', path, 'objects')
+          endpath = File.join('$HOME', path, 'objects')
+        else
+          path = '$HOME/'+ path + '/objects'
+          endpath = '$HOME/'+ path + '/objects'
+        end
+
+        begin
+          output = sudo_capture('eval', 'find', path, '-type', 'f', '|', 'wc', '-l')
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          empty_repo = false
+        else
+          logger.debug { "Counted objects in repository directory '#{path}' : '#{output}'" }
+
+          if output.to_i == 0
+            empty_repo = true
+          else
+            empty_repo = false
+          end
+        end
+
+        return empty_repo
+      end
+
+
+      ###############################
+      ##                           ##
+      ##        SUDO TESTS         ##
+      ##                           ##
+      ###############################
+
+      ## SUDO TEST1
+      def can_gitolite_sudo_to_redmine_user?
+        logger.info { "Testing if Gitolite user '#{gitolite_user}' can sudo to Redmine user '#{redmine_user}'..." }
+
+        if gitolite_user == redmine_user
+          logger.info { "OK!" }
+          return true
+        end
+
+        begin
+          test = sudo_capture('sudo', '-n', '-u', redmine_user, '-i', 'whoami')
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.warn { "Error while testing can_gitolite_sudo_to_redmine_user" }
+          return false
+        else
+          if test.match(/#{redmine_user}/)
+            logger.info { "OK!" }
+            return true
+          else
+            logger.warn { "Error while testing can_gitolite_sudo_to_redmine_user" }
+            return false
+          end
+        end
+      end
+
+
+      ## SUDO TEST2
+      def can_redmine_sudo_to_gitolite_user?
+        logger.info { "Testing if Redmine user '#{redmine_user}' can sudo to Gitolite user '#{gitolite_user}'..." }
+
+        if gitolite_user == redmine_user
+          logger.info { "OK!" }
+          return true
+        end
+
+        begin
+          test = sudo_capture('whoami')
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Error while testing can_redmine_sudo_to_gitolite_user" }
+          return false
+        else
+          if test.match(/#{gitolite_user}/)
+            logger.info { "OK!" }
+            return true
+          else
+            logger.warn { "Error while testing can_redmine_sudo_to_gitolite_user" }
+            return false
+          end
+        end
+      end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper.rb b/lib/redmine_gitolite/gitolite_wrapper.rb
new file mode 100644
index 0000000..03f7e84
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper.rb
@@ -0,0 +1,91 @@
+require 'gitolite'
+
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    include GitoliteModules::GitoliteConfig
+    include GitoliteModules::GitoliteInfos
+    include GitoliteModules::Mirroring
+    include GitoliteModules::SshWrapper
+    include GitoliteModules::SudoWrapper
+
+
+    ##########################
+    #                        #
+    #   Gitolite Accessor    #
+    #                        #
+    ##########################
+
+    class << self
+
+      def gitolite_admin_settings
+        {
+          git_user: gitolite_user,
+          host: "localhost:#{gitolite_server_port}",
+
+          author_name: git_config_username,
+          author_email: git_config_email,
+
+          public_key: gitolite_ssh_public_key,
+          private_key: gitolite_ssh_private_key,
+
+          key_subdir: gitolite_key_subdir,
+          config_file: gitolite_config_file
+        }
+      end
+
+
+      def gitolite_admin
+        create_temp_dir
+        admin_dir = gitolite_admin_dir
+        logger.info { "Acessing gitolite-admin.git at '#{admin_dir}'" }
+        Gitolite::GitoliteAdmin.new(admin_dir, gitolite_admin_settings)
+      end
+
+
+      WRAPPERS = [
+        GitoliteWrapper::Admin, GitoliteWrapper::Repositories,
+        GitoliteWrapper::Users, GitoliteWrapper::Projects
+      ]
+
+      # Update the Gitolite Repository
+      #
+      # action: An API action defined in one of the gitolite/* classes.
+      def update(action, object, options = {})
+        options = options.symbolize_keys
+
+        if options.has_key?(:flush_cache) && options[:flush_cache] == true
+          logger.info { "Flush Settings Cache !" }
+          Setting.check_cache if Setting.respond_to?(:check_cache)
+        end
+
+        begin
+          admin = gitolite_admin
+        rescue Rugged::SshError => e
+          logger.error { e.message }
+        else
+          WRAPPERS.each do |wrappermod|
+            if wrappermod.method_defined?(action)
+              return wrappermod.new(admin, action, object, options).send(action)
+            end
+          end
+          raise GitoliteWrapperException.new(action, "No available Wrapper for action '#{action}' found.")
+        end
+      end
+
+    end
+
+    # Used to register errors when pulling and pushing the conf file
+    class GitoliteWrapperException < StandardError
+      attr_reader :command
+      attr_reader :output
+
+      def initialize(command, output)
+        @command = command
+        @output  = output
+      end
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/admin.rb b/lib/redmine_gitolite/gitolite_wrapper/admin.rb
new file mode 100644
index 0000000..192de13
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/admin.rb
@@ -0,0 +1,53 @@
+module RedmineGitolite
+  module GitoliteWrapper
+
+    class Admin
+
+      attr_reader :admin
+      attr_reader :gitolite_config
+
+      attr_reader :action
+      attr_reader :object_id
+      attr_reader :options
+
+
+      def initialize(admin, action, object_id, options = {})
+        @admin           = admin
+        @gitolite_config = @admin.config
+
+        @action    = action
+        @object_id = object_id
+        @options   = options
+      end
+
+
+      def purge_recycle_bin
+        RedmineGitolite::Recycle.new().delete_expired_files(object_id)
+        logger.info { "#{action} : done !" }
+      end
+
+
+      def flush_settings_cache
+        logger.info { "Settings cache flushed!" }
+      end
+
+
+      private
+
+
+        def logger
+          RedmineGitolite::Log.get_logger(:worker)
+        end
+
+
+        def gitolite_admin_repo_commit(message = '')
+          logger.info { "#{action} : commiting to Gitolite..." }
+          admin.save("#{action} : #{message}")
+        rescue => e
+          logger.error { "#{e.message}" }
+        end
+
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/projects.rb b/lib/redmine_gitolite/gitolite_wrapper/projects.rb
new file mode 100644
index 0000000..e734446
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/projects.rb
@@ -0,0 +1,88 @@
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    class Projects < Admin
+
+      include RedmineGitolite::GitoliteWrapper::ProjectsHelper
+      include RedmineGitolite::GitoliteWrapper::RepositoriesHelper
+
+
+      def update_projects
+        if object_id == 'all'
+          projects = Project.includes(:repositories).all
+        elsif object_id == 'active'
+          projects = Project.active.includes(:repositories).all
+        elsif object_id == 'active_or_closed'
+          projects = Project.active_or_closed.includes(:repositories).all
+        else
+          projects = object_id.map{ |project_id| Project.find_by_id(project_id) }
+        end
+
+        perform_update(projects)
+      end
+
+
+      def move_repositories
+        projects = Project.find_by_id(object_id).self_and_descendants
+
+        # Only take projects that have Git repos.
+        git_projects = projects.uniq.select{ |p| p.gitolite_repos.any? }
+        return if git_projects.empty?
+
+        admin.transaction do
+          @delete_parent_path = []
+          handle_repositories_move(git_projects)
+          clean_path(@delete_parent_path)
+        end
+      end
+
+
+      def move_repositories_tree
+        projects = Project.includes(:repositories).all.select{ |x| x.parent_id.nil? }
+
+        admin.transaction do
+          @delete_parent_path = []
+
+          projects.each do |project|
+            git_projects = project.self_and_descendants.uniq.select{ |p| p.gitolite_repos.any? }
+
+            next if git_projects.empty?
+
+            handle_repositories_move(git_projects)
+          end
+
+          clean_path(@delete_parent_path)
+        end
+      end
+
+
+      private
+
+
+      def perform_update(projects)
+        git_projects = projects.uniq.select{ |p| p.gitolite_repos.any? }
+        return if git_projects.empty?
+
+        admin.transaction do
+          git_projects.each do |project|
+            handle_project_update(project)
+            gitolite_admin_repo_commit("#{project.identifier}")
+          end
+        end
+      end
+
+
+      def handle_project_update(project)
+        project.gitolite_repos.each do |repository|
+          if options[:force] == true
+            handle_repository_add(repository, :force => true)
+          else
+            handle_repository_update(repository)
+          end
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/projects_helper.rb b/lib/redmine_gitolite/gitolite_wrapper/projects_helper.rb
new file mode 100644
index 0000000..101104e
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/projects_helper.rb
@@ -0,0 +1,157 @@
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    module ProjectsHelper
+
+
+      def handle_repositories_move(git_projects)
+        git_projects.reverse.each do |project|
+          repo_list = []
+
+          project.gitolite_repos.reverse.each do |repository|
+            repo_list.push(repository.gitolite_repository_name)
+            perform_repository_move(repository)
+          end
+
+          gitolite_admin_repo_commit("#{action} : #{project.identifier} | #{repo_list}")
+        end
+      end
+
+
+      def perform_repository_move(repository)
+        repo_id   = repository.redmine_name
+        repo_name = repository.old_repository_name
+
+        repo_conf = gitolite_config.repos[repo_name]
+
+        old_repo_name = repository.old_repository_name
+        new_repo_name = repository.new_repository_name
+
+        old_relative_path  = repository.url
+        new_relative_path  = repository.gitolite_repository_path
+
+        old_relative_parent_path = old_relative_path.gsub(repo_id + '.git', '')
+        new_relative_parent_path = new_relative_path.gsub(repo_id + '.git', '')
+
+        logger.info { "#{action} : Moving '#{repo_name}'..." }
+        logger.debug { "  Old repository name (for Gitolite)           : #{old_repo_name}" }
+        logger.debug { "  New repository name (for Gitolite)           : #{new_repo_name}" }
+        logger.debug { "-----" }
+        logger.debug { "  Old relative path (for Redmine code browser) : #{old_relative_path}" }
+        logger.debug { "  New relative path (for Redmine code browser) : #{new_relative_path}" }
+        logger.debug { "-----" }
+        logger.debug { "  Old relative parent path (for Gitolite)      : #{old_relative_parent_path}" }
+        logger.debug { "  New relative parent path (for Gitolite)      : #{new_relative_parent_path}" }
+
+        if !repo_conf
+          logger.error { "#{action} : repository '#{repo_name}' does not exist in Gitolite, exit !" }
+          return
+        else
+          if move_physical_repo(old_relative_path, new_relative_path, new_relative_parent_path)
+            @delete_parent_path.push(old_relative_parent_path)
+
+            repository.update_column(:url, new_relative_path)
+            repository.update_column(:root_url, new_relative_path)
+
+            # update gitolite conf
+            old_perms = get_old_permissions(repo_conf)
+            gitolite_config.rm_repo(old_repo_name)
+            handle_repository_add(repository, :force => true, :old_perms => old_perms)
+          else
+            return false
+          end
+        end
+      end
+
+
+      def move_physical_repo(old_path, new_path, new_parent_path)
+        ## CASE 0
+        if old_path == new_path
+          logger.info { "#{action} : old repository and new repository are identical '#{old_path}', nothing to do, exit !" }
+          return true
+        end
+
+        # Now we have multiple options, due to the way gitolite sets up repositories
+        new_path_exists = GitoliteWrapper.sudo_dir_exists?(new_path)
+        old_path_exists = GitoliteWrapper.sudo_dir_exists?(old_path)
+
+        ## CASE 1
+        if new_path_exists && old_path_exists
+
+          if GitoliteWrapper.sudo_repository_empty?(new_path)
+            logger.warn { "#{action} : target repository '#{new_path}' already exists and is empty, remove it ..." }
+            begin
+              GitoliteWrapper.sudo_rmdir(new_path, true)
+            rescue GitHosting::GitHostingException => e
+              logger.error { "#{action} : removing existing target repository failed, exit !" }
+              return false
+            end
+          else
+            logger.warn { "#{action} : target repository '#{new_path}' exists and is not empty, considered as already moved, try to remove the old_path if empty" }
+
+            if GitoliteWrapper.sudo_repository_empty?(old_path)
+              begin
+                GitoliteWrapper.sudo_rmdir(old_path, true)
+                return true
+              rescue GitHosting::GitHostingException => e
+                logger.error { "#{action} : removing source repository directory failed, exit !" }
+                return false
+              end
+            else
+              logger.error { "#{action} : the source repository directory is not empty, cannot remove it, exit ! (This repo will be orphan)" }
+              return false
+            end
+          end
+
+        ## CASE 2
+        elsif !new_path_exists && old_path_exists
+
+          logger.debug { "#{action} : really moving Gitolite repository from '#{old_path}' to '#{new_path}'" }
+
+          if !GitoliteWrapper.sudo_dir_exists?(new_parent_path)
+            begin
+              GitoliteWrapper.sudo_mkdir('-p', new_parent_path)
+            rescue GitHosting::GitHostingException => e
+              logger.error { "#{action} : creation of parent path '#{new_parent_path}' failed, exit !" }
+              return false
+            end
+          end
+
+          begin
+            GitoliteWrapper.sudo_move(old_path, new_path)
+            logger.info { "#{action} : done !" }
+            return true
+          rescue GitHosting::GitHostingException => e
+            logger.error { "move_physical_repo(#{old_path}, #{new_path}) failed" }
+            return false
+          end
+
+        ## CASE 3
+        elsif !new_path_exists && !old_path_exists
+          logger.error { "#{action} : both old repository '#{old_path}' and new repository '#{new_path}' does not exist, cannot move it, exit but let Gitolite create the new repo !" }
+          return true
+
+        ## CASE 4
+        elsif new_path_exists && !old_path_exists
+          logger.error { "#{action} : old repository '#{old_path}' does not exist, but the new one does, use it !" }
+          return true
+
+        end
+      end
+
+
+      def clean_path(path_list)
+        path_list.uniq.sort.reverse.each do |path|
+          begin
+            logger.info { "#{action} : cleaning repository path : '#{path}'" }
+            GitoliteWrapper.sudo_rmdir(path)
+          rescue GitHosting::GitHostingException => e
+            logger.error { "#{action} : error while cleaning repository path '#{path}'" }
+          end
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/repositories.rb b/lib/redmine_gitolite/gitolite_wrapper/repositories.rb
new file mode 100644
index 0000000..7005ad3
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/repositories.rb
@@ -0,0 +1,106 @@
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    class Repositories < Admin
+
+      include RedmineGitolite::GitoliteWrapper::RepositoriesHelper
+
+
+      def add_repository
+        if repository = Repository.find_by_id(object_id)
+
+          if options.has_key?(:create_readme_file) && (options[:create_readme_file] == 'true' || options[:create_readme_file] == true)
+            create_readme = true
+          else
+            create_readme = false
+          end
+
+          admin.transaction do
+
+            handle_repository_add(repository)
+
+            gitolite_admin_repo_commit("#{repository.gitolite_repository_name}")
+
+            recycle = RedmineGitolite::Recycle.new
+
+            @recovered = recycle.recover_repository_if_present?(repository)
+
+            if !@recovered
+              logger.info { "#{action} : let Gitolite create empty repository '#{repository.gitolite_repository_path}'" }
+            else
+              logger.info { "#{action} : restored existing Gitolite repository '#{repository.gitolite_repository_path}' for update" }
+            end
+          end
+
+          if create_readme && !@recovered
+            if RedmineGitolite::GitoliteWrapper.sudo_repository_empty?(repository.gitolite_repository_path)
+              create_readme_file(repository)
+            else
+              logger.warn { "#{action} : repository not empty, cannot create README file in path '#{repository.gitolite_repository_path}'" }
+            end
+          end
+
+          repository.fetch_changesets
+        else
+          logger.error { "#{action} : repository does not exist anymore, object is nil, exit !" }
+        end
+      end
+
+
+      def update_repository
+        if repository = Repository.find_by_id(object_id)
+
+          admin.transaction do
+            handle_repository_update(repository)
+            gitolite_admin_repo_commit("#{repository.gitolite_repository_name}")
+          end
+
+          # Treat options
+          if options.has_key?(:delete_git_config_key) && !options[:delete_git_config_key].empty?
+            delete_hook_param(repository, options[:delete_git_config_key])
+          end
+        else
+          logger.error { "#{action} : repository does not exist anymore, object is nil, exit !" }
+        end
+      end
+
+
+      def delete_repositories
+        repositories_array = object_id
+
+        admin.transaction do
+          repositories_array.each do |repository_data|
+            handle_repository_delete(repository_data)
+
+            recycle = RedmineGitolite::Recycle.new
+            recycle.move_repository_to_recycle(repository_data) if RedmineGitolite::Config.get_setting(:delete_git_repositories, true)
+
+            gitolite_admin_repo_commit("#{repository_data['repo_name']}")
+          end
+        end
+      end
+
+
+      def update_repository_default_branch
+        if repository = Repository.find_by_id(object_id)
+
+          begin
+            RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{repository.gitolite_repository_path}", 'symbolic-ref', 'HEAD', "refs/heads/#{repository.extra[:default_branch]}")
+            logger.info { "Default branch successfully updated for repository '#{repository.gitolite_repository_name}'" }
+          rescue RedmineGitolite::GitHosting::GitHostingException => e
+            logger.error { "Error while updating default branch for repository '#{repository.gitolite_repository_name}'" }
+          end
+
+          RedmineGitolite::Cache.clear_cache_for_repository(repository)
+
+          logger.info { "Fetch changesets for repository '#{repository.gitolite_repository_name}'"}
+          repository.fetch_changesets
+        else
+          logger.error { "#{action} : repository does not exist anymore, object is nil, exit !" }
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/repositories_helper.rb b/lib/redmine_gitolite/gitolite_wrapper/repositories_helper.rb
new file mode 100644
index 0000000..9f267ca
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/repositories_helper.rb
@@ -0,0 +1,350 @@
+require 'rugged'
+
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    module RepositoriesHelper
+
+      def handle_repository_add(repository, opts = {})
+        force = (opts.has_key?(:force) && opts[:force] == true) || false
+        old_perms = (opts.has_key?(:old_perms) && opts[:old_perms].is_a?(Hash)) ? opts[:old_perms] : {}
+
+        repo_name = repository.gitolite_repository_name
+        repo_path = repository.gitolite_repository_path
+        repo_conf = gitolite_config.repos[repo_name]
+
+        if !repo_conf
+          logger.info { "#{action} : repository '#{repo_name}' does not exist in Gitolite, create it ..." }
+          logger.debug { "#{action} : repository path '#{repo_path}'" }
+          old_permissions = old_perms
+        else
+          if force
+            logger.warn { "#{action} : repository '#{repo_name}' already exists in Gitolite, force mode !" }
+            logger.debug { "#{action} : repository path '#{repo_path}'" }
+            old_permissions = get_old_permissions(repo_conf)
+            gitolite_config.rm_repo(repo_name)
+          else
+            logger.warn { "#{action} : repository '#{repo_name}' already exists in Gitolite, exit !" }
+            logger.debug { "#{action} : repository path '#{repo_path}'" }
+            return false
+          end
+        end
+
+        do_update_repository(repository, old_permissions)
+      end
+
+
+      def handle_repository_update(repository)
+        repo_name = repository.gitolite_repository_name
+        repo_path = repository.gitolite_repository_path
+        repo_conf = gitolite_config.repos[repo_name]
+
+        if repo_conf
+          logger.info { "#{action} : repository '#{repo_name}' exists in Gitolite, update it ..." }
+          logger.debug { "#{action} : repository path '#{repo_path}'" }
+          old_perms = get_old_permissions(repo_conf)
+          gitolite_config.rm_repo(repo_name)
+        else
+          logger.warn { "#{action} : repository '#{repo_name}' does not exist in Gitolite, exit !" }
+          logger.debug { "#{action} : repository path '#{repo_path}'" }
+          return false
+        end
+
+        do_update_repository(repository, old_perms)
+      end
+
+
+      def handle_repository_delete(repository_data)
+        repo_name = repository_data['repo_name']
+        repo_path = repository_data['repo_path']
+        repo_conf = gitolite_config.repos[repo_name]
+
+        if !repo_conf
+          logger.warn { "#{action} : repository '#{repo_name}' does not exist in Gitolite, exit !" }
+          logger.debug { "#{action} : repository path '#{repo_path}'" }
+          return false
+        else
+          logger.info { "#{action} : repository '#{repo_name}' exists in Gitolite, delete it ..." }
+          logger.debug { "#{action} : repository path '#{repo_path}'" }
+          gitolite_config.rm_repo(repo_name)
+        end
+      end
+
+
+      def do_update_repository(repository, old_permissions)
+        repo_name = repository.gitolite_repository_name
+        repo_conf = gitolite_config.repos[repo_name]
+        project   = repository.project
+
+        # Create new repo object
+        repo_conf = Gitolite::Config::Repo.new(repo_name)
+
+        # Set post-receive hook params
+        repo_conf.set_git_config("redminegitolite.projectid", repository.project.identifier.to_s)
+        repo_conf.set_git_config("redminegitolite.repositoryid", "#{repository.identifier || ''}")
+        repo_conf.set_git_config("redminegitolite.repositorykey", repository.extra[:key])
+
+        if project.active?
+          if User.anonymous.allowed_to?(:view_changesets, project) || repository.extra[:git_http] != 0
+            repo_conf.set_git_config("http.uploadpack", 'true')
+          else
+            repo_conf.set_git_config("http.uploadpack", 'false')
+          end
+
+          # Set mail-notifications hook params
+          repo_conf = set_mail_settings(repository, repo_conf)
+
+          # Set Git config keys
+          if repository.git_config_keys.any?
+            repository.git_config_keys.each do |git_config_key|
+              repo_conf.set_git_config(git_config_key.key, git_config_key.value)
+            end
+          end
+        else
+          repo_conf.set_git_config("http.uploadpack", 'false')
+          repo_conf.set_git_config("multimailhook.enabled", 'false')
+        end
+
+        gitolite_config.add_repo(repo_conf)
+
+        current_permissions = build_permissions(repository)
+        current_permissions = merge_permissions(current_permissions, old_permissions)
+
+        repo_conf.permissions = [current_permissions]
+      end
+
+
+      def set_mail_settings(repository, repo_conf)
+        notifier = ::GitNotifier.new(repository)
+
+        if repository.extra[:git_notify] && !notifier.mailing_list.empty?
+          repo_conf.set_git_config("multimailhook.enabled", 'true')
+          repo_conf.set_git_config("multimailhook.mailinglist", notifier.mailing_list.join(", "))
+          repo_conf.set_git_config("multimailhook.from", notifier.sender_address)
+          repo_conf.set_git_config("multimailhook.emailPrefix", notifier.email_prefix)
+        else
+          repo_conf.set_git_config("multimailhook.enabled", 'false')
+        end
+
+        return repo_conf
+      end
+
+
+      SKIP_USERS = [ 'gitweb', 'daemon', 'DUMMY_REDMINE_KEY', 'REDMINE_ARCHIVED_PROJECT', 'REDMINE_CLOSED_PROJECT' ]
+
+      def get_old_permissions(repo_conf)
+        gitolite_identifier_prefix = RedmineGitolite::Config.get_setting(:gitolite_identifier_prefix)
+
+        current_permissions = repo_conf.permissions[0]
+        old_permissions = {}
+
+        current_permissions.each do |perm, branch_settings|
+          old_permissions[perm] = {}
+
+          branch_settings.each do |branch, user_list|
+            next if user_list.empty?
+
+            new_user_list = []
+
+            user_list.each do |user|
+              ## We assume here that ':gitolite_config_file' is different than 'gitolite.conf'
+              ## like 'redmine.conf' with 'include "redmine.conf"' in 'gitolite.conf'.
+              ## This way, we know that all repos in this file are managed by Redmine so we
+              ## don't need to backup users
+              next if gitolite_identifier_prefix == ''
+
+              # ignore these users
+              next if SKIP_USERS.include?(user)
+
+              # backup users that are not Redmine users
+              if !user.include?(gitolite_identifier_prefix)
+                new_user_list.push(user)
+              end
+            end
+
+            if new_user_list.any?
+              old_permissions[perm][branch] = new_user_list
+            end
+          end
+        end
+
+        return old_permissions
+      end
+
+
+      def merge_permissions(current_permissions, old_permissions)
+        merge_permissions = {}
+        merge_permissions['RW+'] = {}
+        merge_permissions['RW'] = {}
+        merge_permissions['R'] = {}
+
+        current_permissions.each do |perm, branch_settings|
+          branch_settings.each do |branch, user_list|
+            if user_list.any?
+              if !merge_permissions[perm].has_key?(branch)
+                merge_permissions[perm][branch] = []
+              end
+              merge_permissions[perm][branch] += user_list
+            end
+          end
+        end
+
+        old_permissions.each do |perm, branch_settings|
+          branch_settings.each do |branch, user_list|
+            if user_list.any?
+              if !merge_permissions[perm].has_key?(branch)
+                merge_permissions[perm][branch] = []
+              end
+              merge_permissions[perm][branch] += user_list
+            end
+          end
+        end
+
+        merge_permissions.each do |perm, branch_settings|
+          merge_permissions.delete(perm) if merge_permissions[perm].empty?
+        end
+
+        return merge_permissions
+      end
+
+
+      def build_permissions(repository)
+        users   = repository.project.member_principals.map(&:user).compact.uniq
+        project = repository.project
+
+        rewind = []
+        write  = []
+        read   = []
+
+        rewind_users   = users.select{|user| user.allowed_to?(:manage_repository, project)}
+        write_users    = users.select{|user| user.allowed_to?(:commit_access, project)} - rewind_users
+        read_users     = users.select{|user| user.allowed_to?(:view_changesets, project)} - rewind_users - write_users
+
+        if project.active?
+          rewind = rewind_users.map{|user| user.gitolite_identifier}.sort
+          write  = write_users.map{|user| user.gitolite_identifier}.sort
+          read   = read_users.map{|user| user.gitolite_identifier}.sort
+          developer_team = rewind + write
+
+          ## DEPLOY KEY
+          repository.deployment_credentials.active.each do |cred|
+            if cred.perm == "RW+"
+              rewind << cred.gitolite_public_key.owner
+            elsif cred.perm == "R"
+              read << cred.gitolite_public_key.owner
+            end
+          end
+
+          read << "DUMMY_REDMINE_KEY" if read.empty? && write.empty? && rewind.empty?
+          read << "gitweb" if User.anonymous.allowed_to?(:browse_repository, project) && repository.extra[:git_http] != 0
+          read << "daemon" if User.anonymous.allowed_to?(:view_changesets, project) && repository.extra[:git_daemon]
+        elsif project.archived?
+          read << "REDMINE_ARCHIVED_PROJECT"
+        else
+          all_read = rewind_users + write_users + read_users
+          read     = all_read.map{|user| user.gitolite_identifier}
+          read << "REDMINE_CLOSED_PROJECT"
+        end
+
+        permissions = {}
+        permissions["RW+"] = {}
+        permissions["RW"] = {}
+        permissions["R"] = {}
+
+        if repository.extra[:protected_branch]
+          ## http://gitolite.com/gitolite/rules.html
+          ## The refex field is ignored for read check.
+          ## (Git does not support distinguishing one ref from another for access control during read operations).
+
+          repository.protected_branches.each do |branch|
+            case branch.permissions
+              when 'RW+'
+                permissions["RW+"][branch.path] = branch.allowed_users unless branch.allowed_users.empty?
+              when 'RW'
+                permissions["RW"][branch.path] = branch.allowed_users unless branch.allowed_users.empty?
+            end
+          end
+
+          permissions["RW+"]['personal/USER/'] = developer_team.sort unless developer_team.empty?
+        end
+
+        permissions["RW+"][""] = rewind unless rewind.empty?
+        permissions["RW"][""] = write unless write.empty?
+        permissions["R"][""] = read unless read.empty?
+
+        permissions
+      end
+
+
+      def create_readme_file(repository)
+        logger.info { "Create README file for repository '#{repository.gitolite_repository_name}'"}
+
+        temp_dir = Dir.mktmpdir
+
+        ## Create credentials object
+        credentials = Rugged::Credentials::SshKey.new(
+          :username   => RedmineGitolite::GitoliteWrapper.gitolite_user,
+          :publickey  => RedmineGitolite::GitoliteWrapper.gitolite_ssh_public_key,
+          :privatekey => RedmineGitolite::GitoliteWrapper.gitolite_ssh_private_key
+        )
+
+        commit_author = {
+          :email => RedmineGitolite::GitoliteWrapper.git_config_username,
+          :name  => RedmineGitolite::GitoliteWrapper.git_config_email,
+          :time  => Time.now
+        }
+
+        begin
+          ## Clone repository
+          repo = Rugged::Repository.clone_at(repository.ssh_url, temp_dir, credentials: credentials)
+
+          ## Create file
+          oid = repo.write("## #{repository.gitolite_repository_name}", :blob)
+          index = repo.index
+          index.add(:path => "README.md", :oid => oid, :mode => 0100644)
+
+          ## Create commit
+          commit_tree = index.write_tree(repo)
+          commit = Rugged::Commit.create(repo,
+            :author     => commit_author,
+            :committer  => commit_author,
+            :message    => "Add README file",
+            :parents    => repo.empty? ? [] : [ repo.head.target ].compact,
+            :tree       => commit_tree,
+            :update_ref => 'HEAD'
+          )
+
+          ## Push
+          repo.push('origin', [ "refs/heads/#{repository.extra[:default_branch]}" ], credentials: credentials)
+        rescue => e
+          logger.error { "Error while creating README file for repository '#{repository.gitolite_repository_name}'"}
+          logger.error { e.message }
+        ensure
+          FileUtils.rm_rf temp_dir
+        end
+      end
+
+
+      def delete_hook_param(repository, parameter_name)
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{repository.gitolite_repository_path}", 'config', '--local', '--unset', parameter_name)
+          logger.info { "Git config key '#{parameter_name}' successfully deleted for repository '#{repository.gitolite_repository_name}'"}
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Error while deleting Git config key '#{parameter_name}' for repository '#{repository.gitolite_repository_name}'"}
+        end
+      end
+
+
+      def delete_hook_section(repository, section_name)
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_capture('git', "--git-dir=#{repository.gitolite_repository_path}", 'config', '--local', '--remove-section', section_name)
+          logger.info { "Git config section '#{section_name}' successfully deleted for repository '#{repository.gitolite_repository_name}'"}
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Error while deleting Git config section '#{section_name}' for repository '#{repository.gitolite_repository_name}'"}
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/redmine_gitolite/gitolite_wrapper/users.rb b/lib/redmine_gitolite/gitolite_wrapper/users.rb
new file mode 100644
index 0000000..2fb8b1e
--- /dev/null
+++ b/lib/redmine_gitolite/gitolite_wrapper/users.rb
@@ -0,0 +1,72 @@
+module RedmineGitolite
+
+  module GitoliteWrapper
+
+    class Users < Admin
+
+
+      def add_ssh_key
+        ssh_key = GitolitePublicKey.find_by_id(object_id)
+        logger.info { "Adding SSH key #{ssh_key.identifier}" }
+        admin.transaction do
+          add_gitolite_key(ssh_key)
+          gitolite_admin_repo_commit("Add SSH key : #{ssh_key.identifier}")
+        end
+      end
+
+
+      def delete_ssh_key
+        ssh_key = object_id.symbolize_keys
+        logger.info { "Deleting SSH key #{ssh_key[:identifier]}" }
+        admin.transaction do
+          remove_gitolite_key(ssh_key)
+          gitolite_admin_repo_commit("Delete SSH key : #{ssh_key[:identifier]}")
+        end
+      end
+
+
+      def resync_all_ssh_keys
+        ssh_keys = GitolitePublicKey.all
+        admin.transaction do
+          ssh_keys.each do |ssh_key|
+            add_gitolite_key(ssh_key)
+            gitolite_admin_repo_commit("Add SSH key : #{ssh_key.identifier}")
+          end
+        end
+      end
+
+
+      private
+
+
+      def add_gitolite_key(key)
+        parts     = key.key.split
+        repo_keys = admin.ssh_keys[key.owner]
+        repo_key  = repo_keys.find_all{|k| k.location == key.location && k.owner == key.owner}.first
+
+        unless repo_key
+          repo_key = Gitolite::SSHKey.new(parts[0], parts[1], parts[2], key.owner, key.location)
+          admin.add_key(repo_key)
+        else
+          logger.info { "#{action} : SSH key '#{key.owner}@#{key.location}' already exists in Gitolite, update it ..." }
+          repo_key.type, repo_key.blob, repo_key.email = parts
+          repo_key.owner = key.owner
+          repo_key.location = key.location
+        end
+      end
+
+
+      def remove_gitolite_key(key)
+        repo_keys = admin.ssh_keys[key[:owner]]
+        repo_key  = repo_keys.find_all{|k| k.location == key[:location] && k.owner == key[:owner]}.first
+
+        if repo_key
+          admin.rm_key(repo_key)
+        else
+          logger.info { "#{action} : SSH key '#{key[:owner]}@#{key[:location]}' does not exits in Gitolite, exit !" }
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/redmine_gitolite/hook_dir.rb b/lib/redmine_gitolite/hook_dir.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/hook_file.rb b/lib/redmine_gitolite/hook_file.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/hook_manager/global_params.rb b/lib/redmine_gitolite/hook_manager/global_params.rb
new file mode 100644
index 0000000..09a29c0
--- /dev/null
+++ b/lib/redmine_gitolite/hook_manager/global_params.rb
@@ -0,0 +1,52 @@
+module RedmineGitolite::HookManager
+
+  class GlobalParams < HookParam
+
+    attr_reader :gitolite_hooks_url
+    attr_reader :debug_mode
+    attr_reader :async_mode
+
+    attr_reader :namespace
+    attr_reader :current_params
+
+
+    def initialize
+      ## Params to set
+      @gitolite_hooks_url = RedmineGitolite::GitoliteWrapper.gitolite_hooks_url
+      @debug_mode         = RedmineGitolite::Config.get_setting(:gitolite_hooks_debug, true).to_s
+      @async_mode         = RedmineGitolite::Config.get_setting(:gitolite_hooks_are_asynchronous, true).to_s
+
+      ## Namespace where to set params
+      @namespace = RedmineGitolite::HookManager.gitolite_hooks_namespace
+
+      ## Get current params
+      @current_params = get_git_config_params(@namespace)
+    end
+
+
+    def installed?
+      installed = {}
+
+      if current_params["redmineurl"] != gitolite_hooks_url
+        installed['redmineurl'] = set_git_config_param(namespace, "redmineurl", gitolite_hooks_url)
+      else
+        installed['redmineurl'] = true
+      end
+
+      if current_params["debugmode"] != debug_mode
+        installed['debugmode'] = set_git_config_param(namespace, "debugmode", debug_mode)
+      else
+        installed['debugmode'] = true
+      end
+
+      if current_params["asyncmode"] != async_mode
+        installed['asyncmode'] = set_git_config_param(namespace, "asyncmode", async_mode)
+      else
+        installed['asyncmode'] = true
+      end
+
+      return installed
+    end
+
+  end
+end
diff --git a/lib/redmine_gitolite/hook_manager/hook_dir.rb b/lib/redmine_gitolite/hook_manager/hook_dir.rb
new file mode 100644
index 0000000..d1c34cc
--- /dev/null
+++ b/lib/redmine_gitolite/hook_manager/hook_dir.rb
@@ -0,0 +1,59 @@
+module RedmineGitolite::HookManager
+
+  class HookDir
+
+    attr_reader :name
+    attr_reader :destination_path
+
+
+    def initialize(name, destination_path)
+      @name             = name
+      @destination_path = destination_path
+    end
+
+
+    def installed?
+      if !exists?
+        logger.info { "Global hook directory '#{name}' not created yet, installing it..." }
+
+        if install_hooks_dir
+          logger.info { "Global hook directory '#{name}' installed" }
+        end
+      end
+      return exists?
+    end
+
+
+    private
+
+
+      def logger
+        RedmineGitolite::Log.get_logger(:global)
+      end
+
+
+      def exists?
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_dir_exists?(destination_path)
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          return false
+        end
+      end
+
+
+      def install_hooks_dir
+        logger.info { "Installing hook directory '#{destination_path}'" }
+
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_mkdir('-p', destination_path)
+          RedmineGitolite::GitoliteWrapper.sudo_chmod('755', destination_path)
+          return true
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Problems installing hook directory '#{destination_path}'" }
+          logger.error { e.output }
+          return false
+        end
+      end
+
+  end
+end
diff --git a/lib/redmine_gitolite/hook_manager/hook_file.rb b/lib/redmine_gitolite/hook_manager/hook_file.rb
new file mode 100644
index 0000000..62d70e1
--- /dev/null
+++ b/lib/redmine_gitolite/hook_manager/hook_file.rb
@@ -0,0 +1,90 @@
+require 'digest/md5'
+
+module RedmineGitolite::HookManager
+
+  class HookFile
+
+    attr_reader :name
+    attr_reader :source_path
+    attr_reader :destination_path
+    attr_reader :filemode
+
+
+    def initialize(name, source_path, destination_path, executable)
+      @name             = name
+      @source_path      = source_path
+      @destination_path = destination_path
+      @filemode         = executable ? '755' : '644'
+      @force_update     = RedmineGitolite::Config.get_setting(:gitolite_force_hooks_update, true)
+    end
+
+
+    def installed?
+      if !exists?
+        logger.info { "Hook '#{name}' does not exist, installing it ..." }
+        do_install_file
+      elsif hook_are_different?
+        logger.warn { "Hook '#{name}' is already present but it's not ours!" }
+
+        if @force_update
+          logger.info { "Restoring '#{name}' hook since forceInstallHook == true" }
+          do_install_file
+        end
+      end
+      return exists?
+    end
+
+
+    private
+
+
+      def do_install_file
+        if install_hook_file
+          logger.info { "Hook '#{name}' installed" }
+          update_gitolite
+        end
+      end
+
+
+      def logger
+        RedmineGitolite::Log.get_logger(:global)
+      end
+
+
+      def hook_are_different?
+        local_md5 != distant_md5
+      end
+
+
+      def local_md5
+        Digest::MD5.hexdigest(File.read(source_path))
+      end
+
+
+      def distant_md5
+        content = RedmineGitolite::GitoliteWrapper.sudo_capture('eval', 'cat', destination_path) rescue ''
+        Digest::MD5.hexdigest(content)
+      end
+
+
+      def exists?
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_file_exists?(destination_path)
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          return false
+        end
+      end
+
+
+      def install_hook_file
+        logger.info { "Installing hook '#{source_path}' in '#{destination_path}'" }
+        RedmineGitolite::GitoliteWrapper.sudo_install_file(File.read(source_path), destination_path, filemode)
+      end
+
+
+      def update_gitolite
+        RedmineGitolite::GitoliteWrapper.sudo_update_gitolite!
+      end
+
+  end
+end
diff --git a/lib/redmine_gitolite/hook_manager/hook_param.rb b/lib/redmine_gitolite/hook_manager/hook_param.rb
new file mode 100644
index 0000000..b65128f
--- /dev/null
+++ b/lib/redmine_gitolite/hook_manager/hook_param.rb
@@ -0,0 +1,81 @@
+module RedmineGitolite::HookManager
+
+  class HookParam
+
+
+    private
+
+
+      def logger
+        RedmineGitolite::Log.get_logger(:global)
+      end
+
+
+      # Return a hash with global config parameters.
+      def get_git_config_params(namespace)
+        begin
+          params = RedmineGitolite::GitoliteWrapper.sudo_capture('git', 'config', '-f', '.gitconfig', '--get-regexp', namespace).split("\n")
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Problems to retrieve Gitolite hook parameters in Gitolite config 'namespace : #{namespace}'" }
+          params = []
+        end
+
+        value_hash = {}
+
+        params.each do |value_pair|
+          global_key = value_pair.split(' ')[0]
+          value      = value_pair.split(' ')[1]
+          key        = global_key.split('.')[1]
+          value_hash[key] = value
+        end
+
+        return value_hash
+      end
+
+
+      def set_git_config_param(namespace, key, value)
+        key = prefix_key(namespace, key)
+
+        return unset_git_config_param(key) if value == ''
+
+        logger.info { "Set Git hooks global parameter : #{key} (#{value})" }
+
+        begin
+          RedmineGitolite::GitoliteWrapper.sudo_capture('git', 'config', '--global', key, value)
+          return true
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          logger.error { "Error while setting Git hooks global parameter : #{key} (#{value})" }
+          logger.error { e.output }
+          return false
+        end
+      end
+
+
+      def unset_git_config_param(key)
+        logger.info { "Unset Git hooks global parameter : #{key}" }
+
+        begin
+          _, _, code = RedmineGitolite::GitoliteWrapper.sudo_shell('git', 'config', '--global', '--unset', key)
+          return true
+        rescue RedmineGitolite::GitHosting::GitHostingException => e
+          if code == 5
+            return true
+          else
+            logger.error { "Error while removing Git hooks global parameter : #{key}" }
+            logger.error { e.output }
+            return false
+          end
+        end
+      end
+
+
+      # Returns the global gitconfig prefix for
+      # a config with that given key under the
+      # hooks namespace.
+      #
+      def prefix_key(namespace, key)
+        [namespace, '.', key].join
+      end
+
+  end
+end
diff --git a/lib/redmine_gitolite/hook_manager/mailer_params.rb b/lib/redmine_gitolite/hook_manager/mailer_params.rb
new file mode 100644
index 0000000..016279c
--- /dev/null
+++ b/lib/redmine_gitolite/hook_manager/mailer_params.rb
@@ -0,0 +1,67 @@
+module RedmineGitolite::HookManager
+
+  class MailerParams < HookParam
+
+    attr_reader :namespace
+    attr_reader :current_params
+
+
+    def initialize
+      ## Namespace where to set params
+      @namespace = 'multimailhook'
+
+      ## Get current params
+      @current_params = get_git_config_params(@namespace)
+    end
+
+
+    def installed?
+      params = %w(mailer environment smtpauth smtpserver smtpport smtpuser smtppass)
+      mailer_params = get_mailer_params
+
+      installed = {}
+
+      params.each do |param|
+        if current_params[param] != mailer_params[param]
+          installed[param] = set_git_config_param(namespace, param, mailer_params[param])
+        else
+          installed[param] = true
+        end
+      end
+
+      return installed
+    end
+
+
+    private
+
+
+      def get_mailer_params
+        params = {}
+
+        params['environment'] = 'gitolite'
+
+        if ActionMailer::Base.delivery_method == :smtp
+          params['mailer'] = 'smtp'
+        else
+          params['mailer'] = 'sendmail'
+        end
+
+        auth = ActionMailer::Base.smtp_settings[:authentication]
+
+        if auth != nil && auth != '' && auth != :none
+          params['smtpauth'] = 'true'
+        else
+          params['smtpauth'] = 'false'
+        end
+
+        params['smtpserver'] = ActionMailer::Base.smtp_settings[:address].to_s
+        params['smtpport']   = ActionMailer::Base.smtp_settings[:port].to_s
+        params['smtpuser']   = ActionMailer::Base.smtp_settings[:user_name] || ''
+        params['smtppass']   = ActionMailer::Base.smtp_settings[:password] || ''
+
+        params
+      end
+
+  end
+end
diff --git a/lib/redmine_gitolite/hook_param.rb b/lib/redmine_gitolite/hook_param.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/shell.rb b/lib/redmine_gitolite/shell.rb
old mode 100644
new mode 100755
diff --git a/lib/redmine_gitolite/translations.rb b/lib/redmine_gitolite/translations.rb
old mode 100644
new mode 100755
diff --git a/lib/tasks/ci_tools.rake b/lib/tasks/ci_tools.rake
new file mode 100644
index 0000000..b576168
--- /dev/null
+++ b/lib/tasks/ci_tools.rake
@@ -0,0 +1,66 @@
+namespace :redmine_git_hosting do
+
+  namespace :ci do
+    begin
+      require 'ci/reporter/rake/rspec'
+
+      RSpec::Core::RakeTask.new do |task|
+        task.rspec_opts = "plugins/redmine_git_hosting/spec --color"
+      end
+    rescue Exception => e
+    else
+      ENV["CI_REPORTS"] = Rails.root.join('junit').to_s
+    end
+
+    desc "Check unit tests results"
+    task :check_unit_tests_results => [:environment] do
+      gitolite_admin_dir = RedmineGitolite::GitoliteWrapper.gitolite_admin_dir
+      gitolite_temp_dir  = RedmineGitolite::Config.get_setting(:gitolite_temp_dir)
+
+      puts "#####################"
+      puts "TESTS RESULTS"
+      puts ""
+      puts "gitolite_temp_dir  : #{gitolite_temp_dir}"
+      puts "gitolite_admin_dir : #{gitolite_admin_dir}"
+      puts ""
+
+      puts "* ls -hal #{gitolite_temp_dir}"
+      puts %x[ ls -hal #{gitolite_temp_dir} ]
+      puts ""
+
+      puts "* ls -hal #{gitolite_temp_dir}git"
+      puts %x[ ls -hal #{gitolite_temp_dir}git ]
+      puts ""
+
+      puts "* ls -hal #{gitolite_temp_dir}git/gitolite-admin.git"
+      puts %x[ ls -hal #{gitolite_temp_dir}git/gitolite-admin.git ]
+      puts ""
+
+      begin
+        repo = Rugged::Repository.new(gitolite_admin_dir)
+        puts "git repo work dir  : #{repo.workdir}"
+        puts "git repo path      : #{repo.path}"
+        puts ""
+        puts "GIT STATUS :"
+        puts "------------"
+        puts %x[ git --work-tree #{repo.workdir} --git-dir #{repo.path} status ]
+        puts ""
+        puts "GIT LOG :"
+        puts "---------"
+        puts %x[ git --work-tree #{repo.workdir} --git-dir #{repo.path} log ]
+      rescue => e
+        puts "Error while getting tests results"
+        puts e.message
+      end
+    end
+
+    task :all => ['ci:setup:rspec', 'spec', 'check_unit_tests_results']
+  end
+
+
+  task :default => "redmine_git_hosting:ci:all"
+  task :spec    => "redmine_git_hosting:ci:all"
+  task :rspec   => "redmine_git_hosting:ci:all"
+  task :test    => "redmine_git_hosting:ci:all"
+
+end
diff --git a/lib/tasks/rename_ssh_keys.rake b/lib/tasks/rename_ssh_keys.rake
old mode 100644
new mode 100755
diff --git a/lib/tasks/selinux.rake b/lib/tasks/selinux.rake
old mode 100644
new mode 100755
diff --git a/spec/controllers/repository_deployment_credentials_controller_spec.rb b/spec/controllers/repository_deployment_credentials_controller_spec.rb
new file mode 100644
index 0000000..3bbbe74
--- /dev/null
+++ b/spec/controllers/repository_deployment_credentials_controller_spec.rb
@@ -0,0 +1,253 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryDeploymentCredentialsController do
+
+  def success_url
+    "/repositories/#{@repository.id}/edit?tab=repository_deployment_credentials"
+  end
+
+  DEPLOY_KEY2 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+CcnSqGcwViUxDiOS504o2FckLH6o+RbIFDKDfMXxuS4aAbVn6VfMzQNPYTXJHJMjtO7KJB73WUmDErc2GnI4w6iHVOoODFJnZiYMoaypbuLaHchDM22JsiWXyyeBMTAcJcx6UxUyL4GWHeLAsYJ9ol++40cisOUs46f5dMNIIB2KWZ4LiVQW9MvFPJrWXmJMFKfITYUm3OPpaD1Jq4D6xkkrHK2bx8WYzGMZsPGkb5hB2Uhdff+EquwIQ6nmm3pSgWpezElRVYU6RoDDbsQh7bTV+oA0ErU18SWPdxtO2azneccezFIawNxrMRcAEGroVQV5IplGeaZwmeifbWrV nicolas@tchoum'
+  DEPLOY_KEY3 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCoG/GwPjzEq1Ybph3J+DX8nd3kQM4hYP378rPJLI9RGyUnd1Zs7/T8uu27fgsY10v4sFcsQwBMrZoR/2XchjUTj0e4ai6asVSezhJCLSTG/TQtXzsdxyr+5hm9vQia97IMNhCL+KOW5pz5ZrhV9abR1vSlAAlk919mRD7Nmyo8Qg0g0iYHWsTddYDEMIelCLQTPahuJJb0bOcCZvDVR7Q87vSMiIWTajDhfJYauvP0tbFV7R1VTjKCIv/cSySbrAtTZigQ5Ul1ILkMaETsKS9p9YHNeWhLlHvYDGa+eb4+rfiM2RMxC98wePqINT46EFw0vPiLW+ukqD/5b2cb+7OP nicolas@tchoum'
+
+  before(:all) do
+    @project        = FactoryGirl.create(:project)
+    @repository     = FactoryGirl.create(:repository_git, :project_id => @project.id)
+    @user           = FactoryGirl.create(:user, :admin => true)
+
+    @credential     = FactoryGirl.create(:repository_deployment_credential,
+      :repository_id          => @repository.id,
+      :user_id                => @user.id,
+      :gitolite_public_key_id => FactoryGirl.create(:gitolite_public_key, :user_id => @user.id, :key_type => 1, :title => 'foo1', :key => DEPLOY_KEY2).id
+    )
+
+    @repository2    = FactoryGirl.create(:repository_git, :project_id => @project.id, :identifier => 'credential-test')
+  end
+
+
+  describe "GET #index" do
+    before do
+      request.session[:user_id] = @user.id
+      get :index, :repository_id => @repository.id
+    end
+
+    it "populates an array of repository_deployment_credentials" do
+      expect(assigns(:repository_deployment_credentials)).to eq [@credential]
+    end
+
+    it "renders the :index view" do
+      expect(response).to render_template(:index)
+    end
+  end
+
+
+  describe "GET #show" do
+    before do
+      request.session[:user_id] = @user.id
+      get :show, :repository_id => @repository.id, :id => @credential.id
+    end
+
+    it "renders 404" do
+      expect(response.status).to eq 404
+    end
+  end
+
+
+  describe "GET #new" do
+    before do
+      request.session[:user_id] = @user.id
+      get :new, :repository_id => @repository.id
+    end
+
+    it "assigns a new RepositoryDeploymentCredential to @credential" do
+      expect(assigns(:credential)).to be_an_instance_of(RepositoryDeploymentCredential)
+    end
+
+    it "renders the :new template" do
+      expect(response).to render_template(:new)
+    end
+  end
+
+
+  describe "POST #create" do
+    context "with valid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+        @public_key = FactoryGirl.create(:gitolite_public_key, :user_id => @user.id, :key_type => 1, :title => 'foo11', :key => DEPLOY_KEY3)
+      end
+
+      it "saves the new credential in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_deployment_credential => {
+                          :gitolite_public_key_id => @public_key.id,
+                          :perm => 'RW+'
+                        }
+        }.to change(RepositoryDeploymentCredential, :count).by(1)
+      end
+
+      it "redirects to the repository page" do
+        post :create, :repository_id => @repository.id,
+                      :repository_deployment_credential => {
+                        :gitolite_public_key_id => @public_key.id,
+                        :perm => 'RW+'
+                      }
+        expect(response).to redirect_to(success_url)
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+        @public_key = FactoryGirl.create(:gitolite_public_key, :user_id => @user.id, :key_type => 1, :title => 'foo12', :key => DEPLOY_KEY3)
+      end
+
+      it "does not save the new credential in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_deployment_credential => {
+                          :gitolite_public_key_id => @public_key.id,
+                          :perm => 'RW'
+                        }
+        }.to_not change(RepositoryDeploymentCredential, :count)
+      end
+
+      it "re-renders the :new template" do
+        post :create, :repository_id => @repository.id,
+                      :repository_deployment_credential => {
+                        :gitolite_public_key_id => @public_key.id,
+                        :perm => 'RW'
+                      }
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+
+  describe "GET #edit" do
+    context "with existing credential" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => @credential.id
+      end
+
+      it "assigns the requested credential to @credential" do
+        expect(assigns(:credential)).to eq @credential
+      end
+
+      it "renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    context "with non-existing credential" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => 100
+      end
+
+      it "renders 404" do
+        expect(response.status).to eq 404
+      end
+    end
+
+    context "with non-matching repository" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository2.id, :id => @credential.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+
+    context "with unsufficient permissions" do
+      before do
+        request.session[:user_id] = FactoryGirl.create(:user).id
+        get :edit, :repository_id => @repository.id, :id => @credential.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+  end
+
+
+  describe "PUT #update" do
+    before do
+      request.session[:user_id] = @user.id
+    end
+
+    context "with valid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @credential.id,
+                     :repository_deployment_credential => {
+                        :perm => 'R'
+                     }
+      end
+
+      it "located the requested @credential" do
+        expect(assigns(:credential)).to eq @credential
+      end
+
+      it "changes @credential's attributes" do
+        @credential.reload
+        expect(@credential.perm).to eq 'R'
+      end
+
+      it "redirects to the repository page" do
+        expect(response).to redirect_to success_url
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @credential.id,
+                     :repository_deployment_credential => {
+                        :perm => 'RW',
+                     }
+      end
+
+      it "located the requested @credential" do
+        expect(assigns(:credential)).to eq @credential
+      end
+
+      it "does not change @credential's attributes" do
+        @credential.reload
+        expect(@credential.perm).to eq 'RW+'
+      end
+
+      it "re-renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+
+  describe 'DELETE destroy' do
+    before :each do
+      request.session[:user_id] = @user.id
+
+      @credential_delete = FactoryGirl.create(:repository_deployment_credential,
+        :repository_id          => @repository.id,
+        :user_id                => @user.id,
+        :gitolite_public_key_id => FactoryGirl.create(:gitolite_public_key, :user_id => @user.id, :key_type => 1, :title => 'foo2', :key => DEPLOY_KEY3).id
+      )
+    end
+
+    it "deletes the git_config_key" do
+      expect{
+        delete :destroy, :repository_id => @repository.id, :id => @credential_delete.id, :format => 'js'
+      }.to change(RepositoryDeploymentCredential, :count).by(-1)
+    end
+
+    it "redirects to repositories#edit" do
+      delete :destroy, :repository_id => @repository.id, :id => @credential_delete.id, :format => 'js'
+      expect(response.status).to eq 200
+    end
+  end
+
+end
diff --git a/spec/controllers/repository_git_config_keys_controller_spec.rb b/spec/controllers/repository_git_config_keys_controller_spec.rb
new file mode 100644
index 0000000..662bea6
--- /dev/null
+++ b/spec/controllers/repository_git_config_keys_controller_spec.rb
@@ -0,0 +1,239 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryGitConfigKeysController do
+
+  def success_url
+    "/repositories/#{@repository.id}/edit?tab=repository_git_config_keys"
+  end
+
+
+  before(:all) do
+    @project        = FactoryGirl.create(:project)
+    @repository     = FactoryGirl.create(:repository_git, :project_id => @project.id)
+    @git_config_key = FactoryGirl.create(:repository_git_config_key, :repository_id => @repository.id)
+    @user           = FactoryGirl.create(:user, :admin => true)
+
+    @repository2    = FactoryGirl.create(:repository_git, :project_id => @project.id, :identifier => 'gck-test')
+  end
+
+
+  describe "GET #index" do
+    before do
+      request.session[:user_id] = @user.id
+      get :index, :repository_id => @repository.id
+    end
+
+    it "populates an array of repository_git_config_keys" do
+      expect(assigns(:repository_git_config_keys)).to eq [@git_config_key]
+    end
+
+    it "renders the :index view" do
+      expect(response).to render_template(:index)
+    end
+  end
+
+
+  describe "GET #show" do
+    before do
+      request.session[:user_id] = @user.id
+      get :show, :repository_id => @repository.id, :id => @git_config_key.id
+    end
+
+    it "renders 404" do
+      expect(response.status).to eq 404
+    end
+  end
+
+
+  describe "GET #new" do
+    before do
+      request.session[:user_id] = @user.id
+      get :new, :repository_id => @repository.id
+    end
+
+    it "assigns a new RepositoryGitConfigKey to @git_config_key" do
+      expect(assigns(:git_config_key)).to be_an_instance_of(RepositoryGitConfigKey)
+    end
+
+    it "renders the :new template" do
+      expect(response).to render_template(:new)
+    end
+  end
+
+
+  describe "POST #create" do
+    context "with valid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "saves the new git_config_key in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_git_config_key => {
+                          :key   => 'foo.bar1',
+                          :value => 0
+                        }
+        }.to change(RepositoryGitConfigKey, :count).by(1)
+      end
+
+      it "redirects to the repository page" do
+        post :create, :repository_id => @repository.id,
+                      :repository_git_config_key => {
+                        :key   => 'foo.bar2',
+                        :value => 0
+                      }
+        expect(response).to redirect_to(success_url)
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "does not save the new post_receive_url in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_git_config_key => {
+                          :key   => 'foo',
+                          :value => 0
+                        }
+        }.to_not change(RepositoryGitConfigKey, :count)
+      end
+
+      it "re-renders the :new template" do
+        post :create, :repository_id => @repository.id,
+                      :repository_git_config_key => {
+                        :key   => 'foo',
+                        :value => 0
+                      }
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+
+  describe "GET #edit" do
+    context "with existing git_config_key" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => @git_config_key.id
+      end
+
+      it "assigns the requested git_config_key to @git_config_key" do
+        expect(assigns(:git_config_key)).to eq @git_config_key
+      end
+
+      it "renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    context "with non-existing git_config_key" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => 100
+      end
+
+      it "renders 404" do
+        expect(response.status).to eq 404
+      end
+    end
+
+    context "with non-matching repository" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository2.id, :id => @git_config_key.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+
+    context "with unsufficient permissions" do
+      before do
+        request.session[:user_id] = FactoryGirl.create(:user).id
+        get :edit, :repository_id => @repository.id, :id => @git_config_key.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+  end
+
+
+  describe "PUT #update" do
+    before do
+      request.session[:user_id] = @user.id
+    end
+
+    context "with valid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @git_config_key.id,
+                     :repository_git_config_key => {
+                        :key   => 'foo.bar1',
+                        :value => 1
+                     }
+      end
+
+      it "located the requested @git_config_key" do
+        expect(assigns(:git_config_key)).to eq @git_config_key
+      end
+
+      it "changes @git_config_key's attributes" do
+        @git_config_key.reload
+        expect(@git_config_key.value).to eq '1'
+      end
+
+      it "redirects to the repository page" do
+        expect(response).to redirect_to success_url
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @git_config_key.id,
+                     :repository_git_config_key => {
+                        :key   => 'foo',
+                        :value => 1
+                     }
+      end
+
+      it "located the requested @git_config_key" do
+        expect(assigns(:git_config_key)).to eq @git_config_key
+      end
+
+      it "does not change @git_config_key's attributes" do
+        @git_config_key.reload
+        expect(@git_config_key.value).to eq 'bar'
+      end
+
+      it "re-renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+  describe 'DELETE destroy' do
+    before :each do
+      request.session[:user_id] = @user.id
+      @git_config_key_delete = FactoryGirl.create(:repository_git_config_key, :repository_id => @repository.id)
+    end
+
+    it "deletes the git_config_key" do
+      expect{
+        delete :destroy, :repository_id => @repository.id, :id => @git_config_key_delete.id, :format => 'js'
+      }.to change(RepositoryGitConfigKey, :count).by(-1)
+    end
+
+    it "redirects to repositories#edit" do
+      delete :destroy, :repository_id => @repository.id, :id => @git_config_key_delete.id, :format => 'js'
+      expect(response.status).to eq 200
+    end
+  end
+end
diff --git a/spec/controllers/repository_mirrors_controller_spec.rb b/spec/controllers/repository_mirrors_controller_spec.rb
new file mode 100644
index 0000000..132efe5
--- /dev/null
+++ b/spec/controllers/repository_mirrors_controller_spec.rb
@@ -0,0 +1,249 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryMirrorsController do
+
+  def success_url
+    "/repositories/#{@repository.id}/edit?tab=repository_mirrors"
+  end
+
+
+  before(:all) do
+    @project        = FactoryGirl.create(:project)
+    @repository     = FactoryGirl.create(:repository_git, :project_id => @project.id)
+    @mirror         = FactoryGirl.create(:repository_mirror, :repository_id => @repository.id)
+    @user           = FactoryGirl.create(:user, :admin => true)
+
+    @repository2    = FactoryGirl.create(:repository_git, :project_id => @project.id, :identifier => 'mirror-test')
+  end
+
+
+  describe "GET #index" do
+    before do
+      request.session[:user_id] = @user.id
+      get :index, :repository_id => @repository.id
+    end
+
+    it "populates an array of mirrors" do
+      expect(assigns(:repository_mirrors)).to eq [@mirror]
+    end
+
+    it "renders the :index view" do
+      expect(response).to render_template(:index)
+    end
+  end
+
+
+  describe "GET #show" do
+    before do
+      request.session[:user_id] = @user.id
+      get :show, :repository_id => @repository.id, :id => @mirror.id
+    end
+
+    it "renders 404" do
+      expect(response.status).to eq 404
+    end
+  end
+
+
+  describe "GET #push" do
+    before do
+      request.session[:user_id] = @user.id
+      get :push, :repository_id => @repository.id, :id => @mirror.id
+    end
+
+    it "renders the :push view" do
+      expect(response).to render_template(:push)
+    end
+  end
+
+
+  describe "GET #new" do
+    before do
+      request.session[:user_id] = @user.id
+      get :new, :repository_id => @repository.id
+    end
+
+    it "assigns a new RepositoryMirror to @mirror" do
+      expect(assigns(:mirror)).to be_an_instance_of(RepositoryMirror)
+    end
+
+    it "renders the :new template" do
+      expect(response).to render_template(:new)
+    end
+  end
+
+
+  describe "POST #create" do
+    context "with valid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "saves the new mirror in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_mirror => {
+                          :url => 'ssh://git@redmine.example.org/project1/project2/project3/project4.git',
+                          :push_mode => 0
+                        }
+        }.to change(RepositoryMirror, :count).by(1)
+      end
+
+      it "redirects to the repository page" do
+        post :create, :repository_id => @repository.id,
+                      :repository_mirror => {
+                        :url => 'ssh://git@redmine.example.org/project1/project2/project3/project4/repo1.git',
+                        :push_mode => 0
+                      }
+        expect(response).to redirect_to(success_url)
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "does not save the new mirror in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_mirror => {
+                          :url => 'git@redmine.example.org/project1/project2/project3/project4.git',
+                          :push_mode => 0
+                        }
+        }.to_not change(RepositoryMirror, :count)
+      end
+
+      it "re-renders the :new template" do
+        post :create, :repository_id => @repository.id,
+                      :repository_mirror => {
+                        :url => 'git@redmine.example.org/project1/project2/project3/project4.git',
+                        :push_mode => 0
+                      }
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+
+  describe "GET #edit" do
+    context "with existing mirror" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => @mirror.id
+      end
+
+      it "assigns the requested mirror to @mirror" do
+        expect(assigns(:mirror)).to eq @mirror
+      end
+
+      it "renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    context "with non-existing mirror" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => 100
+      end
+
+      it "renders 404" do
+        expect(response.status).to eq 404
+      end
+    end
+
+    context "with non-matching repository" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository2.id, :id => @mirror.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+
+    context "with unsufficient permissions" do
+      before do
+        request.session[:user_id] = FactoryGirl.create(:user).id
+        get :edit, :repository_id => @repository.id, :id => @mirror.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+  end
+
+
+  describe "PUT #update" do
+    before do
+      request.session[:user_id] = @user.id
+    end
+
+    context "with valid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @mirror.id,
+                     :repository_mirror => {
+                       :url => 'ssh://git@redmine.example.org/project1/project2/project3/project4.git'
+                     }
+      end
+
+      it "located the requested @mirror" do
+        expect(assigns(:mirror)).to eq @mirror
+      end
+
+      it "changes @mirror's attributes" do
+        @mirror.reload
+        expect(@mirror.url).to eq 'ssh://git@redmine.example.org/project1/project2/project3/project4.git'
+      end
+
+      it "redirects to the repository page" do
+        expect(response).to redirect_to success_url
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @mirror.id,
+                     :repository_mirror => {
+                       :url => 'git@redmine.example.org/project1/project2/project3/project4.git'
+                     }
+      end
+
+      it "located the requested @mirror" do
+        expect(assigns(:mirror)).to eq @mirror
+      end
+
+      it "does not change @mirror's attributes" do
+        @mirror.reload
+        expect(@mirror.url).to eq 'ssh://host.xz/path/to/repo1.git'
+      end
+
+      it "re-renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+  describe 'DELETE destroy' do
+    before :each do
+      request.session[:user_id] = @user.id
+      @mirror_delete = FactoryGirl.create(:repository_mirror, :repository_id => @repository.id)
+    end
+
+    it "deletes the mirror" do
+      expect{
+        delete :destroy, :repository_id => @repository.id, :id => @mirror_delete.id, :format => 'js'
+      }.to change(RepositoryMirror, :count).by(-1)
+    end
+
+    it "redirects to repositories#edit" do
+      delete :destroy, :repository_id => @repository.id, :id => @mirror_delete.id, :format => 'js'
+      expect(response.status).to eq 200
+    end
+  end
+end
diff --git a/spec/controllers/repository_post_receive_urls_controller_spec.rb b/spec/controllers/repository_post_receive_urls_controller_spec.rb
new file mode 100644
index 0000000..caae376
--- /dev/null
+++ b/spec/controllers/repository_post_receive_urls_controller_spec.rb
@@ -0,0 +1,238 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryPostReceiveUrlsController do
+
+  def success_url
+    "/repositories/#{@repository.id}/edit?tab=repository_post_receive_urls"
+  end
+
+
+  before(:all) do
+    @project          = FactoryGirl.create(:project)
+    @repository       = FactoryGirl.create(:repository_git, :project_id => @project.id)
+    @post_receive_url = FactoryGirl.create(:repository_post_receive_url, :repository_id => @repository.id)
+    @user             = FactoryGirl.create(:user, :admin => true)
+
+    @repository2      = FactoryGirl.create(:repository_git, :project_id => @project.id, :identifier => 'pru-test')
+  end
+
+
+  describe "GET #index" do
+    before do
+      request.session[:user_id] = @user.id
+      get :index, :repository_id => @repository.id
+    end
+
+    it "populates an array of post_receive_urls" do
+      expect(assigns(:repository_post_receive_urls)).to eq [@post_receive_url]
+    end
+
+    it "renders the :index view" do
+      expect(response).to render_template(:index)
+    end
+  end
+
+
+  describe "GET #show" do
+    before do
+      request.session[:user_id] = @user.id
+      get :show, :repository_id => @repository.id, :id => @post_receive_url.id
+    end
+
+    it "renders 404" do
+      expect(response.status).to eq 404
+    end
+  end
+
+
+  describe "GET #new" do
+    before do
+      request.session[:user_id] = @user.id
+      get :new, :repository_id => @repository.id
+    end
+
+    it "assigns a new RepositoryPostReceiveUrl to @post_receive_url" do
+      expect(assigns(:post_receive_url)).to be_an_instance_of(RepositoryPostReceiveUrl)
+    end
+
+    it "renders the :new template" do
+      expect(response).to render_template(:new)
+    end
+  end
+
+
+  describe "POST #create" do
+    context "with valid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "saves the new post_receive_url in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_post_receive_url => {
+                          :url  => 'http://example.com',
+                          :mode => :github
+                        }
+        }.to change(RepositoryPostReceiveUrl, :count).by(1)
+      end
+
+      it "redirects to the repository page" do
+        post :create, :repository_id => @repository.id,
+                      :repository_post_receive_url => {
+                        :url  => 'http://example2.com',
+                        :mode => :github
+                      }
+        expect(response).to redirect_to(success_url)
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        request.session[:user_id] = @user.id
+      end
+
+      it "does not save the new post_receive_url in the database" do
+        expect{
+          post :create, :repository_id => @repository.id,
+                        :repository_post_receive_url => {
+                          :url  => 'example.com',
+                          :mode => :github
+                        }
+        }.to_not change(RepositoryPostReceiveUrl, :count)
+      end
+
+      it "re-renders the :new template" do
+        post :create, :repository_id => @repository.id,
+                      :repository_post_receive_url => {
+                        :url  => 'example.com',
+                        :mode => :github
+                      }
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+
+
+  describe "GET #edit" do
+    context "with existing post_receive_url" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => @post_receive_url.id
+      end
+
+      it "assigns the requested post_receive_url to @post_receive_url" do
+        expect(assigns(:post_receive_url)).to eq @post_receive_url
+      end
+
+      it "renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+
+    context "with non-existing post_receive_url" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository.id, :id => 100
+      end
+
+      it "renders 404" do
+        expect(response.status).to eq 404
+      end
+    end
+
+    context "with non-matching repository" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :repository_id => @repository2.id, :id => @post_receive_url.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+
+    context "with unsufficient permissions" do
+      before do
+        request.session[:user_id] = FactoryGirl.create(:user).id
+        get :edit, :repository_id => @repository.id, :id => @post_receive_url.id
+      end
+
+      it "renders 403" do
+        expect(response.status).to eq 403
+      end
+    end
+  end
+
+
+  describe "PUT #update" do
+    before do
+      request.session[:user_id] = @user.id
+    end
+
+    context "with valid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @post_receive_url.id,
+                     :repository_post_receive_url => {
+                       :url => 'http://example.com/titi.php'
+                     }
+      end
+
+      it "located the requested @post_receive_url" do
+        expect(assigns(:post_receive_url)).to eq @post_receive_url
+      end
+
+      it "changes @post_receive_url's attributes" do
+        @post_receive_url.reload
+        expect(@post_receive_url.url).to eq 'http://example.com/titi.php'
+      end
+
+      it "redirects to the repository page" do
+        expect(response).to redirect_to success_url
+      end
+    end
+
+    context "with invalid attributes" do
+      before do
+        put :update, :repository_id => @repository.id,
+                     :id => @post_receive_url.id,
+                     :repository_post_receive_url => {
+                       :url => 'example.com'
+                     }
+      end
+
+      it "located the requested @post_receive_url" do
+        expect(assigns(:post_receive_url)).to eq @post_receive_url
+      end
+
+      it "does not change @post_receive_url's attributes" do
+        @post_receive_url.reload
+        expect(@post_receive_url.url).to eq 'http://example.com/toto1.php'
+      end
+
+      it "re-renders the :edit template" do
+        expect(response).to render_template(:edit)
+      end
+    end
+  end
+
+
+  describe 'DELETE destroy' do
+    before :each do
+      request.session[:user_id] = @user.id
+      @post_receive_url_delete = FactoryGirl.create(:repository_post_receive_url, :repository_id => @repository.id)
+    end
+
+    it "deletes the post_receive_url" do
+      expect{
+        delete :destroy, :repository_id => @repository.id, :id => @post_receive_url_delete.id, :format => 'js'
+      }.to change(RepositoryPostReceiveUrl, :count).by(-1)
+    end
+
+    it "redirects to repositories#edit" do
+      delete :destroy, :repository_id => @repository.id, :id => @post_receive_url_delete.id, :format => 'js'
+      expect(response.status).to eq 200
+    end
+  end
+end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
new file mode 100644
index 0000000..09d9041
--- /dev/null
+++ b/spec/controllers/users_controller_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe UsersController do
+
+  USER_KEY1   = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZiNOKQtOrBEPGpLC7KZ+dx4zAGEupIWZMuLBbRZJEAjx959b2AcvK5BH1iQ8z6NkBw64MAnXhXY+cwh8HDKg+7ONnf7U+zyWZ/DTuh9DU1k5EQOAq7QXcZWxXgWIhGlNwu4jgxiyilAG0OfLZcNGZO4vP6cMRhdTuvst1PBxR6htQh2EJaeIiW1BsFcB2RR1x5tJIteAJ2NvBolsSPijVmolsX+y1URL3Pt8W8/jlxnscogZpOQHsDZByUBWEiUZNheVCpCsVUM1LkbL0sIIB40B8rKhchlzJYlRCm8axLbbs2lUtSKZBy0Rk1SiERlnGIGuzIda2h1Dbg7vqbMf3 nicolas@tchoum'
+  DEPLOY_KEY1 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0B/TiFjjGdLJko8/cZoQyL1Z9BMOjeQfpI45ofq6ROy5jFsfvY8hjBUHnmSFxzRsGLNWK9lWbAhW2WLjPomgcv5RrowJsLFlhVJLAgQ6q4g+4i/PccYcZHXqPLqJLIO1Yxvze7eQvOMtzPt2IYljM4kcR47YnwfbvU40Rq+ezdaIUCaQ4ZjRn879htRKt5SPO6qi8Kgd2s8sUjgJqrFZt9dkzwW+frn5VHgmlO5WT3HrmxF5U6un+uIoiMfX5TjNogn1WQm42vd8Q4f6mAQ9EmK5RK24R24m4YT91Q1b5QBb0qoGap0OJWjmNxX4tvuZ8SSRYXzqLERp7Jcy/r11J nicolas@tchoum'
+
+  def create_ssh_key(opts = {})
+    FactoryGirl.create(:gitolite_public_key, opts)
+  end
+
+
+  before(:all) do
+    @user = FactoryGirl.create(:user, :admin => true)
+    User.current = @user
+    @user_key   = create_ssh_key(:user_id => @user.id, :title => 'user_key',   key: USER_KEY1, :key_type => 0)
+    @deploy_key = create_ssh_key(:user_id => @user.id, :title => 'deploy_key', key: DEPLOY_KEY1, :key_type => 1)
+  end
+
+
+  describe "GET #edit" do
+    context "with git hosting patch" do
+      before do
+        request.session[:user_id] = @user.id
+        get :edit, :id => @user.id
+      end
+
+      it "populates an array of gitolite_user_keys" do
+        expect(assigns(:gitolite_user_keys)).to eq [@user_key]
+      end
+
+      it "populates an array of gitolite_deploy_keys" do
+        expect(assigns(:gitolite_deploy_keys)).to eq [@deploy_key]
+      end
+
+      it "populates an empty gitolite_public_key" do
+        expect(assigns(:gitolite_public_key)).to be_an_instance_of(GitolitePublicKey)
+      end
+    end
+  end
+
+end
diff --git a/spec/database_mysql.yml b/spec/database_mysql.yml
new file mode 100644
index 0000000..ccd2035
--- /dev/null
+++ b/spec/database_mysql.yml
@@ -0,0 +1,29 @@
+## MySQL configuration example
+## Data come from environment variables so the test suite can be run
+## on Travis or Jenkins (see https://github.com/codevise/jenkins-mysql-job-databases-plugin)
+production:
+  adapter: mysql2
+  database: <%= ENV['MYSQL_DATABASE'] %>
+  host: <%= ENV['MYSQL_HOST'] %>
+  port: <%= ENV['MYSQL_PORT'] %>
+  username: <%= ENV['MYSQL_USER'] %>
+  password: <%= ENV['MYSQL_PASSWORD'] %>
+  encoding: utf8
+
+development:
+  adapter: mysql2
+  database: <%= ENV['MYSQL_DATABASE'] %>
+  host: <%= ENV['MYSQL_HOST'] %>
+  port: <%= ENV['MYSQL_PORT'] %>
+  username: <%= ENV['MYSQL_USER'] %>
+  password: <%= ENV['MYSQL_PASSWORD'] %>
+  encoding: utf8
+
+test:
+  adapter: mysql2
+  database: <%= ENV['MYSQL_DATABASE'] %>
+  host: <%= ENV['MYSQL_HOST'] %>
+  port: <%= ENV['MYSQL_PORT'] %>
+  username: <%= ENV['MYSQL_USER'] %>
+  password: <%= ENV['MYSQL_PASSWORD'] %>
+  encoding: utf8
diff --git a/spec/database_postgres.yml b/spec/database_postgres.yml
new file mode 100644
index 0000000..96cdc62
--- /dev/null
+++ b/spec/database_postgres.yml
@@ -0,0 +1,21 @@
+# PostgreSQL configuration example
+production:
+  adapter: postgresql
+  database: redmine
+  username: postgres
+  encoding: utf8
+
+development:
+  adapter: postgresql
+  database: redmine
+  username: postgres
+  encoding: utf8
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+  adapter: postgresql
+  database: redmine
+  username: postgres
+  encoding: utf8
diff --git a/spec/factories/gitolite_public_key.rb b/spec/factories/gitolite_public_key.rb
new file mode 100644
index 0000000..b9b1495
--- /dev/null
+++ b/spec/factories/gitolite_public_key.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+
+  factory :gitolite_public_key do |f|
+    f.key_type 0
+    f.title    'test-key'
+    f.key      'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDpqFJzsx3wTi3t3X/eOizU6rdtNQoqg5uSjL89F+Ojjm2/sah3ouzx+3E461FDYaoJL58Qs9eRhL+ev0BY7khYXph8nIVDzNEjhLqjevX+YhpaW9Ll7V807CwAyvMNm08aup/NrrlI/jO+At348/ivJrfO7ClcPhq4+Id9RZfvbrKaitGOURD7q6Bd7xjUjELUN8wmYxu5zvx/2n/5woVdBUMXamTPxOY5y6DxTNJ+EYzrCr+bNb7459rWUvBHUQGI2fXDGmFpGiv6ShKRhRtwob1JHI8QC9OtxonrIUesa2dW6RFneUaM7tfRfffC704Uo7yuSswb7YK+p1A9QIt5 nicolas@tchoum'
+    f.association :user
+  end
+
+end
diff --git a/spec/factories/member.rb b/spec/factories/member.rb
new file mode 100644
index 0000000..ce4672a
--- /dev/null
+++ b/spec/factories/member.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+
+  factory :member do |member|
+  end
+
+end
diff --git a/spec/factories/project.rb b/spec/factories/project.rb
new file mode 100644
index 0000000..0d8a8ca
--- /dev/null
+++ b/spec/factories/project.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+
+  factory :project do |f|
+    f.sequence(:identifier)   { |n| "project#{n}"}
+    f.sequence(:name)         { |n| "Project#{n}"}
+  end
+
+end
diff --git a/spec/factories/repository_deployment_credential.rb b/spec/factories/repository_deployment_credential.rb
new file mode 100644
index 0000000..1d9fcf1
--- /dev/null
+++ b/spec/factories/repository_deployment_credential.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+
+  factory :repository_deployment_credential do |f|
+    f.perm        "RW+"
+    f.association :repository, :factory => :repository_git
+    f.association :user
+    f.association :gitolite_public_key
+  end
+
+end
diff --git a/spec/factories/repository_git.rb b/spec/factories/repository_git.rb
new file mode 100644
index 0000000..a3c5316
--- /dev/null
+++ b/spec/factories/repository_git.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+
+  factory :repository_git, :class => 'Repository::Git' do |f|
+    f.is_default  false
+    f.association :project
+  end
+
+end
diff --git a/spec/factories/repository_git_config_key.rb b/spec/factories/repository_git_config_key.rb
new file mode 100644
index 0000000..dc931ac
--- /dev/null
+++ b/spec/factories/repository_git_config_key.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+
+  factory :repository_git_config_key do |f|
+    f.sequence(:key) { |n| "hookfoo.foo#{n}" }
+    f.value          'bar'
+    f.association    :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_git_extra.rb b/spec/factories/repository_git_extra.rb
new file mode 100644
index 0000000..1ae842c
--- /dev/null
+++ b/spec/factories/repository_git_extra.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+
+  factory :repository_git_extra do |f|
+    f.git_http        0
+    f.default_branch  "master"
+    f.association     :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_git_notification.rb b/spec/factories/repository_git_notification.rb
new file mode 100644
index 0000000..c9c23f9
--- /dev/null
+++ b/spec/factories/repository_git_notification.rb
@@ -0,0 +1,11 @@
+FactoryGirl.define do
+
+  factory :repository_git_notification do |f|
+    f.prefix          '[TEST PROJECT]'
+    f.sender_address  'redmine@example.com'
+    f.include_list    [ 'foo@bar.com', 'bar@foo.com']
+    f.exclude_list    [ 'far@boo.com', 'boo@far.com']
+    f.association     :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_mirror.rb b/spec/factories/repository_mirror.rb
new file mode 100644
index 0000000..b25577d
--- /dev/null
+++ b/spec/factories/repository_mirror.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+
+  factory :repository_mirror do |f|
+    f.sequence(:url) { |n| "ssh://host.xz/path/to/repo#{n}.git" }
+    f.push_mode      0
+    f.association    :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_post_receive_url.rb b/spec/factories/repository_post_receive_url.rb
new file mode 100644
index 0000000..90c2d01
--- /dev/null
+++ b/spec/factories/repository_post_receive_url.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+
+  factory :repository_post_receive_url do |f|
+    f.sequence(:url) { |n| "http://example.com/toto#{n}.php" }
+    f.association    :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_protected_branche.rb b/spec/factories/repository_protected_branche.rb
new file mode 100644
index 0000000..e6f4a65
--- /dev/null
+++ b/spec/factories/repository_protected_branche.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+
+  factory :repository_protected_branche do |f|
+    f.association    :repository, :factory => :repository_git
+  end
+
+end
diff --git a/spec/factories/repository_svn.rb b/spec/factories/repository_svn.rb
new file mode 100644
index 0000000..45dbc38
--- /dev/null
+++ b/spec/factories/repository_svn.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+
+  factory :repository_svn, :class => 'Repository::Subversion' do |f|
+    f.is_default  false
+  end
+
+end
diff --git a/spec/factories/role.rb b/spec/factories/role.rb
new file mode 100644
index 0000000..494dc82
--- /dev/null
+++ b/spec/factories/role.rb
@@ -0,0 +1,67 @@
+FactoryGirl.define do
+
+  factory :role do |f|
+    f.name    "Manager"
+    f.builtin 0
+    f.issues_visibility "all"
+    f.position 1
+    f.permissions  [
+      :add_project,
+      :edit_project,
+      :close_project,
+      :select_project_modules,
+      :manage_members,
+      :manage_versions,
+      :manage_categories,
+      :view_issues,
+      :add_issues,
+      :edit_issues,
+      :manage_issue_relations,
+      :manage_subtasks,
+      :add_issue_notes,
+      :move_issues,
+      :delete_issues,
+      :view_issue_watchers,
+      :add_issue_watchers,
+      :set_issues_private,
+      :set_notes_private,
+      :view_private_notes,
+      :delete_issue_watchers,
+      :manage_public_queries,
+      :save_queries,
+      :view_gantt,
+      :view_calendar,
+      :log_time,
+      :view_time_entries,
+      :edit_time_entries,
+      :delete_time_entries,
+      :manage_news,
+      :comment_news,
+      :view_documents,
+      :add_documents,
+      :edit_documents,
+      :delete_documents,
+      :view_wiki_pages,
+      :export_wiki_pages,
+      :view_wiki_edits,
+      :edit_wiki_pages,
+      :delete_wiki_pages_attachments,
+      :protect_wiki_pages,
+      :delete_wiki_pages,
+      :rename_wiki_pages,
+      :add_messages,
+      :edit_messages,
+      :delete_messages,
+      :manage_boards,
+      :view_files,
+      :manage_files,
+      :browse_repository,
+      :manage_repository,
+      :view_changesets,
+      :manage_related_issues,
+      :manage_project_activities,
+      :create_gitolite_ssh_key
+    ]
+  end
+
+end
diff --git a/spec/factories/user.rb b/spec/factories/user.rb
new file mode 100644
index 0000000..4305bf7
--- /dev/null
+++ b/spec/factories/user.rb
@@ -0,0 +1,13 @@
+FactoryGirl.define do
+
+  factory :user do |f|
+    f.sequence(:login)     { |n| "user#{n}" }
+    f.sequence(:firstname) { |n| "User#{n}" }
+    f.sequence(:lastname)  { |n| "Test#{n}" }
+    f.sequence(:mail)      { |n| "user#{n}@awesome.com" }
+    f.language             "fr"
+    f.hashed_password      "66eb4812e268747f89ec309178e2ea50410653fb"
+    f.salt                 "5abd4e59ac0d483daf2f68d3b6544ff3"
+  end
+
+end
diff --git a/spec/fixtures/branches_payload.yml b/spec/fixtures/branches_payload.yml
new file mode 100644
index 0000000..f673275
--- /dev/null
+++ b/spec/fixtures/branches_payload.yml
@@ -0,0 +1,225 @@
+---
+- :before: 349873f6780acce61770d755f460082f939c35de
+  :after: 4754c319e4565d56d1d680e5a67032f38d7b1686
+  :ref: refs/heads/branch1
+  :commits:
+  - :id: 39eec0e81dd0619f2039e19df6d9b62b5740f15c
+    :message: test
+    :timestamp: 2014-05-22 21:20:54.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/39eec0e81dd0619f2039e19df6d9b62b5740f15c
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c6d5b7772692a0c94c2bc49aa0801c004e5a6b73
+    :message: test
+    :timestamp: 2014-05-22 21:20:54.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c6d5b7772692a0c94c2bc49aa0801c004e5a6b73
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 0116d0a680943e6ff5b41aa1b0d0842c39f20458
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/0116d0a680943e6ff5b41aa1b0d0842c39f20458
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: b914656d7a3de10fa70979771875078ffe7d5f75
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/b914656d7a3de10fa70979771875078ffe7d5f75
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: a6e1da9c875171bb6f15cf4ec97a2c6b75d43eda
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/a6e1da9c875171bb6f15cf4ec97a2c6b75d43eda
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 8034f3c4c7c18845c3e4a3bebc6cefc14083cc78
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/8034f3c4c7c18845c3e4a3bebc6cefc14083cc78
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 4754c319e4565d56d1d680e5a67032f38d7b1686
+    :message: test
+    :timestamp: 2014-05-22 21:20:56.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/4754c319e4565d56d1d680e5a67032f38d7b1686
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
+- :before: 1e063b23e50d274bc667808b1273911b7796b583
+  :after: c51932336314bad33b6a2b7d735205a0686442de
+  :ref: refs/heads/branch2
+  :commits:
+  - :id: 4caf42c4681f39e27c08776a0005c24ef4e76515
+    :message: test
+    :timestamp: 2014-05-22 21:21:03.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/4caf42c4681f39e27c08776a0005c24ef4e76515
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 95d0cddef1682766cbf28e2b101f5ecc61299b46
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/95d0cddef1682766cbf28e2b101f5ecc61299b46
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 01e059c50d418da831afdc6175335a86efef4b64
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/01e059c50d418da831afdc6175335a86efef4b64
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c794954ac2f31a195557bef66dc5b1e1d9253248
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c794954ac2f31a195557bef66dc5b1e1d9253248
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 02b802617b55424a408c4cee9b4c58017d186e4b
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/02b802617b55424a408c4cee9b4c58017d186e4b
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: ff0ae3c64e48de2b33a511694a72118f12a12951
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/ff0ae3c64e48de2b33a511694a72118f12a12951
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 7f8af614170833d59ba5d8e09b8aba36f69ac11f
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/7f8af614170833d59ba5d8e09b8aba36f69ac11f
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 08c1a24b67f6a6113238dfe681ba2c649b678336
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/08c1a24b67f6a6113238dfe681ba2c649b678336
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 942d348461ebaa47c7bca2874de1556af7b30aea
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/942d348461ebaa47c7bca2874de1556af7b30aea
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: a96c9d802c0f5ffe42fde052ee9e37ecfb3a8069
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/a96c9d802c0f5ffe42fde052ee9e37ecfb3a8069
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 5eb8b5a5f2ab6a794974975914cb661f56fe443f
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/5eb8b5a5f2ab6a794974975914cb661f56fe443f
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c51932336314bad33b6a2b7d735205a0686442de
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c51932336314bad33b6a2b7d735205a0686442de
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
diff --git a/spec/fixtures/global_payload.yml b/spec/fixtures/global_payload.yml
new file mode 100644
index 0000000..78d0613
--- /dev/null
+++ b/spec/fixtures/global_payload.yml
@@ -0,0 +1,322 @@
+---
+- :before: 349873f6780acce61770d755f460082f939c35de
+  :after: 4754c319e4565d56d1d680e5a67032f38d7b1686
+  :ref: refs/heads/branch1
+  :commits:
+  - :id: 39eec0e81dd0619f2039e19df6d9b62b5740f15c
+    :message: test
+    :timestamp: 2014-05-22 21:20:54.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/39eec0e81dd0619f2039e19df6d9b62b5740f15c
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c6d5b7772692a0c94c2bc49aa0801c004e5a6b73
+    :message: test
+    :timestamp: 2014-05-22 21:20:54.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c6d5b7772692a0c94c2bc49aa0801c004e5a6b73
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 0116d0a680943e6ff5b41aa1b0d0842c39f20458
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/0116d0a680943e6ff5b41aa1b0d0842c39f20458
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: b914656d7a3de10fa70979771875078ffe7d5f75
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/b914656d7a3de10fa70979771875078ffe7d5f75
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: a6e1da9c875171bb6f15cf4ec97a2c6b75d43eda
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/a6e1da9c875171bb6f15cf4ec97a2c6b75d43eda
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 8034f3c4c7c18845c3e4a3bebc6cefc14083cc78
+    :message: test
+    :timestamp: 2014-05-22 21:20:55.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/8034f3c4c7c18845c3e4a3bebc6cefc14083cc78
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 4754c319e4565d56d1d680e5a67032f38d7b1686
+    :message: test
+    :timestamp: 2014-05-22 21:20:56.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/4754c319e4565d56d1d680e5a67032f38d7b1686
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
+- :before: 1e063b23e50d274bc667808b1273911b7796b583
+  :after: c51932336314bad33b6a2b7d735205a0686442de
+  :ref: refs/heads/branch2
+  :commits:
+  - :id: 4caf42c4681f39e27c08776a0005c24ef4e76515
+    :message: test
+    :timestamp: 2014-05-22 21:21:03.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/4caf42c4681f39e27c08776a0005c24ef4e76515
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 95d0cddef1682766cbf28e2b101f5ecc61299b46
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/95d0cddef1682766cbf28e2b101f5ecc61299b46
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 01e059c50d418da831afdc6175335a86efef4b64
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/01e059c50d418da831afdc6175335a86efef4b64
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c794954ac2f31a195557bef66dc5b1e1d9253248
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c794954ac2f31a195557bef66dc5b1e1d9253248
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 02b802617b55424a408c4cee9b4c58017d186e4b
+    :message: test
+    :timestamp: 2014-05-22 21:21:04.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/02b802617b55424a408c4cee9b4c58017d186e4b
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: ff0ae3c64e48de2b33a511694a72118f12a12951
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/ff0ae3c64e48de2b33a511694a72118f12a12951
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 7f8af614170833d59ba5d8e09b8aba36f69ac11f
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/7f8af614170833d59ba5d8e09b8aba36f69ac11f
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 08c1a24b67f6a6113238dfe681ba2c649b678336
+    :message: test
+    :timestamp: 2014-05-22 21:21:05.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/08c1a24b67f6a6113238dfe681ba2c649b678336
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 942d348461ebaa47c7bca2874de1556af7b30aea
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/942d348461ebaa47c7bca2874de1556af7b30aea
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: a96c9d802c0f5ffe42fde052ee9e37ecfb3a8069
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/a96c9d802c0f5ffe42fde052ee9e37ecfb3a8069
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 5eb8b5a5f2ab6a794974975914cb661f56fe443f
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/5eb8b5a5f2ab6a794974975914cb661f56fe443f
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: c51932336314bad33b6a2b7d735205a0686442de
+    :message: test
+    :timestamp: 2014-05-22 21:21:06.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/c51932336314bad33b6a2b7d735205a0686442de
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
+- :before: 410516ba13d39aa719e129804865643b37b2bbcc
+  :after: ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+  :ref: refs/heads/master
+  :commits:
+  - :id: 16d1777a57eabeae5a3ed7a19da4734b1357bc18
+    :message: test
+    :timestamp: 2014-05-22 21:20:41.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/16d1777a57eabeae5a3ed7a19da4734b1357bc18
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: fac18fd941b691493e9d106a726c5ba1b5e05a72
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/fac18fd941b691493e9d106a726c5ba1b5e05a72
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 50134028ea9b384eab19512e5da762b77425babf
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/50134028ea9b384eab19512e5da762b77425babf
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 8171ba3fa3370f04afb660216a55407534e57ac0
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/8171ba3fa3370f04afb660216a55407534e57ac0
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: d698705a10a2db8144781e3e8cb0f0414da7eb40
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/d698705a10a2db8144781e3e8cb0f0414da7eb40
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 124ba40066c77899de4515577749d7272d43fcbd
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/124ba40066c77899de4515577749d7272d43fcbd
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 2cc238c9250b90c392f0ab8162842e576cce75c0
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/2cc238c9250b90c392f0ab8162842e576cce75c0
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
diff --git a/spec/fixtures/master_payload.yml b/spec/fixtures/master_payload.yml
new file mode 100644
index 0000000..bdc5a1b
--- /dev/null
+++ b/spec/fixtures/master_payload.yml
@@ -0,0 +1,98 @@
+---
+- :before: 410516ba13d39aa719e129804865643b37b2bbcc
+  :after: ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+  :ref: refs/heads/master
+  :commits:
+  - :id: 16d1777a57eabeae5a3ed7a19da4734b1357bc18
+    :message: test
+    :timestamp: 2014-05-22 21:20:41.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/16d1777a57eabeae5a3ed7a19da4734b1357bc18
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: fac18fd941b691493e9d106a726c5ba1b5e05a72
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/fac18fd941b691493e9d106a726c5ba1b5e05a72
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 50134028ea9b384eab19512e5da762b77425babf
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/50134028ea9b384eab19512e5da762b77425babf
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 8171ba3fa3370f04afb660216a55407534e57ac0
+    :message: test
+    :timestamp: 2014-05-22 21:20:42.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/8171ba3fa3370f04afb660216a55407534e57ac0
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: d698705a10a2db8144781e3e8cb0f0414da7eb40
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/d698705a10a2db8144781e3e8cb0f0414da7eb40
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 124ba40066c77899de4515577749d7272d43fcbd
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/124ba40066c77899de4515577749d7272d43fcbd
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: 2cc238c9250b90c392f0ab8162842e576cce75c0
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/2cc238c9250b90c392f0ab8162842e576cce75c0
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  - :id: ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+    :message: test
+    :timestamp: 2014-05-22 21:20:43.000000000 Z
+    :added: []
+    :modified: []
+    :removed: []
+    :url: http://redmine.example.net/projects/test/repository/revisions/ba2cd4a1bf6d18fbb89da2b6359f5938289444cb
+    :author:
+      :name: Nicolas
+      :email: nrodriguez@jbox-web.com
+  :repository:
+    :description: ''
+    :fork: false
+    :forks: 0
+    :homepage: ''
+    :name: test
+    :open_issues: 0
+    :watchers: 0
+    :private: false
+    :url: http://redmine.example.net/projects/test/repository
+    :owner:
+      :name: Redmine
+      :email: redmine@example.net
diff --git a/spec/helpers/gitolite_public_keys_helper_spec.rb b/spec/helpers/gitolite_public_keys_helper_spec.rb
new file mode 100644
index 0000000..3a1a6f7
--- /dev/null
+++ b/spec/helpers/gitolite_public_keys_helper_spec.rb
@@ -0,0 +1,55 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe GitolitePublicKeysHelper do
+
+  TEST_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpOU1DzQzU4/acdt3wWhk43acGs3Jp7jVlnEtc+2C8QFAUiJMrAOzyUnEliwxarGonJ5gKbI9NkqqPpz9LATBQw382+3FjAlptgqn7eGBih0DgwN6wdHflTRdE6sRn7hxB5h50p547n26FpbX9GSOHPhgxSnyvGXnC+YZyTfMiw5JMhw68SfLS8YENrXukg2ItJPspn6mPqIHrcM2NJOG4Bm+1ibYpDfrWJqYp3Q6disgwrsN08pS6lDfoQRiRHXg8WFbQbHloVaYFpdT6VoBQiAydeSpDSYTBJd/v3qTpK8aheC8sdnrddZf1T6L51z7WZ6vPVKQYPjpAxZ4p6eef nicolas@tchoum'
+
+  before(:all) do
+    @admin_user         = FactoryGirl.create(:user, :admin => true)
+
+    @user_without_perm  = FactoryGirl.create(:user)
+    @user_with_perm     = create_user_with_permissions(FactoryGirl.create(:project))
+
+    @gitolite_public_key = FactoryGirl.create(:gitolite_public_key, :user_id => @user_without_perm.id, :key_type => 1, :title => 'foo1', :key => TEST_KEY)
+  end
+
+
+  def create_user_with_permissions(project)
+    role = FactoryGirl.create(:role, :name => 'Manager2')
+    role.permissions << :create_deployment_keys
+    role.save!
+
+    user = FactoryGirl.create(:user)
+
+    members = Member.new(:role_ids => [role.id], :user_id => user.id)
+    project.members << members
+
+    return user
+  end
+
+
+  describe ".keylabel" do
+    context "when current user is the key owner" do
+      before { User.current = @user_without_perm }
+      it { expect(helper.keylabel(@gitolite_public_key)).to eq 'foo1' }
+    end
+
+    context "when current user is not the key owner" do
+      before { User.current = @admin_user }
+      it { expect(helper.keylabel(@gitolite_public_key)).to eq 'user11@foo1' }
+    end
+  end
+
+  describe ".can_create_deployment_keys_for_some_project"
+    context "when current user is admin" do
+      it { expect(helper.can_create_deployment_keys_for_some_project(@admin_user)).to eq true }
+    end
+
+    context "when current user can create_deployment_keys" do
+      it { expect(helper.can_create_deployment_keys_for_some_project(@user_with_perm)).to eq true }
+    end
+
+    context "when current user cannot create_deployment_keys" do
+      it { expect(helper.can_create_deployment_keys_for_some_project(@user_without_perm)).to eq false }
+    end
+end
diff --git a/spec/models/gitolite_public_key_spec.rb b/spec/models/gitolite_public_key_spec.rb
new file mode 100644
index 0000000..72dc21a
--- /dev/null
+++ b/spec/models/gitolite_public_key_spec.rb
@@ -0,0 +1,288 @@
+# coding: utf-8
+
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe GitolitePublicKey do
+
+  SSH_KEY_0 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDpqFJzsx3wTi3t3X/eOizU6rdtNQoqg5uSjL89F+Ojjm2/sah3ouzx+3E461FDYaoJL58Qs9eRhL+ev0BY7khYXph8nIVDzNEjhLqjevX+YhpaW9Ll7V807CwAyvMNm08aup/NrrlI/jO+At348/ivJrfO7ClcPhq4+Id9RZfvbrKaitGOURD7q6Bd7xjUjELUN8wmYxu5zvx/2n/5woVdBUMXamTPxOY5y6DxTNJ+EYzrCr+bNb7459rWUvBHUQGI2fXDGmFpGiv6ShKRhRtwob1JHI8QC9OtxonrIUesa2dW6RFneUaM7tfRfffC704Uo7yuSswb7YK+p1A9QIt5 nicolas@tchoum'
+  SSH_KEY_1 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz0pLXcQWS4gLUimUSLwDOvEmQF8l8EKoj0LjxOyM3y2dpLsn0aiqS0ecA0G/ROomaawop8EZGFetoJKJM468OZlx2aKoQemzvFIq0Mn1ZhcrlA1alAsDYqzZI8iHO4JIS3YbeLLkVGAlYA+bmA5enXN9mGhC9cgoMC79EZiLD9XvOw4iXDjqXaCzFZHU1shMWwaJfpyxBm+Mxs2vtZzwETDqeu9rohNMl60dODf6+JoXYiahP+B+P2iKlL7ORb1YsAH/4ZMsVgRckj8snb4uc3XgwLRNNw+oB78ApZGr0j3Zc32U9rpmulbHIroWO07OV4Xsplnu8lhGvfodA2gjb nicolas@tchoum'
+  SSH_KEY_2 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5+JfM82k03J98GWL6ghJ4TYM8DbvDnVh1s1rUDNlM/1U5rwbgXHOR4xV3lulgYEYRtYeMoL3rt4ZpEyXWkOreOVsUlkW66SZJR5aGVTNJOLX7HruEDqj7RWlt0u0MH6DgBVAJimQrxYN50jYD4XnDUjb/qv55EhPvbJ3jcAb3zuyRXMKZYGNVzVFLUagbvVaOwR23csWSLDTsAEI9JzaxMKvCNRwk3jFepiCovXbw+g0iyvJdp0+AJpC57ZupyxHeX9J2oz7im2UaHHqLa2qUZL6c4PNV/D2p0Bts4Tcnn3OFPL90RF/ao0tjiUFxM3ti8pRHOqRcZHcOgIhKiaLX nicolas@tchoum'
+  SSH_KEY_3 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/pSRh11xbadAh24fQlc0i0dneG0lI+DCkng+bVmumgRvfD0w79vcJ2U1qir2ChjpNvi2n96HUGIEGNV60/VG05JY70mEb//YVBmQ3w0QPO7toEWNms9SQlwR0PN6tarATumFik4MI+8M23P6W8O8OYwsnMmYwaiEU5hDopH88x74MQKjPiRSrhMkGiThMZhLVK6j8yfNPoj9yUxPBWc7zsMCC2uAOfR5Fg6hl2TKGxTi0vecTh1csDcO2agXx42RRiZeIQbv9j0IJjVL8KhXvbndVnJRjGGbxQFAedicw8OrPH7jz6NimmaTooqU9SwaPInK/x3omd297/zzcQm3p nicolas@tchoum'
+  SSH_KEY_4 = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCScLGus1vrZ9OyzOj3TtYa+IHUp5V+2hwcMW7pphGIAPRi5Pe6GwSbSV5GnanerOH9ucmEREaCIdGOzO2zVI35e3RD6wTeW28Ck7JN1r2LSgSvXGvxGyzu0H4Abf66Kajt+lN0/71tbFtoTaJTGSYE3W0rNU6OQBvHf1o4wIyBEFm3cu+e2OrmW/nVIqk8hCN2cU/0OutOWT+vaRLbIU3VQmHftqa4NVxdc4OG48vpZxlJwKexqAHj8Ok/sn3k4CIo8zR0vRaeGPqAmOpm84uEfRWoA71NNS4tIhENlikuD5SJIdyXE9d8CwGTth4jP9/BNT0y4C8cGYljjUWkx3v nicolas@tchoum'
+
+  TEST_USER = 'redmine_user13_14'
+
+  before(:all) do
+    users = FactoryGirl.create_list(:user, 2)
+    @user1 = users[0]
+    @user2 = users[1]
+  end
+
+
+  def build_ssh_key(opts = {})
+    FactoryGirl.build(:gitolite_public_key, opts)
+  end
+
+
+  def create_ssh_key(opts = {})
+    FactoryGirl.create(:gitolite_public_key, opts)
+  end
+
+
+  describe "Valid SSH key build" do
+    before(:each) do
+      @ssh_key = build(:gitolite_public_key)
+    end
+
+    subject { @ssh_key }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:title) }
+    it { should allow_mass_assignment_of(:key) }
+    it { should allow_mass_assignment_of(:key_type) }
+    it { should allow_mass_assignment_of(:delete_when_unused) }
+
+    ## Relations
+    it { should belong_to(:user) }
+    it { should have_many(:repository_deployment_credentials) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:title) }
+    it { should validate_presence_of(:user_id) }
+    it { should validate_presence_of(:key) }
+    it { should validate_presence_of(:key_type) }
+
+    it { should validate_numericality_of(:key_type) }
+
+    it {
+      should ensure_inclusion_of(:key_type).
+      in_array(%w(0 1))
+    }
+
+    it { should ensure_length_of(:title).is_at_most(60) }
+
+    it {
+      should_not allow_value('toto@toto', 'ma_clé').
+      for(:title)
+    }
+
+    it { should respond_to(:identifier) }
+    it { should respond_to(:fingerprint) }
+    it { should respond_to(:owner) }
+    it { should respond_to(:location) }
+    it { should respond_to(:gitolite_path) }
+    it { should respond_to(:to_yaml) }
+
+    ## Attributes content
+    it "can render as string" do
+      expect(@ssh_key.to_s).to eq "test-key"
+    end
+
+    it "has a title" do
+      expect(@ssh_key.title).to eq "test-key"
+    end
+
+    it "is a user key" do
+      expect(@ssh_key.user_key?).to be true
+    end
+
+    it "is not a deploy key" do
+      expect(@ssh_key.deploy_key?).to be false
+    end
+
+    it "must be deleted when unused" do
+      expect(@ssh_key.delete_when_unused?).to be true
+    end
+
+    ## Test data integrity
+    it "should not truncate key" do
+      expect(@ssh_key.key.length).to be == SSH_KEY_0.length
+    end
+
+    ## Test change validation
+    describe "when delete_when_unused is false" do
+      before do
+        @ssh_key.delete_when_unused = false
+      end
+
+      it "should not be deleted when unused" do
+        expect(@ssh_key.delete_when_unused?).to be false
+      end
+    end
+
+    describe "when delete_when_unused is true" do
+      before do
+        @ssh_key.delete_when_unused = true
+      end
+
+      it "should be deleted when unused" do
+        expect(@ssh_key.delete_when_unused?).to be true
+      end
+    end
+  end
+
+
+  describe "Valid SSH key creation" do
+    before do
+      @ssh_key = create_ssh_key(:user_id => @user1.id)
+    end
+
+    subject { @ssh_key }
+
+    it "has an identifier" do
+      expect(@ssh_key.identifier).to eq "#{TEST_USER}@redmine_test_key"
+    end
+
+    it "has a fingerprint" do
+      expect(@ssh_key.fingerprint).to eq "af:af:da:41:5f:7e:6b:dd:e3:d9:bc:78:a6:8a:fc:be"
+    end
+
+    it "has a owner" do
+      expect(@ssh_key.owner).to eq "#{TEST_USER}"
+    end
+
+    it "has a location" do
+      expect(@ssh_key.location).to eq "redmine_test_key"
+    end
+
+    it "has a gitolite_path" do
+      expect(@ssh_key.gitolite_path).to eq "keydir/redmine_git_hosting/#{TEST_USER}/redmine_test_key/#{TEST_USER}.pub"
+    end
+
+    it "can be rendered as yaml" do
+      valid_hash = { :key => SSH_KEY_0, :location => 'redmine_test_key', :owner => TEST_USER, :title => "#{TEST_USER}@redmine_test_key" }
+      expect(@ssh_key.to_yaml).to eq valid_hash
+    end
+
+    context "when identifier is changed" do
+      before { @ssh_key.identifier = "foo" }
+      it { should_not be_valid }
+    end
+
+    context "when key is changed" do
+      before { @ssh_key.key = "foo" }
+      it { should_not be_valid }
+    end
+
+    context "when user_id is changed" do
+      before { @ssh_key.user_id = @user2.id }
+      it { should_not be_valid }
+    end
+
+    context "when key_type is changed" do
+      before { @ssh_key.key_type = 1 }
+      it { should_not be_valid }
+    end
+
+    # Test reset_identifiers
+    context "when identifiers are reset" do
+      before do
+        @old_identifier = @ssh_key.identifier
+        @old_fingerprint = @ssh_key.fingerprint
+
+        @ssh_key.reset_identifiers
+      end
+
+      it { should be_valid }
+
+      it "should have the same identifier" do
+        expect(@ssh_key.identifier).to eq @old_identifier
+      end
+
+      it "should have the same fingerprint" do
+        expect(@ssh_key.fingerprint).to eq @old_fingerprint
+      end
+    end
+  end
+
+
+  describe "Valid SSH key format" do
+    describe "when ssh key format is valid" do
+      ssh_keys = [
+        'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/Ec2gummukPxlpPHZ7K96iBdG5n8v0PJEDvTVZRRFlS0QYa407gj9HuMrPjwEfVHqy+3KZmvKLWQBsSlf0Fn+eAPgnoqwVfZaJnfgkSxJiAzRraKQZX1m2wx2SVMfjw7/1j59zV60UhFiwEQ3Eqlg3xjQmjvrwDM+SoshrWB+TeqwO/K+QEP1ZbURYoCxc92GrLYWKixsAov/zr0loXqul9fydZcWwJE3H/BWC7PTtn4jfjG9+9F+SZ0OMwQvSGKhVlj3GBDtaDBnsuoHGh/CA2W240nwpQysG2BJ5DWXu6vKbjNn6uV91wXeKDEDpuWqv5Vi2XAxGTWKc5lF0IJ5 nicolas@tchoum',
+        'ssh-dss AAAAB3NzaC1kc3MAAACBAKscxrmjRgXtb0ZUaaBUteBtF2cI0vStnni9KVQd94L8qqxvKLbDl5JTKjUvG2s7rD4sVRzBoTkuDGb7OZLf56wJyF3k+k8uNRJzvH/CZbkKM2hjuRVYVort1EwcH7JiEQr7bCLe7MRaltuo/M1vhapwy7fhKxAo9YoYVWiGoFTVAAAAFQDPywT8yFDahFvxtt/95Q9Emq8R7QAAAIBHYnvt3hT9NYy+nOuZG+cQTz0hnVzUIWuj0XF2iyx52s2eSmF0HxIsZ0D9g2A0L1Xr/vlkWBMq/zJZJgJw2Ifys8L47HzjhL8K0Skdm23Z6rQR9hlOEZ5Rcank98U6VRYPWpYk7OLdRDruwXb+Ms5YhIztxsGO3YfRBdSBrW4DMAAAAIAJmmwivw3XoFP6C97LB+tJAjWRYJHpiDwOWNDKu0dZewUzUAo40NuHNgKJS2nsxW0sphaeMtf70IbvDsFQG45I+G2dlt+s19t4YCbVcz7xrw7LceEz+f0UR2/Z+LIK2GPIIoyymOq/kIIxni3xgKDl4mvvt45TTsQzs0zhkmEy/g== nicolas@tchoum',
+        'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLIcYw8NbJc28tDsC+8sf/o14hQmQfdC31OFP0eb5qFVRgEjMJ9mwolqWIW+AcbIAhX2GJVdTLZoUJj6T5PiUtM= nicolas@tchoum'
+      ]
+
+      ssh_keys.each do |valid_key|
+        it "should be valid" do
+          expect(build(:gitolite_public_key, :key => valid_key)).to be_valid
+        end
+      end
+    end
+  end
+
+
+  context "when SSH key already exist" do
+    before do
+      @ssh_key = create_ssh_key(:user_id => @user1.id)
+    end
+
+    ## Test uniqueness validation
+    describe "and title is already taken" do
+      it { expect(build_ssh_key(:user_id => @user1.id, :key => SSH_KEY_1)).not_to be_valid }
+    end
+
+    describe "and is already taken by someone" do
+      it { expect(build_ssh_key(:user_id => @user1.id, :title => 'foo')).not_to be_valid }
+    end
+
+    describe "and is already taken by current user" do
+      before do
+        User.current = @user1
+      end
+
+      it { expect(build_ssh_key(:user_id => @user1.id, :title => 'foo')).not_to be_valid }
+    end
+
+    describe "and is already taken by other user and current user is admin" do
+      before do
+        @user2.admin = true
+        User.current = @user2
+      end
+
+      it { expect(build_ssh_key(:user_id => @user1.id, :title => 'foo')).not_to be_valid }
+    end
+
+    describe "and is already taken by other user and current user is not admin" do
+      before do
+        User.current = @user2
+      end
+
+      it { expect(build_ssh_key(:user_id => @user1.id, :title => 'foo')).not_to be_valid }
+    end
+  end
+
+
+  context "when many keys are saved" do
+    before do
+      create_ssh_key(:user => @user1, :title => 'active1', key: SSH_KEY_1, :key_type => 1)
+      create_ssh_key(:user => @user1, :title => 'active2', key: SSH_KEY_2, :key_type => 1)
+      create_ssh_key(:user => @user2, :title => 'active3', key: SSH_KEY_3)
+      create_ssh_key(:user => @user2, :title => 'active4', key: SSH_KEY_4)
+    end
+
+    it "should have 8 keys" do
+      expect(GitolitePublicKey.all.length).to be == 8
+    end
+
+    it "should have 3 user keys" do
+      expect(GitolitePublicKey.user_key.length).to be == 3
+    end
+
+    it "should have 5 deploy keys" do
+      expect(GitolitePublicKey.deploy_key.length).to be == 5
+    end
+
+    it "user1 should have 2 keys" do
+      expect(GitolitePublicKey.by_user(@user1).length).to be == 2
+    end
+
+    it "user2 should have 2 keys" do
+      expect(GitolitePublicKey.by_user(@user2).length).to be == 2
+    end
+  end
+
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
new file mode 100644
index 0000000..71e0fc8
--- /dev/null
+++ b/spec/models/project_spec.rb
@@ -0,0 +1,36 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Project do
+
+  before(:all) do
+    @project    = create(:project)
+
+    @git_repo_1 = create(:repository_git, :project => @project, :is_default => true)
+    @git_repo_2 = create(:repository_git, :project => @project, :identifier => 'git-repo-test')
+
+    @svn_repo_1 = create(:repository_svn, :project => @project, :identifier => 'svn-repo-test', :url => 'http://svn-repo-test')
+  end
+
+  subject { @project }
+
+  ## Test relations
+  it { should respond_to(:gitolite_repos) }
+  it { should respond_to(:repo_blank_ident) }
+
+  it "should have 1 repository with blank ident" do
+    expect(@project.repo_blank_ident).to eq @git_repo_1
+  end
+
+  it "should have 2 Git repositories" do
+    expect(@project.gitolite_repos).to eq [@git_repo_1, @git_repo_2]
+  end
+
+  it "should not match existing repository identifier" do
+    expect(build(:project, :identifier => 'git-repo-test')).to be_invalid
+  end
+
+  it "should not match Gitolite Admin repository identifier" do
+    expect(build(:project, :identifier => 'gitolite-admin')).to be_invalid
+  end
+
+end
diff --git a/spec/models/repository_deployment_credential_spec.rb b/spec/models/repository_deployment_credential_spec.rb
new file mode 100644
index 0000000..dd151f1
--- /dev/null
+++ b/spec/models/repository_deployment_credential_spec.rb
@@ -0,0 +1,77 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryDeploymentCredential do
+
+  DEPLOY_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz0pLXcQWS4gLUimUSLwDOvEmQF8l8EKoj0LjxOyM3y2dpLsn0aiqS0ecA0G/ROomaawop8EZGFetoJKJM468OZlx2aKoQemzvFIq0Mn1ZhcrlA1alAsDYqzZI8iHO4JIS3YbeLLkVGAlYA+bmA5enXN9mGhC9cgoMC79EZiLD9XvOw4iXDjqXaCzFZHU1shMWwaJfpyxBm+Mxs2vtZzwETDqeu9rohNMl60dODf6+JoXYiahP+B+P2iKlL7ORb1YsAH/4ZMsVgRckj8snb4uc3XgwLRNNw+oB78ApZGr0j3Zc32U9rpmulbHIroWO07OV4Xsplnu8lhGvfodA2gjb nicolas@tchoum'
+  USER_KEY   = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5+JfM82k03J98GWL6ghJ4TYM8DbvDnVh1s1rUDNlM/1U5rwbgXHOR4xV3lulgYEYRtYeMoL3rt4ZpEyXWkOreOVsUlkW66SZJR5aGVTNJOLX7HruEDqj7RWlt0u0MH6DgBVAJimQrxYN50jYD4XnDUjb/qv55EhPvbJ3jcAb3zuyRXMKZYGNVzVFLUagbvVaOwR23csWSLDTsAEI9JzaxMKvCNRwk3jFepiCovXbw+g0iyvJdp0+AJpC57ZupyxHeX9J2oz7im2UaHHqLa2qUZL6c4PNV/D2p0Bts4Tcnn3OFPL90RF/ao0tjiUFxM3ti8pRHOqRcZHcOgIhKiaLX nicolas@tchoum'
+
+  before(:all) do
+    users = create_list(:user, 2)
+    @user1 = users[0]
+    @user2 = users[1]
+
+    @deploy_key = create(:gitolite_public_key, :user => @user1, :key_type => 1, :title => 'foo1', :key => DEPLOY_KEY)
+    @user_key   = create(:gitolite_public_key, :user => @user1, :key_type => 0, :title => 'foo2', :key => USER_KEY)
+  end
+
+
+  def build_deployment_credential(opts = {})
+    build(:repository_deployment_credential, opts)
+  end
+
+
+  describe "Valid RepositoryDeploymentCredential creation" do
+    before(:each) do
+      @deployment_credential = build_deployment_credential(:user => @user1, :gitolite_public_key => @deploy_key)
+    end
+
+    subject { @deployment_credential }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:perm) }
+    it { should allow_mass_assignment_of(:active) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+    it { should belong_to(:gitolite_public_key) }
+    it { should belong_to(:user) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:gitolite_public_key_id) }
+    it { should validate_presence_of(:user_id) }
+    it { should validate_presence_of(:perm) }
+
+    it {
+      should ensure_inclusion_of(:perm).
+      in_array(%w(R RW+))
+    }
+
+    ## Attributes content
+    it "is a active credential" do
+      expect(@deployment_credential.active?).to be true
+    end
+
+    describe "when active is false" do
+      before { @deployment_credential.active = false }
+      it 'shoud be inactive' do
+        expect(@deployment_credential.active?).to be false
+      end
+    end
+  end
+
+  context "when key is not a deployment key" do
+    it 'should not be valid' do
+      expect(build_deployment_credential(:user => @user1, :gitolite_public_key => @user_key)).not_to be_valid
+    end
+  end
+
+  context "when user id is not the owner of deployment key" do
+    it 'should not be valid' do
+      expect(build_deployment_credential(:user => @user2, :gitolite_public_key => @user_key)).not_to be_valid
+    end
+  end
+
+end
diff --git a/spec/models/repository_git_config_key_spec.rb b/spec/models/repository_git_config_key_spec.rb
new file mode 100644
index 0000000..b678407
--- /dev/null
+++ b/spec/models/repository_git_config_key_spec.rb
@@ -0,0 +1,48 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryGitConfigKey do
+
+  describe "Valid RepositoryGitConfigKey creation" do
+    before(:each) do
+      @git_config_key = build(:repository_git_config_key)
+    end
+
+    subject { @git_config_key }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:key) }
+    it { should allow_mass_assignment_of(:value) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:key) }
+    it { should validate_presence_of(:value) }
+
+    it { should validate_uniqueness_of(:key).scoped_to(:repository_id) }
+
+    it {
+      should allow_value('hookfoo.foo', 'hookfoo.foo.bar').
+      for(:key)
+    }
+
+    it {
+      should_not allow_value('hookfoo').
+      for(:key)
+    }
+
+    context "when key is updated" do
+      before do
+        @git_config_key.save
+        @git_config_key.key = 'hookbar.foo'
+        @git_config_key.save
+      end
+
+      it { should be_valid }
+    end
+  end
+end
diff --git a/spec/models/repository_git_extra_spec.rb b/spec/models/repository_git_extra_spec.rb
new file mode 100644
index 0000000..4021c65
--- /dev/null
+++ b/spec/models/repository_git_extra_spec.rb
@@ -0,0 +1,64 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryGitExtra do
+
+  describe "Valid RepositoryGitExtra creation" do
+    before(:each) do
+      @git_extra = create(:repository_git_extra)
+    end
+
+    subject { @git_extra }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:git_http) }
+    it { should allow_mass_assignment_of(:git_daemon) }
+    it { should allow_mass_assignment_of(:git_notify) }
+    it { should allow_mass_assignment_of(:default_branch) }
+    it { should allow_mass_assignment_of(:protected_branch) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:git_http) }
+    it { should validate_presence_of(:default_branch) }
+    it { should validate_presence_of(:key) }
+
+    it { should validate_uniqueness_of(:repository_id) }
+
+    it { should validate_numericality_of(:git_http) }
+
+    it {
+      should ensure_inclusion_of(:git_http).
+      in_array(%w(0 1 2 3))
+    }
+
+    it "should have default values for git_http" do
+      expect(@git_extra.git_http).to eq 0
+    end
+
+    it "should have default values for git_daemon" do
+      expect(@git_extra.git_daemon).to be true
+    end
+
+    it "should have default values for git_notify" do
+      expect(@git_extra.git_notify).to be true
+    end
+
+    it "should have default values for default_branch" do
+      expect(@git_extra.default_branch).to eq 'master'
+    end
+
+    it "should have default values for protected_branch" do
+      expect(@git_extra.protected_branch).to be false
+    end
+
+    it "should have default values for key" do
+      expect(@git_extra.key).to match /\A[A-Z]+\z/
+    end
+  end
+
+end
diff --git a/spec/models/repository_git_notification_spec.rb b/spec/models/repository_git_notification_spec.rb
new file mode 100644
index 0000000..5307948
--- /dev/null
+++ b/spec/models/repository_git_notification_spec.rb
@@ -0,0 +1,98 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryGitNotification do
+
+  VALID_MAIL   = [ 'user@foo.COM', 'A_US-ER@f.b.org', 'frst.lst@foo.jp', 'a+b@baz.cn' ]
+  INVALID_MAIL = [ 'user@foo,com', 'user_at_foo.org', 'example.user@foo.', 'foo@bar_baz.com', 'foo@bar+baz.com', 'foo@bar..com' ]
+
+
+  describe "Valid RepositoryGitNotification creation" do
+    before(:each) do
+      @git_notification = build(:repository_git_notification)
+    end
+
+    subject { @git_notification }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:prefix) }
+    it { should allow_mass_assignment_of(:sender_address) }
+    it { should allow_mass_assignment_of(:include_list) }
+    it { should allow_mass_assignment_of(:exclude_list) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+
+    it { should validate_uniqueness_of(:repository_id) }
+
+    it {
+      should allow_value(*VALID_MAIL).
+      for(:sender_address)
+    }
+
+    it {
+      should_not allow_value(*INVALID_MAIL).
+      for(:sender_address)
+    }
+
+    ## Serializations
+    it { should serialize(:include_list) }
+    it { should serialize(:exclude_list) }
+
+    ## Attributes content
+    it { expect(@git_notification.prefix).to eq "[TEST PROJECT]" }
+    it { expect(@git_notification.sender_address).to eq "redmine@example.com" }
+    it { expect(@git_notification.include_list).to eq [ 'foo@bar.com', 'bar@foo.com'] }
+    it { expect(@git_notification.exclude_list).to eq [ 'far@boo.com', 'boo@far.com'] }
+
+    context "when include_list contains emails with valid format" do
+      before { @git_notification.include_list = VALID_MAIL }
+      it "should be valid" do
+        expect(@git_notification).to be_valid
+      end
+    end
+
+    context "when include_list contains emails with invalid format" do
+      before { @git_notification.include_list = INVALID_MAIL }
+      it "should be valid" do
+        expect(@git_notification).not_to be_valid
+      end
+    end
+
+    context "when exclude_list contains emails with valid format" do
+      before { @git_notification.exclude_list = VALID_MAIL }
+      it "should be valid" do
+        expect(@git_notification).to be_valid
+      end
+    end
+
+    context "when exclude_list contains emails with invalid format" do
+      before { @git_notification.exclude_list = INVALID_MAIL }
+      it "should be valid" do
+        expect(@git_notification).not_to be_valid
+      end
+    end
+
+    describe "when emails is in both list" do
+      addresses = [
+        'user@foo.COM',
+        'A_US-ER@f.b.org',
+        'frst.lst@foo.jp',
+        'a+b@baz.cn'
+      ]
+
+      before do
+        @git_notification.include_list = addresses
+        @git_notification.exclude_list = addresses
+      end
+
+      it "should be invalid" do
+        expect(@git_notification).not_to be_valid
+      end
+    end
+  end
+end
diff --git a/spec/models/repository_git_spec.rb b/spec/models/repository_git_spec.rb
new file mode 100644
index 0000000..a412639
--- /dev/null
+++ b/spec/models/repository_git_spec.rb
@@ -0,0 +1,1064 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Repository::Git do
+
+  GIT_USER = 'git'
+
+  before(:all)  do
+    Setting.plugin_redmine_git_hosting[:gitolite_redmine_storage_dir] = 'redmine/'
+    Setting.plugin_redmine_git_hosting[:http_server_subdir] = 'git/'
+    User.current = nil
+
+    @project_parent = FactoryGirl.create(:project, :identifier => 'project-parent')
+    @project_child  = FactoryGirl.create(:project, :identifier => 'project-child', :parent_id => @project_parent.id)
+  end
+
+
+  def build_git_repository(opts = {})
+    FactoryGirl.build(:repository_git, opts)
+  end
+
+
+  def create_git_repository(opts = {})
+    FactoryGirl.create(:repository_git, opts)
+  end
+
+
+  def create_user_with_permissions(project)
+    role = FactoryGirl.create(:role)
+    user = FactoryGirl.create(:user, :login => 'redmine-test-user')
+
+    members = Member.new(:role_ids => [role.id], :user_id => user.id)
+    project.members << members
+
+    return user
+  end
+
+
+  describe "common_tests" do
+    before(:each) do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = 'true'
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'false'
+
+      @repository_1 = create_git_repository(:project => @project_child, :is_default => true)
+      @repository_2 = create_git_repository(:project => @project_child, :identifier => 'repo-test')
+    end
+
+    subject { @repository_1 }
+
+    ## Relations
+    it { should have_many(:mirrors) }
+    it { should have_many(:post_receive_urls) }
+    it { should have_many(:deployment_credentials) }
+    it { should have_many(:git_config_keys) }
+    it { should have_many(:protected_branches) }
+
+    it { should have_one(:git_extra) }
+    it { should have_one(:git_notification) }
+
+    it { should be_valid }
+
+    ## Attributes
+    it { should respond_to(:identifier) }
+    it { should respond_to(:url) }
+    it { should respond_to(:root_url) }
+
+    ## Methods
+    it { should respond_to(:extra) }
+
+    it { should respond_to(:report_last_commit_with_git_hosting) }
+    it { should respond_to(:extra_report_last_commit_with_git_hosting) }
+
+    it { should respond_to(:git_cache_id) }
+    it { should respond_to(:redmine_name) }
+
+    it { should respond_to(:gitolite_repository_path) }
+    it { should respond_to(:gitolite_repository_name) }
+    it { should respond_to(:redmine_repository_path) }
+
+    it { should respond_to(:new_repository_name) }
+    it { should respond_to(:old_repository_name) }
+
+    it { should respond_to(:http_user_login) }
+    it { should respond_to(:git_access_path) }
+    it { should respond_to(:http_access_path) }
+
+    it { should respond_to(:ssh_url) }
+    it { should respond_to(:git_url) }
+    it { should respond_to(:http_url) }
+    it { should respond_to(:https_url) }
+
+    it { should respond_to(:available_urls) }
+
+    it { should respond_to(:default_list) }
+    it { should respond_to(:mail_mapping) }
+
+    it { should respond_to(:get_full_parent_path) }
+    it { should respond_to(:exists_in_gitolite?) }
+    it { should respond_to(:gitolite_hook_key) }
+
+    ## Test attributes more specifically
+    it { expect(@repository_1.report_last_commit_with_git_hosting).to be true }
+    it { expect(@repository_1.extra_report_last_commit_with_git_hosting).to be true }
+
+    it { expect(@repository_1.extra[:git_http]).to eq 1 }
+    it { expect(@repository_1.extra[:git_daemon]).to be false }
+    it { expect(@repository_1.extra[:git_notify]).to be false }
+    it { expect(@repository_1.extra[:default_branch]).to eq 'master' }
+
+    it { expect(@repository_1.available_urls).to be_a(Hash) }
+
+
+    describe "invalid cases" do
+      it "should not allow identifier gitolite-admin" do
+        expect(build_git_repository(:project => @project_parent, :identifier => 'gitolite-admin')).to be_invalid
+      end
+
+      context "when blank identifier" do
+        before do
+          @repository_1.identifier = 'gitolite-admin'
+        end
+        it "should not allow identifier changes" do
+          expect(@repository_1).to be_invalid
+          expect(@repository_1.identifier).to eq 'gitolite-admin'
+        end
+      end
+
+      context "when non blank identifier" do
+        before do
+          @repository_2.identifier = 'gitolite-admin'
+        end
+        it "should not allow identifier changes" do
+          expect(@repository_2).to be_valid
+          expect(@repository_2.identifier).to eq 'repo-test'
+        end
+      end
+    end
+
+
+    describe "Test uniqueness" do
+      context "when blank identifier is already taken by a repository" do
+        it { expect(build_git_repository(:project => @project_child, :identifier => '')).to be_invalid }
+      end
+
+      context "when set as default and blank identifier is already taken by a repository" do
+        it { expect(build_git_repository(:project => @project_child, :identifier => '', :is_default => true)).to be_invalid }
+      end
+
+      context "when identifier is already taken by a project" do
+        it { expect(build_git_repository(:project => @project_child, :identifier => 'project-child')).to be_invalid }
+      end
+
+      context "when identifier is already taken by a repository with same project" do
+        it { expect(build_git_repository(:project => @project_child, :identifier => 'repo-test')).to be_invalid }
+      end
+
+      context "when identifier are not unique" do
+        it { expect(build_git_repository(:project => @project_parent, :identifier => 'repo-test')).to be_valid }
+      end
+
+      context "when identifier are unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'true'
+        end
+
+        it { expect(build_git_repository(:project => @project_parent, :identifier => 'repo-test')).to be_invalid }
+      end
+    end
+
+
+    describe "#available_urls" do
+      context "with no option" do
+        before do
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = {}
+
+        it "should return an empty Hash" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with all options" do
+        before do
+          @user = create_user_with_permissions(@project_child)
+          User.current = @user
+
+          @repository_1.extra[:git_daemon] = true
+          @repository_1.extra[:git_http]   = 2
+          @repository_1.save
+        end
+
+        my_hash = {
+          :ssh   => {:url => "ssh://#{GIT_USER}@localhost/redmine/project-parent/project-child.git",             :commiter => "false"},
+          :https => {:url => "https://redmine-test-user@localhost/git/project-parent/project-child.git", :commiter => "false"},
+          :http  => {:url => "http://redmine-test-user@localhost/git/project-parent/project-child.git",  :commiter => "false"},
+          :git   => {:url => "git://localhost/redmine/project-parent/project-child.git",                 :commiter => "false"}
+        }
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with git daemon" do
+        before do
+          User.current = nil
+
+          @repository_1.extra[:git_daemon] = true
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = {:git => {:url=>"git://localhost/redmine/project-parent/project-child.git", :commiter=>"false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with ssh" do
+        before do
+          @user = create_user_with_permissions(@project_child)
+          User.current = @user
+
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = { :ssh => {:url => "ssh://#{GIT_USER}@localhost/redmine/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with http" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 3
+          @repository_1.save
+        end
+
+        my_hash = { :http => {:url => "http://localhost/git/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with https" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 1
+          @repository_1.save
+        end
+
+        my_hash = { :https => {:url => "https://localhost/git/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with http and https" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 2
+          @repository_1.save
+        end
+
+        my_hash = {
+          :https => {:url => "https://localhost/git/project-parent/project-child.git", :commiter => "false"},
+          :http  => {:url => "http://localhost/git/project-parent/project-child.git",  :commiter => "false"}
+        }
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+    end
+
+    describe "Repository::Git class" do
+      it { expect(Repository::Git).to respond_to(:repo_ident_unique?) }
+      it { expect(Repository::Git).to respond_to(:have_duplicated_identifier?) }
+      it { expect(Repository::Git).to respond_to(:repo_path_to_git_cache_id) }
+      it { expect(Repository::Git).to respond_to(:find_by_path) }
+
+      describe ".repo_ident_unique?" do
+        it { expect(Repository::Git.repo_ident_unique?).to be false }
+      end
+
+      describe ".have_duplicated_identifier?" do
+        it { expect(Repository::Git.have_duplicated_identifier?).to be false }
+      end
+
+      describe ".repo_path_to_git_cache_id" do
+        describe "when repo path is not found" do
+          before do
+            @git_cache_id = Repository::Git.repo_path_to_git_cache_id('foo.git')
+          end
+
+          it { expect(@git_cache_id).to be nil }
+        end
+      end
+    end
+  end
+
+
+  def collection_of_unique_repositories
+    @repository_1 = create_git_repository(:project => @project_child, :is_default => true)
+    @repository_2 = create_git_repository(:project => @project_child, :identifier => 'repo1-test')
+
+    @repository_3 = create_git_repository(:project => @project_parent, :is_default => true)
+    @repository_4 = create_git_repository(:project => @project_parent, :identifier => 'repo2-test')
+  end
+
+
+  def collection_of_non_unique_repositories
+    @repository_1 = create_git_repository(:project => @project_child, :is_default => true)
+    @repository_2 = create_git_repository(:project => @project_child, :identifier => 'repo-test')
+
+    @repository_3 = create_git_repository(:project => @project_parent, :is_default => true)
+    @repository_4 = create_git_repository(:project => @project_parent, :identifier => 'repo-test')
+  end
+
+
+  context "when hierarchical_organisation with non_unique_identifier" do
+    before(:each) do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = 'true'
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'false'
+      collection_of_non_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it "should be false" do
+        expect(Repository::Git.repo_ident_unique?).to be false
+      end
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it "should be true" do
+        expect(Repository::Git.have_duplicated_identifier?).to be true
+      end
+    end
+
+
+    describe "repository1" do
+      it "should be default repository" do
+        expect(@repository_1.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_1.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_1.url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_1.root_url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_1.git_cache_id).to eq 'project-child'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_1.redmine_name).to eq 'project-child'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_1.redmine_repository_path).to eq 'project-parent/project-child'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_1.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-parent/project-child.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_1.git_url).to eq 'git://localhost/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_1.http_url).to eq 'http://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_1.https_url).to eq 'https://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_1.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_1.git_access_path).to eq 'redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_1.http_access_path).to eq 'git/project-parent/project-child.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_1.new_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_1.old_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+    end
+
+
+    describe "repository2" do
+      it "should not be default repository" do
+        expect(@repository_2.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_2.identifier).to eq 'repo-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_2.url).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_2.root_url).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_2.git_cache_id).to eq 'project-child/repo-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_2.redmine_name).to eq 'repo-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_2.gitolite_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_2.redmine_repository_path).to eq 'project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_2.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-parent/project-child/repo-test.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_2.git_url).to eq 'git://localhost/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_2.http_url).to eq 'http://localhost/git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_2.https_url).to eq 'https://localhost/git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_2.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_2.git_access_path).to eq 'redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_2.http_access_path).to eq 'git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_2.new_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_2.old_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+    end
+
+
+    describe "repository3" do
+      it "should be default repository" do
+        expect(@repository_3.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_3.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_3.url).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_3.root_url).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_3.git_cache_id).to eq 'project-parent'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_3.redmine_name).to eq 'project-parent'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_3.gitolite_repository_path).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_3.gitolite_repository_name).to eq 'redmine/project-parent'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_3.redmine_repository_path).to eq 'project-parent'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_3.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-parent.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_3.git_url).to eq 'git://localhost/redmine/project-parent.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_3.http_url).to eq 'http://localhost/git/project-parent.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_3.https_url).to eq 'https://localhost/git/project-parent.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_3.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_3.git_access_path).to eq 'redmine/project-parent.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_3.http_access_path).to eq 'git/project-parent.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_3.new_repository_name).to eq 'redmine/project-parent'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_3.old_repository_name).to eq 'redmine/project-parent'
+      end
+    end
+
+
+    describe "repository4" do
+      it "should not be default repository" do
+        expect(@repository_4.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_4.identifier).to eq 'repo-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_4.url).to eq 'repositories/redmine/project-parent/repo-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_4.root_url).to eq 'repositories/redmine/project-parent/repo-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_4.git_cache_id).to eq 'project-parent/repo-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_4.redmine_name).to eq 'repo-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_4.gitolite_repository_path).to eq 'repositories/redmine/project-parent/repo-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_4.gitolite_repository_name).to eq 'redmine/project-parent/repo-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_4.redmine_repository_path).to eq 'project-parent/repo-test'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_4.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-parent/repo-test.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_4.git_url).to eq 'git://localhost/redmine/project-parent/repo-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_4.http_url).to eq 'http://localhost/git/project-parent/repo-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_4.https_url).to eq 'https://localhost/git/project-parent/repo-test.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_4.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_4.git_access_path).to eq 'redmine/project-parent/repo-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_4.http_access_path).to eq 'git/project-parent/repo-test.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_4.new_repository_name).to eq 'redmine/project-parent/repo-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_4.old_repository_name).to eq 'redmine/project-parent/repo-test'
+      end
+    end
+
+
+    describe ".repo_path_to_git_cache_id" do
+      before do
+        @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+        @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+        @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+        @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+        @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+        @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+        @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+        @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+      end
+
+      describe "repositories should match" do
+        it { expect(@repo1).to eq @repository_1 }
+        it { expect(@repo2).to eq @repository_2 }
+        it { expect(@repo3).to eq @repository_3 }
+        it { expect(@repo4).to eq @repository_4 }
+
+        it { expect(@git_cache_id1).to eq 'project-child' }
+        it { expect(@git_cache_id2).to eq 'project-child/repo-test' }
+        it { expect(@git_cache_id3).to eq 'project-parent' }
+        it { expect(@git_cache_id4).to eq 'project-parent/repo-test' }
+      end
+    end
+  end
+
+
+  context "when flat_organisation with unique_identifier" do
+    before(:each) do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = 'false'
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'true'
+      collection_of_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it "should be false" do
+        expect(Repository::Git.repo_ident_unique?).to be true
+      end
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it "should be true" do
+        expect(Repository::Git.have_duplicated_identifier?).to be false
+      end
+    end
+
+
+    describe "repository1" do
+      it "should be default repository" do
+        expect(@repository_1.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_1.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_1.url).to eq 'repositories/redmine/project-child.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_1.root_url).to eq 'repositories/redmine/project-child.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_1.git_cache_id).to eq 'project-child'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_1.redmine_name).to eq 'project-child'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-child.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-child'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_1.redmine_repository_path).to eq  'project-child'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_1.new_repository_name).to eq 'redmine/project-child'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_1.old_repository_name).to eq 'redmine/project-child'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_1.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_1.git_access_path).to eq 'redmine/project-child.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_1.http_access_path).to eq 'git/project-child.git'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_1.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-child.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_1.git_url).to eq 'git://localhost/redmine/project-child.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_1.http_url).to eq 'http://localhost/git/project-child.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_1.https_url).to eq 'https://localhost/git/project-child.git'
+      end
+    end
+
+
+    describe "repository2" do
+      it "should not be default repository" do
+        expect(@repository_2.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_2.identifier).to eq 'repo1-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_2.url).to eq 'repositories/redmine/repo1-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_2.root_url).to eq 'repositories/redmine/repo1-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_2.git_cache_id).to eq 'repo1-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_2.redmine_name).to eq 'repo1-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/repo1-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_2.gitolite_repository_name).to eq 'redmine/repo1-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_2.redmine_repository_path).to eq  'repo1-test'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_2.new_repository_name).to eq 'redmine/repo1-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_2.old_repository_name).to eq 'redmine/repo1-test'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_2.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_2.git_access_path).to eq 'redmine/repo1-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_2.http_access_path).to eq 'git/repo1-test.git'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_2.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/repo1-test.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_2.git_url).to eq 'git://localhost/redmine/repo1-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_2.http_url).to eq 'http://localhost/git/repo1-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_2.https_url).to eq 'https://localhost/git/repo1-test.git'
+      end
+    end
+
+
+    describe "repository3" do
+      it "should not be default repository" do
+        expect(@repository_3.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_3.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_3.url).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_3.root_url).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_3.git_cache_id).to eq 'project-parent'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_3.redmine_name).to eq 'project-parent'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_3.gitolite_repository_path).to eq 'repositories/redmine/project-parent.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_3.gitolite_repository_name).to eq 'redmine/project-parent'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_3.redmine_repository_path).to eq  'project-parent'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_3.new_repository_name).to eq 'redmine/project-parent'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_3.old_repository_name).to eq 'redmine/project-parent'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_3.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_3.git_access_path).to eq 'redmine/project-parent.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_3.http_access_path).to eq 'git/project-parent.git'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_3.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/project-parent.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_3.git_url).to eq 'git://localhost/redmine/project-parent.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_3.http_url).to eq 'http://localhost/git/project-parent.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_3.https_url).to eq 'https://localhost/git/project-parent.git'
+      end
+    end
+
+
+    describe "repository4" do
+      it "should not be default repository" do
+        expect(@repository_4.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_4.identifier).to eq 'repo2-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_4.url).to eq 'repositories/redmine/repo2-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_4.root_url).to eq 'repositories/redmine/repo2-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_4.git_cache_id).to eq 'repo2-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_4.redmine_name).to eq 'repo2-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_4.gitolite_repository_path).to eq 'repositories/redmine/repo2-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_4.gitolite_repository_name).to eq 'redmine/repo2-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_4.redmine_repository_path).to eq  'repo2-test'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_4.new_repository_name).to eq 'redmine/repo2-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_4.old_repository_name).to eq 'redmine/repo2-test'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_4.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_4.git_access_path).to eq 'redmine/repo2-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_4.http_access_path).to eq 'git/repo2-test.git'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_4.ssh_url).to eq "ssh://#{GIT_USER}@localhost/redmine/repo2-test.git"
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_4.git_url).to eq 'git://localhost/redmine/repo2-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_4.http_url).to eq 'http://localhost/git/repo2-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_4.https_url).to eq 'https://localhost/git/repo2-test.git'
+      end
+    end
+
+
+    describe ".repo_path_to_git_cache_id" do
+      before do
+        @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+        @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+        @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+        @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+        @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+        @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+        @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+        @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+      end
+
+      describe "repositories should match" do
+        it { expect(@repo1).to eq @repository_1 }
+        it { expect(@repo2).to eq @repository_2 }
+        it { expect(@repo3).to eq @repository_3 }
+        it { expect(@repo4).to eq @repository_4 }
+
+        it { expect(@git_cache_id1).to eq 'project-child' }
+        it { expect(@git_cache_id2).to eq 'repo1-test' }
+        it { expect(@git_cache_id3).to eq 'project-parent' }
+        it { expect(@git_cache_id4).to eq 'repo2-test' }
+      end
+    end
+  end
+
+
+  # describe "Gitolite specific tests" do
+  #   describe "repo foo" do
+  #     before do
+  #       Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = 'true'
+  #       Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'false'
+
+  #       @foo = create_git_repository(:project => @project_child, :identifier => 'foo')
+  #       RedmineGitolite::GitHosting.resync_gitolite(:add_repository, @foo.id, :create_readme_file => true)
+  #       @foo.fetch_changesets
+  #     end
+
+  #     it "should create repositories" do
+  #       expect(@foo.exists_in_gitolite?).to be true
+  #       expect(@foo.empty?).to be false
+  #     end
+  #   end
+
+  #   describe "repo bar" do
+  #     before do
+  #       Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = 'true'
+  #       Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = 'false'
+
+  #       @bar = create_git_repository(:project => @project_child, :identifier => 'bar')
+  #       RedmineGitolite::GitHosting.resync_gitolite(:add_repository, @bar.id, :create_readme_file => false)
+  #       @bar.fetch_changesets
+  #     end
+
+  #     it "should create repositories" do
+  #       expect(@bar.exists_in_gitolite?).to be true
+  #       expect(@bar.empty?).to be true
+  #     end
+  #   end
+
+  # end
+end
diff --git a/spec/models/repository_mirror_spec.rb b/spec/models/repository_mirror_spec.rb
new file mode 100644
index 0000000..505f74c
--- /dev/null
+++ b/spec/models/repository_mirror_spec.rb
@@ -0,0 +1,187 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryMirror do
+
+  MIRROR_URL = 'ssh://host.xz/path/to/repo.git'
+
+  VALID_URLS = [
+    'ssh://user@host.xz:2222/path/to/repo.git',
+    'ssh://user@host.xz/path/to/repo.git',
+    'ssh://host.xz:2222/path/to/repo.git',
+    'ssh://host.xz/path/to/repo.git',
+    'ssh://user@host.xz/path/to/repo.git',
+    'ssh://host.xz/path/to/repo.git',
+    'ssh://user@host.xz/~user/path/to/repo.git',
+    'ssh://host.xz/~user/path/to/repo.git',
+    'ssh://user@host.xz/~/path/to/repo.git',
+    'ssh://host.xz/~/path/to/repo.git'
+  ]
+
+
+  def build_mirror(opts = {})
+    build(:repository_mirror, opts)
+  end
+
+
+  describe "Valid RepositoryMirror creation" do
+    before(:each) do
+      @mirror = build(:repository_mirror)
+    end
+
+    subject { @mirror }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:url) }
+    it { should allow_mass_assignment_of(:push_mode) }
+    it { should allow_mass_assignment_of(:include_all_branches) }
+    it { should allow_mass_assignment_of(:include_all_tags) }
+    it { should allow_mass_assignment_of(:explicit_refspec) }
+    it { should allow_mass_assignment_of(:active) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:url) }
+    it { should validate_presence_of(:push_mode) }
+
+    it { should validate_uniqueness_of(:url).scoped_to(:repository_id) }
+
+    it {
+      should allow_value(*VALID_URLS).
+      for(:url)
+    }
+
+    it { should validate_numericality_of(:push_mode) }
+
+    it {
+      should ensure_inclusion_of(:push_mode).
+      in_array(%w(0 1 2))
+    }
+
+    ## Attributes content
+    it { expect(@mirror.active).to be true }
+    it { expect(@mirror.include_all_branches).to be false }
+    it { expect(@mirror.include_all_tags).to be false }
+    it { expect(@mirror.explicit_refspec).to eq "" }
+    it { expect(@mirror.push_mode).to eq 0 }
+    it { expect(@mirror.push_mode).to be_a(Integer) }
+
+    ## Test changes
+    describe "when active is true" do
+      before { @mirror.active = true }
+      it "shoud be active" do
+        expect(@mirror.active).to be true
+      end
+    end
+
+    describe "when active is false" do
+      before { @mirror.active = false }
+      it "should be inactive" do
+        expect(@mirror.active).to be false
+      end
+    end
+  end
+
+
+  describe "Push args" do
+    ## Validate push args : forced mode
+    context "when push_mode forced with params" do
+      before do
+        @mirror = build_mirror(:url => MIRROR_URL, :push_mode => 1, :explicit_refspec => 'devel')
+      end
+
+      it "should have push_args" do
+        expect(@mirror.push_args).to eq ["--force", MIRROR_URL, "devel"]
+      end
+    end
+
+    ## Validate push args : fast_forward mode
+    context "when push_mode fast_forward with params" do
+      before do
+        @mirror = build_mirror(:url => MIRROR_URL, :push_mode => 2, :explicit_refspec => 'devel')
+      end
+
+      it "should have push_args" do
+        expect(@mirror.push_args).to eq [MIRROR_URL, "devel"]
+      end
+    end
+
+    ## Validate push args : mirror mode
+    context "when push_mode is mirror" do
+      before do
+        @mirror = build_mirror(:url => MIRROR_URL, :push_mode => 0)
+      end
+
+      it "should have push_args" do
+        expect(@mirror.push_args).to eq ["--mirror", MIRROR_URL]
+      end
+    end
+
+    ## Validate push args : all tags mode
+    context "when push_mode is all tags" do
+      before do
+        @mirror = build_mirror(:url => MIRROR_URL, :push_mode => 1, :include_all_tags => true)
+      end
+
+      it "should have push_args" do
+        expect(@mirror.push_args).to eq ["--force", "--tags", MIRROR_URL]
+      end
+    end
+
+    ## Validate push args : all branches mode
+    context "when push_mode is all branches" do
+      before do
+        @mirror = build_mirror(:url => MIRROR_URL, :push_mode => 1, :include_all_branches => true)
+      end
+
+      it "should have push_args" do
+        expect(@mirror.push_args).to eq ["--force", "--all", MIRROR_URL]
+      end
+    end
+  end
+
+
+  describe "Invalid Mirror creation" do
+    ## Test presence conflicts
+    it "is invalid when include_all_branches && include_all_tags" do
+      expect(build_mirror(:push_mode => 1, :include_all_branches => true, :include_all_tags => true)).not_to be_valid
+    end
+
+    it "is invalid when include_all_branches && explicit_refspec" do
+      expect(build_mirror(:push_mode => 1, :include_all_branches => true, :explicit_refspec => 'devel')).not_to be_valid
+    end
+
+    ## Validate push mode : forced
+    it "is invalid when push_mode forced without params" do
+      expect(build_mirror(:push_mode => 1)).not_to be_valid
+    end
+
+    ## Validate push mode : fast_forward
+    it "is invalid when push_mode fast_forward without params" do
+      expect(build_mirror(:push_mode => 2)).not_to be_valid
+    end
+
+    ## Validate explicit_refspec
+    it "is invalid when explicit_refspec is invalid" do
+      expect(build_mirror(:push_mode => 1, :explicit_refspec => ':/devel')).not_to be_valid
+    end
+  end
+
+
+  context "when many mirror are saved" do
+    before do
+      create(:repository_mirror, :active => true)
+      create(:repository_mirror, :active => true)
+      create(:repository_mirror, :active => false)
+      create(:repository_mirror, :active => false)
+    end
+
+    it { expect(RepositoryMirror.active.length).to be == 3 }
+    it { expect(RepositoryMirror.inactive.length).to be == 2 }
+  end
+
+end
diff --git a/spec/models/repository_post_receive_url_spec.rb b/spec/models/repository_post_receive_url_spec.rb
new file mode 100644
index 0000000..6572e17
--- /dev/null
+++ b/spec/models/repository_post_receive_url_spec.rb
@@ -0,0 +1,165 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryPostReceiveUrl do
+
+  GLOBAL_PAYLOAD   = YAML::load(File.open(File.expand_path(File.dirname(__FILE__) + '/../fixtures/global_payload.yml')))
+  MASTER_PAYLOAD   = YAML::load(File.open(File.expand_path(File.dirname(__FILE__) + '/../fixtures/master_payload.yml')))
+  BRANCHES_PAYLOAD = YAML::load(File.open(File.expand_path(File.dirname(__FILE__) + '/../fixtures/branches_payload.yml')))
+
+
+  describe "Valid RepositoryPostReceiveUrl creation" do
+    before(:each) do
+      @post_receive_url = build(:repository_post_receive_url)
+    end
+
+    subject { @post_receive_url }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:url) }
+    it { should allow_mass_assignment_of(:mode) }
+    it { should allow_mass_assignment_of(:active) }
+    it { should allow_mass_assignment_of(:use_triggers) }
+    it { should allow_mass_assignment_of(:triggers) }
+    it { should allow_mass_assignment_of(:split_payloads) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:url) }
+    it { should validate_presence_of(:mode) }
+
+    it { should validate_uniqueness_of(:url).scoped_to(:repository_id) }
+
+    it {
+      should ensure_inclusion_of(:mode).
+      in_array([:github, :get])
+    }
+
+    it {
+      should allow_value('http://foo.com', 'https://bar.com/baz').
+      for(:url)
+    }
+
+    ## Serializations
+    it { should serialize(:triggers) }
+
+    ## Attributes content
+    it { expect(@post_receive_url.active).to be true }
+    it { expect(@post_receive_url.mode).to eq :github }
+    it { expect(@post_receive_url.use_triggers).to be false }
+    it { expect(@post_receive_url.triggers).to be_a(Array) }
+    it { expect(@post_receive_url.split_payloads).to be false }
+
+    describe "when active is true" do
+      before { @post_receive_url.active = true }
+      it { expect(@post_receive_url.active).to be true }
+    end
+
+    describe "when active is false" do
+      before { @post_receive_url.active = false }
+      it { expect(@post_receive_url.active).to be false }
+    end
+
+    describe "when use_triggers is true" do
+      before { @post_receive_url.use_triggers = true }
+      it { expect(@post_receive_url.use_triggers).to be true }
+    end
+
+    describe "when use_triggers is false" do
+      before { @post_receive_url.use_triggers = false }
+      it { expect(@post_receive_url.use_triggers).to be false }
+    end
+
+    describe "when split_payloads is true" do
+      before { @post_receive_url.split_payloads = true }
+      it { expect(@post_receive_url.split_payloads).to be true }
+    end
+
+    describe "when split_payloads is false" do
+      before { @post_receive_url.split_payloads = false }
+      it { expect(@post_receive_url.split_payloads).to be false }
+    end
+  end
+
+
+  context "when many post receive url are saved" do
+    before do
+      create(:repository_post_receive_url, :active => true)
+      create(:repository_post_receive_url, :active => true)
+      create(:repository_post_receive_url, :active => false)
+      create(:repository_post_receive_url, :active => false)
+    end
+
+    it { expect(RepositoryPostReceiveUrl.active.length).to be == 3 }
+    it { expect(RepositoryPostReceiveUrl.inactive.length).to be == 2 }
+  end
+
+
+  describe "#needs_push" do
+    before do
+      @post_receive_url = build(:repository_post_receive_url)
+    end
+
+    subject { @post_receive_url }
+
+    context "when payload is empty" do
+      before do
+        @needs_push = @post_receive_url.needs_push([])
+      end
+
+      it "shoud return false" do
+        expect(@needs_push).to be false
+      end
+    end
+
+    context "when triggers are not used" do
+      before do
+        @needs_push = @post_receive_url.needs_push(GLOBAL_PAYLOAD)
+      end
+
+      it "should return the global payload to push" do
+        expect(@needs_push).to eq GLOBAL_PAYLOAD
+      end
+    end
+
+    context "when triggers are empty" do
+      before do
+        @post_receive_url.use_triggers = true
+        @needs_push = @post_receive_url.needs_push(GLOBAL_PAYLOAD)
+      end
+
+      it "should return the global payload to push" do
+        expect(@needs_push).to eq GLOBAL_PAYLOAD
+      end
+    end
+
+    context "when triggers is set to master" do
+      before do
+        @post_receive_url.use_triggers = true
+        @post_receive_url.triggers = [ 'master' ]
+        @needs_push = @post_receive_url.needs_push(GLOBAL_PAYLOAD)
+      end
+
+      it "should return the master payload" do
+        expect(@needs_push).to eq MASTER_PAYLOAD
+      end
+    end
+
+    context "when triggers is set to master" do
+      before do
+        @post_receive_url.use_triggers = true
+        @post_receive_url.triggers = [ 'master' ]
+        @needs_push = @post_receive_url.needs_push(BRANCHES_PAYLOAD)
+      end
+
+      it "should not be found in branches payload and return false" do
+        expect(@needs_push).to be false
+      end
+    end
+  end
+
+end
diff --git a/spec/models/repository_protected_branche_spec.rb b/spec/models/repository_protected_branche_spec.rb
new file mode 100644
index 0000000..542a14b
--- /dev/null
+++ b/spec/models/repository_protected_branche_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RepositoryProtectedBranche do
+
+  def build_protected_branch(opts = {})
+    build(:repository_protected_branche, opts)
+  end
+
+
+  describe "Valid RepositoryProtectedBranche creation" do
+    before(:each) do
+      @protected_branch = build_protected_branch(:path => 'devel', :permissions => 'RW', :user_list => %w(user1 user2))
+    end
+
+    subject { @protected_branch }
+
+    ## Attributes
+    it { should allow_mass_assignment_of(:path) }
+    it { should allow_mass_assignment_of(:permissions) }
+    it { should allow_mass_assignment_of(:position) }
+    it { should allow_mass_assignment_of(:user_list) }
+
+    ## Relations
+    it { should belong_to(:repository) }
+
+    ## Validations
+    it { should be_valid }
+
+    it { should validate_presence_of(:repository_id) }
+    it { should validate_presence_of(:path) }
+    it { should validate_presence_of(:permissions) }
+    it { should validate_presence_of(:user_list) }
+
+    it {
+      should ensure_inclusion_of(:permissions).
+      in_array(%w(RW RW+))
+    }
+
+    ## Serializations
+    it { should serialize(:user_list) }
+  end
+end
diff --git a/spec/models/repository_spec.rb.old b/spec/models/repository_spec.rb.old
new file mode 100644
index 0000000..4d8b755
--- /dev/null
+++ b/spec/models/repository_spec.rb.old
@@ -0,0 +1,1167 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Repository::Git do
+
+  before :all do
+    Setting.plugin_redmine_git_hosting[:gitolite_redmine_storage_dir] = 'redmine/'
+    Setting.plugin_redmine_git_hosting[:http_server_subdir] = 'git/'
+    User.current = nil
+
+    @project_parent = FactoryGirl.create(:project, :identifier => 'project-parent')
+    @project_child  = FactoryGirl.create(:project, :identifier => 'project-child', :parent_id => @project_parent.id)
+  end
+
+
+  def create_user_with_permissions(project)
+    role = FactoryGirl.create(:role)
+    user = FactoryGirl.create(:user, :login => 'redmine-test-user')
+
+    members = Member.new(:role_ids => [role.id], :user_id => user.id)
+    project.members << members
+
+    return user
+  end
+
+
+  context "common_tests" do
+    before do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = true
+
+      @repository_1   = FactoryGirl.create(:repository, :project_id => @project_child.id, :is_default => true)
+      @repository_2   = FactoryGirl.create(:repository, :project_id => @project_child.id, :identifier => 'repo-test')
+
+      @repository_1.extra
+      @repository_2.extra
+    end
+
+    subject { @repository_1 }
+
+    ## Test relations
+    it { should respond_to(:git_extra) }
+    it { should respond_to(:git_notification) }
+    it { should respond_to(:repository_mirrors) }
+    it { should respond_to(:repository_post_receive_urls) }
+    it { should respond_to(:repository_deployment_credentials) }
+    it { should respond_to(:repository_git_config_keys) }
+
+    ## Test attributes
+    it { should respond_to(:identifier) }
+    it { should respond_to(:url) }
+    it { should respond_to(:root_url) }
+
+    ## Test methods
+    it { should respond_to(:extra) }
+
+    it { should respond_to(:report_last_commit_with_git_hosting) }
+    it { should respond_to(:extra_report_last_commit_with_git_hosting) }
+
+    it { should respond_to(:git_cache_id) }
+    it { should respond_to(:redmine_name) }
+
+    it { should respond_to(:gitolite_repository_path) }
+    it { should respond_to(:gitolite_repository_name) }
+    it { should respond_to(:redmine_repository_path) }
+
+    it { should respond_to(:new_repository_name) }
+    it { should respond_to(:old_repository_name) }
+
+    it { should respond_to(:http_user_login) }
+    it { should respond_to(:git_access_path) }
+    it { should respond_to(:http_access_path) }
+
+    it { should respond_to(:ssh_url) }
+    it { should respond_to(:git_url) }
+    it { should respond_to(:http_url) }
+    it { should respond_to(:https_url) }
+
+    it { should respond_to(:available_urls) }
+
+    it { should respond_to(:mailing_list_default_users) }
+    it { should respond_to(:mailing_list_effective) }
+    it { should respond_to(:mailing_list_params) }
+
+    it { should respond_to(:get_full_parent_path) }
+    it { should respond_to(:exists_in_gitolite?) }
+    it { should respond_to(:gitolite_hook_key) }
+
+    it { should be_valid }
+
+    ## Test attributes more specifically
+    it { expect(@repository_1.report_last_commit_with_git_hosting).to be true }
+    it { expect(@repository_1.extra_report_last_commit_with_git_hosting).to be true }
+
+    it { expect(@repository_1.extra[:git_http]).to eq 1 }
+    it { expect(@repository_1.extra[:git_daemon]).to be false }
+    it { expect(@repository_1.extra[:git_notify]).to be true }
+    it { expect(@repository_1.extra[:default_branch]).to eq 'master' }
+
+    it { expect(@repository_1.available_urls).to be_a(Hash) }
+
+    describe "#available_urls" do
+
+      context "with no option" do
+        before do
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = {}
+
+        it "should return an empty Hash" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with all options" do
+        before do
+          @user = create_user_with_permissions(@project_child)
+          User.current = @user
+
+          @repository_1.extra[:git_daemon] = true
+          @repository_1.extra[:git_http]   = 2
+          @repository_1.save
+        end
+
+        my_hash = {
+          :ssh   => {:url => "ssh://git@localhost/redmine/project-parent/project-child.git",             :commiter => "false"},
+          :https => {:url => "https://redmine-test-user@localhost/git/project-parent/project-child.git", :commiter => "false"},
+          :http  => {:url => "http://redmine-test-user@localhost/git/project-parent/project-child.git",  :commiter => "false"},
+          :git   => {:url => "git://localhost/redmine/project-parent/project-child.git",                 :commiter => "false"}
+        }
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with git daemon" do
+        before do
+          User.current = nil
+
+          @repository_1.extra[:git_daemon] = true
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = {:git => {:url=>"git://localhost/redmine/project-parent/project-child.git", :commiter=>"false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with ssh" do
+        before do
+          @user = create_user_with_permissions(@project_child)
+          User.current = @user
+
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 0
+          @repository_1.save
+        end
+
+        my_hash = { :ssh => {:url => "ssh://git@localhost/redmine/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with http" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 3
+          @repository_1.save
+        end
+
+        my_hash = { :http => {:url => "http://localhost/git/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with https" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 1
+          @repository_1.save
+        end
+
+        my_hash = { :https => {:url => "https://localhost/git/project-parent/project-child.git", :commiter => "false"}}
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+
+      context "with http and https" do
+        before do
+          User.current = nil
+          @repository_1.extra[:git_daemon] = false
+          @repository_1.extra[:git_http]   = 2
+          @repository_1.save
+        end
+
+        my_hash = {
+          :https => {:url => "https://localhost/git/project-parent/project-child.git", :commiter => "false"},
+          :http  => {:url => "http://localhost/git/project-parent/project-child.git",  :commiter => "false"}
+        }
+
+        it "should return a Hash of Git url" do
+          expect(@repository_1.available_urls).to eq my_hash
+        end
+      end
+    end
+
+    ## Test uniqueness validation
+    describe "when blank identifier is already taken by a repository" do
+      before do
+        @repository = FactoryGirl.build(:repository, :project_id => @project_child.id, :identifier => '')
+      end
+
+      it { expect(@repository).not_to be_valid }
+    end
+
+    describe "when set as default and blank identifier is already taken by a repository" do
+      before do
+        @repository = FactoryGirl.build(:repository, :project_id => @project_child.id, :identifier => '', :is_default => true)
+      end
+
+      it { expect(@repository).not_to be_valid }
+    end
+
+    describe "when identifier is already taken by a project" do
+      before do
+        @repository = FactoryGirl.build(:repository, :project_id => @project_child.id, :identifier => 'project-child')
+      end
+
+      it { expect(@repository).not_to be_valid }
+    end
+
+    describe "when identifier is already taken by a repository with same project" do
+      before do
+        @repository = FactoryGirl.build(:repository, :project_id => @project_child.id, :identifier => 'repo-test')
+      end
+
+      it { expect(@repository).not_to be_valid }
+    end
+
+    describe "when identifier are not unique" do
+      before do
+        @repository = FactoryGirl.build(:repository, :project_id => @project_parent.id, :identifier => 'repo-test')
+      end
+
+      it { expect(@repository).to be_valid }
+    end
+
+    describe "when identifier are unique" do
+      before do
+        Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+        @repository = FactoryGirl.build(:repository, :project_id => @project_parent.id, :identifier => 'repo-test')
+      end
+
+      it { expect(@repository).not_to be_valid }
+    end
+
+
+    ## Test change validation
+    describe "when identifier is changed" do
+      before do
+        @repository_1.identifier = "foo"
+      end
+
+      it { expect(@repository_1).not_to be_valid }
+    end
+
+    describe "when git_daemon is true" do
+      before do
+        @repository_1.extra[:git_daemon] = true
+        @repository_1.save
+      end
+
+      it { should be_valid }
+      it { expect(@repository_1.extra[:git_daemon]).to be true }
+    end
+
+    describe "when git_daemon is false" do
+      before do
+        @repository_1.extra[:git_daemon] = false
+        @repository_1.save
+      end
+
+      it { should be_valid }
+      it { expect(@repository_1.extra[:git_daemon]).to be false }
+    end
+
+    describe "when git_notify is true" do
+      before do
+        @repository_1.extra[:git_notify] = true
+        @repository_1.save
+      end
+
+      it { should be_valid }
+      it { expect(@repository_1.extra[:git_notify]).to be true }
+    end
+
+    describe "when git_notify is false" do
+      before do
+        @repository_1.extra[:git_notify] = false
+        @repository_1.save
+      end
+
+      it { should be_valid }
+      it { expect(@repository_1.extra[:git_notify]).to be false }
+    end
+
+    describe "when default_branch is changed" do
+      before do
+        @repository_1.extra[:default_branch] = 'devel'
+        @repository_1.save
+      end
+
+      it { should be_valid }
+      it { expect(@repository_1.extra[:default_branch]).to eq 'devel' }
+    end
+
+
+    ## Test format validation
+    describe "when git http is valid" do
+      it "should be valid" do
+        modes = [ 0, 1, 2, 3 ]
+        modes.each do |valid_mode|
+          @repository_1.extra[:git_http] = valid_mode
+          expect(@repository_1.extra).to be_valid
+        end
+      end
+    end
+
+    describe "when git http is invalid" do
+      it "should be invalid" do
+        modes = [ 4, 5, 6, 7 ]
+        modes.each do |invalid_mode|
+          @repository_1.extra[:git_http] = invalid_mode
+          expect(@repository_1.extra).not_to be_valid
+        end
+      end
+    end
+
+
+    describe "Repository::Git class" do
+      it { expect(Repository::Git).to respond_to(:repo_ident_unique?) }
+      it { expect(Repository::Git).to respond_to(:have_duplicated_identifier?) }
+      it { expect(Repository::Git).to respond_to(:repo_path_to_git_cache_id) }
+      it { expect(Repository::Git).to respond_to(:find_by_path) }
+
+      describe ".repo_ident_unique?" do
+        it { expect(Repository::Git.repo_ident_unique?).to be true }
+      end
+
+      describe ".have_duplicated_identifier?" do
+        it { expect(Repository::Git.have_duplicated_identifier?).to be false }
+      end
+
+      describe ".repo_path_to_git_cache_id" do
+        describe "when repo path is not found" do
+          before do
+            @git_cache_id = Repository::Git.repo_path_to_git_cache_id('foo.git')
+          end
+
+          it { expect(@git_cache_id).to be nil }
+        end
+      end
+    end
+  end
+
+
+  def collection_of_unique_repositories
+    @repository_1   = FactoryGirl.create(:repository, :project_id => @project_child.id, :is_default => true)
+    @repository_2   = FactoryGirl.create(:repository, :project_id => @project_child.id, :identifier => 'repo1-test')
+
+    @repository_3   = FactoryGirl.create(:repository, :project_id => @project_parent.id, :is_default => true)
+    @repository_4   = FactoryGirl.create(:repository, :project_id => @project_parent.id, :identifier => 'repo2-test')
+
+    @repository_1.extra
+    @repository_2.extra
+    @repository_3.extra
+    @repository_4.extra
+  end
+
+
+  def collection_of_non_unique_repositories
+    @repository_1   = FactoryGirl.create(:repository, :project_id => @project_child.id, :is_default => true)
+    @repository_2   = FactoryGirl.create(:repository, :project_id => @project_child.id, :identifier => 'repo-test')
+
+    @repository_3   = FactoryGirl.create(:repository, :project_id => @project_parent.id, :is_default => true)
+    @repository_4   = FactoryGirl.create(:repository, :project_id => @project_parent.id, :identifier => 'repo-test')
+
+    @repository_1.extra
+    @repository_2.extra
+    @repository_3.extra
+    @repository_4.extra
+  end
+
+
+  context "when hierarchical_organisation_with_unique_identifier" do
+    before do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = true
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+      collection_of_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it { expect(Repository::Git.repo_ident_unique?).to be true }
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it { expect(Repository::Git.have_duplicated_identifier?).to be false }
+    end
+
+    describe "repository1" do
+      it "should be default repository" do
+        expect(@repository_1.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_1.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_1.url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_1.root_url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_1.git_cache_id).to eq 'project-child'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_1.redmine_name).to eq 'project-child'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_1.redmine_repository_path).to eq 'project-parent/project-child'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_1.ssh_url).to eq 'ssh://git@localhost/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_1.git_url).to eq 'git://localhost/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_1.http_url).to eq 'http://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_1.https_url).to eq 'https://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_1.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_1.git_access_path).to eq 'redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_1.http_access_path).to eq 'git/project-parent/project-child.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_1.new_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_1.old_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+    end
+
+    describe "repository2" do
+      it "should not be default repository" do
+        expect(@repository_2.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_2.identifier).to eq 'repo1-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_2.url).to eq 'repositories/redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_2.root_url).to eq 'repositories/redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_2.git_cache_id).to eq 'repo1-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_2.redmine_name).to eq 'repo1-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_2.gitolite_repository_name).to eq 'redmine/project-parent/project-child/repo1-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_2.redmine_repository_path).to eq 'project-parent/project-child/repo1-test'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_2.ssh_url).to eq 'ssh://git@localhost/redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_2.git_url).to eq 'git://localhost/redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_2.http_url).to eq 'http://localhost/git/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_2.https_url).to eq 'https://localhost/git/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_2.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_2.git_access_path).to eq 'redmine/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_2.http_access_path).to eq 'git/project-parent/project-child/repo1-test.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_2.new_repository_name).to eq 'redmine/project-parent/project-child/repo1-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_2.old_repository_name).to eq 'redmine/project-parent/project-child/repo1-test'
+      end
+    end
+
+    describe "repository3" do
+      it { expect(@repository_3.git_cache_id).to eq 'project-parent' }
+      it { expect(@repository_3.redmine_name).to eq 'project-parent' }
+    end
+
+    describe "repository4" do
+      it { expect(@repository_4.git_cache_id).to eq 'repo2-test' }
+      it { expect(@repository_4.redmine_name).to eq 'repo2-test' }
+    end
+
+    describe ".repo_path_to_git_cache_id" do
+      describe "when identifier are not unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be false" do
+          expect(Repository::Git.repo_ident_unique?).to be false
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'project-child/repo1-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'project-parent/repo2-test' }
+        end
+      end
+
+      describe "when identifier are unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be true
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'repo1-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'repo2-test' }
+        end
+      end
+    end
+  end
+
+
+  context "when hierarchical_organisation_with_non_unique_identifier" do
+    before do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = true
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+      collection_of_non_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it { expect(Repository::Git.repo_ident_unique?).to be false }
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it { expect(Repository::Git.have_duplicated_identifier?).to be true }
+    end
+
+    describe "repository1" do
+      it "should be default repository" do
+        expect(@repository_1.is_default).to be true
+      end
+
+      it "should have nil identifier" do
+        expect(@repository_1.identifier).to be nil
+      end
+
+      it "should have a valid url" do
+        expect(@repository_1.url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_1.root_url).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_1.git_cache_id).to eq 'project-child'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_1.redmine_name).to eq 'project-child'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_1.redmine_repository_path).to eq 'project-parent/project-child'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_1.ssh_url).to eq 'ssh://git@localhost/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_1.git_url).to eq 'git://localhost/redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_1.http_url).to eq 'http://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_1.https_url).to eq 'https://localhost/git/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_1.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_1.git_access_path).to eq 'redmine/project-parent/project-child.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_1.http_access_path).to eq 'git/project-parent/project-child.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_1.new_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_1.old_repository_name).to eq 'redmine/project-parent/project-child'
+      end
+    end
+
+    describe "repository2" do
+      it "should not be default repository" do
+        expect(@repository_2.is_default).to be false
+      end
+
+      it "should have a valid identifier" do
+        expect(@repository_2.identifier).to eq 'repo-test'
+      end
+
+      it "should have a valid url" do
+        expect(@repository_2.url).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid root_url" do
+        expect(@repository_2.root_url).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid git_cache_id" do
+        expect(@repository_2.git_cache_id).to eq 'project-child/repo-test'
+      end
+
+      it "should have a valid redmine_name" do
+        expect(@repository_2.redmine_name).to eq 'repo-test'
+      end
+
+      it "should have a valid gitolite_repository_path" do
+        expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid gitolite_repository_name" do
+        expect(@repository_2.gitolite_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid redmine_repository_path" do
+        expect(@repository_2.redmine_repository_path).to eq 'project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid ssh_url" do
+        expect(@repository_2.ssh_url).to eq 'ssh://git@localhost/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid git_url" do
+        expect(@repository_2.git_url).to eq 'git://localhost/redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_url" do
+        expect(@repository_2.http_url).to eq 'http://localhost/git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid https_url" do
+        expect(@repository_2.https_url).to eq 'https://localhost/git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_user_login" do
+        expect(@repository_2.http_user_login).to eq ''
+      end
+
+      it "should have a valid git_access_path" do
+        expect(@repository_2.git_access_path).to eq 'redmine/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid http_access_path" do
+        expect(@repository_2.http_access_path).to eq 'git/project-parent/project-child/repo-test.git'
+      end
+
+      it "should have a valid new_repository_name" do
+        expect(@repository_2.new_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+
+      it "should have a valid old_repository_name" do
+        expect(@repository_2.old_repository_name).to eq 'redmine/project-parent/project-child/repo-test'
+      end
+    end
+
+    describe "repository3" do
+      it { expect(@repository_3.git_cache_id).to eq 'project-parent' }
+      it { expect(@repository_3.redmine_name).to eq 'project-parent' }
+    end
+
+    describe "repository4" do
+      it { expect(@repository_4.git_cache_id).to eq 'project-parent/repo-test' }
+      it { expect(@repository_4.redmine_name).to eq 'repo-test' }
+    end
+
+    describe ".repo_path_to_git_cache_id" do
+      describe "when identifier are not unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be false" do
+          expect(Repository::Git.repo_ident_unique?).to be false
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'project-child/repo-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'project-parent/repo-test' }
+        end
+      end
+
+      describe "when identifier are unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be true
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          ## LIMIT CASE
+          it { expect(@repo2).to eq @repository_4 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'repo-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'repo-test' }
+        end
+      end
+    end
+  end
+
+
+  context "flat_organisation_with_unique_identifier" do
+    before do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = false
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+      collection_of_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it { expect(Repository::Git.repo_ident_unique?).to be true }
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it { expect(Repository::Git.have_duplicated_identifier?).to be false }
+    end
+
+
+    describe "repository1" do
+      it { expect(@repository_1.is_default).to be true }
+      it { expect(@repository_1.identifier).to be nil }
+      it { expect(@repository_1.url).to eq      'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.root_url).to eq 'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.git_cache_id).to eq 'project-child' }
+      it { expect(@repository_1.redmine_name).to eq 'project-child' }
+      it { expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.redmine_repository_path).to eq  'project-child' }
+      it { expect(@repository_1.new_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.old_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.http_user_login).to eq  '' }
+      it { expect(@repository_1.git_access_path).to eq  'redmine/project-child.git' }
+      it { expect(@repository_1.http_access_path).to eq 'git/project-child.git' }
+      it { expect(@repository_1.ssh_url).to eq   'ssh://git@localhost/redmine/project-child.git' }
+      it { expect(@repository_1.git_url).to eq   'git://localhost/redmine/project-child.git' }
+      it { expect(@repository_1.http_url).to eq  'http://localhost/git/project-child.git' }
+      it { expect(@repository_1.https_url).to eq 'https://localhost/git/project-child.git' }
+    end
+
+
+    describe "repository2" do
+      it { expect(@repository_2.is_default).to be false }
+      it { expect(@repository_2.identifier).to eq 'repo1-test' }
+      it { expect(@repository_2.url).to eq      'repositories/redmine/repo1-test.git' }
+      it { expect(@repository_2.root_url).to eq 'repositories/redmine/repo1-test.git' }
+      it { expect(@repository_2.git_cache_id).to eq 'repo1-test' }
+      it { expect(@repository_2.redmine_name).to eq 'repo1-test' }
+      it { expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/repo1-test.git' }
+      it { expect(@repository_2.gitolite_repository_name).to eq 'redmine/repo1-test' }
+      it { expect(@repository_2.redmine_repository_path).to eq  'repo1-test' }
+      it { expect(@repository_2.new_repository_name).to eq 'redmine/repo1-test' }
+      it { expect(@repository_2.old_repository_name).to eq 'redmine/repo1-test' }
+      it { expect(@repository_2.http_user_login).to eq  '' }
+      it { expect(@repository_2.git_access_path).to eq  'redmine/repo1-test.git' }
+      it { expect(@repository_2.http_access_path).to eq 'git/repo1-test.git' }
+      it { expect(@repository_2.ssh_url).to eq   'ssh://git@localhost/redmine/repo1-test.git' }
+      it { expect(@repository_2.git_url).to eq   'git://localhost/redmine/repo1-test.git' }
+      it { expect(@repository_2.http_url).to eq  'http://localhost/git/repo1-test.git' }
+      it { expect(@repository_2.https_url).to eq 'https://localhost/git/repo1-test.git' }
+    end
+
+
+    describe "repository3" do
+      it { expect(@repository_3.url).to eq      'repositories/redmine/project-parent.git' }
+      it { expect(@repository_3.root_url).to eq 'repositories/redmine/project-parent.git' }
+      it { expect(@repository_3.git_cache_id).to eq 'project-parent' }
+      it { expect(@repository_3.redmine_name).to eq 'project-parent' }
+    end
+
+
+    describe "repository4" do
+      it { expect(@repository_4.url).to eq      'repositories/redmine/repo2-test.git' }
+      it { expect(@repository_4.root_url).to eq 'repositories/redmine/repo2-test.git' }
+      it { expect(@repository_4.git_cache_id).to eq 'repo2-test' }
+      it { expect(@repository_4.redmine_name).to eq 'repo2-test' }
+    end
+
+
+    describe ".repo_path_to_git_cache_id" do
+      describe "when identifier are not unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be false
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'project-child/repo1-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'project-parent/repo2-test' }
+        end
+      end
+
+      describe "when identifier are unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be true
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'repo1-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'repo2-test' }
+        end
+      end
+    end
+  end
+
+
+  context "flat_organisation_with_non_unique_identifier" do
+    before do
+      Setting.plugin_redmine_git_hosting[:hierarchical_organisation] = false
+      Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+      collection_of_non_unique_repositories
+    end
+
+    describe ".repo_ident_unique?" do
+      it { expect(Repository::Git.repo_ident_unique?).to be false }
+    end
+
+    describe ".have_duplicated_identifier?" do
+      it { expect(Repository::Git.have_duplicated_identifier?).to be true }
+    end
+
+
+    describe "repository1" do
+      it { expect(@repository_1.is_default).to be true }
+      it { expect(@repository_1.identifier).to be nil }
+      it { expect(@repository_1.url).to eq      'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.root_url).to eq 'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.git_cache_id).to eq 'project-child' }
+      it { expect(@repository_1.redmine_name).to eq 'project-child' }
+      it { expect(@repository_1.gitolite_repository_path).to eq 'repositories/redmine/project-child.git' }
+      it { expect(@repository_1.gitolite_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.redmine_repository_path).to eq  'project-child' }
+      it { expect(@repository_1.new_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.old_repository_name).to eq 'redmine/project-child' }
+      it { expect(@repository_1.http_user_login).to eq  '' }
+      it { expect(@repository_1.git_access_path).to eq  'redmine/project-child.git' }
+      it { expect(@repository_1.http_access_path).to eq 'git/project-child.git' }
+      it { expect(@repository_1.ssh_url).to eq   'ssh://git@localhost/redmine/project-child.git' }
+      it { expect(@repository_1.git_url).to eq   'git://localhost/redmine/project-child.git' }
+      it { expect(@repository_1.http_url).to eq  'http://localhost/git/project-child.git' }
+      it { expect(@repository_1.https_url).to eq 'https://localhost/git/project-child.git' }
+    end
+
+
+    describe "repository2" do
+      it { expect(@repository_2.is_default).to be false }
+      it { expect(@repository_2.identifier).to eq 'repo-test' }
+      it { expect(@repository_2.url).to eq      'repositories/redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.root_url).to eq 'repositories/redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.git_cache_id).to eq 'project-child/repo-test' }
+      it { expect(@repository_2.redmine_name).to eq 'repo-test' }
+      it { expect(@repository_2.gitolite_repository_path).to eq 'repositories/redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.gitolite_repository_name).to eq 'redmine/project-child/repo-test' }
+      it { expect(@repository_2.redmine_repository_path).to eq  'project-child/repo-test' }
+      it { expect(@repository_2.new_repository_name).to eq 'redmine/project-child/repo-test' }
+      it { expect(@repository_2.old_repository_name).to eq 'redmine/project-child/repo-test' }
+      it { expect(@repository_2.http_user_login).to eq  '' }
+      it { expect(@repository_2.git_access_path).to eq  'redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.http_access_path).to eq 'git/project-child/repo-test.git' }
+      it { expect(@repository_2.ssh_url).to eq   'ssh://git@localhost/redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.git_url).to eq   'git://localhost/redmine/project-child/repo-test.git' }
+      it { expect(@repository_2.http_url).to eq  'http://localhost/git/project-child/repo-test.git' }
+      it { expect(@repository_2.https_url).to eq 'https://localhost/git/project-child/repo-test.git' }
+    end
+
+
+    describe "repository3" do
+      it { expect(@repository_3.git_cache_id).to eq 'project-parent' }
+      it { expect(@repository_3.redmine_name).to eq 'project-parent' }
+    end
+
+
+    describe "repository4" do
+      it { expect(@repository_4.git_cache_id).to eq 'project-parent/repo-test' }
+      it { expect(@repository_4.redmine_name).to eq 'repo-test' }
+    end
+
+
+    describe ".repo_path_to_git_cache_id" do
+      describe "when identifier are not unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = false
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be false
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          it { expect(@repo2).to eq @repository_2 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'project-child/repo-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'project-parent/repo-test' }
+        end
+      end
+
+      describe "when identifier are unique" do
+        before do
+          Setting.plugin_redmine_git_hosting[:unique_repo_identifier] = true
+          @repo1 = Repository::Git.repo_path_to_object(@repository_1.url)
+          @repo2 = Repository::Git.repo_path_to_object(@repository_2.url)
+          @repo3 = Repository::Git.repo_path_to_object(@repository_3.url)
+          @repo4 = Repository::Git.repo_path_to_object(@repository_4.url)
+
+          @git_cache_id1 = Repository::Git.repo_path_to_git_cache_id(@repository_1.url)
+          @git_cache_id2 = Repository::Git.repo_path_to_git_cache_id(@repository_2.url)
+          @git_cache_id3 = Repository::Git.repo_path_to_git_cache_id(@repository_3.url)
+          @git_cache_id4 = Repository::Git.repo_path_to_git_cache_id(@repository_4.url)
+        end
+
+        it "unique_repo_identifier should be true" do
+          expect(Repository::Git.repo_ident_unique?).to be true
+        end
+
+        describe "repositories should match" do
+          it { expect(@repo1).to eq @repository_1 }
+          ## LIMIT CASE
+          it { expect(@repo2).to eq @repository_4 }
+          it { expect(@repo3).to eq @repository_3 }
+          it { expect(@repo4).to eq @repository_4 }
+
+          it { expect(@git_cache_id1).to eq 'project-child' }
+          it { expect(@git_cache_id2).to eq 'repo-test' }
+          it { expect(@git_cache_id3).to eq 'project-parent' }
+          it { expect(@git_cache_id4).to eq 'repo-test' }
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
new file mode 100644
index 0000000..d132bc4
--- /dev/null
+++ b/spec/models/setting_spec.rb
@@ -0,0 +1,16 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Setting do
+
+  before do
+    RedmineGitolite::Config.reload_from_file!
+    @settings = Setting.plugin_redmine_git_hosting
+    @default_settings = Redmine::Plugin.find("redmine_git_hosting").settings[:default]
+  end
+
+  subject { @settings }
+
+  it { should be_an_instance_of(Hash) }
+
+  # it { expect(@settings).to eq @default_settings }
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 0000000..7b4e068
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,18 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe User do
+
+  before(:each) do
+    @user = create(:user)
+  end
+
+  subject { @user }
+
+  it { should be_valid }
+
+  it { should respond_to(:gitolite_identifier) }
+
+  it "has a gitolite_identifier" do
+    expect(@user.gitolite_identifier).to match(/redmine_user\d+_\d+/)
+  end
+end
diff --git a/spec/root_spec_helper.rb b/spec/root_spec_helper.rb
new file mode 100644
index 0000000..1ed3355
--- /dev/null
+++ b/spec/root_spec_helper.rb
@@ -0,0 +1,54 @@
+require 'coveralls'
+Coveralls.wear!
+
+require 'simplecov'
+require 'simplecov-rcov'
+
+## Configure SimpleCov
+SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
+  SimpleCov::Formatter::HTMLFormatter,
+  SimpleCov::Formatter::RcovFormatter
+]
+
+## Start Simplecov
+SimpleCov.start 'rails' do
+  add_group 'Redmine Git Hosting', 'plugins/redmine_git_hosting'
+end
+
+## Load Redmine App
+ENV["RAILS_ENV"] = 'test'
+require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
+require 'rspec/rails'
+
+## Load FactoryGirls factories
+Dir[Rails.root.join("plugins/*/spec/factories/**/*.rb")].each {|f| require f}
+
+## Configure RSpec
+RSpec.configure do |config|
+  config.include FactoryGirl::Syntax::Methods
+
+  config.infer_spec_type_from_file_location!
+
+  config.color = true
+  config.fail_fast = false
+
+  config.expect_with :rspec do |c|
+    c.syntax = :expect
+  end
+
+  config.before(:suite) do
+    DatabaseCleaner.clean_with(:truncation)
+  end
+
+  config.before(:each) do
+    DatabaseCleaner.strategy = :transaction
+  end
+
+  config.before(:each) do
+    DatabaseCleaner.start
+  end
+
+  config.after(:each) do
+    DatabaseCleaner.clean
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..ec887e8
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,10 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../../spec/spec_helper')
+
+## Configure RSpec
+RSpec.configure do |config|
+
+  config.before(:suite) do
+    RedmineGitolite::Config.reload_from_file!
+  end
+
+end
diff --git a/spec/unit_tests/gitolite_wrapper_spec.rb b/spec/unit_tests/gitolite_wrapper_spec.rb
new file mode 100644
index 0000000..0e6036c
--- /dev/null
+++ b/spec/unit_tests/gitolite_wrapper_spec.rb
@@ -0,0 +1,31 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe RedmineGitolite::GitoliteWrapper do
+
+  GITOLITE_VERSION_2 = [
+    'hello redmine_gitolite_admin_id_rsa, this is gitolite v2.3.1-0-g912a8bd-dt running on git 1.7.2.5',
+    'hello gitolite_admin_id_rsa, this is gitolite gitolite-2.3.1 running on git 1.8.1.5',
+    'hello gitolite_admin_id_rsa, this is gitolite 2.3.1-1.el6 running on git 1.7.1',
+    'hello gitolite_admin_id_rsa, this is gitolite 2.2-1 (Debian) running on git 1.7.9.5'
+  ]
+
+  GITOLITE_VERSION_3 = [
+    'hello redmine_gitolite_admin_id_rsa, this is git@dev running gitolite3 v3.3-11-ga1aba93 on git 1.7.2.5'
+  ]
+
+  GITOLITE_VERSION_2.each do |gitolite_version|
+    it "should recognize Gitolite2" do
+      version = RedmineGitolite::GitoliteWrapper.find_version(gitolite_version)
+      expect(version).to eq 2
+    end
+  end
+
+
+  GITOLITE_VERSION_3.each do |gitolite_version|
+    it "should recognize Gitolite3" do
+      version = RedmineGitolite::GitoliteWrapper.find_version(gitolite_version)
+      expect(version).to eq 3
+    end
+  end
+
+end
diff --git a/ssh_keys/redmine_gitolite_admin_id_rsa b/ssh_keys/redmine_gitolite_admin_id_rsa
new file mode 120000
index 0000000..08cf78e
--- /dev/null
+++ b/ssh_keys/redmine_gitolite_admin_id_rsa
@@ -0,0 +1 @@
+/opt/redmine/.ssh/redmine_gitolite_admin_id_rsa
\ No newline at end of file
diff --git a/ssh_keys/redmine_gitolite_admin_id_rsa.pub b/ssh_keys/redmine_gitolite_admin_id_rsa.pub
new file mode 120000
index 0000000..b601a2b
--- /dev/null
+++ b/ssh_keys/redmine_gitolite_admin_id_rsa.pub
@@ -0,0 +1 @@
+/opt/redmine/.ssh/redmine_gitolite_admin_id_rsa.pub
\ No newline at end of file
