7.7 Инструменты Git — Раскрытие тайн reset
Перед тем, как перейти к более специализированными утилитам, давайте поговорим о reset и checkout . Эти команды кажутся самыми непонятными из всех, которые есть в Git, когда вы в первый раз сталкиваетесь с ними. Они делают так много, что попытки по-настоящему их понять и правильно использовать кажутся безнадёжными. Для того, чтобы всё же достичь этого, мы советуем воспользоваться простой аналогией.
Три дерева
Разобраться с командами reset и checkout будет проще, если считать, что Git управляет содержимым трёх различных деревьев. Здесь под «деревом» мы понимаем «набор файлов», а не специальную структуру данных. (В некоторых случаях индекс ведет себя не совсем так, как дерево, но для наших текущих целей его проще представлять именно таким.)
В своих обычных операциях Git управляет тремя деревьями:
Снимок последнего коммита, родитель следующего
Снимок следующего намеченного коммита
Указатель HEAD
HEAD — это указатель на текущую ветку, которая, в свою очередь, является указателем на последний коммит, сделанный в этой ветке. Это значит, что HEAD будет родителем следующего созданного коммита. Как правило, самое простое считать HEAD снимком вашего последнего коммита.
На самом деле, довольно легко увидеть, что представляет из себя этот снимок. Ниже приведён пример получения содержимого каталога и контрольных сумм для каждого файла в HEAD:
$ git cat-file -p HEAD tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf author Scott Chacon 1301511835 -0700 committer Scott Chacon 1301511835 -0700 initial commit $ git ls-tree -r HEAD 100644 blob a906cb2a4a904a152. README 100644 blob 8f94139338f9404f2. Rakefile 040000 tree 99f1a6d12cb4b6f19. lib
Команды cat-file и ls-tree являются «служебными» (plumbing) командами, которые используются внутри системы и не требуются при ежедневной работе, но они помогают нам разобраться, что же происходит на самом деле.
Индекс
Индекс — это ваш следующий намеченный коммит. Мы также упоминали это понятие как «область подготовленных изменений» Git — то, что Git просматривает, когда вы выполняете git commit .
Git заполняет индекс списком изначального содержимого всех файлов, выгруженных в последний раз в ваш рабочий каталог. Затем вы заменяете некоторые из таких файлов их новыми версиями и команда git commit преобразует изменения в дерево для нового коммита.
$ git ls-files -s 100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README 100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile 100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb
Повторим, здесь мы используем служебную команду ls-files , которая показывает вам, как выглядит сейчас ваш индекс.
Технически, индекс не является древовидной структурой, на самом деле, он реализован как сжатый список (flattened manifest) — но для наших целей такого представления будет достаточно.
Рабочий Каталог
Наконец, у вас есть рабочий каталог. Два других дерева сохраняют свое содержимое эффективным, но неудобным способом внутри каталога .git . Рабочий Каталог распаковывает их в настоящие файлы, что упрощает для вас их редактирование. Считайте Рабочий Каталог песочницей, где вы можете опробовать изменения перед их коммитом в индекс (область подготовленных изменений) и затем в историю.
$ tree . ├── README ├── Rakefile └── lib └── simplegit.rb 1 directory, 3 files
Технологический процесс
Основное предназначение Git — это сохранение снимков последовательно улучшающихся состояний вашего проекта, путём управления этими тремя деревьями.

Давайте рассмотрим этот процесс: пусть вы перешли в новый каталог, содержащий один файл. Данную версию этого файла будем называть v1 и изображать голубым цветом. Выполним команду git init , которая создаст Git-репозиторий, у которого ссылка HEAD будет указывать на ещё несуществующую ветку ( master пока не существует).

На данном этапе только дерево Рабочего Каталога содержит данные.
Теперь мы хотим закоммитить этот файл, поэтому мы используем git add для копирования содержимого Рабочего Каталога в Индекс.

Затем, мы выполняем команду git commit , которая сохраняет содержимое Индекса как неизменяемый снимок, создает объект коммита, который указывает на этот снимок, и обновляет master так, чтобы он тоже указывал на этот коммит.

