Системы контроля версий (на примере Git)

Теоретическая часть

Система контроля версий — программное обеспечение для облегчения работы с изменяющейся информацией.
  • Основная задача системы управление версий — это упрощение работы с потоками изменяющейся информации.
  • Система управления версиями позволяет хранить несколько версий одного и того же документа, при необходимости возвращаться к более ранним версиям, определять, кто и когда сделал то или иное изменение, и многое другое.
  • Какие существуют системы управления версиями: 1. централизованные - код хранится на сервере, любой имеет доступ к централизованному хранилищу; 2. децентрализованные - у каждого есть локальная копия репозитория.
Git — одна из распределенных систем контроля версий.
  • Предназначена в основном для работы распределенной команды разработчиков (т.е. разработчики могут одновременно работать над одним проектом, каждый изменяет свою версию, затем изменения сливаются в центральную).
  • Система Git очень экономична и не требует рассылки большого количества файлов.

Устройство Git

repository — хранилище файлов (база данных) со всей историей изменений проекта за всё его время жизни. Также в репозитории хранятся конфигурации, такие как имя пользователя, ключи шифрования и прочие локальные настройки, которые не копируются при клонировании репозитория.
  • Clone – клон удаленного репозитория, располагается локально. При клонировании копируются все данные, включая историю коммитов и существующие ветки, изменения исходного репозитория и копии синхронизованы.
  • Fork – независимая копия репозитория. После форка новые изменения хранятся только на нём.

Репозиторий содержит 2 главных структуры данных - object store и index.

object store сожержит исходные файлы данных и все логи, информацию об авторе, даты и другую информацию, необходимую для перестроения любой версии или ветви проекта. В object store 4 типа объектов:
  • blob (binary large object) - содержит данные файла, но никаких метаданных о самом файле
  • tree - содержит id всех blob-ов, пути, некоторые метаданные. Может рекурсивно ссылаться на объекты под-дерева таким образом строить полную иерархию файлов
  • commit - содержит метаданные каждого изменения, внесенного в репозиторий (+автор, дата, логи и проч). Каждый коммит указывает на объект в tree и состояние репозитория в момент коммита. Все коммиты так же хранятся в виде дерева. На вершине дерева коммита находится initial (root) commit, у которого нет родителя.
  • tag - хэш объекта, обычно коммита. Может быть и в читаемой форме.

Object store в Git организовано как система хранения с адресацией по содержимому. В частности, каждый объект в хранилище объектов имеет уникальное имя, полученное путем применения SHA1 к содержимому объекта, что дает хэш-значение SHA1. Поскольку полное содержимое объекта влияет на хеш-значение, а хеш-значение считается фактически уникальным для этого конкретного контента, хэш SHA1 является достаточным индексом или именем для этого объекта в базе данных объектов. Любое крошечное изменение в файле приводит к изменению хэша SHA1, в результате чего новая версия файла индексируется отдельно. По мере роста проекта количество изменений растет, но гит использует диск эффективно, сжимая объекты, которые тоже хранятся в object store

index - временный бинарный файл, описывающий файловую структуру репозитория. Индекс меняется после внесения изменений (редактирования добавления/удаления файлов) из staging area и хранит эти изменения до их коммита. Изменения в индексе можно менять или удалять (например при помощи git rebase)

branch — указатель на конкретный commit. Так как commit-ы указывают друг на друга, цепочка таких указателей и есть branch.

HEAD — (специальный указатель) символическая ссылка на последние изменения, указатель на текущую ветку.
  • Ваше рабочее дерево обычно получается из состояния дерева, на которое ссылается HEAD. HEAD — это ссылка на один из заголовков в вашем хранилище, за исключением случаев использования отдельного HEAD, в этом случае он напрямую ссылается на произвольный commit.
  • Не обязательно ссылается на commit, может указывать на ветвь.
  • Обратите внимание на это различие: «head» (в нижнем регистре) относится к любому из названных заголовков в хранилище; «HEAD» (верхний регистр) относится исключительно к текущему активному заголовку(ссылке).
  • HEAD может указывать на именованную вершину какой-либо ветки или на commit.

Отличие Git от других систем контроля версий - content tracking

Во-первых, как было сказано, git хэширует объекты на основе их содержимого, а не пути, и индексирует blob соответсвующего файла по его хэшу. Когда файл меняется, создаются новые hash и blob. Во-вторых, git хранит каждую версию файла, а не только изменения, так как он использует хэш на основе полного содержимого файла. Таким образом, история изменений файла - это вычисляемая разница между всеми blob-ами с их хэшами, а не просто хранение одного файла с указанными изменениями. Файловая структура git совершенно отличается от файловой структуры пользователя, но спокойно может её воспроизвести.

