schacon-grit 0.9.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.
Files changed (47) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +57 -0
  3. data/README.txt +213 -0
  4. data/Rakefile +29 -0
  5. data/grit.gemspec +60 -0
  6. data/lib/grit.rb +53 -0
  7. data/lib/grit/actor.rb +36 -0
  8. data/lib/grit/blob.rb +117 -0
  9. data/lib/grit/commit.rb +221 -0
  10. data/lib/grit/commit_stats.rb +104 -0
  11. data/lib/grit/config.rb +44 -0
  12. data/lib/grit/diff.rb +70 -0
  13. data/lib/grit/errors.rb +7 -0
  14. data/lib/grit/git-ruby.rb +175 -0
  15. data/lib/grit/git-ruby/commit_db.rb +52 -0
  16. data/lib/grit/git-ruby/file_index.rb +186 -0
  17. data/lib/grit/git-ruby/git_object.rb +344 -0
  18. data/lib/grit/git-ruby/internal/loose.rb +136 -0
  19. data/lib/grit/git-ruby/internal/mmap.rb +59 -0
  20. data/lib/grit/git-ruby/internal/pack.rb +332 -0
  21. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  22. data/lib/grit/git-ruby/object.rb +319 -0
  23. data/lib/grit/git-ruby/repository.rb +675 -0
  24. data/lib/grit/git.rb +130 -0
  25. data/lib/grit/head.rb +83 -0
  26. data/lib/grit/index.rb +102 -0
  27. data/lib/grit/lazy.rb +31 -0
  28. data/lib/grit/ref.rb +95 -0
  29. data/lib/grit/repo.rb +381 -0
  30. data/lib/grit/status.rb +149 -0
  31. data/lib/grit/tag.rb +71 -0
  32. data/lib/grit/tree.rb +100 -0
  33. data/test/test_actor.rb +35 -0
  34. data/test/test_blob.rb +79 -0
  35. data/test/test_commit.rb +184 -0
  36. data/test/test_config.rb +58 -0
  37. data/test/test_diff.rb +18 -0
  38. data/test/test_git.rb +70 -0
  39. data/test/test_grit.rb +32 -0
  40. data/test/test_head.rb +41 -0
  41. data/test/test_real.rb +19 -0
  42. data/test/test_reality.rb +17 -0
  43. data/test/test_remote.rb +14 -0
  44. data/test/test_repo.rb +277 -0
  45. data/test/test_tag.rb +25 -0
  46. data/test/test_tree.rb +96 -0
  47. metadata +110 -0
