Системы контроля версий (VCS) первого поколения отслеживали изменения в отдельных файлах, а редактирование поддерживалось только локально и одним пользователем за раз. Системы строились на предположении, что все пользователи будут заходить по своим учётными записям на один и тот же общий узел Unix.
В VCS второго поколения появилась поддержка сети, что привело к централизованным хранилищам с "официальными" версиями проектов. Это был значительный прогресс, поскольку несколько пользователей могли одновременно работать с кодом, делая коммиты в один и тот же центральный репозиторий. Однако для коммитов требовался доступ к сети.
Третье поколение состоит из распределённых VCS, где все копии репозитория считаются равными, нет центрального репозитория. Это открывает путь для коммитов, ветвей и слияний, которые создаются локально без доступа к сети и перемещаются в другие репозитории по мере необходимости.
Хронология выхода VCS
Для контекста, вот график c датами появления этих инструментов:
SCCS (Source Code Control System): первое поколение
SCCS считается одной из первых успешных систем управления версиями. Она была разработана в 1972 году Марком Рочкиндом из Bell Labs. Система написана на C и создана для отслеживания версий исходного файла. Кроме того, она значительно облегчила поиск источников ошибок в программе. Базовая архитектура и синтаксис SCCS позволяют понять корни современных инструментов VCS.
- Архитектура
Как и большинство современных систем, в SCCS есть набор команд для работы с версиями файлов:
При добавлении файла для отслеживания в SCCS создаётся файл специального типа, который называется s-файл или файл истории. Он именуется как исходный файл, только с префиксом s., и хранится в подкаталоге SCCS. Таким образом, для файла test.txt будет создан файл истории s.test.txt в директории ./SCCS/. В момент создания файл истории содержит начальное содержимое исходного файла, а также некоторые метаданные, помогающие отслеживать версии. Здесь хранятся контрольные суммы для гарантии, что содержимое не было изменено. Содержимое файла истории не сжимается и не кодируется (как в VCS следующего поколения).
Поскольку содержимое исходного файла теперь хранится в файле истории, его можно извлечь в рабочий каталог для просмотра, компиляции или редактирования. В файл истории можно внести изменения, такие как добавления строк, изменения и удаления, что увеличивает его номер версии.
Последующие добавления файла хранят только дельты или изменения, а не всё его содержимое. Это уменьшает размер файла истории. Каждая дельта сохраняется внутри файла истории в структуре под названием дельта-таблица. Как упоминалось ранее, фактическое содержимое файла более или менее копируется дословно, со специальными управляющими последовательностями для маркировки начала и конца разделов добавленного и удалённого содержимого. Поскольку файлы истории SCCS не используют сжатие, они обычно имеют больший размер, чем фактический файл, в котором отслеживаются изменения. SCCS использует метод под названием чередующиеся дельты (interleaved deltas), который гарантирует постоянное время извлечения независимо от давности извлечённой версии, то есть более старые версии извлекаются с той же скоростью, что и новые.
Важно отметить, что все файлы отслеживаются и регистрируются отдельно. Невозможно проверить изменения в нескольких файлах в виде одного атомарного блока, как коммиты в Git. У каждого отслеживаемого файла свой файл истории, в котором хранится его история изменений. В общем случае это означает, что номера версий различных файлов в проекте обычно не совпадают друг с другом. Однако эти версии можно согласовать путём одновременного редактирования всех файлов в проекте (даже не внося в них реальные изменения) и одновременного добавления всех файлов. Это одновременно увеличит номер версии для всех файлов, сохраняя их согласованность, но обратите внимание, что это не то же самое, что включение нескольких файлов в один коммит, как в Git. В SCCS происходит индивидуальное добавление в каждый файл истории, в отличие от одного большого коммита, включающего все изменения сразу.
Когда файл извлекается для редактирования в SCCS, на него ставится блокировка, так что его никто больше не может редактировать. Это предотвращает перезапись изменений другими пользователями, но также ограничивает разработку, потому что в каждый момент времени только один пользователь может работать с данным файлом.
SCCS поддерживает ветви, которые хранят последовательности изменений в определённом файле. Можно произвести слияние ветви с исходной версией или с другой веткой.
- Основные команды
Ниже приведён список наиболее распространенных команд SCCS:
- Пример файла истории SCCS
^Ah20562 ^As 00001/00001/00002 ^Ad D 1.3 19/11/26 14:37:08 jack 3 2 ^Ac Here is a comment. ^Ae ^As 00002/00000/00001 ^Ad D 1.2 19/11/26 14:36:00 jack 2 1 ^Ac No. ^Ae ^As 00001/00000/00000 ^Ad D 1.1 19/11/26 14:35:27 jack 1 0 ^Ac date and time created 19/11/26 14:35:27 by jack ^Ae ^Au ^AU ^Af e 0 ^At ^AT ^AI 1 Hi there ^AE 1 ^AI 2 ^AD 3 This is a test of SCCS ^AE 2 ^AE 3 ^AI 3 A test of SCCS ^AE 3
RCS (Revision Control System): первое поколение
RCS написана в 1982 году Уолтером Тихи на языке С в качестве альтернативы системе SCCS, которая в то время не была опенсорсной.
- Архитектура
У RCS много общего со своим предшественником, в том числе:
Когда файл впервые добавляется в RCS, для него в локальном хранилище создаётся соответствующий файл истории в локальной директории ./RCS/. К этому файлу добавляется расширение ,v, то есть файл с названием test.txt будет отслеживаться файлом под названием test.txt,v.
Для хранения изменений RCS использует схему обратных дельт (reverse-delta). При добавлении файла полный снимок его содержимого сохраняется в файле истории. Когда файл изменяется и возвращается снова, вычисляется дельта на основе существующего содержимого файла истории. Старый снимок отбрасывается, а новый сохраняется вместе с дельтой, чтобы вернуться в старое состояние. Это называется обратной дельтой, так как для извлечения более старой версии RCS берёт последнюю версию и последовательно применяет дельты до тех пор, пока не достигнет нужной версии. Этот метод позволяет очень быстро извлекать текущие версии, так как всегда доступен полный снимок текущей ревизии. Однако чем старше версия, тем больше времени занимает проверка, потому что нужно проверить всё больше дельт.
В SCCS иначе: там извлечение любой версии занимает одинаково времени. Кроме того, в файлах истории RCS не хранится контрольная сумма, поэтому нельзя обеспечить целостность файла.
- Основные команды
Ниже список наиболее распространённых команд RCS:
- Пример файла истории RCS:
head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2019.11.25.05.51.55; author jstopak; state Exp; branches; next 1.1; 1.1 date 2019.11.25.05.49.02; author jstopak; state Exp; branches; next ; desc @This is a test. @ 1.2 log @Edited the file. @ text @hi there, you are my bud. You are so cool! The end. @ 1.1 log @Initial revision @ text @d1 5 a5 1 hi there @
CVS (Concurrent Versions System): второе поколение
CVS создана Диком Груном в 1986 году с целью добавить в систему управления версиями поддержку сети. Она также написана на C и знаменует собой рождение второго поколения инструментов VCS, благодаря которым географически рассредоточенные команды разработчиков получили возможность работать над проектами вместе.
- Архитектура
CVS - это фронтенд для RCS, в нём появился новый набор команд для взаимодействия с файлами в проекте, но под капотом используется тот же формат файла истории RCS и команды RCS. Впервые CVS позволил нескольким разработчикам одновременно работать с одними и теми же файлами. Это реализовано с помощью модели централизованного репозитория. Первый шаг - настройка на удалённом сервере централизованного репозитория с помощью CVS. Затем проекты можно импортировать в репозиторий. Когда проект импортируется в CVS, каждый файл преобразуется в файл истории ,v и хранится в центральной директории: module. Репозиторий обычно находится на удалённом сервере, доступ к которому осуществляется через локальную сеть или интернет.
Разработчик получает копию модуля, который копируется в рабочий каталог на его локальном компьютере. В этом процессе никакие файлы не блокируются, так что нет ограничения на количество разработчиков, которые могут одновременно работать с модулем. Разработчики могут изменять свои файлы и по мере необходимости фиксировать изменения (делать коммит). Если разработчик фиксирует изменение, другие разработчики должны обновить свои рабочие копии с помощью (обычно) автоматизированного процесса слияния перед фиксацией своих изменений. Иногда приходится вручную разрешать конфликты слияния, прежде чем выполнить коммит. CVS также предоставляет возможность создавать и объединять ветви.
- Основные команды:
- Пример файла истории CVS:
head 1.1; branch 1.1.1; access ; symbols start:1.1.1.1 jack:1.1.1; locks ; strict; comment @# @; 1.1 date 2019.11.26.18.45.07; author jstopak; state Exp; branches 1.1.1.1; next ; commitid zsEBhVyPc4lonoMB; 1.1.1.1 date 2019.11.26.18.45.07; author jstopak; state Exp; branches ; next ; commitid zsEBhVyPc4lonoMB; desc @@ 1.1 log @Initial revision @ text @hi there @ 1.1.1.1 log @Imported sources @ text @@
SVN (Subversion): второе поколение
Subversion создана в 2000 году компанией Collabnet Inc., а в настоящее время поддерживается Apache Software Foundation. Система написана на C и разработана как более надёжное централизованное решение, чем CVS.
- Архитектура
Как и CVS, Subversion использует модель централизованного репозитория. Удалённым пользователям требуется сетевое подключение для коммитов в центральный репозиторий.
Subversion представила функциональность атомарных коммитов с гарантией, что коммит либо полностью успешен, либо полностью отменяется в случае проблемы. В CVS при неполадке посреди коммита (например, из-за сбоя сети) репозиторий мог остаться в повреждённом и несогласованном состоянии. Кроме того, коммит или версия в Subversion может включать в себя несколько файлов и директорий. Это важно, потому что позволяет отслеживать наборы связанных изменений вместе как сгруппированный блок, а не отдельно для каждого файла, как в системах прошлого.
В настоящее время Subversion использует файловую систему FSFS (File System atop the File System). Здесь создаётся база данных со структурой файлов и каталогов, которые соответствуют файловой системе хоста. Уникальная особенность FSFS заключается в том, что она предназначена для отслеживания не только файлов и каталогов, но и их версий. Это файловая система с восприятием времени. Кроме того, директории являются полноценными объектами в Subversion. В систему можно коммитить пустые директории, тогда как остальные (даже Git) не замечают их.
При создании репозитория Subversion в его составе создаётся (почти) пустая база данных файлов и папок. Создаётся каталог db/revs, в котором хранится вся информация отслеживания версий для добавленных (зафиксированных) файлов. Каждый коммит (который может включать изменения в нескольких файлах) хранится в новом файле в каталоге revs, и ему присваивается имя с последовательным числовым идентификатором, начинающимся с 1. При первом коммите сохраняется полное содержимое файла. Будущие коммиты одного и того же файла приведут к сохранению только изменений, которые также называются диффами или дельтами - для экономии места. Кроме того, для уменьшения размера дельты сжимаются с помощью алгоритмов сжатия lz4 или zlib.
Такая система работает только до определёного момента. Хотя дельты экономят место, но если их очень много, то на операции уходит немало времени, так как для воссоздания текущего состояния файла нужно обработать все дельты. По этой причине по умолчанию Subversion сохраняет до 1023 дельт на файл, а потом делает новую полную копию файла. Это обеспечивает хороший баланс хранения и скорости.
SVN не использует обычную систему ветвления и тегов. Обычный шаблон репозитория Subversion содержит три папки в корне:
Директория trunk/ используется для продакшн-версии проекта. Директория branches/ - для хранения вложенных папок, соответствующих отдельным ветвям. Директория tags/ - для хранения тегов, представляющих определённые (обычно значительные) версии проекта.
- Основные команды:
- Пример файла истории SVN:
DELTA SVN^B^@^@ ^B ^A<89> hi there ENDREP id: 2-1.0.r1/4 type: file count: 0 text: 1 3 21 9 12f6bb1941df66b8f138a446d4e8670c 279d9035886d4c0427549863c4c2101e4a63e041 0-0/_4 cpath: /trunk/hi.txt copyroot: 0 / DELTA SVN^B^@^@$^B%^A¤$K 6 hi.txt V 15 file 2-1.0.r1/4 END ENDREP id: 0-1.0.r1/6 type: dir count: 0 text: 1 5 48 36 d84cb1c29105ee7739f3e834178e6345 - - cpath: /trunk copyroot: 0 / DELTA SVN^B^@^@'^B#^A¢'K 5 trunk V 14 dir 0-1.0.r1/6 END ENDREP id: 0.0.r1/2 type: dir pred: 0.0.r0/2 count: 1 text: 1 7 46 34 1d30e888ec9e633100992b752c2ff4c2 - - cpath: / copyroot: 0 / _0.0.t0-0 add-dir false false false /trunk _2.0.t0-0 add-file true false false /trunk/hi.txt L2P-INDEX ^A<80>@^A^A^A^M^H^@ä^H÷^Aé^FDÎ^Bzè^AP2L-INDEX ^A<91>^E<80><80>@^A?^@'2^@<8d>»Ý<90>^C§^A^X^@õ ½½^N= ^@ü<8d>Ôã^Ft^V^@<92><9a><89>Ã^E; ^@<8a>åw|I^@<88><83>Î<93>^L`^M^@ù<92>À^Eïú?^[^@^@657 6aad60ec758d121d5181ea4b81a9f5f4 688 75f59082c8b5ab687ae87708432ca406I
Git: третье поколение
Систему Git разработал в 2005 году Линус Торвальдс (создатель Linux). Она написана в основном на C в сочетании с некоторыми сценариями командной строки. Отличается от VCS по функциям, гибкости и скорости. Торвальдс изначально написал систему для кодовой базы Linux, но со временем её сфера использования расширилась, и сегодня это самая популярная в мире система управлениями версиями.
- Архитектура
Git является распределённой системой. Центрального репозитория не существует: все копии создаются равными, что резко отличается от VCS второго поколения, где работа основана на добавлении и извлечении файлов из центрального репозитория. Это означает, что разработчики могут обмениваться изменениями друг с другом непосредственно перед объединением своих изменений в официальную ветвь.
Кроме того, разработчики могут вносить свои изменения в локальную копию репозитория без ведома других репозиториев. Это допускает коммиты без подключения к сети или интернету. Разработчики могут работать локально в автономном режиме, пока не будут готовы поделиться своей работой с другими. В этот момент изменения отправляются в другие репозитории для проверки, тестирования или развёртывания.
Когда файл добавляется для отслеживания в Git, он сжимается с помощью алгоритма сжатия zlib. Результат хэшируется с помощью хэш-функции SHA-1. Это даёт уникальный хэш, который соответствует конкретно содержимому в этом файле. Git хранит его в базе объектов, которая находится в скрытой папке .git/objects. Имя файла - это сгенерированный хэш, а файл содержит сжатый контент. Данные файлы называются блобами и создаются каждый раз при добавлении в репозиторий нового файла (или изменённой версии существующего файла).
Git реализует промежуточный индекс (staging index), который выступает в качестве промежуточной области для изменений, которые готовятся к коммиту. По мере подготовки новых изменений на их сжатое содержимое ссылаются в специальном индексном файле, который принимает форму объекта дерева. Дерево - это объект Git, который связывает блобы с их реальными именами файлов, разрешениями на доступ к файлам и ссылками на другие деревья и таким образом представляет состояние определённого набора файлов и каталогов. Когда все соответствующие изменения подготовлены для коммита, индексное дерево можно зафиксировать в репозитории, который создаёт объект коммит в базе данных объектов Git. Коммит ссылается на дерево заголовков для конкретной версии, а также на автора коммита, адрес электронной почты, дату и сообщение коммита. Каждый коммит также хранит ссылку на свой родительский коммит(-ы), и так со временем создаётся история развития проекта.
Как уже упоминалось, все объекты Git - блобы, деревья и коммиты - сжимаются, хэшируются и хранятся в базе данных объектов на основе их хэшей. Они называются свободными объектами (loose objects). Здесь не используются никакие диффы для экономии места, что делает Git очень быстрым, поскольку полное содержимое каждой версии файла доступно как свободный объект. Однако некоторые операции, такие как передача коммитов в удалённый репозиторий, хранение очень большого количества объектов или ручной запуск команды сборки мусора Git вызывают переупаковку объектов в пакетные файлы. В процессе упаковки вычисляются обратные диффы, которые сжимаются для исключения избыточности и уменьшения размера. В результате создаются файлы .pack с содержимым объекта, а для каждого из них создаётся файл .idx (или индекс) со ссылкой на упакованные объекты и их расположение в пакетном файле.
Когда ветви перемещаются в удалённые хранилища или извлекаются из них, по сети передаются эти пакетные файлы. При вытягивании или извлечении ветвей файлы пакета распаковываются для создания свободных объектов в репозитории объектов.
- Основные команды:
- Пример блоба, дерева и коммита Git
Блоб с хэшем 37d4e6c5c48ba0d245164c4e10d5f41140cab980:
hi there
Объект дерева с хэшем b769f35b07fbe0076dcfc36fd80c121d747ccc04:
100644 blob 37d4e6c5c48ba0d245164c4e10d5f41140cab980hi.txt
Коммит с хэшем dc512627287a61f6111705151f4e53f204fbda9b:
tree b769f35b07fbe0076dcfc36fd80c121d747ccc04 author Jacob Stopak 1574915303 -0800 committer Jacob Stopak 1574915303 -0800 Initial commit
Mercurial: третье поколение
Mercurial создан в 2005 году Мэттом Макколлом и написан на Python. Он тоже разработан для хостинга кодовой базы Linux, но для этой задачи в итоге выбрали Git. Это вторая по популярности система управления версиями, хотя она используется гораздо реже.
- Архитектура
Mercurial - тоже распределённая система, которая позволяет любому числу разработчиков работать со своей копией проекта независимо от других. Mercurial использует многие из тех же технологий, что и Git, в том числе сжатие и хэширование SHA-1, но делает это иначе.
Когда новый файл фиксируется для отслеживания в Mercurial, для него создаётся соответствующий файл revlog в скрытом каталоге .hg/store/data/. Можете рассматривать файл revlog (журнал изменений) как модернизированную версию файлов истории в старых VCS, таких как CVS, RCS и SCCS. В отличие от Git, который создаёт новый блоб для каждой версии каждого подготовленного файла, Mercurial просто создаёт новую запись в revlog для этого файла. Для экономии места каждая новая запись содержит только дельту от предыдущей версии. Когда достигается пороговое число дельт, снова сохраняется полный снимок файла. Это сокращает время поиска при обработке большого количества дельт для восстановления определённой версии файла.
Каждый revlog именуется в соответствии с файлом, который он отслеживает, но с расширением .i или .d. Файлы .d содержат сжатую дельту. Файлы .i используются в качестве индексов для быстрого отслеживания различных версий внутри файлов .d. Для небольших файлов с малым количеством изменений индексы и содержимое хранятся внутри файлов .i. Записи файла revlog сжимаются для производительности и хэшируются для идентификации. Эти хэш-значения именуются nodeid.
При каждом коммите Mercurial отслеживает все версии файлов этого коммита в так называемом манифесте. Манифест также является файлом revlog - в нём хранятся записи, соответствующие определённым состояниям репозитория. Вместо отдельного содержимого файла, как revlog, манифест хранит список имён файлов и nodeids, которые определяют, какие версии файла существуют в версии проекта. Эти записи манифеста также сжимаются и хэшируются. Хэш-значения тоже называются nodeid.
Наконец, Mercurial использует ещё один тип revlog, который называется changelog, то есть журнал изменений. Это список записей, которые связывают каждый коммит со следующей информацией:
Каждая запись changelog также генерирует хэш, известный как его nodeid.
- Основные команды:
- Пример файлов Mercurial
Манифест:
hey.txt208b6e0998e8099b16ad0e43f036ec745d58ec04 hi.txt74568dc1a5b9047c8041edd99dd6f566e78d3a42
Журнал изменений (changelog):
b8ee947ce6f25b84c22fbefecab99ea918fc0969 Jacob Stopak 1575082451 28800 hey.txt Add hey.txt
Вот и всё!