Как структуры данных git соединены вместе

Взаимодействие с другими системами контроля версий: в стандартной поставке Git поддерживается взаимодействие с CVS (импорт и экспорт, эмуляция CVS-сервера) и Subversion (частичная поддержка импорта и экспорта). Стандартный инструмент импорта и экспорта внутри экосистемы — архивы серий версионированных файлов в форматах .tar.gz и .tar.bz2.

Практическая часть

Для использования системы git вам нужно:

  1. Установить программу git на вашей системе.
  2. Настроить программу и проверить её работоспособность локально
  3. Зарегистрировать ваш аккаунт на GitHub (если нужно скопировать репозиторий c github)
  4. Создать локальный репозиторий или копировать репозиторий существующего проекта
  5. Написать файл README.MD
  6. В случае, если вы начинаете проект, создать удаленный репозиторий
  7. Фиксировать изменения локально
  8. Отправлять изменения на удаленный репозиторий
  9. Зарегистрировать аккаунты разработчиков вашего проекта
  10. Выдать им ссылку на проект

Установка git

sudo apt-get update && sudo apt-get upgrade #обновление перед установкой
sudo apt-get install git #установка git

Откройте терминал (Ctrl+Alt+T — терминал, если у вас не назначены другие горячие клавиши) и введите:

git --version

В случае успешной установки на консоль выведется версия вашего git.

Настройка программы Git

Примечание:

Следует упомянуть, что настройку Git вы осуществляете на нескольких уровнях. То есть некоторые настройки вы делаете для определенного пользователя операционной системы (не системы git, а операционной системы). Другие настройки вы делаете для всех пользователей операционной системы. Далее вы можете делать настройки для определенной папки (локально). Вы делаете настройки для репозитория находящегося на сервере. Эти настройки вы можете не делать, если работаете только со своим локальным репозиторием.

Настройка пользователя и емейл:

git config --global user.name "My Name"
git config --global user.email myEmail@example.com

Чтобы ввести настройки только одного репозитория, перейдите в его папку и сделайте то же без --global.

Настройка внешнего редактора:

git config --global core.editor emacs  #подключить внешний редактор emacs

Вы можете выбрать другой текстовый редактор. Например не emacs, a vi или nano или другой на ваше усмотрение.

Настройки git хранятся в файлах.

Git проверяет 4 места для файла конфигурации(здесь в Linux): Файл вашего компьютера .gitconfig. Ваш пользовательский, файл вашего пользователя .gitconfig файл находится в ~/.gitconfig. Второй пользовательский файл конфигурации, расположенный в $ XDG_CONFIG_HOME/git/config или $HOME/.config/git/config. Конфигурационный файл локального репозитория: .git/config

cat .git/config #просмотр конфигурации локального репозитория

Каждый файл добавляет или переопределяет параметры git, определенные в файле над ним.

Вы можете просмотреть файлы конфигурации

(для системы и всех пользователей):

git config --system --list
git config --system --edit

(для пользователя):

git config --global --list
git config --global --edit

Проверка настроек вашей конфигурации git:

git config --list #вывести на экран конфигурацию.

Если список большой, вы можете пролистывать его с помощью стрелок клавиатуры или «pg up», «pg dn». Для выхода клавиша q.

(какая конфигурация, где установлена):

git config --list --show-origin

Команды Git(консольные)

Для чего нужно рассмотреть консольные команды, ведь существуют UI? Часто в консоли вы можете сделать, что-то гораздо быстрее. С помощью набора консольных команд вы сами в будущем сможете автоматизировать процесс. Консольные команды более гибкий инструмент. Почему? Да потому что ваш UI может и «не знать» о существовании той или иной команды. UI может вообще отсутствовать как таковой, например на сервере ввиду своей небезопасности. На первом этапе консольные команды во многом помогут в общем понимании того как работает система. Все их запоминать нет необходимости. Вы в любой момент сможете найти справку по той или иной команде. Теоретические знания, без которых никуда, лучше усваиваются с применением на практике.

Синтаксис команд git: .. code-block:: bash

git опции команда аргументы

Пример:

git branch -d <name> # удалить локальную ветку с именем name
git branch -d bugFix00 #удалить локальную ветку с именем bugFix00.

Опции:

-C — использовать указанную папку репозитория вместо текущей папки;

-c параметр=значение — использовать указанное значение параметра конфигурации;

-p — прокручивать весь вывод с помощью less;

Инициализация локального репозитория и базовый флоу

1. Переходим в папку проекта.

cd ваша_папка #команда терминала, переход в папку с именем ваша_папка

2.

git init #инициализация локального репозитория

3.

git add . #тут мы добавляем все.

Можно добавить отдельный файл Например:

git add имя.расширение

