Git. Урок 3.
Ветвление. Создание, переключение и удаление веток. Команды: branch, checkout, status, log, diff.

В этом уроке мы узнаем, что такое ветки и зачем они нужны. Научимся их создавать и переключаться между ними. Посмотрим историю коммитов и научимся удалять ветки после работы.

Git. Урок 3.
Ветвление. Создание, переключение и удаление веток. Команды: branch, checkout, status, log, diff.

В этом уроке мы узнаем, что такое ветки и зачем они нужны. Научимся их создавать и переключаться между ними. Посмотрим историю коммитов и научимся удалять ветки после работы.
Smartiqa Git cover
Урок: 3
Команды: branch, checkout, status, log, diff.

Оглавление

1
Теоретический блок
1. Что такое ветка.
2. Зачем нужны ветки.
3. Создание новых веток.
4. Просмотр списка веток.
5. Переключение между ветками.
6. Просмотр состояния ветки. Команды: git status, git log, git diff.
6.1. Просмотр состояния файлов ветки. Команда git status.
6.2. Просмотр истории коммитов ветки. Команда git log.
6.3. Просмотр различий между коммитами. Команда git diff.
7. Удаление веток.

Перейти
2
Практический блок
1. Задание
2. Решение
Перейти

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

В этом уроке мы займемся изучением веток и операций с ними. Вы можете помнить, мы уже немного затрагивали тему ветвления в предыдущем уроке, но не останавливались на ней подробно. Самое время узнать о ветках больше!
1

Что такое ветка

Дадим два определения ветки: на логическом и физическом уровнях.

1. Логический уровень.
С точки зрения логики, ветка – это последовательность коммитов. Чтобы проще было понять, что такое ветка, рассматривайте ее как некоторую временную шкалу. Коммиты в ней – снимки интересных моментов, идущие друг за другом в хронологической последовательности. Рисунок ниже поможет вам в интуитивном представлении.
Логическое представление веток. Ветки: main, develop, feature
Логическое представление веток. Ветки: main, develop, feature
На рисунке выше изображены три ветки: main, develop и feature. Каждая представляет из себя "поток" коммитов в хронологической последовательности. Важно заметить, что эти потоки не пересекаются, то есть работа в ветках идет параллельно.

2. Физический уровень
На физическом уровне, то есть с точки зрения внутренней реализации Git, ветка – это ссылка на последний коммит в этой ветке. Картинка ниже поможет вам понять, что к чему.
Внутреннее представление веток Git. Ветки: main, develop, feature
Внутреннее представление веток Git. Ветки: main, develop, feature
Сравнивая рисунки, можно заметить, что ветка-ссылка указывает на коммит, который является последним в "потоке" коммитов в данной ветке. Таким образом, коммит С10 - последний сделанный коммит в ветке main, поэтому указатель main стоит над С10. То же самое можно сказать про develop – для этой ветки последним коммитом является С11, – и про feature, ее последним коммитом будет С7.
Немного про указатели
Указатель – это ссылка на определенный коммит или ветку. А ссылка – это некоторая метка, которую использует Git или сам пользователь, чтобы указать на коммит или ветку.
И как мы уже сказали, физически ветки – самые настоящие указатели. Давайте приведем примеры еще некоторых часто используемых указателей:
  1. HEAD – так называемый курсор Git. Главное назначение HEAD - определять, в каком состоянии находится рабочая копия (напомним, что рабочая копия – это все файлы репозитория, за исключением директории .git/). На какой коммит указывает HEAD – из того коммита и загружаются файлы в рабочую директорию.
  2. ORIG_HEAD – указатель, который появляется, когда мы вручную передвигаем HEAD на какой-нибудь НЕ последний коммит. ORIG_HEAD указывает на тот же коммит, на который указывал HEAD до передвижения назад. Нужен он, чтобы мы имели возможность вернуться на хронологически последний коммит без существенных затрат (в истории мы не будем видеть все коммиты старше нашего, а поэтому не сможем узнать хэш последнего).
  3. Пользовательские указатели. Пользователи сами могут создавать указатели. Например, вы можете создать указатель version-1.2.1, который будет указывать на коммит, в котором хранится версия 1.2.1 вашего проекта. Это довольно удобно, поскольку вы можете переключаться на коммит с той или иной версией, не запоминая его хэш.
Преимущество веток в их независимости. Вы можете вносить изменения в файлы в одной ветке, например, пробовать новую функцию, и они никак не скажутся на файлах в другой ветке. Изначально в репозитории одна ветка, но позже мы рассмотрим, как создавать другие.

На самом деле, вначале, когда мы делаем свой первый коммит, Git автоматически создает основную ветку. Вы можете помнить, что ее имя по умолчанию "main" мы задавали в настройках Git в предыдущем уроке. Каждый раз, когда мы создаем новый коммит, Git автоматически перемещает указатель main на последний коммит. Тем не менее, в следующем уроке мы узнаем, как перемещать указатель ветки между коммитами самостоятельно.
Представление веток внутри Git
Представление веток внутри Git
На рисунке овалы с хэшами – наши коммиты, main – ветка по умолчанию и в то же время указатель на коммит 62aa, а HEAD – указатель на ветку, с которой мы сейчас работаем, то есть на ветку main.
2

