georgi-git_store 0.2.4 → 0.3

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.
@@ -4,60 +4,20 @@ class GitStore
4
4
  # deserialized data object.
5
5
  class Blob
6
6
 
7
- attr_accessor :store, :id, :mode, :path, :data
7
+ attr_accessor :store, :id, :data, :mode, :object
8
8
 
9
- # Initialize a Blob with default mode of '100644'.
10
- def initialize(store)
9
+ # Initialize a Blob
10
+ def initialize(store, id = nil, data = nil)
11
11
  @store = store
12
- @mode = '100644'
13
- end
14
-
15
- # Set all attributes at once.
16
- def set(id, mode = nil, path = nil, data = nil, object = nil)
17
- @id, @mode, @path, @data, @object = id, mode, path, data, object
18
- end
19
-
20
- # Returns the extension of the filename.
21
- def extname
22
- File.extname(path)[1..-1]
23
- end
24
-
25
- # Returns the handler for serializing the blob data.
26
- def handler
27
- Handler[extname]
28
- end
29
-
30
- # Returns true if data is new or hash value is different from current id.
31
- def modified?
32
- id.nil? || @modified
33
- end
34
-
35
- # Returns the data object.
36
- def object
37
- @object ||= handler.read(path, data)
38
- end
39
-
40
- # Set the data object.
41
- def object=(value)
42
- @modified = true
43
- @object = value
44
- @data = handler.respond_to?(:write) ? handler.write(path, value) : value
45
- end
46
-
47
- def load_from_disk
48
- @object = nil
49
- @data = open("#{store.path}/#{path}", 'rb') { |f| f.read }
12
+ @id = id || store.id_for('blob', data)
13
+ @data = data
14
+ @mode = "100644"
50
15
  end
51
16
 
52
17
  # Write the data to the git object store
53
- def write_to_store
54
- if modified?
55
- @modified = false
56
- @id = store.put_object(data, 'blob')
57
- else
58
- @id
59
- end
60
- end
18
+ def write
19
+ @id = store.put_object('blob', data)
20
+ end
61
21
 
62
22
  end
63
23
 
