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/actor.rb ADDED
@@ -0,0 +1,36 @@
1
+ module Grit
2
+
3
+ class Actor
4
+ attr_reader :name
5
+ attr_reader :email
6
+
7
+ def initialize(name, email)
8
+ @name = name
9
+ @email = email
10
+ end
11
+ alias_method :to_s, :name
12
+
13
+ # Create an Actor from a string.
14
+ # +str+ is the string, which is expected to be in regular git format
15
+ #
16
+ # Format
17
+ # John Doe <jdoe@example.com>
18
+ #
19
+ # Returns Actor
20
+ def self.from_string(str)
21
+ case str
22
+ when /<.+>/
23
+ m, name, email = *str.match(/(.*) <(.+?)>/)
24
+ return self.new(name, email)
25
+ else
26
+ return self.new(str, nil)
27
+ end
28
+ end
29
+
30
+ # Pretty object inspection
31
+ def inspect
32
+ %Q{#<Grit::Actor "#{@name} <#{@email}>">}
33
+ end
34
+ end # Actor
35
+
36
+ end # Grit
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,229 @@
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
+ def sha
187
+ @id
188
+ end
189
+
190
+ def date
191
+ @committed_date
192
+ end
193
+
194
+ # Pretty object inspection
195
+ def inspect
196
+ %Q{#<Grit::Commit "#{@id}">}
197
+ end
198
+
199
+ # private
200
+
201
+ # Parse out the actor (author or committer) info
202
+ #
203
+ # Returns [String (actor name and email), Time (acted at time)]
204
+ def self.actor(line)
205
+ m, actor, epoch = *line.match(/^.+? (.*) (\d+) .*$/)
206
+ [Actor.from_string(actor), Time.at(epoch.to_i)]
207
+ end
208
+
209
+ def to_hash
210
+ {
211
+ 'id' => id,
212
+ 'parents' => parents.map { |p| { 'id' => p.id } },
213
+ 'tree' => tree.id,
214
+ 'message' => message,
215
+ 'author' => {
216
+ 'name' => author.name,
217
+ 'email' => author.email
218
+ },
219
+ 'committer' => {
220
+ 'name' => committer.name,
221
+ 'email' => committer.email
222
+ },
223
+ 'authored_date' => authored_date.xmlschema,
224
+ 'committed_date' => committed_date.xmlschema,
225
+ }
226
+ end
227
+ end # Commit
228
+
229
+ 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