Зачем нужны ветки

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

Дело в том, что Git – универсальная система контроля версий: она подходит и большим командам крупных корпораций, и индивидуальным разработчикам для их личных проектов.

Если вы работаете один – скорее всего вы будете редко использовать ветки, но если вы работаете в большой компании, без веток вам не обойтись.

Итак, чаще всего ветки используются в следующих случаях.

  1. Ветки нужны, чтобы несколько программистов могли вести работу над одним и тем же проектом или даже файлом одновременно, при этом не мешая друг другу.
  2. Кроме того, ветки используются для тестирования экспериментальных функций: чтобы не повредить основному проекту, создается новая ветка специально для экспериментов. Если эксперимент удался, изменения с экспериментальной ветки переносятся на основную, если нет – новая ветка попросту удаляется, а проект остается нетронутым.
  3. Помимо прочего, ветки можно использовать для разных выходящих параллельно релизов одного проекта. Например, в репозитории Python может быть две ветки: python-2 и python-3. До закрытия python-2 релизы этих версий языка выходили независимо друг от друга, поэтому могло иметь место такое разделение на ветки.

На самом деле, у веток есть еще множество применений – все зависит от конкретного проекта, – мы же перечислили только самые распространенные практики их использования. Ветвление в Git – это действительно мощный инструмент, поэтому важно уметь им пользоваться.
3

Создание новых веток

Выше мы уже упоминали, что как только вы создаете свой первый коммит, Git создаёт основную ветку, в нашем случае ее имя будет main. Тем не менее, мы можем создавать свои ветки и переключаться между ними. Создать свою ветку можно двумя способами.

Способ 1. Команды git branch + git checkout

Этот способ является классическим. В нем для начала работы с новой веткой нужно выполнить два действия:
Создать ветку с помощью команды git branch
Переключиться на свежесозданную ветку с помощью команды git checkout <имя ветки>

Команда git branch

Формат
git branch <имя ветки>
Что делает
Создает новую ветку.
Пример
# Создадим новую ветку с именем feature
$ git branch feature

На самом деле, git branch – очень мощная команда, которая умеет много всего. Сейчас мы рассматриваем ее как инструмент для создания веток. Ниже мы рассмотрим еще некоторые способы ее применения.

И как мы уже сказали, в качестве второго этапа после создания новой ветки, вам нужно переключиться на нее командой git checkout.

Способ 2. Команда git checkout -b

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

Команда git checkout

Что делает
Создает новую ветку и сразу переключается на нее.
Пример
# Создадим ветку 'feature' и сразу же на нее переключимся
$ git checkout -b feature
Switched to a new branch 'feature'

Как и git branch, git checkout – очень многофункциональная команда. Главное ее назначение – перемещать указатель HEAD. О том, как она работает и какие еще имеет применения, мы поговорим ниже.