data/lib/grit/git.rb ADDED
@@ -0,0 +1,130 @@
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
+ include GitRuby
23
+
24
+ class << self
25
+ attr_accessor :git_binary, :git_timeout
26
+ end
27
+
28
+ self.git_binary = "/usr/bin/env git"
29
+ self.git_timeout = 5
30
+
31
+ attr_accessor :git_dir, :bytes_read
32
+
33
+ def initialize(git_dir)
34
+ self.git_dir = git_dir
35
+ self.bytes_read = 0
36
+ end
37
+
38
+ # Run the given git command with the specified arguments and return
39
+ # the result as a String
40
+ # +cmd+ is the command
41
+ # +options+ is a hash of Ruby style options
42
+ # +args+ is the list of arguments (to be joined by spaces)
43
+ #
44
+ # Examples
45
+ # git.rev_list({:max_count => 10, :header => true}, "master")
46
+ #
47
+ # Returns String
48
+ def method_missing(cmd, options = {}, *args)
49
+ run('', cmd, '', options, args)
50
+ end
51
+
52
+ def run(prefix, cmd, postfix, options, args)
53
+ timeout = options.delete(:timeout)
54
+ timeout = true if timeout.nil?
55
+
56
+ opt_args = transform_options(options)
57
+ ext_args = args.map { |a| a == '--' ? a : "'#{a}'" }
58
+
59
+ call = "#{prefix}#{Git.git_binary} --git-dir='#{self.git_dir}' #{cmd.to_s.gsub(/_/, '-')} #{(opt_args + ext_args).join(' ')}#{postfix}"
60
+ Grit.log(call) if Grit.debug
61
+ response, err = timeout ? sh(call) : wild_sh(call)
62
+ Grit.log(response) if Grit.debug
63
+ Grit.log(err) if Grit.debug
64
+ response
65
+ end
66
+
67
+ def sh(command)
68
+ ret, pid, err = nil, nil, nil
69
+ Open4.popen4(command) do |id, _, stdout, stderr|
70
+ pid = id
71
+ ret = Timeout.timeout(self.class.git_timeout) { stdout.read }
72
+ err = stderr.read
73
+ @bytes_read += ret.size
74
+
75
+ if @bytes_read > 5242880 # 5.megabytes
76
+ bytes = @bytes_read
77
+ @bytes_read = 0
78
+ raise GitTimeout.new(command, bytes)
79
+ end
80
+ end
81
+ [ret, err]
82
+ rescue Errno::ECHILD
83
+ [ret, err]
84
+ rescue Object => e
85
+ Process.kill('KILL', pid) rescue nil
86
+ bytes = @bytes_read
87
+ @bytes_read = 0
88
+ raise GitTimeout.new(command, bytes)
89
+ end
90
+
91
+ def wild_sh(command)
92
+ ret, err = nil, nil
93
+ Open4.popen4(command) {|pid, _, stdout, stderr|
94
+ ret = stdout.read
95
+ err = stderr.read
96
+ }
97
+ [ret, err]
98
+ rescue Errno::ECHILD
99
+ [ret, err]
100
+ end
101
+
102
+ # Transform Ruby style options into git command line options
103
+ # +options+ is a hash of Ruby style options
104
+ #
105
+ # Returns String[]
106
+ # e.g. ["--max-count=10", "--header"]
107
+ def transform_options(options)
108
+ args = []
109
+ options.keys.each do |opt|
110
+ if opt.to_s.size == 1
111
+ if options[opt] == true
112
+ args << "-#{opt}"
113
+ else
114
+ val = options.delete(opt)
115
+ args << "-#{opt.to_s} '#{val}'"
116
+ end
117
+ else
118
+ if options[opt] == true
119
+ args << "--#{opt.to_s.gsub(/_/, '-')}"
120
+ else
121
+ val = options.delete(opt)
122
+ args << "--#{opt.to_s.gsub(/_/, '-')}='#{val}'"
123
+ end
124
+ end
125
+ end
126
+ args
127
+ end
128
+ end # Git
129
+
130
+ end # Grit
data/lib/grit/head.rb ADDED
@@ -0,0 +1,83 @@
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 = {
33
+ :sort => "committerdate",
34
+ :format => "%(refname)%00%(objectname)",
35
+ :timeout => false
36
+ }
37
+
38
+ actual_options = default_options.merge(options)
39
+
40
+ output = repo.git.for_each_ref(actual_options, HEAD_PREFIX)
41
+
42
+ self.list_from_string(repo, output)
43
+ end
44
+
45
+ # Parse out head information into an array of baked head objects
46
+ # +repo+ is the Repo
47
+ # +text+ is the text output from the git command
48
+ #
49
+ # Returns Grit::Head[] (baked)
50
+ def self.list_from_string(repo, text)
51
+ heads = []
52
+
53
+ text.split("\n").each do |line|
54
+ heads << self.from_string(repo, line)
55
+ end
56
+
57
+ heads
58
+ end
59
+
60
+ # Create a new Head instance from the given string.
61
+ # +repo+ is the Repo
62
+ # +line+ is the formatted head information
63
+ #
64
+ # Format
65
+ # name: [a-zA-Z_/]+
66
+ # <null byte>
67
+ # id: [0-9A-Fa-f]{40}
68
+ #
69
+ # Returns Grit::Head (baked)
70
+ def self.from_string(repo, line)
71
+ full_name, id = line.split("\0")
72
+ name = full_name.sub("#{HEAD_PREFIX}/", '')
73
+ commit = Commit.create(repo, :id => id)
74
+ self.new(name, commit)
75
+ end
76
+
77
+ # Pretty object inspection
78
+ def inspect
79
+ %Q{#<Grit::Head "#{@name}">}
80
+ end
81
+ end # Head
82
+
83
+ end # Grit
data/lib/grit/index.rb ADDED
@@ -0,0 +1,102 @@
1
+ module Grit
2
+
3
+ class Index
4
+ attr_accessor :repo, :tree
5
+
6
+ def initialize(repo)
7
+ self.repo = repo
8
+ self.tree = {}
9
+ end
10
+
11
+ # Add a file to the index
12
+ # +path+ is the path (including filename)
13
+ # +data+ is the binary contents of the file
14
+ #
15
+ # Returns nothing
16
+ def add(file_path, data)
17
+ path = file_path.split('/')
18
+ filename = path.pop
19
+
20
+ current = self.tree
21
+
22
+ path.each do |dir|
23
+ current[dir] ||= {}
24
+ node = current[dir]
25
+ current = node
26
+ end
27
+
28
+ current[filename] = data
29
+ end
30
+
31
+ # Commit the contents of the index
32
+ # +message+ is the commit message
33
+ #
34
+ # Returns a String of the SHA1 of the commit
35
+ def commit(message, parents = nil, actor = nil, last_tree = nil)
36
+ tree_sha1 = write_tree(self.tree)
37
+ return false if tree_sha1 == last_tree # don't write identical commits
38
+
39
+ contents = []
40
+ contents << ['tree', tree_sha1].join(' ')
41
+ parents.each do |p|
42
+ contents << ['parent', p].join(' ') if p
43
+ end if parents
44
+
45
+ if actor
46
+ name = actor.name
47
+ email = actor.email
48
+ else
49
+ config = Config.new(self.repo)
50
+ name = config['user.name']
51
+ email = config['user.email']
52
+ end
53
+
54
+ author_string = "#{name} <#{email}> #{Time.now.to_i} -0700" # !! TODO : gotta fix this
55
+ contents << ['author', author_string].join(' ')
56
+ contents << ['committer', author_string].join(' ')
57
+ contents << ''
58
+ contents << message
59
+
60
+ commit_sha1 = self.repo.git.ruby_git.put_raw_object(contents.join("\n"), 'commit')
61
+
62
+ # self.repo.git.update_ref({}, 'HEAD', commit_sha1)
63
+ File.open(File.join(self.repo.path, 'refs', 'heads', 'master'), 'w') do |f|
64
+ f.write(commit_sha1)
65
+ end if commit_sha1
66
+
67
+ commit_sha1
68
+ end
69
+
70
+ # Recursively write a tree to the index
71
+ # +tree+ is the tree
72
+ #
73
+ # Returns the SHA1 String of the tree
74
+ def write_tree(tree)
75
+ tree_contents = ''
76
+ tree.each do |k, v|
77
+ case v
78
+ when String:
79
+ sha = write_blob(v)
80
+ sha = [sha].pack("H*")
81
+ str = "%s %s\0%s" % ['100644', k, sha]
82
+ tree_contents += str
83
+ when Hash:
84
+ sha = write_tree(v)
85
+ sha = [sha].pack("H*")
86
+ str = "%s %s\0%s" % ['040000', k, sha]
87
+ tree_contents += str
88
+ end
89
+ end
90
+ self.repo.git.ruby_git.put_raw_object(tree_contents, 'tree')
91
+ end
92
+
93
+ # Write the blob to the index
94
+ # +data+ is the data to write
95
+ #
96
+ # Returns the SHA1 String of the blob
97
+ def write_blob(data)
98
+ self.repo.git.ruby_git.put_raw_object(data, 'blob')
99
+ end
100
+ end # Index
101
+
102
+ 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/ref.rb ADDED
@@ -0,0 +1,95 @@
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
+
15
+ Dir.chdir(repo.path) do
16
+ if File.file?('packed-refs')
17
+ File.readlines('packed-refs').each do |line|
18
+ if m = /^(\w{40}) (.*?)$/.match(line)
19
+ next if !Regexp.new('^' + prefix).match(m[2])
20
+ name = m[2].sub("#{prefix}/", '')
21
+ commit = Commit.create(repo, :id => m[1])
22
+ refs << self.new(name, commit)
23
+ end
24
+ end
25
+ end
26
+
27
+ files = Dir.glob(prefix + '/**/*')
28
+ files.each do |ref|
29
+ next if !File.file?(ref)
30
+ id = File.read(ref).chomp
31
+ name = ref.sub("#{prefix}/", '')
32
+ commit = Commit.create(repo, :id => id)
33
+ refs << self.new(name, commit)
34
+ end
35
+ end
36
+
37
+ refs
38
+ end
39
+
40
+ protected
41
+
42
+ def prefix
43
+ "refs/#{name.to_s.gsub(/^.*::/, '').downcase}s"
44
+ end
45
+
46
+ end
47
+
48
+ attr_reader :name
49
+ attr_reader :commit
50
+
51
+ # Instantiate a new Head
52
+ # +name+ is the name of the head
53
+ # +commit+ is the Commit that the head points to
54
+ #
55
+ # Returns Grit::Head (baked)
56
+ def initialize(name, commit)
57
+ @name = name
58
+ @commit = commit
59
+ end
60
+
61
+ # Pretty object inspection
62
+ def inspect
63
+ %Q{#<#{self.class.name} "#{@name}">}
64
+ end
65
+ end # Ref
66
+
67
+ # A Head is a named reference to a Commit. Every Head instance contains a name
68
+ # and a Commit object.
69
+ #
70
+ # r = Grit::Repo.new("/path/to/repo")
71
+ # h = r.heads.first
72
+ # h.name # => "master"
73
+ # h.commit # => #<Grit::Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455">
74
+ # h.commit.id # => "1c09f116cbc2cb4100fb6935bb162daa4723f455"
75
+ class Head < Ref
76
+
77
+ # Get the HEAD revision of the repo.
78
+ # +repo+ is the Repo
79
+ # +options+ is a Hash of options
80
+ #
81
+ # Returns Grit::Head (baked)
82
+ def self.current(repo, options = {})
83
+ head = File.open(File.join(repo.path, 'HEAD')).read.chomp
84
+ if /ref: refs\/heads\/(.*)/.match(head)
85
+ self.new($1, repo.git.rev_parse(options, 'HEAD'))
86
+ end
87
+ end
88
+
89
+ end # Head
90
+
91
+ class Tag < Ref ; end
92
+
93
+ class Remote < Ref; end
94
+
95
+ end # Grit
data/lib/grit/repo.rb ADDED
@@ -0,0 +1,381 @@
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, options = {})
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$/ || options[:is_bare])
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
+ def self.init(path)
40
+ # !! TODO !!
41
+ # create directory
42
+ # generate initial git directory
43
+ # create new Grit::Repo on that dir, return it
44
+ end
45
+
46
+ # The project's description. Taken verbatim from GIT_REPO/description
47
+ #
48
+ # Returns String
49
+ def description
50
+ File.open(File.join(self.path, 'description')).read.chomp
51
+ end
52
+
53
+ # An array of Head objects representing the branch heads in
54
+ # this repo
55
+ #
56
+ # Returns Grit::Head[] (baked)
57
+ def heads
58
+ Head.find_all(self)
59
+ end
60
+
61
+ alias_method :branches, :heads
62
+
63
+ # Object reprsenting the current repo head.
64
+ #
65
+ # Returns Grit::Head (baked)
66
+ def head
67
+ Head.current(self)
68
+ end
69
+
70
+
71
+ # Commits current index
72
+ #
73
+ # Returns true/false if commit worked
74
+ def commit_index(message)
75
+ self.git.commit({}, '-m', message)
76
+ end
77
+
78
+ # Commits all tracked and modified files
79
+ #
80
+ # Returns true/false if commit worked
81
+ def commit_all(message)
82
+ self.git.commit({}, '-a', '-m', message)
83
+ end
84
+
85
+ # Adds files to the index
86
+ def add(*files)
87
+ self.git.add({}, *files.flatten)
88
+ end
89
+
90
+ # Adds files to the index
91
+ def remove(*files)
92
+ self.git.rm({}, *files.flatten)
93
+ end
94
+
95
+
96
+ def blame_tree(commit, path = nil)
97
+ commit_array = self.git.blame_tree(commit, path)
98
+
99
+ final_array = {}
100
+ commit_array.each do |file, sha|
101
+ final_array[file] = commit(sha)
102
+ end
103
+ final_array
104
+ end
105
+
106
+ def status
107
+ Status.new(self)
108
+ end
109
+
110
+
111
+ # An array of Tag objects that are available in this repo
112
+ #
113
+ # Returns Grit::Tag[] (baked)
114
+ def tags
115
+ Tag.find_all(self)
116
+ end
117
+
118
+ # An array of Remote objects representing the remote branches in
119
+ # this repo
120
+ #
121
+ # Returns Grit::Remote[] (baked)
122
+ def remotes
123
+ Remote.find_all(self)
124
+ end
125
+
126
+ # An array of Ref objects representing the refs in
127
+ # this repo
128
+ #
129
+ # Returns Grit::Ref[] (baked)
130
+ def refs
131
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
132
+ end
133
+
134
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
135
+ options = {:max_count => max_count,
136
+ :skip => skip}
137
+
138
+ CommitStats.find_all(self, start, options)
139
+ end
140
+
141
+ # An array of Commit objects representing the history of a given ref/commit
142
+ # +start+ is the branch/commit name (default 'master')
143
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
144
+ # +skip+ is the number of commits to skip (default 0)
145
+ #
146
+ # Returns Grit::Commit[] (baked)
147
+ def commits(start = 'master', max_count = 10, skip = 0)
148
+ options = {:max_count => max_count,
149
+ :skip => skip}
150
+
151
+ Commit.find_all(self, start, options)
152
+ end
153
+
154
+ # The Commits objects that are reachable via +to+ but not via +from+
155
+ # Commits are returned in chronological order.
156
+ # +from+ is the branch/commit name of the younger item
157
+ # +to+ is the branch/commit name of the older item
158
+ #
159
+ # Returns Grit::Commit[] (baked)
160
+ def commits_between(from, to)
161
+ Commit.find_all(self, "#{from}..#{to}").reverse
162
+ end
163
+
164
+ # The Commits objects that are newer than the specified date.
165
+ # Commits are returned in chronological order.
166
+ # +start+ is the branch/commit name (default 'master')
167
+ # +since+ is a string represeting a date/time
168
+ # +extra_options+ is a hash of extra options
169
+ #
170
+ # Returns Grit::Commit[] (baked)
171
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
172
+ options = {:since => since}.merge(extra_options)
173
+
174
+ Commit.find_all(self, start, options)
175
+ end
176
+
177
+ # The number of commits reachable by the given branch/commit
178
+ # +start+ is the branch/commit name (default 'master')
179
+ #
180
+ # Returns Integer
181
+ def commit_count(start = 'master')
182
+ Commit.count(self, start)
183
+ end
184
+
185
+ # The Commit object for the specified id
186
+ # +id+ is the SHA1 identifier of the commit
187
+ #
188
+ # Returns Grit::Commit (baked)
189
+ def commit(id)
190
+ options = {:max_count => 1}
191
+
192
+ Commit.find_all(self, id, options).first
193
+ end
194
+
195
+ # The Tree object for the given treeish reference
196
+ # +treeish+ is the reference (default 'master')
197
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
198
+ #
199
+ # Examples
200
+ # repo.tree('master', ['lib/'])
201
+ #
202
+ # Returns Grit::Tree (baked)
203
+ def tree(treeish = 'master', paths = [])
204
+ Tree.construct(self, treeish, paths)
205
+ end
206
+
207
+ # The Blob object for the given id
208
+ # +id+ is the SHA1 id of the blob
209
+ #
210
+ # Returns Grit::Blob (unbaked)
211
+ def blob(id)
212
+ Blob.create(self, :id => id)
213
+ end
214
+
215
+ # The commit log for a treeish
216
+ #
217
+ # Returns Grit::Commit[]
218
+ def log(commit = 'master', path = nil, options = {})
219
+ default_options = {:pretty => "raw"}
220
+ actual_options = default_options.merge(options)
221
+ arg = path ? [commit, '--', path] : [commit]
222
+ commits = self.git.log(actual_options, *arg)
223
+ Commit.list_from_string(self, commits)
224
+ end
225
+
226
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
227
+ # +a+ is the base commit
228
+ # +b+ is the other commit
229
+ # +paths+ is an optional list of file paths on which to restrict the diff
230
+ def diff(a, b, *paths)
231
+ self.git.diff({}, a, b, '--', *paths)
232
+ end
233
+
234
+ # The commit diff for the given commit
235
+ # +commit+ is the commit name/id
236
+ #
237
+ # Returns Grit::Diff[]
238
+ def commit_diff(commit)
239
+ Commit.diff(self, commit)
240
+ end
241
+
242
+ # Initialize a bare git repository at the given path
243
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
244
+ # +options+ is any additional options to the git init command
245
+ #
246
+ # Examples
247
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
248
+ #
249
+ # Returns Grit::Repo (the newly created repo)
250
+ def self.init_bare(path, git_options = {}, repo_options = {})
251
+ git = Git.new(path)
252
+ git.init(git_options)
253
+ self.new(path, repo_options)
254
+ end
255
+
256
+ # Fork a bare git repository from this repo
257
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
258
+ # +options+ is any additional options to the git clone command
259
+ #
260
+ # Returns Grit::Repo (the newly forked repo)
261
+ def fork_bare(path, options = {})
262
+ default_options = {:bare => true, :shared => true}
263
+ real_options = default_options.merge(options)
264
+ self.git.clone(real_options, self.path, path)
265
+ Repo.new(path)
266
+ end
267
+
268
+ # Archive the given treeish
269
+ # +treeish+ is the treeish name/id (default 'master')
270
+ # +prefix+ is the optional prefix
271
+ #
272
+ # Examples
273
+ # repo.archive_tar
274
+ # # => <String containing tar archive>
275
+ #
276
+ # repo.archive_tar('a87ff14')
277
+ # # => <String containing tar archive for commit a87ff14>
278
+ #
279
+ # repo.archive_tar('master', 'myproject/')
280
+ # # => <String containing tar archive and prefixed with 'myproject/'>
281
+ #
282
+ # Returns String (containing tar archive)
283
+ def archive_tar(treeish = 'master', prefix = nil)
284
+ options = {}
285
+ options[:prefix] = prefix if prefix
286
+ self.git.archive(options, treeish)
287
+ end
288
+
289
+ # Archive and gzip the given treeish
290
+ # +treeish+ is the treeish name/id (default 'master')
291
+ # +prefix+ is the optional prefix
292
+ #
293
+ # Examples
294
+ # repo.archive_tar_gz
295
+ # # => <String containing tar.gz archive>
296
+ #
297
+ # repo.archive_tar_gz('a87ff14')
298
+ # # => <String containing tar.gz archive for commit a87ff14>
299
+ #
300
+ # repo.archive_tar_gz('master', 'myproject/')
301
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
302
+ #
303
+ # Returns String (containing tar.gz archive)
304
+ def archive_tar_gz(treeish = 'master', prefix = nil)
305
+ options = {}
306
+ options[:prefix] = prefix if prefix
307
+ self.git.archive(options, treeish, "| gzip")
308
+ end
309
+
310
+ # run archive directly to a file
311
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz')
312
+ options = {}
313
+ options[:prefix] = prefix if prefix
314
+ self.git.archive(options, treeish, "| gzip > #{filename}")
315
+ end
316
+
317
+
318
+ # Enable git-daemon serving of this repository by writing the
319
+ # git-daemon-export-ok file to its git directory
320
+ #
321
+ # Returns nothing
322
+ def enable_daemon_serve
323
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
324
+ end
325
+
326
+ # Disable git-daemon serving of this repository by ensuring there is no
327
+ # git-daemon-export-ok file in its git directory
328
+ #
329
+ # Returns nothing
330
+ def disable_daemon_serve
331
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
332
+ end
333
+
334
+ # The list of alternates for this repo
335
+ #
336
+ # Returns Array[String] (pathnames of alternates)
337
+ def alternates
338
+ alternates_path = File.join(self.path, *%w{objects info alternates})
339
+
340
+ if File.exist?(alternates_path)
341
+ File.read(alternates_path).strip.split("\n")
342
+ else
343
+ []
344
+ end
345
+ end
346
+
347
+ # Sets the alternates
348
+ # +alts+ is the Array of String paths representing the alternates
349
+ #
350
+ # Returns nothing
351
+ def alternates=(alts)
352
+ alts.each do |alt|
353
+ unless File.exist?(alt)
354
+ raise "Could not set alternates. Alternate path #{alt} must exist"
355
+ end
356
+ end
357
+
358
+ if alts.empty?
359
+ File.delete(File.join(self.path, *%w{objects info alternates}))
360
+ else
361
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
362
+ f.write alts.join("\n")
363
+ end
364
+ end
365
+ end
366
+
367
+ def config
368
+ @config ||= Config.new(self)
369
+ end
370
+
371
+ def index
372
+ Index.new(self)
373
+ end
374
+
375
+ # Pretty object inspection
376
+ def inspect
377
+ %Q{#<Grit::Repo "#{@path}">}
378
+ end
379
+ end # Repo
380
+
381
+ end # Grit