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.
@@ -0,0 +1,51 @@
1
+ module Gitolite
2
+ class Config
3
+ # Represents a group inside the gitolite configuration. The name and users
4
+ # options are all encapsulated in this class. All users are stored as
5
+ # Strings!
6
+ class Group
7
+ attr_accessor :name, :users
8
+
9
+ PREPEND_CHAR = '@'
10
+
11
+ def initialize(name)
12
+ # naively remove the prepend char
13
+ # I don't think you can have two of them in a group name
14
+ @name = name.gsub(PREPEND_CHAR, '')
15
+ @users = []
16
+ end
17
+
18
+ def empty!
19
+ @users.clear
20
+ end
21
+
22
+ def add_user(user)
23
+ return if has_user?(user)
24
+ @users.push(user.to_s).sort!
25
+ end
26
+
27
+ def add_users(*users)
28
+ fixed_users = users.flatten.map{ |u| u.to_s }
29
+ @users.concat(fixed_users).sort!.uniq!
30
+ end
31
+
32
+ def rm_user(user)
33
+ @users.delete(user.to_s)
34
+ end
35
+
36
+ def has_user?(user)
37
+ @users.include? user.to_s
38
+ end
39
+
40
+ def size
41
+ @users.length
42
+ end
43
+
44
+ def to_s
45
+ members = @users.join(' ')
46
+ name = "#{PREPEND_CHAR}#{@name}"
47
+ "#{name.ljust(20)}= #{members}\n"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,83 @@
1
+ require 'hashery'
2
+
3
+ module Gitolite
4
+ class Config
5
+ #Represents a repo inside the gitolite configuration. The name, permissions, and git config
6
+ #options are all encapsulated in this class
7
+ class Repo
8
+ ALLOWED_PERMISSIONS = /-|R|RW+?C?D?/
9
+
10
+ attr_accessor :permissions, :name, :config, :owner, :description
11
+
12
+ def initialize(name)
13
+ #Store the perm hash in a lambda since we have to create a new one on every deny rule
14
+ #The perm hash is stored as a 2D hash, with individual permissions being the first
15
+ #degree and individual refexes being the second degree. Both Hashes must respect order
16
+ @perm_hash_lambda = lambda { OrderedHash.new {|k,v| k[v] = OrderedHash.new{|k2, v2| k2[v2] = [] }} }
17
+ @permissions = Array.new.push(@perm_hash_lambda.call)
18
+
19
+ @name = name
20
+ @config = {} #git config
21
+ end
22
+
23
+ def clean_permissions
24
+ @permissions = Array.new.push(@perm_hash_lambda.call)
25
+ end
26
+
27
+ def add_permission(perm, refex = "", *users)
28
+ if perm =~ ALLOWED_PERMISSIONS
29
+ #Handle deny rules
30
+ if perm == '-'
31
+ @permissions.push(@perm_hash_lambda.call)
32
+ end
33
+
34
+ @permissions.last[perm][refex].concat users.flatten
35
+ @permissions.last[perm][refex].uniq!
36
+ else
37
+ raise InvalidPermissionError, "#{perm} is not in the allowed list of permissions!"
38
+ end
39
+ end
40
+
41
+ def set_git_config(key, value)
42
+ @config[key] = value
43
+ end
44
+
45
+ def unset_git_config(key)
46
+ @config.delete(key)
47
+ end
48
+
49
+ def to_s
50
+ repo = "repo #{@name}\n"
51
+
52
+ @permissions.each do |perm_hash|
53
+ perm_hash.each do |perm, list|
54
+ list.each do |refex, users|
55
+ repo += " " + perm.ljust(6) + refex.ljust(25) + "= " + users.join(' ') + "\n"
56
+ end
57
+ end
58
+ end
59
+
60
+ @config.each do |k, v|
61
+ repo += " config " + k + " = " + v + "\n"
62
+ end
63
+
64
+ repo
65
+ end
66
+
67
+ def gitweb_description
68
+ if @description.nil?
69
+ nil
70
+ else
71
+ desc = "#{@name} "
72
+ desc += "\"#{@owner}\" " unless @owner.nil?
73
+ desc += "= \"#{@description}\""
74
+ end
75
+ end
76
+
77
+ #Gets raised if a permission that isn't in the allowed
78
+ #list is passed in
79
+ class InvalidPermissionError < ArgumentError
80
+ end
81
+ end
82
+ end
83
+ end
@@ -6,8 +6,8 @@ module Gitolite
6
6
  CONFDIR = "conf"
