jbox-gitolite 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f37bb126327d01abd1e3fb270b1da512d823c68f
4
+ data.tar.gz: 0d8485bf1ac13abebfeba9329cb2a31829941d69
5
+ SHA512:
6
+ metadata.gz: 6beb1f436fabad483f7e88c0a9633a7d24c1cf2393fa61046b32c2fd678da7c2f0abde8200624aa48f52aac9235e962df0dc037afde248df562493da437dba41
7
+ data.tar.gz: dee2cb5e1f9c97988cb9ccea6ad0b63e1eecb45a08a19c16365450228ce3f310016a277027dacc1edcfecddaddc71c79baa1226539f472390b7ab17fdc22174c
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ rdoc/*
5
+ *.conf
6
+ coverage
7
+ *.lock
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gitolite.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2010-2011 Stafford Brunk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,190 @@
1
+ # gitolite [![Build Status](https://secure.travis-ci.org/wingrunr21/gitolite.png)](http://travis-ci.org/wingrunr21/gitolite)
2
+
3
+ This gem is designed to provide a Ruby interface to the [gitolite](https://github.com/sitaramc/gitolite) git backend system. I am aiming to provide all management functionality that is available via the gitolite-admin repository (like SSH keys, repository permissions, etc)
4
+
5
+ This gem can still have problems. Please file an issue if you encounter a bug. If you have a feature request, file one please.
6
+
7
+ ## Features ##
8
+ * Allows for the creation and management of repos within gitolite
9
+ * Allows for the creation and deletion of SSH keys within gitolite
10
+ * Allows for the bootstrapping of a gitolite-admin repository
11
+
12
+ ## Requirements ##
13
+ * Ruby 1.8.x or 1.9.x
14
+ * a working [gitolite](https://github.com/sitaramc/gitolite) installation
15
+ * the <tt>gitolite-admin</tt> repository checked out locally
16
+
17
+ ## Installation ##
18
+
19
+ gem install gitolite
20
+
21
+ ## Usage ##
22
+
23
+ ### Load a gitolite-admin repo ###
24
+
25
+ require 'gitolite'
26
+ ga_repo = Gitolite::GitoliteAdmin.new("/path/to/gitolite/admin/repo")
27
+
28
+ This method can only be called on an existing gitolite-admin repo. If you need to create a new gitolite-admin repo, see "Bootstrapping".
29
+
30
+ ### Configuration Files ###
31
+
32
+ conf = ga_repo.config
33
+
34
+ #Empty configs can also be initialized
35
+ conf2 = Config.init # => defaults to a filename of gitolite.conf
36
+ conf2 = Config.init("new_config.conf")
37
+
38
+ #Filename is set to whatever the filename was when the config was created
39
+ conf.filename # => "gitolite.conf"
40
+ conf2.filename # => "new_config.conf")
41
+
42
+ #filename can be changed via the setter
43
+ conf2.filename = "new_config.conf"
44
+
45
+ #to_file will write the config out to the file system
46
+ #using the value of the filename attribute. An alternative
47
+ #filename can also be specified
48
+ conf.to_file("/new/config/path") # => writes /new/config/path/gitolite.conf
49
+ conf.to_file("/new/config/path", "test.conf") # => writes /new/config/path/test.conf
50
+
51
+ ### Repo management ###
52
+
53
+ repo = Gitolite::Config::Repo.new("AwesomeRepo")
54
+
55
+ #For a list of permissions, see http://sitaramc.github.com/gitolite/conf.html#gitolite
56
+ repo.add_permission("RW+", "", "bob", "joe", "susan")
57
+
58
+ #Set a git config option to the repo
59
+ repo.set_git_config("hooks.mailinglist", "gitolite-commits@example.tld") # => "gitolite-commits@example.tld"
60
+
61
+ #Unset a git config option from the repo
62
+ repo.unset_git_config("hooks.mailinglist") # => "gitolite-commits@example.tld"
63
+
64
+ #Set a gitolite option to the repo
65
+ repo.set_gitolite_option("mirroring.master", "kenobi") # => "kenobi"
66
+
67
+ #Remove a gitolite option from the repo
68
+ repo.unset_gitolite_option("mirroring.master") # => "kenobi"
69
+
70
+ #Add repo to config
71
+ conf.add_repo(repo)
72
+
73
+ #Delete repo by object
74
+ conf.rm_repo(repo)
75
+
76
+ #Delete a repo by name
77
+ conf.rm_repo("AwesomeRepo")
78
+ conf.rm_repo(:AwesomeRepo)
79
+
80
+ #Test if repo exists by name
81
+ conf.has_repo?('cool_repo') # => false
82
+ conf.has_repo?(:cool_repo) # => false
83
+
84
+ #Can also pass a Gitolite::Config::Repo object
85
+ repo = Gitolite::Config::Repo.new('cool_repo')
86
+ conf.has_repo?(repo) # => true
87
+
88
+ #Get a repo object from the config
89
+ repo = conf.get_repo('cool_repo')
90
+ repo = conf.get_repo(:cool_repo)
91
+
92
+ ### SSH Key Management ###
93
+
94
+ #Three ways to create keys: manually, from an existing key, or from a string representing a key
95
+ key = Gitolite::SSHKey.new("ssh-rsa", "big-public-key-blob", "email")
96
+ key2 = Gitolite::SSHKey.from_file("/path/to/ssh/key.pub")
97
+
98
+ key_string = File.read("/path/to/ssh/key.pub")
99
+ key3 = Gitolite::SSHKey.from_string(key_string, "owner")
100
+
101
+ #Add the keys
102
+ ga_repo.add_key(key)
103
+ ga_repo.add_key(key2)
104
+ ga_repo.add_key(key3)
105
+
106
+ #Remove key2
107
+ ga_repo.rm_key(key2)
108
+
109
+ ### Save changes ###
110
+
111
+ ga_repo.save
112
+
113
+ When this method is called, all changes get written to the file system and staged in git. For the time being, gitolite assumes full control of the gitolite-admin repository. This means that any keys in the keydir that are not being tracked will be removed and any human changes to gitolite.conf will be erased.
114
+
115
+ ### Apply changes ###
116
+ ga_repo.apply
117
+
118
+ This method will commit all changes with a generic message (will be improved upon later) and push to <tt>origin master</tt>.
119
+
120
+ ### Save and apply ###
121
+ ga_repo.save_and_apply
122
+
123
+ ### Updating remote changes ###
124
+ #In order to avoid conflicts, this will perform a reset! by default
125
+ #pass :reset => false to disable the reset (Git conflicts will have to be manually fixed)
126
+ ga_repo.update
127
+ ga_repo.update(:reset => false)
128
+
129
+ #Update while performing a rebase
130
+ ga_repo.update(:rebase => true)
131
+
132
+ ### Reloading from the file system ###
133
+ ga_repo.reload!
134
+
135
+ ### Resetting to HEAD, destroying all local changes (including untracked files) ###
136
+ #This will also perform a reload!
137
+ ga_repo.reset!
138
+
139
+ ### Bootstrapping ###
140
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo")
141
+
142
+ This will create the folders <tt>conf</tt> and <tt>keydir</tt> in the supplied path. A config file will also be created in the conf directory. The default configuration supplies RW+ permissions to a user named git for a repo named <tt>gitolite-admin</tt>. You can specify an options hash to change some values:
143
+
144
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:user => "admin", :perm => "RW"})
145
+
146
+ You can also pass a message to be used for the initial bootstrap commit:
147
+
148
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:message => "Bootstrapped new repo"})
149
+
150
+ Please note that while bootstrapping is supported, I highly recommend that the initial gitolite-admin repo be created by gitolite itself.
151
+
152
+ ## Caveats ##
153
+ ### Windows compatibility ###
154
+ The grit gem (which is used for under-the-hood git operations) does not currently support Windows. Until it does, gitolite will be unable to support Windows.
155
+
156
+ ### Group Ordering ###
157
+ When the gitolite backend parses the config file, it does so in one pass. Because of this, groups that are modified after being used do not see those changes reflected in previous uses.
158
+
159
+ For example:
160
+
161
+ @groupa = bob joe sue
162
+ @groupb = jim @groupa
163
+ @groupa = sam
164
+
165
+ Group b will contain the users <tt>jim, bob, joe, and sue</tt>
166
+
167
+ The gitolite gem, on the other hand, will <em>always</em> output groups so that all modifications are represented before it is ever used. For the above example, group b will be output with the following users: <tt>jim, bob, joe, sue, and sam</tt>. The groups in the config file will look like this:
168
+
169
+ @groupa = bob joe sue sam
170
+ @groupb = jim @groupa
171
+
172
+ ## Issues ##
173
+ * Gem is not thread safe. For now, the gem will change directories in order to perform git operations. It will, however, return to the old working directory once it is finished. I am looking into making the gem thread safe. Note that this is only an issue on Rubies that do not have a GIL (ex jRuby or Rubinius)
174
+
175
+ # Contributing #
176
+ * Tests! If you ask me to pull changes that are not adequately tested, I'm not going to do it.
177
+ * If you introduce new features/public methods on objects, you must update the README.
178
+
179
+ ### Contributors ###
180
+ * Alexander Simonov - [simonoff](https://github.com/simonoff)
181
+ * Pierre Guinoiseau - [peikk0](https://github.com/peikk0)
182
+
183
+ ## Documentation ##
184
+ * Rdoc is coming eventually
185
+
186
+ ## Future ##
187
+ * support folders in the keydir
188
+ * support include tags
189
+ * cleanup methods to make adding and removing easier (like add_key should accept an array of keys)
190
+ * Make the gem thread safe
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+ require 'rspec/core/rake_task'
4
+
5
+ # Rake tasks from https://github.com/mojombo/rakegem/blob/master/Rakefile
6
+
7
+ # Helper Functions
8
+ def name
9
+ @name ||= Dir['*.gemspec'].first.split('.').first
10
+ end
11
+
12
+ def version
13
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
14
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
15
+ end
16
+
17
+ # Standard tasks
18
+ RSpec::Core::RakeTask.new(:spec)
19
+ task :test => :spec
20
+ task :default => :spec
21
+
22
+ require 'rdoc/task'
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = "#{name} #{version}"
26
+ rdoc.rdoc_files.include('README*')
27
+ rdoc.rdoc_files.include('lib/**/*.rb')
28
+ end
29
+
30
+ desc "Open an irb session preloaded with this library"
31
+ task :console do
32
+ sh "irb -rubygems -r ./lib/#{name}.rb"
33
+ end
data/gitolite.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gitolite/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jbox-gitolite"
7
+ s.version = Gitolite::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Nicolas Rodriguez"]
10
+ s.email = ["nrodriguez@jbox-web.com"]
11
+ s.homepage = "https://github.com/n-rodriguez/gitolite"
12
+ s.summary = %q{A Ruby gem for manipulating the gitolite git backend via the gitolite-admin repository.}
13
+ s.description = %q{This gem is designed to provide a Ruby interface to the gitolite git backend system. 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.rubyforge_project = "jbox-gitolite"
17
+
18
+ s.add_development_dependency "rspec", "~> 2.9.0"
19
+ s.add_development_dependency "forgery", "~> 0.5.0"
20
+ s.add_development_dependency "rdoc", "~> 3.12"
21
+ s.add_development_dependency "simplecov", "~> 0.6.2"
22
+ s.add_development_dependency "rake", "~> 10.0.2"
23
+
24
+ s.add_dependency "gitlab-grit", "~> 2.6.0"
25
+ s.add_dependency "hashery", "~> 1.5.0"
26
+ s.add_dependency "gratr19", "~> 0.4.4.1"
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
30
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
+ s.require_paths = ["lib"]
32
+ end
data/lib/gitolite.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Gitolite
2
+ require 'grit'
3
+ require 'hashery'
4
+ require 'gratr'
5
+ require 'gitolite/ssh_key'
6
+ require 'gitolite/config'
7
+ require 'gitolite/gitolite_admin'
8
+ end
@@ -0,0 +1,253 @@
1
+ require 'tempfile'
2
+ require File.join(File.dirname(__FILE__), 'config', 'repo')
3
+ require File.join(File.dirname(__FILE__), 'config', 'group')
4
+
5
+ module Gitolite
6
+ class Config
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
+ def self.init(filename = "gitolite.conf")
17
+ file = Tempfile.new(filename)
18
+ conf = self.new(file.path)
19
+ conf.filename = filename #kill suffix added by Tempfile
20
+ file.close(unlink_now = true)
21
+ conf
22
+ end
23
+
24
+ #TODO: merge repo unless overwrite = true
25
+ def add_repo(repo, overwrite = false)
26
+ raise ArgumentError, "Repo must be of type Gitolite::Config::Repo!" unless repo.instance_of? Gitolite::Config::Repo
27
+ @repos[repo.name] = repo
28
+ end
29
+
30
+ def rm_repo(repo)
31
+ name = normalize_repo_name(repo)
32
+ @repos.delete(name)
33
+ end
34
+
35
+ def has_repo?(repo)
36
+ name = normalize_repo_name(repo)
37
+ @repos.has_key?(name)
38
+ end
39
+
40
+ def get_repo(repo)
41
+ name = normalize_repo_name(repo)
42
+ @repos[name]
43
+ end
44
+
45
+ def add_group(group, overwrite = false)
46
+ raise ArgumentError, "Group must be of type Gitolite::Config::Group!" unless group.instance_of? Gitolite::Config::Group
47
+ @groups[group.name] = group
48
+ end
49
+
50
+ def rm_group(group)
51
+ name = normalize_group_name(group)
52
+ @groups.delete(name)
53
+ end
54
+
55
+ def has_group?(group)
56
+ name = normalize_group_name(group)
57
+ @groups.has_key?(name)
58
+ end
59
+
60
+ def get_group(group)
61
+ name = normalize_group_name(group)
62
+ @groups[name]
63
+ end
64
+
65
+ def to_file(path=".", filename=@filename)
66
+ raise ArgumentError, "Path contains a filename or does not exist" unless File.directory?(path)
67
+
68
+ new_conf = File.join(path, filename)
69
+ File.open(new_conf, "w") do |f|
70
+ #Output groups
71
+ dep_order = build_groups_depgraph
72
+ dep_order.each {|group| f.write group.to_s }
73
+
74
+ gitweb_descs = []
75
+ @repos.sort.each do |k, v|
76
+ f.write "\n"
77
+ f.write v.to_s
78
+
79
+ gwd = v.gitweb_description
80
+ gitweb_descs.push(gwd) unless gwd.nil?
81
+ end
82
+
83
+ f.write "\n"
84
+ f.write gitweb_descs.join("\n")
85
+ end
86
+
87
+ new_conf
88
+ end
89
+
90
+ private
91
+ #Based on
92
+ #https://github.com/sitaramc/gitolite/blob/pu/src/gl-compile-conf#cleanup_conf_line
93
+ def cleanup_config_line(line)
94
+ #remove comments, even those that happen inline
95
+ line.gsub!(/^((".*?"|[^#"])*)#.*/) {|m| m=$1}
96
+
97
+ #fix whitespace
98
+ line.gsub!('=', ' = ')
99
+ line.gsub!(/\s+/, ' ')
100
+ line.strip
101
+ end
102
+
103
+ def process_config(config)
104
+ context = [] #will store our context for permissions or config declarations
105
+
106
+ #Read each line of our config
107
+ File.open(config, 'r').each do |l|
108
+
109
+ line = cleanup_config_line(l)
110
+ next if line.empty? #lines are empty if we killed a comment
111
+
112
+ case line
113
+ #found a repo definition
114
+ when /^repo (.*)/
115
+ #Empty our current context
116
+ context = []
117
+
118
+ repos = $1.split
119
+ repos.each do |r|
120
+ context << r
121
+
122
+ @repos[r] = Repo.new(r) unless has_repo?(r)
123
+ end
124
+ #repo permissions
125
+ when /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/
126
+ perm = $1
127
+ refex = $2 || ""
128
+ users = $3.split
129
+
130
+ context.each do |c|
131
+ @repos[c].add_permission(perm, refex, users)
132
+ end
133
+ #repo git config
134
+ when /^config (.+) = ?(.*)/
135
+ key = $1
136
+ value = $2
137
+
138
+ context.each do |c|
139
+ @repos[c].set_git_config(key, value)
140
+ end
141
+ #repo gitolite option
142
+ when /^option (.+) = (.*)/
143
+ key = $1
144
+ value = $2
145
+
146
+ raise ParseError, "Missing gitolite option value for repo: #{repo} and key: #{key}" if value.nil?
147
+
148
+ context.each do |c|
149
+ @repos[c].set_gitolite_option(key, value)
150
+ end
151
+ #group definition
152
+ when /^#{Group::PREPEND_CHAR}(\S+) = ?(.*)/
153
+ group = $1
154
+ users = $2.split
155
+
156
+ @groups[group] = Group.new(group) unless has_group?(group)
157
+ @groups[group].add_users(users)
158
+ #gitweb definition
159
+ when /^(\S+)(?: "(.*?)")? = "(.*)"$/
160
+ repo = $1
161
+ owner = $2
162
+ description = $3
163
+
164
+ #Check for missing description
165
+ raise ParseError, "Missing Gitweb description for repo: #{repo}" if description.nil?
166
+
167
+ #Check for groups
168
+ raise ParseError, "Gitweb descriptions cannot be set for groups" if repo =~ /@.+/
169
+
170
+ if has_repo? repo
171
+ r = @repos[repo]
172
+ else
173
+ r = Repo.new(repo)
174
+ add_repo(r)
175
+ end
176
+
177
+ r.owner = owner
178
+ r.description = description
179
+ when /^include "(.+)"/
180
+ #TODO: implement includes
181
+ #ignore includes for now
182
+ when /^subconf (\S+)$/
183
+ #TODO: implement subconfs
184
+ #ignore subconfs for now
185
+ else
186
+ raise ParseError, "'#{line}' cannot be processed"
187
+ end
188
+ end
189
+ end
190
+
191
+ # Normalizes the various different input objects to Strings
192
+ def normalize_name(context, constant = nil)
193
+ case context
194
+ when constant
195
+ context.name
196
+ when Symbol
197
+ context.to_s
198
+ else
199
+ context
200
+ end
201
+ end
202
+
203
+ def method_missing(meth, *args, &block)
204
+ if meth.to_s =~ /normalize_(\w+)_name/
205
+ #Could use Object.const_get to figure out the constant here
206
+ #but for only two cases, this is more readable
207
+ case $1
208
+ when "repo"
209
+ normalize_name(args[0], Gitolite::Config::Repo)
210
+ when "group"
211
+ normalize_name(args[0], Gitolite::Config::Group)
212
+ end
213
+ else
214
+ super
215
+ end
216
+ end
217
+
218
+ # Builds a dependency tree from the groups in order to ensure all groups
219
+ # are defined before they are used
220
+ def build_groups_depgraph
221
+ dp = ::GRATR::Digraph.new
222
+
223
+ # Add each group to the graph
224
+ @groups.each_value do |group|
225
+ dp.add_vertex! group
226
+
227
+ # Select group names from the users
228
+ subgroups = group.users.select {|u| u =~ /^#{Group::PREPEND_CHAR}.*$/}.map{|g| get_group g.gsub(Group::PREPEND_CHAR, '') }
229
+
230
+ subgroups.each do |subgroup|
231
+ dp.add_edge! subgroup, group
232
+ end
233
+ end
234
+
235
+ # Figure out if we have a good depedency graph
236
+ dep_order = dp.topsort
237
+
238
+ if dep_order.empty?
239
+ raise GroupDependencyError unless @groups.empty?
240
+ end
241
+
242
+ dep_order
243
+ end
244
+
245
+ #Raised when something in a config fails to parse properly
246
+ class ParseError < RuntimeError
247
+ end
248
+
249
+ # Raised when group dependencies cannot be suitably resolved for output
250
+ class GroupDependencyError < RuntimeError
251
+ end
252
+ end
253
+ end