IT-компания <<VK>> имеет огромную инфраструктуру проектов, и чтобы разработчики могли гармонично работать вместе и структурировать вносимые в код изменения, им необходима система контроля версий (VCS).
Полноценные системы контроля версий устроены достаточно сложно, и некоторые компании даже разрабатывают свои собственные VCS, заточенные под конкретные нужды или особенности рабочего процесса. Разумеется, разработчики из <<VK>> могут справиться с задачей реализовать свою VCS, но сегодня эта задача предлагается вам.
Ваша примитивная система контроля версий должна иметь вид ацикличного ориентированного графа изменений состояния проекта.
-
<<Исток>> графа — стартовая вершина, соответствующая изначальному состоянию проекта. Это единственная вершина, в которую не входят ребра.
-
Каждая вершина, кроме корня, соответствует определенному коммиту. Коммит — блок из одного или более изменений.
-
Версия проекта, задаваемая вершиной — набор всех изменений на путях между стартовой вершиной и данной.
-
Есть несколько выделенных веток, каждая имеет уникальное имя и задается указателем на определенную вершину графа. С веткой ассоциируется версия проекта, соответствующая вершине, на которую она указывает.
-
Из всех веток выделяется текущая ветка — версия проекта, с которой сейчас работает пользователь. Вершину, на которую указывает текущая ветка, будем обозначать как HEAD
.
Пример графа можно видеть ниже. Рядом с каждым коммитом указаны соответствующие ему изменения. Вершина номер \(8\) соответствует команде merge
между ветками <<main
>> и <<new_config
>> (описание команд см. ниже). Набор команд, позволяющий получить приведенную структуру графа версий, приведен в первом тесте. Пошаговые иллюстрации изменения графа версий можно видеть в прикрепленном к условию архиве.
Изначально граф состоит из единственной вершины с номером \(1\), на которую указывает текущая ветка <<main
>>. Требуется поддерживать следующие команды:
-
<<add <файл> <хеш изменений>
>> — запомнить изменения, внесенные в данный файл. Хеш однозначно описывает набор изменений в файле. Иными словами, хеши двух независимых изменений совпадают тогда и только тогда, когда состояния файла до и после изменения одинаковы.
Иными словами, если в пустой файл добавляется строчка <<print(something)
>>, и в непустой файл добавляется та же строчка, хеши этих двух изменений будут различными.
-
<<commit
>> — подвесить к HEAD
новую вершину, состоящую из всех изменений (add
), сделанных с момента предыдущего успешного коммита. Новой вершине присваивается первый неиспользованный натуральный номер, после чего HEAD
перемещается на нее.
Операция возможна только тогда, когда множество сделанных изменений непустое. В противном случае требуется выдать ошибку <<ERROR: no changes
>>, при этом новая вершина не создается.
-
<<reset <номер>
>> — переместить HEAD
на вершину с данным номером.
Гарантируется, что вершина с данным номером существует. Если присутствуют несохраненные (commit
) изменения (add
), операция отклоняется с ошибкой <<ERROR: uncommitted changes
>>.
-
<<checkout <имя ветки>
>> — поменять текущую ветку на ветку с указанным именем (и, соответственно, переместить HEAD
на версию, соответствующую выбранной ветке). Если ветки с таким именем не существует, создать новую ветку с таким именем, которая будет указывать на HEAD
, после чего сделать ее текущей.
Если присутствуют несохраненные изменения, операция отклоняется с ошибкой <<ERROR: uncommitted changes
>>, аналолгично операции reset
.
-
<<merge <имя ветки>
>> — объединить изменения в текущей ветке и в ветке с указанным именем. При этом создается новая вершина, которая подвешивается к HEAD
и к вершине, соответствующей второй ветке. HEAD
при этом перемещается на новую вершину, а указатель второй ветки не двигается. Гарантируется, что имя второй ветки не совпадает с текущей.
Если есть незакоммиченные изменения, операция отклоняется с ошибкой <<ERROR: uncommitted changes
>>.
Если несохраненных изменений нет, проверяется отсутствие конфликтов при объединении. Конфликтом считается ситуация, когда в состояниях проекта, соответствующим данным веткам, с одним и тем же файлом сделаны различные изменения. Иными словами, если обозначить множество изменений определенного файла в текущей ветке как \(D_1\), а во второй — как \(D_2\), то конфликт возникает, когда оба множества \(D_1 \setminus D_2\) и \(D_2 \setminus D_1\) непустые. В таком случае операция отклоняется с ошибкой <<ERROR: conflicts detected
>>.
Обратите внимание, что множества коммитов в двух версиях сравниваются как математические множества. Стоит считать, что ситуаций, когда в разных ветках одни и те же изменения одного файла располагаются в разном порядке, не существует.
Если какое-то изменение содержится в версиях разное ненулевое количество раз (например, изменение может присутствовать дважды после merge
двух веток, в которые оно входило), конфликт не возникает. Конфликт возникает только если в каждой версии есть измененение одного и того же файла, которого нет в другой версии.
Ниже можно найти иллюстрации к успешным и конфликтным вызовам операции merge
.
Вам дается последовательность команд, которые требуется обработать. Для каждой команды выведите через пробел слово <<OK
>> и номер вершины, на которую указывает HEAD
, если команда выполнена успешно, или же соответствующую ошибку, если команда отклонена.
Формат входных данных
В первой строке дано единственное целое число \(T\) — количество наборов входных данных, которые вам предстоит обработать (\(1 \le T \le 20\)). Далее следуют описания наборов входных данных.
Каждый набор входных данных начинается со строки, содержащей единственное целое число \(n\) — количество команд в наборе (\(0 \le n \le 400\)). В \(i\)-й из следующих \(n\) строк задается \(i\)-я команда.
Гарантируется, что имена веток состоят только из маленьких латинских букв (‘a
’ – ‘z
’) и нижних подчеркиваний (‘_
’), а хеши изменений — уникальные шестнадцатеричные строки длины ровно \(6\) (состоят из цифр и маленьких латинских букв от ‘a
’ до ‘f
’). Имена файлов в команде add
состоят из маленьких латинских букв, точек и слешей (‘/
’).
Также гарантируется, что команде reset
всегда передается существующая вершина, а команде merge
— существующая ветка, не совпадающая с текущей.
Формат выходных данных
Для каждого набора входных данных в порядке их следования во вводе сначала выведите строку <<Test case <номер>
>> (наборы нумеруются от \(1\) до \(T\)), а затем \(n\) результатов выполнения команд, каждый в своей строке.
Результат выполнения каждой команды должен соответствовать либо формату <<OK <HEAD>
>>, либо формату <<ERROR: <сообщение об ошибке>
>>.
Примеры
№ | Входные данные | Выходные данные |
1
|
2
12
add a.cpp 1f1f1f
reset 1
commit
checkout dev
reset 1
add a.cpp d2d2d2
commit
merge main
reset 1
add b.cpp abc123
commit
merge main
23
add main.cpp 1a44bf
add conf.json 3bb3c1
commit
add conf.json 11fdd5
checkout new_config
commit
checkout new_config
checkout main
reset 2
checkout experimental
add driver.cpp b19de7
commit
checkout main
add main.cpp aa46ac
add core/db.cpp f78c43
commit
add core/socket.cpp 554d3b
commit
checkout new_config
add conf.json bbf451
commit
checkout main
merge new_config
|
Test case 1
OK 1
ERROR: uncommitted changes
OK 2
OK 2
OK 1
OK 1
OK 3
ERROR: conflicts detected
OK 1
OK 1
OK 4
OK 5
Test case 2
OK 1
OK 1
OK 2
OK 2
ERROR: uncommitted changes
OK 3
OK 3
OK 3
OK 2
OK 2
OK 2
OK 4
OK 2
OK 2
OK 2
OK 5
OK 5
OK 6
OK 3
OK 3
OK 7
OK 6
OK 8
|