7
7
  KEYDIR = "keydir"
8
8
 
9
- #Intialize with the path to
10
- #the gitolite-admin repository
9
+ # Intialize with the path to
10
+ # the gitolite-admin repository
11
11
  def initialize(path, options = {})
12
12
  @gl_admin = Grit::Repo.new(path)
13
13
 
@@ -19,38 +19,78 @@ module Gitolite
19
19
  @config = Config.new(File.join(path, @confdir, @conf))
20
20
  end
21
21
 
22
- #Writes all aspects out to the file system
23
- #will also stage all changes
24
- def save
25
- Dir.chdir(@gl_admin.working_dir)
22
+ # This method will bootstrap a gitolite-admin repo
23
+ # at the given path. A typical gitolite-admin
24
+ # repo will have the following tree:
25
+ #
26
+ # gitolite-admin
27
+ # conf
28
+ # gitolite.conf
29
+ # keydir
30
+ def self.bootstrap(path, options = {})
31
+ if self.is_gitolite_admin_repo?(path)
32
+ if options[:overwrite]
33
+ FileUtils.rm_rf(File.join(path, '*'))
34
+ else
35
+ return self.new(path)
36
+ end
37
+ end
26
38
 
27
- #Process config file
28
- new_conf = @config.to_file(@confdir)
29
- @gl_admin.add(new_conf)
39
+ FileUtils.mkdir_p([File.join(path,"conf"), File.join(path,"keydir")])
30
40
 
31
- #Process ssh keys
32
- files = list_keys(@keydir).map{|f| File.basename f}
33
- keys = @ssh_keys.values.map{|f| f.map {|t| t.filename}}.flatten
41
+ options[:perm] ||= "RW+"
42
+ options[:refex] ||= ""
43
+ options[:user] ||= "git"
34
44
 
35
- to_remove = (files - keys).map { |f| File.join(@keydir, f)}
36
- @gl_admin.remove(to_remove)
45
+ c = Config.init
46
+ r = Config::Repo.new(options[:repo] || "gitolite-admin")
47
+ r.add_permission(options[:perm], options[:refex], options[:user])
48
+ c.add_repo(r)
49
+ config = c.to_file(File.join(path, "conf"))
50
+
51
+ repo = Grit::Repo.init(path)
52
+ Dir.chdir(path) do
53
+ repo.add(config)
54
+ repo.commit_index(options[:message] || "Config bootstrapped by the gitolite gem")
55
+ end
56
+
57
+ self.new(path)
58
+ end
37
59
 
38
- @ssh_keys.each_value do |key|
39
- key.each do |k|
40
- @gl_admin.add(k.to_file(@keydir))
60
+ #Writes all aspects out to the file system
61
+ #will also stage all changes
62
+ def save
63
+ Dir.chdir(@gl_admin.working_dir) do
64
+ #Process config file
65
+ new_conf = @config.to_file(@confdir)
66
+ @gl_admin.add(new_conf)
67
+
68
+ #Process ssh keys
69
+ files = list_keys(@keydir).map{|f| File.basename f}
70
+ keys = @ssh_keys.values.map{|f| f.map {|t| t.filename}}.flatten
71
+
72
+ to_remove = (files - keys).map { |f| File.join(@keydir, f)}
73
+ @gl_admin.remove(to_remove)
74
+
75
+ @ssh_keys.each_value do |key|
76
+ key.each do |k|
77
+ @gl_admin.add(k.to_file(@keydir))
78
+ end
41
79
  end
42
80
  end
43
81
  end
44
82
 
45
83
  #commits all staged changes and pushes back
46
84
  #to origin
47
- def apply
48
- #TODO: generate a better commit message
49
- @gl_admin.commit_index("Commit by gitolite gem")
85
+ #
86
+ #TODO: generate a better commit message
87
+ #TODO: add the ability to specify the remote and branch
88
+ #TODO: detect existance of origin instead of just dying
89
+ def apply(commit_message = "Commit by gitolite gem")
90
+ @gl_admin.commit_index(commit_message)
50
91
  @gl_admin.git.push({}, "origin", "master")