@@ -0,0 +1,66 @@
1
+ class GitStore
2
+
3
+ class Commit
4
+ attr_accessor :store, :id, :data, :author, :committer, :tree, :parent, :message, :headers
5
+ attr_reader :author_name, :author_email, :author_time
6
+ attr_reader :committer_name, :committer_email, :committer_time
7
+
8
+ def initialize(store, id = nil, data = nil)
9
+ @store = store
10
+ @id = id
11
+ @parent = []
12
+
13
+ parse(data) if data
14
+
15
+ @author_name, @author_email, @author_time = parse_user(author) if author
16
+ @committer_name, @commiter_email, @committer_time = parse_user(committer) if committer
17
+ end
18
+
19
+ def parse_user(user)
20
+ if match = user.match(/(.*)<(.*)> (\d+) ([+-]\d+)/)
21
+ [ match[1].chomp,
22
+ match[2].chomp,
23
+ Time.at(match[3].to_i)]
24
+ end
25
+ end
26
+
27
+ def parse(data)
28
+ headers, @message = data.split(/\n\n/, 2)
29
+
30
+ headers.split(/\n/).each do |header|
31
+ key, value = header.split(/ /, 2)
32
+ if key == 'parent'
33
+ @parent << value
34
+ else
35
+ instance_variable_set "@#{key}", value
36
+ end
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def diff(commit, path = nil)
43
+ commit = commit.id if Commit === commit
44
+ Diff.exec(store, "git diff --full-index #{commit} #{id} -- #{path}")
45
+ end
46
+
47
+ def diffs(path = nil)
48
+ diff(parent.first, path)
49
+ end
50
+
51
+ def write
52
+ @id = store.put_object('commit', dump)
53
+ end
54
+
55
+ def dump
56
+ [ "tree #@tree",
57
+ @parent.map { |parent| "parent #{parent}" },
58
+ "author #@author",
59
+ "committer #@committer",
60
+ '',
61
+ @message ].flatten.join("\n")
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,76 @@
1
+ class GitStore
2
+
3
+ # adapted from Grit
4
+ class Diff
5
+ attr_reader :store
6
+ attr_reader :a_path, :b_path
7
+ attr_reader :a_blob, :b_blob
8
+ attr_reader :a_mode, :b_mode
9
+ attr_reader :new_file, :deleted_file
10
+ attr_reader :diff
11
+
12
+ def initialize(store, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
13
+ @store = store
14
+ @a_path = a_path
15
+ @b_path = b_path
16
+ @a_blob = a_blob =~ /^0{40}$/ ? nil : store.get(a_blob)
17
+ @b_blob = b_blob =~ /^0{40}$/ ? nil : store.get(b_blob)
18
+ @a_mode = a_mode
19
+ @b_mode = b_mode
20
+ @new_file = new_file
21
+ @deleted_file = deleted_file
22
+ @diff = diff
23
+ end
24
+
25
+ def self.exec(store, cmd)
26
+ list(store, IO.popen(cmd) { |io| io.read })
27
+ end
28
+
29
+ def self.list(store, text)
30
+ lines = text.split("\n")
31
+
32
+ diffs = []
33
+
34
+ while !lines.empty?
35
+ m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(.+?) b/(.+)$})
36
+
37
+ if lines.first =~ /^old mode/
38
+ m, a_mode = *lines.shift.match(/^old mode (\d+)/)
39
+ m, b_mode = *lines.shift.match(/^new mode (\d+)/)
40
+ end
41
+
42
+ if lines.empty? || lines.first =~ /^diff --git/
43
+ diffs << Diff.new(store, a_path, b_path, nil, nil, a_mode, b_mode, false, false, nil)
44
+ next
45
+ end
46
+
47
+ new_file = false
48
+ deleted_file = false
49
+
50
+ if lines.first =~ /^new file/
51
+ m, b_mode = lines.shift.match(/^new file mode (.+)$/)
52
+ a_mode = nil
53
+ new_file = true
54
+ elsif lines.first =~ /^deleted file/
55
+ m, a_mode = lines.shift.match(/^deleted file mode (.+)$/)
56
+ b_mode = nil
57
+ deleted_file = true
58
+ end
59
+
60
+ m, a_blob, b_blob, b_mode = *lines.shift.match(%r{^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$})
61
+ b_mode.strip! if b_mode
62
+
63
+ diff_lines = []
64
+ while lines.first && lines.first !~ /^diff/
65
+ diff_lines << lines.shift
66
+ end
67
+ diff = diff_lines.join("\n")
68
+
69
+ diffs << Diff.new(store, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
70
+ end
71
+
72
+ diffs
73
+ end
74
+ end
75
+
76
+ end
@@ -15,43 +15,22 @@ end
15
15
  class GitStore
16
16
 
17
17
  class DefaultHandler
18
- def read(path, data)
18
+ def read(data)
19
19
  data
20
20
  end
21
21
 
22
- def write(path, data)
22
+ def write(data)
23
23
  data.to_s
24
24
  end
25
25
  end
26
26
 
27
27
  class YAMLHandler
28
- def read(path, data)
28
+ def read(data)
29
29
  YAML.load(data)
30
30
  end
31
31
 
32
- def write(path, data)
32
+ def write(data)
33
33
  data.to_yaml
34
- end
35
- end
36
-
37
- class RubyHandler
38
- def read(path, data)
39
- Object.module_eval(data)
40
34
  end
41
35
  end
42
-
43
- class ERBHandler
44
- def read(path, data)
45
- ERB.new(data)
46
- end
47
- end
48
-
49
- Handler = {
50
- 'yml' => YAMLHandler.new,
51
- 'rhtml' => ERBHandler.new,
52
- 'rxml' => ERBHandler.new,
53
- 'rb' => RubyHandler.new
54
- }
55
-
56
- Handler.default = DefaultHandler.new
57
36
  end
@@ -1,42 +1,23 @@
1
1
  class GitStore
2
2
 
3
3
  class Tree
4
- TYPE_CLASS = {
5
- 'tree' => Tree,
6
- 'blob' => Blob
7
- }
8
-
9
4
  include Enumerable
10
5
 
11
- attr_reader :store
12
- attr_accessor :id, :mode, :path, :data, :table
6
+ attr_reader :store, :table
7
+ attr_accessor :id, :data, :mode
13
8
 
14
- # Initialize a tree with default mode '040000'
15
- def initialize(store)
16
- @store = store
17
- @mode ||= '040000'
18
- @path = ''
9
+ # Initialize a tree
10
+ def initialize(store, id = nil, data = nil)
11
+ @store = store
12
+ @id = id
19
13
  @table = {}
20
- end
21
-
22
- # Set all attributes at once.
23
- def set(id, mode = '040000', path = nil, data = nil)
24
- @id, @mode, @path, @data = id, mode, path, data
25
- end
26
-
27
- # Does this tree exist in the repository?
28
- def created?
29
- not @id.nil?
14
+ @mode = "040000"
15
+ parse(data) if data
30
16
  end
31
17
 
32
18
  # Has this tree been modified?
33
19
  def modified?
34
- @modified || (table && table.values.any? { |value| value.modified? })
35
- end
36
-
37
- # Path of a child element with specified name.
38
- def child_path(name)
39
- path.empty? ? name : "#{path}/#{name}"
20
+ @modified or @table.values.any? { |entry| Tree === entry and entry.modified? }
40
21
  end
41
22
 
42
23
  # Find or create a subtree with specified name.
@@ -44,157 +25,148 @@ class GitStore
44
25
  get(name) or put(name, Tree.new(store))
45
26
  end
46
27
 
47
- # Load this tree from a real directory instead of a repository.
48
- def load_from_disk
49
- dir = File.join(store.path, self.path)
50
- entries = Dir.entries(dir) - ['.', '..']
51
-
52
- @table = entries.inject({}) do |hash, name|
53
- if name[-1, 1] != '~' && name[0, 1] != '.'
54
- path = "#{dir}/#{name}"
55
- stat = File.stat(path)
56
- mode = '%o' % stat.mode
57
- klass = stat.directory? ? Tree : Blob
58
-
59
- child = table[name] ||= klass.new(store)
60
- child.set(nil, mode, child_path(name), data)
61
- child.load_from_disk
62
-
63
- hash[name] = child
64
- end
65
- hash
66
- end
67
- end
68
-
69
28
  # Read the contents of a raw git object.
70
- #
71
- # Return an array of [mode, name, id] entries.
72
- def read_contents(data)
73
- contents = []
29
+ def parse(data)
30
+ @table.clear
74
31
 
75
32
  while data.size > 0
76
33
  mode, data = data.split(" ", 2)
77
34
  name, data = data.split("\0", 2)
78
35
  id = data.slice!(0, 20).unpack("H*").first
79
- contents << [ mode, name, id ]
80
- end
81
-
82
- contents
83
- end
84
-
85
- # Load this tree from a git repository.
86
- def load_from_store
87
- @table = read_contents(data).inject({}) do |hash, (mode, name, id)|
88
- content, type = store.get_object(id)
89
-
90
- child = table[name] || TYPE_CLASS[type].new(store)
91
- child.set(id, mode, child_path(name), content)
92
- child.load_from_store if Tree === child
93
-
94
- hash[name] = child
95
- hash
36
+
37
+ @table[name] = store.get(id)
96
38
  end
97
39
  end
98
-
40
+
99
41
  # Write this tree back to the git repository.
100
42
  #
101
43
  # Returns the object id of the tree.
102
- def write_to_store
44
+ def write
103
45
  return id if not modified?
104
46
 
105
- contents = table.map do |name, entry|
106
- entry.write_to_store
107
- "%s %s\0%s" % [entry.mode, name, [entry.id].pack("H*")]
47
+ contents = @table.map do |name, entry|
48
+ "#{ entry.mode } #{ name }\0#{ [entry.write].pack("H*") }"
108
49
  end
109
50
 
110
51
  @modified = false
111
- @id = store.put_object(contents.join, 'tree')
52
+ @id = store.put_object('tree', contents.join)
112
53
  end
113
-
54
+
114
55
  # Read entry with specified name.
115
56
  def get(name)
116
- name = name.to_s
117
- entry = table[name]
57
+ entry = @table[name]
118
58
 
119
59
  case entry
120
- when Blob; entry.object
121
- when Tree; entry
60
+ when Blob
61
+ entry.object ||= handler_for(name).read(entry.data)
62
+
63
+ when Tree
64
+ entry
122
65
  end
123
66
  end
124
67
 
68
+ def handler_for(name)
69
+ store.handler_for(name)
70
+ end
71
+
125
72
  # Write entry with specified name.
126
73
  def put(name, value)
127
74
  @modified = true
128
- name = name.to_s
129
75
 
130
76
  if value.is_a?(Tree)
131
- value.path = child_path(name)
132
- table[name] = value
77
+ @table[name] = value
133
78
  else
134
- blob = table[name]
135
- blob = Blob.new(store) if not blob.is_a?(Blob)
136
- blob.path = child_path(name)
137
- blob.object = value
138
- table[name] = blob
79
+ @table[name] = Blob.new(store, nil, handler_for(name).write(value))
139
80
  end
140
-
81
+
141
82
  value
142
83
  end
143
84
 
144
85
  # Remove entry with specified name.
145
86
  def remove(name)
146
87
  @modified = true
147
- table.delete(name.to_s)
88
+ @table.delete(name.to_s)
148
89
  end
149
90
 
150
91
  # Does this key exist in the table?
151
92
  def has_key?(name)
152
- table.has_key?(name)
93
+ @table.has_key?(name.to_s)
153
94
  end
154
95
 
96
+ def normalize_path(path)
97
+ (path[0, 1] == '/' ? path[1..-1] : path).split('/')
98
+ end
99
+
155
100
  # Read a value on specified path.
156
- #
157
- # Use an argument list or a string with slashes.
158
- def [](*args)
159
- args = args.first.to_s.split('/') if args.size == 1
160
- args.inject(self) { |tree, key| tree.get(key) or return nil }
101
+ def [](path)
102
+ normalize_path(path).inject(self) do |tree, key|
103
+ tree.get(key) or return nil
104
+ end
161
105
  end
162
106
 
163
107
  # Write a value on specified path.
164
- #
165
- # Use an argument list or a string with slashes.
166
- def []=(*args)
167
- value = args.pop
168
- args = args.first.to_s.split('/') if args.size == 1
169
- tree = args[0..-2].to_a.inject(self) { |tree, name| tree.tree(name) }
170
- tree.put(args.last, value)
108
+ def []=(path, value)
109
+ list = normalize_path(path)
110
+ tree = list[0..-2].to_a.inject(self) { |tree, name| tree.tree(name) }
111
+ tree.put(list.last, value)
171
112
  end
172
113
 
173
114
  # Delete a value on specified path.
174
- #
175
- # Use an argument list or a string with slashes.
176
- def delete(*args)
177
- args = args.first.to_s.split('/') if args.size == 1
178
- tree = args[0..-2].to_a.inject(self) do |tree, key|
115
+ def delete(path)
116
+ list = normalize_path(path)
117
+
118
+ tree = list[0..-2].to_a.inject(self) do |tree, key|
179
119
  tree.get(key) or return
180
120
  end
181
- tree.remove(args.last)
121
+
122
+ tree.remove(list.last)
182
123
  end
183
124
 
184
125
  # Iterate over all objects found in this subtree.
185
- def each(&block)
186
- table.sort.each do |name, entry|
126
+ def each(path = [], &block)
127
+ @table.sort.each do |name, entry|
128
+ child_path = path + [name]
129
+ case entry
130
+ when Blob
131
+ entry.object ||= handler_for(name).read(entry.data)
132
+ yield child_path.join("/"), entry.object
133
+
134
+ when Tree
135
+ entry.each(child_path, &block)
136
+ end
137
+ end
138
+ end
139
+
140
+ def each_blob(path = [], &block)
141
+ @table.sort.each do |name, entry|
142
+ child_path = path + [name]
143
+
187
144
  case entry
188
- when Blob; yield entry.object
189
- when Tree; entry.each(&block)
145
+ when Blob
146
+ yield child_path.join("/"), entry
147
+
148
+ when Tree
149
+ entry.each_blob(child_path, &block)
190
150
  end
191
151
  end
192
152
  end
193
153
 
154
+ def paths
155
+ map { |path, data| path }
156
+ end
157
+
158
+ def values
159
+ map { |path, data| data }
160
+ end
161
+
194
162
  # Convert this tree into a hash object.
195
163
  def to_hash
196
- table.inject({}) do |hash, (name, entry)|
197
- hash[name] = entry.is_a?(Tree) ? entry.to_hash : entry.object
164
+ @table.inject({}) do |hash, (name, entry)|
165
+ if entry.is_a?(Tree)
166
+ hash[name] = entry.to_hash
167
+ else
168
+ hash[name] = entry.object ||= handler_for(name).read(entry.data)
169
+ end
198
170
  hash
199
171
  end
200
172
  end