robinluckey-grit 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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