Йоан обнови решението на 22.11.2015 01:20 (преди над 9 години)
+require 'digest/sha1'
+
+class ObjectStoreError < StandardError
+end
+
+class UnexistingBranchError < ObjectStoreError
+ def info
+ "Branch #{message} does not exist."
+ end
+end
+
+class EmptyBranchError < ObjectStoreError
+ def info
+ "Branch #{message} does not have any commits yet."
+ end
+end
+
+class ObjectNotCommitedError < ObjectStoreError
+ def info
+ "Object #{message} is not committed."
+ end
+end
+
+class UnexistingCommitError < ObjectStoreError
+ def info
+ "Commit #{message} does not exist."
+ end
+end
+
+class Commit
+ attr_reader :date
+ attr_reader :objects
+ attr_reader :message
+
+ def initialize(message, objects)
+ @objects = objects
+ @message = message
+ @date = Time.new
+ end
+
+ def hash
+ Digest::SHA1.hexdigest "#{formatted_date}#{message}"
+ end
+
+ def to_s
+ "Commit #{hash}\nDate: #{formatted_date}\n\n\t#{message}"
+ end
+
+ private
+
+ def formatted_date
+ date.strftime('%a %b %d %H:%M %Y %z')
+ end
+end
+
+class Operation
+ attr_reader :message
+ attr_reader :result
+
+ def initialize(message, successful = true, result: nil)
+ @message = message
+ @successful = successful
+ @result = result
+ end
+
+ def success?
+ @successful
+ end
+
+ def error?
+ !(@successful)
+ end
+end
+
+class Branch
+ attr_accessor :current
+ attr_reader :name
+ attr_reader :commits
+
+ def initialize(name, commits = [], current: false)
+ @commits = commits
+ @name = name
+ @objects = {}
+ @current = current
+ end
+
+ def to_s
+ prefix = @current ? '*' : ' '
+
+ "#{prefix} #{@name}"
+ end
+
+ def commit(message, commands)
+ error_message = 'Nothing to commit, working directory clean.'
+
+ fail ObjectStoreError, error_message if commands.empty?
+
+ apply_commands(commands)
+
+ commit = Commit.new(message, @objects.dup)
+
+ @commits.push(commit)
+
+ success_message = "#{message}\n\t#{commands.length} objects changed"
+
+ Operation.new(success_message, result: commit)
+ rescue ObjectStoreError => error
+ Operation.new(error.message, false)
+ end
+
+ def head
+ fail EmptyBranchError, name if @commits.empty?
+
+ latest_commit = @commits.last
+
+ Operation.new(latest_commit.message, result: latest_commit)
+ rescue EmptyBranchError => error
+ Operation.new(error.info, false)
+ end
+
+ def get(name)
+ fail ObjectNotCommitedError, name unless @objects.key? name
+
+ Operation.new("Found object #{name}.", result: @objects[name])
+ rescue ObjectNotCommitedError => error
+ Operation.new(error.info, false)
+ end
+
+ def log
+ fail EmptyBranchError, name if @commits.empty?
+
+ Operation.new(@commits.reverse.map(&:to_s).join("\n\n"))
+ rescue EmptyBranchError => error
+ Operation.new(error.info, false)
+ end
+
+ def checkout(hash)
+ commit_index = get_commit_index(hash)
+
+ remove_commits(commit_index)
+
+ current_commit = @commits[commit_index]
+
+ @objects = current_commit.objects.dup
+
+ success_message = "HEAD is now at #{current_commit.hash}."
+
+ Operation.new(success_message, result: current_commit)
+ rescue UnexistingCommitError => error
+ Operation.new(error.info, false)
+ end
+
+ private
+
+ def apply_commands(commands)
+ commands.each do |command|
+ if command.type == :add
+ @objects[command.identificator] = command.data
+ else
+ @objects.delete(command.identificator)
+ end
+ end
+ end
+
+ def remove_commits(from_index)
+ count = @commits.length
+
+ removal_count = count - from_index
+
+ @commits.slice!(from_index + 1, removal_count - 1)
+ end
+
+ def get_commit_index(hash)
+ commit_index = @commits.index { |commit| commit.hash == hash }
+
+ fail UnexistingCommitError, hash if commit_index.nil?
+
+ commit_index
+ end
+end
+
+class BranchFactory
+ def initialize
+ @branches = [Branch.new('master', current: true)]
+ end
+
+ def create(name)
+ error_message = "Branch #{name} already exists."
+
+ branch_exists = @branches.any? { |branch| branch.name == name }
+
+ return Operation.new(error_message, false) if branch_exists
+
+ @branches.push(Branch.new(name, current.commits))
+
+ Operation.new("Created branch #{name}.")
+ end
+
+ def checkout(name)
+ branch_index = get_branch_index(name)
+
+ current.current = false
+
+ @branches[branch_index].current = true
+
+ Operation.new("Switched to branch #{name}.")
+ rescue UnexistingBranchError => error
+ Operation.new(error.info, false)
+ end
+
+ def remove(name)
+ branch_index = get_branch_index(name)
+
+ error_message = 'Cannot remove current branch.'
+
+ fail ObjectStoreError, error_message if @branches[branch_index].current
+
+ @branches.slice!(branch_index)
+
+ Operation.new("Removed branch #{name}.")
+ rescue UnexistingBranchError => error
+ Operation.new(error.info, false)
+ rescue ObjectStoreError => error
+ Operation.new(error.message, false)
+ end
+
+ def list
+ sort_branches = @branches.sort_by(&:name)
+
+ Operation.new(sort_branches.map(&:to_s).join("\n"))
+ end
+
+ def current
+ current_branch_index = @branches.index { |branch| branch.current == true }
+
+ @branches[current_branch_index]
+ end
+
+ private
+
+ def get_branch_index(name)
+ branch_index = @branches.index { |branch| branch.name == name }
+
+ fail UnexistingBranchError, name if branch_index.nil?
+
+ branch_index
+ end
+end
+
+class Command
+ attr_reader :type
+ attr_reader :identificator
+ attr_reader :data
+
+ def initialize(type, identificator, data = nil)
+ @type = type
+ @identificator = identificator
+ @data = data
+ end
+end
+
+class ObjectStore
+ def initialize
+ @branch_factory = BranchFactory.new
+ @staging = []
+ end
+
+ def self.init(&block)
+ instance = new
+
+ instance.instance_eval(&block) if block_given?
+
+ instance
+ end
+
+ def add(name, object)
+ @staging.push(Command.new(:add, name, object))
+
+ Operation.new("Added #{name} to stage.", result: object)
+ end
+
+ def remove(name)
+ return get(name) if get(name).error?
+
+ @staging.push(Command.new(:remove, name))
+
+ successful_message = "Added #{name} for removal."
+
+ Operation.new(successful_message, result: get(name))
+ end
+
+ def commit(message)
+ current_staging = @staging.dup
+
+ @staging.clear
+
+ @branch_factory.current.commit(message, current_staging)
+ end
+
+ def branch
+ @branch_factory
+ end
+
+ def get(name)
+ branch.current.get(name)
+ end
+
+ def head
+ branch.current.head
+ end
+
+ def log
+ branch.current.log
+ end
+
+ def checkout(hash)
+ branch.current.checkout(hash)
+ end
+
+ private
+
+ def files
+ branch.current.files
+ end
+end