schacon-grit 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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