Если сейчас выполнить git status , то мы не увидим никаких изменений, так как все три дерева одинаковые.
Теперь мы хотим внести изменения в файл и закоммитить его. Мы пройдём через всё ту же процедуру; сначала мы отредактируем файл в нашем рабочем каталоге. Давайте называть эту версию файла v2 и обозначать красным цветом.

Если сейчас мы выполним git status , то увидим, что файл выделен красным в разделе «Изменения, не подготовленные к коммиту», так как его представления в Индексе и Рабочем Каталоге различны. Затем мы выполним git add для этого файла, чтобы поместить его в Индекс.

Если сейчас мы выполним git status , то увидим, что этот файл выделен зелёным цветом в разделе «Изменения, которые будут закоммичены», так как Индекс и HEAD различны — то есть, наш следующий намеченный коммит сейчас отличается от нашего последнего коммита. Наконец, мы выполним git commit , чтобы окончательно совершить коммит.

Сейчас команда git status не показывает ничего, так как снова все три дерева одинаковые.
Переключение веток и клонирование проходят через похожий процесс. Когда вы переключаетесь (checkout) на ветку, HEAD начинает также указывать на новую ветку, ваш Индекс замещается снимком коммита этой ветки, и затем содержимое Индекса копируется в ваш Рабочий Каталог.
Назначение команды reset
Команда reset становится более понятной, если рассмотреть её с учётом вышеизложенного.
В следующих примерах предположим, что мы снова изменили файл file.txt и закоммитили его в третий раз. Так что наша история теперь выглядит так:

Давайте теперь внимательно проследим, что именно происходит при вызове reset . Эта команда простым и предсказуемым способом управляет тремя деревьями, существующими в Git. Она выполняет три основных операции.
Шаг 1: Перемещение указателя HEAD
Первое, что сделает reset — переместит то, на что указывает HEAD. Обратите внимание, изменяется не сам HEAD (что происходит при выполнении команды checkout ); reset перемещает ветку, на которую указывает HEAD. Таким образом, если HEAD указывает на ветку master (то есть вы сейчас работаете с веткой master ), выполнение команды git reset 9e5e6a4 сделает так, что master будет указывать на 9e5e6a4 .

Не важно с какими опциями вы вызвали команду reset с указанием коммита ( reset также можно вызывать с указанием пути), она всегда будет пытаться сперва сделать данный шаг. При вызове reset —soft на этом выполнение команды и остановится.
Теперь взгляните на диаграмму и постарайтесь разобраться, что случилось: фактически была отменена последняя команда git commit . Когда вы выполняете git commit , Git создает новый коммит и перемещает на него ветку, на которую указывает HEAD. Если вы выполняете reset на HEAD~ (родителя HEAD), то вы перемещаете ветку туда, где она была раньше, не изменяя при этом ни Индекс, ни Рабочий Каталог. Вы можете обновить Индекс и снова выполнить git commit , таким образом добиваясь того же, что делает команда git commit —amend (смотрите Изменение последнего коммита).
Шаг 2: Обновление Индекса (—mixed)
Заметьте, если сейчас вы выполните git status , то увидите отмеченные зелёным цветом изменения между Индексом и новым HEAD.
Следующим, что сделает reset , будет обновление Индекса содержимым того снимка, на который указывает HEAD.

Если вы указали опцию —mixed , выполнение reset остановится на этом шаге. Такое поведение также используется по умолчанию, поэтому если вы не указали совсем никаких опций (в нашем случае git reset HEAD~ ), выполнение команды также остановится на этом шаге.
Снова взгляните на диаграмму и постарайтесь разобраться, что произошло: отменен не только ваш последний commit , но также и добавление в индекс всех файлов. Вы откатились назад до момента выполнения команд git add и git commit .
Шаг 3: Обновление Рабочего Каталога (—hard)
Третье, что сделает reset — это приведение вашего Рабочего Каталога к тому же виду, что и Индекс. Если вы используете опцию —hard , то выполнение команды будет продолжено до этого шага.

