Теодор обнови решението на 22.11.2015 14:20 (преди почти 10 години)
+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
