gitolite 0.0.2.alpha → 0.0.3.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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