Git. Урок 2.
Внутренняя реализация.
Индексация. Коммиты.
Команды: init, config, status, add, commit.

Узнаем, как работает Git, и чем репозиторий отличается от обычной папки. Научимся индексировать изменения и создавать коммиты.

Git. Урок 2. Внутренняя реализация.
Индексация. Коммиты.
Команды: init, config, status, add, commit.

Узнаем, как работает Git, и чем репозиторий отличается от обычной папки. Научимся индексировать изменения и создавать коммиты.
Smartiqa Git cover
  • Урок: 2
  • Команды: init, config, status, add, commit
В КОНЦЕ УРОКА ЕСТЬ ВИДЕО

Оглавление

1
Теоретический блок
1. Основные понятия
2. Настройки пользователя Git
3. Создание репозитория. Команда git init.
4. Состояния файлов в Git репозитории. Команда git status.
5. Внутреннее устройство Git. Объекты.
6. Делаем файлы отслеживаемыми. Команда git add.
7. Делаем первый коммит. Команда git commit.
7.1. Создание коммита. Этап 1. Создание графа.
7.2. Создание коммита. Этап 2. Создание объекта коммита
7.3. Создание коммита. Этап 3. Направить ветку на текущий коммит.
8. Делаем второй коммит

Перейти
2
Практический блок
1. Задание. Файловая структура.
2. Задание. Условие.
3. Задание. Решение: Код + Видео.
4. Домашнее задание


Перейти

ТЕОРЕТИЧЕСКИЙ БЛОК

1

Основные понятия

В прошлом уроке мы бегло разобрали, что такое репозиторий, коммит и ветка. Остановимся сейчас подробнее на этих понятиях. И для начала дадим более-менее формальные определения, а после разберем их подробнее на примерах.

  1. Репозиторий – папка проекта, отслеживаемого Git, содержащая дерево изменений проекта в хронологическом порядке. Все файлы истории хранятся в специальной папке .git/ внутри папки проекта.
  2. Индекс – файл, в котором содержатся изменения, подготовленные для добавления в коммит. Вы можете добавлять и убирать файлы из индекса.
  3. Коммит – фиксация изменений, внесенных в индекс. Другими словами, коммит – это единица изменений в вашем проекте. Коммит хранит измененные файлы, имя автора коммита и время, в которое был сделан коммит. Кроме того, каждый коммит имеет уникальный идентификатор, который позволяет в любое время к нему откатиться.
  4. Указатели HEAD, ORIGHEAD и т. д. – это ссылка на определенный коммит. Ссылка – это некоторая метка, которую использует Git или сам пользователь, чтобы указать на коммит.
  5. Ветка – это последовательность коммитов. Технически же, ветка – это ссылка на последний коммит в этой ветке. Преимущество веток в их независимости. Вы можете вносить изменения в файлы на одной ветке, например, пробовать новую функцию, и они никак не скажутся на файлах в другой ветке. Изначально в репозитории одна ветка, но позже мы рассмотрим, как создавать другие.
  6. Рабочая копия. Директория .git/ с её содержимым относится к Git. Все остальные файлы называются рабочей копией и принадлежат пользователю

В этом уроке мы выучим новые команды Git:

  1. git init – создает новый репозиторий
  2. git status – отображает список измененных, добавленных и удаленных файлов
  3. git add – добавляет указанные файлы в индекс
  4. git commit – фиксирует добавленные в индекс изменения
Важно
Мы стараемся делать наши уроки доступными для широкого круга пользователей, независимо от операционной системы, на которой они работают. Поэтому все примеры в данном и последующих уроках мы будем приводить на Git Bash. Git Bash – это оболочка, основанная на Bash и реализующая все его стандартные функции.

Если вы пользователь Linux, у вас уже есть Bash, и код из наших примеров может быть выполнен напрямую в Терминале.

Если же вы пользуетесь Windows, то могли заметить, что вместе с основными компонентами Git поставил к вам на компьютер Git Bash. Вы можете запустить ее, нажав ПКМ по директории, откуда вы хотите ее запустить, и выбрав из меню Git bash here. Также вы можете вызывать Git напрямую из встроенной консоли cmd, если вам так удобнее.
2

Настройки Git

В зависимости от области действия и места хранения в Git cуществуют 3 типа настроек:

  1. Системные. Представляют собой настройки на уровне всей системы, то есть они распространяются на всех пользователей. Файл с этими настройками хранится по следующему пути: C:\Program Files\Git\etc\gitconfig для Windows и /etc/gitconfig для пользователей Linux/MacOS.
  2. Глобальные. Эти настройки одинаковы для всех репозиториев, созданных под вашим пользователем. Среди них есть, например, имя ветки по умолчанию. Файл с этими параметрами хранятся по следующему адресу: C:/User/<имя пользователя>/.gitconfig в windows, или ~ /.gitconfig в Unix системах.
  3. Локальные. Это настройки на уровне репозитория, они не будут применяться к другим вашим проектам. Эти параметры хранятся в каждом вашем репозитории по адресу: .git/config.
