Теодор обнови решението на 22.11.2015 14:20 (преди около 9 години)
+require 'digest/sha1'
+require 'set'
+
+
+def answer
+ AnswerBuilder.new
+end
+
+class AnswerBuilder
+ def positively(message)
+ @message = message
+ @is_success = true
+ self
+ end
+
+ def negatively(message)
+ @message = message
+ @is_success = false
+ self
+ end
+
+ def when
+ yield ? Answer.new(@message, @is_success) : nil
+ end
+
+ def with_result
+ Answer.new(@message, @is_success, yield)
+ end
+
+ def to
+ yield
+ Answer.new(@message, @is_success, nil)
+ end
+end
+
+class Answer
+ attr_reader :message, :result
+
+ def initialize(message, is_success, result = nil)
+ @message = message
+ @is_success = is_success
+ @result = result
+ end
+
+ def success?
+ @is_success
+ end
+
+ def error?
+ not @is_success
+ end
+end
+
+class ActionAdd
+ attr_reader :name, :object
+
+ def initialize(name, object)
+ @name = name
+ @object = object
+ @old_object = nil
+ end
+
+ def apply(store)
+ @old_object = store[@name] if store.key? @name
+ store[@name] = @object
+ end
+
+ def undo(store)
+ @old_object ? (store[@name] = @old_object) : (store.delete @name)
+ end
+end
+
+class ActionRemove
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ @old_object = nil
+ end
+
+ def apply(store)
+ @old_object = store.delete @name
+ end
+
+ def undo(store)
+ store[@name] = @old_object
+ end
+end
+
+class Commit
+ attr_reader :message, :date, :hash, :parent
+
+ def initialize(message, changes, parent, store)
+ @message = message
+ @changes = changes
+ @date = Time.now.strftime "%a %b %d %T %Y %z"
+ @hash = Digest::SHA1.hexdigest "#{date}#{message}"
+ @parent = parent
+ @store = store
+ @branches = [].to_set
+ end
+
+ def include_in_branch(name)
+ @branches.add name
+ end
+
+ def exclude_from_branch(name)
+ @branches.delete name
+ end
+
+ def on_branch?(name)
+ @branches.include? name
+ end
+
+ def objects
+ @store.values
+ end
+
+ def ==(other)
+ hash == other.hash
+ end
+
+ def to_s
+ "Commit #{hash}\nDate: #{date}\n\n\t#{message}"
+ end
+
+ def apply
+ @changes.each { |change| change.apply @store }
+ end
+
+ def undo
+ @changes.reverse_each { |change| change.undo @store }
+ end
+end
+
+class Branch
+ include Enumerable
+
+ attr_reader :name, :head
+
+ def initialize(name, head)
+ @name = name
+ @head = head
+ each { |commit| commit.include_in_branch @name }
+ end
+
+ def each
+ commit = @head
+ while commit
+ yield commit
+ commit = commit.parent
+ end
+ end
+
+ def empty?
+ @head.nil?
+ end
+
+ def commit(message, changes, store)
+ @head = Commit.new(message, changes, @head, store)
+ @head.include_in_branch @name
+ @head.apply
+ end
+
+ def drop_all_commits
+ each { |commit| commit.exclude_from_branch @name}
+ @head = nil
+ end
+
+ def checkout(hash)
+ return nil if none? { |commit| hash == commit.hash }
+ while @head.hash != hash
+ @head.undo
+ @head.exclude_from_branch @name
+ @head = @head.parent
+ end
+ @head
+ end
+
+ def undo(other)
+ take_while { |commit| not commit.on_branch? other }.each(&:undo)
+ end
+
+ def apply(other)
+ take_while { |commit| not commit.on_branch? other }.reverse_each(&:apply)
+ end
+end
+
+class StoreState
+ BRANCH_MASTER = 'master'
+
+ def initialize
+ @branches = { BRANCH_MASTER => Branch.new(BRANCH_MASTER, nil) }
+ @active_branch = @branches[BRANCH_MASTER]
+ @changes = []
+ @objects = {}
+ end
+
+ def perform(&block)
+ self.instance_eval(&block)
+ end
+end
+
+class ObjectStore
+ def self.init(&block)
+ store = ObjectStore.new
+ store.instance_eval(&block) if block_given?
+ store
+ end
+
+ def initialize
+ @state = StoreState.new
+ end
+
+ def add(name, object)
+ @state.perform do
+ answer.positively("Added #{name} to stage.").with_result do
+ @changes.push(ActionAdd.new name, object)
+ object
+ end
+ end
+ end
+
+ def remove(name)
+ @state.perform do
+ answer.negatively("Object #{name} is not committed.").when do
+ not @objects.key? name
+ end or
+
+ answer.positively("Added #{name} for removal.").with_result do
+ @changes.push ActionRemove.new(name)
+ @objects[name]
+ end
+ end
+ end
+
+ def commit(message)
+ @state.perform do
+ count = @changes.map(&:name).uniq.count
+
+ answer.negatively('Nothing to commit, working directory clean.').when do
+ @changes.empty?
+ end or
+
+ answer.positively("#{message}\n\t#{count} objects changed").to do
+ @active_branch.commit(message, @changes, @objects)
+ @changes = []
+ end
+ end
+ end
+
+ def head
+ @state.perform do
+ name = @active_branch.name
+ commit = @active_branch.head
+
+ answer.negatively("Branch #{name} does not have any commits yet.").when do
+ @active_branch.empty?
+ end or
+
+ answer.positively(commit.message).with_result do
+ commit
+ end
+ end
+ end
+
+ def get(name)
+ @state.perform do
+ answer.negatively("Object #{name} is not committed.").when do
+ not @objects.key? name
+ end or
+
+ answer.positively("Found object #{name}.").with_result do
+ @objects[name]
+ end
+ end
+ end
+
+ def log
+ @state.perform do
+ name = @active_branch.name
+
+ answer.negatively("Branch #{name} does not have any commits yet.").when do
+ @active_branch.empty?
+ end or
+
+ answer.positively(@active_branch.to_a.join("\n\n")).to { }
+ end
+ end
+
+ def checkout(hash)
+ @state.perform do
+ new_head = @active_branch.checkout hash
+
+ answer.negatively("Commit #{hash} does not exist.").when do
+ new_head.nil?
+ end or
+
+ answer.positively("HEAD is now at #{hash}.").with_result do
+ new_head
+ end
+ end
+ end
+
+ def branch
+ BranchController.new @state
+ end
+end
+
+class BranchController
+ def initialize(state)
+ @state = state
+ end
+
+ def create(name)
+ @state.perform do
+ answer.negatively("Branch #{name} already exists.").when do
+ @branches.key? name
+ end or
+
+ answer.positively("Created branch #{name}.").to do
+ @branches[name] = Branch.new(name, @active_branch.head)
+ end
+ end
+ end
+
+ def checkout(target_name)
+ @state.perform do
+ answer.negatively("Branch #{target_name} does not exist.").when do
+ not @branches.key? target_name
+ end or
+
+ answer.positively("Switched to branch #{target_name}.").to do
+ current_name = @active_branch.name
+ @active_branch.undo target_name
+
+ @active_branch = @branches[target_name]
+ @active_branch.apply current_name
+ end
+ end
+ end
+
+ def remove(name)
+ @state.perform do
+ answer.negatively("Branch #{name} does not exist.").when do
+ not @branches.key? name
+ end or
+
+ answer.negatively('Cannot remove current branch.').when do
+ @active_branch.name == name
+ end or
+
+ answer.positively("Removed branch #{name}.").to do
+ @branches[name].drop_all_commits
+ @branches.delete name
+ end
+ end
+ end
+
+ def list
+ @state.perform do
+ branch_names = @branches.keys.map.sort.map do |name|
+ @active_branch.name == name and "* #{name}" or " #{name}"
+ end
+
+ answer.positively(branch_names.join "\n").to { }
+ end
+ end
+end