51
92
  end
52
93
 
53
- #Calls save and apply in order
54
94
  def save_and_apply
55
95
  self.save
56
96
  self.apply
@@ -65,6 +105,24 @@ module Gitolite
65
105
  @ssh_keys[key.owner].delete key
66
106
  end
67
107
 
108
+ #Checks to see if the given path is a gitolite-admin repository
109
+ #A valid repository contains a conf folder, keydir folder,
110
+ #and a configuration file within the conf folder
111
+ def self.is_gitolite_admin_repo?(dir)
112
+ # First check if it is a git repository
113
+ begin
114
+ Grit::Repo.new(dir)
115
+ rescue Grit::InvalidGitRepositoryError
116
+ return false
117
+ end
118
+
119
+ # If we got here it is a valid git repo,
120
+ # now check directory structure
121
+ File.exists?(File.join(dir, 'conf')) &&
122
+ File.exists?(File.join(dir, 'keydir')) &&
123
+ !Dir.glob(File.join(dir, 'conf', '*.conf')).empty?
124
+ end
125
+
68
126
  private
69
127
  #Loads all .pub files in the gitolite-admin
70
128
  #keydir directory
@@ -82,11 +140,10 @@ module Gitolite
82
140
  end
83
141
 
84
142
  def list_keys(path)
85
- old_path = Dir.pwd
86
- Dir.chdir(path)
87
- keys = Dir.glob("**/*.pub")
88
- Dir.chdir(old_path)
89
- keys
143
+ Dir.chdir(path) do
144
+ keys = Dir.glob("**/*.pub")
145
+ keys
146
+ end
90
147
  end
91
148
  end
92
149
  end
@@ -25,7 +25,7 @@ module Gitolite
25
25
  raise "#{key} does not exist!" unless File.exists?(key)
26
26
 
27
27
  #Get our owner and location
28
- File.basename(key) =~ /^(\w+(?:@(?:\w+\.)+\D{2,4})?)(?:@(\w+))?.pub$/i
28
+ File.basename(key) =~ /^([\w\.-]+(?:@(?:[\w-]+\.)+\D{2,4})?)(?:@(\w+))?.pub$/i
29
29
  owner = $1
30
30
  location = $2 || ""
31
31
 
@@ -47,7 +47,7 @@ module Gitolite
47
47
  def to_file(path)
48
48
  key_file = File.join(path, self.filename)
49
49
  File.open(key_file, "w") do |f|
50
- f.write (self.to_s)
50
+ f.write(self.to_s)
51
51
  end
52
52
  key_file
53
53
  end
@@ -1,3 +1,3 @@
1
1
  module Gitolite
2
- VERSION = "0.0.2.alpha"
2
+ VERSION = "0.0.3.alpha"
3
3
  end
@@ -0,0 +1,2 @@
1
+ #require 'gitolite'
2
+ #require 'spec_helper'
@@ -1,4 +1,357 @@
1
1
  require 'gitolite/config'
2
+ require 'spec_helper'
2
3
 
3
4
  describe Gitolite::Config do
