pjhyett-grit 0.9.11

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 +71 -0
  3. data/README.txt +213 -0
  4. data/Rakefile +29 -0
  5. data/grit.gemspec +62 -0
  6. data/lib/grit.rb +54 -0
  7. data/lib/grit/actor.rb +36 -0
  8. data/lib/grit/blob.rb +117 -0
  9. data/lib/grit/commit.rb +229 -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 +184 -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 +137 -0
  19. data/lib/grit/git-ruby/internal/mmap.rb +58 -0
  20. data/lib/grit/git-ruby/internal/pack.rb +382 -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 +731 -0
  24. data/lib/grit/git.rb +122 -0
  25. data/lib/grit/head.rb +83 -0
  26. data/lib/grit/index.rb +121 -0
  27. data/lib/grit/lazy.rb +33 -0
  28. data/lib/grit/ref.rb +95 -0
  29. data/lib/grit/repo.rb +387 -0
  30. data/lib/grit/status.rb +151 -0
  31. data/lib/grit/tag.rb +71 -0
  32. data/lib/grit/tree.rb +104 -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 +47 -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 +128 -0
data/lib/grit/repo.rb ADDED
@@ -0,0 +1,387 @@
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
+ #
17
+ # Examples
18
+ # g = Repo.new("/Users/tom/dev/grit")
19
+ # g = Repo.new("/Users/tom/public/grit.git")
20
+ #
21
+ # Returns Grit::Repo
22
+ def initialize(path, options = {})
23
+ epath = File.expand_path(path)
24
+
25
+ if File.exist?(File.join(epath, '.git'))
26
+ self.working_dir = epath
27
+ self.path = File.join(epath, '.git')
28
+ @bare = false
29
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
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
+ def self.init(path)
42
+ # !! TODO !!
43
+ # create directory
44
+ # generate initial git directory
45
+ # create new Grit::Repo on that dir, return it
46
+ end
47
+
48
+ # The project's description. Taken verbatim from GIT_REPO/description
49
+ #
50
+ # Returns String
51
+ def description
52
+ File.open(File.join(self.path, 'description')).read.chomp
53
+ end
54
+
55
+ # An array of Head objects representing the branch heads in
56
+ # this repo
57
+ #
58
+ # Returns Grit::Head[] (baked)
59
+ def heads
60
+ Head.find_all(self)
61
+ end
62
+
63
+ alias_method :branches, :heads
64
+
65
+ def is_head?(head_name)
66
+ heads.find { |h| h.name == head_name }
67
+ end
68
+
69
+ # Object reprsenting the current repo head.
70
+ #
71
+ # Returns Grit::Head (baked)
72
+ def head
73
+ Head.current(self)
74
+ end
75
+
76
+
77
+ # Commits current index
78
+ #
79
+ # Returns true/false if commit worked
80
+ def commit_index(message)
81
+ self.git.commit({}, '-m', message)
82
+ end
83
+
84
+ # Commits all tracked and modified files
85
+ #
86
+ # Returns true/false if commit worked
87
+ def commit_all(message)
88
+ self.git.commit({}, '-a', '-m', message)
89
+ end
90
+
91
+ # Adds files to the index
92
+ def add(*files)
93
+ self.git.add({}, *files.flatten)
94
+ end
95
+
96
+ # Adds files to the index
97
+ def remove(*files)
98
+ self.git.rm({}, *files.flatten)
99
+ end
100
+
101
+
102
+ def blame_tree(commit, path = nil)
103
+ commit_array = self.git.blame_tree(commit, path)
104
+
105
+ final_array = {}
106
+ commit_array.each do |file, sha|
107
+ final_array[file] = commit(sha)
108
+ end
109
+ final_array
110
+ end
111
+
112
+ def status
113
+ Status.new(self)
114
+ end
115
+
116
+
117
+ # An array of Tag objects that are available in this repo
118
+ #
119
+ # Returns Grit::Tag[] (baked)
120
+ def tags
121
+ Tag.find_all(self)
122
+ end
123
+
124
+ # An array of Remote objects representing the remote branches in
125
+ # this repo
126
+ #
127
+ # Returns Grit::Remote[] (baked)
128
+ def remotes
129
+ Remote.find_all(self)
130
+ end
131
+
132
+ # An array of Ref objects representing the refs in
133
+ # this repo
134
+ #
135
+ # Returns Grit::Ref[] (baked)
136
+ def refs
137
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
138
+ end
139
+
140
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
141
+ options = {:max_count => max_count,
142
+ :skip => skip}
143
+
144
+ CommitStats.find_all(self, start, options)
145
+ end
146
+
147
+ # An array of Commit objects representing the history of a given ref/commit
148
+ # +start+ is the branch/commit name (default 'master')
149
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
150
+ # +skip+ is the number of commits to skip (default 0)
151
+ #
152
+ # Returns Grit::Commit[] (baked)
153
+ def commits(start = 'master', max_count = 10, skip = 0)
154
+ options = {:max_count => max_count,
155
+ :skip => skip}
156
+
157
+ Commit.find_all(self, start, options)
158
+ end
159
+
160
+ # The Commits objects that are reachable via +to+ but not via +from+
161
+ # Commits are returned in chronological order.
162
+ # +from+ is the branch/commit name of the younger item
163
+ # +to+ is the branch/commit name of the older item
164
+ #
165
+ # Returns Grit::Commit[] (baked)
166
+ def commits_between(from, to)
167
+ Commit.find_all(self, "#{from}..#{to}").reverse
168
+ end
169
+
170
+ # The Commits objects that are newer than the specified date.
171
+ # Commits are returned in chronological order.
172
+ # +start+ is the branch/commit name (default 'master')
173
+ # +since+ is a string represeting a date/time
174
+ # +extra_options+ is a hash of extra options
175
+ #
176
+ # Returns Grit::Commit[] (baked)
177
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
178
+ options = {:since => since}.merge(extra_options)
179
+
180
+ Commit.find_all(self, start, options)
181
+ end
182
+
183
+ # The number of commits reachable by the given branch/commit
184
+ # +start+ is the branch/commit name (default 'master')
185
+ #
186
+ # Returns Integer
187
+ def commit_count(start = 'master')
188
+ Commit.count(self, start)
189
+ end
190
+
191
+ # The Commit object for the specified id
192
+ # +id+ is the SHA1 identifier of the commit
193
+ #
194
+ # Returns Grit::Commit (baked)
195
+ def commit(id)
196
+ options = {:max_count => 1}
197
+
198
+ Commit.find_all(self, id, options).first
199
+ end
200
+
201
+ # The Tree object for the given treeish reference
202
+ # +treeish+ is the reference (default 'master')
203
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
204
+ #
205
+ # Examples
206
+ # repo.tree('master', ['lib/'])
207
+ #
208
+ # Returns Grit::Tree (baked)
209
+ def tree(treeish = 'master', paths = [])
210
+ Tree.construct(self, treeish, paths)
211
+ end
212
+
213
+ # The Blob object for the given id
214
+ # +id+ is the SHA1 id of the blob
215
+ #
216
+ # Returns Grit::Blob (unbaked)
217
+ def blob(id)
218
+ Blob.create(self, :id => id)
219
+ end
220
+
221
+ # The commit log for a treeish
222
+ #
223
+ # Returns Grit::Commit[]
224
+ def log(commit = 'master', path = nil, options = {})
225
+ default_options = {:pretty => "raw"}
226
+ actual_options = default_options.merge(options)
227
+ arg = path ? [commit, '--', path] : [commit]
228
+ commits = self.git.log(actual_options, *arg)
229
+ Commit.list_from_string(self, commits)
230
+ end
231
+
232
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
233
+ # +a+ is the base commit
234
+ # +b+ is the other commit
235
+ # +paths+ is an optional list of file paths on which to restrict the diff
236
+ def diff(a, b, *paths)
237
+ self.git.diff({}, a, b, '--', *paths)
238
+ end
239
+
240
+ # The commit diff for the given commit
241
+ # +commit+ is the commit name/id
242
+ #
243
+ # Returns Grit::Diff[]
244
+ def commit_diff(commit)
245
+ Commit.diff(self, commit)
246
+ end
247
+
248
+ # Initialize a bare git repository at the given path
249
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
250
+ # +options+ is any additional options to the git init command
251
+ #
252
+ # Examples
253
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
254
+ #
255
+ # Returns Grit::Repo (the newly created repo)
256
+ def self.init_bare(path, git_options = {}, repo_options = {})
257
+ git = Git.new(path)
258
+ git.init(git_options)
259
+ self.new(path, repo_options)
260
+ end
261
+
262
+ # Fork a bare git repository from this repo
263
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
264
+ # +options+ is any additional options to the git clone command
265
+ #
266
+ # Returns Grit::Repo (the newly forked repo)
267
+ def fork_bare(path, options = {})
268
+ default_options = {:bare => true, :shared => true}
269
+ real_options = default_options.merge(options)
270
+ self.git.clone(real_options, self.path, path)
271
+ Repo.new(path)
272
+ end
273
+
274
+ # Archive the given treeish
275
+ # +treeish+ is the treeish name/id (default 'master')
276
+ # +prefix+ is the optional prefix
277
+ #
278
+ # Examples
279
+ # repo.archive_tar
280
+ # # => <String containing tar archive>
281
+ #
282
+ # repo.archive_tar('a87ff14')
283
+ # # => <String containing tar archive for commit a87ff14>
284
+ #
285
+ # repo.archive_tar('master', 'myproject/')
286
+ # # => <String containing tar archive and prefixed with 'myproject/'>
287
+ #
288
+ # Returns String (containing tar archive)
289
+ def archive_tar(treeish = 'master', prefix = nil)
290
+ options = {}
291
+ options[:prefix] = prefix if prefix
292
+ self.git.archive(options, treeish)
293
+ end
294
+
295
+ # Archive and gzip the given treeish
296
+ # +treeish+ is the treeish name/id (default 'master')
297
+ # +prefix+ is the optional prefix
298
+ #
299
+ # Examples
300
+ # repo.archive_tar_gz
301
+ # # => <String containing tar.gz archive>
302
+ #
303
+ # repo.archive_tar_gz('a87ff14')
304
+ # # => <String containing tar.gz archive for commit a87ff14>
305
+ #
306
+ # repo.archive_tar_gz('master', 'myproject/')
307
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
308
+ #
309
+ # Returns String (containing tar.gz archive)
310
+ def archive_tar_gz(treeish = 'master', prefix = nil)
311
+ options = {}
312
+ options[:prefix] = prefix if prefix
313
+ self.git.archive(options, treeish, "| gzip")
314
+ end
315
+
316
+ # run archive directly to a file
317
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz')
318
+ options = {}
319
+ options[:prefix] = prefix if prefix
320
+ self.git.archive(options, treeish, "| gzip > #{filename}")
321
+ end
322
+
323
+
324
+ # Enable git-daemon serving of this repository by writing the
325
+ # git-daemon-export-ok file to its git directory
326
+ #
327
+ # Returns nothing
328
+ def enable_daemon_serve
329
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
330
+ end
331
+
332
+ # Disable git-daemon serving of this repository by ensuring there is no
333
+ # git-daemon-export-ok file in its git directory
334
+ #
335
+ # Returns nothing
336
+ def disable_daemon_serve
337
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
338
+ end
339
+
340
+ # The list of alternates for this repo
341
+ #
342
+ # Returns Array[String] (pathnames of alternates)
343
+ def alternates
344
+ alternates_path = File.join(self.path, *%w{objects info alternates})
345
+
346
+ if File.exist?(alternates_path)
347
+ File.read(alternates_path).strip.split("\n")
348
+ else
349
+ []
350
+ end
351
+ end
352
+
353
+ # Sets the alternates
354
+ # +alts+ is the Array of String paths representing the alternates
355
+ #
356
+ # Returns nothing
357
+ def alternates=(alts)
358
+ alts.each do |alt|
359
+ unless File.exist?(alt)
360
+ raise "Could not set alternates. Alternate path #{alt} must exist"
361
+ end
362
+ end
363
+
364
+ if alts.empty?
365
+ File.delete(File.join(self.path, *%w{objects info alternates}))
366
+ else
367
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
368
+ f.write alts.join("\n")
369
+ end
370
+ end
371
+ end
372
+
373
+ def config
374
+ @config ||= Config.new(self)
375
+ end
376
+
377
+ def index
378
+ Index.new(self)
379
+ end
380
+
381
+ # Pretty object inspection
382
+ def inspect
383
+ %Q{#<Grit::Repo "#{@path}">}
384
+ end
385
+ end # Repo
386
+
387
+ end # Grit
@@ -0,0 +1,151 @@
1
+ module Grit
2
+
3
+ class Status
4
+ include Enumerable
5
+
6
+ @base = nil
7
+ @files = nil
8
+
9
+ def initialize(base)
10
+ @base = base
11
+ construct_status
12
+ end
13
+
14
+ def changed
15
+ @files.select { |k, f| f.type == 'M' }
16
+ end
17
+
18
+ def added
19
+ @files.select { |k, f| f.type == 'A' }
20
+ end
21
+
22
+ def deleted
23
+ @files.select { |k, f| f.type == 'D' }
24
+ end
25
+
26
+ def untracked
27
+ @files.select { |k, f| f.untracked }
28
+ end
29
+
30
+ def pretty
31
+ out = ''
32
+ self.each do |file|
33
+ out << file.path
34
+ out << "\n\tsha(r) " + file.sha_repo.to_s + ' ' + file.mode_repo.to_s
35
+ out << "\n\tsha(i) " + file.sha_index.to_s + ' ' + file.mode_index.to_s
36
+ out << "\n\ttype " + file.type.to_s
37
+ out << "\n\tstage " + file.stage.to_s
38
+ out << "\n\tuntrac " + file.untracked.to_s
39
+ out << "\n"
40
+ end
41
+ out << "\n"
42
+ out
43
+ end
44
+
45
+ # enumerable method
46
+
47
+ def [](file)
48
+ @files[file]
49
+ end
50
+
51
+ def each
52
+ @files.each do |k, file|
53
+ yield file
54
+ end
55
+ end
56
+
57
+ class StatusFile
58
+ attr_accessor :path, :type, :stage, :untracked
59
+ attr_accessor :mode_index, :mode_repo
60
+ attr_accessor :sha_index, :sha_repo
61
+
62
+ @base = nil
63
+
64
+ def initialize(base, hash)
65
+ @base = base
66
+ @path = hash[:path]
67
+ @type = hash[:type]
68
+ @stage = hash[:stage]
69
+ @mode_index = hash[:mode_index]
70
+ @mode_repo = hash[:mode_repo]
71
+ @sha_index = hash[:sha_index]
72
+ @sha_repo = hash[:sha_repo]
73
+ @untracked = hash[:untracked]
74
+ end
75
+
76
+ def blob(type = :index)
77
+ if type == :repo
78
+ @base.object(@sha_repo)
79
+ else
80
+ @base.object(@sha_index) rescue @base.object(@sha_repo)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ private
87
+
88
+ def construct_status
89
+ @files = ls_files
90
+
91
+ Dir.chdir(@base.working_dir) do
92
+ # find untracked in working dir
93
+ Dir.glob('**/*') do |file|
94
+ if !@files[file]
95
+ @files[file] = {:path => file, :untracked => true} if !File.directory?(file)
96
+ end
97
+ end
98
+
99
+ # find modified in tree
100
+ diff_files.each do |path, data|
101
+ @files[path] ? @files[path].merge!(data) : @files[path] = data
102
+ end
103
+
104
+ # find added but not committed - new files
105
+ diff_index('HEAD').each do |path, data|
106
+ @files[path] ? @files[path].merge!(data) : @files[path] = data
107
+ end
108
+
109
+ @files.each do |k, file_hash|
110
+ @files[k] = StatusFile.new(@base, file_hash)
111
+ end
112
+ end
113
+ end
114
+
115
+ # compares the index and the working directory
116
+ def diff_files
117
+ hsh = {}
118
+ @base.git.diff_files.split("\n").each do |line|
119
+ (info, file) = line.split("\t")
120
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
121
+ hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
122
+ :sha_file => sha_src, :sha_index => sha_dest, :type => type}
123
+ end
124
+ hsh
125
+ end
126
+
127
+ # compares the index and the repository
128
+ def diff_index(treeish)
129
+ hsh = {}
130
+ @base.git.diff_index({}, treeish).split("\n").each do |line|
131
+ (info, file) = line.split("\t")
132
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
133
+ hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
134
+ :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
135
+ end
136
+ hsh
137
+ end
138
+
139
+ def ls_files
140
+ hsh = {}
141
+ lines = @base.git.ls_files({:stage => true})
142
+ lines.split("\n").each do |line|
143
+ (info, file) = line.split("\t")
144
+ (mode, sha, stage) = info.split
145
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
146
+ end
147
+ hsh
148
+ end
149
+ end
150
+
151
+ end