Состояния файлов Git
Области действия настроек Git
Изменить настройки Git можно двумя способами:

  1. Отредактировать файл gitconfig(на уровне системы) или .gitconfig(глобально) или .git/config(на уровне репозитория) напрямую, то есть используя текстовый редактор.
  2. Воспользоваться утилитой git config. Кроме того, с помощью этой утилиты можно посмотреть значение соответствующего параметра.

Команда git config

Формат
git config <ключ> <параметр> <значение>
Ключи
--global
Изменение настроек на уровне пользователя. Без указания данного ключа настройки будут изменены только на уровне текущего репозитория.

--system
Изменение настроек на уровне системы (то есть сразу для всех пользователей).
Параметры, флаги
user.name Имя пользователя
user.email Электронная почта пользователя
Что делает
Устанавливает значение соответствующего параметра в конфигурации Git
Пример
# Задаем значение имени на уровне репозитория
$ git config user.name John
# Просматриваем значение параметра
$ git config user.name
John
# Задаем значение email глобально
$ git config --global user.email daniella@email.com
Давайте попробуем задать имя пользователя глобально. Воспользуемся утилитой git config с параметром --global:
Git Bash

$ git config --global user.name smartiqa
$ git config --global user.email info@smartiqa.com
Приведем файл /.gitconfig после изменения:
Файл /.gitconfig с заданным пользователем

[filter "lfs"]
    clean = git-lfs clean -- %f
    smudge = git-lfs smudge -- %f
    process = git-lfs filter-process
    required = true
[user]
    name = smartiqa
    email = info@smartiqa.com
[init]
    defaultBranch = develop
Как видно поля user.name и user.email действительно стали такими, какими мы их задали.
3

Создание репозитория. Команда git init

Как было сказано выше, репозиторий должен обязательно содержать папку .git/ с историей этого репозитория. Создать эту папку можно двумя способами:

  1. Создать новый репозиторий.
  2. Клонировать к себе на компьютер существующий репозиторий.

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

Команда git init

Формат
git init <параметры>
Что делает
Создает пустой репозиторий в директории, откуда была вызвана
Пример
# Создаем пустой репозиторий в папке проекта
$ cd projects/project_folder/
$ git init
Initialized empty Git repository in projects/project_folder/

Создадим папку test_repository и на ее базе создадим репозиторий Git:
Git Bash

$ mkdir test_repository
$ cd test_repository/
$ git init
Initialized empty Git repository in /Users/smartiqa/test_repository/.git/
Эта команда создала папку .git внутри папки вашего проекта. По ходу работы мы будем разбираться, чем же наполнена эта папка. А пока приведем структуру ее содержимого:
Папка test_repository/.git

test_repository/.git
├── HEAD
├── branches
├── config
├── description
├── hooks
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

4

Состояния файлов в Git репозитории. Команда git status

Репозиторий Git условно можно разделить на три составляющие:
  1. Рабочая директория - Working directory. Это файловая структура, с которой непосредственно работает пользователь в конкретный момент времени. Технически же - это копия определенной версии вашего проекта, которую вы извлекли из базы Git и в которую пытаетесь внести свои изменения.
  2. Индекс или Область подготовленных файлов - Index / Staging area. Это область, где хранятся имена файлов и изменения в них, которые должны войти в следующий коммит. Технически индекс - это просто файл.
  3. Директория Git - Git Directory. Папка, в которой Git хранит все версии вашего проекта и также свои служебные файлы. Данная папка носит название .git и располагается в корневой директории вашего проекта.
Состояния файлов Git
Части проекта Git
Таким образом, получается, что ваши файлы путешествуют между этими тремя областями. Файлы, с которыми вы напрямую работаете - это Working Directory. Что-то изменили в этих файлах - изменилось состояние Working Directory.

Хотите зафиксировать эти изменения - скажите Git, какие именно из всех изменений, вы хотите сохранить. Для этого вы добавляете изменения в файлах во вторую область - Staging (он же Index). Это некое среднее состояние между Working Directory и Git Directory - изменения уже на пути к фиксации, но еще не сохранены в базе Git.

Если вы уверены, что все изменения, которые вы добавили в Index / Staging, необходимо сохранить в базу Git, то вы делаете коммит, и они в сжатом виде помещаются в Git Directory. Теперь все надежно сохранено в папке .git.
Состояния файлов Git
Перемещение изменений между областями Git
А теперь давайте более подробно разберемся с тем, в каких состояниях могут быть файлы с точки зрения Git. Каждый файл может находится только в одном из двух состояний:

1. Отслеживаемый. Об этих файлах Git знает и отслеживает изменения в них. Отслеживаемые файлы в свою очередь могут находится в следующих состояниях:

  1. Неизмененный. То есть с момента последнего коммита в файле не было никаких изменений
  2. Измененный. То есть с последнего коммита в файле были произведены какие-то изменения.
  3. Подготовленный к коммиту. Это значит, что вы внесли изменения в этот файл и затем проиндексировали их, и эти изменения будут добавлены в следующий коммит.

2. Неотслеживаемый. О неотслеживаемых файлах Git не знает, поэтому изменения в них не будут добавлены в коммит. Это любые файлы в вашем рабочем каталоге, которые не входили в последний коммит и не подготовлены к текущему коммиту.

Приведем наглядную визуализацию состояний и переходов между ними.
Состояния файлов Git
Состояния файлов Git
Чтобы посмотреть статус текущих файлов, нам потребуется команда git status.