4
- end
5
+ conf_dir = File.join(File.dirname(__FILE__),'configs')
6
+
7
+ describe "#new" do
8
+ it 'should read a simple configuration' do
9
+ c = Gitolite::Config.new(File.join(conf_dir, 'simple.conf'))
10
+ c.repos.length.should == 2
11
+ c.groups.length.should == 0
12
+ end
13
+
14
+ it 'should read a complex configuration' do
15
+ c = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf'))
16
+ c.groups.length.should == 5
17
+ c.repos.length.should == 12
18
+ end
19
+
20
+ describe 'gitweb operations' do
21
+ before :all do
22
+ @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf'))
23
+ end
24
+
25
+ it 'should correctly read gitweb options for an existing repo' do
26
+ r = @config.get_repo('gitolite')
27
+ r.owner.should == "Sitaram Chamarty"
28
+ r.description.should == "fast, secure, access control for git in a corporate environment"
29
+ end
30
+
31
+ it 'should correctly read a gitweb option with no owner for an existing repo' do
32
+ r = @config.get_repo('foo')
33
+ r.owner.should be nil
34
+ r.description.should == "Foo is a nice test repo"
35
+ end
36
+
37
+ it 'should correctly read gitweb options for a new repo' do
38
+ r = @config.get_repo('foobar')
39
+ r.owner.should == "Bob Zilla"
40
+ r.description.should == "Foobar is top secret"
41
+ end
42
+
43
+ it 'should correctly read gitweb options with no owner for a new repo' do
44
+ r = @config.get_repo('bar')
45
+ r.owner.should be nil
46
+ r.description.should == "A nice place to get drinks"
47
+ end
48
+
49
+ it 'should raise a ParseError when a description is not specified' do
50
+ t = Tempfile.new('bad_conf.conf')
51
+ t.write('gitolite "Bob Zilla"')
52
+ t.close
53
+
54
+ lambda { Gitolite::Config.new(t.path) }.should raise_error(Gitolite::Config::ParseError)
55
+
56
+ t.unlink
57
+ end
58
+
59
+ it 'should raise a ParseError when a Gitweb description is specified for a group' do
60
+ t = Tempfile.new('bad_conf.conf')
61
+ t.write('@gitolite "Bob Zilla" = "Test description"')
62
+ t.close
63
+
64
+ lambda { Gitolite::Config.new(t.path) }.should raise_error(Gitolite::Config::ParseError)
65
+
66
+ t.unlink
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#init" do
72
+ it 'should create a valid, blank Gitolite::Config' do
73
+ c = Gitolite::Config.init
74
+
75
+ c.should be_an_instance_of Gitolite::Config
76
+ c.repos.should_not be nil
77
+ c.repos.length.should be 0
78
+ c.groups.should_not be nil
79
+ c.groups.length.should be 0
80
+ c.filename.should == "gitolite.conf"
81
+ end
82
+
83
+ it 'should create a valid, blank Gitolite::Config with the given filename' do
84
+ filename = "test.conf"
85
+ c = Gitolite::Config.init(filename)
86
+
87
+ c.should be_an_instance_of Gitolite::Config
88
+ c.repos.should_not be nil
89
+ c.repos.length.should be 0
90
+ c.groups.should_not be nil
91
+ c.groups.length.should be 0
92
+ c.filename.should == filename
93
+ end
94
+ end
95
+
96
+ describe "repo management" do
97
+ before :each do
98
+ @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf'))
99
+ end
100
+
101
+ describe "#get_repo" do
102
+ it 'should fetch a repo by a string containing the name' do
103
+ @config.get_repo('gitolite').should be_an_instance_of Gitolite::Config::Repo
104
+ end
105
+
106
+ it 'should fetch a repo via a symbol representing the name' do
107
+ @config.get_repo(:gitolite).should be_an_instance_of Gitolite::Config::Repo
108
+ end
109
+
110
+ it 'should return nil for a repo that does not exist' do
111
+ @config.get_repo(:glite).should be nil
112
+ end
113
+ end
114
+
115
+ describe "#has_repo?" do
116
+ it 'should return false for a repo that does not exist' do
117
+ @config.has_repo?(:glite).should be false
118
+ end
119
+
120
+ it 'should check for the existance of a repo given a repo object' do
121
+ r = @config.repos["gitolite"]
122
+ @config.has_repo?(r).should be true
123
+ end
124
+
125
+ it 'should check for the existance of a repo given a string containing the name' do
126
+ @config.has_repo?('gitolite').should be true
127
+ end
128
+
129
+ it 'should check for the existance of a repo given a symbol representing the name' do
130
+ @config.has_repo?(:gitolite).should be true
131
+ end
132
+ end
133
+
134
+ describe "#add_repo" do
135
+ it 'should throw an ArgumentError for non-Gitolite::Config::Repo objects passed in' do
136
+ lambda{ @config.add_repo("not-a-repo") }.should raise_error(ArgumentError)
137
+ end
138
+
139
+ it 'should add a given repo to the list of repos' do
140
+ r = Gitolite::Config::Repo.new('cool_repo')
141
+ nrepos = @config.repos.size
142
+ @config.add_repo(r)
143
+
144
+ @config.repos.size.should == nrepos + 1
145
+ @config.has_repo?(:cool_repo).should be true
146
+ end
147
+
148
+ it 'should merge a given repo with an existing repo' do
149
+ #Make two new repos
150
+ repo1 = Gitolite::Config::Repo.new('cool_repo')
151
+ repo2 = Gitolite::Config::Repo.new('cool_repo')
152
+
153
+ #Add some perms to those repos
154
+ repo1.add_permission("RW+", "", "bob", "joe", "sam")
155
+ repo1.add_permission("R", "", "sue", "jen", "greg")
156
+ repo1.add_permission("-", "refs/tags/test[0-9]", "@students", "jessica")
157
+ repo1.add_permission("RW", "refs/tags/test[0-9]", "@teachers", "bill", "todd")
158
+ repo1.add_permission("R", "refs/tags/test[0-9]", "@profs")
159
+
160
+ repo2.add_permission("RW+", "", "jim", "cynthia", "arnold")
161
+ repo2.add_permission("R", "", "daniel", "mary", "ben")
162
+ repo2.add_permission("-", "refs/tags/test[0-9]", "@more_students", "stephanie")
163
+ repo2.add_permission("RW", "refs/tags/test[0-9]", "@student_teachers", "mike", "judy")
164
+ repo2.add_permission("R", "refs/tags/test[0-9]", "@leaders")
165
+
166
+ #Add the repos
167
+ @config.add_repo(repo1)
168
+ @config.add_repo(repo2)
169
+
170
+ #Make sure perms were properly merged
171
+ end
172
+
173
+ it 'should overwrite an existing repo when overwrite = true' do
174
+ #Make two new repos
175
+ repo1 = Gitolite::Config::Repo.new('cool_repo')
176
+ repo2 = Gitolite::Config::Repo.new('cool_repo')
177
+
178
+ #Add some perms to those repos
179
+ repo1.add_permission("RW+", "", "bob", "joe", "sam")
180
+ repo1.add_permission("R", "", "sue", "jen", "greg")
181
+ repo2.add_permission("RW+", "", "jim", "cynthia", "arnold")
182
+ repo2.add_permission("R", "", "daniel", "mary", "ben")
183
+
184
+ #Add the repos
185
+ @config.add_repo(repo1)
186
+ @config.add_repo(repo2, true)
187
+
188
+ #Make sure repo2 overwrote repo1
189
+ end
190
+ end
191
+
192
+ describe "#rm_repo" do
193
+ it 'should remove a repo for the Gitolite::Config::Repo object given' do
194
+ r = @config.get_repo(:gitolite)
195
+ r2 = @config.rm_repo(r)
196
+ r2.name.should == r.name
197
+ r2.permissions.length.should == r.permissions.length
198
+ r2.owner.should == r.owner
199
+ r2.description.should == r.description
200
+ end
201
+
202
+ it 'should remove a repo given a string containing the name' do
203
+ r = @config.get_repo(:gitolite)
204
+ r2 = @config.rm_repo('gitolite')
205
+ r2.name.should == r.name
206
+ r2.permissions.length.should == r.permissions.length
207
+ r2.owner.should == r.owner
208
+ r2.description.should == r.description
209
+ end
210
+
211
+ it 'should remove a repo given a symbol representing the name' do
212
+ r = @config.get_repo(:gitolite)
213
+ r2 = @config.rm_repo(:gitolite)
214
+ r2.name.should == r.name
215
+ r2.permissions.length.should == r.permissions.length
216
+ r2.owner.should == r.owner
217
+ r2.description.should == r.description
218
+ end
219
+ end
220
+ end
221
+
222
+ describe "group management" do
223
+ before :each do
224
+ @config = Gitolite::Config.new(File.join(conf_dir, 'complicated.conf'))
225
+ end
226
+
227
+ describe "#has_group?" do
228
+ it 'should find the staff group using a symbol' do
229
+ @config.has_group?(:staff).should be true
230
+ end
231
+
232
+ it 'should find the staff group using a string' do
233
+ @config.has_group?('staff').should be true
234
+ end
235
+
236
+ it 'should find the staff group using a Gitolite::Config::Group object' do
237
+ g = Gitolite::Config::Group.new("staff")
238
+ @config.has_group?(g).should be true
239
+ end
240
+ end
241
+
242
+ describe "#get_group" do
243
+ it 'should return the Gitolite::Config::Group object for the group name String' do
244
+ g = @config.get_group("staff")
245
+ g.is_a?(Gitolite::Config::Group).should be true
246
+ g.size.should == 6
247
+ end
248
+
249
+ it 'should return the Gitolite::Config::Group object for the group name Symbol' do
250
+ g = @config.get_group(:staff)
251
+ g.is_a?(Gitolite::Config::Group).should be true
252
+ g.size.should == 6
253
+ end
254
+ end
255
+
256
+ describe "#add_group" do
257
+ it 'should throw an ArgumentError for non-Gitolite::Config::Group objects passed in' do
258
+ lambda{ @config.add_group("not-a-group") }.should raise_error(ArgumentError)
259
+ end
260
+
261
+ it 'should add a given group to the groups list' do
262
+ g = Gitolite::Config::Group.new('cool_group')
263
+ ngroups = @config.groups.size
264
+ @config.add_group(g)
265
+ @config.groups.size.should be ngroups + 1
266
+ @config.has_group?(:cool_group).should be true
267
+ end
268
+
269
+ end
270
+
271
+ describe "#rm_group" do
272
+ it 'should remove a group for the Gitolite::Config::Group object given' do
273
+ g = @config.get_group(:oss_repos)
274
+ g2 = @config.rm_group(g)
275
+ g.should_not be nil
276
+ g2.name.should == g.name
277
+ end
278
+
279
+ it 'should remove a group given a string containing the name' do
280
+ g = @config.get_group(:oss_repos)
281
+ g2 = @config.rm_group('oss_repos')
282
+ g2.name.should == g.name
283
+ end
284
+
285
+ it 'should remove a group given a symbol representing the name' do
286
+ g = @config.get_group(:oss_repos)
287
+ g2 = @config.rm_group(:oss_repos)
288
+ g2.name.should == g.name
289
+ end
290
+ end
291
+
292
+ end
293
+
294
+ describe "#to_file" do
295
+ it 'should create a file at the given path with the config\'s file name' do
296
+ c = Gitolite::Config.init
297
+ file = c.to_file('/tmp')
298
+ File.file?(File.join('/tmp', c.filename)).should be true
299
+ File.unlink(file)
300
+ end
301
+
302
+ it 'should create a file at the given path when a different filename is specified' do
303
+ filename = "test.conf"
304
+ c = Gitolite::Config.init
305
+ c.filename = filename
306
+ file = c.to_file('/tmp')
307
+ File.file?(File.join('/tmp', filename)).should be true
308
+ File.unlink(file)
309
+ end
310
+
311
+ it 'should raise an ArgumentError when an invalid path is specified' do
312
+ c = Gitolite::Config.init
313
+ lambda { c.to_file('/does/not/exist') }.should raise_error(ArgumentError)
314
+ end
315
+
316
+ it 'should raise an ArgumentError when a filename is specified in the path' do
317
+ c = Gitolite::Config.init
318
+ lambda{ c.to_file('/home/test.rb') }.should raise_error(ArgumentError)
319
+ end
320
+ end
321
+
322
+ describe "#cleanup_config_line" do
323
+ before(:each) do
324
+ @config = Gitolite::Config.init
325
+ end
326
+
327
+ it 'should remove comments' do
328
+ s = "#comment"
329
+ @config.instance_eval { cleanup_config_line(s) }.empty?.should == true
330
+ end
331
+
332
+ it 'should remove inline comments, keeping content before the comment' do
333
+ s = "blablabla #comment"
334
+ @config.instance_eval { cleanup_config_line(s) }.should == "blablabla"
335
+ end
336
+
337
+ it 'should pad = with spaces on each side' do
338
+ s = "bob=joe"
339
+ @config.instance_eval { cleanup_config_line(s) }.should == "bob = joe"
340
+ end
341
+
342
+ it 'should replace multiple space characters with a single space' do
343
+ s = "bob = joe"
344
+ @config.instance_eval { cleanup_config_line(s) }.should == "bob = joe"
345
+ end
346
+
347
+ it 'should cleanup whitespace at the beginning and end of lines' do
348
+ s = " bob = joe "
349
+ @config.instance_eval { cleanup_config_line(s) }.should == "bob = joe"
350
+ end
351
+
352
+ it 'should cleanup whitespace and comments effectively' do
353
+ s = " bob = joe #comment"
354
+ @config.instance_eval { cleanup_config_line(s) }.should == "bob = joe"
355
+ end
356
+ end
357
+ end