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/blob.rb ADDED
@@ -0,0 +1,117 @@
1
+ module Grit
2
+
3
+ class Blob
4
+ DEFAULT_MIME_TYPE = "text/plain"
5
+
6
+ attr_reader :id
7
+ attr_reader :mode
8
+ attr_reader :name
9
+
10
+ # Create an unbaked Blob containing just the specified attributes
11
+ # +repo+ is the Repo
12
+ # +atts+ is a Hash of instance variable data
13
+ #
14
+ # Returns Grit::Blob (unbaked)
15
+ def self.create(repo, atts)
16
+ self.allocate.create_initialize(repo, atts)
17
+ end
18
+
19
+ # Initializer for Blob.create
20
+ # +repo+ is the Repo
21
+ # +atts+ is a Hash of instance variable data
22
+ #
23
+ # Returns Grit::Blob (unbaked)
24
+ def create_initialize(repo, atts)
25
+ @repo = repo
26
+ atts.each do |k, v|
27
+ instance_variable_set("@#{k}".to_sym, v)
28
+ end
29
+ self
30
+ end
31
+
32
+ # The size of this blob in bytes
33
+ #
34
+ # Returns Integer
35
+ def size
36
+ @size ||= @repo.git.cat_file({:s => true}, id).chomp.to_i
37
+ end
38
+
39
+ # The binary contents of this blob.
40
+ #
41
+ # Returns String
42
+ def data
43
+ @data ||= @repo.git.cat_file({:p => true}, id)
44
+ end
45
+
46
+ # The mime type of this file (based on the filename)
47
+ #
48
+ # Returns String
49
+ def mime_type
50
+ guesses = MIME::Types.type_for(self.name) rescue []
51
+ guesses.first ? guesses.first.simplified : DEFAULT_MIME_TYPE
52
+ end
53
+
54
+ # The blame information for the given file at the given commit
55
+ #
56
+ # Returns Array: [Grit::Commit, Array: [<line>]]
57
+ def self.blame(repo, commit, file)
58
+ data = repo.git.blame({:p => true}, commit, '--', file)
59
+
60
+ commits = {}
61
+ blames = []
62
+ info = nil
63
+
64
+ data.split("\n").each do |line|
65
+ parts = line.split(/\s+/, 2)
66
+ case parts.first
67
+ when /^[0-9A-Fa-f]{40}$/
68
+ case line
69
+ when /^([0-9A-Fa-f]{40}) (\d+) (\d+) (\d+)$/
70
+ _, id, origin_line, final_line, group_lines = *line.match(/^([0-9A-Fa-f]{40}) (\d+) (\d+) (\d+)$/)
71
+ info = {:id => id}
72
+ blames << [nil, []]
73
+ when /^([0-9A-Fa-f]{40}) (\d+) (\d+)$/
74
+ _, id, origin_line, final_line = *line.match(/^([0-9A-Fa-f]{40}) (\d+) (\d+)$/)
75
+ info = {:id => id}
76
+ end
77
+ when /^(author|committer)/
78
+ case parts.first
79
+ when /^(.+)-mail$/
80
+ info["#{$1}_email".intern] = parts.last
81
+ when /^(.+)-time$/
82
+ info["#{$1}_date".intern] = Time.at(parts.last.to_i)
83
+ when /^(author|committer)$/
84
+ info[$1.intern] = parts.last
85
+ end
86
+ when /^filename/
87
+ info[:filename] = parts.last
88
+ when /^summary/
89
+ info[:summary] = parts.last
90
+ when ''
91
+ c = commits[info[:id]]
92
+ unless c
93
+ c = Commit.create(repo, :id => info[:id],
94
+ :author => Actor.from_string(info[:author] + ' ' + info[:author_email]),
95
+ :authored_date => info[:author_date],
96
+ :committer => Actor.from_string(info[:committer] + ' ' + info[:committer_email]),
97
+ :committed_date => info[:committer_date],
98
+ :message => info[:summary])
99
+ commits[info[:id]] = c
100
+ end
101
+ _, text = *line.match(/^\t(.*)$/)
102
+ blames.last[0] = c
103
+ blames.last[1] << text
104
+ info = nil
105
+ end
106
+ end
107
+
108
+ blames
109
+ end
110
+
111
+ # Pretty object inspection
112
+ def inspect
113
+ %Q{#<Grit::Blob "#{@id}">}
114
+ end
115
+ end # Blob
116
+
117
+ end # Grit
@@ -0,0 +1,221 @@
1
+ module Grit
2
+
3
+ class Commit
4
+ attr_reader :id
5
+ lazy_reader :parents
6
+ lazy_reader :tree
7
+ lazy_reader :author
8
+ lazy_reader :authored_date
9
+ lazy_reader :committer
10
+ lazy_reader :committed_date
11
+ lazy_reader :message
12
+ lazy_reader :short_message
13
+
14
+ # Instantiate a new Commit
15
+ # +id+ is the id of the commit
16
+ # +parents+ is an array of commit ids (will be converted into Commit instances)
17
+ # +tree+ is the correspdonding tree id (will be converted into a Tree object)
18
+ # +author+ is the author string
19
+ # +authored_date+ is the authored Time
20
+ # +committer+ is the committer string
21
+ # +committed_date+ is the committed Time
22
+ # +message+ is an array of commit message lines
23
+ #
24
+ # Returns Grit::Commit (baked)
25
+ def initialize(repo, id, parents, tree, author, authored_date, committer, committed_date, message)
26
+ @repo = repo
27
+ @id = id
28
+ @parents = parents.map { |p| Commit.create(repo, :id => p) }
29
+ @tree = Tree.create(repo, :id => tree)
30
+ @author = author
31
+ @authored_date = authored_date
32
+ @committer = committer
33
+ @committed_date = committed_date
34
+ @message = message.join("\n")
35
+ @short_message = message[0] || ''
36
+ end
37
+
38
+ def id_abbrev
39
+ @id_abbrev ||= @repo.git.rev_parse({}, self.id).chomp[0, 7]
40
+ end
41
+
42
+ # Create an unbaked Commit containing just the specified attributes
43
+ # +repo+ is the Repo
44
+ # +atts+ is a Hash of instance variable data
45
+ #
46
+ # Returns Grit::Commit (unbaked)
47
+ def self.create(repo, atts)
48
+ self.allocate.create_initialize(repo, atts)
49
+ end
50
+
51
+ # Initializer for Commit.create
52
+ # +repo+ is the Repo
53
+ # +atts+ is a Hash of instance variable data
54
+ #
55
+ # Returns Grit::Commit (unbaked)
56
+ def create_initialize(repo, atts)
57
+ @repo = repo
58
+ atts.each do |k, v|
59
+ instance_variable_set("@#{k}", v)
60
+ end
61
+ self
62
+ end
63
+
64
+ def lazy_source
65
+ self.class.find_all(@repo, @id, {:max_count => 1}).first
66
+ end
67
+
68
+ # Count the number of commits reachable from this ref
69
+ # +repo+ is the Repo
70
+ # +ref+ is the ref from which to begin (SHA1 or name)
71
+ #
72
+ # Returns Integer
73
+ def self.count(repo, ref)
74
+ repo.git.rev_list({}, ref).strip.split("\n").size
75
+ end
76
+
77
+ # Find all commits matching the given criteria.
78
+ # +repo+ is the Repo
79
+ # +ref+ is the ref from which to begin (SHA1 or name) or nil for --all
80
+ # +options+ is a Hash of optional arguments to git
81
+ # :max_count is the maximum number of commits to fetch
82
+ # :skip is the number of commits to skip
83
+ #
84
+ # Returns Grit::Commit[] (baked)
85
+ def self.find_all(repo, ref, options = {})
86
+ allowed_options = [:max_count, :skip, :since]
87
+
88
+ default_options = {:pretty => "raw"}
89
+ actual_options = default_options.merge(options)
90
+
91
+ if ref
92
+ output = repo.git.rev_list(actual_options, ref)
93
+ else
94
+ output = repo.git.rev_list(actual_options.merge(:all => true))
95
+ end
96
+
97
+ self.list_from_string(repo, output)
98
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
99
+ []
100
+ end
101
+
102
+ # Parse out commit information into an array of baked Commit objects
103
+ # +repo+ is the Repo
104
+ # +text+ is the text output from the git command (raw format)
105
+ #
106
+ # Returns Grit::Commit[] (baked)
107
+ #
108
+ # really should re-write this to be more accepting of non-standard commit messages
109
+ # - it broke when 'encoding' was introduced - not sure what else might show up
110
+ #
111
+ def self.list_from_string(repo, text)
112
+ lines = text.split("\n")
113
+
114
+ commits = []
115
+
116
+ while !lines.empty?
117
+ id = lines.shift.split.last
118
+ tree = lines.shift.split.last
119
+
120
+ parents = []
121
+ parents << lines.shift.split.last while lines.first =~ /^parent/
122
+
123
+ author, authored_date = self.actor(lines.shift)
124
+ committer, committed_date = self.actor(lines.shift)
125
+
126
+ # not doing anything with this yet, but it's sometimes there
127
+ encoding = lines.shift.split.last if lines.first =~ /^encoding/
128
+
129
+ lines.shift
130
+
131
+ message_lines = []
132
+ message_lines << lines.shift[4..-1] while lines.first =~ /^ {4}/
133
+
134
+ lines.shift while lines.first && lines.first.empty?
135
+
136
+ commits << Commit.new(repo, id, parents, tree, author, authored_date, committer, committed_date, message_lines)
137
+ end
138
+
139
+ commits
140
+ end
141
+
142
+ # Show diffs between two trees:
143
+ # +repo+ is the Repo
144
+ # +a+ is a named commit
145
+ # +b+ is an optional named commit. Passing an array assumes you
146
+ # wish to omit the second named commit and limit the diff to the
147
+ # given paths.
148
+ # +paths* is an array of paths to limit the diff.
149
+ #
150
+ # Returns Grit::Diff[] (baked)
151
+ def self.diff(repo, a, b = nil, paths = [])
152
+ if b.is_a?(Array)
153
+ paths = b
154
+ b = nil
155
+ end
156
+ paths.unshift("--") unless paths.empty?
157
+ paths.unshift(b) unless b.nil?
158
+ paths.unshift(a)
159
+ text = repo.git.diff({:full_index => true}, *paths)
160
+ Diff.list_from_string(repo, text)
161
+ end
162
+
163
+ def show
164
+ diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
165
+ if diff =~ /diff --git a/
166
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
167
+ else
168
+ diff = ''
169
+ end
170
+ Diff.list_from_string(@repo, diff)
171
+ end
172
+
173
+ def diffs
174
+ if parents.empty?
175
+ show
176
+ else
177
+ self.class.diff(@repo, parents.first.id, @id)
178
+ end
179
+ end
180
+
181
+ # Convert this Commit to a String which is just the SHA1 id
182
+ def to_s
183
+ @id
184
+ end
185
+
186
+ # Pretty object inspection
187
+ def inspect
188
+ %Q{#<Grit::Commit "#{@id}">}
189
+ end
190
+
191
+ # private
192
+
193
+ # Parse out the actor (author or committer) info
194
+ #
195
+ # Returns [String (actor name and email), Time (acted at time)]
196
+ def self.actor(line)
197
+ m, actor, epoch = *line.match(/^.+? (.*) (\d+) .*$/)
198
+ [Actor.from_string(actor), Time.at(epoch.to_i)]
199
+ end
200
+
201
+ def to_hash
202
+ {
203
+ 'id' => id,
204
+ 'parents' => parents.map { |p| { 'id' => p.id } },
205
+ 'tree' => tree.id,
206
+ 'message' => message,
207
+ 'author' => {
208
+ 'name' => author.name,
209
+ 'email' => author.email
210
+ },
211
+ 'committer' => {
212
+ 'name' => committer.name,
213
+ 'email' => committer.email
214
+ },
215
+ 'authored_date' => authored_date.xmlschema,
216
+ 'committed_date' => committed_date.xmlschema,
217
+ }
218
+ end
219
+ end # Commit
220
+
221
+ end # Grit
@@ -0,0 +1,104 @@
1
+ module Grit
2
+
3
+ class CommitStats
4
+
5
+ attr_reader :id, :files, :additions, :deletions, :total
6
+
7
+ # Instantiate a new CommitStats
8
+ # +id+ is the id of the commit
9
+ # +files+ is an array of :
10
+ # [ [filename, adds, deletes, total],
11
+ # [filename, adds, deletes, total],
12
+ # [filename, adds, deletes, total] ]
13
+ #
14
+ # Returns Grit::CommitStats (baked)
15
+ def initialize(repo, id, files)
16
+ @repo = repo
17
+ @id = id
18
+ @files = files
19
+ @additions = files.inject(0) { |total, a| total += a[1] }
20
+ @deletions = files.inject(0) { |total, a| total += a[2] }
21
+ @total = files.inject(0) { |total, a| total += a[3] }
22
+ end
23
+
24
+ # Find all commit stats matching the given criteria.
25
+ # +repo+ is the Repo
26
+ # +ref+ is the ref from which to begin (SHA1 or name) or nil for --all
27
+ # +options+ is a Hash of optional arguments to git
28
+ # :max_count is the maximum number of commits to fetch
29
+ # :skip is the number of commits to skip
30
+ #
31
+ # Returns assoc array [sha, Grit::Commit[] (baked)]
32
+ def self.find_all(repo, ref, options = {})
33
+ allowed_options = [:max_count, :skip, :since]
34
+
35
+ default_options = {:numstat => true}
36
+ actual_options = default_options.merge(options)
37
+
38
+ if ref
39
+ output = repo.git.log(actual_options, ref)
40
+ else
41
+ output = repo.git.log(actual_options.merge(:all => true))
42
+ end
43
+
44
+ self.list_from_string(repo, output)
45
+ end
46
+
47
+ # Parse out commit information into an array of baked Commit objects
48
+ # +repo+ is the Repo
49
+ # +text+ is the text output from the git command (raw format)
50
+ #
51
+ # Returns assoc array [sha, Grit::Commit[] (baked)]
52
+ def self.list_from_string(repo, text)
53
+ lines = text.split("\n")
54
+
55
+ commits = []
56
+
57
+ while !lines.empty?
58
+ id = lines.shift.split.last
59
+
60
+ lines.shift
61
+ lines.shift
62
+ lines.shift
63
+
64
+ message_lines = []
65
+ message_lines << lines.shift[4..-1] while lines.first =~ /^ {4}/
66
+
67
+ lines.shift while lines.first && lines.first.empty?
68
+
69
+ files = []
70
+ while lines.first =~ /^(\d+)\s+(\d+)\s+(.+)/
71
+ (additions, deletions, filename) = lines.shift.split
72
+ additions = additions.to_i
73
+ deletions = deletions.to_i
74
+ total = additions + deletions
75
+ files << [filename, additions, deletions, total]
76
+ end
77
+
78
+ lines.shift while lines.first && lines.first.empty?
79
+
80
+ commits << [id, CommitStats.new(repo, id, files)]
81
+ end
82
+
83
+ commits
84
+ end
85
+
86
+ # Pretty object inspection
87
+ def inspect
88
+ %Q{#<Grit::CommitStats "#{@id}">}
89
+ end
90
+
91
+ # private
92
+
93
+ def to_hash
94
+ {
95
+ 'id' => id,
96
+ 'files' => files,
97
+ 'additions' => additions,
98
+ 'deletions' => deletions,
99
+ 'total' => total
100
+ }
101
+ end
102
+ end # CommitStats
103
+
104
+ end # Grit
@@ -0,0 +1,44 @@
1
+ module Grit
2
+
3
+ class Config
4
+ def initialize(repo)
5
+ @repo = repo
6
+ end
7
+
8
+ def []=(key, value)
9
+ @repo.git.config({}, key, value)
10
+ @data = nil
11
+ end
12
+
13
+ def [](key)
14
+ data[key]
15
+ end
16
+
17
+ def fetch(key, default = nil)
18
+ data[key] || default || raise(IndexError.new("key not found"))
19
+ end
20
+
21
+ def keys
22
+ data.keys
23
+ end
24
+
25
+ protected
26
+ def data
27
+ @data ||= load_config
28
+ end
29
+
30
+ def load_config
31
+ hash = {}
32
+ config_lines.map do |line|
33
+ key, value = line.split(/=/, 2)
34
+ hash[key] = value
35
+ end
36
+ hash
37
+ end
38
+
39
+ def config_lines
40
+ @repo.git.config(:list => true).split(/\n/)
41
+ end
42
+ end # Config
43
+
44
+ end # Grit