robinluckey-grit 1.1.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.
@@ -0,0 +1,61 @@
1
+ module Grit
2
+
3
+ class Blame
4
+
5
+ attr_reader :lines
6
+
7
+ def initialize(repo, file, commit)
8
+ @repo = repo
9
+ @file = file
10
+ @commit = commit
11
+ @lines = []
12
+ load_blame
13
+ end
14
+
15
+ def load_blame
16
+ output = @repo.git.blame({'p' => true}, @commit, '--', @file)
17
+ process_raw_blame(output)
18
+ end
19
+
20
+ def process_raw_blame(output)
21
+ lines, final = [], []
22
+ info, commits = {}, {}
23
+
24
+ # process the output
25
+ output.split("\n").each do |line|
26
+ if line[0, 1] == "\t"
27
+ lines << line[1, line.size]
28
+ elsif m = /^(\w{40}) (\d+) (\d+)/.match(line)
29
+ if !commits[m[1]]
30
+ commits[m[1]] = @repo.commit(m[1])
31
+ end
32
+ info[m[3].to_i] = [commits[m[1]], m[2].to_i]
33
+ end
34
+ end
35
+
36
+ # get it together
37
+ info.sort.each do |lineno, commit|
38
+ final << BlameLine.new(lineno, commit[1], commit[0], lines[lineno - 1])
39
+ end
40
+
41
+ @lines = final
42
+ end
43
+
44
+ # Pretty object inspection
45
+ def inspect
46
+ %Q{#<Grit::Blame "#{@file} <#{@commit}>">}
47
+ end
48
+
49
+ class BlameLine
50
+ attr_accessor :lineno, :oldlineno, :commit, :line
51
+ def initialize(lineno, oldlineno, commit, line)
52
+ @lineno = lineno
53
+ @oldlineno = oldlineno
54
+ @commit = commit
55
+ @line = line
56
+ end
57
+ end
58
+
59
+ end # Blame
60
+
61
+ end # Grit
@@ -0,0 +1,126 @@
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
+ def basename
112
+ File.basename(name)
113
+ end
114
+
115
+ # Pretty object inspection
116
+ def inspect
117
+ %Q{#<Grit::Blob "#{@id}">}
118
+ end
119
+
120
+ # Compares blobs by name
121
+ def <=>(other)
122
+ name <=> other.name
123
+ end
124
+ end # Blob
125
+
126
+ end # Grit
@@ -0,0 +1,252 @@
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 :authored_tz
10
+ lazy_reader :committer
11
+ lazy_reader :committed_date
12
+ lazy_reader :committed_tz
13
+ lazy_reader :message
14
+ lazy_reader :short_message
15
+ lazy_reader :author_string
16
+
17
+ # Instantiate a new Commit
18
+ # +id+ is the id of the commit
19
+ # +parents+ is an array of commit ids (will be converted into Commit instances)
20
+ # +tree+ is the correspdonding tree id (will be converted into a Tree object)
21
+ # +author+ is the author string
22
+ # +authored_date+ is the authored Time
23
+ # +committer+ is the committer string
24
+ # +committed_date+ is the committed Time
25
+ # +message+ is an array of commit message lines
26
+ #
27
+ # Returns Grit::Commit (baked)
28
+ def initialize(repo, id, parents, tree, author, authored_date, authored_tz,
29
+ committer, committed_date, committed_tz, message)
30
+ @repo = repo
31
+ @id = id
32
+ @parents = parents.map { |p| Commit.create(repo, :id => p) }
33
+ @tree = Tree.create(repo, :id => tree)
34
+ @author = author
35
+ @authored_date = authored_date
36
+ @authored_tz = authored_tz
37
+ @committer = committer
38
+ @committed_date = committed_date
39
+ @committed_tz = committed_tz
40
+ @message = message.join("\n")
41
+ @short_message = message[0] || ''
42
+ end
43
+
44
+ def id_abbrev
45
+ @id_abbrev ||= @repo.git.rev_parse({}, self.id).chomp[0, 7]
46
+ end
47
+
48
+ # Create an unbaked Commit containing just the specified attributes
49
+ # +repo+ is the Repo
50
+ # +atts+ is a Hash of instance variable data
51
+ #
52
+ # Returns Grit::Commit (unbaked)
53
+ def self.create(repo, atts)
54
+ self.allocate.create_initialize(repo, atts)
55
+ end
56
+
57
+ # Initializer for Commit.create
58
+ # +repo+ is the Repo
59
+ # +atts+ is a Hash of instance variable data
60
+ #
61
+ # Returns Grit::Commit (unbaked)
62
+ def create_initialize(repo, atts)
63
+ @repo = repo
64
+ atts.each do |k, v|
65
+ instance_variable_set("@#{k}", v)
66
+ end
67
+ self
68
+ end
69
+
70
+ def lazy_source
71
+ self.class.find_all(@repo, @id, {:max_count => 1}).first
72
+ end
73
+
74
+ # Count the number of commits reachable from this ref
75
+ # +repo+ is the Repo
76
+ # +ref+ is the ref from which to begin (SHA1 or name)
77
+ #
78
+ # Returns Integer
79
+ def self.count(repo, ref)
80
+ repo.git.rev_list({}, ref).size / 41
81
+ end
82
+
83
+ # Find all commits matching the given criteria.
84
+ # +repo+ is the Repo
85
+ # +ref+ is the ref from which to begin (SHA1 or name) or nil for --all
86
+ # +options+ is a Hash of optional arguments to git
87
+ # :max_count is the maximum number of commits to fetch
88
+ # :skip is the number of commits to skip
89
+ #
90
+ # Returns Grit::Commit[] (baked)
91
+ def self.find_all(repo, ref, options = {})
92
+ allowed_options = [:max_count, :skip, :since]
93
+
94
+ default_options = {:pretty => "raw"}
95
+ actual_options = default_options.merge(options)
96
+
97
+ if ref
98
+ output = repo.git.rev_list(actual_options, ref)
99
+ else
100
+ output = repo.git.rev_list(actual_options.merge(:all => true))
101
+ end
102
+
103
+ self.list_from_string(repo, output)
104
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
105
+ []
106
+ end
107
+
108
+ # Parse out commit information into an array of baked Commit objects
109
+ # +repo+ is the Repo
110
+ # +text+ is the text output from the git command (raw format)
111
+ #
112
+ # Returns Grit::Commit[] (baked)
113
+ #
114
+ # really should re-write this to be more accepting of non-standard commit messages
115
+ # - it broke when 'encoding' was introduced - not sure what else might show up
116
+ #
117
+ def self.list_from_string(repo, text)
118
+ lines = text.split("\n")
119
+
120
+ commits = []
121
+
122
+ while !lines.empty?
123
+ id = lines.shift.split.last
124
+ tree = lines.shift.split.last
125
+
126
+ parents = []
127
+ parents << lines.shift.split.last while lines.first =~ /^parent/
128
+
129
+ author, authored_date, authored_tz = self.actor(lines.shift)
130
+ committer, committed_date, committed_tz = self.actor(lines.shift)
131
+
132
+ # not doing anything with this yet, but it's sometimes there
133
+ encoding = lines.shift.split.last if lines.first =~ /^encoding/
134
+
135
+ lines.shift
136
+
137
+ message_lines = []
138
+ message_lines << lines.shift[4..-1] while lines.first =~ /^ {4}/
139
+
140
+ lines.shift while lines.first && lines.first.empty?
141
+
142
+ commits << Commit.new(repo, id, parents, tree, author, authored_date, authored_tz,
143
+ committer, committed_date, committed_tz, message_lines)
144
+ end
145
+
146
+ commits
147
+ end
148
+
149
+ # Show diffs between two trees:
150
+ # +repo+ is the Repo
151
+ # +a+ is a named commit
152
+ # +b+ is an optional named commit. Passing an array assumes you
153
+ # wish to omit the second named commit and limit the diff to the
154
+ # given paths.
155
+ # +paths* is an array of paths to limit the diff.
156
+ #
157
+ # Returns Grit::Diff[] (baked)
158
+ def self.diff(repo, a, b = nil, paths = [])
159
+ if b.is_a?(Array)
160
+ paths = b
161
+ b = nil
162
+ end
163
+ paths.unshift("--") unless paths.empty?
164
+ paths.unshift(b) unless b.nil?
165
+ paths.unshift(a)
166
+ text = repo.git.diff({:full_index => true}, *paths)
167
+ Diff.list_from_string(repo, text)
168
+ end
169
+
170
+ def show
171
+ diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
172
+ if diff =~ /diff --git a/
173
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
174
+ else
175
+ diff = ''
176
+ end
177
+ Diff.list_from_string(@repo, diff)
178
+ end
179
+
180
+ def diffs
181
+ if parents.empty?
182
+ show
183
+ else
184
+ self.class.diff(@repo, parents.first.id, @id)
185
+ end
186
+ end
187
+
188
+ def stats
189
+ stats = @repo.commit_stats(self.sha, 1)[0][-1]
190
+ end
191
+
192
+ # Convert this Commit to a String which is just the SHA1 id
193
+ def to_s
194
+ @id
195
+ end
196
+
197
+ def sha
198
+ @id
199
+ end
200
+
201
+ def date
202
+ @committed_date
203
+ end
204
+
205
+ def to_patch
206
+ @repo.git.format_patch({'1' => true, :stdout => true}, to_s)
207
+ end
208
+
209
+ # Pretty object inspection
210
+ def inspect
211
+ %Q{#<Grit::Commit "#{@id}">}
212
+ end
213
+
214
+ # private
215
+
216
+ # Parse out the actor (author or committer) info
217
+ #
218
+ # Returns [String (actor name and email), Time (acted at time), String (actor time zone)]
219
+ def self.actor(line)
220
+ m, actor, epoch, tz = *line.match(/^.+? (.*) (\d+) ([-+]\d\d\d\d)$/)
221
+ [Actor.from_string(actor), Time.at(epoch.to_i), tz]
222
+ end
223
+
224
+ def author_string
225
+ "%s <%s> %s %s" % [author.name, author.email, authored_date.to_i, authored_tz]
226
+ end
227
+
228
+ def committer_string
229
+ "%s <%s> %s %s" % [committer.name, committer.email, committed_date.to_i, committed_tz]
230
+ end
231
+
232
+ def to_hash
233
+ {
234
+ 'id' => id,
235
+ 'parents' => parents.map { |p| { 'id' => p.id } },
236
+ 'tree' => tree.id,
237
+ 'message' => message,
238
+ 'author' => {
239
+ 'name' => author.name,
240
+ 'email' => author.email
241
+ },
242
+ 'committer' => {
243
+ 'name' => committer.name,
244
+ 'email' => committer.email
245
+ },
246
+ 'authored_date' => authored_date.xmlschema,
247
+ 'committed_date' => committed_date.xmlschema,
248
+ }
249
+ end
250
+ end # Commit
251
+
252
+ end # Grit
@@ -0,0 +1,128 @@
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}/ || lines.first == ''
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
+ # Convert to an easy-to-traverse structure
92
+ def to_diffstat
93
+ files.map do |metadata|
94
+ DiffStat.new(*metadata)
95
+ end
96
+ end
97
+
98
+ # private
99
+
100
+ def to_hash
101
+ {
102
+ 'id' => id,
103
+ 'files' => files,
104
+ 'additions' => additions,
105
+ 'deletions' => deletions,
106
+ 'total' => total
107
+ }
108
+ end
109
+
110
+ end # CommitStats
111
+
112
+ class DiffStat
113
+ attr_reader :filename, :additions, :deletions
114
+
115
+ def initialize(filename, additions, deletions, total=nil)
116
+ @filename, @additions, @deletions = filename, additions, deletions
117
+ end
118
+
119
+ def net
120
+ additions - deletions
121
+ end
122
+
123
+ def inspect
124
+ "#{filename}: +#{additions} -#{deletions}"
125
+ end
126
+ end
127
+
128
+ end # Grit