├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── AUTHORS ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── _guard-core ├── bundle ├── guard ├── rake ├── rspec └── rubocop ├── gitolite.gemspec ├── lib ├── gitolite.rb └── gitolite │ ├── config.rb │ ├── config │ ├── group.rb │ └── repo.rb │ ├── dirty_proxy.rb │ ├── gitolite_admin.rb │ ├── gitolite_admin │ ├── accessors.rb │ ├── config.rb │ └── ssh_keys.rb │ ├── ssh_key.rb │ └── version.rb └── spec ├── core_ext └── faker │ ├── git.rb │ └── ssh.rb ├── fixtures ├── configs │ ├── complicated-output.conf │ ├── complicated.conf │ └── simple.conf ├── gitolite-admin │ ├── .gitted │ │ ├── COMMIT_EDITMSG │ │ ├── HEAD │ │ ├── config │ │ ├── description │ │ ├── hooks │ │ │ ├── applypatch-msg.sample │ │ │ ├── commit-msg.sample │ │ │ ├── post-update.sample │ │ │ ├── pre-applypatch.sample │ │ │ ├── pre-commit.sample │ │ │ ├── pre-push.sample │ │ │ ├── pre-rebase.sample │ │ │ ├── prepare-commit-msg.sample │ │ │ └── update.sample │ │ ├── index │ │ ├── info │ │ │ └── exclude │ │ ├── logs │ │ │ ├── HEAD │ │ │ └── refs │ │ │ │ └── heads │ │ │ │ └── master │ │ ├── objects │ │ │ ├── 13 │ │ │ │ └── 81680579c7e0626544e28bbca00b0c3b5004e2 │ │ │ ├── 25 │ │ │ │ └── 2a0e969ccca06c8258f811d2a6f01d883c2a0c │ │ │ ├── 39 │ │ │ │ └── 5dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b │ │ │ ├── 41 │ │ │ │ └── 1a9fa047b7c928939882769d2fac89a1c87dd0 │ │ │ ├── 42 │ │ │ │ └── 333c37a7910bff07c7e7a95a3d09b3f9966571 │ │ │ ├── 43 │ │ │ │ └── cb5e7b11750c16f2196bd8548e7b6277b372ca │ │ │ ├── 68 │ │ │ │ └── b71afffb0b6781d974af6cb93ef076ded21bb5 │ │ │ ├── 74 │ │ │ │ └── 0ef5222fcd51ad7eba4a559396fdf856b20482 │ │ │ ├── 86 │ │ │ │ └── 692711b922198563496a69cf30ca772f6a6af3 │ │ │ ├── 94 │ │ │ │ └── f2765eb76820309b11de0018dd2a6bc53ae8f7 │ │ │ ├── 04 │ │ │ │ └── 6aa40bf1f4fbef8fe915274bd8af30784f4974 │ │ │ ├── 06 │ │ │ │ └── 3a0c108a0a431fff7d9ea819e55653c6fda14c │ │ │ ├── 0a │ │ │ │ └── 0493ecd1ef1102cc11211b95d19ef7c4823a23 │ │ │ ├── 0b │ │ │ │ ├── 8ae0021fafc32dbecbbcdf82bfe1ec3728432b │ │ │ │ └── 9ea7db8498c31c36567cc7dc528e9a06587ef9 │ │ │ ├── 3b │ │ │ │ └── 8c81ac4b6d9276920ec06139d959b9aeb25456 │ │ │ ├── 5c │ │ │ │ └── f57acca019629fd98a3d9014cd63580a74f4ac │ │ │ ├── 9c │ │ │ │ └── c84c9d97b8fa8d4cb6c52d6495eca4bff04130 │ │ │ ├── 9d │ │ │ │ ├── 248211ad73f2fd188f75988dfd9e66431f2e8a │ │ │ │ └── ce718e57053022f2e184ba94e2b5131fcbe551 │ │ │ ├── ac │ │ │ │ └── b157e4a3d567d42132d608db26143b91c4789c │ │ │ ├── ad │ │ │ │ └── 91cc1d968ad5fdcb8bd0df04db3523d0b98156 │ │ │ ├── b3 │ │ │ │ └── 178d7dfddbc3df3e03eb271f0cdbe864aa902f │ │ │ ├── b6 │ │ │ │ └── de0801eb994701c5a55a0793796aebc2440372 │ │ │ ├── c9 │ │ │ │ ├── 0707f5301413f121360c9b26784167652a3d2a │ │ │ │ └── 3d2aaacc82cb05a88994d2b44691d07477bde9 │ │ │ ├── d9 │ │ │ │ └── 3bb9a696d405af34d9dcafc2e4611e1d5959dd │ │ │ ├── df │ │ │ │ └── cd7e43b90664b02a7765c54d45277f54bcd2ee │ │ │ ├── e1 │ │ │ │ └── 57cf40f5a30aa527d77c10840010c4654b4eab │ │ │ ├── e3 │ │ │ │ └── c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 │ │ │ └── f4 │ │ │ │ └── a351cf0debfb5ac18061f7cb4e7ff2e5916719 │ │ └── refs │ │ │ └── heads │ │ │ └── master │ ├── conf │ │ └── gitolite.conf │ └── keydir │ │ ├── admin.pub │ │ └── bob.pub └── keys │ └── bob │ ├── bob.pub │ ├── bob@example.com.pub │ ├── desktop │ └── bob.pub │ └── school │ └── bob.pub ├── spec_helper.rb ├── support └── helper.rb └── unit_tests ├── config_spec.rb ├── dirty_proxy_spec.rb ├── gitolite_admin_spec.rb ├── group_spec.rb ├── repo_spec.rb └── ssh_key_spec.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | pull_request: 9 | branches: 10 | - '**' 11 | schedule: 12 | - cron: '0 4 1 * *' 13 | 14 | jobs: 15 | rspec: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | ruby: 21 | - '3.3' 22 | - '3.2' 23 | - '3.1' 24 | - '3.0' 25 | - '2.7' 26 | - 'head' 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: ${{ matrix.ruby }} 36 | bundler-cache: true 37 | 38 | - name: RSpec & publish code coverage 39 | uses: paambaati/codeclimate-action@v8.0.0 40 | env: 41 | CC_TEST_REPORTER_ID: 3dfc7b128d751fb7905e3fc1b919d81b9203ef295341ea324305fac22a8be8bc 42 | with: 43 | coverageCommand: bin/rspec 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.lock 3 | *.log 4 | .bundle 5 | /pkg 6 | /rdoc 7 | /coverage 8 | /junit 9 | /tmp 10 | 11 | # Gemnasium gem configuration file 12 | config/gemnasium.yml 13 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | require: 3 | - rubocop-performance 4 | - rubocop-rake 5 | - rubocop-rspec 6 | 7 | AllCops: 8 | NewCops: enable 9 | TargetRubyVersion: 2.7 10 | Exclude: 11 | - bin/* 12 | - spec/**/* 13 | 14 | Style/Documentation: 15 | Enabled: false 16 | 17 | Layout/EmptyLines: 18 | Enabled: false 19 | 20 | Layout/EmptyLinesAroundModuleBody: 21 | Enabled: false 22 | 23 | Layout/EmptyLinesAroundClassBody: 24 | Enabled: false 25 | 26 | Layout/EmptyLineBetweenDefs: 27 | Enabled: false 28 | 29 | Layout/IndentationConsistency: 30 | EnforcedStyle: indented_internal_methods 31 | 32 | Layout/HashAlignment: 33 | Enabled: false 34 | 35 | Style/PerlBackrefs: 36 | Enabled: false 37 | 38 | Metrics/MethodLength: 39 | Max: 15 40 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | JBox Admin 2 | Nicolas Rodriguez 3 | Oliver Günther 4 | Stafford Brunk 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'faker' 8 | gem 'forgery' 9 | gem 'guard-rspec' 10 | gem 'pry' 11 | gem 'rake' 12 | gem 'rspec' 13 | gem 'rubocop' 14 | gem 'rubocop-performance' 15 | gem 'rubocop-rake' 16 | gem 'rubocop-rspec' 17 | gem 'simplecov' 18 | gem 'sshkey' 19 | 20 | gem 'base64' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0') 21 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | guard :rspec, cmd: 'bundle exec rspec' do 4 | require 'guard/rspec/dsl' 5 | dsl = Guard::RSpec::Dsl.new(self) 6 | 7 | # RSpec files 8 | rspec = dsl.rspec 9 | watch(rspec.spec_helper) { rspec.spec_dir } 10 | watch(rspec.spec_support) { rspec.spec_dir } 11 | watch(rspec.spec_files) 12 | 13 | # Ruby files 14 | ruby = dsl.ruby 15 | dsl.watch_spec_files_for(ruby.lib_files) 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## gitolite-rugged 2 | 3 | [![GitHub license](https://img.shields.io/github/license/jbox-web/gitolite-rugged.svg)](https://github.com/jbox-web/gitolite-rugged/blob/devel/LICENSE) 4 | [![GitHub release](https://img.shields.io/github/release/jbox-web/gitolite-rugged.svg)](https://github.com/jbox-web/gitolite-rugged/releases/latest) 5 | [![CI](https://github.com/jbox-web/gitolite-rugged/workflows/CI/badge.svg)](https://github.com/jbox-web/gitolite-rugged/actions) 6 | [![Code Climate](https://codeclimate.com/github/jbox-web/gitolite-rugged/badges/gpa.svg)](https://codeclimate.com/github/jbox-web/gitolite-rugged) 7 | [![Test Coverage](https://codeclimate.com/github/jbox-web/gitolite-rugged/badges/coverage.svg)](https://codeclimate.com/github/jbox-web/gitolite-rugged/coverage) 8 | 9 | ### A Ruby interface to manage the Gitolite Git backend system, easy ;) 10 | 11 | This gem is designed to provide a Ruby interface to the [Gitolite](https://github.com/sitaramc/gitolite) Git backend system via [libgit2/rugged](https://github.com/libgit2/rugged) gem. 12 | 13 | It provides these functionalities : 14 | 15 | * SSH Public Keys Management 16 | * Repositories Management 17 | * Gitolite Admin Repository Bootstrapping 18 | 19 | ## Requirements 20 | 21 | * Ruby 2.7 or 3.0+ 22 | * a working [Gitolite](https://github.com/sitaramc/gitolite) installation 23 | 24 | ## Installation 25 | 26 | Install dependencies : 27 | 28 | ```sh 29 | # On Debian/Ubuntu 30 | root# apt-get install build-essential libssh2-1 libssh2-1-dev cmake libgpg-error-dev 31 | 32 | # On Fedora/CentoS/RedHat 33 | root# yum groupinstall "Development Tools" 34 | root# yum install libssh2 libssh2-devel cmake libgpg-error-devel 35 | ``` 36 | 37 | Then put this in your ```Gemfile``` : 38 | 39 | ```ruby 40 | gem 'gitolite-rugged', git: 'https://github.com/jbox-web/gitolite-rugged.git', tag: '1.5.0' 41 | ``` 42 | 43 | then `bundle install`. 44 | 45 | ## Usage 46 | 47 | ### Bootstrapping the gitolite-admin.git repository 48 | 49 | You can have `gitolite-rugged` clone the repository for you on demand, however I would recommend cloning it manually. 50 | See it as a basic check that your gitolite installation was correctly set up. 51 | 52 | In both cases, use the following code to create an instance of the manager: 53 | 54 | ```ruby 55 | settings = { :public_key => '~/.ssh/id_rsa.pub', :private_key => '~/.ssh/id_rsa' } 56 | admin = Gitolite::GitoliteAdmin.new('/home/myuser/gitolite-admin', settings) 57 | ``` 58 | 59 | For cloning and pushing to the gitolite-admin.git, you have to provide several options to `GitoliteAdmin` in the settings hash. The following keys are used. 60 | 61 | Option | Default | Description | 62 | ---------------------| ------------------------------------------| ------------| 63 | **:private_key** | ```empty``` (this field is mandatory) | Path to the file containing the private SSH key for ```:git_user``` 64 | **:public_key** | ```empty``` (this field is mandatory) | Path to the file containing the public SSH key for ```:git_user``` 65 | **:git_user** | ```git``` | The git user to SSH to 66 | **:hostname** | ```localhost``` | Hostname for clone url 67 | **:author_name** | ```gitolite-rugged gem``` | The git author name to commit with 68 | **:author_email** | ```gitolite-rugged@localhost``` | The git author e-mail address to commit with 69 | **:commit_msg** | ```Commited by the gitolite-rugged gem``` | The commit message to use when updating the repo 70 | **:config_dir** | ```conf``` | Config directory within gitolite repository 71 | **:key_dir** | ```keydir``` | Public key directory within gitolite repository 72 | **:config_file** | ```gitolite.conf``` | Config file to parse **(use only when you use the 'include' directive of gitolite)** 73 | **:key_subdir** | ```''``` (i.e., directly in keydir) | Where to store gitolite-rugged known keys 74 | **:lock\_file_path** | ```gitolite-admin.git/.lock``` | location of the transaction lockfile 75 | 76 | 77 | ### Managing Public Keys 78 | 79 | To add a key, create a `SSHKey` object and use the `add_key(key)` method of GitoliteAdmin. 80 | 81 | ```ruby 82 | # From filesystem 83 | key_from_file = SSHKey.from_file("/home/alice/.ssh/id_rsa.pub") 84 | 85 | # From String, which requires us to add an owner manually 86 | key_from_string = SSHKey.from_string('ssh-rsa AAAAB3N/* .... */JjZ5SgfIKab bob@localhost', 'bob') 87 | 88 | admin.add_key(key_from_string) 89 | admin.add_key(key_from_file) 90 | ``` 91 | 92 | Note that you can add a *location* using the syntax described in [the Gitolite documentation](https://gitolite.com/gitolite/basic-admin.html#multiple-keys-per-user). 93 | 94 | To write out the changes to the keys to the filesystem and push them to gitolite, call `admin.save_and_apply`. 95 | You can also manually call `admin.save` to commit the changes locally, but not push them. 96 | 97 | 98 | ### Managing Repositories 99 | 100 | To add a new repository, we first create and configure it, and then add it to the memory representation of gitolite: 101 | 102 | ```ruby 103 | repo = Gitolite::Config::Repo.new('foobar') 104 | repo.add_permission("RW+", "alice", "bob") 105 | 106 | # Add the repo 107 | admin.config.add_repo(repo) 108 | ``` 109 | 110 | To remove a repository called 'foobar', execute `config.rm_repo('foobar')`. 111 | 112 | 113 | ### Groups 114 | 115 | As in the [Gitolite Config](https://gitolite.com/gitolite/conf#group-definitions) you can define groups as an alias to repos or users. 116 | 117 | ```ruby 118 | # Creating a group 119 | devs = Gitolite::Config::Group.new('developers') 120 | devs.add_users("alice", "bob") 121 | 122 | # Adding a group to config 123 | admin.config.add_group(devs) 124 | ``` 125 | 126 | ## See also 127 | 128 | You can checkout the no longer maintain previous version of this lib : [jbox-gitolite](https://github.com/jbox-web/gitolite) (based on [gitlab-grit](https://github.com/gitlabhq/grit)). 129 | 130 | ## Contribute 131 | 132 | You can contribute to this plugin in many ways such as : 133 | * Helping with documentation 134 | * Contributing code (features or bugfixes) 135 | * Reporting a bug 136 | * Submitting translations 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | task default: :spec 8 | 9 | task :console do 10 | require 'pry' 11 | require 'gitolite' 12 | puts 'Loaded Gitolite' 13 | ARGV.clear 14 | Pry.start 15 | end 16 | -------------------------------------------------------------------------------- /bin/_guard-core: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application '_guard-core' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("guard", "_guard-core") 30 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /bin/guard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'guard' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("guard", "guard") 30 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rake", "rake") 30 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rspec' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rspec-core", "rspec") 30 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("rubocop", "rubocop") 30 | -------------------------------------------------------------------------------- /gitolite.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/gitolite/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'gitolite-rugged' 7 | s.version = Gitolite::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Oliver Günther', 'Nicolas Rodriguez'] 10 | s.email = ['mail@oliverguenther.de', 'nico@nicoladmin.fr'] 11 | s.homepage = 'https://github.com/redmine-git-hosting/gitolite-rugged' 12 | s.summary = 'A Ruby gem for manipulating the Gitolite Git backend via the gitolite-admin repository.' 13 | s.description = 'This gem is designed to provide a Ruby interface to the Gitolite Git backend system using libgit2/rugged. This gem aims to provide all management functionality that is available via the gitolite-admin repository (like SSH keys, repository permissions, etc)' 14 | s.license = 'MIT' 15 | 16 | s.required_ruby_version = '>= 2.7.0' 17 | 18 | s.files = `git ls-files`.split("\n") 19 | 20 | s.add_dependency 'gratr19', '~> 0.4.4', '>= 0.4.4.1' 21 | s.add_dependency 'rugged', '>= 1.5.1' 22 | s.add_dependency 'zeitwerk' 23 | end 24 | -------------------------------------------------------------------------------- /lib/gitolite.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'tempfile' 4 | require 'fileutils' 5 | require 'pathname' 6 | 7 | require 'rugged' 8 | require 'gratr' 9 | 10 | require 'zeitwerk' 11 | loader = Zeitwerk::Loader.for_gem 12 | loader.inflector.inflect 'ssh_key' => 'SSHKey' 13 | loader.setup 14 | 15 | module Gitolite 16 | end 17 | -------------------------------------------------------------------------------- /lib/gitolite/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | 5 | class Config 6 | 7 | attr_accessor :repos, :groups, :filename 8 | 9 | def initialize(config) 10 | @repos = {} 11 | @groups = {} 12 | @filename = File.basename(config) 13 | process_config(config) 14 | end 15 | 16 | 17 | class << self 18 | 19 | def init(filename = 'gitolite.conf') 20 | file = Tempfile.new(filename) 21 | conf = new(file.path) 22 | conf.filename = filename # kill suffix added by Tempfile 23 | file.close(unlink_now = true) 24 | conf 25 | end 26 | 27 | end 28 | 29 | 30 | # TODO: merge repo unless overwrite = true 31 | # 32 | def add_repo(repo, _overwrite = false) 33 | unless repo.instance_of? Gitolite::Config::Repo 34 | raise ArgumentError, 'Repo must be of type Gitolite::Config::Repo!' 35 | end 36 | 37 | @repos[repo.name] = repo 38 | end 39 | 40 | 41 | def rm_repo(repo) 42 | name = normalize_repo_name(repo) 43 | @repos.delete(name) 44 | end 45 | 46 | 47 | def has_repo?(repo) 48 | name = normalize_repo_name(repo) 49 | @repos.key?(name) 50 | end 51 | 52 | 53 | def get_repo(repo) 54 | name = normalize_repo_name(repo) 55 | @repos[name] 56 | end 57 | 58 | 59 | def add_group(group, _overwrite = false) 60 | unless group.instance_of? Gitolite::Config::Group 61 | raise ArgumentError, 'Group must be of type Gitolite::Config::Group!' 62 | end 63 | 64 | @groups[group.name] = group 65 | end 66 | 67 | 68 | def rm_group(group) 69 | name = normalize_group_name(group) 70 | @groups.delete(name) 71 | end 72 | 73 | 74 | def has_group?(group) 75 | name = normalize_group_name(group) 76 | @groups.key?(name) 77 | end 78 | 79 | 80 | def get_group(group) 81 | name = normalize_group_name(group) 82 | @groups[name] 83 | end 84 | 85 | 86 | # rubocop:disable Metrics/AbcSize 87 | def to_file(path = '.', filename = @filename) 88 | FileUtils.mkdir_p(path) unless File.directory?(path) 89 | 90 | new_conf = File.join(path, filename) 91 | 92 | File.open(new_conf, 'w') do |f| 93 | f.sync = true 94 | 95 | # Output groups 96 | build_groups_depgraph.each { |group| f.write group.to_s } 97 | 98 | # Output repositories 99 | @repos.sort.each { |_k, v| f.write "\n#{v}" } 100 | 101 | # Output descriptions 102 | f.write "\n" 103 | f.write gitweb_descriptions.join("\n") 104 | end 105 | 106 | new_conf 107 | end 108 | # rubocop:enable Metrics/AbcSize 109 | 110 | 111 | def gitweb_descriptions 112 | @repos.sort.map { |_k, v| v.gitweb_description }.compact 113 | end 114 | 115 | 116 | private 117 | 118 | 119 | # Based on 120 | # https://github.com/sitaramc/gitolite/blob/pu/src/gl-compile-conf#cleanup_conf_line 121 | # 122 | def cleanup_config_line(line) 123 | # remove comments, even those that happen inline 124 | line.gsub!(/^((".*?"|[^#"])*)#.*/) { |m| m = $1 } 125 | 126 | # fix whitespace 127 | line.gsub!('=', ' = ') 128 | line.gsub!(/\s+/, ' ') 129 | line.strip 130 | end 131 | 132 | 133 | # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength 134 | def process_config(config) 135 | # will store our context for permissions or config declarations 136 | context = [] 137 | 138 | # On first call with a custom *.conf, the config might not yet exist 139 | return unless File.exist?(config) 140 | 141 | # Read each line of our config 142 | File.open(config, 'r').each do |l| 143 | line = cleanup_config_line(l) 144 | next if line.empty? # lines are empty if we killed a comment 145 | 146 | case line 147 | 148 | # found a repo definition 149 | when /^repo (.*)/ 150 | # Empty our current context 151 | context = [] 152 | 153 | repos = $1.split 154 | repos.each do |r| 155 | context << r 156 | @repos[r] = Repo.new(r) unless has_repo?(r) 157 | end 158 | 159 | # repo permissions 160 | when /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/ 161 | perm = $1 162 | refex = $2 || '' 163 | users = $3.split 164 | 165 | context.each do |c| 166 | @repos[c].add_permission(perm, refex, users) 167 | end 168 | 169 | # repo git config 170 | when /^config (.+) = ?(.*)/ 171 | key = $1 172 | value = $2 173 | 174 | context.each do |c| 175 | @repos[c].set_git_config(key, value) 176 | end 177 | 178 | # repo gitolite option 179 | when /^option (.+) = (.*)/ 180 | key = $1 181 | value = $2 182 | 183 | raise ParseError, "Missing gitolite option value for repo: #{repo} and key: #{key}" if value.nil? 184 | 185 | context.each do |c| 186 | @repos[c].set_gitolite_option(key, value) 187 | end 188 | 189 | # group definition 190 | when /^#{Group::PREPEND_CHAR}(\S+) = ?(.*)/ 191 | group = $1 192 | users = $2.split 193 | 194 | @groups[group] = Group.new(group) unless has_group?(group) 195 | @groups[group].add_users(users) 196 | 197 | # gitweb definition 198 | when /^(\S+)(?: "(.*?)")? = "(.*)"$/ 199 | repo = $1 200 | owner = $2 201 | description = $3 202 | 203 | # Check for missing description 204 | raise ParseError, "Missing Gitweb description for repo: #{repo}" if description.nil? 205 | 206 | # Check for groups 207 | raise ParseError, 'Gitweb descriptions cannot be set for groups' if /@.+/.match?(repo) 208 | 209 | if has_repo? repo 210 | r = @repos[repo] 211 | else 212 | r = Repo.new(repo) 213 | add_repo(r) 214 | end 215 | 216 | r.owner = owner 217 | r.description = description 218 | 219 | when /^include "(.+)"/ 220 | # TODO: implement includes 221 | # ignore includes for now 222 | 223 | when /^subconf (\S+)$/ 224 | # TODO: implement subconfs 225 | # ignore subconfs for now 226 | 227 | else 228 | raise ParseError, "'#{line}' cannot be processed" 229 | end 230 | end 231 | end 232 | # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength 233 | 234 | 235 | # Normalizes the various different input objects to Strings 236 | # 237 | def normalize_name(context, constant = nil) 238 | case context 239 | when constant 240 | context.name 241 | when Symbol 242 | context.to_s 243 | else 244 | context 245 | end 246 | end 247 | 248 | 249 | def method_missing(meth, *args, &block) 250 | if meth.to_s =~ /normalize_(\w+)_name/ 251 | # Could use Object.const_get to figure out the constant here 252 | # but for only two cases, this is more readable 253 | case $1 254 | when 'repo' 255 | normalize_name(args[0], Gitolite::Config::Repo) 256 | when 'group' 257 | normalize_name(args[0], Gitolite::Config::Group) 258 | end 259 | else 260 | super 261 | end 262 | end 263 | 264 | 265 | # Builds a dependency tree from the groups in order to ensure all groups 266 | # are defined before they are used 267 | # 268 | # rubocop:disable Metrics/AbcSize 269 | def build_groups_depgraph 270 | dp = ::GRATR::Digraph.new 271 | 272 | # Add each group to the graph 273 | @groups.each_value do |group| 274 | dp.add_vertex! group 275 | 276 | # Select group names from the users 277 | subgroups = group.users.select { |u| u =~ /^#{Group::PREPEND_CHAR}.*$/ }.map { |g| get_group g.gsub(Group::PREPEND_CHAR, '') } 278 | 279 | subgroups.each do |subgroup| 280 | dp.add_edge! subgroup, group 281 | end 282 | end 283 | 284 | # Figure out if we have a good depedency graph 285 | dep_order = dp.topsort 286 | 287 | if dep_order.empty? 288 | raise GroupDependencyError unless @groups.empty? 289 | end 290 | 291 | dep_order 292 | end 293 | # rubocop:enable Metrics/AbcSize 294 | 295 | 296 | # Raised when something in a config fails to parse properly 297 | # 298 | class ParseError < RuntimeError 299 | end 300 | 301 | 302 | # Raised when group dependencies cannot be suitably resolved for output 303 | # 304 | class GroupDependencyError < RuntimeError 305 | end 306 | 307 | end 308 | end 309 | -------------------------------------------------------------------------------- /lib/gitolite/config/group.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class Config 5 | 6 | # Represents a group inside the gitolite configuration. The name and users 7 | # options are all encapsulated in this class. All users are stored as 8 | # Strings! 9 | class Group 10 | 11 | attr_accessor :name, :users 12 | 13 | PREPEND_CHAR = '@' 14 | 15 | def initialize(name) 16 | # naively remove the prepend char 17 | # I don't think you can have two of them in a group name 18 | @name = name.gsub(PREPEND_CHAR, '') 19 | @users = [] 20 | end 21 | 22 | 23 | def empty! 24 | @users.clear 25 | end 26 | 27 | 28 | def add_user(user) 29 | return if has_user?(user) 30 | 31 | @users.push(user.to_s).sort! 32 | end 33 | 34 | 35 | def add_users(*users) 36 | fixed_users = users.flatten.map(&:to_s) 37 | @users.concat(fixed_users).sort!.uniq! 38 | end 39 | 40 | 41 | def rm_user(user) 42 | @users.delete(user.to_s) 43 | end 44 | 45 | 46 | def has_user?(user) 47 | @users.include? user.to_s 48 | end 49 | 50 | 51 | def size 52 | @users.length 53 | end 54 | 55 | 56 | def to_s 57 | members = @users.join(' ') 58 | name = "#{PREPEND_CHAR}#{@name}" 59 | "#{name.ljust(20)}= #{members}\n" 60 | end 61 | 62 | end 63 | 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/gitolite/config/repo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class Config 5 | 6 | # Represents a repo inside the gitolite configuration. The name, permissions, and git config 7 | # options are all encapsulated in this class 8 | class Repo 9 | 10 | ALLOWED_PERMISSIONS = /-|C|R|RW\+?(?:C?D?|D?C?)M?/.freeze 11 | 12 | attr_accessor :permissions, :name, :config, :options, :owner, :description 13 | 14 | # Store the perm hash in a lambda since we have to create a new one on every deny rule 15 | # The perm hash is stored as a 2D hash, with individual permissions being the first 16 | # degree and individual refexes being the second degree. Both Hashes must respect order 17 | # 18 | def initialize(name) 19 | @perm_hash_lambda = -> { Hash.new { |k, v| k[v] = Hash.new { |k2, v2| k2[v2] = [] } } } 20 | @permissions = [@perm_hash_lambda.call] 21 | 22 | @name = name 23 | @config = {} # git config 24 | @options = {} # gitolite config 25 | end 26 | 27 | 28 | def clean_permissions 29 | @permissions = [@perm_hash_lambda.call] 30 | end 31 | 32 | 33 | def add_permission(perm, refex = '', *users) 34 | if ALLOWED_PERMISSIONS.match?(perm) 35 | # Handle deny rules 36 | if perm == '-' 37 | @permissions.push(@perm_hash_lambda.call) 38 | end 39 | 40 | @permissions.last[perm][refex].concat users.flatten 41 | @permissions.last[perm][refex].uniq! 42 | else 43 | raise InvalidPermissionError, "#{perm} is not in the allowed list of permissions!" 44 | end 45 | end 46 | 47 | 48 | def set_git_config(key, value) 49 | @config[key] = value 50 | end 51 | 52 | 53 | def unset_git_config(key) 54 | @config.delete(key) 55 | end 56 | 57 | 58 | def set_gitolite_option(key, value) 59 | @options[key] = value 60 | end 61 | 62 | 63 | def unset_gitolite_option(key) 64 | @options.delete(key) 65 | end 66 | 67 | 68 | # rubocop:disable Metrics/AbcSize 69 | def to_s 70 | repo = "repo #{@name}\n" 71 | 72 | @permissions.each do |perm_hash| 73 | perm_hash.each do |perm, list| 74 | list.each do |refex, users| 75 | repo += ' ' + perm.ljust(6) + refex.ljust(25) + '= ' + users.join(' ') + "\n" 76 | end 77 | end 78 | end 79 | 80 | @config.each do |k, v| 81 | repo += ' config ' + k + ' = ' + v.to_s + "\n" 82 | end 83 | 84 | @options.each do |k, v| 85 | repo += ' option ' + k + ' = ' + v.to_s + "\n" 86 | end 87 | 88 | repo 89 | end 90 | # rubocop:enable Metrics/AbcSize 91 | 92 | 93 | def gitweb_description 94 | return nil if @description.nil? 95 | 96 | desc = "#{@name} " 97 | desc += "\"#{@owner}\" " unless @owner.nil? 98 | desc += "= \"#{@description}\"" 99 | desc 100 | end 101 | 102 | 103 | # Gets raised if a permission that isn't in the allowed 104 | # list is passed in 105 | class InvalidPermissionError < ArgumentError 106 | end 107 | 108 | end 109 | 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/gitolite/dirty_proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | 5 | # Very simple proxy object for checking if the proxied object was modified 6 | # since the last clean_up! method called. It works correctly only for objects 7 | # with proper hash method! 8 | 9 | class DirtyProxy < BasicObject 10 | 11 | def initialize(target) 12 | @target = target 13 | clean_up! 14 | end 15 | 16 | def method_missing(method, *args, &block) 17 | @target.send(method, *args, &block) 18 | end 19 | 20 | def respond_to?(symbol, include_private = false) 21 | super || %i[dirty? clean_up!].include?(symbol.to_sym) 22 | end 23 | 24 | def dirty? 25 | @clean_hash != @target.hash 26 | end 27 | 28 | def clean_up! 29 | @clean_hash = @target.hash 30 | end 31 | 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/gitolite/gitolite_admin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class GitoliteAdmin 5 | 6 | # Default settings 7 | DEFAULTS = { 8 | # clone/push url settings 9 | git_user: 'git', 10 | hostname: 'localhost', 11 | 12 | # Commit settings 13 | author_name: 'gitolite-rugged gem', 14 | author_email: 'gitolite-rugged@localhost', 15 | commit_msg: 'Commited by the gitolite-rugged gem', 16 | 17 | # Gitolite-Admin settings 18 | config_dir: 'conf', 19 | key_dir: 'keydir', 20 | key_subdir: '', 21 | config_file: 'gitolite.conf', 22 | lock_file_path: '.lock', 23 | local_branch: 'master', 24 | remote_branch: 'master', 25 | 26 | # Repo settings 27 | update_on_init: true, 28 | reset_before_update: true 29 | }.freeze 30 | 31 | include GitoliteAdmin::Accessors 32 | include GitoliteAdmin::Config 33 | include GitoliteAdmin::SshKeys 34 | 35 | attr_reader :repo, :path 36 | 37 | # Intialize with the path to 38 | # the gitolite-admin repository 39 | # 40 | # Settings: 41 | # [Connection] 42 | # :private_key: The key file containing the private SSH key for :git_user 43 | # :public_key: The key file containing the public SSH key for :git_user 44 | # :git_user: The git user to SSH to (:git_user@localhost:gitolite-admin.git), defaults to 'git' 45 | # :hostname: Hostname for clone url. Defaults to 'localhost' 46 | # 47 | # [Gitolite-Admin] 48 | # :config_dir: Config directory within gitolite repository (defaults to 'conf') 49 | # :key_dir: Public key directory within gitolite repository (defaults to 'keydir') 50 | # :config_file: Config file to parse (default: 'gitolite.conf') 51 | # **use only when you use the 'include' directive of gitolite)** 52 | # :key_subdir: Where to store gitolite-rugged known keys, defaults to '' (i.e., directly in keydir) 53 | # :lock_file_path: location of the transaction lockfile, defaults to /.lock 54 | # 55 | # The settings hash is forwarded to +GitoliteAdmin.new+ as options. 56 | # 57 | def initialize(path, settings = {}) 58 | @path = path 59 | @settings = DEFAULTS.merge(settings) 60 | 61 | # Ensure SSH key settings exist 62 | @settings.fetch(:public_key) 63 | @settings.fetch(:private_key) 64 | 65 | # Set repository instance variable 66 | @repo = set_repo 67 | 68 | # Update repository if asked 69 | update if @settings[:update_on_init] 70 | 71 | # Load Gitolite config 72 | reload! 73 | end 74 | 75 | 76 | class << self 77 | 78 | # Checks if the given path is a gitolite-admin repository. 79 | # A valid repository contains a conf folder, keydir folder, 80 | # and a configuration file within the conf folder. 81 | # 82 | def is_gitolite_admin_repo?(dir) 83 | # First check if it is a git repository 84 | begin 85 | repo = Rugged::Repository.new(dir) 86 | return false if repo.empty? 87 | rescue Rugged::RepositoryError, Rugged::OSError 88 | return false 89 | end 90 | 91 | # Check if config file, key directory exist 92 | [ 93 | File.join(dir, DEFAULTS[:config_dir]), 94 | File.join(dir, DEFAULTS[:key_dir]), 95 | File.join(dir, DEFAULTS[:config_dir], DEFAULTS[:config_file]) 96 | ].each { |f| return false unless File.exist?(f) } 97 | 98 | true 99 | end 100 | 101 | end 102 | 103 | 104 | def admin_url 105 | ['ssh://', @settings[:git_user], '@', @settings[:hostname], '/gitolite-admin.git'].join 106 | end 107 | 108 | 109 | def commit_author 110 | { email: @settings[:author_email], name: @settings[:author_name] }.clone 111 | end 112 | 113 | 114 | def exists? 115 | Dir.exist?(path) 116 | end 117 | 118 | 119 | def lock_file_path 120 | File.expand_path(@settings[:lock_file_path], path) 121 | end 122 | 123 | 124 | def local_branch 125 | get_references "refs/heads/#{@settings[:local_branch]}" 126 | end 127 | 128 | 129 | def remote_branch 130 | get_references "refs/remotes/origin/#{@settings[:remote_branch]}" 131 | end 132 | 133 | 134 | def update_ref 135 | "refs/heads/#{@settings[:local_branch]}" 136 | end 137 | 138 | 139 | def update_message 140 | "[gitolite-rugged] Merged `origin/#{@settings[:remote_branch]}` into `#{@settings[:local_branch]}`" 141 | end 142 | 143 | 144 | ###################### 145 | # Config accessors # 146 | ###################### 147 | 148 | # This method will destroy the in-memory data structures and reload everything 149 | # from the file system 150 | # 151 | def reload! 152 | @ssh_keys = load_keys 153 | @config = load_config 154 | end 155 | 156 | 157 | def credentials 158 | @credentials ||= 159 | Rugged::Credentials::SshKey.new( 160 | username: @settings[:git_user], 161 | publickey: @settings[:public_key], 162 | privatekey: @settings[:private_key] 163 | ) 164 | end 165 | 166 | 167 | def get_references(name) 168 | ref = repo.references[name] 169 | return nil if ref.nil? 170 | 171 | ref.target 172 | end 173 | 174 | 175 | private 176 | 177 | 178 | def set_repo 179 | if self.class.is_gitolite_admin_repo?(path) 180 | Rugged::Repository.new(path, credentials: credentials) 181 | else 182 | clone 183 | end 184 | end 185 | 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /lib/gitolite/gitolite_admin/accessors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class GitoliteAdmin 5 | module Accessors 6 | 7 | ########################## 8 | # Repository accessors # 9 | ########################## 10 | 11 | # Clone the gitolite-admin repo 12 | # to the given path. 13 | # 14 | # The repo is cloned from the url 15 | # +(:git_user)@(:hostname)/gitolite-admin.git+ 16 | # 17 | # The hostname may use an optional :port to allow for custom SSH ports. 18 | # E.g., +git@localhost:2222/gitolite-admin.git+ 19 | # 20 | def clone 21 | clean_up 22 | Rugged::Repository.clone_at(admin_url, path, credentials: credentials) 23 | end 24 | 25 | 26 | # This method will destroy all local tracked changes, resetting the local gitolite 27 | # git repo to HEAD 28 | # 29 | def reset! 30 | repo.reset('origin/master', :hard) 31 | end 32 | 33 | 34 | # Push back to origin 35 | # 36 | def apply 37 | repo.push('origin', ['refs/heads/master'], credentials: credentials) 38 | end 39 | 40 | 41 | # Commits all staged changes and pushes back to origin 42 | # 43 | def save_and_apply(commit_msg = nil) 44 | save(commit_msg) 45 | apply 46 | end 47 | 48 | 49 | def clean_up 50 | FileUtils.rm_rf(path) if exists? 51 | end 52 | 53 | 54 | # Writes all changed aspects out to the file system 55 | # will also stage all changes then commit 56 | # 57 | def save(commit_msg = nil) 58 | # Add all changes to index (staging area) 59 | index = repo.index 60 | 61 | # Process config file (if loaded, i.e. may be modified) 62 | save_config_file(index) if @config 63 | 64 | # Process ssh keys (if loaded, i.e. may be modified) 65 | save_ssh_keys(index) if @ssh_keys 66 | 67 | # Write index to git and resync fs 68 | commit_tree = index.write_tree(repo) 69 | index.write 70 | 71 | author = commit_author.merge(time: Time.now) 72 | 73 | opts = { 74 | parents: [repo.head.target], 75 | tree: commit_tree, 76 | update_ref: 'HEAD', 77 | message: (commit_msg || @settings[:commit_msg]), 78 | author: author, 79 | committer: author 80 | } 81 | Rugged::Commit.create(repo, opts) 82 | end 83 | 84 | 85 | # Updates the repo with changes from remote master 86 | # Warning: This resets the repo before pulling in the changes. 87 | # 88 | # rubocop:disable Metrics/AbcSize 89 | def update 90 | # Reset --hard repo before update 91 | reset! if @settings[:reset_before_update] 92 | 93 | # Fetch changes from origin 94 | repo.fetch('origin', credentials: credentials) 95 | 96 | # Create the merged index in memory 97 | merge_index = repo.merge_commits(local_branch, remote_branch) 98 | 99 | # Complete the merge by comitting it 100 | opts = { 101 | parents: [local_branch, remote_branch], 102 | tree: merge_index.write_tree(repo), 103 | update_ref: update_ref, 104 | message: update_message, 105 | author: commit_author, 106 | committer: commit_author 107 | } 108 | Rugged::Commit.create(repo, opts) 109 | 110 | reload! 111 | end 112 | # rubocop:enable Metrics/AbcSize 113 | 114 | 115 | # Lock the gitolite-admin directory and yield. 116 | # After the block is completed, calls +apply+ only. 117 | # You have to commit your changes within the transaction block 118 | # 119 | def transaction 120 | File.open(lock_file_path, File::RDWR | File::CREAT, 0o644) do |file| 121 | # Get lock 122 | file.sync = true 123 | file.flock(File::LOCK_EX) 124 | 125 | # Execute block 126 | yield 127 | 128 | # Push all changes 129 | apply 130 | 131 | # Release lock 132 | file.flock(File::LOCK_UN) 133 | end 134 | end 135 | 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /lib/gitolite/gitolite_admin/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class GitoliteAdmin 5 | module Config 6 | 7 | #################### 8 | # Path accessors # 9 | #################### 10 | 11 | def config_dir_path 12 | File.join(path, @settings[:config_dir]) 13 | end 14 | 15 | 16 | def config_file_path 17 | File.join(config_dir_path, @settings[:config_file]) 18 | end 19 | 20 | 21 | # Returns the relative directory to the gitolite config file location. 22 | # I.e., settings[config_dir]/settings[config_file] 23 | # Defaults to 'conf/gitolite.conf' 24 | # 25 | def relative_config_file 26 | File.join(@settings[:config_dir], @settings[:config_file]) 27 | end 28 | 29 | 30 | ###################### 31 | # Config accessors # 32 | ###################### 33 | 34 | def config 35 | @config ||= load_config 36 | end 37 | 38 | 39 | def config=(config) 40 | @config = config 41 | end 42 | 43 | 44 | private 45 | 46 | 47 | def load_config 48 | Gitolite::Config.new(config_file_path) 49 | end 50 | 51 | 52 | def save_config_file(index) 53 | @config.to_file(config_dir_path) 54 | index.add(relative_config_file) 55 | end 56 | 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/gitolite/gitolite_admin/ssh_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | class GitoliteAdmin 5 | module SshKeys 6 | 7 | #################### 8 | # Path accessors # 9 | #################### 10 | 11 | def key_dir_path 12 | File.join(path, relative_key_dir) 13 | end 14 | 15 | 16 | # Returns the relative directory to the public key location. 17 | # I.e., settings[key_dir]/settings[key_subdir] 18 | # Defaults to 'keydir/' 19 | # 20 | def relative_key_dir 21 | File.join(@settings[:key_dir], @settings[:key_subdir]) 22 | end 23 | 24 | 25 | ######################## 26 | # SSH Keys accessors # 27 | ######################## 28 | 29 | def ssh_keys 30 | @ssh_keys ||= load_keys 31 | end 32 | 33 | 34 | def add_key(key) 35 | raise ArgumentError, 'Key must be of type Gitolite::SSHKey!' unless key.instance_of? Gitolite::SSHKey 36 | 37 | ssh_keys[key.owner] << key 38 | true 39 | end 40 | 41 | 42 | def rm_key(key) 43 | raise ArgumentError, 'Key must be of type Gitolite::SSHKey!' unless key.instance_of? Gitolite::SSHKey 44 | 45 | ssh_keys[key.owner].delete(key) ? true : false 46 | end 47 | 48 | 49 | private 50 | 51 | 52 | # Loads all .pub files in the gitolite-admin 53 | # keydir directory 54 | def load_keys 55 | keys = Hash.new { |k, v| k[v] = DirtyProxy.new([]) } 56 | 57 | list_keys.each do |key| 58 | new_key = SSHKey.from_file(key) 59 | keys[new_key.owner] << new_key 60 | end 61 | 62 | # Mark key sets as unmodified (for dirty checking) 63 | keys.each_value(&:clean_up!) 64 | keys 65 | end 66 | 67 | 68 | def list_keys 69 | Dir.glob(key_dir_path + '/**/*.pub') 70 | end 71 | 72 | 73 | def save_ssh_keys(index) 74 | remove_ssh_keys(index) 75 | add_ssh_keys(index) 76 | end 77 | 78 | 79 | def remove_ssh_keys(index) 80 | (current_files - current_keys).each do |key| 81 | SSHKey.remove(key, key_dir_path) 82 | index.remove File.join(relative_key_dir, key) 83 | end 84 | end 85 | 86 | 87 | def current_files 88 | list_keys.map { |f| relative_key_path(f) } 89 | end 90 | 91 | 92 | def current_keys 93 | @ssh_keys.values.map { |f| f.map(&:relative_path) }.flatten 94 | end 95 | 96 | 97 | def add_ssh_keys(index) 98 | @ssh_keys.each_value do |key| 99 | # Write only keys from sets that has been modified 100 | next if key.respond_to?(:dirty?) && !key.dirty? 101 | 102 | key.each do |k| 103 | k.to_file(key_dir_path) 104 | index.add File.join(relative_key_dir, k.relative_path) 105 | end 106 | end 107 | end 108 | 109 | 110 | # Returns the relative key path 111 | # // given an absolute path 112 | # below the keydir. 113 | def relative_key_path(key_path) 114 | Pathname.new(key_path).relative_path_from(Pathname.new(key_dir_path)).to_s 115 | end 116 | 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/gitolite/ssh_key.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | 5 | # Models an SSH key within gitolite 6 | # provides support for multikeys 7 | # 8 | # Types of multi keys: 9 | # username: bob => /bob/bob.pub 10 | # username: bob, location: desktop => /bob/desktop/bob.pub 11 | 12 | class SSHKey 13 | 14 | attr_accessor :owner, :location, :type, :blob, :email 15 | 16 | class << self 17 | 18 | def from_file(key) 19 | raise ArgumentError, "#{key} does not exist!" unless File.exist?(key) 20 | 21 | # Owner is the basename of the key 22 | # i.e., //.pub 23 | owner = File.basename(key, '.pub') 24 | 25 | # Location is the middle section of the path, if any 26 | location = location_from_path(File.dirname(key), owner) 27 | 28 | # Use string key constructor 29 | from_string(File.read(key), owner, location) 30 | end 31 | 32 | 33 | # Construct a SSHKey from a string 34 | # 35 | def from_string(key_string, owner, location = '') 36 | raise ArgumentError, 'Owner was nil, you must specify an owner' if owner.nil? 37 | 38 | # Get parts of the key 39 | type, blob, email = key_string.split 40 | 41 | # We need at least a type or blob 42 | raise ArgumentError, "'#{key_string}' is not a valid SSH key string" if type.nil? || blob.nil? 43 | 44 | # If the key didn't have an email, just use the owner 45 | email = owner if email.nil? 46 | 47 | new(type, blob, email, owner, location) 48 | end 49 | 50 | 51 | # Parse the key path above the key to be read. 52 | # As we can omit the location, there are two possible options: 53 | # 54 | # 1. Location is empty. Path is // 55 | # 2. Location is non-empty. Path is // 56 | # 57 | # We test this by checking the parent of the given path. 58 | # If it equals owner, a location was set. 59 | # This allows the daft case of e.g., using /bob/bob/bob.pub. 60 | # 61 | def location_from_path(path, owner) 62 | keyroot = File.dirname(path) 63 | File.basename(keyroot) == owner ? File.basename(path) : '' 64 | end 65 | 66 | 67 | def delete_dir_if_empty(dir) 68 | Dir.rmdir(dir) if File.directory?(dir) && Dir["#{dir}/*"].empty? 69 | rescue => e 70 | $stderr.puts("Warning: Couldn't delete empty directory: #{e.message}") 71 | end 72 | 73 | 74 | # Remove a key given a relative path 75 | # 76 | # Unlinks the key file and removes any empty parent directory 77 | # below key_dir 78 | # 79 | def remove(key_file, key_dir_path) 80 | abs_key_path = File.join(key_dir_path, key_file) 81 | key = from_file(abs_key_path) 82 | 83 | # Remove the file itself 84 | File.unlink(abs_key_path) 85 | 86 | key_owner_dir = File.join(key_dir_path, key.owner) 87 | 88 | # Remove the location, if it exists and is empty 89 | delete_dir_if_empty(File.join(key_owner_dir, key.location)) if key.location 90 | 91 | # Remove the owner dir, if empty 92 | delete_dir_if_empty(key_owner_dir) 93 | end 94 | 95 | end 96 | 97 | 98 | def initialize(type, blob, email, owner = nil, location = '') 99 | @type = type 100 | @blob = blob 101 | @email = email 102 | @owner = owner || email 103 | @location = location 104 | end 105 | 106 | 107 | def to_s 108 | [@type, @blob, @email].join(' ') 109 | end 110 | 111 | 112 | def to_file(path) 113 | # Ensure multi-key directory structure 114 | # ///.pub 115 | key_dir = File.join(path, @owner, @location) 116 | key_file = File.join(key_dir, filename) 117 | 118 | # Ensure subdirs exist 119 | FileUtils.mkdir_p(key_dir) unless File.directory?(key_dir) 120 | 121 | File.open(key_file, 'w') do |f| 122 | f.sync = true 123 | f.write(to_s) 124 | end 125 | key_file 126 | end 127 | 128 | 129 | def relative_path 130 | File.join(@owner, @location, filename) 131 | end 132 | 133 | 134 | def filename 135 | [@owner, '.pub'].join 136 | end 137 | 138 | 139 | def ==(other) 140 | @type == other.type && 141 | @blob == other.blob && 142 | @email == other.email && 143 | @owner == other.owner && 144 | @location == other.location 145 | end 146 | 147 | 148 | def hash 149 | [@owner, @location, @type, @blob, @email].hash 150 | end 151 | 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/gitolite/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Gitolite 4 | VERSION = '1.5.0' 5 | end 6 | -------------------------------------------------------------------------------- /spec/core_ext/faker/git.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Faker 4 | class Git < Base 5 | flexible :git 6 | 7 | class << self 8 | 9 | def http_url 10 | "http://#{domain_name}/#{base_path}" 11 | end 12 | 13 | 14 | def https_url 15 | "https://#{domain_name}/#{base_path}" 16 | end 17 | 18 | 19 | def ssh_url(port = 22) 20 | "ssh://git@#{domain_name}:#{port}/#{base_path}" 21 | end 22 | 23 | 24 | def git_url 25 | "git@#{domain_name}:#{base_path}" 26 | end 27 | 28 | 29 | private 30 | 31 | 32 | def domain_name 33 | "www.#{Internet.domain_name}" 34 | end 35 | 36 | 37 | def base_path 38 | "#{project_identifier}/#{project_identifier}/#{project_identifier}.git" 39 | end 40 | 41 | 42 | def project_identifier 43 | Internet.user_name(nil, ['-', '_']) 44 | end 45 | 46 | end 47 | 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/core_ext/faker/ssh.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'sshkey' 4 | 5 | module Faker 6 | class Ssh < Base 7 | flexible :ssh 8 | 9 | class << self 10 | 11 | def public_key 12 | generate_ssh_key[:public_key] 13 | end 14 | 15 | 16 | def private_key 17 | generate_ssh_key[:private_key] 18 | end 19 | 20 | 21 | def both_keys 22 | generate_ssh_key 23 | end 24 | 25 | 26 | private 27 | 28 | 29 | def generate_ssh_key 30 | key = ::SSHKey.generate(comment: "faker@#{Internet.domain_name}") 31 | { private_key: key.private_key, public_key: key.ssh_public_key } 32 | end 33 | 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/fixtures/configs/complicated-output.conf: -------------------------------------------------------------------------------- 1 | @admins = admin2 sitaram 2 | @important = QA_done master$ refs/tags/v[0-9] 3 | @oss_repos = entrans git gitolite linux perl rakudo vkc 4 | @interns = han indy james 5 | @staff = @interns another-dev au.thor bob sitaram some_dev 6 | 7 | repo @all 8 | RW+ = @admins 9 | 10 | repo @oss_repos 11 | R = @all daemon 12 | 13 | repo EXTCMD/rsync 14 | RW NAME/ = sitaram 15 | RW NAME/foo/ = user1 16 | RW NAME/baz/.*/*.c = user3 17 | R NAME/bar/ = user2 18 | 19 | repo bar 20 | 21 | repo foo 22 | RW+ = lead_dev 23 | RW = dev1 dev2 dev3 dev4 24 | RW NAME/ = lead_dev 25 | RW NAME/doc/ = dev1 dev2 26 | RW NAME/src/ = dev1 dev2 dev3 dev4 27 | option mirror.master = mars 28 | option mirror.slaves = phobos deimos 29 | option mirror.redirectOK = all 30 | 31 | repo foo2 32 | RW+ = @all-devs 33 | - VREF/COUNT/5 = @junior-devs 34 | - VREF/NAME/Makefile = @junior-devs 35 | 36 | repo foobar 37 | 38 | repo git 39 | RW = bobzilla 40 | RW master = junio 41 | RW cogito$ = pasky 42 | RW bw/ = linus 43 | RW tmp/ = @all 44 | RW refs/tags/v[0-9] = junio 45 | RW+ pu = junio 46 | - refs/tags/v[0-9] = linus pasky @others 47 | RW refs/tags/ = junio linus pasky @others 48 | 49 | repo gitolite 50 | R = @staff 51 | RW+ = sitaram 52 | config hooks.mailinglist = gitolite-commits@example.tld 53 | config hooks.emailprefix = "[gitolite] " 54 | config foo.bar = "" 55 | config foo.baz = 56 | 57 | repo gitolite-admin 58 | RW+ = @admins 59 | 60 | repo linux 61 | R = gitweb 62 | 63 | repo perl 64 | R = gitweb 65 | 66 | repo testing 67 | RW+ = @all 68 | 69 | bar = "A nice place to get drinks" 70 | foo = "Foo is a nice test repo" 71 | foobar "Bob Zilla" = "Foobar is top secret" 72 | gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" -------------------------------------------------------------------------------- /spec/fixtures/configs/complicated.conf: -------------------------------------------------------------------------------- 1 | # example conf file for gitolite 2 | 3 | # ---------------------------------------------------------------------------- 4 | # overall syntax: 5 | # - everything is space-separated; no commas, semicolons, etc (except in 6 | # the description string for gitweb) 7 | # - comments in the normal shell-ish style; no surprises there 8 | # - there are NO continuation lines of any kind 9 | # - user/repo names as simple as possible; they must start with an 10 | # alphanumeric, but after that they can also contain ".", "_", "-". 11 | # - usernames can optionally be followed by an "@" and a domainname 12 | # containing at least one "." (this allows you to use an email 13 | # address as someone's username) 14 | # - reponames can contain "/" characters (this allows you to 15 | # put your repos in a tree-structure for convenience) 16 | 17 | # objectives, over and above gitosis: 18 | # - simpler syntax 19 | # - easier gitweb/daemon control 20 | # - specify who can push a branch/tag 21 | # - specify who can rewind a branch/rewrite a tag 22 | 23 | # ---------------------------------------------------------------------------- 24 | 25 | # GROUPS 26 | # ------ 27 | 28 | # syntax: 29 | # @groupname = [one or more names] 30 | 31 | # groups let you club (user or group) names together for convenience 32 | 33 | # * a group is like a #define in C except that it can *accumulate* values 34 | # * the config file is parsed in a single-pass, so later *additions* to a 35 | # group name cannot affect earlier *uses* of it 36 | 37 | # The following examples should illustrate all this: 38 | 39 | # you can have a group of people... 40 | @staff = sitaram some_dev another-dev 41 | 42 | # ...or a group of repos 43 | @oss_repos = gitolite linux git perl rakudo entrans vkc 44 | 45 | # ...or even a group of refexes 46 | @important = master$ QA_done refs/tags/v[0-9] 47 | # (see later for what "refex"s are; I'm only mentioning it 48 | # here to emphasise that you can group them too) 49 | 50 | # even sliced and diced differently 51 | @admins = sitaram admin2 52 | # notice that sitaram is in 2 groups (staff and admins) 53 | 54 | # if you repeat a group name in another definition line, the 55 | # new ones get added to the old ones (they accumulate) 56 | @staff = au.thor 57 | # so now "@staff" expands to all 4 names 58 | 59 | # groups can include other groups, and the included group will 60 | # be expanded to whatever value it currently has 61 | @interns = indy james 62 | @staff = bob @interns 63 | # "@staff" expands to 7 names now 64 | @interns = han 65 | # "@interns" now has 3 names in it, but note that this does 66 | # not change @staff 67 | 68 | # REPO AND BRANCH PERMISSIONS 69 | # --------------------------- 70 | 71 | # syntax: 72 | # start line: 73 | # repo [one or more repos and/or repo groups] 74 | # followed by one or more permissions lines: 75 | # (C|R|RW|RW+|RWC|RW+C|RWD|RW+D|RWCD|RW+CD) [zero or more refexes] = [one or more users] 76 | 77 | # there are 6 types of permissions: R, RW, and RW+ are simple (the "+" means 78 | # permission to "rewind" -- force push a non-fast forward to -- a branch). 79 | # The *standalone* C permission pertains to creating a REPO and is described 80 | # in doc/wildcard-repositories.mkd. The C and D *suffixes* to the RW/RW+ 81 | # permissions pertain to creating or deleting a BRANCH, and are described in 82 | # doc/3-faq-tips-etc.mkd, in the sections on "separating push and create 83 | # rights" and "separating delete and rewind rights" respectively. 84 | 85 | # how permissions are matched: 86 | # - user, repo, and access (W or +) are known. For that combination, if 87 | # any of the refexes match the refname being updated, the push succeeds. 88 | # If none of them match, it fails 89 | 90 | # what's a refex? a regex to match against the ref being updated (get it?) 91 | # See next section for more on refexes 92 | 93 | # BASIC PERMISSIONS (repo level only; apply to all branches/tags in repo) 94 | 95 | # most important rule of all -- specify who can make changes 96 | # to *this* file take effect 97 | repo gitolite-admin 98 | RW+ = @admins 99 | 100 | # "@all" is a special, predefined, group name of all users 101 | # (everyone who has a pubkey in keydir) 102 | repo testing 103 | RW+ = @all 104 | 105 | # this repo is visible to staff but only sitaram can write to it 106 | repo gitolite 107 | R = @staff 108 | RW+ = sitaram 109 | 110 | # you can split up access rules for a repo for convenience 111 | # (notice that @oss_repos contains gitolite also) 112 | repo @oss_repos 113 | R = @all 114 | 115 | # set permissions to all repos. *Please* do see 116 | # doc/3-faq-tips-etc.mkd for notes on this feature 117 | repo @all 118 | RW+ = @admins 119 | 120 | # SPECIFYING AND USING A REFEX 121 | 122 | # - refexes are specified in perl regex syntax 123 | # - refexes are prefix-matched (they are internally anchored with "^" 124 | # before being used), which means a refex like "refs/tags/v[0-9]" 125 | # matches anything *starting with* that pattern. There may be text 126 | # after it (example: refs/tags/v4-r3/p7), and it will still match 127 | 128 | # ADVANCED PERMISSIONS USING REFEXES 129 | 130 | # - if no refex appears, the rule applies to all refs in that repo 131 | # - a refex is automatically prefixed by "refs/heads/" if it doesn't start 132 | # with "refs/" (so tags have to be explicitly named as 133 | # refs/tags/pattern) 134 | 135 | # here's the example from 136 | # Documentation/howto/update-hook-example.txt: 137 | 138 | # refs/heads/master junio 139 | # +refs/heads/pu junio 140 | # refs/heads/cogito$ pasky 141 | # refs/heads/bw/.* linus 142 | # refs/heads/tmp/.* .* 143 | # refs/tags/v[0-9].* junio 144 | 145 | # and here're the equivalent gitolite refexes 146 | repo git 147 | RW = bobzilla 148 | RW master = junio 149 | RW+ pu = junio 150 | RW cogito$ = pasky 151 | RW bw/ = linus 152 | RW tmp/ = @all 153 | RW refs/tags/v[0-9] = junio 154 | 155 | # DENY/EXCLUDE RULES 156 | 157 | # ***IMPORTANT NOTES ABOUT "DENY" RULES***: 158 | 159 | # - deny rules do NOT affect read access. They only apply to write access. 160 | # 161 | # - when using deny rules, the order of your rules starts to matter, where 162 | # earlier it did not. The first matching rule applies, where "matching" is 163 | # defined as either permitting the operation you're attempting (`W` or `+`), 164 | # which results in success, or a "deny" (`-`), which results in failure. 165 | # (As before, a fallthrough also results in failure). 166 | 167 | # in the example above, you cannot easily say "anyone can write any tag, 168 | # except version tags can only be written by junio". The following might look 169 | # like it works but it doesn't: 170 | 171 | # RW refs/tags/v[0-9] = junio 172 | # RW refs/tags/ = junio linus pasky @others 173 | 174 | # if you use "deny" rules, however, you can do this (a "deny" rule just uses 175 | # "-" instead of "R" or "RW" or "RW+" in the permission field) 176 | 177 | RW refs/tags/v[0-9] = junio 178 | - refs/tags/v[0-9] = linus pasky @others 179 | RW refs/tags/ = junio linus pasky @others 180 | 181 | # FILE/DIR NAME BASED RESTRICTIONS 182 | # -------------------------------- 183 | 184 | # Here's a hopefully self-explanatory example. Assume the project has the 185 | # following contents at the top level: a README, a "doc/" directory, and an 186 | # "src/" directory. 187 | 188 | repo foo 189 | RW+ = lead_dev # rule 1 190 | RW = dev1 dev2 dev3 dev4 # rule 2 191 | 192 | RW NAME/ = lead_dev # rule 3 193 | RW NAME/doc/ = dev1 dev2 # rule 4 194 | RW NAME/src/ = dev1 dev2 dev3 dev4 # rule 5 195 | option mirror.master = mars 196 | option mirror.slaves = phobos deimos 197 | option mirror.redirectOK = all 198 | 199 | 200 | repo foo2 201 | RW+ = @all-devs 202 | - VREF/COUNT/5 = @junior-devs 203 | - VREF/NAME/Makefile = @junior-devs 204 | 205 | # Notes 206 | 207 | # - the "NAME/" is part of the syntax; think of it as a keyword if you like. 208 | # The rest of it is treated as a refex to match against each file being 209 | # touched (see "SPECIFYING AND USING A REFEX" above for details) 210 | 211 | # - file/dir NAME-based restrictions are *in addition* to normal (branch-name 212 | # based) restrictions; they are not a *replacement* for them. This is why 213 | # rule #2 (or something like it, maybe with a more specific branch-name) is 214 | # needed; without it, dev1/2/3/4 cannot push any branches. 215 | 216 | # - if a repo has *any* NAME/ rules, then NAME-based restrictions are checked 217 | # for *all* users. This is why rule 3 is needed, even though we don't 218 | # actually have any NAME-based restrictions on lead_dev. Notice the pattern 219 | # on rule 3. 220 | 221 | # - *each* file touched by the commits being pushed is checked against those 222 | # rules. So, lead_dev can push changes to any files, dev1/2 can push 223 | # changes to files in "doc/" and "src/" (but not the top level README), and 224 | # dev3/4 can only push changes to files in "src/". 225 | 226 | # GITWEB AND DAEMON STUFF 227 | # ----------------------- 228 | 229 | # No specific syntax for gitweb and daemon access; just make the repo readable 230 | # ("R" access) to the special users "gitweb" and "daemon" 231 | 232 | # make "@oss_repos" (all 7 of them!) accessible via git daemon 233 | repo @oss_repos 234 | R = daemon 235 | 236 | # make the two *large* repos accessible via gitweb 237 | repo linux perl 238 | R = gitweb 239 | 240 | # REPO OWNER/DESCRIPTION LINE FOR GITWEB 241 | 242 | # syntax, one of: 243 | # reponame = "some description string in double quotes" 244 | # reponame "owner name" = "some description string in double quotes" 245 | 246 | # note: setting a description also gives gitweb access; you do not have to 247 | # give gitweb access as described above if you're specifying a description 248 | 249 | gitolite "Sitaram Chamarty" = "fast, secure, access control for git in a corporate environment" 250 | foo = "Foo is a nice test repo" 251 | foobar "Bob Zilla" = "Foobar is top secret" 252 | bar = "A nice place to get drinks" 253 | 254 | # REPO SPECIFIC GITCONFIG 255 | # ----------------------- 256 | 257 | # update 2010-02-06; this won't work unless the rc file has the right 258 | # settings; please see comments around the variable $GL_GITCONFIG_KEYS in 259 | # conf/example.gitolite.rc for details and security information. 260 | 261 | # (Thanks to teemu dot matilainen at iki dot fi) 262 | 263 | # this should be specified within a "repo" stanza 264 | 265 | # syntax: 266 | # config sectionname.keyname = [optional value_string] 267 | 268 | # example usage: if you placed a hook in hooks/common that requires 269 | # configuration information that is specific to each repo, you could do this: 270 | 271 | repo gitolite 272 | config hooks.mailinglist = gitolite-commits@example.tld 273 | config hooks.emailprefix = "[gitolite] " 274 | config foo.bar = "" 275 | config foo.baz = 276 | 277 | # This does either a plain "git config section.key value" (for the first 3 278 | # examples above) or "git config --unset-all section.key" (for the last 279 | # example). Other forms (--add, the value_regex, etc) are not supported. 280 | 281 | # INCLUDE SOME OTHER FILE 282 | # ----------------------- 283 | 284 | include "foo.conf" 285 | subconf "bar.conf" 286 | 287 | # this includes the contents of $GL_ADMINDIR/conf/foo.conf here 288 | 289 | # Notes: 290 | # - the include statement is not allowed inside delegated fragments for 291 | # security reasons. 292 | # - you can also use an absolute path if you like, although in the interests 293 | # of cloning the admin-repo sanely you should avoid doing this! 294 | 295 | # EXTERNAL COMMAND HELPERS -- RSYNC 296 | # --------------------------------- 297 | 298 | # If $RSYNC_BASE is non-empty, the following config entries come into play 299 | # (otherwise they are ignored): 300 | 301 | # a "fake" git repository to collect rsync rules. Gitolite does not 302 | # auto-create any repo whose name starts with EXTCMD/ 303 | repo EXTCMD/rsync 304 | # grant permissions to files/dirs within the $RSYNC_BASE tree. A leading 305 | # NAME/ is required as a prefix; the actual path starts after that. Matching 306 | # follows the same rules as given in "FILE/DIR NAME BASED RESTRICTIONS" above 307 | RW NAME/ = sitaram 308 | RW NAME/foo/ = user1 309 | R NAME/bar/ = user2 310 | # just to remind you that these are perl regexes, not shell globs 311 | RW NAME/baz/.*/*.c = user3 312 | -------------------------------------------------------------------------------- /spec/fixtures/configs/simple.conf: -------------------------------------------------------------------------------- 1 | repo gitolite-admin 2 | RW+ = bobzilla 3 | 4 | repo testing 5 | RW+ = @all -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/COMMIT_EDITMSG: -------------------------------------------------------------------------------- 1 | Initial repos w/ admin+bob 2 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | autocrlf = false 9 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config --bool hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | exec git diff-index --check --cached $against -- 50 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | IFS=' ' 28 | while read local_ref local_sha remote_ref remote_sha 29 | do 30 | if [ "$local_sha" = $z40 ] 31 | then 32 | # Handle delete 33 | : 34 | else 35 | if [ "$remote_sha" = $z40 ] 36 | then 37 | # New branch, examine all commits 38 | range="$local_sha" 39 | else 40 | # Update to existing branch, examine new commits 41 | range="$remote_sha..$local_sha" 42 | fi 43 | 44 | # Check for WIP commit 45 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 46 | if [ -n "$commit" ] 47 | then 48 | echo "Found WIP commit in $local_ref, not pushing" 49 | exit 1 50 | fi 51 | fi 52 | done 53 | 54 | exit 0 55 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/index -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/logs/HEAD: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 c93d2aaacc82cb05a88994d2b44691d07477bde9 Oliver Guenther 1400167798 +0200 commit (initial): Initial repos w/ admin+bob 2 | c93d2aaacc82cb05a88994d2b44691d07477bde9 5cf57acca019629fd98a3d9014cd63580a74f4ac gitolite-rugged gem 1400169242 +0200 commit: Commited by the gitolite-rugged gem 3 | 5cf57acca019629fd98a3d9014cd63580a74f4ac 0a0493ecd1ef1102cc11211b95d19ef7c4823a23 gitolite-rugged gem 1400171068 +0200 commit: Commited by the gitolite-rugged gem 4 | 0a0493ecd1ef1102cc11211b95d19ef7c4823a23 046aa40bf1f4fbef8fe915274bd8af30784f4974 gitolite-rugged gem 1400171167 +0200 commit: Commited by the gitolite-rugged gem 5 | 046aa40bf1f4fbef8fe915274bd8af30784f4974 c90707f5301413f121360c9b26784167652a3d2a gitolite-rugged gem 1400171330 +0200 commit: Commited by the gitolite-rugged gem 6 | c90707f5301413f121360c9b26784167652a3d2a e157cf40f5a30aa527d77c10840010c4654b4eab gitolite-rugged gem 1400171347 +0200 commit: Commited by the gitolite-rugged gem 7 | e157cf40f5a30aa527d77c10840010c4654b4eab 1381680579c7e0626544e28bbca00b0c3b5004e2 gitolite-rugged gem 1400171379 +0200 commit: Commited by the gitolite-rugged gem 8 | 1381680579c7e0626544e28bbca00b0c3b5004e2 acb157e4a3d567d42132d608db26143b91c4789c gitolite-rugged gem 1400171415 +0200 commit: Commited by the gitolite-rugged gem 9 | acb157e4a3d567d42132d608db26143b91c4789c 68b71afffb0b6781d974af6cb93ef076ded21bb5 gitolite-rugged gem 1400171481 +0200 commit: Commited by the gitolite-rugged gem 10 | 68b71afffb0b6781d974af6cb93ef076ded21bb5 e3c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 gitolite-rugged gem 1400171504 +0200 commit: Commited by the gitolite-rugged gem 11 | e3c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 42333c37a7910bff07c7e7a95a3d09b3f9966571 gitolite-rugged gem 1400171542 +0200 commit: Commited by the gitolite-rugged gem 12 | 42333c37a7910bff07c7e7a95a3d09b3f9966571 94f2765eb76820309b11de0018dd2a6bc53ae8f7 gitolite-rugged gem 1400171606 +0200 commit: Commited by the gitolite-rugged gem 13 | 94f2765eb76820309b11de0018dd2a6bc53ae8f7 0b8ae0021fafc32dbecbbcdf82bfe1ec3728432b gitolite-rugged gem 1400171627 +0200 commit: Commited by the gitolite-rugged gem 14 | 0b8ae0021fafc32dbecbbcdf82bfe1ec3728432b 3b8c81ac4b6d9276920ec06139d959b9aeb25456 gitolite-rugged gem 1400171634 +0200 commit: Commited by the gitolite-rugged gem 15 | 3b8c81ac4b6d9276920ec06139d959b9aeb25456 063a0c108a0a431fff7d9ea819e55653c6fda14c gitolite-rugged gem 1400171945 +0200 commit: Commited by the gitolite-rugged gem 16 | 063a0c108a0a431fff7d9ea819e55653c6fda14c d93bb9a696d405af34d9dcafc2e4611e1d5959dd gitolite-rugged gem 1400172024 +0200 commit: Commited by the gitolite-rugged gem 17 | d93bb9a696d405af34d9dcafc2e4611e1d5959dd 740ef5222fcd51ad7eba4a559396fdf856b20482 gitolite-rugged gem 1400172219 +0200 commit: Commited by the gitolite-rugged gem 18 | 740ef5222fcd51ad7eba4a559396fdf856b20482 395dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b gitolite-rugged gem 1400172287 +0200 commit: Commited by the gitolite-rugged gem 19 | 395dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b 411a9fa047b7c928939882769d2fac89a1c87dd0 gitolite-rugged gem 1400175175 +0200 commit: Commited by the gitolite-rugged gem 20 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/logs/refs/heads/master: -------------------------------------------------------------------------------- 1 | 0000000000000000000000000000000000000000 c93d2aaacc82cb05a88994d2b44691d07477bde9 Oliver Guenther 1400167798 +0200 commit (initial): Initial repos w/ admin+bob 2 | c93d2aaacc82cb05a88994d2b44691d07477bde9 5cf57acca019629fd98a3d9014cd63580a74f4ac gitolite-rugged gem 1400169242 +0200 commit: Commited by the gitolite-rugged gem 3 | 5cf57acca019629fd98a3d9014cd63580a74f4ac 0a0493ecd1ef1102cc11211b95d19ef7c4823a23 gitolite-rugged gem 1400171068 +0200 commit: Commited by the gitolite-rugged gem 4 | 0a0493ecd1ef1102cc11211b95d19ef7c4823a23 046aa40bf1f4fbef8fe915274bd8af30784f4974 gitolite-rugged gem 1400171167 +0200 commit: Commited by the gitolite-rugged gem 5 | 046aa40bf1f4fbef8fe915274bd8af30784f4974 c90707f5301413f121360c9b26784167652a3d2a gitolite-rugged gem 1400171330 +0200 commit: Commited by the gitolite-rugged gem 6 | c90707f5301413f121360c9b26784167652a3d2a e157cf40f5a30aa527d77c10840010c4654b4eab gitolite-rugged gem 1400171347 +0200 commit: Commited by the gitolite-rugged gem 7 | e157cf40f5a30aa527d77c10840010c4654b4eab 1381680579c7e0626544e28bbca00b0c3b5004e2 gitolite-rugged gem 1400171379 +0200 commit: Commited by the gitolite-rugged gem 8 | 1381680579c7e0626544e28bbca00b0c3b5004e2 acb157e4a3d567d42132d608db26143b91c4789c gitolite-rugged gem 1400171415 +0200 commit: Commited by the gitolite-rugged gem 9 | acb157e4a3d567d42132d608db26143b91c4789c 68b71afffb0b6781d974af6cb93ef076ded21bb5 gitolite-rugged gem 1400171481 +0200 commit: Commited by the gitolite-rugged gem 10 | 68b71afffb0b6781d974af6cb93ef076ded21bb5 e3c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 gitolite-rugged gem 1400171504 +0200 commit: Commited by the gitolite-rugged gem 11 | e3c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 42333c37a7910bff07c7e7a95a3d09b3f9966571 gitolite-rugged gem 1400171542 +0200 commit: Commited by the gitolite-rugged gem 12 | 42333c37a7910bff07c7e7a95a3d09b3f9966571 94f2765eb76820309b11de0018dd2a6bc53ae8f7 gitolite-rugged gem 1400171606 +0200 commit: Commited by the gitolite-rugged gem 13 | 94f2765eb76820309b11de0018dd2a6bc53ae8f7 0b8ae0021fafc32dbecbbcdf82bfe1ec3728432b gitolite-rugged gem 1400171627 +0200 commit: Commited by the gitolite-rugged gem 14 | 0b8ae0021fafc32dbecbbcdf82bfe1ec3728432b 3b8c81ac4b6d9276920ec06139d959b9aeb25456 gitolite-rugged gem 1400171634 +0200 commit: Commited by the gitolite-rugged gem 15 | 3b8c81ac4b6d9276920ec06139d959b9aeb25456 063a0c108a0a431fff7d9ea819e55653c6fda14c gitolite-rugged gem 1400171945 +0200 commit: Commited by the gitolite-rugged gem 16 | 063a0c108a0a431fff7d9ea819e55653c6fda14c d93bb9a696d405af34d9dcafc2e4611e1d5959dd gitolite-rugged gem 1400172024 +0200 commit: Commited by the gitolite-rugged gem 17 | d93bb9a696d405af34d9dcafc2e4611e1d5959dd 740ef5222fcd51ad7eba4a559396fdf856b20482 gitolite-rugged gem 1400172219 +0200 commit: Commited by the gitolite-rugged gem 18 | 740ef5222fcd51ad7eba4a559396fdf856b20482 395dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b gitolite-rugged gem 1400172287 +0200 commit: Commited by the gitolite-rugged gem 19 | 395dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b 411a9fa047b7c928939882769d2fac89a1c87dd0 gitolite-rugged gem 1400175175 +0200 commit: Commited by the gitolite-rugged gem 20 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/04/6aa40bf1f4fbef8fe915274bd8af30784f4974: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/04/6aa40bf1f4fbef8fe915274bd8af30784f4974 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/06/3a0c108a0a431fff7d9ea819e55653c6fda14c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/06/3a0c108a0a431fff7d9ea819e55653c6fda14c -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/0a/0493ecd1ef1102cc11211b95d19ef7c4823a23: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/0a/0493ecd1ef1102cc11211b95d19ef7c4823a23 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/0b/8ae0021fafc32dbecbbcdf82bfe1ec3728432b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/0b/8ae0021fafc32dbecbbcdf82bfe1ec3728432b -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/0b/9ea7db8498c31c36567cc7dc528e9a06587ef9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/0b/9ea7db8498c31c36567cc7dc528e9a06587ef9 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/13/81680579c7e0626544e28bbca00b0c3b5004e2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/13/81680579c7e0626544e28bbca00b0c3b5004e2 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/25/2a0e969ccca06c8258f811d2a6f01d883c2a0c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/25/2a0e969ccca06c8258f811d2a6f01d883c2a0c -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/39/5dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/39/5dfadc8dd72c4d0ab5d28a3cdd83500a41fb1b -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/3b/8c81ac4b6d9276920ec06139d959b9aeb25456: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/3b/8c81ac4b6d9276920ec06139d959b9aeb25456 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/41/1a9fa047b7c928939882769d2fac89a1c87dd0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/41/1a9fa047b7c928939882769d2fac89a1c87dd0 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/42/333c37a7910bff07c7e7a95a3d09b3f9966571: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/42/333c37a7910bff07c7e7a95a3d09b3f9966571 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/43/cb5e7b11750c16f2196bd8548e7b6277b372ca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/43/cb5e7b11750c16f2196bd8548e7b6277b372ca -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/5c/f57acca019629fd98a3d9014cd63580a74f4ac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/5c/f57acca019629fd98a3d9014cd63580a74f4ac -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/68/b71afffb0b6781d974af6cb93ef076ded21bb5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/68/b71afffb0b6781d974af6cb93ef076ded21bb5 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/74/0ef5222fcd51ad7eba4a559396fdf856b20482: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/74/0ef5222fcd51ad7eba4a559396fdf856b20482 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/86/692711b922198563496a69cf30ca772f6a6af3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/86/692711b922198563496a69cf30ca772f6a6af3 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/94/f2765eb76820309b11de0018dd2a6bc53ae8f7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/94/f2765eb76820309b11de0018dd2a6bc53ae8f7 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/9c/c84c9d97b8fa8d4cb6c52d6495eca4bff04130: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/9c/c84c9d97b8fa8d4cb6c52d6495eca4bff04130 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/9d/248211ad73f2fd188f75988dfd9e66431f2e8a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/9d/248211ad73f2fd188f75988dfd9e66431f2e8a -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/9d/ce718e57053022f2e184ba94e2b5131fcbe551: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/9d/ce718e57053022f2e184ba94e2b5131fcbe551 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/ac/b157e4a3d567d42132d608db26143b91c4789c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/ac/b157e4a3d567d42132d608db26143b91c4789c -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/ad/91cc1d968ad5fdcb8bd0df04db3523d0b98156: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/ad/91cc1d968ad5fdcb8bd0df04db3523d0b98156 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/b3/178d7dfddbc3df3e03eb271f0cdbe864aa902f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/b3/178d7dfddbc3df3e03eb271f0cdbe864aa902f -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/b6/de0801eb994701c5a55a0793796aebc2440372: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/b6/de0801eb994701c5a55a0793796aebc2440372 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/c9/0707f5301413f121360c9b26784167652a3d2a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/c9/0707f5301413f121360c9b26784167652a3d2a -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/c9/3d2aaacc82cb05a88994d2b44691d07477bde9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/c9/3d2aaacc82cb05a88994d2b44691d07477bde9 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/d9/3bb9a696d405af34d9dcafc2e4611e1d5959dd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/d9/3bb9a696d405af34d9dcafc2e4611e1d5959dd -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/df/cd7e43b90664b02a7765c54d45277f54bcd2ee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/df/cd7e43b90664b02a7765c54d45277f54bcd2ee -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/e1/57cf40f5a30aa527d77c10840010c4654b4eab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/e1/57cf40f5a30aa527d77c10840010c4654b4eab -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/e3/c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/e3/c37d85f4800ed4fe4a3f4ca49e3b4a85a88ae1 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/objects/f4/a351cf0debfb5ac18061f7cb4e7ff2e5916719: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redmine-git-hosting/gitolite-rugged/32d08b9098b9dd6cd47c770c9ab67ca08e45340a/spec/fixtures/gitolite-admin/.gitted/objects/f4/a351cf0debfb5ac18061f7cb4e7ff2e5916719 -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/.gitted/refs/heads/master: -------------------------------------------------------------------------------- 1 | 411a9fa047b7c928939882769d2fac89a1c87dd0 2 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/conf/gitolite.conf: -------------------------------------------------------------------------------- 1 | repo gitolite-admin 2 | RW+ = alice 3 | 4 | repo foobar 5 | RW+ = alice bob 6 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/keydir/admin.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvTKiPnDeDJHH88f2va6q3Q4RNpvmO5OM7S7jG5xJNNqY5HD58eWK8ieNylz37X6Hswgy4Xh+KGuEKo/Z3hpg7/P+eIo+vxUh/lyL3KzsMd2bfAtXdcnRV7hXeG98kp8z92zhyQ6UfafXbWHZpxeoV4oj3DQiqc9+4vs4D3kNJP87ScdjWTW4W/fTgyX14uwOH2qYpzhy+lZSfnzVUqxPXu9+qbyMQr4BUXm5wSdp2RV/1XXkUgYjkMazKoQ1sSmNcO96UMNlRJaE7Zo/8qH2KsgqsidrjepuT+caT1gxsvI6+VQBx7ahCrN2jNX1zJ5WrgElhDgS0t+4MCc9FL8Yt admin@localhost 2 | -------------------------------------------------------------------------------- /spec/fixtures/gitolite-admin/keydir/bob.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgoCXh9TPcO0cvYJ3LosFFBzCK0H1g5cArP95Z+Z3fIzxZLklzjmbyqk0zI0J3lC+knuxWss/WmX1ZkMNz4stxpwnyEil/KEfMqSHMVijcl4OhVi9S/Oel7JN+mi0jLVMItC6cicy9ZCCQ8bSZY0RgnQ6juqOpss0Uvuv/9bpIEFVfKrbKR93TkumnuwTZ1JvgsIFtnV353YXEmgPsEfvXISHccCVYXO2KG3L6UpJmCw+t7HtKeVXNxMno+W7a1mQ8irVZuEGVNfkn3ICYuqc00/DmBf8peAWmk8ui+qmM+EY6ONevaqhRhNfvnsnfAvXt16dzdxitXJjZ5SgfIKab bob@localhost 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/bob/bob.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6EFlh48tzCnepmggd09sUEM4m1zH3Fs/X6XWm1MAkEnMsD5hFGjkcNabDM8vq9zIRZ05YC6Gxo2plstAf+X4Y636+hyFvbDONB9mRP7DxJhFRaBScSFH60jeTz4ue2ExH3xA1JkaHMcV5vooUqG4BW8Vy/sz8wt/s0aIg9xqkrPOnfvqwunZ/zFUNyL8tC1HY3zGUkRzEVd2yRKaI+DGyRsh8HuYIb2X3NQ0YsU3uGGud7ObmxDbM7WGniyxRVK3lYCvgnTjvdPGi7Xx9QNQz53zLFbklGPZSfpFFHS84qR0Rd/+MnpT50FODhTmXHZtZF1eik09z63GW3YVt4PGoQ== bob@zilla.com 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/bob/bob@example.com.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6EFlh48tzCnepmggd09sUEM4m1zH3Fs/X6XWm1MAkEnMsD5hFGjkcNabDM8vq9zIRZ05YC6Gxo2plstAf+X4Y636+hyFvbDONB9mRP7DxJhFRaBScSFH60jeTz4ue2ExH3xA1JkaHMcV5vooUqG4BW8Vy/sz8wt/s0aIg9xqkrPOnfvqwunZ/zFUNyL8tC1HY3zGUkRzEVd2yRKaI+DGyRsh8HuYIb2X3NQ0YsU3uGGud7ObmxDbM7WGniyxRVK3lYCvgnTjvdPGi7Xx9QNQz53zLFbklGPZSfpFFHS84qR0Rd/+MnpT50FODhTmXHZtZF1eik09z63GW3YVt4PGoQ== bob@zilla.com 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/bob/desktop/bob.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6EFlh48tzCnepmggd09sUEM4m1zH3Fs/X6XWm1MAkEnMsD5hFGjkcNabDM8vq9zIRZ05YC6Gxo2plstAf+X4Y636+hyFvbDONB9mRP7DxJhFRaBScSFH60jeTz4ue2ExH3xA1JkaHMcV5vooUqG4BW8Vy/sz8wt/s0aIg9xqkrPOnfvqwunZ/zFUNyL8tC1HY3zGUkRzEVd2yRKaI+DGyRsh8HuYIb2X3NQ0YsU3uGGud7ObmxDbM7WGniyxRVK3lYCvgnTjvdPGi7Xx9QNQz53zLFbklGPZSfpFFHS84qR0Rd/+MnpT50FODhTmXHZtZF1eik09z63GW3YVt4PGoQ== bob@zilla.com 2 | -------------------------------------------------------------------------------- /spec/fixtures/keys/bob/school/bob.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6EFlh48tzCnepmggd09sUEM4m1zH3Fs/X6XWm1MAkEnMsD5hFGjkcNabDM8vq9zIRZ05YC6Gxo2plstAf+X4Y636+hyFvbDONB9mRP7DxJhFRaBScSFH60jeTz4ue2ExH3xA1JkaHMcV5vooUqG4BW8Vy/sz8wt/s0aIg9xqkrPOnfvqwunZ/zFUNyL8tC1HY3zGUkRzEVd2yRKaI+DGyRsh8HuYIb2X3NQ0YsU3uGGud7ObmxDbM7WGniyxRVK3lYCvgnTjvdPGi7Xx9QNQz53zLFbklGPZSfpFFHS84qR0Rd/+MnpT50FODhTmXHZtZF1eik09z63GW3YVt4PGoQ== bob@zilla.com 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'forgery' 3 | require 'rspec' 4 | require 'faker' 5 | require 'support/helper' 6 | 7 | ## Start Simplecov 8 | SimpleCov.start do 9 | add_filter 'spec/' 10 | end 11 | 12 | ## Configure RSpec 13 | RSpec.configure do |config| 14 | include Helper 15 | 16 | config.color = true 17 | config.fail_fast = false 18 | 19 | config.expect_with :rspec do |c| 20 | c.syntax = :expect 21 | end 22 | 23 | # disable monkey patching 24 | # see: https://relishapp.com/rspec/rspec-core/v/3-8/docs/configuration/zero-monkey-patching-mode 25 | config.disable_monkey_patching! 26 | end 27 | 28 | require 'gitolite' 29 | require 'core_ext/faker/git' 30 | require 'core_ext/faker/ssh' 31 | -------------------------------------------------------------------------------- /spec/support/helper.rb: -------------------------------------------------------------------------------- 1 | module Helper 2 | 3 | def load_fixture(*args) 4 | File.read(fixture_path(*args)) 5 | end 6 | 7 | 8 | def fixture_path(*args) 9 | path = File.join(File.dirname(__FILE__), '..', 'fixtures', *args) 10 | File.expand_path(path) 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit_tests/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Gitolite::Config do 4 | 5 | conf_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'configs') 6 | output_dir = '/tmp' 7 | 8 | describe "#new" do 9 | it 'should read a simple configuration' do 10 | c = Gitolite::Config.new(File.join(conf_dir, 'simple.conf')) 11 | expect(c.repos.length).to eq 2 12 | expect(c.groups.length).to eq 0 13 | end 14 | 15 | it 'should read a complex configuration' do 16 | c = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 17 | expect(c.groups.length).to eq 5 18 | expect(c.repos.length).to eq 13 19 | end 20 | 21 | describe 'gitweb operations' do 22 | before :all do 23 | @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 24 | end 25 | 26 | it 'should correctly read gitweb options for an existing repo' do 27 | r = @config.get_repo('gitolite') 28 | expect(r.owner).to eq "Sitaram Chamarty" 29 | expect(r.description).to eq "fast, secure, access control for git in a corporate environment" 30 | end 31 | 32 | it 'should correctly read a gitweb option with no owner for an existing repo' do 33 | r = @config.get_repo('foo') 34 | expect(r.owner).to be nil 35 | expect(r.description).to eq "Foo is a nice test repo" 36 | end 37 | 38 | it 'should correctly read gitweb options for a new repo' do 39 | r = @config.get_repo('foobar') 40 | expect(r.owner).to eq "Bob Zilla" 41 | expect(r.description).to eq "Foobar is top secret" 42 | end 43 | 44 | it 'should correctly read gitweb options with no owner for a new repo' do 45 | r = @config.get_repo('bar') 46 | expect(r.owner).to be nil 47 | expect(r.description).to eq "A nice place to get drinks" 48 | end 49 | 50 | it 'should raise a ParseError when a description is not specified' do 51 | t = Tempfile.new('bad_conf.conf') 52 | t.write('gitolite "Bob Zilla"') 53 | t.close 54 | 55 | expect { Gitolite::Config.new(t.path) }.to raise_error(Gitolite::Config::ParseError) 56 | 57 | t.unlink 58 | end 59 | 60 | it 'should raise a ParseError when a Gitweb description is specified for a group' do 61 | t = Tempfile.new('bad_conf.conf') 62 | t.write('@gitolite "Bob Zilla" = "Test description"') 63 | t.close 64 | 65 | expect { Gitolite::Config.new(t.path) }.to raise_error(Gitolite::Config::ParseError) 66 | 67 | t.unlink 68 | end 69 | end 70 | 71 | describe "git config settings" do 72 | before :all do 73 | @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 74 | end 75 | 76 | it 'should correctly read in git config settings' do 77 | r = @config.get_repo(:gitolite) 78 | expect(r.config.length).to eq 4 79 | end 80 | end 81 | 82 | describe "gitolite options" do 83 | before :all do 84 | @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 85 | end 86 | 87 | it 'should correctly read in gitolite options' do 88 | r = @config.get_repo(:foo) 89 | expect(r.options.length).to eq 3 90 | end 91 | 92 | it 'should raise a ParseError when a value is not specified' do 93 | t = Tempfile.new('bad_conf.conf') 94 | t.write("repo foobar\n option mirror.master =") 95 | t.close 96 | 97 | expect { Gitolite::Config.new(t.path) }.to raise_error(Gitolite::Config::ParseError) 98 | 99 | t.unlink 100 | end 101 | end 102 | end 103 | 104 | # describe "#init" do 105 | # it 'should create a valid, blank Gitolite::Config' do 106 | # c = Gitolite::Config.init 107 | 108 | # c.should be_an_instance_of Gitolite::Config 109 | # c.repos.should_not be nil 110 | # c.repos.length.should be 0 111 | # c.groups.should_not be nil 112 | # c.groups.length.should be 0 113 | # c.filename.should == "gitolite.conf" 114 | # end 115 | 116 | # it 'should create a valid, blank Gitolite::Config with the given filename' do 117 | # filename = "test.conf" 118 | # c = Gitolite::Config.init(filename) 119 | 120 | # c.should be_an_instance_of Gitolite::Config 121 | # c.repos.should_not be nil 122 | # c.repos.length.should be 0 123 | # c.groups.should_not be nil 124 | # c.groups.length.should be 0 125 | # c.filename.should == filename 126 | # end 127 | # end 128 | 129 | describe "repo management" do 130 | before :each do 131 | @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 132 | end 133 | 134 | describe "#get_repo" do 135 | it 'should fetch a repo by a string containing the name' do 136 | expect(@config.get_repo('gitolite')).to be_an_instance_of Gitolite::Config::Repo 137 | end 138 | 139 | it 'should fetch a repo via a symbol representing the name' do 140 | expect(@config.get_repo(:gitolite)).to be_an_instance_of Gitolite::Config::Repo 141 | end 142 | 143 | it 'should return nil for a repo that does not exist' do 144 | expect(@config.get_repo(:glite)).to be nil 145 | end 146 | end 147 | 148 | describe "#has_repo?" do 149 | it 'should return false for a repo that does not exist' do 150 | expect(@config.has_repo?(:glite)).to be false 151 | end 152 | 153 | it 'should check for the existance of a repo given a repo object' do 154 | r = @config.repos["gitolite"] 155 | expect(@config.has_repo?(r)).to be true 156 | end 157 | 158 | it 'should check for the existance of a repo given a string containing the name' do 159 | expect(@config.has_repo?('gitolite')).to be true 160 | end 161 | 162 | it 'should check for the existance of a repo given a symbol representing the name' do 163 | expect(@config.has_repo?(:gitolite)).to be true 164 | end 165 | end 166 | 167 | describe "#add_repo" do 168 | it 'should throw an ArgumentError for non-Gitolite::Config::Repo objects passed in' do 169 | expect { @config.add_repo("not-a-repo") }.to raise_error(ArgumentError) 170 | end 171 | 172 | it 'should add a given repo to the list of repos' do 173 | r = Gitolite::Config::Repo.new('cool_repo') 174 | nrepos = @config.repos.size 175 | @config.add_repo(r) 176 | 177 | expect(@config.repos.size).to eq nrepos + 1 178 | expect(@config.has_repo?(:cool_repo)).to be true 179 | end 180 | 181 | it 'should merge a given repo with an existing repo' do 182 | #Make two new repos 183 | repo1 = Gitolite::Config::Repo.new('cool_repo') 184 | repo2 = Gitolite::Config::Repo.new('cool_repo') 185 | 186 | #Add some perms to those repos 187 | repo1.add_permission("RW+", "", "bob", "joe", "sam") 188 | repo1.add_permission("R", "", "sue", "jen", "greg") 189 | repo1.add_permission("-", "refs/tags/test[0-9]", "@students", "jessica") 190 | repo1.add_permission("RW", "refs/tags/test[0-9]", "@teachers", "bill", "todd") 191 | repo1.add_permission("R", "refs/tags/test[0-9]", "@profs") 192 | 193 | repo2.add_permission("RW+", "", "jim", "cynthia", "arnold") 194 | repo2.add_permission("R", "", "daniel", "mary", "ben") 195 | repo2.add_permission("-", "refs/tags/test[0-9]", "@more_students", "stephanie") 196 | repo2.add_permission("RW", "refs/tags/test[0-9]", "@student_teachers", "mike", "judy") 197 | repo2.add_permission("R", "refs/tags/test[0-9]", "@leaders") 198 | 199 | #Add the repos 200 | @config.add_repo(repo1) 201 | @config.add_repo(repo2) 202 | 203 | #Make sure perms were properly merged 204 | end 205 | 206 | it 'should overwrite an existing repo when overwrite = true' do 207 | #Make two new repos 208 | repo1 = Gitolite::Config::Repo.new('cool_repo') 209 | repo2 = Gitolite::Config::Repo.new('cool_repo') 210 | 211 | #Add some perms to those repos 212 | repo1.add_permission("RW+", "", "bob", "joe", "sam") 213 | repo1.add_permission("R", "", "sue", "jen", "greg") 214 | repo2.add_permission("RW+", "", "jim", "cynthia", "arnold") 215 | repo2.add_permission("R", "", "daniel", "mary", "ben") 216 | 217 | #Add the repos 218 | @config.add_repo(repo1) 219 | @config.add_repo(repo2, true) 220 | 221 | #Make sure repo2 overwrote repo1 222 | end 223 | end 224 | 225 | describe "#rm_repo" do 226 | it 'should remove a repo for the Gitolite::Config::Repo object given' do 227 | r = @config.get_repo(:gitolite) 228 | r2 = @config.rm_repo(r) 229 | expect(r2.name).to eq r.name 230 | expect(r2.permissions.length).to eq r.permissions.length 231 | expect(r2.owner).to eq r.owner 232 | expect(r2.description).to eq r.description 233 | end 234 | 235 | it 'should remove a repo given a string containing the name' do 236 | r = @config.get_repo(:gitolite) 237 | r2 = @config.rm_repo('gitolite') 238 | expect(r2.name).to eq r.name 239 | expect(r2.permissions.length).to eq r.permissions.length 240 | expect(r2.owner).to eq r.owner 241 | expect(r2.description).to eq r.description 242 | end 243 | 244 | it 'should remove a repo given a symbol representing the name' do 245 | r = @config.get_repo(:gitolite) 246 | r2 = @config.rm_repo(:gitolite) 247 | expect(r2.name).to eq r.name 248 | expect(r2.permissions.length).to eq r.permissions.length 249 | expect(r2.owner).to eq r.owner 250 | expect(r2.description).to eq r.description 251 | end 252 | end 253 | end 254 | 255 | describe "group management" do 256 | before :each do 257 | @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 258 | end 259 | 260 | describe "#has_group?" do 261 | it 'should find the staff group using a symbol' do 262 | expect(@config.has_group?(:staff)).to be true 263 | end 264 | 265 | it 'should find the staff group using a string' do 266 | expect(@config.has_group?('staff')).to be true 267 | end 268 | 269 | it 'should find the staff group using a Gitolite::Config::Group object' do 270 | g = Gitolite::Config::Group.new("staff") 271 | expect(@config.has_group?(g)).to be true 272 | end 273 | end 274 | 275 | describe "#get_group" do 276 | it 'should return the Gitolite::Config::Group object for the group name String' do 277 | g = @config.get_group("staff") 278 | expect(g.is_a?(Gitolite::Config::Group)).to be true 279 | expect(g.size).to eq 6 280 | end 281 | 282 | it 'should return the Gitolite::Config::Group object for the group name Symbol' do 283 | g = @config.get_group(:staff) 284 | expect(g.is_a?(Gitolite::Config::Group)).to be true 285 | expect(g.size).to eq 6 286 | end 287 | end 288 | 289 | describe "#add_group" do 290 | it 'should throw an ArgumentError for non-Gitolite::Config::Group objects passed in' do 291 | expect { @config.add_group("not-a-group") }.to raise_error(ArgumentError) 292 | end 293 | 294 | it 'should add a given group to the groups list' do 295 | g = Gitolite::Config::Group.new('cool_group') 296 | ngroups = @config.groups.size 297 | @config.add_group(g) 298 | expect(@config.groups.size).to eq ngroups + 1 299 | expect(@config.has_group?(:cool_group)).to be true 300 | end 301 | 302 | end 303 | 304 | describe "#rm_group" do 305 | it 'should remove a group for the Gitolite::Config::Group object given' do 306 | g = @config.get_group(:oss_repos) 307 | g2 = @config.rm_group(g) 308 | expect(g).to_not be nil 309 | expect(g2.name).to eq g.name 310 | end 311 | 312 | it 'should remove a group given a string containing the name' do 313 | g = @config.get_group(:oss_repos) 314 | g2 = @config.rm_group('oss_repos') 315 | expect(g2.name).to eq g.name 316 | end 317 | 318 | it 'should remove a group given a symbol representing the name' do 319 | g = @config.get_group(:oss_repos) 320 | g2 = @config.rm_group(:oss_repos) 321 | expect(g2.name).to eq g.name 322 | end 323 | end 324 | 325 | end 326 | 327 | describe "#to_file" do 328 | it 'should create a file at the given path with the config\'s file name' do 329 | c = Gitolite::Config.init 330 | file = c.to_file(output_dir) 331 | expect(File.file?(File.join(output_dir, c.filename))).to be true 332 | File.unlink(file) 333 | end 334 | 335 | it 'should create a file at the given path with the config file passed' do 336 | c = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 337 | file = c.to_file(output_dir) 338 | expect(File.file?(File.join(output_dir, c.filename))).to be true 339 | end 340 | 341 | it 'should create a file at the given path when a different filename is specified' do 342 | filename = "test.conf" 343 | c = Gitolite::Config.init 344 | c.filename = filename 345 | file = c.to_file(output_dir) 346 | expect(File.file?(File.join(output_dir, filename))).to be true 347 | File.unlink(file) 348 | end 349 | 350 | it 'should create the given directory if it does not exist' do 351 | c = Gitolite::Config.init 352 | Dir.mktmpdir("foo") do |dir| 353 | target = File.join(dir, "someconfigfile") 354 | expect(File.exist?(target)).to be false 355 | c.to_file(target) 356 | expect(File.exist?(target)).to be true 357 | end 358 | end 359 | 360 | it 'should resolve group dependencies such that all groups are defined before they are used' do 361 | c = Gitolite::Config.init 362 | c.filename = "test_deptree.conf" 363 | 364 | # Build some groups out of order 365 | g = Gitolite::Config::Group.new "groupa" 366 | g.add_users "bob", "@groupb" 367 | c.add_group(g) 368 | 369 | g = Gitolite::Config::Group.new "groupb" 370 | g.add_users "joe", "sam", "susan", "andrew" 371 | c.add_group(g) 372 | 373 | g = Gitolite::Config::Group.new "groupc" 374 | g.add_users "jane", "@groupb", "brandon" 375 | c.add_group(g) 376 | 377 | g = Gitolite::Config::Group.new "groupd" 378 | g.add_users "larry", "@groupc" 379 | c.add_group(g) 380 | 381 | # Write the config to a file 382 | file = c.to_file(output_dir) 383 | 384 | # Read the conf and make sure our order is correct 385 | f = File.read(file) 386 | lines = f.lines.map {|l| l.strip} 387 | 388 | # Compare the file lines. Spacing is important here since we are doing a direct comparision 389 | expect(lines[0]).to eq "@groupb = andrew joe sam susan" 390 | expect(lines[1]).to eq "@groupc = @groupb brandon jane" 391 | expect(lines[2]).to eq "@groupd = @groupc larry" 392 | expect(lines[3]).to eq "@groupa = @groupb bob" 393 | 394 | # Cleanup 395 | File.unlink(file) 396 | end 397 | 398 | it 'should raise a GroupDependencyError if there is a cyclic dependency' do 399 | c = Gitolite::Config.init 400 | c.filename = "test_deptree.conf" 401 | 402 | # Build some groups out of order 403 | g = Gitolite::Config::Group.new "groupa" 404 | g.add_users "bob", "@groupb" 405 | c.add_group(g) 406 | 407 | g = Gitolite::Config::Group.new "groupb" 408 | g.add_users "joe", "sam", "susan", "@groupc" 409 | c.add_group(g) 410 | 411 | g = Gitolite::Config::Group.new "groupc" 412 | g.add_users "jane", "@groupa", "brandon" 413 | c.add_group(g) 414 | 415 | g = Gitolite::Config::Group.new "groupd" 416 | g.add_users "larry", "@groupc" 417 | c.add_group(g) 418 | 419 | # Attempt to write the config file 420 | expect { c.to_file(output_dir)}.to raise_error(Gitolite::Config::GroupDependencyError) 421 | end 422 | 423 | it 'should resolve group dependencies even when there are disconnected portions of the graph' do 424 | c = Gitolite::Config.init 425 | c.filename = "test_deptree.conf" 426 | 427 | # Build some groups out of order 428 | g = Gitolite::Config::Group.new "groupa" 429 | g.add_users "bob", "timmy", "stephanie" 430 | c.add_group(g) 431 | 432 | g = Gitolite::Config::Group.new "groupb" 433 | g.add_users "joe", "sam", "susan", "andrew" 434 | c.add_group(g) 435 | 436 | g = Gitolite::Config::Group.new "groupc" 437 | g.add_users "jane", "earl", "brandon", "@groupa" 438 | c.add_group(g) 439 | 440 | g = Gitolite::Config::Group.new "groupd" 441 | g.add_users "larry", "chris", "emily" 442 | c.add_group(g) 443 | 444 | # Write the config to a file 445 | file = c.to_file(output_dir) 446 | 447 | # Read the conf and make sure our order is correct 448 | f = File.read(file) 449 | lines = f.lines.map {|l| l.strip} 450 | 451 | # Compare the file lines. Spacing is important here since we are doing a direct comparision 452 | expect(lines[0]).to eq "@groupd = chris emily larry" 453 | expect(lines[1]).to eq "@groupb = andrew joe sam susan" 454 | expect(lines[2]).to eq "@groupa = bob stephanie timmy" 455 | expect(lines[3]).to eq "@groupc = @groupa brandon earl jane" 456 | 457 | # Cleanup 458 | File.unlink(file) 459 | end 460 | end 461 | 462 | describe "#gitweb_descriptions" do 463 | it 'should return a list of gitweb descriptions' do 464 | c = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf')) 465 | expect(c.gitweb_descriptions).to eq [ 466 | "bar = \"A nice place to get drinks\"", 467 | "foo = \"Foo is a nice test repo\"", 468 | "foobar \"Bob Zilla\" = \"Foobar is top secret\"", 469 | "gitolite \"Sitaram Chamarty\" = \"fast, secure, access control for git in a corporate environment\"" 470 | ] 471 | end 472 | end 473 | 474 | 475 | describe "#cleanup_config_line" do 476 | before(:each) do 477 | @config = Gitolite::Config.init 478 | end 479 | 480 | it 'should remove comments' do 481 | s = "#comment" 482 | expect(@config.instance_eval { cleanup_config_line(s) }.empty?).to be true 483 | end 484 | 485 | it 'should remove inline comments, keeping content before the comment' do 486 | s = "blablabla #comment" 487 | expect(@config.instance_eval { cleanup_config_line(s) }).to eq "blablabla" 488 | end 489 | 490 | it 'should pad = with spaces on each side' do 491 | s = "bob=joe" 492 | expect(@config.instance_eval { cleanup_config_line(s) }).to eq "bob = joe" 493 | end 494 | 495 | it 'should replace multiple space characters with a single space' do 496 | s = "bob = joe" 497 | expect(@config.instance_eval { cleanup_config_line(s) }).to eq "bob = joe" 498 | end 499 | 500 | it 'should cleanup whitespace at the beginning and end of lines' do 501 | s = " bob = joe " 502 | expect(@config.instance_eval { cleanup_config_line(s) }).to eq "bob = joe" 503 | end 504 | 505 | it 'should cleanup whitespace and comments effectively' do 506 | s = " bob = joe #comment" 507 | expect(@config.instance_eval { cleanup_config_line(s) }).to eq "bob = joe" 508 | end 509 | end 510 | end 511 | -------------------------------------------------------------------------------- /spec/unit_tests/dirty_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Gitolite::DirtyProxy do 4 | 5 | it "should create a new instance given valid attributes" do 6 | expect(Gitolite::DirtyProxy.new([])).to_not be nil 7 | end 8 | 9 | 10 | let(:target) { ['foo', 'bar'] } 11 | let(:proxy) { Gitolite::DirtyProxy.new(target) } 12 | 13 | 14 | describe 'delegating to the target object' do 15 | it 'should act as instance of the target' do 16 | expect(proxy).to be_instance_of target.class 17 | end 18 | 19 | it 'should respond to all methods of the target' do 20 | expect(proxy).to respond_to(*target.methods) 21 | end 22 | 23 | it 'should equal the target' do 24 | expect(proxy).to eql target 25 | end 26 | end 27 | 28 | 29 | describe 'dirty checking methods' do 30 | it 'should respond to clean_up!' do 31 | expect(proxy.respond_to?(:clean_up!)).to be true 32 | end 33 | 34 | it 'should respond to dirty?' do 35 | expect(proxy.respond_to?(:dirty?)).to be true 36 | end 37 | 38 | context 'when just initialized' do 39 | it 'should be clean' do 40 | expect(proxy.dirty?).to be false 41 | end 42 | end 43 | 44 | shared_examples 'dirty? clean_up!' do 45 | it 'should be dirty' do 46 | expect(proxy.dirty?).to be true 47 | end 48 | 49 | it 'should be clean again after clean_up!' do 50 | proxy.clean_up! 51 | expect(proxy.dirty?).to be false 52 | end 53 | end 54 | 55 | context 'when target object has changed directly' do 56 | before(:each) { proxy << 'baz' } 57 | include_examples 'dirty? clean_up!' 58 | end 59 | 60 | context 'when target object has changed in depth' do 61 | before(:each) { proxy[0] << 'ooo' } 62 | include_examples 'dirty? clean_up!' 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /spec/unit_tests/gitolite_admin_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ostruct' 3 | 4 | RSpec.describe Gitolite::GitoliteAdmin do 5 | 6 | def build_gitolite_admin_klass(path = '/tmp/toto') 7 | Gitolite::GitoliteAdmin.new(path, { private_key: nil, public_key: nil, update_on_init: false }) 8 | end 9 | 10 | 11 | def build_ssh_key_klass 12 | Gitolite::SSHKey.from_string(Faker::Ssh.public_key, 'root') 13 | end 14 | 15 | 16 | def in_fake_gitolite_repo(source_dir, &block) 17 | Dir.mktmpdir('gitolite-rugged-admin-repo') do |dir| 18 | tmp_repo = File.join(dir, 'gitolite-admin') 19 | FileUtils.cp_r(source_dir, tmp_repo) 20 | FileUtils.mv(File.join(tmp_repo, '.gitted'), File.join(tmp_repo, '.git')) 21 | yield tmp_repo 22 | end 23 | end 24 | 25 | 26 | describe '.is_gitolite_admin_repo?' do 27 | it 'should detect a non gitolite-admin repository' do 28 | expect(Gitolite::GitoliteAdmin.is_gitolite_admin_repo?('/tmp')).to be false 29 | end 30 | end 31 | 32 | 33 | describe 'mocked/stubbed' do 34 | let(:klass) { build_gitolite_admin_klass } 35 | 36 | before(:each) { expect_any_instance_of(Gitolite::GitoliteAdmin).to receive(:set_repo).and_return(double('Rugged::Repository')) } 37 | 38 | describe '#path' do 39 | it 'should return the path of the Gitolite Admin repository' do 40 | expect(klass.path).to eq '/tmp/toto' 41 | end 42 | end 43 | 44 | 45 | describe '#config' do 46 | it 'should return the current config' do 47 | expect(klass.config).to be_an_instance_of(Gitolite::Config) 48 | end 49 | end 50 | 51 | 52 | describe '#config' do 53 | it 'should set a new configuration' do 54 | config = Gitolite::Config.new(fixture_path('configs', 'complicated.conf')) 55 | expect(klass.config = config).to be_an_instance_of(Gitolite::Config) 56 | end 57 | end 58 | 59 | 60 | describe '#ssh_keys' do 61 | it 'should return a hash of keys' do 62 | expect(klass.ssh_keys).to be_a Hash 63 | end 64 | end 65 | 66 | 67 | describe '#reload!' do 68 | it 'should reload the configuration' do 69 | expect(klass).to receive(:load_keys) 70 | expect(klass).to receive(:load_config) 71 | klass.reload! 72 | end 73 | end 74 | 75 | 76 | describe '#admin_url' do 77 | it 'should return Gitolite admin url' do 78 | expect(klass.admin_url).to eq 'ssh://git@localhost/gitolite-admin.git' 79 | end 80 | end 81 | 82 | 83 | describe '#commit_author' do 84 | it 'should return a hash about git author' do 85 | expect(klass.commit_author).to eq({ email: 'gitolite-rugged@localhost', name: 'gitolite-rugged gem' }) 86 | end 87 | end 88 | 89 | 90 | describe '#exists?' do 91 | it 'should say if the Gitolite Admin directory exists' do 92 | expect(Dir).to receive(:exist?).with(klass.path) 93 | klass.exists? 94 | end 95 | end 96 | 97 | 98 | describe '#config_dir_path' do 99 | it 'should give Gitolite config_dir_path' do 100 | expect(klass.config_dir_path).to eq File.join('/tmp/toto', 'conf') 101 | end 102 | end 103 | 104 | 105 | describe '#config_file_path' do 106 | it 'should give Gitolite config_file_path' do 107 | expect(klass.config_file_path).to eq File.join('/tmp/toto', 'conf', 'gitolite.conf') 108 | end 109 | end 110 | 111 | 112 | describe '#key_dir_path' do 113 | it 'should give Gitolite key_dir_path' do 114 | expect(klass.key_dir_path).to eq File.join('/tmp/toto', 'keydir/') 115 | end 116 | end 117 | 118 | 119 | describe '#relative_config_file' do 120 | it 'should give Gitolite relative_config_file' do 121 | expect(klass.relative_config_file).to eq 'conf/gitolite.conf' 122 | end 123 | end 124 | 125 | 126 | describe '#relative_key_dir' do 127 | it 'should give Gitolite relative_key_dir' do 128 | expect(klass.relative_key_dir).to eq 'keydir/' 129 | end 130 | end 131 | 132 | 133 | describe '#lock_file_path' do 134 | it 'should give Gitolite lock_file_path' do 135 | expect(klass.lock_file_path).to eq File.join('/tmp/toto', '.lock') 136 | end 137 | end 138 | 139 | 140 | describe '#get_references' do 141 | context 'when reference exists' do 142 | it 'should return a commit id' do 143 | expect(klass.repo).to receive(:references).and_return({ 'foo' => OpenStruct.new(target: 'commit_id') }) 144 | expect(klass.get_references('foo')).to eq 'commit_id' 145 | end 146 | end 147 | 148 | context 'when reference dont exists' do 149 | it 'should return nil' do 150 | expect(klass.repo).to receive(:references).and_return({}) 151 | klass.get_references('foo') 152 | end 153 | end 154 | end 155 | 156 | 157 | describe '#add_key' do 158 | it 'should raise an error if param passed is not a Gitolite::SSHKey' do 159 | expect { 160 | klass.add_key('toto') 161 | }.to raise_error(ArgumentError) 162 | end 163 | 164 | it 'should add a ssh key to the global hash' do 165 | key = build_ssh_key_klass 166 | expect(klass.add_key(key)).to be true 167 | end 168 | end 169 | 170 | 171 | describe '#rm_key' do 172 | it 'should raise an error if param passed is not a Gitolite::SSHKey' do 173 | expect { 174 | klass.rm_key('toto') 175 | }.to raise_error(ArgumentError) 176 | end 177 | 178 | context 'when the key exists' do 179 | it 'should remove a ssh key from the global hash' do 180 | key = build_ssh_key_klass 181 | expect(klass.add_key(key)).to be true 182 | expect(klass.rm_key(key)).to be true 183 | end 184 | end 185 | 186 | context 'when the key dont exists' do 187 | it 'should remove a ssh key from the global hash' do 188 | key = build_ssh_key_klass 189 | expect(klass.rm_key(key)).to be false 190 | end 191 | end 192 | end 193 | 194 | 195 | describe '#clone' do 196 | it 'should clone the Gitolite admin repository' do 197 | expect(klass).to receive(:clean_up) 198 | expect(Rugged::Repository).to receive(:clone_at).with(klass.admin_url, klass.path, credentials: klass.credentials) 199 | klass.clone 200 | end 201 | end 202 | 203 | 204 | describe '#clean_up' do 205 | it 'should clean_up the Gitolite admin repository' do 206 | expect(klass).to receive(:exists?).and_return(true) 207 | expect(FileUtils).to receive(:rm_rf).with(klass.path) 208 | klass.clean_up 209 | end 210 | end 211 | 212 | 213 | describe '#reset!' do 214 | it 'should reset the configuration' do 215 | expect(klass.repo).to receive(:reset).with('origin/master', :hard) 216 | klass.reset! 217 | end 218 | end 219 | 220 | 221 | describe '#apply' do 222 | it 'should apply the configuration' do 223 | expect(klass.repo).to receive(:push).with('origin', ['refs/heads/master'], credentials: klass.credentials) 224 | klass.apply 225 | end 226 | end 227 | 228 | 229 | 230 | describe '#update' do 231 | it 'should update the repository with remote content' do 232 | index = double('Rugged::Index') 233 | expect(klass).to receive(:reset!) 234 | expect(klass).to receive(:local_branch).at_least(:once).and_return('refs/heads/master') 235 | expect(klass).to receive(:remote_branch).at_least(:once).and_return('refs/heads/master') 236 | expect(klass.repo).to receive(:fetch).with('origin', credentials: klass.credentials) 237 | expect(klass.repo).to receive(:merge_commits).with(klass.local_branch, klass.remote_branch).and_return(index) 238 | expect(index).to receive(:write_tree).with(klass.repo).and_return('foo') 239 | expect(Rugged::Commit).to receive(:create) 240 | expect(klass).to receive(:reload!) 241 | klass.update 242 | end 243 | end 244 | 245 | 246 | describe '#save_and_apply' do 247 | it 'should save_and_apply the configuration' do 248 | expect(klass).to receive(:save).with('message') 249 | expect(klass).to receive(:apply) 250 | klass.save_and_apply('message') 251 | end 252 | end 253 | 254 | 255 | describe '#transaction' do 256 | it 'should put a lock around the action' do 257 | FileUtils.touch '/tmp/toto' 258 | expect(klass).to receive(:lock_file_path).and_return('/tmp/toto') 259 | expect(klass).to receive(:apply) 260 | expect { |b| 261 | klass.transaction(&b) 262 | }.to yield_with_no_args 263 | FileUtils.rm_f '/tmp/toto' 264 | end 265 | end 266 | 267 | 268 | describe '#local_branch' do 269 | it 'should return the local_branch' do 270 | expect(klass).to receive(:get_references).with('refs/heads/master') 271 | klass.local_branch 272 | end 273 | end 274 | 275 | 276 | describe '#remote_branch' do 277 | it 'should return the remote_branch' do 278 | expect(klass).to receive(:get_references).with('refs/remotes/origin/master') 279 | klass.remote_branch 280 | end 281 | end 282 | 283 | 284 | describe '#update_ref' do 285 | it 'should return the update_ref' do 286 | expect(klass.update_ref).to eq 'refs/heads/master' 287 | end 288 | end 289 | 290 | 291 | describe '#update_message' do 292 | it 'should return the update_message' do 293 | expect(klass.update_message).to eq "[gitolite-rugged] Merged `origin/master` into `master`" 294 | end 295 | end 296 | 297 | end 298 | 299 | 300 | describe 'for real' do 301 | describe '#save' do 302 | it 'should commit file to gitolite-admin repository' do 303 | in_fake_gitolite_repo(fixture_path('gitolite-admin')) do |tmp_repo| 304 | gl_admin = build_gitolite_admin_klass(tmp_repo) 305 | 306 | config = Gitolite::Config.new(fixture_path('configs', 'complicated.conf')) 307 | config.filename = 'gitolite.conf' 308 | 309 | gl_admin.config = config 310 | gl_admin.save 311 | 312 | new_file = File.join(tmp_repo, 'conf', config.filename) 313 | 314 | expect(File.file?(new_file)).to be true 315 | expect(IO.read(new_file)).to eq IO.read(fixture_path('configs', 'complicated-output.conf')) 316 | end 317 | end 318 | end 319 | end 320 | 321 | end 322 | -------------------------------------------------------------------------------- /spec/unit_tests/group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Gitolite::Config::Group do 4 | describe "#new" do 5 | it "should create a new group with an empty list of users" do 6 | group = Gitolite::Config::Group.new("testgroup") 7 | expect(group.users.empty?).to be true 8 | expect(group.name).to eq "testgroup" 9 | end 10 | 11 | it "should create a new group with a name containing #{Gitolite::Config::Group::PREPEND_CHAR}" do 12 | name = "#{Gitolite::Config::Group::PREPEND_CHAR}testgroup" 13 | group = Gitolite::Config::Group.new(name) 14 | expect(group.name).to eq "testgroup" 15 | end 16 | end 17 | 18 | describe "users" do 19 | before :each do 20 | @group = Gitolite::Config::Group.new('testgroup') 21 | end 22 | 23 | describe "#add_user" do 24 | it "should allow adding one user with a string" do 25 | @group.add_user("bob") 26 | expect(@group.size).to eq 1 27 | expect(@group.users.first).to eq "bob" 28 | end 29 | 30 | it "should allow adding one user with a symbol" do 31 | @group.add_user(:bob) 32 | expect(@group.size).to eq 1 33 | expect(@group.users.first).to eq "bob" 34 | end 35 | 36 | it "should not add the same user twice" do 37 | @group.add_user("bob") 38 | expect(@group.size).to eq 1 39 | @group.add_user(:bob) 40 | expect(@group.size).to eq 1 41 | expect(@group.users.first).to eq "bob" 42 | end 43 | 44 | it "should maintain users in sorted order" do 45 | @group.add_user("susan") 46 | @group.add_user("peyton") 47 | @group.add_user("bob") 48 | expect(@group.users.first).to eq "bob" 49 | expect(@group.users.last).to eq "susan" 50 | end 51 | end 52 | 53 | describe "#add_users" do 54 | it "should allow adding multiple users at once" do 55 | @group.add_users("bob", "joe", "sue", "sam", "dan") 56 | expect(@group.size).to eq 5 57 | end 58 | 59 | it "should allow adding multiple users in nested arrays" do 60 | @group.add_users(["bob", "joe", ["sam", "sue", "dan"]], "bill") 61 | expect(@group.size).to eq 6 62 | end 63 | 64 | it "should allow adding users of symbols and strings" do 65 | @group.add_users("bob", :joe, :sue, "sam") 66 | expect(@group.size).to eq 4 67 | end 68 | 69 | it "should not add the same user twice" do 70 | @group.add_users("bob", :bob, "bob", "sam") 71 | expect(@group.size).to eq 2 72 | end 73 | end 74 | 75 | describe "#rm_user" do 76 | before :each do 77 | @group.add_users("bob", "joe", "susan", "sam", "alex") 78 | end 79 | 80 | it "should support removing a user via a String" do 81 | @group.rm_user("bob") 82 | expect(@group.size).to eq 4 83 | end 84 | 85 | it "should support removing a user via a Symbol" do 86 | @group.rm_user(:bob) 87 | expect(@group.size).to eq 4 88 | end 89 | end 90 | 91 | describe "#empty!" do 92 | it "should clear all users from the group" do 93 | @group.add_users("bob", "joe", "sue", "jim") 94 | expect(@group.size).to eq 4 95 | @group.empty! 96 | expect(@group.size).to eq 0 97 | end 98 | end 99 | 100 | describe "#size" do 101 | it "should reflect how many users are in the group" do 102 | @group.add_users("bob", "joe", "sue", "jim") 103 | expect(@group.users.length).to eq @group.size 104 | end 105 | end 106 | 107 | describe "#has_user?" do 108 | it "should search for a user via a String" do 109 | @group.add_user("bob") 110 | expect(@group.has_user?("bob")).to be true 111 | end 112 | 113 | it "should search for a user via a Symbol" do 114 | @group.add_user(:bob) 115 | expect(@group.has_user?(:bob)).to be true 116 | end 117 | end 118 | end 119 | 120 | describe "#to_s" do 121 | group = Gitolite::Config::Group.new("testgroup") 122 | group.add_users("bob", "joe", "sam", "sue") 123 | it "should render to string" do 124 | expect(group.to_s).to eq "@testgroup = bob joe sam sue\n" #10 spaces after @testgroup 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /spec/unit_tests/repo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Gitolite::Config::Repo do 4 | before(:each) do 5 | @repo = Gitolite::Config::Repo.new("CoolRepo") 6 | end 7 | 8 | describe '#new' do 9 | it 'should create a repo called "CoolRepo"' do 10 | expect(@repo.name).to eq "CoolRepo" 11 | end 12 | end 13 | 14 | describe "deny rules" do 15 | it 'should maintain the order of rules for a repository' do 16 | @repo.add_permission("RW+", "", "bob", "joe", "sam") 17 | @repo.add_permission("R", "", "sue", "jen", "greg") 18 | @repo.add_permission("-", "refs/tags/test[0-9]", "@students", "jessica") 19 | @repo.add_permission("RW", "refs/tags/test[0-9]", "@teachers", "bill", "todd") 20 | @repo.add_permission("R", "refs/tags/test[0-9]", "@profs") 21 | 22 | expect(@repo.permissions.length).to eq 2 23 | expect(@repo.permissions[0].size).to eq 2 24 | expect(@repo.permissions[1].size).to eq 3 25 | end 26 | end 27 | 28 | describe '#add_permission' do 29 | it 'should allow adding a permission to the permissions list' do 30 | @repo.add_permission("RW+") 31 | expect(@repo.permissions.length).to eq 1 32 | expect(@repo.permissions.first.keys.first).to eq "RW+" 33 | end 34 | 35 | it 'should allow adding a permission while specifying a refex' do 36 | @repo.add_permission("RW+", "refs/heads/master") 37 | expect(@repo.permissions.length).to eq 1 38 | expect(@repo.permissions.first.keys.first).to eq "RW+" 39 | expect(@repo.permissions.first.values.last.first.first).to eq "refs/heads/master" 40 | end 41 | 42 | it 'should allow specifying users individually' do 43 | @repo.add_permission("RW+", "", "bob", "joe", "susan", "sam", "bill") 44 | expect(@repo.permissions.first["RW+"][""]).to eq %w[bob joe susan sam bill] 45 | end 46 | 47 | it 'should allow specifying users as an array' do 48 | users = %w[bob joe susan sam bill] 49 | 50 | @repo.add_permission("RW+", "", users) 51 | expect(@repo.permissions.first["RW+"][""]).to eq users 52 | end 53 | 54 | it 'should not allow adding an invalid permission via an InvalidPermissionError' do 55 | expect { @repo.add_permission("BadPerm") }.to raise_error(Gitolite::Config::Repo::InvalidPermissionError) 56 | end 57 | end 58 | 59 | describe "permissions" do 60 | before(:each) do 61 | @repo = Gitolite::Config::Repo.new("CoolRepo") 62 | end 63 | 64 | it 'should allow adding the permission C' do 65 | @repo.add_permission("C", "", "bob") 66 | end 67 | 68 | it 'should allow adding the permission -' do 69 | @repo.add_permission("-", "", "bob") 70 | end 71 | 72 | it 'should allow adding the permission R' do 73 | @repo.add_permission("R", "", "bob") 74 | end 75 | 76 | it 'should allow adding the permission RM' do 77 | @repo.add_permission("RM", "", "bob") 78 | end 79 | 80 | it 'should allow adding the permission RW' do 81 | @repo.add_permission("RW", "", "bob") 82 | end 83 | 84 | it 'should allow adding the permission RWM' do 85 | @repo.add_permission("RWM", "", "bob") 86 | end 87 | 88 | it 'should allow adding the permission RW+' do 89 | @repo.add_permission("RW+", "", "bob") 90 | end 91 | 92 | it 'should allow adding the permission RW+M' do 93 | @repo.add_permission("RW+M", "", "bob") 94 | end 95 | 96 | it 'should allow adding the permission RWC' do 97 | @repo.add_permission("RWC", "", "bob") 98 | end 99 | 100 | it 'should allow adding the permission RWCM' do 101 | @repo.add_permission("RWCM", "", "bob") 102 | end 103 | 104 | it 'should allow adding the permission RW+C' do 105 | @repo.add_permission("RW+C", "", "bob") 106 | end 107 | 108 | it 'should allow adding the permission RW+CM' do 109 | @repo.add_permission("RW+CM", "", "bob") 110 | end 111 | 112 | it 'should allow adding the permission RWD' do 113 | @repo.add_permission("RWD", "", "bob") 114 | end 115 | 116 | it 'should allow adding the permission RWDM' do 117 | @repo.add_permission("RWDM", "", "bob") 118 | end 119 | 120 | it 'should allow adding the permission RW+D' do 121 | @repo.add_permission("RW+D", "", "bob") 122 | end 123 | 124 | it 'should allow adding the permission RW+DM' do 125 | @repo.add_permission("RW+DM", "", "bob") 126 | end 127 | 128 | it 'should allow adding the permission RWCD' do 129 | @repo.add_permission("RWCD", "", "bob") 130 | end 131 | 132 | it 'should allow adding the permission RWCDM' do 133 | @repo.add_permission("RWCDM", "", "bob") 134 | end 135 | 136 | it 'should allow adding the permission RW+CD' do 137 | @repo.add_permission("RW+CD", "", "bob") 138 | end 139 | 140 | it 'should allow adding the permission RW+CDM' do 141 | @repo.add_permission("RW+CDM", "", "bob") 142 | end 143 | end 144 | 145 | describe 'git config options' do 146 | it 'should allow setting a git configuration option' do 147 | email = "bob@zilla.com" 148 | expect(@repo.set_git_config("email", email)).to eq email 149 | end 150 | 151 | it 'should allow deletion of an existing git configuration option' do 152 | email = "bob@zilla.com" 153 | @repo.set_git_config("email", email) 154 | expect(@repo.unset_git_config("email")).to eq email 155 | end 156 | 157 | end 158 | 159 | describe 'gitolite options' do 160 | it 'should allow setting a gitolite option' do 161 | master = "kenobi" 162 | slaves = "one" 163 | expect(@repo.set_gitolite_option("mirror.master", master)).to eq master 164 | expect(@repo.set_gitolite_option("mirror.slaves", slaves)).to eq slaves 165 | expect(@repo.options.length).to eq 2 166 | end 167 | 168 | it 'should allow deletion of an existing gitolite option' do 169 | master = "kenobi" 170 | slaves = "one" 171 | @repo.set_gitolite_option("mirror.master", master) 172 | @repo.set_gitolite_option("mirror.slaves", slaves) 173 | expect(@repo.options.length).to eq 2 174 | expect(@repo.unset_gitolite_option("mirror.master")).to eq master 175 | expect(@repo.options.length).to eq 1 176 | end 177 | end 178 | 179 | describe 'permission management' do 180 | it 'should combine two entries for the same permission and refex' do 181 | users = %w[bob joe susan sam bill] 182 | more_users = %w[sally peyton andrew erin] 183 | 184 | @repo.add_permission("RW+", "", users) 185 | @repo.add_permission("RW+", "", more_users) 186 | expect(@repo.permissions.first["RW+"][""]).to eq users.concat(more_users) 187 | expect(@repo.permissions.first["RW+"][""].length).to eq 9 188 | end 189 | 190 | it 'should not list the same users twice for the same permission level' do 191 | users = %w[bob joe susan sam bill] 192 | more_users = %w[bob peyton andrew erin] 193 | even_more_users = %w[bob jim wayne courtney] 194 | 195 | @repo.add_permission("RW+", "", users) 196 | @repo.add_permission("RW+", "", more_users) 197 | @repo.add_permission("RW+", "", even_more_users) 198 | expect(@repo.permissions.first["RW+"][""]).to eq users.concat(more_users).concat(even_more_users).uniq! 199 | expect(@repo.permissions.first["RW+"][""].length).to eq 11 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /spec/unit_tests/ssh_key_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Gitolite::SSHKey do 4 | 5 | key_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'keys', 'bob') 6 | output_dir = '/tmp' 7 | 8 | describe ".from_string" do 9 | it 'should construct an SSH key from a string' do 10 | key = File.join(key_dir, 'bob.pub') 11 | key_string = File.read(key) 12 | s = Gitolite::SSHKey.from_string(key_string, "bob") 13 | 14 | expect(s.owner).to eq 'bob' 15 | expect(s.location).to eq "" 16 | expect(s.blob).to eq key_string.split[1] 17 | end 18 | 19 | it 'should raise an ArgumentError when an owner isnt specified' do 20 | key_string = "not_a_real_key" 21 | expect { Gitolite::SSHKey.from_string(key_string) }.to raise_error(ArgumentError) 22 | end 23 | 24 | it 'should have a location when one is specified' do 25 | key = File.join(key_dir, 'bob.pub') 26 | key_string = File.read(key) 27 | s = Gitolite::SSHKey.from_string(key_string, "bob", "kansas") 28 | 29 | expect(s.owner).to eq 'bob' 30 | expect(s.location).to eq "kansas" 31 | expect(s.blob).to eq key_string.split[1] 32 | end 33 | 34 | it 'should raise an ArgumentError when owner is nil' do 35 | expect { Gitolite::SSHKey.from_string("bad_string", nil) }.to raise_error(ArgumentError) 36 | end 37 | 38 | it 'should raise an ArgumentError when we get an invalid Gitolite::SSHKey string' do 39 | expect { Gitolite::SSHKey.from_string("bad_string", "bob") }.to raise_error(ArgumentError) 40 | end 41 | end 42 | 43 | 44 | describe ".from_file" do 45 | it 'should load a key from a file' do 46 | key = File.join(key_dir, 'bob.pub') 47 | s = Gitolite::SSHKey.from_file(key) 48 | key_string = File.read(key).split 49 | 50 | expect(s.owner).to eq "bob" 51 | expect(s.blob).to eq key_string[1] 52 | expect(s.location).to eq '' 53 | end 54 | 55 | it 'should load a key from a file' do 56 | key = File.join(key_dir, 'bob.pub') 57 | s = Gitolite::SSHKey.from_file(key) 58 | expect(s.owner).to eq 'bob' 59 | expect(s.location).to eq '' 60 | end 61 | 62 | it 'should load a key with an e-mail owner from a file' do 63 | key = File.join(key_dir, 'bob@example.com.pub') 64 | s = Gitolite::SSHKey.from_file(key) 65 | expect(s.owner).to eq 'bob@example.com' 66 | expect(s.location).to eq '' 67 | end 68 | 69 | it 'should load a key from a file within location' do 70 | key = File.join(key_dir, 'desktop', 'bob.pub') 71 | s = Gitolite::SSHKey.from_file(key) 72 | expect(s.owner).to eq 'bob' 73 | expect(s.location).to eq 'desktop' 74 | end 75 | 76 | it 'should load a key from a file within location' do 77 | key = File.join(key_dir, 'school', 'bob.pub') 78 | s = Gitolite::SSHKey.from_file(key) 79 | expect(s.owner).to eq 'bob' 80 | expect(s.location).to eq 'school' 81 | end 82 | end 83 | 84 | 85 | describe '#keys' do 86 | it 'should load ssh key properly' do 87 | key = File.join(key_dir, 'bob.pub') 88 | s = Gitolite::SSHKey.from_file(key) 89 | parts = File.read(key).split #should get type, blob, email 90 | 91 | expect(s.type).to eq parts[0] 92 | expect(s.blob).to eq parts[1] 93 | expect(s.email).to eq parts[2] 94 | end 95 | end 96 | 97 | 98 | describe '#new' do 99 | it 'should create a valid ssh key' do 100 | type = "ssh-rsa" 101 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 102 | email = Forgery::Internet.email_address 103 | 104 | s = Gitolite::SSHKey.new(type, blob, email) 105 | 106 | expect(s.to_s).to eq [type, blob, email].join(' ') 107 | expect(s.owner).to eq email 108 | end 109 | 110 | it 'should create a valid ssh key while specifying an owner' do 111 | type = "ssh-rsa" 112 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 113 | email = Forgery::Internet.email_address 114 | owner = Forgery::Name.first_name 115 | 116 | s = Gitolite::SSHKey.new(type, blob, email, owner) 117 | 118 | expect(s.to_s).to eq [type, blob, email].join(' ') 119 | expect(s.owner).to eq owner 120 | end 121 | 122 | it 'should create a valid ssh key while specifying an owner and location' do 123 | type = "ssh-rsa" 124 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 125 | email = Forgery::Internet.email_address 126 | owner = Forgery::Name.first_name 127 | location = Forgery::Name.location 128 | 129 | s = Gitolite::SSHKey.new(type, blob, email, owner, location) 130 | 131 | expect(s.to_s).to eq [type, blob, email].join(' ') 132 | expect(s.owner).to eq owner 133 | expect(s.location).to eq location 134 | end 135 | end 136 | 137 | 138 | describe '#hash' do 139 | it 'should have two hash equalling one another' do 140 | type = "ssh-rsa" 141 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 142 | email = Forgery::Internet.email_address 143 | owner = Forgery::Name.first_name 144 | location = Forgery::Name.location 145 | 146 | hash_test = [owner, location, type, blob, email].hash 147 | s = Gitolite::SSHKey.new(type, blob, email, owner, location) 148 | 149 | expect(s.hash).to eq hash_test 150 | end 151 | end 152 | 153 | 154 | describe '#filename' do 155 | it 'should create a filename that is the .pub' do 156 | type = "ssh-rsa" 157 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 158 | email = Forgery::Internet.email_address 159 | 160 | s = Gitolite::SSHKey.new(type, blob, email) 161 | 162 | expect(s.filename).to eq "#{email}.pub" 163 | end 164 | 165 | it 'should create a filename that is the .pub' do 166 | type = "ssh-rsa" 167 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 168 | email = Forgery::Internet.email_address 169 | owner = Forgery::Name.first_name 170 | 171 | s = Gitolite::SSHKey.new(type, blob, email, owner) 172 | 173 | expect(s.filename).to eq "#{owner}.pub" 174 | end 175 | 176 | it 'should create a filename that is the .pub' do 177 | type = "ssh-rsa" 178 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 179 | email = Forgery::Internet.email_address 180 | location = Forgery::Basic.text(:at_least => 8, :at_most => 15) 181 | 182 | s = Gitolite::SSHKey.new(type, blob, email, nil, location) 183 | 184 | expect(s.filename).to eq "#{email}.pub" 185 | expect(s.relative_path).to eq File.join(email, location, "#{email}.pub") 186 | end 187 | 188 | it 'should create a filename that is the @.pub' do 189 | type = "ssh-rsa" 190 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 191 | email = Forgery::Internet.email_address 192 | owner = Forgery::Name.first_name 193 | location = Forgery::Basic.text(:at_least => 8, :at_most => 15) 194 | 195 | s = Gitolite::SSHKey.new(type, blob, email, owner, location) 196 | 197 | expect(s.filename).to eq "#{owner}.pub" 198 | end 199 | end 200 | 201 | 202 | describe '#to_file' do 203 | it 'should write a "valid" SSH public key to the file system' do 204 | type = "ssh-rsa" 205 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 206 | email = Forgery::Internet.email_address 207 | owner = Forgery::Name.first_name 208 | location = Forgery::Basic.text(:at_least => 8, :at_most => 15) 209 | 210 | s = Gitolite::SSHKey.new(type, blob, email, owner, location) 211 | 212 | ## write file 213 | s.to_file(output_dir) 214 | 215 | ## compare raw string with written file 216 | expect(s.to_s).to eq File.read(File.join(output_dir, owner, location, s.filename)) 217 | end 218 | 219 | it 'should return the filename written' do 220 | type = "ssh-rsa" 221 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 222 | email = Forgery::Internet.email_address 223 | owner = Forgery::Name.first_name 224 | location = Forgery::Basic.text(:at_least => 8, :at_most => 15) 225 | 226 | s = Gitolite::SSHKey.new(type, blob, email, owner, location) 227 | expect(s.to_file(output_dir)).to eq File.join(output_dir, owner, location, s.filename) 228 | end 229 | end 230 | 231 | 232 | describe '==' do 233 | it 'should have two keys equalling one another' do 234 | type = "ssh-rsa" 235 | blob = Forgery::Basic.text(:at_least => 372, :at_most => 372) 236 | email = Forgery::Internet.email_address 237 | 238 | s1 = Gitolite::SSHKey.new(type, blob, email) 239 | s2 = Gitolite::SSHKey.new(type, blob, email) 240 | 241 | expect(s1).to eq s2 242 | end 243 | end 244 | end 245 | --------------------------------------------------------------------------------