lib/eternity/patch.rb
module Eternity
module Patch
def self.merge(current_commit, target_commit)
Merge.new current_commit, target_commit
end
def self.diff(current_commit, target_commit)
Diff.new current_commit, target_commit
end
module Common
attr_reader :current_commit, :target_commit
def initialize(current_commit, target_commit)
@current_commit = current_commit
@target_commit = target_commit
end
def base_commit
@base_commit ||= Commit.base_of current_commit, target_commit
end
def delta
@delta ||= TransparentProxy.new { calculate_delta }
end
def base_history
@base_history ||= [base_commit] + base_commit.history
end
def current_history
@current_history ||= [current_commit] + current_commit.history - base_history
end
def target_history
@target_history ||= [target_commit] + target_commit.history - base_history
end
def remaining_history
@remaining_history ||= current_history - target_history
end
private
def calculate_delta
if current_commit.nil?
target_commit.with_index do |target_index|
target_index.each_with_object({}) do |(collection, collection_index), hash|
hash[collection] = collection_index.ids.each_with_object({}) do |id, h|
h[id] = {
'action' => INSERT,
'data' => collection_index[id].data
}
end
end
end
else
base_commit.with_index do |base_index|
current_commit.with_index do |current_index|
current_delta = Delta.merge current_history.reverse.map(&:delta)
target_delta = Delta.merge target_history.reverse.map(&:delta)
revert_delta = Delta.revert current_delta, base_index
merged_delta = merge_deltas target_delta, revert_delta, base_index
merged_delta.each_with_object({}) do |(collection, elements), hash|
hash[collection] = {}
elements.each do |id, change|
if change['action'] == UPDATE && current_index[collection][id].sha1 == Blob.digest(Blob.serialize(change['data']))
change = nil
elsif change['action'] == DELETE && !current_index[collection].include?(id)
change = nil
end
hash[collection][id] = change if change
end
hash.delete collection if hash[collection].empty?
end
end
end
end
end
end
class Merge
include Common
def merged?
@merged ||= current_commit == target_commit ||
target_commit.fast_forward?(current_commit) ||
current_commit.fast_forward?(target_commit)
end
private
def calculate_delta
return {} if merged?
super
end
def merge_deltas(target_delta, revert_delta, base_index)
remaining_delta = Delta.merge remaining_history.reverse.map(&:delta)
Delta.merge [revert_delta, target_delta, remaining_delta], base_index
end
end
class Diff
include Common
private
def merge_deltas(target_delta, revert_delta, base_index)
Delta.merge [revert_delta, target_delta], base_index
end
end
end
end