11 class Error < StandardError
27 attr_reader :author, :log, :date, :changed_dirs
28 attr_reader :added_files, :deleted_files, :updated_files, :copied_files
29 attr_reader :added_dirs, :deleted_dirs, :updated_dirs, :copied_dirs
30 attr_reader :path, :revision, :diffs
31 attr_reader :sha256, :entire_sha256
33 def initialize(path, rev)
48 @added_files + @deleted_files + @updated_files + @copied_files
52 @added_dirs + @deleted_dirs + @updated_dirs + @copied_dirs
56 prefixes = prefix.split(/\/+/)
59 paths = path.split(/\/+/)
60 if prefixes.size < paths.size and prefixes == paths[0, prefixes.size]
61 results << paths[prefixes.size]
70 @revision = Integer(rev)
71 @prev_rev = @revision - 1
72 @repos = Repos.open(@path)
74 @root = @fs.root(@revision)
78 @repos = @root = @fs = nil
82 @author = force_to_utf8(prop(Core::PROP_REVISION_AUTHOR))
83 @date = prop(Core::PROP_REVISION_DATE)
84 @log = force_to_utf8(prop(Core::PROP_REVISION_LOG))
88 editor = traverse(Delta::ChangedDirsEditor)
89 @changed_dirs = editor.changed_dirs
93 editor = traverse(Delta::ChangedEditor, true)
94 @copied_files = editor.copied_files
95 @copied_dirs = editor.copied_dirs
96 @added_files = editor.added_files
97 @added_dirs = editor.added_dirs
98 @deleted_files = editor.deleted_files
99 @deleted_dirs = editor.deleted_dirs
100 @updated_files = editor.updated_files
101 @updated_dirs = editor.updated_dirs
105 tree = @repos.delta_tree(@root, @prev_rev)
107 get_diff_recurse(tree, @fs.root(@prev_rev), "", "")
108 @diffs.each do |key, values|
109 values.each do |type, diff|
110 diff.body = force_to_utf8(diff.body)
118 [:delete?, :deleted],
119 [:replace?, :modified],
123 TYPE_TABLE.each do |meth, type|
124 return type if node.__send__(meth)
129 def get_diff_recurse(node, base_root, path, base_path)
131 base_path = node.copyfrom_path.sub(/\A\//, '')
132 base_root = base_root.fs.root(node.copyfrom_rev)
136 try_diff(node, base_root, path, base_path)
139 if node.prop_mod? and !node.delete?
140 get_prop_diff(node, base_root, path, base_path)
145 get_diff_recurse(node, base_root,
146 "#{path}/#{node.name}",
147 "#{base_path}/#{node.name}")
150 get_diff_recurse(node, base_root,
151 "#{path}/#{node.name}",
152 "#{base_path}/#{node.name}")
157 def get_prop_diff(node, base_root, path, base_path)
158 local_props = @root.node_prop_list(path)
162 base_props = base_root.node_prop_list(base_path)
164 prop_changes = Core::Property.diffs2(local_props, base_props)
165 prop_changes.each do |name, value|
166 entry = diff_entry(path, :property_changed)
167 entry.body << "Name: #{force_to_utf8(name)}\n"
168 orig_value = base_props[name]
169 entry.body << " - #{force_to_utf8(orig_value)}\n" if orig_value
170 entry.body << " + #{force_to_utf8(value)}\n" if value
174 def try_diff(node, base_root, path, base_path)
175 if node.replace? and node.text_mod?
176 differ = Fs::FileDiff.new(base_root, base_path, @root, path)
177 do_diff(node, base_root, path, base_path, differ)
178 elsif node.add? and node.text_mod?
179 differ = Fs::FileDiff.new(nil, base_path, @root, path)
180 do_diff(node, base_root, path, base_path, differ)
182 differ = Fs::FileDiff.new(base_root, base_path, nil, path)
183 do_diff(node, base_root, path, base_path, differ)
185 diff_entry(path, get_type(node))
189 def do_diff(node, base_root, path, base_path, differ)
190 entry = diff_entry(path, get_type(node))
193 entry.body = "(Binary files differ)\n"
195 base_rev = base_root.revision
197 base_date = base_root.fs.prop(Core::PROP_REVISION_DATE, base_rev)
198 date = @root.fs.prop(Core::PROP_REVISION_DATE, rev)
199 base_date = format_date(base_date)
200 date = format_date(date)
201 stripped_base_path = base_path.sub(/\A\//, '')
202 stripped_path = path.sub(/\A\//, '')
203 base_label = "#{stripped_base_path}\t#{base_date} (rev #{base_rev})"
204 label = "#{stripped_path}\t#{date} (rev #{rev})"
205 entry.body = differ.unified(base_label, label)
206 parse_diff_unified(entry)
210 def parse_diff_unified(entry)
212 entry.body.each do |line|
220 entry.count_up_added_line!
222 entry.count_up_deleted_line!
229 def dump_contents(root, path)
230 root.file_contents(path) do |f|
239 def diff_entry(target, type)
240 target = target.sub(/\A\//, '')
241 @diffs[target] ||= {}
242 @diffs[target][type] ||= DiffEntry.new(type)
247 sha = Digest::SHA256.new
255 content = dump_contents(@root, file)
259 :revision => @revision,
260 :sha256 => Digest::SHA256.hexdigest(content),
264 @entire_sha256 = sha.hexdigest
268 @fs.prop(name, @revision)
271 def force_to_utf8(str)
282 def traverse(editor_class, pass_root=false)
284 base_root = @fs.root(base_rev)
286 editor = editor_class.new(@root, base_root)
288 editor = editor_class.new
290 base_root.dir_delta("", "", @root, "", editor)
294 def format_date(date)
295 formatted = date.strftime("%Y-%m-%d %H:%M:%S")
296 formatted << (" %+03d:00" % (date.hour - date.getutc.hour))
301 attr_reader :type, :added_line, :deleted_line
311 def count_up_added_line!
315 def count_up_deleted_line!