mojombo-grit 0.8.0

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.
Files changed (55) hide show
  1. data/History.txt +6 -0
  2. data/Manifest.txt +53 -0
  3. data/README.txt +213 -0
  4. data/Rakefile +29 -0
  5. data/grit.gemspec +16 -0
  6. data/lib/grit/actor.rb +36 -0
  7. data/lib/grit/blob.rb +117 -0
  8. data/lib/grit/commit.rb +208 -0
  9. data/lib/grit/config.rb +44 -0
  10. data/lib/grit/diff.rb +70 -0
  11. data/lib/grit/errors.rb +7 -0
  12. data/lib/grit/git.rb +112 -0
  13. data/lib/grit/head.rb +92 -0
  14. data/lib/grit/lazy.rb +31 -0
  15. data/lib/grit/repo.rb +298 -0
  16. data/lib/grit/tag.rb +71 -0
  17. data/lib/grit/tree.rb +99 -0
  18. data/lib/mojombo-grit.rb +37 -0
  19. data/test/fixtures/blame +131 -0
  20. data/test/fixtures/cat_file_blob +1 -0
  21. data/test/fixtures/cat_file_blob_size +1 -0
  22. data/test/fixtures/diff_2 +54 -0
  23. data/test/fixtures/diff_2f +19 -0
  24. data/test/fixtures/diff_f +15 -0
  25. data/test/fixtures/diff_i +201 -0
  26. data/test/fixtures/diff_mode_only +1152 -0
  27. data/test/fixtures/diff_new_mode +17 -0
  28. data/test/fixtures/diff_p +610 -0
  29. data/test/fixtures/for_each_ref +0 -0
  30. data/test/fixtures/for_each_ref_tags +0 -0
  31. data/test/fixtures/ls_tree_a +7 -0
  32. data/test/fixtures/ls_tree_b +2 -0
  33. data/test/fixtures/ls_tree_commit +3 -0
  34. data/test/fixtures/rev_list +26 -0
  35. data/test/fixtures/rev_list_count +655 -0
  36. data/test/fixtures/rev_list_single +7 -0
  37. data/test/fixtures/rev_parse +1 -0
  38. data/test/fixtures/show_empty_commit +6 -0
  39. data/test/fixtures/simple_config +2 -0
  40. data/test/helper.rb +17 -0
  41. data/test/profile.rb +21 -0
  42. data/test/suite.rb +6 -0
  43. data/test/test_actor.rb +35 -0
  44. data/test/test_blob.rb +74 -0
  45. data/test/test_commit.rb +182 -0
  46. data/test/test_config.rb +58 -0
  47. data/test/test_diff.rb +18 -0
  48. data/test/test_git.rb +52 -0
  49. data/test/test_head.rb +22 -0
  50. data/test/test_real.rb +17 -0
  51. data/test/test_reality.rb +17 -0
  52. data/test/test_repo.rb +270 -0
  53. data/test/test_tag.rb +29 -0
  54. data/test/test_tree.rb +91 -0
  55. metadata +128 -0
