schleyfox-grit 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/grit/lazy.rb ADDED
@@ -0,0 +1,35 @@
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 self.extended(klass)
20
+ klass.send(:attr_writer, :lazy_source)
21
+ end
22
+
23
+ def lazy_reader(*args)
24
+ args.each do |arg|
25
+ ivar = "@#{arg}"
26
+ define_method(arg) do
27
+ if instance_variable_defined?(ivar)
28
+ val = instance_variable_get(ivar)
29
+ return val if val
30
+ end
31
+ instance_variable_set(ivar, (@lazy_source ||= lazy_source).send(arg))
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/grit/merge.rb ADDED
@@ -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,46 @@
1
+ module Open3
2
+ extend self
3
+
4
+ def popen3(*cmd)
5
+ pw = IO::pipe # pipe[0] for read, pipe[1] for write
6
+ pr = IO::pipe
7
+ pe = IO::pipe
8
+
9
+ pid = fork{
10
+ # child
11
+ fork{
12
+ # grandchild
13
+ pw[1].close
14
+ STDIN.reopen(pw[0])
15
+ pw[0].close
16
+
17
+ pr[0].close
18
+ STDOUT.reopen(pr[1])
19
+ pr[1].close
20
+
21
+ pe[0].close
22
+ STDERR.reopen(pe[1])
23
+ pe[1].close
24
+
25
+ exec(*cmd)
26
+ }
27
+ exit!(0)
28
+ }
29
+
30
+ pw[0].close
31
+ pr[1].close
32
+ pe[1].close
33
+ Process.waitpid(pid)
34
+ pi = [pw[1], pr[0], pe[0]]
35
+ pw[1].sync = true
36
+ if defined? yield
37
+ begin
38
+ return yield(*pi)
39
+ ensure
40
+ Process.detach(pid) if pid
41
+ pi.each { |p| p.close unless p.closed? }
42
+ end
43
+ end
44
+ pi
45
+ end
46
+ end
data/lib/grit/ref.rb ADDED
@@ -0,0 +1,78 @@
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 = repo.git.refs(options, prefix)
14
+ refs.split("\n").map do |ref|
15
+ name, id = *ref.split(' ')
16
+ commit = Commit.create(repo, :id => id)
17
+ self.new(name, commit)
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def prefix
24
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
25
+ end
26
+
27
+ end
28
+
29
+ attr_reader :name
30
+ attr_reader :commit
31
+
32
+ # Instantiate a new Head
33
+ # +name+ is the name of the head
34
+ # +commit+ is the Commit that the head points to
35
+ #
36
+ # Returns Grit::Head (baked)
37
+ def initialize(name, commit)
38
+ @name = name
39
+ @commit = commit
40
+ end
41
+
42
+ # Pretty object inspection
43
+ def inspect
44
+ %Q{#<#{self.class.name} "#{@name}">}
45
+ end
46
+ end # Ref
47
+
48
+ # A Head is a named reference to a Commit. Every Head instance contains a name
49
+ # and a Commit object.
50
+ #
51
+ # r = Grit::Repo.new("/path/to/repo")
52
+ # h = r.heads.first
53
+ # h.name # => "master"
54
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
55
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
56
+ class Head < Ref
57
+
58
+ # Get the HEAD revision of the repo.
59
+ # +repo+ is the Repo
60
+ # +options+ is a Hash of options
61
+ #
62
+ # Returns Grit::Head (baked)
63
+ def self.current(repo, options = {})
64
+ head = repo.git.fs_read('HEAD').chomp
65
+ if /ref: refs\/heads\/(.*)/.match(head)
66
+ id = repo.git.rev_parse(options, 'HEAD')
67
+ commit = Commit.create(repo, :id => id)
68
+ self.new($1, commit)
69
+ end
70
+ end
71
+
72
+ end # Head
73
+
74
+ class Remote < Ref; end
75
+
76
+ class Note < Ref; end
77
+
78
+ end # Grit
data/lib/grit/repo.rb ADDED
@@ -0,0 +1,657 @@
1
+ module Grit
2
+
3
+ class Repo
4
+ DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
5
+ BATCH_PARSERS = {
6
+ 'commit' => ::Grit::Commit
7
+ }
8
+
9
+ # Public: The String path of the Git repo.
10
+ attr_accessor :path
11
+
12
+ # Public: The String path to the working directory of the repo, or nil if
13
+ # there is no working directory.
14
+ attr_accessor :working_dir
15
+
16
+ # Public: The Boolean of whether or not the repo is bare.
17
+ attr_reader :bare
18
+
19
+ # Public: The Grit::Git command line interface object.
20
+ attr_accessor :git
21
+
22
+ # Public: Create a new Repo instance.
23
+ #
24
+ # path - The String path to either the root git directory or the bare
25
+ # git repo. Bare repos are expected to end with ".git".
26
+ # options - A Hash of options (default: {}):
27
+ # :is_bare - Boolean whether to consider the repo as bare even
28
+ # if the repo name does not end with ".git".
29
+ #
30
+ # Examples
31
+ #
32
+ # r = Repo.new("/Users/tom/dev/normal")
33
+ # r = Repo.new("/Users/tom/public/bare.git")
34
+ # r = Repo.new("/Users/tom/public/bare", {:is_bare => true})
35
+ #
36
+ # Returns a newly initialized Grit::Repo.
37
+ # Raises Grit::InvalidGitRepositoryError if the path exists but is not
38
+ # a Git repository.
39
+ # Raises Grit::NoSuchPathError if the path does not exist.
40
+ def initialize(path, options = {})
41
+ epath = File.expand_path(path)
42
+
43
+ if File.exist?(File.join(epath, '.git'))
44
+ self.working_dir = epath
45
+ self.path = File.join(epath, '.git')
46
+ @bare = false
47
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
48
+ self.path = epath
49
+ @bare = true
50
+ elsif File.exist?(epath)
51
+ raise InvalidGitRepositoryError.new(epath)
52
+ else
53
+ raise NoSuchPathError.new(epath)
54
+ end
55
+
56
+ self.git = Git.new(self.path)
57
+ end
58
+
59
+ # Public: Initialize a git repository (create it on the filesystem). By
60
+ # default, the newly created repository will contain a working directory.
61
+ # If you would like to create a bare repo, use Grit::Repo.init_bare.
62
+ #
63
+ # path - The String full path to the repo. Traditionally ends with
64
+ # "/<name>.git".
65
+ # git_options - A Hash of additional options to the git init command
66
+ # (default: {}).
67
+ # repo_options - A Hash of additional options to the Grit::Repo.new call
68
+ # (default: {}).
69
+ #
70
+ # Examples
71
+ #
72
+ # Grit::Repo.init('/var/git/myrepo.git')
73
+ #
74
+ # Returns the newly created Grit::Repo.
75
+ def self.init(path, git_options = {}, repo_options = {})
76
+ git_options = {:base => false}.merge(git_options)
77
+ git = Git.new(path)
78
+ git.fs_mkdir('..')
79
+ git.init(git_options, path)
80
+ self.new(path, repo_options)
81
+ end
82
+
83
+ # Public: Initialize a bare git repository (create it on the filesystem).
84
+ #
85
+ # path - The String full path to the repo. Traditionally ends with
86
+ # "/<name>.git".
87
+ # git_options - A Hash of additional options to the git init command
88
+ # (default: {}).
89
+ # repo_options - A Hash of additional options to the Grit::Repo.new call
90
+ # (default: {}).
91
+ #
92
+ # Examples
93
+ #
94
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
95
+ #
96
+ # Returns the newly created Grit::Repo.
97
+ def self.init_bare(path, git_options = {}, repo_options = {})
98
+ git_options = {:bare => true}.merge(git_options)
99
+ git = Git.new(path)
100
+ git.fs_mkdir('..')
101
+ git.init(git_options)
102
+ self.new(path, repo_options)
103
+ end
104
+
105
+ # Public: Initialize a bare Git repository (create it on the filesystem)
106
+ # or, if the repo already exists, simply return it.
107
+ #
108
+ # path - The String full path to the repo. Traditionally ends with
109
+ # "/<name>.git".
110
+ # git_options - A Hash of additional options to the git init command
111
+ # (default: {}).
112
+ # repo_options - A Hash of additional options to the Grit::Repo.new call
113
+ # (default: {}).
114
+ #
115
+ # Returns the new or existing Grit::Repo.
116
+ def self.init_bare_or_open(path, git_options = {}, repo_options = {})
117
+ git = Git.new(path)
118
+
119
+ unless git.exist?
120
+ git.fs_mkdir(path)
121
+ git.init(git_options)
122
+ end
123
+
124
+ self.new(path, repo_options)
125
+ end
126
+
127
+ # Public: Create a bare fork of this repository.
128
+ #
129
+ # path - The String full path of where to create the new fork.
130
+ # Traditionally ends with "/<name>.git".
131
+ # options - The Hash of additional options to the git clone command.
132
+ # These options will be merged on top of the default Hash:
133
+ # {:bare => true, :shared => true}.
134
+ #
135
+ # Returns the newly forked Grit::Repo.
136
+ def fork_bare(path, options = {})
137
+ default_options = {:bare => true, :shared => true}
138
+ real_options = default_options.merge(options)
139
+ Git.new(path).fs_mkdir('..')
140
+ self.git.clone(real_options, self.path, path)
141
+ Repo.new(path)
142
+ end
143
+
144
+ # Public: Fork a bare git repository from another repo.
145
+ #
146
+ # path - The String full path of the repo from which to fork..
147
+ # Traditionally ends with "/<name>.git".
148
+ # options - The Hash of additional options to the git clone command.
149
+ # These options will be merged on top of the default Hash:
150
+ # {:bare => true, :shared => true}.
151
+ #
152
+ # Returns the newly forked Grit::Repo.
153
+ def fork_bare_from(path, options = {})
154
+ default_options = {:bare => true, :shared => true}
155
+ real_options = default_options.merge(options)
156
+ Git.new(self.path).fs_mkdir('..')
157
+ self.git.clone(real_options, path, self.path)
158
+ Repo.new(self.path)
159
+ end
160
+
161
+ # Public: Return the full Git objects from the given SHAs. Only Commit
162
+ # objects are parsed for now.
163
+ #
164
+ # *shas - Array of String SHAs.
165
+ #
166
+ # Returns an Array of Grit objects (Grit::Commit).
167
+ def batch(*shas)
168
+ shas.flatten!
169
+ text = git.native(:cat_file, {:batch => true}) do |stdin|
170
+ stdin.write(shas * "\n")
171
+ stdin.close
172
+ end
173
+
174
+ parse_batch(text)
175
+ end
176
+
177
+ # Parses `git cat-file --batch` output, returning an array of Grit objects.
178
+ #
179
+ # text - Raw String output.
180
+ #
181
+ # Returns an Array of Grit objects (Grit::Commit).
182
+ def parse_batch(text)
183
+ io = StringIO.new(text)
184
+ objects = []
185
+ while line = io.gets
186
+ sha, type, size = line.split(" ", 3)
187
+ parser = BATCH_PARSERS[type]
188
+ if type == 'missing' || !parser
189
+ objects << nil
190
+ next
191
+ end
192
+
193
+ object = io.read(size.to_i + 1)
194
+ objects << parser.parse_batch(self, sha, size, object)
195
+ end
196
+ objects
197
+ end
198
+
199
+ # The project's description. Taken verbatim from GIT_REPO/description
200
+ #
201
+ # Returns String
202
+ def description
203
+ self.git.fs_read('description').chomp
204
+ end
205
+
206
+ def blame(file, commit = nil)
207
+ Blame.new(self, file, commit)
208
+ end
209
+
210
+ # An array of Head objects representing the branch heads in
211
+ # this repo
212
+ #
213
+ # Returns Grit::Head[] (baked)
214
+ def heads
215
+ Head.find_all(self)
216
+ end
217
+
218
+ alias_method :branches, :heads
219
+
220
+ def get_head(head_name)
221
+ heads.find { |h| h.name == head_name }
222
+ end
223
+
224
+ def is_head?(head_name)
225
+ get_head(head_name)
226
+ end
227
+
228
+ # Object reprsenting the current repo head.
229
+ #
230
+ # Returns Grit::Head (baked)
231
+ def head
232
+ Head.current(self)
233
+ end
234
+
235
+
236
+ # Commits current index
237
+ #
238
+ # Returns true/false if commit worked
239
+ def commit_index(message)
240
+ self.git.commit({}, '-m', message)
241
+ end
242
+
243
+ # Commits all tracked and modified files
244
+ #
245
+ # Returns true/false if commit worked
246
+ def commit_all(message)
247
+ self.git.commit({}, '-a', '-m', message)
248
+ end
249
+
250
+ # Adds files to the index
251
+ def add(*files)
252
+ self.git.add({}, *files.flatten)
253
+ end
254
+
255
+ # Remove files from the index
256
+ def remove(*files)
257
+ self.git.rm({}, *files.flatten)
258
+ end
259
+
260
+
261
+ def blame_tree(commit, path = nil)
262
+ commit_array = self.git.blame_tree(commit, path)
263
+
264
+ final_array = {}
265
+ commit_array.each do |file, sha|
266
+ final_array[file] = commit(sha)
267
+ end
268
+ final_array
269
+ end
270
+
271
+ def status
272
+ Status.new(self)
273
+ end
274
+
275
+
276
+ # An array of Tag objects that are available in this repo
277
+ #
278
+ # Returns Grit::Tag[] (baked)
279
+ def tags
280
+ Tag.find_all(self)
281
+ end
282
+
283
+ # Finds the most recent annotated tag name that is reachable from a commit.
284
+ #
285
+ # @repo.recent_tag_name('master')
286
+ # # => "v1.0-0-abcdef"
287
+ #
288
+ # committish - optional commit SHA, branch, or tag name.
289
+ # options - optional hash of options to pass to git.
290
+ # Default: {:always => true}
291
+ # :tags => true # use lightweight tags too.
292
+ # :abbrev => Integer # number of hex digits to form the unique
293
+ # name. Defaults to 7.
294
+ # :long => true # always output tag + commit sha
295
+ # # see `git describe` docs for more options.
296
+ #
297
+ # Returns the String tag name, or just the commit if no tag is
298
+ # found. If there have been updates since the tag was made, a
299
+ # suffix is added with the number of commits since the tag, and
300
+ # the abbreviated object name of the most recent commit.
301
+ # Returns nil if the committish value is not found.
302
+ def recent_tag_name(committish = nil, options = {})
303
+ value = git.describe({:always => true}.update(options), committish.to_s).to_s.strip
304
+ value.size.zero? ? nil : value
305
+ end
306
+
307
+ # An array of Remote objects representing the remote branches in
308
+ # this repo
309
+ #
310
+ # Returns Grit::Remote[] (baked)
311
+ def remotes
312
+ Remote.find_all(self)
313
+ end
314
+
315
+ def remote_list
316
+ self.git.list_remotes
317
+ end
318
+
319
+ def remote_add(name, url)
320
+ self.git.remote({}, 'add', name, url)
321
+ end
322
+
323
+ def remote_fetch(name)
324
+ self.git.fetch({}, name)
325
+ end
326
+
327
+ # takes an array of remote names and last pushed dates
328
+ # fetches from all of the remotes where the local fetch
329
+ # date is earlier than the passed date, then records the
330
+ # last fetched date
331
+ #
332
+ # { 'origin' => date,
333
+ # 'peter => date,
334
+ # }
335
+ def remotes_fetch_needed(remotes)
336
+ remotes.each do |remote, date|
337
+ # TODO: check against date
338
+ self.remote_fetch(remote)
339
+ end
340
+ end
341
+
342
+
343
+ # An array of Ref objects representing the refs in
344
+ # this repo
345
+ #
346
+ # Returns Grit::Ref[] (baked)
347
+ def refs
348
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
349
+ end
350
+
351
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
352
+ options = {:max_count => max_count,
353
+ :skip => skip}
354
+
355
+ CommitStats.find_all(self, start, options)
356
+ end
357
+
358
+ # An array of Commit objects representing the history of a given ref/commit
359
+ # +start+ is the branch/commit name (default 'master')
360
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
361
+ # +skip+ is the number of commits to skip (default 0)
362
+ #
363
+ # Returns Grit::Commit[] (baked)
364
+ def commits(start = 'master', max_count = 10, skip = 0)
365
+ options = {:max_count => max_count,
366
+ :skip => skip}
367
+
368
+ Commit.find_all(self, start, options)
369
+ end
370
+
371
+ # The Commits objects that are reachable via +to+ but not via +from+
372
+ # Commits are returned in chronological order.
373
+ # +from+ is the branch/commit name of the younger item
374
+ # +to+ is the branch/commit name of the older item
375
+ #
376
+ # Returns Grit::Commit[] (baked)
377
+ def commits_between(from, to)
378
+ Commit.find_all(self, "#{from}..#{to}").reverse
379
+ end
380
+
381
+ # The Commits objects that are newer than the specified date.
382
+ # Commits are returned in chronological order.
383
+ # +start+ is the branch/commit name (default 'master')
384
+ # +since+ is a string represeting a date/time
385
+ # +extra_options+ is a hash of extra options
386
+ #
387
+ # Returns Grit::Commit[] (baked)
388
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
389
+ options = {:since => since}.merge(extra_options)
390
+
391
+ Commit.find_all(self, start, options)
392
+ end
393
+
394
+ # The number of commits reachable by the given branch/commit
395
+ # +start+ is the branch/commit name (default 'master')
396
+ #
397
+ # Returns Integer
398
+ def commit_count(start = 'master')
399
+ Commit.count(self, start)
400
+ end
401
+
402
+ # The Commit object for the specified id
403
+ # +id+ is the SHA1 identifier of the commit
404
+ #
405
+ # Returns Grit::Commit (baked)
406
+ def commit(id)
407
+ options = {:max_count => 1}
408
+
409
+ Commit.find_all(self, id, options).first
410
+ end
411
+
412
+ # Returns a list of commits that is in +other_repo+ but not in self
413
+ #
414
+ # Returns Grit::Commit[]
415
+ def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
416
+ # TODO: we should be able to figure out the branch point, rather than
417
+ # rev-list'ing the whole thing
418
+ repo_refs = self.git.rev_list({}, ref).strip.split("\n")
419
+ other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
420
+
421
+ (other_repo_refs - repo_refs).map do |ref|
422
+ Commit.find_all(other_repo, ref, {:max_count => 1}).first
423
+ end
424
+ end
425
+
426
+ def objects(refs)
427
+ Grit.no_quote = true
428
+ obj = self.git.rev_list({:objects => true, :timeout => false}, refs).split("\n").map { |a| a[0, 40] }
429
+ Grit.no_quote = false
430
+ obj
431
+ end
432
+
433
+ def commit_objects(refs)
434
+ Grit.no_quote = true
435
+ obj = self.git.rev_list({:timeout => false}, refs).split("\n").map { |a| a[0, 40] }
436
+ Grit.no_quote = false
437
+ obj
438
+ end
439
+
440
+ def objects_between(ref1, ref2 = nil)
441
+ if ref2
442
+ refs = "#{ref2}..#{ref1}"
443
+ else
444
+ refs = ref1
445
+ end
446
+ self.objects(refs)
447
+ end
448
+
449
+ def diff_objects(commit_sha, parents = true)
450
+ revs = []
451
+ Grit.no_quote = true
452
+ if parents
453
+ # PARENTS:
454
+ cmd = "-r -t -m #{commit_sha}"
455
+ revs = self.git.diff_tree({:timeout => false}, cmd).strip.split("\n").map{ |a| r = a.split(' '); r[3] if r[1] != '160000' }
456
+ else
457
+ # NO PARENTS:
458
+ cmd = "-r -t #{commit_sha}"
459
+ revs = self.git.method_missing('ls-tree', {:timeout => false}, "-r -t #{commit_sha}").split("\n").map{ |a| a.split("\t").first.split(' ')[2] }
460
+ end
461
+ revs << self.commit(commit_sha).tree.id
462
+ Grit.no_quote = false
463
+ return revs.uniq.compact
464
+ end
465
+
466
+ # The Tree object for the given treeish reference
467
+ # +treeish+ is the reference (default 'master')
468
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
469
+ #
470
+ # Examples
471
+ # repo.tree('master', ['lib/'])
472
+ #
473
+ # Returns Grit::Tree (baked)
474
+ def tree(treeish = 'master', paths = [])
475
+ Tree.construct(self, treeish, paths)
476
+ end
477
+
478
+ # The Blob object for the given id
479
+ # +id+ is the SHA1 id of the blob
480
+ #
481
+ # Returns Grit::Blob (unbaked)
482
+ def blob(id)
483
+ Blob.create(self, :id => id)
484
+ end
485
+
486
+ # The commit log for a treeish
487
+ #
488
+ # Returns Grit::Commit[]
489
+ def log(commit = 'master', path = nil, options = {})
490
+ default_options = {:pretty => "raw"}
491
+ actual_options = default_options.merge(options)
492
+ arg = path ? [commit, '--', path] : [commit]
493
+ commits = self.git.log(actual_options, *arg)
494
+ Commit.list_from_string(self, commits)
495
+ end
496
+
497
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
498
+ # +a+ is the base commit
499
+ # +b+ is the other commit
500
+ # +paths+ is an optional list of file paths on which to restrict the diff
501
+ def diff(a, b, *paths)
502
+ diff = self.git.native('diff', {:full_index => true}, a, b, '--', *paths)
503
+
504
+ if diff =~ /diff --git a/
505
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
506
+ else
507
+ diff = ''
508
+ end
509
+ Diff.list_from_string(self, diff)
510
+ end
511
+
512
+ # The commit diff for the given commit
513
+ # +commit+ is the commit name/id
514
+ #
515
+ # Returns Grit::Diff[]
516
+ def commit_diff(commit)
517
+ Commit.diff(self, commit)
518
+ end
519
+
520
+ # Archive the given treeish
521
+ # +treeish+ is the treeish name/id (default 'master')
522
+ # +prefix+ is the optional prefix
523
+ #
524
+ # Examples
525
+ # repo.archive_tar
526
+ # # => <String containing tar archive>
527
+ #
528
+ # repo.archive_tar('a87ff14')
529
+ # # => <String containing tar archive for commit a87ff14>
530
+ #
531
+ # repo.archive_tar('master', 'myproject/')
532
+ # # => <String containing tar archive and prefixed with 'myproject/'>
533
+ #
534
+ # Returns String (containing tar archive)
535
+ def archive_tar(treeish = 'master', prefix = nil)
536
+ options = {}
537
+ options[:prefix] = prefix if prefix
538
+ self.git.archive(options, treeish)
539
+ end
540
+
541
+ # Archive and gzip the given treeish
542
+ # +treeish+ is the treeish name/id (default 'master')
543
+ # +prefix+ is the optional prefix
544
+ #
545
+ # Examples
546
+ # repo.archive_tar_gz
547
+ # # => <String containing tar.gz archive>
548
+ #
549
+ # repo.archive_tar_gz('a87ff14')
550
+ # # => <String containing tar.gz archive for commit a87ff14>
551
+ #
552
+ # repo.archive_tar_gz('master', 'myproject/')
553
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
554
+ #
555
+ # Returns String (containing tar.gz archive)
556
+ def archive_tar_gz(treeish = 'master', prefix = nil)
557
+ options = {}
558
+ options[:prefix] = prefix if prefix
559
+ self.git.archive(options, treeish, "| gzip -n")
560
+ end
561
+
562
+ # Write an archive directly to a file
563
+ # +treeish+ is the treeish name/id (default 'master')
564
+ # +prefix+ is the optional prefix (default nil)
565
+ # +filename+ is the name of the file (default 'archive.tar.gz')
566
+ # +format+ is the optional format (default nil)
567
+ # +pipe+ is the command to run the output through (default 'gzip')
568
+ #
569
+ # Returns nothing
570
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
571
+ options = {}
572
+ options[:prefix] = prefix if prefix
573
+ options[:format] = format if format
574
+ self.git.archive(options, treeish, "| #{pipe} > #{filename}")
575
+ end
576
+
577
+ # Enable git-daemon serving of this repository by writing the
578
+ # git-daemon-export-ok file to its git directory
579
+ #
580
+ # Returns nothing
581
+ def enable_daemon_serve
582
+ self.git.fs_write(DAEMON_EXPORT_FILE, '')
583
+ end
584
+
585
+ # Disable git-daemon serving of this repository by ensuring there is no
586
+ # git-daemon-export-ok file in its git directory
587
+ #
588
+ # Returns nothing
589
+ def disable_daemon_serve
590
+ self.git.fs_delete(DAEMON_EXPORT_FILE)
591
+ end
592
+
593
+ def gc_auto
594
+ self.git.gc({:auto => true})
595
+ end
596
+
597
+ # The list of alternates for this repo
598
+ #
599
+ # Returns Array[String] (pathnames of alternates)
600
+ def alternates
601
+ alternates_path = "objects/info/alternates"
602
+ self.git.fs_read(alternates_path).strip.split("\n")
603
+ rescue Errno::ENOENT
604
+ []
605
+ end
606
+
607
+ # Sets the alternates
608
+ # +alts+ is the Array of String paths representing the alternates
609
+ #
610
+ # Returns nothing
611
+ def alternates=(alts)
612
+ alts.each do |alt|
613
+ unless File.exist?(alt)
614
+ raise "Could not set alternates. Alternate path #{alt} must exist"
615
+ end
616
+ end
617
+
618
+ if alts.empty?
619
+ self.git.fs_write('objects/info/alternates', '')
620
+ else
621
+ self.git.fs_write('objects/info/alternates', alts.join("\n"))
622
+ end
623
+ end
624
+
625
+ def config
626
+ @config ||= Config.new(self)
627
+ end
628
+
629
+ def index
630
+ Index.new(self)
631
+ end
632
+
633
+ def update_ref(head, commit_sha)
634
+ return nil if !commit_sha || (commit_sha.size != 40)
635
+ self.git.fs_write("refs/heads/#{head}", commit_sha)
636
+ commit_sha
637
+ end
638
+
639
+ # Rename the current repository directory.
640
+ # +name+ is the new name
641
+ #
642
+ # Returns nothing
643
+ def rename(name)
644
+ if @bare
645
+ self.git.fs_move('/', "../#{name}")
646
+ else
647
+ self.git.fs_move('/', "../../#{name}")
648
+ end
649
+ end
650
+
651
+ # Pretty object inspection
652
+ def inspect
653
+ %Q{#<Grit::Repo "#{@path}">}
654
+ end
655
+ end # Repo
656
+
657
+ end # Grit