Команда git status

Что делает
Выводит информацию о статусе файлов, находящихся в репозитории
Пример
# Cмотрим на статус файлов
$ git status

new file: discrete math/hw-3/dm_sem_11_done.pdf
new file: it theory/3/itt_3.pdf
deleted: progrmmings/c/non-evaluated/out.txt

Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .gitignore

Перейдем к примеру. Создадим в нашем тестовом репозитории два файла: alpha.txt и num.txt. Первый будет содержать букву a, а второй – цифру 1:
Папка test_repository

test_repository/
     ├── alpha.txt
     └── num.txt
Посмотрим, что при этом покажет команда git status:
Git Bash

$ git status

 
On branch develop

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        alpha.txt
        num.txt

nothing added to commit but untracked files present (use "git add" to track)

Видно, что на данный момент у нас нет ни одного коммита. Давайте это исправим. Но прежде всего нам нужно разобраться с тем, как Git работает изнутри.
5

Внутреннее устройство Git. Объекты.

Всю информацию Git представляет в виде "объектов". Объект – это файл, содержащий определенную информацию о репозитории и его файлах. Все объекты хранятся в директории .git/objects/. Объекты бывают трех типов:

  1. Blob (англ. binary large object) – большой бинарный объект, другими словами просто бинарный файл. Для каждого файла в репозитории формируется blob-файл, который содержит его имя и сжатое содержимое. Blob-файл формируется, когда мы добавляем файл в индекс.
  2. Tree (англ. tree – дерево). Дерево – это такой тип графа. Оно нужно нам, чтобы показывать связи между файлами в репозитории. Деревья формируются для каждой директории репозитория (в том числе для корневой) во время коммита и показывают, какие файлы (или поддиректории) лежат в данной директории. Таким образом, объект дерева состоит из имен 1) blob-объектов для файлов, которые лежат в данной директории, и 2) других деревьев для всех поддиректорий.
  3. Объект коммита. Этот объект содержит в себе имя автора коммита, время коммита и объект дерева корневой директории проекта.

Кроме этих трех объектов, важным во внутреннем устройстве Git является файл индекса.
Индекс – файл, в котором содержатся изменения, подготовленные для добавления в коммит. Во время добавления файлов командой git add, которую мы рассмотрим ниже, Git:

  1. Сжимает содержимое этого файла и создает blob-объект.
  2. Записывает имя этого объекта в файл индекса.
Структура хранения данных репозитория в Git
Структура хранения данных репозитория в Git
Подытожим
  1. Git – это большая картотека объектов.
  2. Git хранит все файлы и связи между ними, как объекты в директории .git/objects.
  3. Объект – это файл с некоторой информацией о репозитории.
  4. Объекты бывают трех типов: Blob, Tree и Commit.
  5. Blob-объекты хранят информацию о файлах репозитория и их содержимом.
  6. Tree-объекты хранят информацию о расположении этих файлов в репозитории.
  7. Индекс же нужен Git, чтобы понимать, какие из файлов мы добавим в последующий коммит, а какие – нет.
Теперь рассмотрим подробнее, как добавлять файлы в индекс и что при этом происходит.
6

Делаем файлы отслеживаемыми. Команда git add

Чтобы сделать файл отслеживаемым, существует команда git add. Разберемся с ней чуть подробнее.

Команда git add

Формат
git add <имя файла> <флаги>
Параметры, флаги
-A, --all
С этим флагом под действие команды попадут все файлы в репозитории
Что делает
Добавляет файлы в индекс
Пример
# Добавляем файл dijkstra.c в индекс:
$ git add dijkstra.c

# Добавляем все измененные файлы в индекс:
$ git add -A
# или
$ git add --all
Теперь добавим файл alpha.txt в индекс:
Git Bash

$ git add alpha.txt
Посмотрим на статус файлов теперь:
Git Bash

$ git status

 
On branch develop

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   alpha.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        num.txt

Команда git add. Добавление новых файлов в индекс.
Команда git add. Добавление новых файлов в индекс.
Теперь видно, что мы добавили файл alpha.txt в индекс и Git видит его. Но что же все-таки произошло? Работал Git так:


1. Создал новый blob-объект.

Имя этого файла – SHA-1 хэш – 40-символьная контрольная сумма содержимого и заголовка. Вычисляется он так:
  1. К типу файла (т.е. blob) через пробел дописывается длина содержимого и нулевой байт. В нашем случае для файла alpha.txt, содержащего a мы получим: blob 1\0.
  2. Затем к полученной строке прибавляется само содержимое файла. То есть blob 1\0a.
  3. Затем эта строка отдается хэш-функции SHA-1, которая и выдает нам 40-символьный результат.

Первые два символа полученной суммы определяют имя поддиректории в базе объектов, а остальные 38 – имя файла. Например, хэш файла alpha.txt получился такой:
Хэш файла alpha.txt

ca87a297fe24e72165a6c462b2e1df12a01cbc34
Это значит, что Git создаст директорию .git/objects/ca/ и сохранит в нее blob-файл с именем 87a297fe24e72165a6c462b2e1df12a01cbc34.
Команда git add. Этап 1. Формирование Blob-файла.
Команда git add. Этап 1. Формирование Blob-файла.
Хэширование нужно затем, чтобы однозначно определить файл его содержимым (если содержимое файлов различно хотя бы одним символом, то хэши будут сильно отличаться друг от друга). А разбиение по поддиректориям нужно затем, чтобы потом было проще найти нужный нам файл среди множества других.