Давайте разберемся, что сейчас случилось. Вы отменили ваш последний коммит, результаты выполнения команд git add и git commit , а также все изменения, которые вы сделали в рабочем каталоге.
Важно отметить, что только указание этого флага ( —hard ) делает команду reset опасной, это один из немногих случаев, когда Git действительно удаляет данные. Все остальные вызовы reset легко отменить, но при указании опции —hard команда принудительно перезаписывает файлы в Рабочем Каталоге. В данном конкретном случае, версия v3 нашего файла всё ещё остаётся в коммите внутри базы данных Git и мы можем вернуть её, просматривая наш reflog , но если вы не коммитили эту версию, Git перезапишет файл и её уже нельзя будет восстановить.
Резюме
Команда reset в заранее определённом порядке перезаписывает три дерева Git, останавливаясь тогда, когда вы ей скажете:
- Перемещает ветку, на которую указывает HEAD (останавливается на этом, если указана опция —soft )
- Делает Индекс таким же как и HEAD (останавливается на этом, если не указана опция —hard )
- Делает Рабочий Каталог таким же как и Индекс.
Reset с указанием пути
Основной форме команды reset (без опций —soft и —hard ) вы также можете передавать путь, с которым она будет оперировать. В этом случае, reset пропустит первый шаг, а на остальных будет работать только с указанным файлом или набором файлов. Первый шаг пропускается, так как HEAD является указателем и не может ссылаться частично на один коммит, а частично на другой. Но Индекс и Рабочий Каталог могут быть изменены частично, поэтому reset выполняет шаги 2 и 3.
Итак, предположим вы выполнили команду git reset file.txt . Эта форма записи (так как вы не указали ни SHA-1 коммита, ни ветку, ни опций —soft или —hard ) является сокращением для git reset —mixed HEAD file.txt , которая:
- Перемещает ветку, на которую указывает HEAD (будет пропущено)
- Делает Индекс таким же как и HEAD (остановится здесь)
То есть, фактически, она копирует файл file.txt из HEAD в Индекс.

Это создает эффект отмены индексации файла. Если вы посмотрите на диаграммы этой команды и команды git add , то увидите, что их действия прямо противоположные.

Именно поэтому в выводе git status предлагается использовать такую команду для отмены индексации файла. (Смотрите подробности в Отмена индексации файла.)
Мы легко можем заставить Git «брать данные не из HEAD», указав коммит, из которого нужно взять версию этого файла. Для этого мы должны выполнить следующее git reset eb43bf file.txt .

Можно считать, что, фактически, мы в Рабочем Каталоге вернули содержимое файла к версии v1, выполнили для него git add , а затем вернули содержимое обратно к версии v3 (в действительности все эти шаги не выполняются). Если сейчас мы выполним git commit , то будут сохранены изменения, которые возвращают файл к версии v1, но при этом файл в Рабочем Каталоге никогда не возвращался к такой версии.
Заметим, что как и команде git add , reset можно указывать опцию —patch для отмены индексации части содержимого. Таким способом вы можете избирательно отменять индексацию или откатывать изменения.
Слияние коммитов
Давайте посмотрим, как, используя вышеизложенное, сделать кое-что интересное — слияние коммитов.
Допустим, у вас есть последовательность коммитов с сообщениями вида «упс.», «В работе» и «позабыл этот файл». Вы можете использовать reset для того, чтобы просто и быстро слить их в один. (В разделе Объединение коммитов главы 7 представлен другой способ сделать то же самое, но в данном примере проще воспользоваться reset .)
Предположим, у вас есть проект, в котором первый коммит содержит один файл, второй коммит добавляет новый файл и изменяет первый, а третий коммит снова изменяет первый файл. Второй коммит был сделан в процессе работы и вы хотите слить его со следующим.

Вы можете выполнить git reset —soft HEAD~2 , чтобы вернуть ветку HEAD на какой-то из предыдущих коммитов (на первый коммит, который вы хотите оставить):

