robinluckey-grit 1.1.1

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,140 @@
1
+ module Grit
2
+
3
+ class Git
4
+ class GitTimeout < RuntimeError
5
+ attr_reader :command, :bytes_read
6
+
7
+ def initialize(command = nil, bytes_read = nil)
8
+ @command = command
9
+ @bytes_read = bytes_read
10
+ end
11
+ end
12
+
13
+ undef_method :clone
14
+
15
+ include GitRuby
16
+
17
+ class << self
18
+ attr_accessor :git_binary, :git_timeout, :git_max_size
19
+ end
20
+
21
+ self.git_binary = "/usr/bin/env git"
22
+ self.git_timeout = 10
23
+ self.git_max_size = 5242880 # 5.megabytes
24
+
25
+ def self.with_timeout(timeout = 10.seconds)
26
+ old_timeout = Grit::Git.git_timeout
27
+ Grit::Git.git_timeout = timeout
28
+ yield
29
+ Grit::Git.git_timeout = old_timeout
30
+ end
31
+
32
+ attr_accessor :git_dir, :bytes_read
33
+
34
+ def initialize(git_dir)
35
+ self.git_dir = git_dir
36
+ self.bytes_read = 0
37
+ end
38
+
39
+ def shell_escape(str)
40
+ str.to_s.gsub("'", "\\\\'").gsub(";", '\\;')
41
+ end
42
+ alias_method :e, :shell_escape
43
+
44
+ # Run the given git command with the specified arguments and return
45
+ # the result as a String
46
+ # +cmd+ is the command
47
+ # +options+ is a hash of Ruby style options
48
+ # +args+ is the list of arguments (to be joined by spaces)
49
+ #
50
+ # Examples
51
+ # git.rev_list({:max_count => 10, :header => true}, "master")
52
+ #
53
+ # Returns String
54
+ def method_missing(cmd, options = {}, *args)
55
+ run('', cmd, '', options, args)
56
+ end
57
+
58
+ def run(prefix, cmd, postfix, options, args)
59
+ timeout = options.delete(:timeout) rescue nil
60
+ timeout = true if timeout.nil?
61
+
62
+ opt_args = transform_options(options)
63
+ ext_args = args.reject { |a| a.empty? }.map { |a| (a == '--' || a[0].chr == '|') ? a : "'#{e(a)}'" }
64
+
65
+ call = "#{prefix}#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{e(postfix)}"
66
+ Grit.log(call) if Grit.debug
67
+ response, err = timeout ? sh(call) : wild_sh(call)
68
+ Grit.log(response) if Grit.debug
69
+ Grit.log(err) if Grit.debug
70
+ response
71
+ end
72
+
73
+ def sh(command)
74
+ ret, err = '', ''
75
+ Open3.popen3(command) do |_, stdout, stderr|
76
+ Timeout.timeout(self.class.git_timeout) do
77
+ while tmp = stdout.read(1024)
78
+ ret += tmp
79
+ if (@bytes_read += tmp.size) > self.class.git_max_size
80
+ bytes = @bytes_read
81
+ @bytes_read = 0
82
+ raise GitTimeout.new(command, bytes)
83
+ end
84
+ end
85
+ end
86
+
87
+ while tmp = stderr.read(1024)
88
+ err += tmp
89
+ end
90
+ end
91
+ [ret, err]
92
+ rescue Timeout::Error, Grit::Git::GitTimeout
93
+ bytes = @bytes_read
94
+ @bytes_read = 0
95
+ raise GitTimeout.new(command, bytes)
96
+ end
97
+
98
+ def wild_sh(command)
99
+ ret, err = '', ''
100
+ Open3.popen3(command) do |_, stdout, stderr|
101
+ while tmp = stdout.read(1024)
102
+ ret += tmp
103
+ end
104
+
105
+ while tmp = stderr.read(1024)
106
+ err += tmp
107
+ end
108
+ end
109
+ [ret, err]
110
+ end
111
+
112
+ # Transform Ruby style options into git command line options
113
+ # +options+ is a hash of Ruby style options
114
+ #
115
+ # Returns String[]
116
+ # e.g. ["--max-count=10", "--header"]
117
+ def transform_options(options)
118
+ args = []
119
+ options.keys.each do |opt|
120
+ if opt.to_s.size == 1
121
+ if options[opt] == true
122
+ args << "-#{opt}"
123
+ else
124
+ val = options.delete(opt)
125
+ args << "-#{opt.to_s} '#{e(val)}'"
126
+ end
127
+ else
128
+ if options[opt] == true
129
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
130
+ else
131
+ val = options.delete(opt)
132
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{e(val)}'"
133
+ end
134
+ end
135
+ end
136
+ args
137
+ end
138
+ end # Git
139
+
140
+ end # Grit
@@ -0,0 +1,122 @@
1
+ module Grit
2
+
3
+ class Index
4
+ attr_accessor :repo, :tree, :current_tree
5
+
6
+ def initialize(repo)
7
+ self.repo = repo
8
+ self.tree = {}
9
+ self.current_tree = nil
10
+ end
11
+
12
+ # Add a file to the index
13
+ # +path+ is the path (including filename)
14
+ # +data+ is the binary contents of the file
15
+ #
16
+ # Returns nothing
17
+ def add(file_path, data)
18
+ path = file_path.split('/')
19
+ filename = path.pop
20
+
21
+ current = self.tree
22
+
23
+ path.each do |dir|
24
+ current[dir] ||= {}
25
+ node = current[dir]
26
+ current = node
27
+ end
28
+
29
+ current[filename] = data
30
+ end
31
+
32
+ # Sets the current tree
33
+ # +tree+ the branch/tag/sha... to use - a string
34
+ #
35
+ # Returns index (self)
36
+ def read_tree(tree)
37
+ self.current_tree = self.repo.tree(tree)
38
+ end
39
+
40
+ # Commit the contents of the index
41
+ # +message+ is the commit message [nil]
42
+ # +parents+ is one or more commits to attach this commit to to form a new head [nil]
43
+ # +actor+ is the details of the user making the commit [nil]
44
+ # +last_tree+ is a tree to compare with - to avoid making empty commits [nil]
45
+ # +head+ is the branch to write this head to [master]
46
+ #
47
+ # Returns a String of the SHA1 of the commit
48
+ def commit(message, parents = nil, actor = nil, last_tree = nil, head = 'master')
49
+ tree_sha1 = write_tree(self.tree, self.current_tree)
50
+ return false if tree_sha1 == last_tree # don't write identical commits
51
+
52
+ contents = []
53
+ contents << ['tree', tree_sha1].join(' ')
54
+ parents.each do |p|
55
+ contents << ['parent', p].join(' ') if p
56
+ end if parents
57
+
58
+ if actor
59
+ name = actor.name
60
+ email = actor.email
61
+ else
62
+ config = Config.new(self.repo)
63
+ name = config['user.name']
64
+ email = config['user.email']
65
+ end
66
+
67
+ author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
68
+ contents << ['author', author_string].join(' ')
69
+ contents << ['committer', author_string].join(' ')
70
+ contents << ''
71
+ contents << message
72
+
73
+ commit_sha1 = self.repo.git.ruby_git.put_raw_object(contents.join("\n"), 'commit')
74
+
75
+ self.repo.update_ref(head, commit_sha1)
76
+ end
77
+
78
+ # Recursively write a tree to the index
79
+ # +tree+ is the tree
80
+ #
81
+ # Returns the SHA1 String of the tree
82
+ def write_tree(tree, now_tree = nil)
83
+ tree_contents = {}
84
+
85
+ # fill in original tree
86
+ now_tree.contents.each do |obj|
87
+ sha = [obj.id].pack("H*")
88
+ k = obj.name
89
+ k += '/' if (obj.class == Grit::Tree)
90
+ tree_contents[k] = "%s %s\0%s" % [obj.mode.to_s, obj.name, sha]
91
+ end if now_tree
92
+
93
+ # overwrite with new tree contents
94
+ tree.each do |k, v|
95
+ case v
96
+ when String
97
+ sha = write_blob(v)
98
+ sha = [sha].pack("H*")
99
+ str = "%s %s\0%s" % ['100644', k, sha]
100
+ tree_contents[k] = str
101
+ when Hash
102
+ ctree = now_tree/k if now_tree
103
+ sha = write_tree(v, ctree)
104
+ sha = [sha].pack("H*")
105
+ str = "%s %s\0%s" % ['040000', k, sha]
106
+ tree_contents[k + '/'] = str
107
+ end
108
+ end
109
+ tr = tree_contents.sort.map { |k, v| v }.join('')
110
+ self.repo.git.ruby_git.put_raw_object(tr, 'tree')
111
+ end
112
+
113
+ # Write the blob to the index
114
+ # +data+ is the data to write
115
+ #
116
+ # Returns the SHA1 String of the blob
117
+ def write_blob(data)
118
+ self.repo.git.ruby_git.put_raw_object(data, 'blob')
119
+ end
120
+ end # Index
121
+
122
+ end # Grit
@@ -0,0 +1,33 @@
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
+ if instance_variable_defined?(ivar)
24
+ val = instance_variable_get(ivar)
25
+ return val if val
26
+ end
27
+ instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Object.extend Lazy unless Object.ancestors.include? Lazy
@@ -0,0 +1,45 @@
1
+ module Grit
2
+
3
+ class Merge
4
+
5
+ STATUS_BOTH = 'both'
6
+ STATUS_OURS = 'ours'
7
+ STATUS_THEIRS = 'theirs'
8
+
9
+ attr_reader :conflicts, :text, :sections
10
+
11
+ def initialize(str)
12
+ status = STATUS_BOTH
13
+
14
+ section = 1
15
+ @conflicts = 0
16
+ @text = {}
17
+
18
+ lines = str.split("\n")
19
+ lines.each do |line|
20
+ if /^<<<<<<< (.*?)/.match(line)
21
+ status = STATUS_OURS
22
+ @conflicts += 1
23
+ section += 1
24
+ elsif line == '======='
25
+ status = STATUS_THEIRS
26
+ elsif /^>>>>>>> (.*?)/.match(line)
27
+ status = STATUS_BOTH
28
+ section += 1
29
+ else
30
+ @text[section] ||= {}
31
+ @text[section][status] ||= []
32
+ @text[section][status] << line
33
+ end
34
+ end
35
+ @text = @text.values
36
+ @sections = @text.size
37
+ end
38
+
39
+ # Pretty object inspection
40
+ def inspect
41
+ %Q{#<Grit::Merge}
42
+ end
43
+ end # Merge
44
+
45
+ end # Grit
@@ -0,0 +1,99 @@
1
+ module Grit
2
+
3
+ class Ref
4
+
5
+ class << self
6
+
7
+ # Find all Refs
8
+ # +repo+ is the Repo
9
+ # +options+ is a Hash of options
10
+ #
11
+ # Returns Grit::Ref[] (baked)
12
+ def find_all(repo, options = {})
13
+ refs = []
14
+ already = {}
15
+ Dir.chdir(repo.path) do
16
+ files = Dir.glob(prefix + '/**/*')
17
+ files.each do |ref|
18
+ next if !File.file?(ref)
19
+ id = File.read(ref).chomp
20
+ name = ref.sub("#{prefix}/", '')
21
+ commit = Commit.create(repo, :id => id)
22
+ if !already[name]
23
+ refs << self.new(name, commit)
24
+ already[name] = true
25
+ end
26
+ end
27
+
28
+ if File.file?('packed-refs')
29
+ File.readlines('packed-refs').each do |line|
30
+ if m = /^(\w{40}) (.*?)$/.match(line)
31
+ next if !Regexp.new('^' + prefix).match(m[2])
32
+ name = m[2].sub("#{prefix}/", '')
33
+ commit = Commit.create(repo, :id => m[1])
34
+ if !already[name]
35
+ refs << self.new(name, commit)
36
+ already[name] = true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ refs
44
+ end
45
+
46
+ protected
47
+
48
+ def prefix
49
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
50
+ end
51
+
52
+ end
53
+
54
+ attr_reader :name
55
+ attr_reader :commit
56
+
57
+ # Instantiate a new Head
58
+ # +name+ is the name of the head
59
+ # +commit+ is the Commit that the head points to
60
+ #
61
+ # Returns Grit::Head (baked)
62
+ def initialize(name, commit)
63
+ @name = name
64
+ @commit = commit
65
+ end
66
+
67
+ # Pretty object inspection
68
+ def inspect
69
+ %Q{#<#{self.class.name} "#{@name}">}
70
+ end
71
+ end # Ref
72
+
73
+ # A Head is a named reference to a Commit. Every Head instance contains a name
74
+ # and a Commit object.
75
+ #
76
+ # r = Grit::Repo.new("/path/to/repo")
77
+ # h = r.heads.first
78
+ # h.name # => "master"
79
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
80
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
81
+ class Head < Ref
82
+
83
+ # Get the HEAD revision of the repo.
84
+ # +repo+ is the Repo
85
+ # +options+ is a Hash of options
86
+ #
87
+ # Returns Grit::Head (baked)
88
+ def self.current(repo, options = {})
89
+ head = File.open(File.join(repo.path, 'HEAD')).read.chomp
90
+ if /ref: refs\/heads\/(.*)/.match(head)
91
+ self.new($1, repo.git.rev_parse(options, 'HEAD'))
92
+ end
93
+ end
94
+
95
+ end # Head
96
+
97
+ class Remote < Ref; end
98
+
99
+ end # Grit
@@ -0,0 +1,437 @@
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_accessor :working_dir
9
+ attr_reader :bare
10
+
11
+ # The git command line interface object
12
+ attr_accessor :git
13
+
14
+ # Create a new Repo instance
15
+ # +path+ is the path to either the root git directory or the bare git repo
16
+ # +options+ :is_bare force to load a bare repo
17
+ #
18
+ # Examples
19
+ # g = Repo.new("/Users/tom/dev/grit")
20
+ # g = Repo.new("/Users/tom/public/grit.git")
21
+ #
22
+ # Returns Grit::Repo
23
+ def initialize(path, options = {})
24
+ epath = File.expand_path(path)
25
+
26
+ if File.exist?(File.join(epath, '.git'))
27
+ self.working_dir = epath
28
+ self.path = File.join(epath, '.git')
29
+ @bare = false
30
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
31
+ self.path = epath
32
+ @bare = true
33
+ elsif File.exist?(epath)
34
+ raise InvalidGitRepositoryError.new(epath)
35
+ else
36
+ raise NoSuchPathError.new(epath)
37
+ end
38
+
39
+ self.git = Git.new(self.path)
40
+ end
41
+
42
+ # Does nothing yet...
43
+ def self.init(path)
44
+ # !! TODO !!
45
+ # create directory
46
+ # generate initial git directory
47
+ # create new Grit::Repo on that dir, return it
48
+ end
49
+
50
+ # The project's description. Taken verbatim from GIT_REPO/description
51
+ #
52
+ # Returns String
53
+ def description
54
+ File.open(File.join(self.path, 'description')).read.chomp
55
+ end
56
+
57
+ def blame(file, commit = nil)
58
+ Blame.new(self, file, commit)
59
+ end
60
+
61
+
62
+ # An array of Head objects representing the branch heads in
63
+ # this repo
64
+ #
65
+ # Returns Grit::Head[] (baked)
66
+ def heads
67
+ Head.find_all(self)
68
+ end
69
+
70
+ alias_method :branches, :heads
71
+
72
+ def get_head(head_name)
73
+ heads.find { |h| h.name == head_name }
74
+ end
75
+
76
+ def is_head?(head_name)
77
+ get_head(head_name)
78
+ end
79
+
80
+ # Object reprsenting the current repo head.
81
+ #
82
+ # Returns Grit::Head (baked)
83
+ def head
84
+ Head.current(self)
85
+ end
86
+
87
+
88
+ # Commits current index
89
+ #
90
+ # Returns true/false if commit worked
91
+ def commit_index(message)
92
+ self.git.commit({}, '-m', message)
93
+ end
94
+
95
+ # Commits all tracked and modified files
96
+ #
97
+ # Returns true/false if commit worked
98
+ def commit_all(message)
99
+ self.git.commit({}, '-a', '-m', message)
100
+ end
101
+
102
+ # Adds files to the index
103
+ def add(*files)
104
+ self.git.add({}, *files.flatten)
105
+ end
106
+
107
+ # Remove files from the index
108
+ def remove(*files)
109
+ self.git.rm({}, *files.flatten)
110
+ end
111
+
112
+
113
+ def blame_tree(commit, path = nil)
114
+ commit_array = self.git.blame_tree(commit, path)
115
+
116
+ final_array = {}
117
+ commit_array.each do |file, sha|
118
+ final_array[file] = commit(sha)
119
+ end
120
+ final_array
121
+ end
122
+
123
+ def status
124
+ Status.new(self)
125
+ end
126
+
127
+
128
+ # An array of Tag objects that are available in this repo
129
+ #
130
+ # Returns Grit::Tag[] (baked)
131
+ def tags
132
+ Tag.find_all(self)
133
+ end
134
+
135
+ # An array of Remote objects representing the remote branches in
136
+ # this repo
137
+ #
138
+ # Returns Grit::Remote[] (baked)
139
+ def remotes
140
+ Remote.find_all(self)
141
+ end
142
+
143
+ # An array of Ref objects representing the refs in
144
+ # this repo
145
+ #
146
+ # Returns Grit::Ref[] (baked)
147
+ def refs
148
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
149
+ end
150
+
151
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
152
+ options = {:max_count => max_count,
153
+ :skip => skip}
154
+
155
+ CommitStats.find_all(self, start, options)
156
+ end
157
+
158
+ # An array of Commit objects representing the history of a given ref/commit
159
+ # +start+ is the branch/commit name (default 'master')
160
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
161
+ # +skip+ is the number of commits to skip (default 0)
162
+ #
163
+ # Returns Grit::Commit[] (baked)
164
+ def commits(start = 'master', max_count = 10, skip = 0)
165
+ options = {:max_count => max_count,
166
+ :skip => skip}
167
+
168
+ Commit.find_all(self, start, options)
169
+ end
170
+
171
+ # The Commits objects that are reachable via +to+ but not via +from+
172
+ # Commits are returned in chronological order.
173
+ # +from+ is the branch/commit name of the younger item
174
+ # +to+ is the branch/commit name of the older item
175
+ #
176
+ # Returns Grit::Commit[] (baked)
177
+ def commits_between(from, to)
178
+ Commit.find_all(self, "#{from}..#{to}").reverse
179
+ end
180
+
181
+ # The Commits objects that are newer than the specified date.
182
+ # Commits are returned in chronological order.
183
+ # +start+ is the branch/commit name (default 'master')
184
+ # +since+ is a string represeting a date/time
185
+ # +extra_options+ is a hash of extra options
186
+ #
187
+ # Returns Grit::Commit[] (baked)
188
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
189
+ options = {:since => since}.merge(extra_options)
190
+
191
+ Commit.find_all(self, start, options)
192
+ end
193
+
194
+ # The number of commits reachable by the given branch/commit
195
+ # +start+ is the branch/commit name (default 'master')
196
+ #
197
+ # Returns Integer
198
+ def commit_count(start = 'master')
199
+ Commit.count(self, start)
200
+ end
201
+
202
+ # The Commit object for the specified id
203
+ # +id+ is the SHA1 identifier of the commit
204
+ #
205
+ # Returns Grit::Commit (baked)
206
+ def commit(id)
207
+ options = {:max_count => 1}
208
+
209
+ Commit.find_all(self, id, options).first
210
+ end
211
+
212
+ # Returns a list of commits that is in +other_repo+ but not in self
213
+ #
214
+ # Returns Grit::Commit[]
215
+ def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
216
+ # TODO: we should be able to figure out the branch point, rather than
217
+ # rev-list'ing the whole thing
218
+ repo_refs = self.git.rev_list({}, ref).strip.split("\n")
219
+ other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
220
+
221
+ (other_repo_refs - repo_refs).map do |ref|
222
+ Commit.find_all(other_repo, ref, {:max_count => 1}).first
223
+ end
224
+ end
225
+
226
+ # The Tree object for the given treeish reference
227
+ # +treeish+ is the reference (default 'master')
228
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
229
+ #
230
+ # Examples
231
+ # repo.tree('master', ['lib/'])
232
+ #
233
+ # Returns Grit::Tree (baked)
234
+ def tree(treeish = 'master', paths = [])
235
+ Tree.construct(self, treeish, paths)
236
+ end
237
+
238
+ # The Blob object for the given id
239
+ # +id+ is the SHA1 id of the blob
240
+ #
241
+ # Returns Grit::Blob (unbaked)
242
+ def blob(id)
243
+ Blob.create(self, :id => id)
244
+ end
245
+
246
+ # The commit log for a treeish
247
+ #
248
+ # Returns Grit::Commit[]
249
+ def log(commit = 'master', path = nil, options = {})
250
+ default_options = {:pretty => "raw"}
251
+ actual_options = default_options.merge(options)
252
+ arg = path ? [commit, '--', path] : [commit]
253
+ commits = self.git.log(actual_options, *arg)
254
+ Commit.list_from_string(self, commits)
255
+ end
256
+
257
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
258
+ # +a+ is the base commit
259
+ # +b+ is the other commit
260
+ # +paths+ is an optional list of file paths on which to restrict the diff
261
+ def diff(a, b, *paths)
262
+ self.git.diff({}, a, b, '--', *paths)
263
+ end
264
+
265
+ # The commit diff for the given commit
266
+ # +commit+ is the commit name/id
267
+ #
268
+ # Returns Grit::Diff[]
269
+ def commit_diff(commit)
270
+ Commit.diff(self, commit)
271
+ end
272
+
273
+ # Initialize a bare git repository at the given path
274
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
275
+ # +options+ is any additional options to the git init command
276
+ #
277
+ # Examples
278
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
279
+ #
280
+ # Returns Grit::Repo (the newly created repo)
281
+ def self.init_bare(path, git_options = {}, repo_options = {})
282
+ git = Git.new(path)
283
+ git.init(git_options)
284
+ self.new(path, repo_options)
285
+ end
286
+
287
+ # Fork a bare git repository from this repo
288
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
289
+ # +options+ is any additional options to the git clone command (:bare and :shared are true by default)
290
+ #
291
+ # Returns Grit::Repo (the newly forked repo)
292
+ def fork_bare(path, options = {})
293
+ default_options = {:bare => true, :shared => true}
294
+ real_options = default_options.merge(options)
295
+ self.git.clone(real_options, self.path, path)
296
+ Repo.new(path)
297
+ end
298
+
299
+ # Archive the given treeish
300
+ # +treeish+ is the treeish name/id (default 'master')
301
+ # +prefix+ is the optional prefix
302
+ #
303
+ # Examples
304
+ # repo.archive_tar
305
+ # # => <String containing tar archive>
306
+ #
307
+ # repo.archive_tar('a87ff14')
308
+ # # => <String containing tar archive for commit a87ff14>
309
+ #
310
+ # repo.archive_tar('master', 'myproject/')
311
+ # # => <String containing tar archive and prefixed with 'myproject/'>
312
+ #
313
+ # Returns String (containing tar archive)
314
+ def archive_tar(treeish = 'master', prefix = nil)
315
+ options = {}
316
+ options[:prefix] = prefix if prefix
317
+ self.git.archive(options, treeish)
318
+ end
319
+
320
+ # Archive and gzip the given treeish
321
+ # +treeish+ is the treeish name/id (default 'master')
322
+ # +prefix+ is the optional prefix
323
+ #
324
+ # Examples
325
+ # repo.archive_tar_gz
326
+ # # => <String containing tar.gz archive>
327
+ #
328
+ # repo.archive_tar_gz('a87ff14')
329
+ # # => <String containing tar.gz archive for commit a87ff14>
330
+ #
331
+ # repo.archive_tar_gz('master', 'myproject/')
332
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
333
+ #
334
+ # Returns String (containing tar.gz archive)
335
+ def archive_tar_gz(treeish = 'master', prefix = nil)
336
+ options = {}
337
+ options[:prefix] = prefix if prefix
338
+ self.git.archive(options, treeish, "| gzip")
339
+ end
340
+
341
+ # Write an archive directly to a file
342
+ # +treeish+ is the treeish name/id (default 'master')
343
+ # +prefix+ is the optional prefix (default nil)
344
+ # +filename+ is the name of the file (default 'archive.tar.gz')
345
+ # +format+ is the optional format (default nil)
346
+ # +pipe+ is the command to run the output through (default 'gzip')
347
+ #
348
+ # Returns nothing
349
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
350
+ options = {}
351
+ options[:prefix] = prefix if prefix
352
+ options[:format] = format if format
353
+ self.git.archive(options, treeish, "| #{pipe} > #{filename}")
354
+ end
355
+
356
+ # Enable git-daemon serving of this repository by writing the
357
+ # git-daemon-export-ok file to its git directory
358
+ #
359
+ # Returns nothing
360
+ def enable_daemon_serve
361
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
362
+ end
363
+
364
+ # Disable git-daemon serving of this repository by ensuring there is no
365
+ # git-daemon-export-ok file in its git directory
366
+ #
367
+ # Returns nothing
368
+ def disable_daemon_serve
369
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
370
+ end
371
+
372
+ def gc_auto
373
+ self.git.gc({:auto => true})
374
+ end
375
+
376
+ # The list of alternates for this repo
377
+ #
378
+ # Returns Array[String] (pathnames of alternates)
379
+ def alternates
380
+ alternates_path = File.join(self.path, *%w{objects info alternates})
381
+
382
+ if File.exist?(alternates_path)
383
+ File.read(alternates_path).strip.split("\n")
384
+ else
385
+ []
386
+ end
387
+ end
388
+
389
+ # Sets the alternates
390
+ # +alts+ is the Array of String paths representing the alternates
391
+ #
392
+ # Returns nothing
393
+ def alternates=(alts)
394
+ alts.each do |alt|
395
+ unless File.exist?(alt)
396
+ raise "Could not set alternates. Alternate path #{alt} must exist"
397
+ end
398
+ end
399
+
400
+ if alts.empty?
401
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
402
+ f.write ''
403
+ end
404
+ else
405
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
406
+ f.write alts.join("\n")
407
+ end
408
+ end
409
+ end
410
+
411
+ def config
412
+ @config ||= Config.new(self)
413
+ end
414
+
415
+ def index
416
+ Index.new(self)
417
+ end
418
+
419
+ def update_ref(head, commit_sha)
420
+ return nil if !commit_sha || (commit_sha.size != 40)
421
+
422
+ ref_heads = File.join(self.path, 'refs', 'heads')
423
+ FileUtils.mkdir_p(ref_heads)
424
+ File.open(File.join(ref_heads, head), 'w') do |f|
425
+ f.write(commit_sha)
426
+ end
427
+ commit_sha
428
+
429
+ end
430
+
431
+ # Pretty object inspection
432
+ def inspect
433
+ %Q{#<Grit::Repo "#{@path}">}
434
+ end
435
+ end # Repo
436
+
437
+ end # Grit