Решение на Пета задача от Йоан Динков

Обратно към всички решения

Към профила на Йоан Динков

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 25 успешни тест(а)
  • 5 неуспешни тест(а)

Код

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

Лог от изпълнението

...F.......F....F...........FF

Failures:

  1) ObjectStore can remove objects
     Failure/Error: expect(repo.remove("object1")).to be_success("Added object1 for removal.", "content1")
       expected #<Operation:0x007f5c97b7f1b0 @message="Added object1 for removal.", @successful=true, @result=#<Operation:0x007f5c97b7f228 @message="Found object object1.", @successful=true, @result="content1">> to be success "Added object1 for removal." and "content1"
     # /tmp/d20160111-5693-1vh6fu2/spec.rb:43:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  2) ObjectStore shows the log for the current branch only
     Failure/Error: expect(repo.log).to be_success("Commit #{commit1_hash}\nDate: #{current_time}\n\n\tFirst commit")
       expected #<Operation:0x007f5c97be6a40 @message="Commit a6bf7130a3150d375443c25d06c1bf8077e4b4fb\nDate: Mon Jan 11 11:55 2016 +0200\n\n\tSecond commit\n\nCommit 9a97b358afe869ba1e523d99dd2101dfc0570dc0\nDate: Mon Jan 11 11:55 2016 +0200\n\n\tFirst commit", @successful=true, @result=nil> to be success "Commit 9a97b358afe869ba1e523d99dd2101dfc0570dc0\nDate: Mon Jan 11 11:55 2016 +0200\n\n\tFirst commit"
     # /tmp/d20160111-5693-1vh6fu2/spec.rb:128:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  3) ObjectStore can switch branches
     Failure/Error: expect(repo.head).to be_success("First commit", first_commit)
       expected #<Operation:0x007f5c97983c30 @message="Second commit", @successful=true, @result=#<Commit:0x007f5c9798c830 @objects={"object2"=>"content2"}, @message="Second commit", @date=2016-01-11 11:55:30 +0200>> to be success "First commit" and #<Commit:0x007f5c97994738 @objects={"object1"=>"content1"}, @message="First commit", @date=2016-01-11 11:55:30 +0200>
     # /tmp/d20160111-5693-1vh6fu2/spec.rb:163:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  4) ObjectStore can show the objects in a repo after overwriting an object
     Failure/Error: expect(first_commit.objects).to match_array(["content1"])
       expected an array, actual collection was {"object1"=>"content1"}
     # /tmp/d20160111-5693-1vh6fu2/spec.rb:243:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

  5) ObjectStore can show the objects of a repo after removing an object
     Failure/Error: expect(first_commit.objects).to match_array(["content1", "content2", "content3"])
       expected an array, actual collection was {"object1"=>"content1", "object2"=>"content2", "object3"=>"content3"}
     # /tmp/d20160111-5693-1vh6fu2/spec.rb:259:in `block (2 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.03273 seconds
30 examples, 5 failures

Failed examples:

rspec /tmp/d20160111-5693-1vh6fu2/spec.rb:38 # ObjectStore can remove objects
rspec /tmp/d20160111-5693-1vh6fu2/spec.rb:111 # ObjectStore shows the log for the current branch only
rspec /tmp/d20160111-5693-1vh6fu2/spec.rb:153 # ObjectStore can switch branches
rspec /tmp/d20160111-5693-1vh6fu2/spec.rb:239 # ObjectStore can show the objects in a repo after overwriting an object
rspec /tmp/d20160111-5693-1vh6fu2/spec.rb:253 # ObjectStore can show the objects of a repo after removing an object

История (1 версия и 0 коментара)

Йоан обнови решението на 22.11.2015 01:20 (преди над 8 години)

+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