steveh-grit 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,242 @@
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
+ lazy_reader :author_string
14
+
15
+ # Instantiate a new Commit
16
+ # +id+ is the id of the commit
17
+ # +parents+ is an array of commit ids (will be converted into Commit instances)
18
+ # +tree+ is the correspdonding tree id (will be converted into a Tree object)
19
+ # +author+ is the author string
20
+ # +authored_date+ is the authored Time
21
+ # +committer+ is the committer string
22
+ # +committed_date+ is the committed Time
23
+ # +message+ is an array of commit message lines
24
+ #
25
+ # Returns Grit::Commit (baked)
26
+ def initialize(repo, id, parents, tree, author, authored_date, committer, committed_date, message)
27
+ @repo = repo
28
+ @id = id
29
+ @parents = parents.map { |p| Commit.create(repo, :id => p) }
30
+ @tree = Tree.create(repo, :id => tree)
31
+ @author = author
32
+ @authored_date = authored_date
33
+ @committer = committer
34
+ @committed_date = committed_date
35
+ @message = message.join("\n")
36
+ @short_message = message[0] || ''
37
+ end
38
+
39
+ def id_abbrev
40
+ @id_abbrev ||= @repo.git.rev_parse({}, self.id).chomp[0, 7]
41
+ end
42
+
43
+ # Create an unbaked Commit containing just the specified attributes
44
+ # +repo+ is the Repo
45
+ # +atts+ is a Hash of instance variable data
46
+ #
47
+ # Returns Grit::Commit (unbaked)
48
+ def self.create(repo, atts)
49
+ self.allocate.create_initialize(repo, atts)
50
+ end
51
+
52
+ # Initializer for Commit.create
53
+ # +repo+ is the Repo
54
+ # +atts+ is a Hash of instance variable data
55
+ #
56
+ # Returns Grit::Commit (unbaked)
57
+ def create_initialize(repo, atts)
58
+ @repo = repo
59
+ atts.each do |k, v|
60
+ instance_variable_set("@#{k}", v)
61
+ end
62
+ self
63
+ end
64
+
65
+ def lazy_source
66
+ self.class.find_all(@repo, @id, {:max_count => 1}).first
67
+ end
68
+
69
+ # Count the number of commits reachable from this ref
70
+ # +repo+ is the Repo
71
+ # +ref+ is the ref from which to begin (SHA1 or name)
72
+ #
73
+ # Returns Integer
74
+ def self.count(repo, ref)
75
+ repo.git.rev_list({}, ref).size / 41
76
+ end
77
+
78
+ # Find all commits matching the given criteria.
79
+ # +repo+ is the Repo
80
+ # +ref+ is the ref from which to begin (SHA1 or name) or nil for --all
81
+ # +options+ is a Hash of optional arguments to git
82
+ # :max_count is the maximum number of commits to fetch
83
+ # :skip is the number of commits to skip
84
+ #
85
+ # Returns Grit::Commit[] (baked)
86
+ def self.find_all(repo, ref, options = {})
87
+ allowed_options = [:max_count, :skip, :since]
88
+
89
+ default_options = {:pretty => "raw"}
90
+ actual_options = default_options.merge(options)
91
+
92
+ if ref
93
+ output = repo.git.rev_list(actual_options, ref)
94
+ else
95
+ output = repo.git.rev_list(actual_options.merge(:all => true))
96
+ end
97
+
98
+ self.list_from_string(repo, output)
99
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
100
+ []
101
+ end
102
+
103
+ # Parse out commit information into an array of baked Commit objects
104
+ # +repo+ is the Repo
105
+ # +text+ is the text output from the git command (raw format)
106
+ #
107
+ # Returns Grit::Commit[] (baked)
108
+ #
109
+ # really should re-write this to be more accepting of non-standard commit messages
110
+ # - it broke when 'encoding' was introduced - not sure what else might show up
111
+ #
112
+ def self.list_from_string(repo, text)
113
+ lines = text.split("\n")
114
+
115
+ commits = []
116
+
117
+ while !lines.empty?
118
+ id = lines.shift.split.last
119
+ tree = lines.shift.split.last
120
+
121
+ parents = []
122
+ parents << lines.shift.split.last while lines.first =~ /^parent/
123
+
124
+ author, authored_date = self.actor(lines.shift)
125
+ committer, committed_date = self.actor(lines.shift)
126
+
127
+ # not doing anything with this yet, but it's sometimes there
128
+ encoding = lines.shift.split.last if lines.first =~ /^encoding/
129
+
130
+ lines.shift
131
+
132
+ message_lines = []
133
+ message_lines << lines.shift[4..-1] while lines.first =~ /^ {4}/
134
+
135
+ lines.shift while lines.first && lines.first.empty?
136
+
137
+ commits << Commit.new(repo, id, parents, tree, author, authored_date, committer, committed_date, message_lines)
138
+ end
139
+
140
+ commits
141
+ end
142
+
143
+ # Show diffs between two trees:
144
+ # +repo+ is the Repo
145
+ # +a+ is a named commit
146
+ # +b+ is an optional named commit. Passing an array assumes you
147
+ # wish to omit the second named commit and limit the diff to the
148
+ # given paths.
149
+ # +paths* is an array of paths to limit the diff.
150
+ #
151
+ # Returns Grit::Diff[] (baked)
152
+ def self.diff(repo, a, b = nil, paths = [])
153
+ if b.is_a?(Array)
154
+ paths = b
155
+ b = nil
156
+ end
157
+ paths.unshift("--") unless paths.empty?
158
+ paths.unshift(b) unless b.nil?
159
+ paths.unshift(a)
160
+ text = repo.git.diff({:full_index => true}, *paths)
161
+ Diff.list_from_string(repo, text)
162
+ end
163
+
164
+ def show
165
+ diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
166
+ if diff =~ /diff --git a/
167
+ diff = diff.sub(/.+?(diff --git a)/m, '\1')
168
+ else
169
+ diff = ''
170
+ end
171
+ Diff.list_from_string(@repo, diff)
172
+ end
173
+
174
+ def diffs
175
+ if parents.empty?
176
+ show
177
+ else
178
+ self.class.diff(@repo, parents.first.id, @id)
179
+ end
180
+ end
181
+
182
+ def stats
183
+ stats = @repo.commit_stats(self.sha, 1)[0][-1]
184
+ end
185
+
186
+ # Convert this Commit to a String which is just the SHA1 id
187
+ def to_s
188
+ @id
189
+ end
190
+
191
+ def sha
192
+ @id
193
+ end
194
+
195
+ def date
196
+ @committed_date
197
+ end
198
+
199
+ def to_patch
200
+ @repo.git.format_patch({'1' => true, :stdout => true}, to_s)
201
+ end
202
+
203
+ # Pretty object inspection
204
+ def inspect
205
+ %Q{#<Grit::Commit "#{@id}">}
206
+ end
207
+
208
+ # private
209
+
210
+ # Parse out the actor (author or committer) info
211
+ #
212
+ # Returns [String (actor name and email), Time (acted at time)]
213
+ def self.actor(line)
214
+ m, actor, epoch = *line.match(/^.+? (.*) (\d+) .*$/)
215
+ [Actor.from_string(actor), Time.at(epoch.to_i)]
216
+ end
217
+
218
+ def author_string
219
+ "%s <%s> %s %+05d" % [author.name, author.email, authored_date.to_i, 800]
220
+ end
221
+
222
+ def to_hash
223
+ {
224
+ 'id' => id,
225
+ 'parents' => parents.map { |p| { 'id' => p.id } },
226
+ 'tree' => tree.id,
227
+ 'message' => message,
228
+ 'author' => {
229
+ 'name' => author.name,
230
+ 'email' => author.email
231
+ },
232
+ 'committer' => {
233
+ 'name' => committer.name,
234
+ 'email' => committer.email
235
+ },
236
+ 'authored_date' => authored_date.xmlschema,
237
+ 'committed_date' => committed_date.xmlschema,
238
+ }
239
+ end
240
+ end # Commit
241
+
242
+ 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
@@ -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
data/lib/grit/diff.rb ADDED
@@ -0,0 +1,70 @@
1
+ module Grit
2
+
3
+ class Diff
4
+ attr_reader :a_path, :b_path
5
+ attr_reader :a_blob, :b_blob
6
+ attr_reader :a_mode, :b_mode
7
+ attr_reader :new_file, :deleted_file
8
+ attr_reader :diff
9
+
10
+ def initialize(repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
11
+ @repo = repo
12
+ @a_path = a_path
13
+ @b_path = b_path
14
+ @a_blob = a_blob =~ /^0{40}$/ ? nil : Blob.create(repo, :id => a_blob)
15
+ @b_blob = b_blob =~ /^0{40}$/ ? nil : Blob.create(repo, :id => b_blob)
16
+ @a_mode = a_mode
17
+ @b_mode = b_mode
18
+ @new_file = new_file
19
+ @deleted_file = deleted_file
20
+ @diff = diff
21
+ end
22
+
23
+ def self.list_from_string(repo, text)
24
+ lines = text.split("\n")
25
+
26
+ diffs = []
27
+
28
+ while !lines.empty?
29
+ m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(.+?) b/(.+)$})
30
+
31
+ if lines.first =~ /^old mode/
32
+ m, a_mode = *lines.shift.match(/^old mode (\d+)/)
33
+ m, b_mode = *lines.shift.match(/^new mode (\d+)/)
34
+ end
35
+
36
+ if lines.empty? || lines.first =~ /^diff --git/
37
+ diffs << Diff.new(repo, a_path, b_path, nil, nil, a_mode, b_mode, false, false, nil)
38
+ next
39
+ end
40
+
41
+ new_file = false
42
+ deleted_file = false
43
+
44
+ if lines.first =~ /^new file/
45
+ m, b_mode = lines.shift.match(/^new file mode (.+)$/)
46
+ a_mode = nil
47
+ new_file = true
48
+ elsif lines.first =~ /^deleted file/
49
+ m, a_mode = lines.shift.match(/^deleted file mode (.+)$/)
50
+ b_mode = nil
51
+ deleted_file = true
52
+ end
53
+
54
+ m, a_blob, b_blob, b_mode = *lines.shift.match(%r{^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$})
55
+ b_mode.strip! if b_mode
56
+
57
+ diff_lines = []
58
+ while lines.first && lines.first !~ /^diff/
59
+ diff_lines << lines.shift
60
+ end
61
+ diff = diff_lines.join("\n")
62
+
63
+ diffs << Diff.new(repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
64
+ end
65
+
66
+ diffs
67
+ end
68
+ end # Diff
69
+
70
+ end # Grit
@@ -0,0 +1,7 @@
1
+ module Grit
2
+ class InvalidGitRepositoryError < StandardError
3
+ end
4
+
5
+ class NoSuchPathError < StandardError
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ begin
2
+ require 'sequel'
3
+
4
+ module Grit
5
+
6
+ class CommitDb
7
+
8
+ SCHEMA_VERSION = 1
9
+
10
+ attr_accessor :db, :git
11
+
12
+ def initialize(git_obj, index_location = nil)
13
+ @git = git_obj
14
+ db_file = File.join(index_location || @git.git_dir, 'commit_db')
15
+ if !File.exists?(db_file)
16
+ @db = Sequel.open "sqlite:///#{db_file}"
17
+ setup_tables
18
+ else
19
+ @db = Sequel.open "sqlite:///#{db_file}"
20
+ end
21
+ end
22
+
23
+ def rev_list(branch, options)
24
+ end
25
+
26
+ def update_db(branch = nil)
27
+ # find all refs/heads, for each
28
+ # add branch if not there
29
+ # go though all commits in branch
30
+ # add new commit_branches a
31
+ # and commit_nodes for each new one
32
+ # stop if reach commit that already has branch and node links
33
+ end
34
+
35
+ def setup_tables
36
+ @db << "create table meta (meta_key text, meta_value text)"
37
+ @db[:meta] << {:meta_key => 'schema', :meta_value => SCHEMA_VERSION}
38
+
39
+ @db << "create table commits (id integer, sha text, author_date integer)"
40
+ @db << "create table nodes (id integer, path text, type text)"
41
+ @db << "create table branches (id integer, ref text, commit_id integer)"
42
+
43
+ @db << "create table commit_branches (commit_id integer, branch_id integer)"
44
+ @db << "create table commit_nodes (commit_id integer, node_id integer, node_sha string)"
45
+ end
46
+
47
+ end
48
+ end
49
+
50
+ rescue LoadError
51
+ # no commit db
52
+ end