Олимпиадный тренинг

Задача . Система контроля версий


Задача

Темы:

IT-компания <<VK>> имеет огромную инфраструктуру проектов, и чтобы разработчики могли гармонично работать вместе и структурировать вносимые в код изменения, им необходима система контроля версий (VCS).

Полноценные системы контроля версий устроены достаточно сложно, и некоторые компании даже разрабатывают свои собственные VCS, заточенные под конкретные нужды или особенности рабочего процесса. Разумеется, разработчики из <<VK>> могут справиться с задачей реализовать свою VCS, но сегодня эта задача предлагается вам.

Ваша примитивная система контроля версий должна иметь вид ацикличного ориентированного графа изменений состояния проекта.

  • <<Исток>> графа — стартовая вершина, соответствующая изначальному состоянию проекта. Это единственная вершина, в которую не входят ребра.

  • Каждая вершина, кроме корня, соответствует определенному коммиту. Коммит — блок из одного или более изменений.

  • Версия проекта, задаваемая вершиной — набор всех изменений на путях между стартовой вершиной и данной.

  • Есть несколько выделенных веток, каждая имеет уникальное имя и задается указателем на определенную вершину графа. С веткой ассоциируется версия проекта, соответствующая вершине, на которую она указывает.

  • Из всех веток выделяется текущая ветка — версия проекта, с которой сейчас работает пользователь. Вершину, на которую указывает текущая ветка, будем обозначать как HEAD.

Пример графа можно видеть ниже. Рядом с каждым коммитом указаны соответствующие ему изменения. Вершина номер \(8\) соответствует команде merge между ветками <<main>> и <<new_config>> (описание команд см. ниже). Набор команд, позволяющий получить приведенную структуру графа версий, приведен в первом тесте. Пошаговые иллюстрации изменения графа версий можно видеть в прикрепленном к условию архиве.

Изначально граф состоит из единственной вершины с номером \(1\), на которую указывает текущая ветка <<main>>. Требуется поддерживать следующие команды:

  1. <<add <файл> <хеш изменений>>> — запомнить изменения, внесенные в данный файл. Хеш однозначно описывает набор изменений в файле. Иными словами, хеши двух независимых изменений совпадают тогда и только тогда, когда состояния файла до и после изменения одинаковы.

    Иными словами, если в пустой файл добавляется строчка <<print(something)>>, и в непустой файл добавляется та же строчка, хеши этих двух изменений будут различными.

  2. <<commit>> — подвесить к HEAD новую вершину, состоящую из всех изменений (add), сделанных с момента предыдущего успешного коммита. Новой вершине присваивается первый неиспользованный натуральный номер, после чего HEAD перемещается на нее.

    Операция возможна только тогда, когда множество сделанных изменений непустое. В противном случае требуется выдать ошибку <<ERROR: no changes>>, при этом новая вершина не создается.

  3. <<reset <номер>>> — переместить HEAD на вершину с данным номером.

    Гарантируется, что вершина с данным номером существует. Если присутствуют несохраненные (commit) изменения (add), операция отклоняется с ошибкой <<ERROR: uncommitted changes>>.

  4. <<checkout <имя ветки>>> — поменять текущую ветку на ветку с указанным именем (и, соответственно, переместить HEAD на версию, соответствующую выбранной ветке). Если ветки с таким именем не существует, создать новую ветку с таким именем, которая будет указывать на HEAD, после чего сделать ее текущей.

    Если присутствуют несохраненные изменения, операция отклоняется с ошибкой <<ERROR: uncommitted changes>>, аналолгично операции reset.

  5. <<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.

    image image

Вам дается последовательность команд, которые требуется обработать. Для каждой команды выведите через пробел слово <<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

time 1000 ms
memory 256 Mb
Правила оформления программ и список ошибок при автоматической проверке задач

Статистика успешных решений по компиляторам
 Кол-во
Python1
Комментарий учителя