lib/gitolite/gitolite_admin.rb in gitolite-rugged-1.2.pre.devel vs lib/gitolite/gitolite_admin.rb in gitolite-rugged-1.2.1.pre.devel

- old
+ new

@@ -1,27 +1,28 @@ +require 'pathname' module Gitolite class GitoliteAdmin attr_accessor :repo - CONF_DIR = "conf" - KEY_DIR = "keydir" - - CONFIG_FILE = "gitolite.conf" - CONFIG_PATH = File.join(CONF_DIR, "gitolite.conf") - - # Default settings - DEFAULT_SETTINGS = { + DEFAULTS = { # clone/push url settings - git_user: 'git', + git_user: 'git', hostname: 'localhost', # Commit settings author_name: 'gitolite-rugged gem', author_email: 'gitolite-rugged@localhost', - commit_msg: 'Commited by the gitolite-rugged gem' + commit_msg: 'Commited by the gitolite-rugged gem', + + # Gitolite-Admin settings + config_dir: "conf", + key_dir: "keydir", + key_subdir: "", + config_file: "gitolite.conf", + lock_file_path: '.lock' } class << self # Checks if the given path is a gitolite-admin repository @@ -35,12 +36,12 @@ rescue Rugged::RepositoryError return false end # Check if config file, key directory exist - [ File.join(dir, CONF_DIR), File.join(dir, KEY_DIR), - File.join(dir, CONFIG_PATH) + [ File.join(dir, DEFAULTS[:config_dir]), File.join(dir, DEFAULTS[:key_dir]), + File.join(dir, DEFAULTS[:config_dir], DEFAULTS[:config_file]) ].each { |f| return false unless File.exists?(f) } true end @@ -51,44 +52,71 @@ # Intialize with the path to # the gitolite-admin repository # # Settings: + # [Connection] # :git_user: The git user to SSH to (:git_user@localhost:gitolite-admin.git), defaults to 'git' # :private_key: The key file containing the private SSH key for :git_user # :public_key: The key file containing the public SSH key for :git_user # :host: Hostname for clone url. Defaults to 'localhost' + # + # [Gitolite-Admin] + # :config_dir: Config directory within gitolite repository (defaults to 'conf') + # :key_dir: Public key directory within gitolite repository (defaults to 'keydir') + # :config_file: Config file to parse (default: 'gitolite.conf') + # **use only when you use the 'include' directive of gitolite)** + # :key_subdir: Where to store gitolite-rugged known keys, defaults to '' (i.e., directly in keydir) + # :lock_file_path: location of the transaction lockfile, defaults to <gitolite-admin.git>/.lock + # # The settings hash is forwarded to +GitoliteAdmin.new+ as options. def initialize(path, settings = {}) @path = path - @settings = DEFAULT_SETTINGS.merge(settings) + @settings = DEFAULTS.merge(settings) # Ensure SSH key settings exist @settings.fetch(:public_key) @settings.fetch(:private_key) # setup credentials @credentials = Rugged::Credentials::SshKey.new( username: settings[:git_user], publickey: settings[:public_key], privatekey: settings[:private_key] ) - @repo = + @repo = if self.class.is_gitolite_admin_repo?(path) - Rugged::Repository.new(path) + Rugged::Repository.new(path, credentials: @credentials) else clone end - @config_file_path = File.join(@path, CONF_DIR, CONFIG_FILE) - @conf_dir_path = File.join(@path, CONF_DIR) - @key_dir_path = File.join(@path, KEY_DIR) + @config_dir_path = File.join(@path, @settings[:config_dir]) + @config_file_path = File.join(@config_dir_path, @settings[:config_file]) + @key_dir_path = File.join(@path, relative_key_dir) @commit_author = { email: settings[:author_email], name: settings[:author_name] } reload! end + + # + # Returns the relative directory to the gitolite config file location. + # I.e., settings[config_dir]/settings[config_file] + # Defaults to 'conf/gitolite.conf' + def relative_config_file + File.join(@settings[:config_dir], @settings[:config_file]) + end + + # + # Returns the relative directory to the public key location. + # I.e., settings[key_dir]/settings[key_subdir] + # Defaults to 'keydir/' + def relative_key_dir + File.join(@settings[:key_dir], @settings[:key_subdir]) + end + def config @config ||= load_config end @@ -136,40 +164,37 @@ end # Writes all changed aspects out to the file system # will also stage all changes then commit - def save() + def save(commit_msg = nil) # Add all changes to index (staging area) index = @repo.index #Process config file (if loaded, i.e. may be modified) if @config - new_conf = @config.to_file(@conf_dir_path) - - # Rugged wants relative paths - index.add(CONFIG_PATH) + new_conf = @config.to_file(path=@config_dir_path) + index.add(relative_config_file) end #Process ssh keys (if loaded, i.e. may be modified) if @ssh_keys - files = list_keys.map{|f| File.basename f} - keys = @ssh_keys.values.map{|f| f.map {|t| t.filename}}.flatten + files = list_keys.map{|f| relative_key_path(f) } + keys = @ssh_keys.values.map{|f| f.map {|t| t.relative_path}}.flatten - to_remove = (files - keys).map { |f| File.join(@key_dir, f) } - to_remove.each do |key| - File.unlink key - index.remove key + to_remove = (files - keys).each do |key| + SSHKey.remove(key, @key_dir_path) + index.remove File.join(relative_key_dir, key) end @ssh_keys.each_value do |key| # Write only keys from sets that has been modified next if key.respond_to?(:dirty?) && !key.dirty? key.each do |k| new_key = k.to_file(@key_dir_path) - index.add new_key + index.add File.join(relative_key_dir, k.relative_path) end end end # Write index to git and resync fs @@ -179,31 +204,44 @@ commit_author = { email: 'wee@example.org', name: 'gitolite-rugged gem', time: Time.now } Rugged::Commit.create(@repo, author: commit_author, committer: commit_author, - message: @settings[:commit_msg], + message: commit_msg || @settings[:commit_msg], parents: [repo.head.target], tree: commit_tree, update_ref: 'HEAD' ) end # Push back to origin def apply - @repo.push 'origin', ['refs/heads/master'] + @repo.push('origin', ['refs/heads/master'], credentials: @credentials) end # Commits all staged changes and pushes back to origin def save_and_apply() save apply end + # Lock the gitolite-admin directory and yield. + # After the block is completed, calls +apply+ only. + # You have to commit your changes within the transaction block + def transaction + get_lock do + yield + + # Push all changes + apply + end + end + + # Updates the repo with changes from remote master # Warning: This resets the repo before pulling in the changes. def update(settings = {}) reset! @@ -213,11 +251,11 @@ # Create the merged index in memory merge_index = repo.merge_commits(master, origin_master) # Complete the merge by comitting it - merge_commit = Rugged::Commit.create(@repo, + merge_commit = Rugged::Commit.create(@repo, parents: [ master, origin_master ], tree: merge_index.write_tree(@repo), message: '[gitolite-rugged] Merged `origin/master` into `master`', author: @commit_author, committer: @commit_author, @@ -230,21 +268,21 @@ private # Clone the gitolite-admin repo - # to the given path. - # + # to the given path. + # # The repo is cloned from the url # +(:git_user)@(:hostname)/gitolite-admin.git+ # # The hostname may use an optional :port to allow for custom SSH ports. # E.g., +git@localhost:2222/gitolite-admin.git+ # def clone() - Rugged::Repository.clone_at(admin_url(@settings), File.expand_path(@path), credentials: @creds) - end + Rugged::Repository.clone_at(admin_url(@settings), File.expand_path(@path), credentials: @credentials) + end def load_config Config.new(@config_file_path) end @@ -252,11 +290,18 @@ def list_keys Dir.glob(@key_dir_path + '/**/*.pub') end + # Returns the relative key path + # <owner>/<location>/<owner> given an absolute path + # below the keydir. + def relative_key_path(key_path) + Pathname.new(key_path).relative_path_from(Pathname.new(@key_dir_path)).to_s + end + # Loads all .pub files in the gitolite-admin # keydir directory def load_keys keys = Hash.new {|k,v| k[v] = DirtyProxy.new([])} @@ -269,8 +314,26 @@ # Mark key sets as unmodified (for dirty checking) keys.values.each{|set| set.clean_up!} keys + end + + def lock_file_path + File.expand_path(@settings[:lock_file_path], @path) + end + + + # Aquire LOCK_EX on the gitolite-admin.git directory . + # Use +GitoliteAdmin.transaction+ to modify with flock. + def get_lock + File.open(lock_file_path, File::RDWR|File::CREAT, 0644) do |file| + file.sync = true + file.flock(File::LOCK_EX) + + yield + + file.flock(File::LOCK_UN) + end end end end