Заметьте, что простое добавление файла в Git приводит к сохранению его содержимого в директории objects. Оно будет храниться там, даже если мы удалим alpha.txt из рабочей копии.

2. После сохранения blob-файла, гит добавляет его имя в индекс.

Как было сказано выше, индекс – это список файлов, за которыми следит Git. Он хранится в .git/index. Каждая строчка состоит из имени файла и его хэша. Вот таким получился индекс репозитория, рассмотренного выше:
Содержимое файла .git/index

alpha.txt ca87a297fe24e72165a6c462b2e1df12a01cbc34.
Команда git add. Этап 2. Добавление файла в индекс.
Команда git add. Этап 2. Добавление файла в индекс.
Добавим таким же образом, файл num.txt:
Git Bash

$ git add num.txt
И посмотрим статус:
Git Bash

$ git status


On branch develop

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   alpha.txt
        new file:   num.txt
Теперь допустим, что мы передумали и решили изменить содержимое файла num.txt c 1 на 1024. Сделаем это и посмотрим статус снова:
Git Bash

$ git status


On branch develop

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   alpha.txt
        new file:   num.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   num.txt
Видно, что файл num.txt одновременно находится в двух состояниях: подготовленный к коммиту и измененный. Но как такое возможно? Давайте разбираться.

Помните, мы находили хэш-сумму от содержимого файла? Теперь мы это содержимое поменяли и хэш-сумма стала другой. Git это заметил, и предупредил нас. Если мы сейчас сделаем коммит, то в него попадет файл num.txt со значением 1, а не 1024. Чтобы в коммит попали новые изменения, нам нужно заново проиндексировать файл num.txt.
Git Bash

$ git add num.txt
Команда git add. Добавление новых изменений файла в индекс.
Команда git add. Добавление новых изменений файла в индекс.
Можем убедиться, что все хорошо, заново просмотрев статус:
Git Bash

$ git status


On branch develop

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   alpha.txt
        new file:   num.txt
Подведем итог
Мы узнали, что:

  1. Чтобы сделать файл отслеживаемым, существует команда git add.
  2. Когда мы делаем файл отслеживаемым, происходит следующее:
    2.1. Создается blob-объект для этого файла. Имя blob-объекта – 40-символьный хэш содержимого файла, причем первые две буквы хэша отводятся под имя поддиректории в папке .git/objects, а остальные 38 – под имя самого файла. Такое разделение имени ускоряет поиск blob-файла среди других.
    2.2. Имя этого blob-файла записывается в индекс (.git/index). С этого момента GIt считает файл подготовленным к коммиту.
  3. Если мы поменяем содержимое файла, нам нужно снова добавить его в индекс командой git add.
7

Делаем первый коммит. Команда git commit.

Итак, как уже было сказано выше, коммит хранит не только снимок (все индексированные файлы) репозитория, но и имя автора со временем, что бывает полезно. Чтобы сделать коммит в Git, есть команда git commit.

Команда git commit

Формат
git commit <флаги>
Параметры, флаги
-m <описание>
Добавляет к коммиту комментарий. Создать коммит без описания нельзя, но описание можно добавить другими способами (например, из файла). Описывать коммит надо так, чтобы другому человеку было понятно, какие именно изменения вы внесли в данном коммите.

-c <commit>
Берет описание, и информацию об авторе из переданного коммита, когда создает новый. Открывает редактор, давая возможность отредактировать описание коммита.

-С <commit>
То же, что и , но не открывает редактор. Берет описание переданного коммита неизменяемым.
Что делает
Создает новый коммит с файлами из индекса.
Пример
# Создаем первый коммит с комментарием Add math hometask

$ git commit -m "Add math hometask"

[master 0b1f669] Add math hometask
11 files changed, 7 insertions(+), 638 deletions(-)
rewrite .gitignore (99%)
delete mode 100644 discrete math/hw-3/dm3.pdf

В нашем репозитории из прошлого пункта мы уже добавили все файлы в индекс, поэтому нам остается только сделать коммит.
Git Bash

$ git commit -m "Initial commit"
[develop (root-commit) 10962e7] Initial commit
 2 files changed, 2 insertions(+)
 create mode 100644 data/alpha.txt
 create mode 100644 data/num.txt
Теперь давайте разберемся, что только что произошло и что это за буквы с цифрами 10962e7 в первой строчке вывода.
В целом команда git commit делает три шага:

  1. Создает граф (дерево), представляющий содержимое версии проекта, для которой делают коммит.
  2. Создает объект коммита.
  3. Направляет текущую ветку на новый коммит

Рассмотрим эти шаги по отдельности.

Создание коммита. Этап 1. Создание графа.

Git представляет текущее состояние проекта, как древовидный граф, информация для которого читается из индекса. Этот граф записывает расположение и содержание файлов в проекте. Граф - это объект-дерево, о котором упоминалось выше. Каждая строчка любого графа записана по следующему правилу:
Формат записи внутри объекта Tree

