Пета задача
- Краен срок:
- 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. Ако пуснете кода си публично (например във форумите), ще смятаме
това за преписване.