gitolite 0.0.2.alpha → 0.0.3.alpha

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
+ rdoc/*
5
+ *.conf
6
+ coverage
@@ -1,32 +1,37 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitolite (0.0.1.alpha)
4
+ gitolite (0.0.3.alpha)
5
5
  grit (~> 2.4.1)
6
+ hashery (~> 1.4.0)
6
7
 
7
8
  GEM
8
9
  remote: http://rubygems.org/
9
10
  specs:
10
11
  diff-lcs (1.1.2)
11
- forgery (0.3.6)
12
+ forgery (0.5.0)
12
13
  grit (2.4.1)
13
14
  diff-lcs (~> 1.1)
14
15
  mime-types (~> 1.15)
16
+ hashery (1.4.0)
15
17
  mime-types (1.16)
16
- rspec (2.4.0)
17
- rspec-core (~> 2.4.0)
18
- rspec-expectations (~> 2.4.0)
19
- rspec-mocks (~> 2.4.0)
20
- rspec-core (2.4.0)
21
- rspec-expectations (2.4.0)
18
+ rcov (0.9.11)
19
+ rdoc (3.9.4)
20
+ rspec (2.6.0)
21
+ rspec-core (~> 2.6.0)
22
+ rspec-expectations (~> 2.6.0)
23
+ rspec-mocks (~> 2.6.0)
24
+ rspec-core (2.6.0)
25
+ rspec-expectations (2.6.0)
22
26
  diff-lcs (~> 1.1.2)
23
- rspec-mocks (2.4.0)
27
+ rspec-mocks (2.6.0)
24
28
 
25
29
  PLATFORMS
26
30
  ruby
27
- x86-mingw32
28
31
 
29
32
  DEPENDENCIES
30
- forgery (~> 0.3.6)
33
+ forgery (~> 0.5.0)
31
34
  gitolite!
32
- rspec (~> 2.4.0)
35
+ rcov (~> 0.9.11)
36
+ rdoc (~> 3.9.4)
37
+ rspec (~> 2.6.0)
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.
@@ -7,9 +7,13 @@ This gem is still under very active development. There are a number of issues w
7
7
  == Features
8
8
  * Allows for the creation and management of repos within gitolite
9
9
  * Allows for the creation and deletion of SSH keys within gitolite
10
+ * Allows for the bootstrapping of a gitolite-admin repository
11
+
12
+ == Issues
13
+ * 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.
10
14
 
11
15
  == Requirements
12
- * Ruby 1.9.2 or higher
16
+ * Ruby 1.8.x or 1.9.x
13
17
  * a working {gitolite}[https://github.com/sitaramc/gitolite] installation
14
18
  * the <tt>gitolite-admin</tt> repository checked out locally
15
19
 
@@ -23,28 +27,65 @@ This gem is still under very active development. There are a number of issues w
23
27
 
24
28
  require 'gitolite'
25
29
  ga_repo = Gitolite::GitoliteAdmin.new("/path/to/gitolite/admin/repo")
26
-
27
- For now, this method can only be called on an existing gitolite-admin repo. Support for bootstrapping a gitolite-repo will be added in the future (but will never be recommended).
28
-
30
+
31
+ This method can only be called on an existing gitolite-admin repo. If you need to create a new gitolite-admin repo, see "Bootstrapping".
32
+
33
+ === Configuration Files
34
+
35
+ conf = ga_repo.config
36
+
37
+ #Empty configs can also be initialized
38
+ conf2 = Config.init # => defaults to a filename of gitolite.conf
39
+ conf2 = Config.init("new_config.conf")
40
+
41
+ #Filename is set to whatever the filename was when the config was created
42
+ conf.filename # => "gitolite.conf"
43
+ conf2.filename # => "new_config.conf")
44
+
45
+ #filename can be changed via the setter
46
+ conf2.filename = "new_config.conf"
47
+
48
+ #to_file will write the config out to the file system
49
+ #using the value of the filename attribute. An alternative
50
+ #filename can also be specified
51
+ conf.to_file("/new/config/path") # => writes /new/config/path/gitolite.conf
52
+ conf.to_file("/new/config/path", "test.conf") # => writes /new/config/path/test.conf
53
+
29
54
  === Repo management
30
55
 
31
- repo = Config::Repo.new("AwesomeRepo")
32
-
56
+ repo = Gitolite::Config::Repo.new("AwesomeRepo")
57
+
33
58
  #For a list of permissions, see https://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.conf.mkd
34
59
  repo.add_permission("RW+", "", "bob", "joe", "susan")
35
-
36
- #Add repo
37
- ga_repo.add_repo(repo)
38
-
39
- #Delete repo
40
- ga_repo.rm_repo(repo)
60
+
61
+ #Add repo to config
62
+ conf.add_repo(repo)
63
+
64
+ #Delete repo by object
65
+ conf.rm_repo(repo)
66
+
67
+ #Delete a repo by name
68
+ conf.rm_repo("AwesomeRepo")
69
+ conf.rm_repo(:AwesomeRepo)
70
+
71
+ #Test if repo exists by name
72
+ conf.has_repo?('cool_repo') # => false
73
+ conf.has_repo?(:cool_repo) # => false
74
+
75
+ #Can also pass a Gitolite::Config::Repo object
76
+ repo = Gitolite::Config::Repo.new('cool_repo')
77
+ conf.has_repo?(repo) # => true
78
+
79
+ #Get a repo object from the config
80
+ repo = conf.get_repo('cool_repo')
81
+ repo = conf.get_repo(:cool_repo)
41
82
 
42
83
  === SSH Key Management
43
84
 
44
85
  #Two ways to create keys: manually or from an existing key
45
86
  key = Gitolite::SSHKey.new("ssh-rsa", "big-public-key-blob", "email")
46
87
  key2 = Gitolite::SSHKey.from_file("/path/to/ssh/key.pub")
47
-
88
+
48
89
  #Add the keys
49
90
  ga_repo.add_key(key)
50
91
  ga_repo.add_key(key2)
@@ -55,26 +96,49 @@ For now, this method can only be called on an existing gitolite-admin repo. Sup
55
96
  === Save changes
56
97
 
57
98
  ga_repo.save
58
-
99
+
59
100
  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.
60
101
 
61
102
  === Apply changes
62
103
  ga_repo.apply
63
104
 
64
105
  This method will commit all changes with a generic message (will be improved upon later) and push to <tt>origin master</tt>.
65
-
106
+
66
107
  === Save and apply
67
108
  ga_repo.save_and_apply
68
109
 
110
+ === Bootstrapping
111
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo")
112
+
113
+ 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:
114
+
115
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:user => "admin", :perm => "RW"})
116
+
117
+ You can also pass a message to be used for the initial bootstrap commit:
118
+
119
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:message => "Bootstrapped new repo"})
120
+
121
+ Please note that while bootstrapping is supported, I highly recommend that the initial gitolite-admin repo be created by gitolite itself.
122
+
69
123
  == Caveats
70
- === 1.8.x compatibility
71
- This gem should work properly on Ruby 1.8.x with the exception of deny rules. In order to fully support Ruby < 1.9.x, an ordered Hash is required. The one implemented by ActiveSupport could probably be used. Support will be added if there appears to be a demand for it.
124
+ === Windows compatibility
125
+ 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.
126
+
127
+ == Contributing
128
+ * Tests! If you ask me to pull changes that are not adequately tested, I'm not going to do it.
129
+ * If you introduce new features/public methods on objects, you must update the README.
130
+
131
+ === Contributors
132
+ * Alexander Simonov - {simonoff}[https://github.com/simonoff]
72
133
 
73
134
  == Documentation
74
135
  * Rdoc is coming soon
75
136
 
76
137
  == Future
77
138
  * support folders in the keydir
78
- * support includes tags
79
- * support bootstrapping a gitolite-admin repo
80
- * cleanup methods to make adding and removing easier (like add_key should accept an array of keys)
139
+ * support include tags
140
+ * support pull operations to sync the local gitolite-admin repository with the server
141
+ * cleanup methods to make adding and removing easier (like add_key should accept an array of keys)
142
+ * Rails integration
143
+ * Make the gem thread safe
144
+ * Add full support for Wildcard repos
data/Rakefile CHANGED
@@ -1,2 +1,34 @@
1
1
  require 'bundler'
2
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
+ require 'rcov'
19
+ RSpec::Core::RakeTask.new(:spec)
20
+ task :test => :spec
21
+ task :default => :spec
22
+
23
+ require 'rdoc/task'
24
+ Rake::RDocTask.new do |rdoc|
25
+ rdoc.rdoc_dir = 'rdoc'
26
+ rdoc.title = "#{name} #{version}"
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ desc "Open an irb session preloaded with this library"
32
+ task :console do
33
+ sh "irb -rubygems -r ./lib/#{name}.rb"
34
+ end
@@ -14,9 +14,12 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "gitolite"
16
16
 
17
- s.add_development_dependency "rspec", "~> 2.4.0"
18
- s.add_development_dependency "forgery", "~> 0.3.6"
17
+ s.add_development_dependency "rspec", "~> 2.6.0"
18
+ s.add_development_dependency "forgery", "~> 0.5.0"
19
+ s.add_development_dependency "rdoc", "~> 3.9.4"
20
+ s.add_development_dependency "rcov", "~> 0.9.11"
19
21
  s.add_dependency "grit", "~> 2.4.1"
22
+ s.add_dependency "hashery", "~> 1.4.0"
20
23
 
21
24
  s.files = `git ls-files`.split("\n")
22
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,5 +1,6 @@
1
1
  module Gitolite
2
2
  require 'grit'
3
+ require 'hashery'
3
4
  require 'gitolite/ssh_key'
4
5
  require 'gitolite/config'
5
6
  require 'gitolite/gitolite_admin'
@@ -1,83 +1,84 @@
1
+ require 'tempfile'
2
+ require File.join(File.dirname(__FILE__), 'config', 'repo')
3
+ require File.join(File.dirname(__FILE__), 'config', 'group')
4
+
1
5
  module Gitolite
2
6
  class Config
3
7
  attr_accessor :repos, :groups, :filename
4
8
 
5
9
  def initialize(config)
6
10
  @repos = {}
7
- @groups = Hash.new { |k,v| k[v] = [] }
11
+ @groups = {}
8
12
  @filename = File.basename(config)
9
13
  process_config(config)
10
14
  end
11
15
 
12
- #Represents a repo inside the gitolite configuration. The name, permissions, and git config
13
- #options are all encapsulated in this class
14
- class Repo
15
- ALLOWED_PERMISSIONS = ['C', 'R', 'RW', 'RW+', 'RWC', 'RW+C', 'RWD', 'RW+D', 'RWCD', 'RW+CD', '-']
16
-
17
- attr_accessor :permissions, :name, :config
18
-
19
- def initialize(name)
20
- @name = name
21
- @permissions = Hash.new {|k,v| k[v] = Hash.new{|k2, v2| k2[v2] = [] }}
22
- @config = {}
23
- end
24
-
25
- def add_permission(perm, refex, *users)
26
- if ALLOWED_PERMISSIONS.include? perm
27
- @permissions[perm][refex].concat users
28
- else
29
- raise InvalidPermissionError, "#{perm} is not in the allowed list of permissions!"
30
- end
31
- end
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
32
23
 
33
- def set_git_config(key, value)
34
- @config[key] = value
35
- end
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
36
29
 
37
- def unset_git_config(key)
38
- @config.delete(key)
39
- end
30
+ def rm_repo(repo)
31
+ name = normalize_repo_name(repo)
32
+ @repos.delete(name)
33
+ end
40
34
 
41
- def to_s
42
- repo = "repo #{@name}\n"
35
+ def has_repo?(repo)
36
+ name = normalize_repo_name(repo)
37
+ @repos.has_key?(name)
38
+ end
43
39
 
44
- @permissions.each do |perm, list|
45
- list.each do |refex, users|
46
- repo += " " + perm.ljust(6) + refex.ljust(20) + "= " + users.join(' ') + "\n"
47
- end
48
- end
40
+ def get_repo(repo)
41
+ name = normalize_repo_name(repo)
42
+ @repos[name]
43
+ end
49
44
 
50
- repo
51
- end
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
52
49
 
53
- #Gets raised if a permission that isn't in the allowed
54
- #list is passed in
55
- class InvalidPermissionError < RuntimeError
56
- end
50
+ def rm_group(group)
51
+ name = normalize_group_name(group)
52
+ @groups.delete(name)
57
53
  end
58
54
 
59
- #TODO: merge repo unless overwrite = true
60
- def add_repo(repo, overwrite = false)
61
- raise "Repo must be of type Gitolite::Config::Repo!" unless repo.instance_of? Gitolite::Config::Repo
62
- @repos[repo.name] = repo
55
+ def has_group?(group)
56
+ name = normalize_group_name(group)
57
+ @groups.has_key?(name)
63
58
  end
64
-
65
- def rm_repo(repo)
66
- raise "Repo must be of type Gitolite::Config::Repo!" unless repo.instance_of? Gitolite::Config::Repo
67
- @repos.delete repo.name
59
+
60
+ def get_group(group)
61
+ name = normalize_group_name(group)
62
+ @groups[name]
68
63
  end
69
64
 
70
- def to_file(path)
71
- new_conf = File.join(path, @filename)
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)
72
69
  File.open(new_conf, "w") do |f|
73
- @groups.each do |k,v|
74
- members = v.join(' ')
75
- f.write "#{k.ljust(20)}= #{members}\n"
76
- end
70
+ #Output groups
71
+ @groups.each_value {|group| f.write group.to_s }
77
72
 
73
+ gitweb_descs = []
78
74
  @repos.each do |k, v|
79
75
  f.write v.to_s
76
+
77
+ gwd = v.gitweb_description
78
+ gitweb_descs.push(gwd) unless gwd.nil?
80
79
  end
80
+
81
+ f.write gitweb_descs.join("\n")
81
82
  end
82
83
 
83
84
  new_conf
@@ -93,7 +94,7 @@ module Gitolite
93
94
  #fix whitespace
94
95
  line.gsub!('=', ' = ')
95
96
  line.gsub!(/\s+/, ' ')
96
- line.strip!
97
+ line.strip
97
98
  end
98
99
 
99
100
  def process_config(config)
@@ -115,7 +116,7 @@ module Gitolite
115
116
  repos.each do |r|
116
117
  context << r
117
118
 
118
- @repos[r] = Repo.new(r) unless @repos.has_key? r
119
+ @repos[r] = Repo.new(r) unless has_repo?(r)
119
120
  end
120
121
  #repo permissions
121
122
  when /^(-|C|R|RW\+?(?:C?D?|D?C?)) (.* )?= (.+)/
@@ -135,23 +136,71 @@ module Gitolite
135
136
  @repos[c].set_git_config(key, value)
136
137
  end
137
138
  #group definition
138
- when /^(@\S+) = ?(.*)/
139
+ when /^#{Group::PREPEND_CHAR}(\S+) = ?(.*)/
139
140
  group = $1
140
141
  users = $2.split
141
142
 
142
- @groups[group].concat users
143
- @groups[group].uniq!
143
+ @groups[group] = Group.new(group) unless has_group?(group)
144
+ @groups[group].add_users(users)
144
145
  #gitweb definition
145
146
  when /^(\S+)(?: "(.*?)")? = "(.*)"$/
146
- #ignore gitweb right now
147
- puts line
147
+ repo = $1
148
+ owner = $2
149
+ description = $3
150
+
151
+ #Check for missing description
152
+ raise ParseError, "Missing Gitweb description for repo: #{repo}" if description.nil?
153
+
154
+ #Check for groups
155
+ raise ParseError, "Gitweb descriptions cannot be set for groups" if repo =~ /@.+/
156
+
157
+ if has_repo? repo
158
+ r = @repos[repo]
159
+ else
160
+ r = Repo.new(repo)
161
+ add_repo(r)
162
+ end
163
+
164
+ r.owner = owner
165
+ r.description = description
148
166
  when /^include "(.+)"/
167
+ #TODO: implement includes
149
168
  #ignore includes for now
150
169
  else
151
- puts "The following line cannot be processed:"
152
- puts "'#{line}'"
170
+ raise ParseError, "'#{line}' cannot be processed"
171
+ end
172
+ end
173
+ end
174
+
175
+ # Normalizes the various different input objects to Strings
176
+ def normalize_name(context, constant = nil)
177
+ case context
178
+ when constant
179
+ context.name
180
+ when Symbol
181
+ context.to_s
182
+ else
183
+ context
184
+ end
185
+ end
186
+
187
+ def method_missing(meth, *args, &block)
188
+ if meth.to_s =~ /normalize_(\w+)_name/
189
+ #Could use Object.const_get to figure out the constant here
190
+ #but for only two cases, this is more readable
191
+ case $1
192
+ when "repo"
193
+ normalize_name(args[0], Gitolite::Config::Repo)
194
+ when "group"
195
+ normalize_name(args[0], Gitolite::Config::Group)
153
196
  end
197
+ else
198
+ super
154
199
  end
155
200
  end
201
+
202
+ #Raised when something in a config fails to parse properly
203
+ class ParseError < RuntimeError
204
+ end
156
205
  end
157
206
  end