Blob of vendor/grit/lib/grit/repo.rb (raw blob data)

1 require "enumerator"
2
3 module Grit
4
5 class Repo
6 DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
7
8 # The path of the git repo as a String
9 attr_accessor :path
10 attr_reader :bare
11
12 # The git command line interface object
13 attr_accessor :git
14
15 # Create a new Repo instance
16 # +path+ is the path to either the root git directory or the bare git 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)
24 epath = File.expand_path(path)
25
26 if File.exist?(File.join(epath, '.git'))
27 self.path = File.join(epath, '.git')
28 @bare = false
29 elsif File.exist?(epath) && epath =~ /\.git$/
30 self.path = epath
31 @bare = true
32 elsif File.exist?(epath)
33 raise InvalidGitRepositoryError.new(epath)
34 else
35 raise NoSuchPathError.new(epath)
36 end
37
38 self.git = Git.new(self.path)
39 end
40
41 # The project's description. Taken verbatim from GIT_REPO/description
42 #
43 # Returns String
44 def description
45 File.open(File.join(self.path, 'description')).read.chomp
46 end
47
48 # An array of Head objects representing the branch heads in
49 # this repo
50 #
51 # Returns Grit::Head[] (baked)
52 def heads
53 Head.find_all(self)
54 end
55
56 alias_method :branches, :heads
57
58 # An array of Tag objects that are available in this repo
59 #
60 # Returns Grit::Tag[] (baked)
61 def tags
62 Tag.find_all(self)
63 end
64
65 # An array of Commit objects representing the history of a given ref/commit
66 # +start+ is the branch/commit name (default 'master')
67 # +max_count+ is the maximum number of commits to return (default 10)
68 # +skip+ is the number of commits to skip (default 0)
69 #
70 # Returns Grit::Commit[] (baked)
71 def commits(start = 'master', max_count = 10, skip = 0)
72 options = {:max_count => max_count,
73 :skip => skip}
74
75 Commit.find_all(self, start, options)
76 end
77
78 # The Commits objects that are reachable via +to+ but not via +from+
79 # Commits are returned in chronological order.
80 # +from+ is the branch/commit name of the younger item
81 # +to+ is the branch/commit name of the older item
82 #
83 # Returns Grit::Commit[] (baked)
84 def commits_between(from, to)
85 Commit.find_all(self, "#{from}..#{to}").reverse
86 end
87
88 # The Commits objects that are newer than the specified date.
89 # Commits are returned in chronological order.
90 # +start+ is the branch/commit name (default 'master')
91 # +since+ is a string represeting a date/time
92 # +extra_options+ is a hash of extra options
93 #
94 # Returns Grit::Commit[] (baked)
95 def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
96 options = {:since => since}.merge(extra_options)
97
98 Commit.find_all(self, start, options)
99 end
100
101 # The number of commits reachable by the given branch/commit
102 # +start+ is the branch/commit name (default 'master')
103 #
104 # Returns Integer
105 def commit_count(start = 'master')
106 Commit.count(self, start)
107 end
108
109 # The Commit object for the specified id
110 # +id+ is the SHA1 identifier of the commit
111 #
112 # Returns Grit::Commit (baked)
113 def commit(id)
114 options = {:max_count => 1}
115
116 Commit.find_all(self, id, options).first
117 end
118
119 # Returns a list of commits that is in +other_repo+ but not in self
120 #
121 # Returns Grit::Commit[]
122 def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
123 repo_refs = self.git.rev_list({}, ref).strip.split("\n")
124 other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
125
126 (other_repo_refs - repo_refs).map do |ref|
127 Commit.find_all(other_repo, ref, {:max_count => 1}).first
128 end
129
130 # commits = []
131 # (other_repo_refs - repo_refs).each_slice(5) do |refs| # due to cmdline arg length
132 # commits.concat Commit.find_all(self, refs.join(" "), {:max_count => refs.size})
133 # end
134 # commits
135 end
136
137 # The Tree object for the given treeish reference
138 # +treeish+ is the reference (default 'master')
139 # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
140 #
141 # Examples
142 # repo.tree('master', ['lib/'])
143 #
144 # Returns Grit::Tree (baked)
145 def tree(treeish = 'master', paths = [])
146 Tree.construct(self, treeish, paths)
147 end
148
149 # The Blob object for the given id
150 # +id+ is the SHA1 id of the blob
151 #
152 # Returns Grit::Blob (unbaked)
153 def blob(id)
154 Blob.create(self, :id => id)
155 end
156
157 # The commit log for a treeish
158 #
159 # Returns Grit::Commit[]
160 def log(commit = 'master', path = nil, options = {})
161 default_options = {:pretty => "raw"}
162 actual_options = default_options.merge(options)
163 arg = path ? [commit, '--', path] : [commit]
164 commits = self.git.log(actual_options, *arg)
165 Commit.list_from_string(self, commits)
166 end
167
168 # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
169 # +a+ is the base commit
170 # +b+ is the other commit
171 # +paths+ is an optional list of file paths on which to restrict the diff
172 def diff(a, b, *paths)
173 self.git.diff({}, a, b, '--', *paths)
174 end
175
176 # The commit diff for the given commit
177 # +commit+ is the commit name/id
178 #
179 # Returns Grit::Diff[]
180 def commit_diff(commit)
181 Commit.diff(self, commit)
182 end
183
184 # Initialize a bare git repository at the given path
185 # +path+ is the full path to the repo (traditionally ends with /<name>.git)
186 # +options+ is any additional options to the git init command
187 #
188 # Examples
189 # Grit::Repo.init_bare('/var/git/myrepo.git')
190 #
191 # Returns Grit::Repo (the newly created repo)
192 def self.init_bare(path, options = {})
193 git = Git.new(path)
194 git.init(options)
195 self.new(path)
196 end
197
198 # Fork a bare git repository from this repo
199 # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
200 # +options+ is any additional options to the git clone command
201 #
202 # Returns Grit::Repo (the newly forked repo)
203 def fork_bare(path, options = {})
204 default_options = {:bare => true, :shared => false}
205 real_options = default_options.merge(options)
206 self.git.clone(real_options, self.path, path)
207 Repo.new(path)
208 end
209
210 # Archive the given treeish
211 # +treeish+ is the treeish name/id (default 'master')
212 # +prefix+ is the optional prefix
213 #
214 # Examples
215 # repo.archive_tar
216 # # => <String containing tar archive>
217 #
218 # repo.archive_tar('a87ff14')
219 # # => <String containing tar archive for commit a87ff14>
220 #
221 # repo.archive_tar('master', 'myproject/')
222 # # => <String containing tar archive and prefixed with 'myproject/'>
223 #
224 # Returns String (containing tar archive)
225 def archive_tar(treeish = 'master', prefix = nil)
226 options = {}
227 options[:prefix] = prefix if prefix
228 self.git.archive(options, treeish)
229 end
230
231 # Archive and gzip the given treeish
232 # +treeish+ is the treeish name/id (default 'master')
233 # +prefix+ is the optional prefix
234 #
235 # Examples
236 # repo.archive_tar_gz
237 # # => <String containing tar.gz archive>
238 #
239 # repo.archive_tar_gz('a87ff14')
240 # # => <String containing tar.gz archive for commit a87ff14>
241 #
242 # repo.archive_tar_gz('master', 'myproject/')
243 # # => <String containing tar.gz archive and prefixed with 'myproject/'>
244 #
245 # Returns String (containing tar.gz archive)
246 def archive_tar_gz(treeish = 'master', prefix = nil)
247 options = {}
248 options[:prefix] = prefix if prefix
249 self.git.archive(options, treeish, "| gzip")
250 end
251
252 # Enable git-daemon serving of this repository by writing the
253 # git-daemon-export-ok file to its git directory
254 #
255 # Returns nothing
256 def enable_daemon_serve
257 FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
258 end
259
260 # Disable git-daemon serving of this repository by ensuring there is no
261 # git-daemon-export-ok file in its git directory
262 #
263 # Returns nothing
264 def disable_daemon_serve
265 FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
266 end
267
268 # The list of alternates for this repo
269 #
270 # Returns Array[String] (pathnames of alternates)
271 def alternates
272 alternates_path = File.join(self.path, *%w{objects info alternates})
273
274 if File.exist?(alternates_path)
275 File.read(alternates_path).strip.split("\n")
276 else
277 []
278 end
279 end
280
281 # Sets the alternates
282 # +alts+ is the Array of String paths representing the alternates
283 #
284 # Returns nothing
285 def alternates=(alts)
286 alts.each do |alt|
287 unless File.exist?(alt)
288 raise "Could not set alternates. Alternate path #{alt} must exist"
289 end
290 end
291
292 if alts.empty?
293 File.delete(File.join(self.path, *%w{objects info alternates}))
294 else
295 File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
296 f.write alts.join("\n")
297 end
298 end
299 end
300
301 def config
302 @config ||= Config.new(self)
303 end
304
305 # Pretty object inspection
306 def inspect
307 %Q{#<Grit::Repo "#{@path}">}
308 end
309 end # Repo
310
311 end # Grit