Таким образом мы говорим — отслеживать изменения нашего файла.

Посмотреть все изменения:

git diff

Для добавления всех изменений в папке без удалений файлов:

git add -A / --all

4. Создание commit

git commit #сохранить изменения в локальном репозитории (в индексе)

-m «комментарий» #аргумент создания комментария коммиту. Ваши изменения будут уже с осмысленным комментарием.

Вы можете использовать полное имя ключа, вместо его сокращения. К примеру, вместо -m вы можете использовать --message=«комментарий»:

git commit -m "Ваш осмысленный комментарий"

Чтобы использовать русские буквы в комментариях, нужно сделать предварительные настройки. Вам нужно настроить кодировку символов в системе, кодировку символов в текстовом редакторе или IDE, кодировку символов в терминале, кодировку символов в git.

5.

git show #показать изменения внесенные вашим коммитом

6.

git status  #просмотр текущего состояний git

Показывает информацию — какая ветка текущая, какие файлы изменены и тд. Команда показывает, что находится в рабочей области(в staging area).

7. Чтобы отменить последний коммит:

git reset HEAD~1

или последние n коммитов:

git reset HEAD~n

7. Чтобы отменить конретный коммит:

git revert $commit_hash

Это действие создаст новый коммит, в котором будут отменены изменения.

Ветки.

Создание ветки:

git branch имяВетки #будет создана ветка с именем "имяВетки"
  • Используйте для имени латинские буквы. Тут есть одно замечание. Когда мы создали ветку с некоторым именем, текущей осталась ветка, которая была выделена до этого. Ну например master. И если после создания ветки мы скажем git commit, то будет продолжена ветка master. Непонимание этого часто приводит к ошибкам.
  • Чтобы продолжить новую ветку нужно её создать, потом переключиться на неё и сделать commit.

1. Создаем ветку:

git branch feature #создание ветки с именем "feature" локально

2. Переключаемся на созданную ветку:

git checkout feature #выбор ветки с именем "feature" локально

Или вместо 1 и 2 одной командой:

git checkout -b feature

Переключиться обратно на предыдущую ветку:

git checkout --

Важно: новая ветка будет создаваться с последего коммита текущей ветки. Если нужно создать новую ветку с определенного коммита:

git checkout $commit_hash

Посмотреть все локальные ветки:

git branch -l

Посмотреть все ветки, включая хэш и удаленные ветки (upstream branch):

git branch -v -a

Объединение веток (merge).

Git merge объединяет истории двух веток. Стандартный воркфлоу:

  1. Переключаемся на ветку в которую хотим замержить изменения
git checkout master
  1. Мержим все уникальные коммиты из нужной ветки в текущую:
git merge feature  #объединить текущую ветку с веткой feature

По умолчанию после merge создается merge commit - отдельный коммит. Чтобы его не было, можно делать:

git merge feature --no-commit

Мы можем сделать по другому. Переключиться на ветку feature и объединить её с веткой master. Тогда изменения будут только в ветке feature (обычно так делают, если на ветке feature долго делается какое-то изменение, а параллельно с тем ветка master меняется другими коммитами, чтобы feature не отставала и не конфликтовала с master):

1.

git checkout feature #выбор ветки feature

2 (в данном случае feature c веткой master):

git merge master #объединить текущую ветку

stash

stash - временное хранилище изменений, используется когда нужно на время убрать все изменения (например, при переключении на новую ветку): Команда

git stash

сохраняет все не закомиченные изменения во временное хранилище и сбрасывает состояние ветки до HEAD.

Стеш(stash) предназначен для того, что бы спрятать не нужные на данный момент изменения, потому он и называется stash, в переводе — прятать, припрятывать.

git stash apply #применить изменения к текущей версии
git stash list  #вывести список изменений
git stash show #вывести последние изменения
git stash drop #удалить последние изменения в списке
git stash pop  # [apply] + [drop]
git stash clear #очистить список изменений
git stash drop# удалит последний git stash
git stash drop stash@{5}#удалит git stash под номером 5

Работа с удаленным репозиторием

Перед использованием удаленного репозитория у вас должен быть локальный проинициализированный репозиторий.

Добавить удаленный сервер:

git remote add origin https://github.com/имя_ник_пользователя/ИмяРепозитория.git

«имя_ник_пользователя» — в данном случае ник пользователя удаленного репозитория. «ИмяРепозитория» — в данном случае это имя вашего уже созданного заранее репозитория на GitHub

Показать какие удаленные серверы есть у репозитория:

git remote -v

Вывод:

origin  [url]https://github.com/имя_пользователя/имя_репозитория.git[/url] (fetch)
origin  [url]https://github.com/имя_пользователя/имя_репозитория.git[/url] (push)
git remote show #показать какие ветки есть в удаленном репозитории

