Пета задача

Предадени решения

Краен срок:
23.11.2015 17:30
Точки:
6

Срокът за предаване на решения е отминал

ObjectStore

Git е система за менажиране на версии на файлове. Позволява ви да пазите пълна история на това, върху което работите. В тази задача ще ви накараме да имплементирате нещо, което функционира като Git, но работи за обекти в света на Ruby.

Хранилище (repository)

Всичко се върти около обект - "хранилище". Той ви дава достъп до интерфейса на системата за менажиране на версии и ви позволява да достъпвате запазените обекти.

init

Ново хранилище създаваме използвайки метода ObjectStore.init

repo = ObjectStore.init

В рамките на една програма е позволено паралелното съществуване на много на брой хранилища, пазещи, независимо едно от друго, различни обекти.

Методът init може да приеме блок, чрез който да се изпълнят команди върху новосъздаденото хранилище. Резултатът от метода отново е хранилището.

repo = ObjectStore.init do
  add("value", 21)
  commit("message")
end

Операции

Разполагате с набор от операции (методи), които може да изпълнявате върху създаденото хранилище. Всяка операция връща обект, който указва дали тя е била успешна или не.

Върнатият обект отговаря на методите:

  • message, връщайки пояснително съобщение за извършената операция.
  • success? и error? със стойностите true и false, в зависимост от това дали операцията е била успешна или не.
  • При успешна операция е възможно върнатият обект да отговаря на метода result, връщайки друг обект - резултат от операцията, но това не е задължително за всяка операция.

По-долу са изброени поддържаните операции, заедно с очакваните резултати.

Транзакция (commit)

За да добавите обект в хранилището, използвате метода ObjectStore#add. За да завършите "транзакцията", използвате метода commit на хранилището.

repo.add("important", "This is my first version.")
repo.add("not_so_important", "This is just a note.")
repo.commit("A commit message goes here...")

add

# add(name, object)
repo.add("important", "This is my first version.")

Резултатът от метода add е успех:

message: "Added $name$ to stage." # $name$ e името, под което ще запишем обекта.
result: Добавеният току-що обект.

commit

# commit(message)
repo.commit("A commit message goes here...")

Резултатът от метода commit при 0 променени обекта е грешка:

message: "Nothing to commit, working directory clean."

Резултатът от метода commit при променени обекти е успех:

message: "$message$\n\t$count$ objects changed" # $message$ е commit съобщението. $count$ е броят на променените обекти.

remove

Може да използвате метода remove на хранилището, за да премахнете обект. Премахнатият обект може да бъде добавен отново в следващ commit.

# remove(name)
repo.remove("not_so_important")
repo.commit("This was not that important after all")

Резултатът от метода remove при несъществуващ обект с това име е грешка:

message: "Object $name$ is not committed." # $name$ e името на обекта, който се опитваме да изтрием.

Резултатът от метода remove при съществуващ обект с това име е успех:

message: "Added $name$ for removal." # $name$ e името, под което сме записали обекта.
result: Обектът, добавен за премахване.

checkout

Може да използвате метода checkout на хранилището, за да се върнете към предишен commit. След checkout, всички по-нови commit-и стават недостъпни.

# checkout(commit_hash)
repo.checkout("922b99527d4ee8ba672c0d69ddf2e1182b47a8c5")

Резултатът от метода checkout при несъществуващ commit с този hash е грешка:

message: "Commit $hash$ does not exist." # $hash$ e Sha1 hash-а на commit-а.

Как се конструира $hash$-ът на даден commit е обяснено по-долу, в секцията за история на commit-ите.

Резултатът от метода checkout при съществуващ commit с този hash е успех:

message: "HEAD is now at $hash$." # $hash$ е Sha1 hash-а на commit-а
result: Commit-ът, към който сте се върнали.

Разклонение (branch)

Branch-овете ви позволяват да променяте обектите си по независими начини.

При създаване на хранилище по подразбиране се създава branch с името "master" и той става активен (текущ).

Всяко хранилище трябва да има инстанционен метод branch, който да връща обект, отговарящ като минимум на описания по-долу интерфейс.

create

Възможно е в даден момент да създадете няколко различни клона на обектите, намиращи се в хранилището, позволявайки те да се променят независимо. Това става с помощта на метода create на обекта, върнат от ObjectStore#branch.

# create(branch_name)
repo.branch.create("develop")

Резултатът от метода branch.create при вече съществуващ branch с това име е грешка:

message: "Branch $name$ already exists." # $name$ e името, което сте избрали за новия branch.

Резултатът от метода branch.create при несъществуващ branch с това име е успех:

message: "Created branch $name$." # $name$ e името, под което сме записали обекта.

Това създава нов branch, съдържащ всички commit-и от текущия branch.

checkout

Ако желаете да промените текущия branch на новосъздадения, може да използвате метода branch.checkout на хранилището.

# checkout(branch_name)
repo.branch.checkout("develop")

От тук нататък, добавянето на нови обекти в хранилището променя историята от commit-и на "develop" branch-а, останалите branch-ове не се променят.

Резултатът от метода branch.checkout при несъществуващ branch с това име е грешка:

message: "Branch $name$ does not exist." # $name$ e името на branch-а, към който искате да преминете.

Резултатът от метода branch.checkout при съществуващ branch с това име е успех:

message: "Switched to branch $name$." # $name$ e името на branch-а, към който искате да преминете.

remove