Затем просто снова выполните git commit :

Теперь вы можете видеть, что ваша «достижимая» история (история, которую вы впоследствии отправите на сервер), сейчас выглядит так — у вас есть первый коммит с файлом file-a.txt версии v1, и второй, который изменяет файл file-a.txt до версии v3 и добавляет file-b.txt . Коммита, который содержал файл версии v2 не осталось в истории.
Сравнение с checkout
Наконец, вы можете задаться вопросом, в чем же состоит отличие между checkout и reset . Как и reset , команда checkout управляет тремя деревьями Git, и также её поведение зависит от того указали ли вы путь до файла или нет.
Без указания пути
Команда git checkout [branch] очень похожа на git reset —hard [branch] , в процессе их выполнения все три дерева изменяются так, чтобы выглядеть как [branch] . Но между этими командами есть два важных отличия.
Во-первых, в отличие от reset —hard , команда checkout бережно относится к рабочему каталогу, и проверяет, что она не трогает файлы, в которых есть изменения. В действительности, эта команда поступает немного умнее — она пытается выполнить в Рабочем Каталоге простые слияния так, чтобы все файлы, которые вы не изменяли, были обновлены. С другой стороны, команда reset —hard просто заменяет всё целиком, не выполняя проверок.
Второе важное отличие заключается в том, как эти команды обновляют HEAD. В то время как reset перемещает ветку, на которую указывает HEAD, команда checkout перемещает сам HEAD так, чтобы он указывал на другую ветку.
Например, пусть у нас есть ветки master и develop , которые указывают на разные коммиты и мы сейчас находимся на ветке develop (то есть HEAD указывает на неё). Если мы выполним git reset master , сама ветка develop станет ссылаться на тот же коммит, что и master . Если мы выполним git checkout master , то develop не изменится, но изменится HEAD. Он станет указывать на master .
Итак, в обоих случаях мы перемещаем HEAD на коммит A, но важное отличие состоит в том, как мы это делаем. Команда reset переместит также и ветку, на которую указывает HEAD, а checkout перемещает только сам HEAD.

С указанием пути
Другой способ выполнить checkout состоит в том, чтобы указать путь до файла. В этом случае, как и для команды reset , HEAD не перемещается. Эта команда как и git reset [branch] file обновляет файл в индексе версией из коммита, но дополнительно она обновляет и файл в рабочем каталоге. То же самое сделала бы команда git reset —hard [branch] file (если бы reset можно было бы так запускать) — это небезопасно для рабочего каталога и не перемещает HEAD.
Также как git reset и git add , команда checkout принимает опцию —patch для того, чтобы позволить вам избирательно откатить изменения содержимого файла по частям.
Заключение
Надеюсь, вы разобрались с командой reset и можете её спокойно использовать. Но, возможно, вы всё ещё немного путаетесь, чем именно она отличается от checkout , и не запомнили всех правил, используемых в различных вариантах вызова.
Ниже приведена памятка того, как эти команды воздействуют на каждое из деревьев. В столбце «HEAD» указывается «REF» если эта команда перемещает ссылку (ветку), на которую HEAD указывает, и «HEAD» если перемещается только сам HEAD. Обратите особое внимание на столбец «Сохранность рабочего каталога?» — если в нём указано НЕТ, то хорошенько подумайте прежде чем выполнить эту команду.
На уровне коммитов
6. Ветки в GIT
При работе над проектами часто возникает ситуация при которой существует несколько вариантов проекта. Например, один вариант – это текущая версия проекта, другой вариант – это версия, в которую внесены экспериментальные или логически не завершенные элементы. Особенно часто возникают различные варианты проекта при работе над ним нескольких разработчиков.
В СКВ различные варианты проекта хранятся в различных ветках (branch).
При создании проекта существует только одна ветка (один вариант проекта), которая называется master . Эта ветка в течении всего проекта является основной. На ее основе могут создавать другие ветки, которые с помощью новых коммитов будут представлять другие варианты проекта.
Ветки проекта могут объединяться (сливаться). Например, после логического завершения какой-либо части проекта, выполняемой в отдельной ветке, ветка этой части сливается с веткой master .
В СКВ git для организации работы с ветками каждый коммит хранит указатель на предыдущий коммит. Благодаря этому для каждой ветки достаточно хранить только указатель на последний коммит в этой ветке. Указатель на ветку имеет то же имя, что и ветка, и в этом смысле указатель и саму ветку можно считать синонимами.
Помимо указателей на ветку в git существует еще указатель HEAD на текущий коммит (ветку). В случае, если используется одна ветка, указатели HEAD и master совпадают. Если в репозитории имеется несколько веток, то ветка, на которую указывает указатель HEAD называется текущей.
При выполнении команды git commit создается новый коммит, после чего на него перемещаются указатели HEAD и текущей ветки.
Команды для работы с ветками и указателями
Основные команды для работы с ветками схематично приведены на рисунке 2 (с1, с2, с3 – коммиты, стрелки между блоками – указатели).