Обычно там одна ветка origin. То есть это не сама ветка, а её сокращенное название ассоциированное с репозиторием. Вы можете добавить, ассоциировать еще одну ветку на удаленном репозитории.

git remote add <сокращенное_имя_удаленного_репозитория>
git@github.com:имя_пользователя/имя_удаленного_репозитория.git

имя_удаленного_реп — имеется в виду короткое имя которое будет ассоциировано с удаленным репозиторием. имя_удаленного_репозитория — имеется в виду имя репозитория на сервере. То есть имя удаленного репозитория.

git remote set-url origin [url]https://имя_пользователя@github.com/имя_пользователя/test.git[/url] #установить новый путь
git pull origin master #забрать все изменения с сервера из ветки origin  в локальную ветку master

Только данная команда забирает одну ветку из удаленного репозитория. Кроме того она сливает(объединяет, merge) все изменения из удаленного репозитория с вашими локальными. Эту команду следует применять, когда вы только начинаете работать с удаленным репозиторием и у вас своих наработок в локальном пока нет.

git remote -v #показать путь к удаленному репозиторию
git clone --recursive https://github.com/имя_пользователя/имя_репозитория.git #рекурсивное получение репозитория
git clone --recursive https://github.com/имя_пользователя/имя_удаленного_репозитория.git Lимя_локальной_папки

Эта команда создает папку с именем имя_локальной_папки. Берет все изменения из репозитория «github.com/имя_пользователя/имя_удаленного_репозитория.git» и сохраняет их в папке «Lимя_локальной_папки». Здесь я написал префикс L перед именем папки, чтобы отличить локальную папку от удаленной на сервере.

gitk #утилита отображения графа изменений

Чтобы некоторые ваши файлы не попадали в репозиторий.

Вы хотите чтобы некоторые файлы не индексировались и не попадали в репозиторий? Вам нужно создать файл с именем .gitignore.

touch .gitignore #создает пустой файл .gitignore

Пустой созданный файл .gitignore вам ничего не дает. Чтобы некоторые файлы не отправлялись на сервер, вам нужно задать в этом файле правила. В него построчно добавляются либо отдельные пути к файлам/папкам, либо паттерны (чтобы исключить файлы определенного названия или расширения). Например, добавив в .gitignore *.md, git не будет регистрировать изменения в файлах с этим расширением (файлы markdown), хотя локально меняться они будут.

Вы можете скачать готовый файл .gitignore с GitHub. Там есть специальный репозиторий, в котором сохраняются шаблоны .gitignore для разных языков и фреймворков.

По умолчанию файл .gitignore не добавляется в репозиторий.

Вы можете создать глобальный файл для пользователя

git config --global core.excludesfile ~/.gitignore_global #создает ссылку на файл .gitignore_global

Теперь вам нужно узнать куда ведет ссылка и создать файл.

git config --get core.excludesfile #показывает где должен находиться файл .gitignore_global

Вам осталось создать этот файл. В терминале.

cd c:\путьдеолжен_находиться_файл .gitignore_global
type nul > .gitignore_global #создать пустой файл .gitignore_global

git push и git pull

Для того чтобы отправить коммиты на удаленный сервер: .. code-block:: bash

git push

Часто может быть, что коммиты будут конфликтовать с другими изменениями, уже имеющимися в данных файлах на удаленно сервере. Тогда git предложит их разрешить. Для этого нужно будет выбрать нужные изменения, затем застейжить файлы, и закоммитить. Если нужно запушить изменения без разрешения конфликта, то есть оставить только текущую версию, можно использовать:

git push -f

Но такое действие часто запрещается в командах, так как можно переписать историю репозитория. Хотя, в любом случае, коммит можно отменить и запушить все правильно заново.

git fetch #забирает изменения с сервера, но только в локальный репозиторий

Команда fetch забирает данные в ваш локальный репозиторий, но не сливает их с какими-либо вашими наработками и не модифицирует то, над чем вы работаете в данный момент.

git pull #берет данные с сервера в локальный репозитория и сливает их с рабочей веткой.

Проще говоря,

git pull

состоит из двух команд:

git fetch && git merge

Опции(ключи) -n, --dry-run #многие команды git имеют данные ключи. Эти опции нужны для того чтобы посмотреть какие изменения сделает команда.

То есть, вы можете увидеть результат выполнения данной команды и затем применить её при уверенности без ключей -n, --dry-run

Выводы

Я рассмотрел базовую архитектуру и работу системы git и работу с удаленными репозиториями. В некоторой степени коснулся вопроса установки и конфигурации git, применения git как локально, так и удаленно, а также некоторые моменты организации рабочего процесса.