<режим доступа> <тип объекта> <хэш объекта> <имя файла, из которого создан объект>.
Разберем все по порядку.

  1. Режим доступа это одно из следующих чисел:
    1. 100644 – обычный файл.
    2. 100755 – исполняемый файл.
    3. 120000 – символическая ссылка (напр. HEAD).
    Режимы доступа в Git сложно назвать гибкими: указанные три режима – единственные доступные для файлов в Git, хотя существуют и другие режимы, используемые для директорий и подмодулей.
  2. Тип объекта – это строка, которая может принимать значение blob, если объект – это blob-файл, либо tree, если объект – это дерево.
  3. Третье значение – хэш объекта. Это тот самый 40-символьный SHA-1 хэш.
  4. Ну и последнее значение – это имя файла, из которого был создан blob-файл и получен хэш.

Как было сказано выше, граф состоит из двух типов объектов:

  1. Blob-файлы
  2. Tree-файлы, они же деревья.

Первые сохраняются во время выполнения команды git add, вторые – во время выполнения git commit. Для примера рассмотрим репозиторий со следующей структурой:
Структура репозитория test_repository

test_repository
└───data
│	└─alpha.txt
│	└─num.txt
└─── outer.txt
То есть у нас есть вложенная папка data с файлами alpha.txt, num.txt и отдельный файл outer.txt. Граф директории data, созданный при коммите, будет выглядеть так:
.git/objects/09/d20c0539...

100644 blob ca87a297fe24e72165a6c462b2e1df12a01cbc34    alpha.txt
100644 blob 81c545efebe5f57d4cab2ba9ec294c4b0cadf672    num.txt
Визуализируя его получим:
Команда git commit. Визуализация дерева директории data после коммита.
Команда git commit. Визуализация дерева директории data после коммита.
А дерево для корневой директории репозитория test_repository будет выглядеть следующим образом:
.git/objects/c0/d2cc3e13...

040000 tree 09d20c0539d97b2a60d06db73135cda7dcac4121    data
100644 blob 5ef136008f1e8f921622f7eed1fe1925331c9665    outer.txt
Первая строчка – это дерево директории data/, рассмотренное выше, а вторая – файл outer.txt.

При этом само это дерево, включающее директорию data/ и файл outer.txt будет иметь хэш c0d2cc3e13d34e7043d2afddb4af8867cc972741 (спойлер: оно пригодится для объекта коммита).

Визуализируя граф корневой директории test_repository получим следующее:
Команда git commit. Визуализация дерева репозитория после коммита.
Команда git commit. Визуализация дерева репозитория после коммита.
Все эти объекты сохраняются в директорию .git/objects/ по тому же принципу, то есть первые два символа хэша – имя поддиректории в папке .git/objects/, а остальные 38 – имя файла в этой поддиректории.
Подведем итог
  1. Первым шагом в создании коммита является создание древовидных графов.
  2. Древовидный граф - это объект типа tree, который хранится в директории .git/objects/.
  3. Деревья нужны, чтобы отразить взаимное расположение файлов и директорий в репозитории.
  4. Дерево создается для каждой директории репозитория, в том числе и для корневой.
  5. Каждая строчка файла дерева устроена по следующей схеме: <режим доступа> <тип объекта> <хэш объекта> <имя файла, из которого создан объект>.

Создание коммита. Этап 2. Создание объекта коммита

После того, как был создан граф репозитория, Git создает объект коммита. Объект коммита – точно такой же текстовый файл в директории .git/objects/, как деревья и blob-файлы. Объект коммита репозитория из предыдущего примера выглядит так:
.git/objects/f9/8b4a7891...

tree c0d2cc3e13d34e7043d2afddb4af8867cc972741
author smartiqa <info@smartiqa.com@email> 1606261256 +0300
committer smartiqa <info@smartiqa.com@email> 1606261256 +0300

Initial commit
Первая строчка записи указывает на дерево корня репозитория, то есть тот самый граф (test_repository) с хэшем c0d2cc3…

Вторая строчка говорит, кто и когда создал коммит, третья – кто и когда записал коммит в репозиторий. Обычно эти две строчки совпадают, но бывают и ситуации, когда один человек записывает в свой репозиторий коммит другого человека, тогда эти строчки будут различаться. Тот, кто запишет коммит к себе будет значится в поле committer, а тот, кто создал этот коммит – в поле author.

Ну и в последней, четвертой, строчке содержится комментарий коммита. В нашем случае – Initial commit.
Команда git commit. Объект коммита.
Команда git commit. Объект коммита.
Теперь мы знаем, что коммит – это объект коммита. Хэш этого объекта и есть хэш коммита – уникальный идентификатор, по которому можно найти любой коммит. Обычно хэш не надо писать полностью, достаточно первых 6-8 цифр. Например:
Git Bash

$ git commit -m “Initial commit”

[develop (root-commit) f98b4a7] initial commit
 3 files changed, 3 insertions(+)
 create mode 100644 data/alpha.txt
 create mode 100644 data/num.txt
 create mode 100644 outer.txt