Внутри Git создание ветки работает довольно просто. Выполняются следующие шаги:
  1. В директории .git\refs\heads создается новый файл, имя которого – имя ветки, которую вы хотите создать (при условии, что такого файла не существует). Например, если мы выполним команду git branch feature, то создастся файл .git\refs\heads\feature.
  2. В созданный файл записывается хэш текущего коммита. С него ветка и начнется. После этого файл .git\refs\heads\feature будет выглядеть так:
    Содержимое файла .git\refs\heads\feature
    971ef6a7e178324b732e2ed6cbc3b66c1c989e15
    40 шестнадцатеричных цифр в файле выше и есть хэш последнего коммита, на который указывает новосозданная ветка.

    Важно понимать, что как только вы создали новую ветку, она указывает на тот же коммит, что и основная ветка, и HEAD. Графически ситуация выглядит так:
      Граф Git сразу после создания новой ветки. Ветки: main, feature
      Граф Git сразу после создания новой ветки. Ветки: main, feature
      Как видно из картинки, сейчас HEAD указывает на основную ветку (коммит 62aa). Если же вы переключитесь на ветку feature и сделаете два новых коммита (4b00 и 6670), ситуация будет выглядеть так:
        Граф Git после переключения на новую ветку и создания нескольких коммитов. Ветки: main, feature
        Граф Git после переключения на новую ветку и создания нескольких коммитов. Ветки: main, feature
        Как мы видим, указатели feature и HEAD передвинулись на последний коммит 6670.

        С помощью команд git branch и git checkout вы можете создать неограниченное количество веток и переключаться между ними по мере необходимости. Обычно если ветка вам больше не нужна, ее сливают с основной и удаляют. Тема слияния веток заслуживает отдельного урока, поэтому про это мы поговорим в следующий раз, а удаление веток рассмотрим чуть ниже.
          4

          Просмотр списка веток

          Когда вы работаете с большим количеством веток, можно легко забыть имя нужной, а без имени ветки переключится на нее не получится. Для таких ситуаций существует команда просмотра списка веток. На самом деле, это уже знакомая нам git branch, но с другими ключами.

            Команда git branch

            Что делает
            По умолчанию выводит список локальных веток. С ключами -r, -a можно вывести, соответственно, либо только удаленные ветки, либо все ветки.
            Пример
            # Выведем локальные и удаленные ветки
            $ git branch -a
            * develop
            feature
            main
            remotes/origin/HEAD -> origin/main
            remotes/origin/develop
            remotes/origin/feature
            remotes/origin/main

            # Теперь выведем только локальные ветки
            $ git branch
            * develop
            feature
            main

            # А теперь только удаленные ветки
            $ git branch -r
            origin/HEAD -> origin/main
            origin/develop
            origin/feature
            origin/main

            В примере выше можно увидеть, что перед веткой develop стоит звездочка. Такая запись означает, что сейчас указатель HEAD находится на ветке develop. Аналогично строка origin/HEAD -> origin/main означает, что указатель HEAD удаленного репозитория находится на удаленной ветке main.

            Теперь, когда мы поняли, как можно узнать имена веток в репозитории, давайте научимся переключаться между ними.
            5

            Переключение между ветками

            Итак, настало время подробнее разобраться с тем, как переключаться между ветками. Как уже было сказано выше, чтобы переключиться на ветку, вам нужно знать ее имя. Напомним, что просмотреть список всех веток можно с помощью команды git branch. Просмотрим список веток в некотором репозитории:
            Git Bash
            $ git branch
              develop
              main
            * feature
            Как мы уже знаем, звездочка перед веткой означает, что сейчас мы находимся на данной ветке (в нашем случае это ветка feature). Если мы хотим переключиться на другую, нам нужна уже знакомая команда git checkout.

            Команда git checkout

            Формат
            git checkout <ключи> <имя ветки>
            Что делает
            Переключает пользователя на другую ветку
            Пример
            # Переключимся на ветку develop
            $ git checkout develop
            Switched to branch 'develop'

            Кстати, вместо имени ветки, можно писать -, чтобы переключиться на предыдущую ветку, например:
            Git Bash
            # Выведем список локальных веток
            $ git branch
              develop
              feature
            * main
            
            # Переключимся на ветку develop
            $ git checkout develop
            Switched to branch 'develop’
            
            # Переключимся на ветку feature
            $ git checkout feature
            Switched to branch ‘feature’
            
            # Вернемся на ветку develop
            $ git checkout -
            Switched to branch 'develop'
            Этот маленький фокус позволит вам сэкономить время, особенно, если вы часто переключаетесь между ветками.

            Теперь приведем список действий, которые производит git checkout, чтобы переключить нас на новую ветку:
            1. Программа проверяет, существует ли указанная ветка.
            2. Затем программа переключает указатель HEAD на новую ветку.
            3. Последним шагом программа меняет рабочую копию так, чтобы она соответствовала новой ветке.

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

            Шаг 1. Проверка существования ветки

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

            Итак, мы помним, что ветка – это тоже указатель, как и HEAD. Она указывает на последний коммит в "потоке" коммитов ветви графа Git. Графически это выглядит так:
            Граф Git с двумя ветвями. Ветки: main, feature
            Граф Git с двумя ветвями. Ветки: main, feature
            Как видно из рисунка, у нас есть две ветки: main и feature. HEAD сейчас указывает на main, поэтому сейчас рабочая копия репозитория находится в состоянии коммита 5с5с.

            Все, что нужно, чтобы задать ветку – просто записать, на какой коммит она указывает. Этого достаточно, поскольку по предкам этого коммита можно восстановить весь остальной граф. Поэтому внутри Git ветка – это просто файл в директории .git\refs\heads, названный так же, как и сама ветка. Например, в случае репозитория, граф которого изображен на картинке выше, мы получим:
            Директория .git\refs\heads
            main
            feature
            Содержимое файла main
            5с5с…
            
            Содержимое файла feature
            6670…
            
            Таким образом, если файл с именем ветки присутствует в директории .git\refs\heads, можно однозначно сказать, что такая ветка существует.

            Шаг 2. Переключение HEAD на нужную ветку

            Теперь, когда Git убедился, что ветка с нужным именем существует, необходимо как-то переключить указатель HEAD на эту самую ветку. То есть сделать так, чтобы HEAD указывал на файл ветки. Делается это очень просто: нужно в файл .git\HEAD записать путь до нужной нам ветки: refs\heads\<имя ветки>. Тогда HEAD будет указывать на новую ветку.

            В нашем случае, при переключении на ветку feature, файл .git/HEAD будет выглядеть так:
            Содержимое файла .git\HEAD
            refs\heads\feature
            
            После этого шага граф Git будет выглядеть следующим образом:
            Граф Git с двумя ветвями на втором шаге переключения. Ветки: main, feature
            Граф Git с двумя ветвями на втором шаге переключения. Ветки: main, feature
            Как видно, HEAD теперь указывает на ветку-указатель feature. Картинка поменялась, поскольку мы записали в файл .git\HEAD новый адрес. В данном случае, это refs\heads\feature. Таким образом, до полного переключения на другую ветку остался всего один шаг.

            Шаг 3. Изменение рабочей копии

            Рабочие копии на разных ветках могут отличаться, поскольку ветки работают независимо друг от друга. Поэтому последним шагом при переключении на новую ветку станет изменение рабочей копии так, чтобы она соответствовала нашей ветке. Git просто смотрит на последний сделанный в данной ветке коммит, после чего восстанавливает структуру и файлы в рабочей копии, подгружая данные из blob- и tree- объектов, о которых мы говорили в прошлом уроке. То есть Git будет действовать так:
            1. Любой файл, который есть в новом коммите (6670) на рисунке выше и которого нет в коммите, с которого мы переключились (5с5с), будет добавлен в рабочую копию.
            2. Любой файл, который был в коммите, с которого мы переключились (5с5с), и которого нет в новом коммите (6670), будет удален.
            3. Любой файл, которого нет ни в одном из этих двух коммитов, будет просто проигнорирован. То есть удален он НЕ будет.

            Как только все три эти условия выполняются для всех файлов в рабочей копии, можно говорить о том, что Git полностью переключил нас на нужную ветку.

            У внимательного читателя мог возникнуть вопрос: почему файлы в рабочей копии подгружаются именно из последнего коммита ветки? Что если до переключения с данной ветки, в ней были какие-то изменения, еще не добавленные в коммит? Что с ними будет? Ответ довольно прост. Согласно трем шагам, указанным выше, Git просто проигнорирует все такие файлы. То есть они останутся в вашей рабочей копии в том же состоянии, в котором и были до этого. Как правило, такое поведение очень неудобно, поскольку позволяет легко запутаться в структуре собственного проекта. Представьте: у вас два параллельных релиза, а вы, переключаясь между ветками, случайно занесли файлы одного релиза в ветку другого.

            Таким образом, у вас есть два варианта выхода из ситуации:
            1. Либо добавить все изменения в коммит и благополучно сменить ветку
            2. Либо "отложить" изменения, не добавляя их в коммит, воспользовавшись командой git stash.
            В двух словах о git stash
            Говоря кратко, git stash прячет ваши изменения в некоторый внутренний стек Git. Чтобы "отложить" изменения, достаточно ввести команду git stash. На самом деле, каждое применение git stash создает отдельный коммит, хэш которого записывается в стек. Поэтому с помощью этой команды можно неограниченное количество раз откладывать изменения. Просмотреть список всех отложенных изменений можно командой git stash list. Чтобы достать изменения из стека и вернуть их в рабочую копию, нужно использовать команду git stash pop. Таким образом, вы можете откладывать ваши изменения перед переходом на новую ветку, а затем доставать их обратно.
            Забегая вперед, скажем, что git checkout используется не только для перемещения указателя HEAD между ветками. Вы точно также можете перемещать этот указатель по коммитам одной и той же ветки. Это может быть нужно, если вы, например, заметили, что допустили ошибку в пояснении к старому коммиту. Вы можете просто переместить указатель HEAD на нужный коммит, и исправить сообщение. Эту технологию мы разберем в следующем уроке, а сейчас вернемся к более практичным вещам. Давайте поговорим про состояния файлов ветки и историю коммитов.
            6

            Просмотр состояния ветки.
            Команды: git status, git log, git diff.

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

            6.1. Просмотр состояния файлов ветки. Команда git status.

            Чтобы просмотреть состояния файлов конкретной ветки нужно выполнить последовательность из двух команд:
            1. git checkout <имя ветки> # переключаемся на нужную ветку
            2. git status # просматриваем статус файлов ветки
            Выше мы говорили, что файлы, не добавленные в коммит одной ветки, будут перенесены на другую при переключении на нее. Пример такой ситуации можно увидеть ниже.
            Git Bash
            # Просмотрим список доступных веток. Сейчас мы внутри ветки feature.
            $ git branch
              develop
            * feature
              main
            
            # Просмотрим cостояние файлов в этой ветке
            $ git status
            On branch feature
            Untracked files:
              (use "git add <file>..." to include in what will be committed)
                    new_file.txt
            
            # Как видно, у нас есть один неотслеживаемый файл “new_file.txt”. Попробуем переключиться на другую ветку, например, develop.
            $ git checkout develop
            Switched to branch develop
            
            # Теперь просмотрим статус файлов этой ветки.
            $ git status
            On branch develop
            Untracked files:
              (use "git add <file>..." to include in what will be committed)
                    new_file.txt
            
            # Видим, что наш файл  “new_file.txt” последовал за нами в новую ветку. Давайте сделаем коммит и проверим, остался ли этот файл в ветке feature.
            $ git add -A
            $ git commit -m "agging new_file.txt"
            [develop 7b8f2ff] agging new_file.txt
             1 file changed, 0 insertions(+), 0 deletions(-)
             create mode 100644 new_file.txt
            
            # Проверим, что измененных файлов не осталось
            $ git status
            On branch develop
            nothing to commit, working tree clean
            
            # Переключимся обратно на ветку feature и проверим, остался ли там этот файл.
            $ git checkout -
            Switched to branch 'feature'
            
            $ git status
            On branch feature
            nothing to commit, working tree clean
            
            # Как видно – нет.
            Пример выше показывает, что нужно быть очень осторожным, переключая ветки. В идеальном случае до и сразу после переключения ветки вывод git status должен быть "пустым", то есть говорить о том, что измененных файлов нет. Если вы не уверены, что закоммитили все изменения, лучше лишний раз убедитесь в этом, прописав git status. Это спасет вас от многих проблем, вызванных "перетаскиванием" файла не на ту ветку.

            6.2. Просмотр истории коммитов ветки. Команда git log.

            В работе программиста часто бывает нужно просмотреть историю коммитов:
            1. либо чтобы узнать, кто внес те или иные изменения,
            2. либо чтобы вспомнить хэш коммита, к которому хочется откатиться,
            3. либо просто проследить за историей развития проекта.
            Просмотреть историю коммитов нам поможет команда git log:

            Команда git log

            Что делает
            В различных форматах выводит историю коммитов. В различных ситуациях это может быть либо полная история всех коммитов, либо история нескольких из них.
            Пример
            # Выведем последние 7 коммитов, воспользовавшись "красивым" выводом в одну строку.
            $ git log -7 --pretty=oneline
            8bb113c62cffdd3cada27b4179410f110f6f1321 (HEAD -> develop, origin/develop) adding complex algebra project
            603def20e5da2d512da2852011eb5be3fa156940 (origin/main, origin/HEAD) eng. hometask done
            86f24ac203c9a1a609e4f303e5c896746b13a054 minor changes and files movements
            0b1f66921a08d42122e0f69babd912a2cc01ec82 minor changes
            8f0479b6676b7e38eb0dd409690845bd9a64f19d new hometasks were done
            cb3395bb21a353ea4ee885c4d493690e3c6294cb adding code docs
            0117d8a38ed557c95a7f2959c89c1f3107a1d614 discrete math hometask done

            # Выведем последние 2 коммита с обычным оформлением.
            $ git log -2
            commit 8bb113c62cffdd3cada27b4179410f110f6f1321 (HEAD -> develop, origin/develop)
            Author: smartiqa <info@smartiqa.ru>
            Date: Thu Feb 11 02:04:24 2021 +0300

            adding complex algebra project

            commit 603def20e5da2d512da2852011eb5be3fa156940 (origin/main, origin/HEAD)
            Author: smartiqa <info@smartiqa.ru>
            Date: Fri Dec 4 02:00:41 2020 +0300

            eng. hometask done

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

            Общее правило, которым руководствуется Git при выводе истории, такое: иди по предкам коммитов, пока они не закончатся. Иногда это бывает удобно, иногда – нет.

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

            1. Переключиться на ветку, историю коммитов которой мы хотим просмотреть.
            2. Выполнить команду git log <ключи> <имя родителя текущей ветки>..<имя текущей ветки>.
            Например:
            Git Bash
            # Выведем список локальных веток
            $ git branch
              develop
              feature
            * main
            
            # Переключимся на ветку feature
            $ git checkout feature
            Switched to branch ‘feature’
            
            # Просмотрим историю только для ветки feature
            $ git log --pretty=oneline develop..feature
            b5d154854d794f90eca6426c1a0ae1f8d9696251 (HEAD -> feature) adding new feature
            Давайте разбираться, откуда взялся такой сложный вызов. Дело в том, что в качестве пути к коммитам, историю которых мы хотим просмотреть, мы можем передать два указателя через две точки: <указатель-1>..<указатель-2>. Тогда git log выведет все коммиты после указателя-1 и до указателя-2. Это, кажется, нетрудно. Но почему в качестве указателей мы передаем родителя текущей ветки и ее саму? Чтобы понять, давайте взглянем на граф Git.
            Граф Git произвольного репозитория. Ветки: main, feature
            Граф Git произвольного репозитория. Ветки: main, feature
            Если мы попытаемся просмотреть лог ветки main, то все будет хорошо: Git просто пойдет по предкам коммитов в обратном порядке и выведет: C5, C3, C2, C1. Но если мы попробуем просмотреть лог ветки feature, то получим: C6, C4, C3, C2, C1, что может нас запутать: после вывода всех коммитов ветки feature, Git начал выводить коммиты ветки main до ответвления feature.

            Получается, что если мы хотим получить коммиты исключительно ветки feature, то нам нужно указать, что Git должен остановиться, как только дойдет до коммита, младше указателя ветки main. То есть младше указателя ветки-родителя. Таким образом, мы передаем Git инструкцию: выведи все коммиты старше main и младше feature. Давайте пошагово разберем, как работает Git:
            1. Идем в обратном порядке. С6 младше feature? Да. C6 Старше main? Да, поскольку это другая ветка. Выводим C6.
            2. C4 младше feature? Да. C4 Старше main? Да, выводим C4.
            3. C3 младше feature? Да. C3 Старше main? Нет, поскольку, main указывает на C5, который является наследником С3. Таким образом С3 младше main. Останавливаемся.

            Такой алгоритм работает всегда: как только ветка, историю с которой мы выводим, присоединяется к своему родителю, срабатывает наше условие и вывод останавливается. Очевидно, что для самой основной ветки таких условий не требуется, ведь она первоначальная.

            В любом случае, если вам сложно визуально представить свой репозиторий, просто воспользуйтесь командой git log --all --graph. С ней вы увидите, как выглядит граф вашего репозитория и поймете, что нужно делать, чтобы получить историю той или иной ветки.

            6.3. Просмотр различий между коммитами. Команда git diff.

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

            Команда git diff

            Формат
            git diff <ключи> <путь до файла> <путь до файла>
            Ключи
            --diff-filter=<метка>
            Позволяет выбирать, изменения между какими файлами показывать. Возможные значения метки: A (от англ. to add – добавить) – файлы, добавленные в индекс, D (от англ. to delete – удалить) – удаленные файлы, M (от англ. to modify – изменить) – файлы, в которые были внесены изменения с момента последнего коммита, и некоторые другие.

            --word-diff=color
            Подсвечивает добавленные слова зеленым цветом, а удаленные – красным. По умолчанию словом считается неразрывная последовательность символов между пробелами.
            Что делает
            Помогает просмотреть изменения между файлами, коммитами, ветками.
            Пример
            # Посмотрим различия между двумя последними коммитами. Чтобы это сделать, узнаем хэши этих коммитов с помощью git log.
            $ git log -2 --pretty=oneline
            89577eee09f17cafd7bcf8ba55d0cc6cbd293bbb (HEAD -> develop) changed doc-for-diff
            f0c9083f4a4b989b978d88599741de5653e4746e added doc-for-diff

            # Зная коммиты, просмотрим изменения. Можно заметить, что необязательно полностью вводить хэш коммита, достаточно первых 5-6 символов.
            $ git diff c23ad02 3c5d306
            diff --git a/doc-for-diff.txt b/doc-for-diff.txt
            index 683de51..33a584a 100644
            --- a/doc-for-diff.txt
            +++ b/doc-for-diff.txt
            @@ -1 +1,2 @@
            Здесь есть какие-то строки текста или кода
            +А вот тут мы добавили одну строку

            Чтобы понять, как читать вывод git diff, рассмотрим еще один пример. Допустим, есть репозиторий с файлом doc-for-diff.txt. Изначально файл выглядел так:
            Содержимое файла "doc-for-diff.txt" до изменения:
            Строка 1 (будет удалена)
            Строка 2 (не меняется)
            
            А после изменений стал выглядеть так:
            Содержимое файла "doc-for-diff.txt" после изменения:
            Строка 3 (заменила Строку 1)
            Строка 2 (не меняется)
            Содержимое файла doc-for-diff.txt ДО и ПОСЛЕ изменений
            Содержимое файла doc-for-diff.txt ДО и ПОСЛЕ изменений
            Рассмотрим, как будет выглядеть вывод git diff для этих двух файлов:
            Git Bash
            $ git diff ee05baa b88dc11
            diff --git a/doc-for-diff.txt b/doc-for-diff.txt
            index e044913..c1fe177 100644
            --- a/doc-for-diff.txt
            +++ b/doc-for-diff.txt
            @@ -1,2 +1,2 @@
            -Строка 1 (будет удалена)
            +Строка 3 (заменила Строку 1)
             Строка 2 (не меняется)
            Давайте построчно разберемся, что значит этот странный вывод.

            1. Первая строка вывода diff --git a/doc-for-diff.txt b/doc-for-diff.txt – это данные сравнения. В данном случае сравниваются два файла: a/doc-for-diff.txt из первого коммита, и b/doc-for-diff.txt из второго.
            2. Затем идут метаданные сравнения, в нашем случае: index e044913..c1fe177 100644. В данном случае это часть хэша blob-объекта для файла a/doc-for-diff.txt.
            3. Третья и четвертая строки заключают в себе легенду последующего вывода. в нашем случае она выглядит так: --- a/doc-for-diff.txt +++ b/doc-for-diff.txt. Такая запись означает, что изменения из файла a/doc-for-diff.txt помечаются знаком -, а изменения из файла b/doc-for-diff.txt помечаются знаком +.
            4. После легенды начинаются блоки сравнений. Именно блоки, поскольку общие фрагменты двух файлов игнорируются. Каждый блок затрагивает несколько строк сравнения и начинается с заголовка.
            5. Первая строка блока называется заголовок. Она заключена между знаками @@. В заголовке описано, какие изменения были внесены в файл. В нашем случае заголовок сообщает, что, начиная с 1 строки, было удалено 2 строчки и вместе с тем, начиная с 1 строки, было добавлено 2 строчки. Может возникнуть вопрос: почему 2, если мы поменяли всего одну строку? Дело в том, что Git считает длину блока. В нашем случае, в блоке две строки. Для простоты можно считать, что неизмененные строки удалили, а на их место добавили точно такие же. Если бы неизмененных строк было много, Git пропустил бы их и разбил изменения на несколько блоков. В нашем же случае всего одна строка не изменилась, поэтому Git не стал так делать.
            6. Последующие строки это непосредственно сами изменения. В нашем случае по данному выводу можно сказать, что УДАЛЕННАЯ часть файла a/doc-for-diff.txt выглядела так:
            Удаленное содержимое файла "a/doc-for-diff.txt"
            Строка 1 (будет удалена)
            
            А ДОБАВЛЕННЫЕ в файл b/doc-for-diff.txt строки, это:
            Добавленное содержимое файла "b/doc-for-diff.txt"
            Строка 3 (заменила Строку 1)
            
            Но такой вывод бывает не всегда. Иногда файл оказывается бинарным, тогда нет смысла выводить информацию о удаленных и добавленных в файл строках. Поэтому git diff выводит различную метаинформацию о нем. Например, хэш-сумму и размер вот так:
            Git Bash
            $ git diff fc99663ca20e98012b5a1fb0 abd1282acb623fd8729a6e7
            diff --git a/gpt2-generator/gpt2-memes-params.zip b/gpt2-generator/gpt2-memes-pa
            rams.zip
            new file mode 100644
            index 0000000..e0ad67c
            --- /dev/null
            +++ b/gpt2-generator/gpt2-memes-params.zip
            @@ -0,0 +1,3 @@
            +version https://git-lfs.github.com/spec/v1
            +oid sha256:8f32b36ff1f19f93a6d00a2523a7ed629aea2cae108ce359e3cbc3bcc214881b
            +size 462580017
            Теперь вы знаете, как доставать полезную информацию из истории изменений и различий между файлами.
            Подведем итог
            1. Будьте осторожны переключаясь между ветками. Не "утащите" файл с одной ветки на другую. Перед и сразу после переключения между ветками, вывод git status должен быть пустым.
            2. Чтобы просмотреть историю изменений, используйте git log. Ключ --pretty=oneline позволит сделать вывод лаконичным, а с помощью ключа -n=<число> можно указать количество необходимых коммитов. Если запутались в репозитории, постройте граф с ключом --graph, это поможет разобраться, что к чему.
            3. Чтобы просмотреть различия между коммитами, стоит использовать git diff. Эта команда выведет краткую информацию о внесенных изменениях и построчный анализ внесенных изменений.
            7

            Удаление веток

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

            Как и всегда, когда речь доходит до удаления чего-либо, нужно быть очень осторожным. Когда вы удаляете ветку, все изменения, которые не были добавлены в коммит, будут утеряны. Делаем акцент: не добавленные в коммит, поскольку даже после удаления ветки вы сможете поместить указатель HEAD на тот коммит, на который указывала удаленная ветка (при условии, что вы помните часть хэша этого коммита), в этом случае вы попадете в состояние detached head, но об этом в следующий раз. В любом случае, будьте очень внимательны, удаляя ветки.

            Итак, для удаления мы воспользуемся уже знакомой командой git branch с ключами -d и -D.

            Команда git branch

            Что делает
            Удаляет указанную ветку.
            Пример
            # Выведем список локальных веток
            $ git branch
            * develop
            feature
            main

            # Попробуем удалить ветку feature
            $ git branch -d feature
            error: The branch 'feature' is not fully merged.
            If you are sure you want to delete it, run 'git branch -D feature'.

            # Получаем предупреждение Git, о котором говорилось выше. Воспользуется ключом -D.
            $ git branch -D feature
            Deleted branch feature(was cc8005b).

            # Как видно, ветка была удалена, а Git вывел нам хэш коммита, на который указывала удаленная ветка.

            На удивление, здесь нет никаких тонкостей, о которых стоило бы упомянуть, поэтому теперь вы можете смело заявить, что умеете удалять ветки.
            ОБЩИЙ ИТОГ

            Итак, вспомним, чему мы научились в этом уроке.
            1. Мы узнали, что ветка это тоже указатель, и научились интуитивно представлять себе ветки.
            2. Узнали, что ветки нужны для распараллеливания работы над проектом и тестирования новых функций.
            3. Мы научились создавать ветки. Можно использовать git branch <имя ветки>, но удобнее git checkout -b <имя ветки>, поскольку такая команда сразу переключит нас на новую ветку.
            4. Научились просматривать список веток с помощью команды git branch.
            5. Узнали, как переключаться между ветками, используя команду git checkout, и про то, что можно случайно перенести файл с одной ветки в другую.
            6. Научились просматривать статус файлов в ветке.
            7. Познакомились с командой git log, научились выводить историю одной ветки и строить граф репозитория.
            8. Узнали, как просмотреть различия между двумя коммитами или файлами.
            9. Научились удалять ветки.

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

            1

            Задание. Библиотека Geometric Lib.

            Итак, с теоретической частью мы закончили. Пора применить изученные команды на практике. В этом уроке мы будем использовать уже готовый удаленный репозиторий, не пугайтесь, мы подробно изучим работу с ними в последующих уроках, но пока нам пригодится одна команда: git clone. Она копирует содержимое удаленного репозитория к вам на компьютер, делая его локальным.

            Наш удаленный репозиторий представляет собой библиотеку на Python, которая позволяет высчитывать площади и периметры некоторых геометрических фигур.
            Библиотека Geometric Lib на GitHub: https://github.com/smartiqaorg/geometric_lib

            Структура репозитория выглядит так:
            Структура библиотеки geometric_lib
            geometric_lib
                ├── circle.py
                ├── square.py
                └── docs
                     └── README.md
            В файле circle.py находятся функции для вычисления периметра и площади круга. В файле square.py – то же самое, но для квадрата. В директории docs лежит единственный файл README.md с описанием репозитория.
            2

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

            Теперь когда мы познакомились со структурой репозитория geometric_lib, можно переходить непосредственно к условию задания:
            Условие
            1) Выполните команду git clone https://github.com/smartiqaorg/geometric_lib. Она скопирует наш репозиторий к вам на компьютер.

            2) Создайте новую ветку с названием new_features и переключитесь на нее.

            3) Добавьте новый файл в эту ветку. Например, с вычислениями для фигуры Прямоугольник.
            Его название: rectangle.py
            Его содержимое:

            def area(a, b):
            ____return a * b

            def perimeter(a, b):
            ____return a + b



            4) Сделайте коммит с сообщением "L-03: Added rectangle.py".

            5) Добавьте еще один файл в эту ветку. Например, с вычислениями для фигуры Треугольник.
            Его название: triangle.py
            Его содержимое:

            def area(a, h):
            ____return a * h / 2

            def perimeter(a, b, c):
            ____return a + b + c


            6) Исправьте ошибку в вычислении периметра в файле rectangle.py, теперь он должен стать таким:

            def area(a, b):
            ____return a * b

            def perimeter(a, b):
            ____return 2 * (a + b)



            7) Создайте еще один коммит внутри этой же ветки, его сообщение: "L-03: Added triangle.py and fixed rectangle perimeter bug".

            8) Постройте граф истории всего репозитория с однострочным выводом коммитов.

            9) Постройте граф истории только текущей ветки. В ней должно быть два последних коммита.

            10) Возьмите хэши двух последних коммитов из истории и посмотрите, какие изменения были внесены.

            11) Обычно, так не делают на практике, но мы только учимся, поэтому давайте удалим ветку new_features без слияния. Не забудьте, что нельзя удалить ветку, на которой находится HEAD.
            3

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

            Решение - Git Bash
            # 1. Клонируем репозиторий
            $ git clone https://github.com/smartiqaorg/geometric_lib
            $ cd geometric_lib/
            
            
            # 2. Создаем новую ветку new_feature
            $ git checkout -b new_feature
            
            
            # 3-4. Делаем коммит для Прямоугольника
            $ nano rectangle.py
            <Добавляем содержимое файла rectangle.py>
            $ git add rectangle.py
            $ git commit -m "L-03: Added rectangle.py"
            
            
            # 5-7. Делаем коммит для Треугольника и исправляем ошибку для Прямоугольника
            $ nano triangle.py
            <Добавляем вычисления для Треугольника>
            $ nano rectangle.py 
            <Исправляем ошибку для Прямоугольника>
            $ git add *
            $ git commit -m "L-03: Added triangle.py and fixed rectangle perimeter bug"
            
            
            # 8-9. Строим графы репозитория
            $ git log --all --pretty=oneline --graph
            $ git log --pretty=oneline --graph main..new_feature
            
            
            # 10. Смотрим изменения между соседними коммитами
            $ git diff <хэш_предпоследнего_коммита> <хэш_последнего_коммита>
            
            
            # 11. Удаляем ветку new_feature
            $ git checkout main
            $ git branch -D new_feature

            Более подробный разбор задания

            Также на странице Задания и Ответы по курсу Git мы даем более подробный разбор текущего задания.
            Приводим в нем не только команды Git, но и их консольный вывод, а также даем комментарии к каждой команде.
            Как вам материал?

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