Ако желаете да премахнете branch, може да използвате метода branch.remove на хранилището.

# remove(branch_name)
repo.branch.remove("develop")

Резултатът от метода branch.remove при несъществуващ branch с това име е грешка:

message: "Branch $name$ does not exist." # $name$ e името на branch-а, който искате да премахнете.

Резултатът от метода branch.remove при опит за премахване на текущия branch е грешка:

message: "Cannot remove current branch."

Резултатът от метода branch.remove при съществуващ branch с това име, различен от текущия, е успех:

message: "Removed branch $name$." # $name$ e името на branch-а, който искате да премахнете.

list

За да проверите какви branch-ове съществуват в хранилището, може да използвате метода branch.list. Този метод винаги връща успех, като в съобщението на резултата са изредени всички налични branch-ове, сортирани във възходящ лексикографски (азбучен) ред. Всяко име на branch се намира на самостоятелен ред в текста на съобщението. Ако branch-ът е текущият, то името му се предшества от текста * (звезда и интервал), а ако не е текущият – от два интервала.

Например, ако имаме следния код:

# list()
repo.branch.create("develop")
repo.branch.list

Тогава върнатият от list обект ще има метод message, който трябва да връща следния текст:

message: "  develop\n* master"

История на commit-ите (log)

Във всеки един момент може да видите историята от commit-и в текущо активния branch, използвайки метода log на хранилището.

repo.log

Резултатът от метода log при несъществуващи commit-и в текущия branch е грешка:

message: "Branch $name$ does not have any commits yet." # $name$ е името на текущия branch.

Резултатът от метода log при съществуващи commit-и в текущия branch е успех:

message: "Commit $hash$\nDate: $date$\n\n\t$message$"
# Повтаря се за всеки commit в текущия branch. Commit-ите се разделят с празен ред (т.е.
# два символа за нов ред). Последният commit се намира в началото на съобщението.
#
# $message$ e съобщението на commit-а.
# $date$ е датата, в която е направен commit-а, във формат
# "<ден от седмицата> <месец> <ден от месец> <час>:<минути> <година> <часова зона в часове и минути спрямо UTC>".
# Пример: "Fri Nov 6 14:15 2015 +0000".
# $hash$ е Sha1 hash на текста "$date$$message$".

Например, ако имаме следното хранилище:

repo = ObjectStore.init
repo.add('foo1', :bar1)
repo.commit('First commit')

repo.add('foo2', :bar2)
repo.commit('Second commit')

То ако изведем на екрана резултата от repo.log.message, ще видим следното:

Commit aae266d7a5fc76773744bd255ab6251c5df8ae40
Date: Thu Nov 12 12:04:54 2015 +0200

    Second commit

Commit c96e8e024543d3378a302f3b603c6ee5d99f4673
Date: Thu Nov 12 12:04:38 2015 +0200

    First commit

Последен commit (head)

Методът head на хранилището ви дава достъп до последния commit в текущия branch.

Резултатът от метода head при несъществуващи commit-и в текущия branch е грешка:

message: "Branch $name$ does not have any commits yet." # $name$ е името на текущия branch.

Резултатът от метода head при съществуващи commit-и в текущия branch е успех:

message: "$message$" # $message$ e съобщението на последния commit в текущия branch.
result: Обект, представляващ последния commit в текущия branch.

Обектът, връщан от метода result на резултата на head, представлява commit обект. Този обект трябва да отговаря на следните методи:

  • date - времето на създаване на commit-а като инстанция на вградения в Ruby клас Time
  • message - съобщението на commit-а като низ
  • hash - Sha1 hash-а на commit-а
  • objects - списък от всички обекти, налични в хранилището към момента на commit-а, в най-актуалната им към момента на commit-а версия. Редът, в който ще ги върнете, няма значение.

Търсене на обект (get)

Търсенето на обект в хранилище се съобразява с всички commit-и в текущо активния branch на хранилището. С най-голяма тежест е последният commit. Ако един обект е добавен в commit и е премахнат в следващ, той не може да бъде достъпен от get. get връща най-актуалната версия на обекта.

# get(name)
repo.get("important")

Резултатът от метода get при неоткрит обект е грешка:

message: "Object $name$ is not committed." # $name$ е името на търсения обект.

Резултатът от метода get при открит обект е успех:

message: "Found object $name$." # $name$ e името на търсения обект.
result: Търсеният обект.

Бележки

Не е необходимо да мислите за клониране на обектите, които биват добавени в хранилището. Ако някой добави и commit-не обект тип "речник" в хранилището и след това мутира речника, това не ви интересува. Няма да има такива тестове.

Можете да създавате колкото искате допълнителни класове, да влагате класове в класове и модули и т.н. Всеки клас може да има колкото искате допълнителни методи, ако отговаря на ограниченията и на минималния интерфейс, описан в условието.

Разрешено е да ползвате вградени в Ruby библиотеки (част от т.нар. "стандартна библиотека", накратко stdlib). Ако ползвате такава, трябва да я заредите в началото на решението си с require 'име_на_библиотеката'.

Ограничения

Тази задача има следните ограничения:

  • Най-много 80 символа на ред
  • Най-много 10 реда на метод
  • Най-много 2 нива на влагане
  • Най-много 4 аргумента на метод

Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.

Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop редовно, докато пишете кода. Ако смятате, че rubocop греши по някакъв начин, пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като private gist. Ако пуснете кода си публично (например във форумите), ще смятаме това за преписване.