Здесь f98b4a7 это и есть часть хэша коммита. Git создаст директорию .git/objects/f9 и сохранит в нее объект коммита 8b4a7…, к которому мы потом сможем обращаться.
Подведем итог
  1. После создания объектов деревьев для всех директорий репозитория, создается объект коммита.
  2. Объект коммита состоит из:
    2.1. Объекта дерева корневой директории репозитория
    2.2. Информации о том, кто и когда создал коммит
    2.3. Кто и когда записал коммит в историю репозитория
    2.4. Комментария коммита
  3. Обычно пункты 2.2 и 2.3 совпадают, но не всегда.
  4. Имя объекта коммита – его хэш. По этому хэшу можно найти любой коммит. Не обязательно использовать все 40 символов хэша, обычно 6-8 бывает достаточно.

    Создание коммита. Этап 3. Направить ветку на текущий коммит.

    В заключение нам нужно сообщить указателю-ветке, что у нас есть новый коммит. Делает это Git следующим образом:

    1. Для начала, нужно посмотреть, на какой ветке мы сейчас работаем (их может быть несколько). Git идет в файл .git/HEAD и видит следующее: ref: refs/heads/develop. Такая запись говорит нам, что HEAD указывает на ветку develop, т. е. ветка, в которой мы сейчас работаем – develop (неудивительно, ведь она единственная).
      Напомню, что HEAD и develop – это ссылки, используемые Git или пользователем для указания на определенный коммит.
    2. Затем Git ищет файл .git/refs/heads/develop, но такого файла не существует, поскольку это был первый коммит в нашем репозитории и до сих пор ветке develop было не на что указывать. Поэтому Git создает файл .git/refs/heads/develop и задает его содержимое – хэш объекта-коммита, который мы получили f98b4a7....

    Теперь наш граф репозитория выглядит вот так:
    Команда git commit. Граф репозитория с указателями после первого коммита.
    Команда git commit. Граф репозитория с указателями после первого коммита.
    8

    Делаем второй коммит

    Детально разобравшись с процессами создания коммита, изучим граф после первого и второго коммита.

    Для примера рассмотрим репозиторий со следующей структурой:
    Структура репозитория test_repository
    
    test_repository
    └───data
           └─alpha.txt
           └─num.txt
    
    То есть, как и в предыдущем примере, но без файла outer.txt. Иначе наши графы будут слишком объемными.

    Для начала заметим, что наш индекс снова пуст, поскольку все файлы уже добавлены в коммит, а новых изменений нет:
    Git Bash
    
    git status
    
    On branch develop
    nothing to commit, working tree clean
    
    Давайте рассмотрим, как выглядит рабочая копия и индекс нашего репозитория на графе.
    Граф с рабочей копией и индексом.
    Граф с рабочей копией и индексом.
    Выглядит запутанно, но постарайтесь разобраться. Рабочая копия и индекс указывают на одни и те же файлы из первого коммита. Это значит, что изменений в файлах нет. Теперь давайте поменяем содержимое файла alpha.txt и посмотрим индекс снова:
    Git Bash
    
    $ git status
    
    On branch develop
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
            modified:   data/alpha.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
    Снова посмотрим на граф.
    Граф для измененного файла alpha.txt.
    Граф для измененного файла alpha.txt.
    Как видно, теперь в рабочей копии находится измененный файл alpha.txt, но он еще не проиндексирован. Поэтому индекс указывает на первоначальный файл, а вот в рабочей копии находится уже измененный. Давайте проиндексируем его и посмотрим на статус.
    Git Bash
    
    git add -A
    git status
    
    On branch develop
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            modified:   data/alpha.txt
    
    Вы наверняка догадываетесь, как будет выглядеть граф теперь, но все же приведем схему:
    Граф для измененного и затем проиндексированного файла.
    Граф для измененного и затем проиндексированного файла.
    Теперь рабочая копия указывает на тот же файл, что и индекс, причем этот файл не находится в последнем коммите. Это значит, что у нас есть проиндексированные изменения. Зафиксируем их, то есть сделаем второй коммит.
    Git Bash
    
    $ git commit -m "change alpha.txt"
    
    [develop 4117d58] change alpha.txt
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    Теперь Git создаст новый граф для представления данных из индекса. Старое дерево data/ больше не отражает состояние проиндексированной директории data/ (т.к. файл alpha.txt был изменен), поэтому создается новое. Объект этого дерева(для папки data) выглядит так:
    Дерево папки data (.git/objects/3b/95bcd6d0...)
    
    100644 blob 7e74e68b2a782a3aead46d987a63ca1c91091c13    alpha.txt
    100644 blob 81c545efebe5f57d4cab2ba9ec294c4b0cadf672    num.txt
    
    Поскольку хэш нового дерева data/ отличается от старого, нужно создать новое дерево для всего репозитория. Объект для корневой директории test_repository будет выглядеть так:
    Дерево папки test_repository (.git/objects/22/d4301816...)
    
    040000 tree 3b95bcd6d0a76177985b5d3b2fd046b48e9110ba    data
    
    И уже только на основе этого дерева можно создавать объект второго коммита.
    Объект коммита (.git/objects/41/17d584a7...)
    
    tree 22d4301816880a569417c908ee00b2f6680efb33
    parent 0790c287b727b43156de737f5840cb6584261830
    author smartiqa <info@smartiqa.com> 1606271955 +0300
    committer smartiqa <info@smartiqa.com> 1606271955 +0300
    
    change alpha.txt
    
    Здесь все, как и в объекте первого коммита, за исключением второй строчки – она новая. Теперь у коммита есть родитель (предок, предшествующий коммит). Чтобы найти родителя, Git проходит в файл .git/refs/heads/develop и смотрит на хэш коммита, который там записан.

    После этого Git меняет содержимое .git/refs/heads/develop на хэш текущего коммита. Графически, наш репозиторий теперь выглядит так:
    Граф после второго коммита.
    Граф после второго коммита.
    Заметьте, что мы не меняли num.txt, поэтому Git не стал заново добавлять его в коммит. Вместо этого, в новый коммит вошла ссылка на файл num.txt из прошлого коммита. Такая функция позволяет экономить место и не засорять репозиторий одинаковыми файлами.

    ПРАКТИЧЕСКИЙ БЛОК


    Теперь, когда мы с вами почти полностью овладели функционалом локального Git, предлагаем выполнить следующие упражнения:
    1. Скачать предложенную ниже древовидную структуру из файлов и папок.
    2. На основе этой структуры инициализировать репозиторий Git
    3. Посмотреть, в каком состоянии находятся рабочая директория, индекс и папка .git
    4. Внести изменения в один/несколько файлов, добавить их в индекс и затем закоммитить
    5. Посмотреть, как в ответ на эти действия изменяется состояние рабочей директории / индекса / папки .git

    Для этого мы:
    1. Опишем файловую структуру, на основе которой будем формировать условие задания. При желании вы можете использовать собственную.
    2. Дадим условие задания
    3. [ ВИДЕО ] Выполним задание по списку и дадим соответствующие комментарии
    4. Дадим условие для домашнего задания
    1

    Задание. Файловая структура.

    Дана папка, в которой хранятся файлы свежесозданного блога о диких животных. Файловая структура данной директории:
    Структура папки wild_animals
    
    wild_animals
      ├── index.html
      └── pictures
        ├── elephant.jpg
        ├── giraffe.jpg
        └── paw_print.jpg
    
    Как видно из структуры, в корневой директории лежат:
    1. Основная страница блога index.html
    2. Папка pictures, в которой хранятся изображения ( файлы *.jpg) сайта

    Рассмотрим эти две составляющие подробнее:

    1. Основная страница блога index.html
    Представляет собой файл, который содержит HTML код главной страницы сайта. Ниже приведено его содержимое:
    Файл index.html
    
    <!DOCTYPE html>
    <html>
    
      <head>
        <title>Wild Animals Blog</title>
        <meta charset="utf-8">
      </head>
    
      <body align=center>
    
        <img width=100px src="pictures/paw_print.jpg">
        <h1>Wild Animals</h1>
        <small>- Blog about nature -</small>
    
        <br>
        <br>
        <br>
        <br>
    
        <p>Let's talk about wild animals around the world:</p>
        <h2>Giraffe</h2>
        <img src="pictures/giraffe.jpg">
        <p><b>Area:</b> Africa</p>
        <p><b>Weight:</b> 900-1200kg</p>
        <p><b>Height:</b> 6m</p>
        <br>
    
        <h2>Elehant</h2>
        <img width=350px src="pictures/elephant.jpg">
        <p><b>Area:</b> Africa, Asia</p>
        <p><b>Weight:</b> 4000-7000kg</p>
        <p><b>Height:</b> 3m</p>
        <br>
    
      </body>
    </html>
    
    Если отрыть данный файл в браузере, то выглядит основная (и пока единственная) страница блога таким образом:
    Установка модуля внутри терминала IDE PyCharm
    Главная страница сайта о диких животных
    2. Папка с картинками pictures
    Картинки для главной страницы сайта хранятся в папке pictures. Ниже представлена ее структура:
    Структура папки pictures
    
    pictures
        ├── elephant.jpg
        ├── giraffe.jpg
        └── paw_print.jpg
    
    И сами изображения:
    2

    Задание. Условие.

    Теперь когда мы познакомились со структурой директории wild_animals, можно переходить непосредственно к условию задачи:
    Условие
    1) Создайте репозиторий внутри папки wild_animals. Убедитесь, что внутри папки wild_animals появилась папка .git.

    2) Настройте пользователя Git на уровне репозитория wild_animals:
    1. Изучите содержимое файла конфигурации Git для текущего репозитория .git/config
    2. Настройте имя и email пользователя для текущего репозитория
    3. Убедитесь, что файл .git/config изменился соответствующим образом

    3) Изучите содержимое папки .git/objects
    1. Убедитесь, что отсутствует файл индекса .git/index
    2. Убедитесь, что папка с объектами Git пустая .git/objects
    3. Убедитесь, что указатель HEAD указывает на ветку main

    4) Сделайте 1й коммит:
    1. Сделайте файлы папки wild_animals отслеживаемыми
    2. Обратите внимание на файл индекса .git/index и папку с объектами .git/objects
    3. Сделайте коммит
    4. Найдите хэш коммита

    5) Сделайте 2й коммит
    1. Исправьте опечатку в файле index.html (опечатка в слове Elephant)
    2. Добавьте изменения в индекс
    3. Сделайте коммит

    6) Сделайте 3й коммит
    1. Добавьте в файл index.html секцию для еще одного животного (например, для кенгуру)
    2. Добавьте изменения в индекс
    3. Сделайте коммит
    3

    Задание. Решение.

    Наш вариант выполнения предложенного задания смотрите в видео-инструкции:
    Хронометраж

    ТЕОРИЯ
    00:20 План видео
    01:00 Понятия: репозиторий, Индекс, Коммит, Ветка, Указатель
    03:40 Области проекта Git: Рабочая директория(Working Directory), Индекс (Index) и Каталог Git (Git Directory)
    06:00 Перемещение файлов между областями проекта
    08:20 Состояния файлов Git: Untracked, Unmodified, Modified, Staged
    11:15 Команда git status
    11:50 Команда git status и состояния файлов
    13:50 Объекты Git: Blob- и Tree-объекты, объект коммита
    18:40 Добавление файла в индекс
    20:00 Добавление файла в индекс. Команда git add.
    20:50 Добавление файла в индекс. Этап 1. Формирование Blob-файла
    20:55 Добавление файла в индекс. Этап 2. Добавление файла в Индекс.
    24:30 Создание коммита
    24:50 Создание коммита. Команда git commit.
    26:15 Создание коммита. Этап 1. Создание графа.
    26:55 Создание коммита. Этап 2. Формирование объекта коммита
    29:45 Создание коммита. Этап 3. Смещение указателя HEAD.

    ПРАКТИКА
    31:45 Чем будем заниматься
    32:05 Файловая структура для репозитория
    33:20 Задание 1. Создание репозитория
    36:05 Задание 2. Настройка пользователя Git
    38:40 Задание 3. Работа с директорией .git
    45:15 Задание 4. Создание 1-го коммита
    51:10 Задание 5. Создание 2-го коммита
    55:00 Задание 6. Создание 3-го коммита
    59:55 Контакты
    4

    Домашнее задание

    Дана маленькая библиотека, вычисляющая некоторые тригонометрические функции. Она имеет следующую структуру:
    Структура директории mat_lib
    
    mat_lib
        ├─docs
        │   └─math_lib_docs.txt
        │
        └─pyfiles
                └─factorial.py
                └─test.py
                └─trigonometry.py
    
    Назначение каждого из файлов:
    1. [ math_lib_docs.txt ] Короткая документация по функционалу библиотеки
    2. [ factorial.py ] Модуль с функцией factorial(), которая вычисляет факториал переданного числа
    3. [ trigonometry.py ] Модуль с тригонометрическими функциями, например, sin()
    4. [ test.py ] Модуль для проверки функционала тригонометрических функций для разных значений углов
    Далее приводим содержимое перечисленных модулей:
    Содержимое файла math_lib_docs.txt
    
    This is the library which designation is to implement some math functions
    
    Содержимое файла factorial.py
    
    def factorial(x):
        ans = 1
    
        if x < 0:
            raise ValueError('x must be greater than 0')
    
        for i in range(1, x+1):
            ans *= i
        
        return ans
    
    Содержимое файла trigonometry.py
    
    from factorial import factorial as fct
    
    def sin(x):
        sin = 1 - (x**2/fct(2)) + (x**4/fct(4)) - (x**6/fct(6)) + (x**8/fct(8)) - (x**10/fct(10))
        return round(sin, 5)
    
    Содержимое файла test.py
    
    from trigonometry import sin
    import math
    
    pi = math.pi
    print('pi:', pi)
    for alpha in [0, pi, pi/2, pi/3, pi/4, pi/6]:
        print(f'For angle: {0 if alpha == 0 else "pi/"+str(int(pi/alpha))}, Sine  is ~ {sin(alpha)}')
    
    Задание
    1) Инициализируйте репозиторий в папке mat_lib

    2) Задайте имя пользователя и email глобально
    1. Проверьте, что изменения внесены в файл глобальных настроек .gitconfig
    2. Проверьте, что файл локальных настроек .git/config не был изменен

    3) Изучите содержимое папки .git/
    1. Узнайте, на что сейчас указывает HEAD
    2. Просмотрите файл, на который указывает HEAD (в этом пункте есть подвох)

    4) Добавьте все файлы в индекс

    5) Сделайте первый коммит
    1. Просмотрите объект коммита, найдите хэш объекта-дерева корня репозитория
    2. Просмотрите объект дерева корня репозитория
    3. Проверьте, на что указывает HEAD сейчас
    4. Просмотрите файл, на который указывает HEAD
    5. Ответьте на вопрос: на что указывает текущая ветка? Для этого просмотрите на объект, на который указывает ветка.

    6) Выполните файл test.py
    1. Просмотрите статус файлов, чтобы обнаружить, что появились файлы кэша

    7) Сделайте второй коммит
    1. Просмотрите объект коммита, найдите хэш родительского коммита
    2. Посмотрите, на что сейчас указывает HEAD
    3. Проверьте файл, на который указывает HEAD
    4. Узнайте, на что указывает текущая ветка. Для этого просмотрите на объект, на который указывает ветка.
    Задавайте вопросы и делитесь возможными решениями задач в комментариях. Также предлагаем вам к рассмотрению и изучению наши варианты решений и соответствующие комментарии к каждому из заданий.
    Как вам материал?

    Читайте также