@@ -0,0 +1,44 @@
1
+ module Grit
2
+
3
+ class Config
4
+ def initialize(repo)
5
+ @repo = repo
6
+ end
7
+
8
+ def []=(key, value)
9
+ @repo.git.config({}, key, value)
10
+ @data = nil
11
+ end
12
+
13
+ def [](key)
14
+ data[key]
15
+ end
16
+
17
+ def fetch(key, default = nil)
18
+ data[key] || default || raise(IndexError.new("key not found"))
19
+ end
20
+
21
+ def keys
22
+ data.keys
23
+ end
24
+
25
+ protected
26
+ def data
27
+ @data ||= load_config
28
+ end
29
+
30
+ def load_config
31
+ hash = {}
32
+ config_lines.map do |line|
33
+ key, value = line.split(/=/, 2)
34
+ hash[key] = value
35
+ end
36
+ hash
37
+ end
38
+
39
+ def config_lines
40
+ @repo.git.config(:list => true).split(/\n/)
41
+ end
42
+ end # Config
43
+
44
+ end # Grit
data/lib/grit/diff.rb ADDED
@@ -0,0 +1,70 @@
1
+ module Grit
2
+
3
+ class Diff
4
+ attr_reader :a_path, :b_path
5
+ attr_reader :a_commit, :b_commit
6
+ attr_reader :a_mode, :b_mode
7
+ attr_reader :new_file, :deleted_file
8
+ attr_reader :diff
9
+
10
+ def initialize(repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, diff)
11
+ @repo = repo
12
+ @a_path = a_path
13
+ @b_path = b_path
14
+ @a_commit = a_commit =~ /^0{40}$/ ? nil : Commit.create(repo, :id => a_commit)
15
+ @b_commit = b_commit =~ /^0{40}$/ ? nil : Commit.create(repo, :id => b_commit)
16
+ @a_mode = a_mode
17
+ @b_mode = b_mode
18
+ @new_file = new_file
19
+ @deleted_file = deleted_file
20
+ @diff = diff
21
+ end
22
+
23
+ def self.list_from_string(repo, text)
24
+ lines = text.split("\n")
25
+
26
+ diffs = []
27
+
28
+ while !lines.empty?
29
+ m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(.+?) b/(.+)$})
30
+
31
+ if lines.first =~ /^old mode/
32
+ m, a_mode = *lines.shift.match(/^old mode (\d+)/)
33
+ m, b_mode = *lines.shift.match(/^new mode (\d+)/)
34
+ end
35
+
36
+ if lines.empty? || lines.first =~ /^diff --git/
37
+ diffs << Diff.new(repo, a_path, b_path, nil, nil, a_mode, b_mode, false, false, nil)
38
+ next
39
+ end
40
+
41
+ new_file = false
42
+ deleted_file = false
43
+
44
+ if lines.first =~ /^new file/
45
+ m, b_mode = lines.shift.match(/^new file mode (.+)$/)
46
+ a_mode = nil
47
+ new_file = true
48
+ elsif lines.first =~ /^deleted file/
49
+ m, a_mode = lines.shift.match(/^deleted file mode (.+)$/)
50
+ b_mode = nil
51
+ deleted_file = true
52
+ end
53
+
54
+ m, a_commit, b_commit, b_mode = *lines.shift.match(%r{^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$})
55
+ b_mode.strip! if b_mode
56
+
57
+ diff_lines = []
58
+ while lines.first && lines.first !~ /^diff/
59
+ diff_lines << lines.shift
60
+ end
61
+ diff = diff_lines.join("\n")
62
+
63
+ diffs << Diff.new(repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, diff)
64
+ end
65
+
66
+ diffs
67
+ end
68
+ end # Diff
69
+
70
+ end # Grit
@@ -0,0 +1,7 @@
1
+ module Grit
2
+ class InvalidGitRepositoryError < StandardError
3
+ end
4
+
5
+ class NoSuchPathError < StandardError
6
+ end
7
+ end
data/lib/grit/git.rb ADDED
@@ -0,0 +1,112 @@
1
+ trap("CHLD") do
2
+ begin
3
+ Process.wait(-1, Process::WNOHANG)
4
+ rescue Object
5
+ end
6
+ end
7
+
8
+ module Grit
9
+
10
+ class Git
11
+ class GitTimeout < RuntimeError
12
+ attr_reader :command, :bytes_read
13
+
14
+ def initialize(command = nil, bytes_read = nil)
15
+ @command = command
16
+ @bytes_read = bytes_read
17
+ end
18
+ end
19
+
20
+ undef_method :clone
21
+
22
+ class << self
23
+ attr_accessor :git_binary, :git_timeout
24
+ end
25
+
26
+ self.git_binary = "/usr/bin/env git"
27
+ self.git_timeout = 5
28
+
29
+ attr_accessor :git_dir, :bytes_read
30
+
31
+ def initialize(git_dir)
32
+ self.git_dir = git_dir
33
+ self.bytes_read = 0
34
+ end
35
+
36
+ # Run the given git command with the specified arguments and return
37
+ # the result as a String
38
+ # +cmd+ is the command
39
+ # +options+ is a hash of Ruby style options
40
+ # +args+ is the list of arguments (to be joined by spaces)
41
+ #
42
+ # Examples
43
+ # git.rev_list({:max_count => 10, :header => true}, "master")
44
+ #
45
+ # Returns String
46
+ def method_missing(cmd, options = {}, *args)
47
+ timeout = options.delete(:timeout)
48
+ timeout = true if timeout.nil?
49
+
50
+ opt_args = transform_options(options)
51
+ ext_args = args.map { |a| a == '--' ? a : "'#{a}'" }
52
+
53
+ call = "#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}"
54
+ puts call if Grit.debug
55
+ response = timeout ? sh(call) : wild_sh(call)
56
+ puts response if Grit.debug
57
+ response
58
+ end
59
+
60
+ def sh(command)
61
+ pid, _, io, _ = Open4.popen4(command)
62
+ ret = Timeout.timeout(self.class.git_timeout) { io.read }
63
+ @bytes_read += ret.size
64
+
65
+ if @bytes_read > 5242880 # 5.megabytes
66
+ bytes = @bytes_read
67
+ @bytes_read = 0
68
+ raise GitTimeout.new(command, bytes)
69
+ end
70
+
71
+ ret
72
+ rescue Object => e
73
+ Process.kill('KILL', pid) rescue nil
74
+ bytes = @bytes_read
75
+ @bytes_read = 0
76
+ raise GitTimeout.new(command, bytes)
77
+ end
78
+
79
+ def wild_sh(command)
80
+ pid, _, io, _ = Open4.popen4(command)
81
+ io.read
82
+ end
83
+
84
+ # Transform Ruby style options into git command line options
85
+ # +options+ is a hash of Ruby style options
86
+ #
87
+ # Returns String[]
88
+ # e.g. ["--max-count=10", "--header"]
89
+ def transform_options(options)
90
+ args = []
91
+ options.keys.each do |opt|
92
+ if opt.to_s.size == 1
93
+ if options[opt] == true
94
+ args << "-#{opt}"
95
+ else
96
+ val = options.delete(opt)
97
+ args << "-#{opt.to_s} '#{val}'"
98
+ end
99
+ else
100
+ if options[opt] == true
101
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
102
+ else
103
+ val = options.delete(opt)
104
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{val}'"
105
+ end
106
+ end
107
+ end
108
+ args
109
+ end
110
+ end # Git
111
+
112
+ end # Grit
data/lib/grit/head.rb ADDED
@@ -0,0 +1,92 @@
1
+ module Grit
2
+ HEAD_PREFIX = 'refs/heads'
3
+
4
+ # A Head is a named reference to a Commit. Every Head instance contains a name
5
+ # and a Commit object.
6
+ #
7
+ # r = Grit::Repo.new("/path/to/repo")
8
+ # h = r.heads.first
9
+ # h.name # => "master"
10
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
11
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
12
+ class Head
13
+ attr_reader :name
14
+ attr_reader :commit
15
+
16
+ # Instantiate a new Head
17
+ # +name+ is the name of the head
18
+ # +commit+ is the Commit that the head points to
19
+ #
20
+ # Returns Grit::Head (baked)
21
+ def initialize(name, commit)
22
+ @name = name
23
+ @commit = commit
24
+ end
25
+
26
+ # Find all Heads
27
+ # +repo+ is the Repo
28
+ # +options+ is a Hash of options
29
+ #
30
+ # Returns Grit::Head[] (baked)
31
+ def self.find_all(repo, options = {})
32
+ default_options = {:sort => "committerdate",
33
+ :format => "%(refname)%00%(objectname)"}
34
+
35
+ actual_options = default_options.merge(options)
36
+
37
+ output = repo.git.for_each_ref(actual_options, HEAD_PREFIX)
38
+
39
+ self.list_from_string(repo, output)
40
+ end
41
+
42
+ # Get the HEAD revision of the repo.
43
+ # +repo+ is the Repo
44
+ # +options+ is a Hash of options
45
+ #
46
+ # Returns Grit::Head (baked)
47
+ def self.current(repo, options = {})
48
+ head = File.open(File.join(repo.path, 'HEAD')).read.chomp
49
+ if /ref: refs\/heads\/(.*)/.match(head)
50
+ self.new($1, repo.git.rev_parse(options, 'HEAD'))
51
+ end
52
+ end
53
+
54
+ # Parse out head information into an array of baked head objects
55
+ # +repo+ is the Repo
56
+ # +text+ is the text output from the git command
57
+ #
58
+ # Returns Grit::Head[] (baked)
59
+ def self.list_from_string(repo, text)
60
+ heads = []
61
+
62
+ text.split("\n").each do |line|
63
+ heads << self.from_string(repo, line)
64
+ end
65
+
66
+ heads
67
+ end
68
+
69
+ # Create a new Head instance from the given string.
70
+ # +repo+ is the Repo
71
+ # +line+ is the formatted head information
72
+ #
73
+ # Format
74
+ # name: [a-zA-Z_/]+
75
+ # <null byte>
76
+ # id: [0-9A-Fa-f]{40}
77
+ #
78
+ # Returns Grit::Head (baked)
79
+ def self.from_string(repo, line)
80
+ full_name, id = line.split("\0")
81
+ name = full_name.sub("#{HEAD_PREFIX}/", '')
82
+ commit = Commit.create(repo, :id => id)
83
+ self.new(name, commit)
84
+ end
85
+
86
+ # Pretty object inspection
87
+ def inspect
88
+ %Q{#<Grit::Head "#{@name}">}
89
+ end
90
+ end # Head
91
+
92
+ end # Grit
data/lib/grit/lazy.rb ADDED
@@ -0,0 +1,31 @@
1
+ ##
2
+ # Allows attributes to be declared as lazy, meaning that they won't be
3
+ # computed until they are asked for.
4
+ #
5
+ # Works by delegating each lazy_reader to a cached lazy_source method.
6
+ #
7
+ # class Person
8
+ # lazy_reader :eyes
9
+ #
10
+ # def lazy_source
11
+ # OpenStruct.new(:eyes => 2)
12
+ # end
13
+ # end
14
+ #
15
+ # >> Person.new.eyes
16
+ # => 2
17
+ #
18
+ module Lazy
19
+ def lazy_reader(*args)
20
+ args.each do |arg|
21
+ ivar = "@#{arg}"
22
+ define_method(arg) do
23
+ val = instance_variable_get(ivar)
24
+ return val if val
25
+ instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Object.extend Lazy unless Object.ancestors.include? Lazy
data/lib/grit/repo.rb ADDED
@@ -0,0 +1,298 @@
1
+ module Grit
2
+
3
+ class Repo
4
+ DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
5
+
6
+ # The path of the git repo as a String
7
+ attr_accessor :path
8
+ attr_reader :bare
9
+
10
+ # The git command line interface object
11
+ attr_accessor :git
12
+
13
+ # Create a new Repo instance
14
+ # +path+ is the path to either the root git directory or the bare git repo
15
+ #
16
+ # Examples
17
+ # g = Repo.new("/Users/tom/dev/grit")
18
+ # g = Repo.new("/Users/tom/public/grit.git")
19
+ #
20
+ # Returns Grit::Repo
21
+ def initialize(path)
22
+ epath = File.expand_path(path)
23
+
24
+ if File.exist?(File.join(epath, '.git'))
25
+ self.path = File.join(epath, '.git')
26
+ @bare = false
27
+ elsif File.exist?(epath) && epath =~ /\.git$/
28
+ self.path = epath
29
+ @bare = true
30
+ elsif File.exist?(epath)
31
+ raise InvalidGitRepositoryError.new(epath)
32
+ else
33
+ raise NoSuchPathError.new(epath)
34
+ end
35
+
36
+ self.git = Git.new(self.path)
37
+ end
38
+
39
+ # The project's description. Taken verbatim from GIT_REPO/description
40
+ #
41
+ # Returns String
42
+ def description
43
+ File.open(File.join(self.path, 'description')).read.chomp
44
+ end
45
+
46
+ # An array of Head objects representing the branch heads in
47
+ # this repo
48
+ #
49
+ # Returns Grit::Head[] (baked)
50
+ def heads
51
+ Head.find_all(self)
52
+ end
53
+
54
+ alias_method :branches, :heads
55
+
56
+ # Object reprsenting the current repo head.
57
+ #
58
+ # Returns Grit::Head (baked)
59
+ def head
60
+ Head.current(self)
61
+ end
62
+
63
+ # An array of Tag objects that are available in this repo
64
+ #
65
+ # Returns Grit::Tag[] (baked)
66
+ def tags
67
+ Tag.find_all(self)
68
+ end
69
+
70
+ # An array of Commit objects representing the history of a given ref/commit
71
+ # +start+ is the branch/commit name (default 'master')
72
+ # +max_count+ is the maximum number of commits to return (default 10)
73
+ # +skip+ is the number of commits to skip (default 0)
74
+ #
75
+ # Returns Grit::Commit[] (baked)
76
+ def commits(start = 'master', max_count = 10, skip = 0)
77
+ options = {:max_count => max_count,
78
+ :skip => skip}
79
+
80
+ Commit.find_all(self, start, options)
81
+ end
82
+
83
+ # The Commits objects that are reachable via +to+ but not via +from+
84
+ # Commits are returned in chronological order.
85
+ # +from+ is the branch/commit name of the younger item
86
+ # +to+ is the branch/commit name of the older item
87
+ #
88
+ # Returns Grit::Commit[] (baked)
89
+ def commits_between(from, to)
90
+ Commit.find_all(self, "#{from}..#{to}").reverse
91
+ end
92
+
93
+ # The Commits objects that are newer than the specified date.
94
+ # Commits are returned in chronological order.
95
+ # +start+ is the branch/commit name (default 'master')
96
+ # +since+ is a string represeting a date/time
97
+ # +extra_options+ is a hash of extra options
98
+ #
99
+ # Returns Grit::Commit[] (baked)
100
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
101
+ options = {:since => since}.merge(extra_options)
102
+
103
+ Commit.find_all(self, start, options)
104
+ end
105
+
106
+ # The number of commits reachable by the given branch/commit
107
+ # +start+ is the branch/commit name (default 'master')
108
+ #
109
+ # Returns Integer
110
+ def commit_count(start = 'master')
111
+ Commit.count(self, start)
112
+ end
113
+
114
+ # The Commit object for the specified id
115
+ # +id+ is the SHA1 identifier of the commit
116
+ #
117
+ # Returns Grit::Commit (baked)
118
+ def commit(id)
119
+ options = {:max_count => 1}
120
+
121
+ Commit.find_all(self, id, options).first
122
+ end
123
+
124
+ # The Tree object for the given treeish reference
125
+ # +treeish+ is the reference (default 'master')
126
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
127
+ #
128
+ # Examples
129
+ # repo.tree('master', ['lib/'])
130
+ #
131
+ # Returns Grit::Tree (baked)
132
+ def tree(treeish = 'master', paths = [])
133
+ Tree.construct(self, treeish, paths)
134
+ end
135
+
136
+ # The Blob object for the given id
137
+ # +id+ is the SHA1 id of the blob
138
+ #
139
+ # Returns Grit::Blob (unbaked)
140
+ def blob(id)
141
+ Blob.create(self, :id => id)
142
+ end
143
+
144
+ # The commit log for a treeish
145
+ #
146
+ # Returns Grit::Commit[]
147
+ def log(commit = 'master', path = nil, options = {})
148
+ default_options = {:pretty => "raw"}
149
+ actual_options = default_options.merge(options)
150
+ arg = path ? [commit, '--', path] : [commit]
151
+ commits = self.git.log(actual_options, *arg)
152
+ Commit.list_from_string(self, commits)
153
+ end
154
+
155
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
156
+ # +a+ is the base commit
157
+ # +b+ is the other commit
158
+ # +paths+ is an optional list of file paths on which to restrict the diff
159
+ def diff(a, b, *paths)
160
+ self.git.diff({}, a, b, '--', *paths)
161
+ end
162
+
163
+ # The commit diff for the given commit
164
+ # +commit+ is the commit name/id
165
+ #
166
+ # Returns Grit::Diff[]
167
+ def commit_diff(commit)
168
+ Commit.diff(self, commit)
169
+ end
170
+
171
+ # Initialize a bare git repository at the given path
172
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
173
+ # +options+ is any additional options to the git init command
174
+ #
175
+ # Examples
176
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
177
+ #
178
+ # Returns Grit::Repo (the newly created repo)
179
+ def self.init_bare(path, options = {})
180
+ git = Git.new(path)
181
+ git.init(options)
182
+ self.new(path)
183
+ end
184
+
185
+ # Fork a bare git repository from this repo
186
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
187
+ # +options+ is any additional options to the git clone command
188
+ #
189
+ # Returns Grit::Repo (the newly forked repo)
190
+ def fork_bare(path, options = {})
191
+ default_options = {:bare => true, :shared => true}
192
+ real_options = default_options.merge(options)
193
+ self.git.clone(real_options, self.path, path)
194
+ Repo.new(path)
195
+ end
196
+
197
+ # Archive the given treeish
198
+ # +treeish+ is the treeish name/id (default 'master')
199
+ # +prefix+ is the optional prefix
200
+ #
201
+ # Examples
202
+ # repo.archive_tar
203
+ # # => <String containing tar archive>
204
+ #
205
+ # repo.archive_tar('a87ff14')
206
+ # # => <String containing tar archive for commit a87ff14>
207
+ #
208
+ # repo.archive_tar('master', 'myproject/')
209
+ # # => <String containing tar archive and prefixed with 'myproject/'>
210
+ #
211
+ # Returns String (containing tar archive)
212
+ def archive_tar(treeish = 'master', prefix = nil)
213
+ options = {}
214
+ options[:prefix] = prefix if prefix
215
+ self.git.archive(options, treeish)
216
+ end
217
+
218
+ # Archive and gzip the given treeish
219
+ # +treeish+ is the treeish name/id (default 'master')
220
+ # +prefix+ is the optional prefix
221
+ #
222
+ # Examples
223
+ # repo.archive_tar_gz
224
+ # # => <String containing tar.gz archive>
225
+ #
226
+ # repo.archive_tar_gz('a87ff14')
227
+ # # => <String containing tar.gz archive for commit a87ff14>
228
+ #
229
+ # repo.archive_tar_gz('master', 'myproject/')
230
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
231
+ #
232
+ # Returns String (containing tar.gz archive)
233
+ def archive_tar_gz(treeish = 'master', prefix = nil)
234
+ options = {}
235
+ options[:prefix] = prefix if prefix
236
+ self.git.archive(options, treeish, "| gzip")
237
+ end
238
+
239
+ # Enable git-daemon serving of this repository by writing the
240
+ # git-daemon-export-ok file to its git directory
241
+ #
242
+ # Returns nothing
243
+ def enable_daemon_serve
244
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
245
+ end
246
+
247
+ # Disable git-daemon serving of this repository by ensuring there is no
248
+ # git-daemon-export-ok file in its git directory
249
+ #
250
+ # Returns nothing
251
+ def disable_daemon_serve
252
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
253
+ end
254
+
255
+ # The list of alternates for this repo
256
+ #
257
+ # Returns Array[String] (pathnames of alternates)
258
+ def alternates
259
+ alternates_path = File.join(self.path, *%w{objects info alternates})
260
+
261
+ if File.exist?(alternates_path)
262
+ File.read(alternates_path).strip.split("\n")
263
+ else
264
+ []
265
+ end
266
+ end
267
+
268
+ # Sets the alternates
269
+ # +alts+ is the Array of String paths representing the alternates
270
+ #
271
+ # Returns nothing
272
+ def alternates=(alts)
273
+ alts.each do |alt|
274
+ unless File.exist?(alt)
275
+ raise "Could not set alternates. Alternate path #{alt} must exist"
276
+ end
277
+ end
278
+
279
+ if alts.empty?
280
+ File.delete(File.join(self.path, *%w{objects info alternates}))
281
+ else
282
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
283
+ f.write alts.join("\n")
284
+ end
285
+ end
286
+ end
287
+
288
+ def config
289
+ @config ||= Config.new(self)
290
+ end
291
+
292
+ # Pretty object inspection
293
+ def inspect
294
+ %Q{#<Grit::Repo "#{@path}">}
295
+ end
296
+ end # Repo
297
+
298
+ end # Grit