Рисунок 2 – Команды для работы с ветками
Для создания новой ветки используется команда
git branch имя_ветки
Для просмотра имеющихся в репозитории веток нужно ввести эту команду без аргументов:
git branch
Эта команда только создает ветку, но не изменяет указатель на текущую ветку HEAD . Для того, чтобы сделать ветку текущей (переместить HEAD ) нужно выполнить команду
git checkout имя_ветки
Создать ветку и сразу на нее переключиться можно с помощью команды
git checkout -b имя_ветки
В обычном режиме работы рассмотренных команд достаточно для работы с ветками (за исключением слияния веток, которое будет рассмотрено отдельно). Однако, для исправления ошибок при работе с коммитами может потребоваться команда, позволяющая вручную перемещать указатели веток с коммита на коммит (прежде всего, откатывать указатели ветки на предыдущие коммиты).
Для перемещения указателей веток может использоваться команда
git reset [режим] commit
В поле commit задается идентификатор коммита. Идентификатор представляет собой шестнадцатеричное число, получаемое из контрольной суммы. Узнать идентификаторы коммитов можно с помощью команды git log . Также в поле commit можно использовать специальные мнемокоды, например, HEAD~ будет указывать на предыдущий коммит текущей ветки.
В общем случае эта команда делает следующие действия:
- Перемещает ветку, на которую указывает HEAD на заданный commit.
- Обновляет индекс в соответствии с содержимым коммита commit.
- Обновляет содержимое рабочего каталога в соответствии с содержимым коммита commit.
В различных сценариях использования может потребоваться выполнять не полный набора этих действий, что задается режимом команды:
- —soft – выполняет только первое действие;
- —mixed (режим по-умолчанию) – выполняет 1 и 2 действия;
- —hard – выполняет все действия.
Существует также версия команды git reset для обновления индекса (без перемещение указателей). Она была рассмотрена в предыдущей работе.
Помимо команд для выполнения действий над ветками СКВ git имеет возможности для их просмотра. По-умолчанию, команда git log выводит коммиты только текущей ветки. Для просмотра всех веток для этой команды нужно указать ключи -all . Более наглядным вывод веток в терминал получится, если дополнительно указать ключ —graph , при котором с помощью псевдографики будет нарисовано дерево репозитория.
Задание к работе
После каждого пункта из задания к работе нужно выполнить команду для вывода в терминал дерева репозитория. В отчете нужно также привести содержимое файла репозитория после выполнение каждого пункта, начиная со 2.
- Создайте ветку, назовите ее своим именем (в транслитерации).
- Измените содержимое файла и добавьте коммит в новую ветку. Повторите этот пункт.
- Измените содержимое файла и добавьте коммит в ветку master . Повторите этот пункт.
- Установите указатель новой ветки на коммит с первым изменением файла.
- Установите указатель ветки master на коммит с первым изменением файла в этой ветки. При этом должно произойти обновление индекса в соответствии с содержимым этого коммита. Для просмотра содержимого индекса используйте команду git ls-files .
- Перейдите на новую ветку и переместите ее указатель на первоначальный коммит в этой работе. При этом должно произойти обновление индекса и рабочего каталога в соответствии с содержимым этого коммита.
Как называется указатель на текущее состояние в ветке

Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
61 репозиторий для хакеров на Github
Огромная подборка полезных вещей, инструментов, книг и прочих источников для хакеров, тестировщиков на проникновение и исследователей в области информационной безопасности.
Про Git, Github и Gitflow простыми словами
Не самое исчерпывающее, но точно вполне доходчивое руководство по Git, Github и Gitflow – для тех, кого эти слова смущают, хотя не должны.
Git за полчаса: руководство для начинающих
В последние годы популярность git демонстрирует взрывной рост. Эта система контроля версий используется различными проектами с открытым исходным кодом.
Учебник по Git: о ветках проекта, их создании и параллельной работе в разных ветках
В начале подглавы рассказано о том, что программа «Git» выделяется среди систем контроля версий своей реализацией ветвления при разработке проекта. Другие системы для реализации ветки копируют все файлы проекта в другой каталог. А программа «Git» просто создает указатель на коммит, то есть создает один файл (вместо копирования всех файлов проекта), в котором записана хеш-сумма коммита, на который ссылается этот указатель.
За счет такой организации ветвления создание отдельной ветки (branch) проекта происходит мгновенно. Таким образом программа «Git» поощряет разработчика к созданию множества веток и затем слиянию веток обратно в одну. В подглаве написано, что программа «Git» подтолкнула многих разработчиков перейти на разработку с использованием множества веток проекта.
Подглава мне очень понравилась. Всё объяснено понятно и со множеством иллюстраций. Стало понятно, как в программе «Git» организовано хранение коммитов (объект-коммит хранит ссылку на объект-каталог (их может быть несколько, как и каталогов в рабочей папке проекта), в котором хранится список ссылок на объекты-файлы). Объекты-коммиты хранят ссылку на предыдущий объект-коммит (один или несколько), который называется «родителем» текущего объекта-коммита.
Каждая ветка представляет собой ссылку на текущий коммит. Есть еще одна ссылка, которая указывает на текущую ветку. Эта ссылка называется «HEAD». При переключении между ветками проекта происходит переключение ссылки «HEAD» на указанную ссылку-ветку.
Создание ветки
Изначально у всех проектов есть какая-то первая ветка разработки проекта. В программе «Git» для этой ветки проекта по умолчанию предусмотрено имя «master», но это имя можно в настройках программы «Git» заменить на любое другое. Первая ветка проекта ничем не отличается от других веток проекта. Первая ветка проекта создается при запуске команды «git init» (создание Git-репозитория) и последующем выполнении первого коммита.
Создание новой ветки проекта выполняется с помощью следующей команды:
git branch
Вот как это выглядит у меня в командной строке:

Сначала я вывел список коммитов моего учебного проекта «myapp», сейчас их семь штук. Виден указатель ветки «master», который указывает на последний (самый свежий) коммит с названием «Седьмой коммит». Также виден указатель «HEAD», который указывает на указатель ветки «master», это означает, что ветка «master» — текущая ветка проекта. На данный момент она одна и существует.
После этого я создал новую ветку проекта, которую назвал «testing» с помощью следующей команды:
git branch testing
И в конце я снова вывел список коммитов моего проекта. На вышеприведенной иллюстрации видно, что теперь на последний (самый свежий) коммит с названием «Седьмой коммит» указывают два указателя: «master» и «testing». Указатель «HEAD» продолжает указывать на указатель ветки «master». Таким образом, команда «git branch» лишь создает новый указатель ветки на текущий коммит, но не переключает указатель текущей ветки на новую, только что созданную ветку.
На самом деле, разветвления проекта еще не произошло, ветка в проекте пока что всё еще одна. Но созданы механизмы (два указателя) для работы с отдельными двумя ветками.
Переключение на другую ветку
Переключимся на новую ветку «testing». Это делается с помощью такой команды:
git checkout
Вот как это выглядит у меня в командной строке:

На этой иллюстрации я сначала вывел список коммитов. Видно, что текущей веткой является ветка «master». После этого я ввел команду для переключения на ветку «testing» и снова вывел список коммитов. Видно, что после этого текущей веткой стала ветка «testing». Опять же, фактического разветвления пока не произошло, мы просто переключаемся между указателями.
Важно: команда «git checkout testing» не просто переключает систему контроля версий на другую ветку проекта, а еще заменяет все файлы в рабочем каталоге на файлы из коммита, на который указывает указатель ветки. В данном случае это не видно практически, потому что указатели обоих наших веток указывают на один и тот же коммит (фактического разветвления пока что, как было указано ранее, еще не произошло), но это будет иметь значение, когда указатели веток будут указывать на разные коммиты.
Параллельная работа в разных ветках проекта
Сделаем изменение в проекте и выполним коммит. Понятно, что сейчас это будет коммит в ветку «testing», потому что она является текущей. Вот как это выглядит у меня в командной строке:

На этой иллюстрации не видно, как я делаю изменение в проекте (я просто добавил в текстовый файл «README» строку текста). На иллюстрации видно, как я одной командой поместил измененный файл в индекс и сделал коммит с названием «Изменение для ветки». После этого я вывел список коммитов. На иллюстрации видно, что теперь указатели веток указывают на разные коммиты: указатель ветки «master» остался на коммите с названием «Седьмой коммит», а указатель ветки «testing» перешел на новый, только что созданный, коммит с названием «Изменение для ветки».
Переключимся назад на ветку «master»:

На этой иллюстрации видно, что я переключился на указатель ветки «master», но перестала показываться ветка «testing». Это не значит, что она исчезла. Дело в том, что команда «git log» по умолчанию показывает список коммитов только текущей ветки. Чтобы эта команда вывела список коммитов конкретной ветки, нужно задать ей название нужной ветки. А чтобы эта команда вывела список коммитов всех веток, можно задать ей параметр-флаг «—all».
Пока что наша текущая ветка — это ветка с названием «master». Когда я переключился с ветки «testing» на ветку «master», как уже было сказано выше, файлы в рабочем каталоге были заменены на файлы из коммита, на который указывает ветка «master». То есть фактически произошел «откат» коммита, на который указывает указатель ветки «testing». Файл «README», который я изменял для ветки «testing», вернулся в исходное состояние. Сделаем теперь изменение проекта для ветки «master»: я снова добавил в файл «README» строку текста, но теперь уже другую.
Внесем это изменение в индекс и сделаем коммит для ветки «master» (она у нас сейчас текущая):

На этой иллюстрации видно, что сначала я выполнил одной командой добавление измененного файла в индекс и создание коммита. Затем я вывел список коммитов проекта в консоль. В команде «git log» я использовал параметр-флаг «—all», что позволило мне получить список всех коммитов проекта, а не только коммитов текущей ветки.
Тут уже произошло фактическое разветвление проекта на две ветки. На иллюстрации выше этого не видно, но существует способ отобразить разветвление более наглядно. О нем я упоминал в посте, посвященном подглаве 2.3 (просмотр истории коммитов) обсуждаемого тут учебника: можно использовать параметр «—graph» команды «git log». Ранее я не мог использовать этот параметр, потому что еще не дошел тогда до темы ветвления в программе «Git».
Итак, используем параметр «—graph» команды «git log»:

На этой иллюстрации полученное нами разветвление в разработке проекта отображено более наглядно с помощью средств псевдографики. Правда, линии разветвления плохо видно из-за того, что они отображены бурым цветом на темно-синем фоне. Но это вопрос к разработчикам программы «Git». Лично мне это не мешает.
На иллюстрации видно, что в ветке «master» (она на данный момент — текущая) находится коммит с названием «Изменение для главной», а в ветке «testing» находится коммит с названием «Изменение для ветки». Переключаясь между указателями веток, мы сможем продолжить разработку сразу двух веток проекта, отличающихся друг от друга.