В этом практическом задании ты начнешь создавать Гайд по Git на языке разметки Markdown!
Прежде всего надо получить репозиторий, в котором будет проходить дальнейшая работа, а затем подключить нужные настройки.
Найди в GitHub репозиторий https://github.com/kontur-courses/ulearn-git-guide, а затем склонируй его.
Склонировать репозиторий можно в терминале с помощью команды git clone <url>, где в качестве <url> можно использовать адрес из адресной строки браузера.
После клонирования в терминале перейди в папку с репозиторием: cd ulearn-git-guide.
В склонированной папке найди файлы apply-gitconfig-for-win.cmd и apply-gitconfig-for-nix.sh. Файл apply-gitconfig-for-win.cmd подключает к конфигурации репозитория настройки для Windows,
а файл apply-gitconfig-for-nix.sh подключает настройки для Linux и Mac.
В зависимости от своей операционной системы, выполни один из файлов.
Если на Linux или Mac не хватает прав, то выполни в терминале
sh apply-gitconfig-for-nix.sh.
Подключение этих файлов позволит настроить Git в этом репозитории для выполнения заданий, при этом твои личные настройки Git не поменяются.
Открой папку ulearn-git-guide в VS Code (File / Open Folder в главном меню)
Открой Git Graph (кнопка Git Graph в нижней панели VS Code). Убедись, что в нем есть коммит с названием Initial commit.
Также Git Graph можно найти с помощью бокового меню VS Code:

Если Git Graph нет, то возможно это расширение у тебя не установлено.
Открой в VS Code в боковом меню пункт Extensions (Ctrl + Shift + X), найди через поиск расширение «Git Graph» и установи.
История коммитов должна выглядеть так:

Пришло время добавить первый файл в Гайд и сделать первый коммит!
Создай файл s1.md со следующим содержимым:
# S1. Репозитории
#### Репозиторий — хранилище версий исходников
#### Рабочий каталог — текущая версия кода
- `git init` — создать пустой репозиторий
- `git init --bare` — создать пустой репозиторий без рабочего каталога
- `git clone <url>` — склонировать репозиторий в новый каталог
- `git clone --bare <url>` — склонировать репозиторий без рабочего каталога
- `git clean -xdf` — удалить неотслеживаемые файлы из рабочего каталога
Как создать файл в VS Code? | в Git Bash?
Убедись, что Git обнаружил файл s1.md в Working directory.
Для этого выполни в терминале команду git status. В секции Untracked files должен находиться s1.md, подсвеченный красным цветом.
Консольные команды здесь и далее удобнее выполнять через встроенный в IDE терминал.
Как открыть терминал в VS Code?
Если
git statusне показывает изменений, то скорее всего файл создан, но содержимое не сохранено.
Как сохранить содержимое файла в VS Code?
Как настроить автоматическое сохранение файлов в VS Code?
Добавь файл s1.md в Commit index.
Как добавить файл в Commit index в Git Bash? | в VS Code?
Убедись, что файл s1.md попал в Commit index.
Для этого выполни в терминале команду git status и убедись что s1.md находится в секции Changes to be commited и подсвечен зеленым цветом
Сделай коммит с сообщением Add s1.md.
Как сделать коммит в Git Bash? | в VS Code?
Убедись, что коммит с сообщением Add s1.md появился в истории версий.
Для этого выполни в терминале команду git log, которая выводит историю коммитов, и найди коммит с нужным сообщением. Скорее всего появится прокрутка. Используй клавишу q, когда закончишь, чтобы выйти из прокрутки.
Как прокручивать (скроллить) вывод команды в Git Bash?
Как выйти из режима прокрутки в Git Bash?
А еще найди новый коммит в Git Graph.
Открыть Git Graph можно кнопкой
Git Graphв нижней панели VS Code
Теперь история коммитов должна выглядеть так:

Давай поэкспериментируем с Commit index, а также добавим еще файлов в Гайд!
Для начала файл s2.md со следующим содержимым:
# S2. Коммиты
#### История версий — дерево коммитов
#### Индекс коммита используется для постепенной сборки коммита
Конечно же, убедись, что Git обнаружил файл s2.md в Working directory.
Добавь файл s2.md в Commit index и убедись, что он туда попал.
Теперь интереснее! Замени содержимое s2.md на следующее:
# S2. Коммиты
#### История версий — дерево коммитов
#### Индекс коммита используется для постепенной сборки коммита
- `git add .` — добавить все измененные файлы в индекс
- `git add -p <filename>` — выбрать изменения для добавления в индекс
- `git commit -m <msg>` — сохранить содержимое индекса в репозиторий в виде коммита
- `git commit -am <msg>` — добавить изменения в уже известных файлах в индекс, затем сохранить содержимое индекса в репозиторий в виде коммита
- `git status -sb` — вывести состояние каталога и индекса кратко с указанием текущей ветки
- `git restore .` — отменить изменения каталога в соответствии с индексом
- `git restore -S .` — отменить изменения индекса в соответствии с коммитом (отмена git add .)
- `git rm <filename>` — удалить файл из каталога и индекса, чтобы перестать хранить его историю в репозитории
- `git show <commit>` — показать содержимое коммита
- `git diff <from_commit> [<to_commit>]` — вывести разницу между двумя коммитами
- `git diff --name-status <from_commit> [<to_commit>]` — список измененных файлов
- `git difftool <from_commit> [<to_commit>]` — вывести разницу с помощью difftool из настроек
Как думаешь, что сейчас содержат Working directory и Commit index? Какая разница между ними и выполненным в предыдущем задании коммитом?
Чтобы проверить свои догадки выполни в терминале команду git status.
Убедись, что s2.md находится как в Working Directory (секция Changes not staged for commit), так и в Commit Index (секция Changes to be commited).
Далее открой Source Control в VS Code и убедись, что s2.md находится как в верхнем списке Staged Changes, так и в нижнем списке Changes, причем содержимое у файлов разное и при выборе s2.md в нижнем списке показываются отличия от Commit Index, а не предыдущего от коммита.
Чтобы открыть Source Control в VS Code найди пункт с таким названием в боковом меню слева, либо воспользуйся сочетанием клавиш
Ctrl + Shift + G
Выполни коммит с сообщением Add s2.md header. Обрати внимание, что нужные изменения уже есть в Commit index. А изменения из Working directory будут добавлены отдельным коммитом.
В истории коммитов в Git Graph найди только что созданный коммит и убедись, что в него попали только изменения из Commit index.
Открыть Git Graph можно кнопкой
Git Graphв нижней панели VS Code
Закоммить оставшиеся в Working directory изменения с сообщением Change s2.md. Здесь и далее под фразой «закоммить изменения» будет подразумеваться добавление изменений в Commit index и само выполнение коммита.
Теперь история коммитов должна выглядеть так:

При внесении изменений случаются ошибки. Благо Git отслеживает изменения и позволяет легко откатить неудачные.
Добавь файл secrets.txt с таким содержимым:
login: TechWizard
password: ForTheHorde
Добавь файл secrets.txt в Commit index и убедись, что он туда попал.
Измени содержимое файла s1.md на следующее:
# S1. Репозитории
#### Репозиторий — хранилище версий исходников
#### Рабочий каталог — текущая версия кода
#### Ничего, кроме исходников, не должно попадать в репозиторий
- `git init` — создать пустой репозиторий
- `git init --bare` — создать пустой репозиторий без рабочего каталога
- `git clone <url>` — склонировать репозиторий в новый каталог
- `git clone --bare <url>` — склонировать репозиторий без рабочего каталога
- `git clean -xdf` — удалить неотслеживаемые файлы из рабочего каталога
Измени содержимое файла s2.md на следующее:
# S2. Коммиты
#### История версий — дерево коммитов
#### Коммиты неизменны, а хэш — идентификатор коммита
#### Ничего, кроме исходников, не должно попадать в репозиторий
Убедись, что Git обнаружил изменения s1.md и s2.md.
Как думаешь, какие ошибки были допущены в предыдущих изменениях? В каждом ли из трех изменений была ошибка?
Выведи все изменения в терминале с помощью git diff и просмотри их. Скорее всего появится прокрутка. Используй клавишу q, когда закончишь, чтобы выйти из прокрутки.
Как прокручивать (скроллить) вывод команды в Git Bash?
Как выйти из режима прокрутки в Git Bash?
Ревью кода помогло обнаружить ошибки?
Ревью также удобно делать с помощью графических инструментов VS Code. Открой Source Control в VS Code и посмотри на изменения там.
Так какой твой окончательный вердикт по ошибкам?
В репозитории должны храниться исходники, а секретов быть не должно. Поэтому файл secrets.txt не должен попасть в репозиторий. Но вполне может храниться в Working directory для целей разработки.
Сначала убери secrets.txt из Commit index.
Как отменить добавление в индекс с помощью restore в Git Bash? | в VS Code?
Затем добавь файл secrets.txt в .gitignore, чтобы Git его игнорировал. Да, в .gitignore можно и часто полезно использовать шаблоны, чтобы сразу игнорировать целые множества вариантов. Но здесь будет достаточно игнорировать конкретный файл.
В терминале с помощью git status или с помощью Source Control в VS Code проверь, что secrets.txt теперь не видно среди измененных файлов. Но сам файл должен остаться в файловой системе.
Осталось разобраться с изменениями еще в двух файлах. И вот правильный ответ: неверные изменения были добавлены в файл про коммиты. Там все команды Git исчезли! Отмени изменения в этом файле.
Как отменить неудачные изменения в Git Bash? | в VS Code?
В сухом остатке должно быть два файла с изменениями s1.md и .gitignore. Эти изменения надо закоммитить с сообщением Keep secrets.
Теперь история коммитов должна выглядеть так:

Как ни странно, Git можно использовать, чтобы временно сохранить какой-то файл. В конце-концов такой файл надо будет удалить.
Представь, что тебе надо было описать раздел C1 для Гайда, но работа застопорилась где-то в начале. Надо прерваться, а промежуточный прогресс все же хочется сохранить.
Добавь файл c1.md с таким содержимым:
# C1. Минимальная конфигурация
## Первоначальная настройка
TODO
И закоммить эти изменения с сообщением Add part of c1.md.
Но в итоге эту задачу передали другому разработчику, так что можно удалить недоделанную работу. Продвинутые техники работы с Git позволяют «забыть» про неудачный коммит, но можно поступить проще — удалить лишний файл и закоммитить такое состояние.
С помощью команды git rm в терминале удали файл c1.md, а затем закоммить эти изменения с сообщением Remove unfinished c1.md.
Как удалить файл с помощью команды rm в Git Bash?
Теперь история коммитов должна выглядеть так:

Теги позволяют отмечать какие-то важные коммиты. Например, можно отмечать версии разрабатываемого приложения, а в нашем случае — версии разрабатываемого документа.
Сейчас подходящий момент, чтобы сохранить текущий прогресс по выполнению заданий. Для этого достаточно сделать копию папки ulearn-git-guide.
Рабочий каталог и репозиторий находятся внутри этой скопированной папки, так что в любой момент можно будет вернуться к сохраненному состоянию. Для этого надо либо переключиться на копию папки, заменить все содержимое папки ulearn-git-guide на содержимое папки с сохраненным состоянием.
Создай тег с именем v0.1 с помощью команды git tag "v0.1" и убедись, что у коммита Remove unfinished c1.md появился тег.
Как создать легковесный тег в Git Bash? | в Git Graph в VS Code?
Проверь, что созданный тег работает!
Для этого сперва переключись на начальный коммит Initial commit. Кстати, так как на этом коммите нет локальных веток, то это будет ситуация detached HEAD, что пугать тебя не должно.
Как переключиться на конкретный коммит в режиме detached HEAD в Git Bash? | в Git Graph в VS Code?
Теперь с помощью тега вернись назад, другими словами переключись на тег с именем v0.1.
Как переключиться на тег в Git Bash? | в Git Graph в VS Code?
После предыдущего шага HEAD до сих пор не привязан к ветке и указывает непосредственно на коммит. Чтобы продолжить развитие Гайда, переключись на ветку main.
Как переключиться на ветку в Git Bash? | в Git Graph в VS Code?
Теперь история коммитов должна выглядеть так:

Отдельную функциональность следует разрабатывать в отдельных ветках. Так что раздел Гайда про ссылки (references) подготовь в ветке.
Создай файл s3.md со следующим содержимым:
# S3. Ссылки
#### HEAD — текущая ссылка, tag — фиксированная ссылка, branch — движущаяся за HEAD ссылка
#### checkout — перемещение на ветку или коммит, reset — перемещение с веткой на коммит
#### Видно то, на что есть ссылки, остальное — мусор
- `git tag` — вывести список тегов
- `git tag <tagname>` — создать тег
- `git tag -d <tagname>` — удалить тег
- `git branch --sort=-committerdate` — вывести список локальных веток от новых к старым
- `git branch <branchname>` — создать ветку
- `git branch -f <branchname> <to_commit>` — создать или переместить ветку
- `git branch -M <newbranch>` — переименовать текущую ветку, даже если новое имя уже занято
- `git branch -d <branchname>` — удалить ветку
- `git branch -D <branchname>` — удалить ветку, даже если какие-то коммиты будут скрыты
- `git checkout -d <commit>` или `git switch --detach <commit>` — переместить HEAD на коммит, причем получится detached HEAD
- `git checkout <branch>` или `git switch <branch>` — переместить HEAD на ветку
- `git checkout -b <new_branch>` = `git checkout -b <new_branch> HEAD` или `git switch -c <new_branch>` — создать ветку и перейти на нее
- `git reset --hard <commit>` — переместить HEAD и текущую ветку на `<commit>`
- `git log --oneline --decorate --graph` — вывести историю коммитов от HEAD в виде дерева
- `git log --oneline --decorate --graph --all` — вывести историю коммитов от всех ссылок в виде дерева
- `git log --oneline --decorate --graph --all --reflog` — вывести историю коммитов от всех ссылок и всех недавних положений HEAD в виде дерева
- `git reflog show <ref>` — показать лог действий со ссылкой
- `git reflog` = `git reflog show HEAD` — показать лог действий с HEAD
- `git gc` — удалить ненужные файлы и оптимизировать локальный репозиторий
- `<ref>~N` — 1-ый родитель из N-ого поколения, например, `HEAD~3`
- `<ref>^N` — N-ый родитель предыдущего поколения, например, `HEAD^1^2`
- `<ref>@{N}` — N-ый предшественник по reflog, например, `HEAD@{5}`
Сделай коммит с сообщением Add s3.md.
Как сделать коммит в Git Bash? | в VS Code?
Ой! Эти же изменения надо было делать в новой ветке! Теперь придется исправлять!
Сейчас ветка main находится на коммите Add s3.md, а должна находиться на коммите Remove unfinished c1.md. А на коммите Add s3.md должна быть новая ветка с названием, например, refs-feature, которую надо было создать.
Открой дерево коммитов в терминале или Git Graph и убедись, что все именно так, как описано, и осознай проблему.
Дерево коммитов в терминале можно открыть командой
git log --graph --oneline --decorate --allили с помощью алиасаgit graph
То, что ветка refs-feature не была создана сразу — это не проблема. Просто возьми и создай ее на текущем коммите. Не переключайся на эту ветку, текущей веткой должна остаться ветка main.
Как создать ветку на текущем коммите, не переключаясь на нее, в Git Bash? | в Git Graph в VS Code?
Ветку main надо вернуть на коммит Remove unfinished c1.md. С этим может помочь команда reset, которая не просто перемещает HEAD, но также перемещает ветку, на которую HEAD указывает.
Убедись, что текущая ветка — это main. Если почему-то это не так — надо перейти на main.
Как узнать текущую ветку в Git Bash? | в VS Code?
Как переключиться на ветку в Git Bash? | в Git Graph в VS Code?
Коммит Remove unfinished c1.md — это предыдущий коммит от текущего положения HEAD, поэтому на него можно сослаться в терминале по относительному имени HEAD^ вместо использования хэша коммита.
Обычно все же удобнее выполнять команду reset через контекстное меню графический интерфейс, ведь можно просто выбрать целевой коммит курсором.
Выполни reset в режиме hard на коммит Remove unfinished c1.md через терминал, либо через графический интерфейс.
Как выполнить reset в режиме hard на коммит в Git Bash? | в Git Graph в VS Code?
Теперь main на месте и, чтобы продолжить развивать refs-feature, надо было бы переключиться на нее. Но делать это не надо, ведь сейчас надо будет сделать новую фичу в новой ветке относительно main.
Теперь история коммитов должна выглядеть так:

Еще одна фича — еще одна ветка. В этот раз надо поправить оформление во всех разделах Гайда.
Создай новую ветку quotation-feature и переключись на нее. Кстати, это можно сделать за одну команду. Попробуй сделать именно так!
Как создать ветку на текущем коммите и переключиться на нее за одно действие в Git Bash? | в Git Graph в VS Code?
Во всех доступных md-файлах замени последовательности #### на символ >.
На примере s1.md это выглядит следующим образом.
Было:
# S1. Репозитории
#### Репозиторий — хранилище версий исходников
#### Рабочий каталог — текущая версия кода
#### Ничего, кроме исходников, не должно попадать в репозиторий
- `git init` — создать пустой репозиторий
- `git init --bare` — создать пустой репозиторий без рабочего каталога
- `git clone <url>` — склонировать репозиторий в новый каталог
- `git clone --bare <url>` — склонировать репозиторий без рабочего каталога
- `git clean -xdf` — удалить неотслеживаемые файлы из рабочего каталога
Стало:
# S1. Репозитории
> Репозиторий — хранилище версий исходников
> Рабочий каталог — текущая версия кода
> Ничего, кроме исходников, не должно попадать в репозиторий
- `git init` — создать пустой репозиторий
- `git init --bare` — создать пустой репозиторий без рабочего каталога
- `git clone <url>` — склонировать репозиторий в новый каталог
- `git clone --bare <url>` — склонировать репозиторий без рабочего каталога
- `git clean -xdf` — удалить неотслеживаемые файлы из рабочего каталога
Внеси эти изменения в s1.md и сделай аналогичные изменения в s2.md.
Закоммить изменения с сообщением Replace with quotation
Теперь история коммитов должна выглядеть так:

Когда работаешь в одиночку, можно работать в одной ветке. При работе в команде надо использовать отдельные функциональные ветки. Но если надо что-то очень быстро поправить, может быть соблазн добавить изменения сразу в основную ветку.
Переключись назад на ветку main.
Замени содержимое s2.md на следующее, в котором появляются новые утверждения:
# S2. Коммиты
#### История версий — дерево коммитов
#### Коммиты неизменны, а хэш — идентификатор коммита
#### В каждом коммите сохраняется полное состояние каталога
#### Разница между коммитами вычисляется на лету
#### Индекс коммита используется для постепенной сборки коммита
- `git add .` — добавить все измененные файлы в индекс
- `git add -p <filename>` — выбрать изменения для добавления в индекс
- `git commit -m <msg>` — сохранить содержимое индекса в репозиторий в виде коммита
- `git commit -am <msg>` — добавить изменения в уже известных файлах в индекс, затем сохранить содержимое индекса в репозиторий в виде коммита
- `git status -sb` — вывести состояние каталога и индекса кратко с указанием текущей ветки
- `git restore .` — отменить изменения каталога в соответствии с индексом
- `git restore -S .` — отменить изменения индекса в соответствии с коммитом (отмена git add .)
- `git rm <filename>` — удалить файл из каталога и индекса, чтобы перестать хранить его историю в репозитории
- `git show <commit>` — показать содержимое коммита
- `git diff <from_commit> [<to_commit>]` — вывести разницу между двумя коммитами
- `git diff --name-status <from_commit> [<to_commit>]` — список измененных файлов
- `git difftool <from_commit> [<to_commit>]` — вывести разницу с помощью difftool из настроек
Закоммить изменения с сообщением Hotfix. Add new statements to s2.md.
Открой дерево коммитов в терминале или Git Graph. Обрати внимание, что история коммитов стала похожа на дерево, а на концах веток этого дерева расположены метки main, refs-feature и quotation-feature. А вот тег v0.1, остался на своем месте.
Теперь история коммитов должна выглядеть так:

Git старается защищать от случайного удаления веток, но давай посмотрим, что будет, если все же проигнорировать предупреждения.
Чтобы эксперимент точно завершился успешно, переключись на ветку refs-feature, а затем переключись на ветку main.
Удали ветку refs-feature. Git будет сопротивляться, так что используй принудительный (усиленный) вариант.
Как принудительно удалить локальную ветку в Git Bash? | в Git Graph в VS Code?
Открой дерево коммитов в терминале или Git Graph и убедись, что ветка refs-feature и коммит из нее больше не отображаются.
Дерево коммитов в терминале можно открыть командой
git log --graph --oneline --decorate --allили с помощью алиасаgit graph
Воспользуйся журналом перемещений ссылок reflog, чтобы найти действия, связанные с веткой refs-feature. HEAD перемещался как при создании ветки refs-feature, так и при переключении на нее, так что следы определенно есть.
В действии, связанным с refs-feature, скопируй хэш коммита и переключись на этот коммит по хэшу.
После этого коммит должен стать видимым в дереве коммитов. Убедись в этом с помощью команды в терминале или Git Graph.
Как посмотреть историю перемещений HEAD в Git Bash?
Как прокручивать (скроллить) вывод команды в Git Bash?
Как выйти из режима прокрутки в Git Bash?
Как переключиться на коммит по хэшу в Git Bash?
Как использовать reflog для перехода на скрытый коммит в Git Bash?
Коммит удаленной ветки refs-feature видим, потому что на него указывает HEAD. Чтобы этот коммит снова не исчез, надо использовать более постоянную ссылку: tag или branch.
Восстанови ветку на этом коммите, но назови ее теперь иначе — rerefs-feature. Другими словами, создай ветку rerefs-feature на текущем коммите.
Переключись на ветку main, а затем убедись, что коммит Add s3.md и ветка rerefs-feature никуда не исчезли. Если все так, то поздравляю с успешным нахождением потерянного коммита!
Дополнительный вопрос: как думаешь, шаг 1 этого задания был необходим?
Теперь история коммитов должна выглядеть так:

Когда разработка новой функциональности в отдельной ветке завершается, эту функциональность можно и нужно интегрировать в основную версию. И тут помогает операция слияния. Сейчас надо влить одну из веток с новой функциональностью. И заодно научиться тому, как разрешать конфликты.
Сейчас подходящий момент, чтобы сохранить текущий прогресс по выполнению заданий. Для этого достаточно сделать копию папки ulearn-git-guide.
Рабочий каталог и репозиторий находятся внутри этой скопированной папки, так что в любой момент можно будет вернуться к сохраненному состоянию. Для этого надо либо переключиться на копию папки, заменить все содержимое папки ulearn-git-guide на содержимое папки с сохраненным состоянием.
Убедись, что HEAD находится на main, а затем начни вливать quotation-feature в main.
Так как ты знаешь весь расклад, то можешь предположить, что при слиянии веток quotation-feature и main произойдет конфликт, потому что в файл s2.md вносились правки в обеих ветках. Разрешать конфликт будешь на следующем шаге, а пока просто начти слияние.
Как начать вливать ветку в Git Bash? | в Git Graph в VS Code?
Конфликты удобно разрешать с использованием графического интерфейса. И VS Code для этого прекрасно подходит.
Открой в VS Code в боковом меню пункт Source Control. В списке Merge Changes будет файл — s2.md. Выбери его.
Часто конфликты разрешаются выбором варианта одной из веток, но это не тот случай. Придется объединять изменения аккуратно вручную.
Сначала выбери Accept Both Changes — теперь текст обоих изменений станет доступен для редактирования.
Затем напиши правильную версию раздела S2, объединяющую изменения из обеих веток. В ней должно быть 5 утверждений про коммиты, как в ветке main, и все утверждения должны начинаться с символа >, а не с последовательности символов ####, как в ветке quotation-feature.
Когда закончишь редактирование, сохрани изменения в файле s2.md. А затем добавь эту новую бесконфликтную версию файла s2.md в Commit index.
Раз были конфликты, то слияние происходит в три отдельных шага. И последний шаг — создание объединяющего коммита.
Для надежности сначала выполни в терминале команду git status. В выводе команды должно быть написано, что все конфликты разрешены.
И, наконец, создай объединяющий коммит одним из способов.
При создании объединяющего должен открыться текстовый редактор для ввода сообщения коммита. Там уже будет автоматически сгенерированный текст сообщения к коммиту. Текст хороший, его можно не менять. Так что просто закрой в редакторе открывшийся файл
COMMIT_EDITMSG, после чего коммит будет создан.
Как завершить слияние после разрешения конфликтов в Git Bash? | в VS Code?
Убедись, что в результате твоих действий был создан новый коммит, объединяющий две ветви изменений. Причем HEAD сдвинулся на этот новый коммит, а ветка main сдвинулась вслед за HEAD.
Теперь история коммитов должна выглядеть так:

Слияние не всегда является сложной операцией с разрешением конфликтов. Часто бывает простое слияние в режиме fast-forward. Добавь еще один раздел в Гайд и попробуй такой вариант слияния.
Убедись, что текущей веткой является ветка main. Это означает, что правильный коммит является текущим и можно начинать работу над новой функциональностью в новой ветке.
Создай ветку merge-feature и переключись на нее.
Создай файл a1.md со следующим содержимым:
# A1. Слияние
> Два состояния можно объединить через merge, mergetool и commit
> Участвуют три стороны: current, incoming и base
> Идентичные изменения в разных ветках не приводят к конфликтам
- `git merge <commit>` — объединить текущую ветку с другим коммитом
- `git merge --no-ff <commit>` — объединить текущую ветку с другим коммитом, причем обязательно создать новый объединяющий коммит
- `git mergetool` — разрешить имеющиеся конфликты
- `git merge --abort` — отменить слияние
Закоммить изменения с сообщением Add a1.md.
Работа над новым разделом в ветке merge-feature закончена, так что переключись на main.
Влей merge-feature в main.
Заметь, что в этот раз не только конфликтов не было, но и новый коммит не был создан!
Потому что в main не было изменений и для объединения двух веток было достаточно
передвинуть main на коммит, на который указывала merge-feature.
Теперь история коммитов должна выглядеть так:

Задание A1-3. Hidden conflict
То, что Git находит и показывает конфликты — это круто и удобно. К сожалению, не всегда конфликты бывают явными. О таких конфликтах Git не сообщит. Так что на Git надейся, а головой думай. Убедись на примере слияния ветки rerefs-feature в существовании таких скрытых конфликтов.
Начни вливать rerefs-feature в main, как и в прошлый раз.
Конфликтов в этот раз не будет, поэтому шаг разрешения конфликтов будет пропущен.
Таким образом слияние будет выполнено.
Хоть «настоящих» конфликтов нет, после слияния появился «логический» конфликт.
Дело в том, что в файле s3.md до сих пор используется последовательность символов #### перед утверждениями.
Замени эти последовательности на символы > аналогично другим файлам.
Теперь пришло время добавить в историю эти изменения. Пока ты знаешь только один способ это сделать — создать обычный коммит. А указать, что этот коммит относится к коммиту слияния, можно с помощью сообщения к коммиту.
Так что создай коммит с исправлениями из предыдущего шага прямо в ветке main с сообщением Fix after merge rerefs-feature.
Гайд хорошо развивается, все ветки объединены, поэтому отметь текущий коммит тегом v0.2. Да, еще далеко до публикации Гайда, но это хорошая веха.
Теперь история коммитов должна выглядеть так:

Amend commit — команда, которая позволяет дополнить последний коммит дополнительными изменениями. В задании про скрытый конфликт правки после слияния можно было добавить непосредственно в коммит слияния с помощью этой команды. Конечно, это был бы уже другой коммит слияния, ведь коммиты неизменны, но это уже детали.
На первый взгляд может показаться, что сейчас уже ничего не исправить — ветка rerefs-feature влита, после слияния появился коммит с сообщением Fix after merge rerefs-feature. Но с помощью команды reset в режиме soft можно сделать и не такое!
Сейчас подходящий момент, чтобы сохранить текущий прогресс по выполнению заданий. Для этого достаточно сделать копию папки ulearn-git-guide.
Рабочий каталог и репозиторий находятся внутри этой скопированной папки, так что в любой момент можно будет вернуться к сохраненному состоянию. Для этого надо либо переключиться на копию папки, заменить все содержимое папки ulearn-git-guide на содержимое папки с сохраненным состоянием.
Для начала надо «отменить» последний коммит с помощью soft reset. Напомню, что это действие в точности соответствует алиасу undo, но можешь использовать любой другой способ выполнить soft reset.
Итак, убедись, что main является текущей веткой, а коммит с сообщением Fix after merge rerefs-feature является текущим коммитом. И выполни soft reset на предыдущий коммит.
Отменить последний коммит в терминале можно командой
git reset --soft HEAD^
Чтобы отменить последний коммит в графическом интерфейсе, надо выделить предыдущий коммит, открыть контекстное меню, найти команду reset и выполнить ее. Когда графический интерфейс предоставит выбор режима hard, mixed или soft, надо выбрать soft.
После предыдущего шага в индексе уже находится та версия Гайда, которая должна была быть в коммите слияния. Поэтому остается только дополнить коммит слияния с помощью amend commit.
Если говорить максимально точно, остается заменить коммит слияния на новый коммит, который будет содержать версию Гайда из Commit index, но при этом сохранит связи с родителями текущего коммита слияния. Именно это и сделает amend commit.
Итак, выполни в терминале команду git commit --amend --no-edit. В этом варианте команды не придется писать новое сообщение к коммиту, потому что автоматически будет использовано сообщение из коммита слияния.
Убедись, что в результате твоих действий в дереве коммитов появился ровно один новый коммит, объединяющий две ветви изменений.
При этом старый коммит слияния и коммит с сообщением Fix after merge rerefs-feature должны быть до сих пор видны. Ты же понимаешь, почему они до сих пор видны?
Теперь история коммитов должна выглядеть так:

Команда revert отмены коммитов пригождается, когда в коммите есть ошибка, но по каким-то причинам нельзя этот коммит пересобрать с помощью amend commit или soft reset. Давай искусственно создадим такую ситуацию и героически ее преодолеем с помощью revert. И, заодно, добавим еще один раздел в Гайд!
Первым делом потребуется ошибка в ветке main!
Замени следующее корректное утверждение из файла a1.md:
> Идентичные изменения в разных ветках не приводят к конфликтам
на такое ошибочное утверждение:
> Идентичные изменения в разных ветках ПРИВОДЯТ к конфликтам
Закоммить это изменение с сообщением Hotfix. Change a1.md.
Теперь корректное добавление нового раздела Гайда. Тут все знакомо, так что далее максимально краткие инструкции.
Создай ветку undoing-feature и переключись на нее.
Создай файл a2.md со следующим содержимым:
# A2. Пересоздание коммитов
> Нельзя изменить коммит — можно только пересоздать
- `git commit --amend` — заменить последний коммит ветки на коммит с дополнительными изменениями и задать новое сообщение к коммиту
- `git commit --amend --no-edit` — заменить последний коммит ветки на коммит с дополнительными изменениями, но с тем же сообщением
- `git reset --hard <commit>` — переместить текущую ветку на `<commit>`, задать индекс и каталог по целевому коммиту `<commit>`
- `git reset <commit>` = `git reset --mixed <commit>` — переместить текущую ветку на `<commit>`, переместить текущую версию каталога, задать индекс по целевому коммиту `<commit>`
- `git reset --soft <commit>` — переместить текущую ветку на `<commit>`, переместить текущие версии каталога и индекса
- `git reset --soft HEAD^1` — отменить последний коммит
- `git revert <commit>` — создать коммит, отменяющий изменения из коммита
- `git revert <commit> -m <parent_number>` — создать коммит, отменяющий изменения одной из объединенных коммитом слияния веток, причем обычно 1 — основная ветка до слияния, 2 — ветка, которая была влита.
Закоммить изменения с сообщением Add a2.md.
Совершенно неожиданно стало очевидно, что Идентичные изменения в разных ветках ПРИВОДЯТ к конфликтам — это некорректное утверждение и надо его исправить!
Переключись на ветку main.
Откати коммит Hotfix. Change a1.md с помощью revert. При этом будет автоматически сгенерировано сообщение к коммиту. Это сообщение подходит.
Откатить коммит с помощью revert в терминале можно командой
git revert <commit>
Чтобы откатить коммит в графическом интерфейсе, надо выделить коммит, открыть контекстное меню, найти команду revert и выполнить ее.
В ветке main все корректно, а работа на новым разделом Гайда в ветке undoing-feature завершена. Можно объединить изменения и отметить новую версию тегом!
Влей ветку undoing-feature в ветку main.
Отметь коммит слияния легковесным тегом v0.3.
Теперь история коммитов должна выглядеть так:

Ревью, т.е. просмотр изменений самостоятельно или другими разработчиками — полезная практика, которая позволяет раньше обнаруживать ошибки. Полезно просматривать вносимые изменения перед коммитом, полезно просить других разработчиков смотреть изменения функциональной ветки перед ее влитием в основную.
Изменения можно смотреть отдельно в каждом коммите, но часто удобно смотреть все изменения функциональной ветки сразу. С помощью Git и надстройками над ним это можно делать по-разному.
Давай посмотрим, как посмотреть все изменения, выполненные между некоторыми версиями с помощью soft reset. Например, изменения между версиями v0.1 и v0.3.
Создай ветку review на коммите, отмеченным тегом v0.3 и переключись на нее.
Выполни soft reset на тег v0.1.
Как выполнить soft reset в Git Bash? | в Git Graph в VS Code?
Все, что нужно уже есть в Commit index, так что просто выполни коммит с сообщением Review from v0.1 to v0.3.
Какая ситуация сейчас? Текущий коммит содержит версию Гайда, соответствующую v0.3, и для удобства отмечен веткой review. При этом родителем текущего коммита является коммит, отмеченный тегом v0.1.
В графических интерфейсах и терминале легко увидеть разницу между текущим коммитом и его родителем, а сейчас это как раз разница между версиями v0.3 и v0.1. Так что можно делать ревью, причем при необходимости ветку review можно опубликовать для других разработчиков. Цель достигнута!
После ревью ждут другие свершения, так что вернись на ветку main.
Теперь история коммитов должна выглядеть так:

Когда разработка новой фунциональности в ветке происходит довольно долго, бывает полезно получать свежие правки из основной ветки. Это один из сценариев, где пригождается команда rebase.
Посмотрим, что произойдет если во время добавления нового раздела Гайда в отдельной ветке появится коммит с правками в основной ветке.
Сейчас подходящий момент, чтобы сохранить текущий прогресс по выполнению заданий. Для этого достаточно сделать копию папки ulearn-git-guide.
Рабочий каталог и репозиторий находятся внутри этой скопированной папки, так что в любой момент можно будет вернуться к сохраненному состоянию. Для этого надо либо переключиться на копию папки, заменить все содержимое папки ulearn-git-guide на содержимое папки с сохраненным состоянием.
Создай ветку picking-feature и переключись на нее.
Создай файл a3.md со следующим содержимым:
# A3. Перенос изменений
> Получить отдельные правки из другой ветки — cherry-pick
> Перенести локальные изменения в новое место — stash
> Переместить коммиты для выстраивания истории в линию — rebase
> Схлопнуть коммиты в один — интерактивный rebase со squash или fixup
- `git rebase <newbase>` — применить все коммиты от общего родителя до текущего коммита к `<newbase>`
- `git rebase --autostash <newbase>` — сохранить локальные изменения, выполнить rebase, а затем восстановить локальные изменения
- `git rebase -i <newbase>` — применить заново все коммиты, указав действие с каждым коммитом
- `git rebase --continue` — продолжить rebase после разрешения конфликтов
- `git rebase --abort` — отменить rebase
Закоммить изменения с сообщением Add a3.md.
Пока picking-feature находится в разработке, надо выполнить быструю правку в ветке main!
Переключись на ветку main.
Замени содержимое s3.md на следующее, в котором исправляется описание команды создания тега:
# S3. Ссылки
> HEAD — текущая ссылка, tag — фиксированная ссылка, branch — движущаяся за HEAD ссылка
> checkout — перемещение на ветку или коммит, reset — перемещение с веткой на коммит
> Видно то, на что есть ссылки, остальное — мусор
- `git tag` — вывести список тегов
- `git tag <tagname>` — создать обычный тег
- `git tag -d <tagname>` — удалить тег
- `git branch --sort=-committerdate` — вывести список локальных веток от новых к старым
- `git branch <branchname>` — создать ветку
- `git branch -f <branchname> <to_commit>` — создать или переместить ветку
- `git branch -M <newbranch>` — переименовать текущую ветку, даже если новое имя уже занято
- `git branch -d <branchname>` — удалить ветку
- `git branch -D <branchname>` — удалить ветку, даже если какие-то коммиты будут скрыты
- `git checkout -d <commit>` или `git switch --detach <commit>` — переместить HEAD на коммит, причем получится detached HEAD
- `git checkout <branch>` или `git switch <branch>` — переместить HEAD на ветку
- `git checkout -b <new_branch>` = `git checkout -b <new_branch> HEAD` или `git switch -c <new_branch>` — создать ветку и перейти на нее
- `git reset --hard <commit>` — переместить HEAD и текущую ветку на `<commit>`
- `git log --oneline --decorate --graph` — вывести историю коммитов от HEAD в виде дерева
- `git log --oneline --decorate --graph --all` — вывести историю коммитов от всех ссылок в виде дерева
- `git log --oneline --decorate --graph --all --reflog` — вывести историю коммитов от всех ссылок и всех недавних положений HEAD в виде дерева
- `git reflog show <ref>` — показать лог действий со ссылкой
- `git reflog` = `git reflog show HEAD` — показать лог действий с HEAD
- `git gc` — удалить ненужные файлы и оптимизировать локальный репозиторий
- `<ref>~N` — 1-ый родитель из N-ого поколения, например, `HEAD~3`
- `<ref>^N` — N-ый родитель предыдущего поколения, например, `HEAD^1^2`
- `<ref>@{N}` — N-ый предшественник по reflog, например, `HEAD@{5}`
Закоммить изменения с сообщением Hotfix. Change tag command description in s3.md.
Переключись назад на ветку picking-feature.
Замени содержимое s3.md на следующее, в котором появляется команда создания аннотированного тега:
# S3. Ссылки
> HEAD — текущая ссылка, tag — фиксированная ссылка, branch — движущаяся за HEAD ссылка
> checkout — перемещение на ветку или коммит, reset — перемещение с веткой на коммит
> Видно то, на что есть ссылки, остальное — мусор
- `git tag` — вывести список тегов
- `git tag <tagname>` — создать легковесный тег
- `git tag -a <tagname>` — создать аннотированный тег
- `git tag -d <tagname>` — удалить тег
- `git branch --sort=-committerdate` — вывести список локальных веток от новых к старым
- `git branch <branchname>` — создать ветку
- `git branch -f <branchname> <to_commit>` — создать или переместить ветку
- `git branch -M <newbranch>` — переименовать текущую ветку, даже если новое имя уже занято
- `git branch -d <branchname>` — удалить ветку
- `git branch -D <branchname>` — удалить ветку, даже если какие-то коммиты будут скрыты
- `git checkout -d <commit>` или `git switch --detach <commit>` — переместить HEAD на коммит, причем получится detached HEAD
- `git checkout <branch>` или `git switch <branch>` — переместить HEAD на ветку
- `git checkout -b <new_branch>` = `git checkout -b <new_branch> HEAD` или `git switch -c <new_branch>` — создать ветку и перейти на нее
- `git reset --hard <commit>` — переместить HEAD и текущую ветку на `<commit>`
- `git log --oneline --decorate --graph` — вывести историю коммитов от HEAD в виде дерева
- `git log --oneline --decorate --graph --all` — вывести историю коммитов от всех ссылок в виде дерева
- `git log --oneline --decorate --graph --all --reflog` — вывести историю коммитов от всех ссылок и всех недавних положений HEAD в виде дерева
- `git reflog show <ref>` — показать лог действий со ссылкой
- `git reflog` = `git reflog show HEAD` — показать лог действий с HEAD
- `git gc` — удалить ненужные файлы и оптимизировать локальный репозиторий
- `<ref>~N` — 1-ый родитель из N-ого поколения, например, `HEAD~3`
- `<ref>^N` — N-ый родитель предыдущего поколения, например, `HEAD^1^2`
- `<ref>@{N}` — N-ый предшественник по reflog, например, `HEAD@{5}`
Закоммить изменения с сообщением Change s3.md.
Теперь хочется получить последние правки из ветки main в ветку picking-feature.
Чтобы лучше понять как отработает rebase, установи легковесный тег old-picking-feature на текущий коммит.
Выполни в терминале rebase picking-feature на main: HEAD уже находится на picking-feature, так что просто выполни команду git rebase main.
При rebase возникнет конфликт.
Первый коммит Add a3.md успешно скопирован, а вот Change s3.md по понятным причинам порождает конфликты.
Открой в VS Code в боковом меню пункт Source Control и найди там файл s3.md. В этом файле есть конфликт, но его легко устранить. Так как в ветке picking-feature был правильный текст, нажми Accept Incoming Change и сохрани файл.
Добавь s3.md в Commit index и выполни команду git rebase --continue.
Раз оба коммита были успешно скопированы, rebase на этом будет завершен. Фанфар по этому поводу не будет, в терминале просто появится об успешном выполнении rebase.
Обрати внимание, что в результате rebase были созданы новые коммиты Add a3.md и Change s3.md. Хоть они похожи на исходные, все же это новые коммиты с новыми хэшами.
Также обрати внимание, что ветка picking-feature была перемещена и теперь ссылается на новый коммит. Старые коммиты остались в репозитории, а на последний из них все еще ссылается тег old-picking-feature.
Теперь история коммитов должна выглядеть так:

Бывает, что прямо посреди работы над какой-то функциональностью приходится переключиться на другую работу. В этом случае stash помогает временно сохранить незаконченные изменения, а затем вернуться к ним.
Текущей веткой должна быть picking-feature. Если это не так — переключись на нее.
Работа над разделом A3 продолжается.
Добавь в файл a3.md описание вариантов команды cherry-pick. В итоге должно получиться так:
# A3. Перенос изменений
> Получить отдельные правки из другой ветки — cherry-pick
> Перенести локальные изменения в новое место — stash
> Переместить коммиты для выстраивания истории в линию — rebase
> Схлопнуть коммиты в один — интерактивный rebase со squash или fixup
- `git cherry-pick <commit>` — применить изменения из указанного коммита к HEAD и сделать коммит
- `git cherry-pick -n <commit>` — применить изменения из указанного коммита, оставив их в индексе
- `git rebase <newbase>` — применить все коммиты от общего родителя до текущего коммита к `<newbase>`
- `git rebase --autostash <newbase>` — сохранить локальные изменения, выполнить rebase, а затем восстановить локальные изменения
- `git rebase -i <newbase>` — применить заново все коммиты, указав действие с каждым коммитом
- `git rebase --continue` — продолжить rebase после разрешения конфликтов
- `git rebase --abort` — отменить rebase
Сделай коммит с сообщением Add cherry-pick command to a3.md.
Далее добавь в файл a3.md описание вариантов команды stash. В итоге должно получиться так:
# A3. Перенос изменений
> Получить отдельные правки из другой ветки — cherry-pick
> Перенести локальные изменения в новое место — stash
> Переместить коммиты для выстраивания истории в линию — rebase
> Схлопнуть коммиты в один — интерактивный rebase со squash или fixup
- `git cherry-pick <commit>` — применить изменения из указанного коммита к HEAD и сделать коммит
- `git cherry-pick -n <commit>` — применить изменения из указанного коммита, оставив их в индексе
- `git stash push -u` — сохранить все изменения в отслеживаемых файлах и новые файлы в виде набора изменений
- `git stash pop` — восстановить последний сохраненный набор изменений и удалить его из списка
- `git stash list` — показать список сохраненных наборов изменений
- `git stash apply --index <stash>` — применить конкретный сохраненный набор изменений
- `git stash drop --index <stash>` — удалить конкретный сохраненный набор изменений
- `git rebase <newbase>` — применить все коммиты от общего родителя до текущего коммита к `<newbase>`
- `git rebase --autostash <newbase>` — сохранить локальные изменения, выполнить rebase, а затем восстановить локальные изменения
- `git rebase -i <newbase>` — применить заново все коммиты, указав действие с каждым коммитом
- `git rebase --continue` — продолжить rebase после разрешения конфликтов
- `git rebase --abort` — отменить rebase
А вот тут происходит неприятное! Надо прерваться, чтобы быстро внести правки в ветке main.
Прежде всего сохрани все локальные изменения в stash через терминал git stash -u.
Теперь можно заняться правками в ветке main!
Переключись на ветку main.
Замени содержимое a2.md на следующее, в котором появляются новые утверждения:
# A2. Пересоздание коммитов
> Нельзя изменить коммит — можно только пересоздать
> Дополнить или переименовать коммит — commit --amend
> Разобрать коммиты — reset --soft
> Отменить коммит другим коммитом — revert
- `git commit --amend` — заменить последний коммит ветки на коммит с дополнительными изменениями и задать новое сообщение к коммиту
- `git commit --amend --no-edit` — заменить последний коммит ветки на коммит с дополнительными изменениями, но с тем же сообщением
- `git reset --hard <commit>` — переместить текущую ветку на `<commit>`, задать индекс и каталог по целевому коммиту `<commit>`
- `git reset <commit>` = `git reset --mixed <commit>` — переместить текущую ветку на `<commit>`, переместить текущую версию каталога, задать индекс по целевому коммиту `<commit>`
- `git reset --soft <commit>` — переместить текущую ветку на `<commit>`, переместить текущие версии каталога и индекса
- `git reset --soft HEAD^1` — отменить последний коммит
- `git revert <commit>` — создать коммит, отменяющий изменения из коммита
- `git revert <commit> -m <parent_number>` — создать коммит, отменяющий изменения одной из объединенных коммитом слияния веток, причем обычно 1 — основная ветка до слияния, 2 — ветка, которая была влита.
Закоммить изменения с сообщением Hotfix. Add new statements to a2.md.
Наконец, можно вернуться к ветке picking-feature!
Переключись на ветку picking-feature.
А затем верни изменения из stash. Для этого выполни в терминале команду git stash apply.
Можно было бы продолжить вносить правки в раздел A3, но похоже там все уже хорошо.
Так что просто сделай коммит с сообщением Add stash command to a3.md.
Теперь история коммитов должна выглядеть так:

Rebase не только помогает актуализировать функциональные ветки. Эту команду еще можно использовать влития изменений функциональной ветки в основную.
Это как раз можно попробовать на ветке picking-feature.
Текущей веткой должна быть picking-feature. Если это не так — переключись на нее.
Работа над веткой picking-feature завершена, так что перебазируй ее на последний коммит в ветке main. Конфликтов быть не должно.
После перебазирования ветка picking-feature указывает на коммит со всеми изменениями, а вот main отстает. Значит надо переместить main вперед.
Для этого переключись на ветку main и влей в нее ветку picking-feature. Слияние пройдет в режиме fast-forward.
Тег old-picking-feature был добавлен, чтобы разобраться как работает rebase. Теперь он больше не нужен — удали его.
Как удалить локальный тег в Git Bash? | в Git Graph в VS Code?
Также больше не нужны изменения, сохраненные в stash — удали их.
Как удалить изменения из stash после применения в Git Bash?
На текущий коммит, содержащий очередную порцию улучшений Гайда, добавь тег v0.4.
Сравни, насколько разветвленным было дерево коммитов ранее и насколько линейным оно стало при использовании rebase.
Теперь история коммитов должна выглядеть так:

Репозиторий, в котором ты создаешь Гайд, был склонирован с репозитория из организации kontur-courses на GitHub. Поэтому у тебя нет на него прав.
Создай форк, собственную копию репозитория в рамках GitHub, и переключи локальный репозиторий на него, чтобы в будущем иметь возможность опубликовать свои наработки по Гайду.
Сейчас подходящий момент, чтобы сохранить текущий прогресс по выполнению заданий. Для этого достаточно сделать копию папки ulearn-git-guide.
Рабочий каталог и репозиторий находятся внутри этой скопированной папки, так что в любой момент можно будет вернуться к сохраненному состоянию. Для этого надо либо переключиться на копию папки, заменить все содержимое папки ulearn-git-guide на содержимое папки с сохраненным состоянием.
Открой в GitHub репозиторий https://github.com/kontur-courses/ulearn-git-guide и сделай его fork в свой профиль на GitHub.
Найди SSH-адрес получившегося форка и скопируй в буфер обмена.
В терминале задай новый адрес для origin с помощью команды git remote set-url origin <new_url>, где вместо <new_url>подставь скопированный SSH-адрес репозитория.
С помощью команды git remote -v выведи список известных удаленных репозиториев и убедись, что имени origin соответствует SSH-адрес твоего форка.
Обрати внимание, что в этом адресе должен фигурировать твой ник на GitHub. Если ника нет, то что-то пошло не так: убедись, что получилось создать форк, скопировать правильный адрес и задать его в команде git remote set-url.
Репозиторий может обмениваться версиями с множеством других удаленных репозиториев. Давай добавим еще один удаленный репозиторий, в котором ведется разработка Гайда, и получим коммиты из него.
Добавь новый репозиторий https://github.com/kontur-courses/ulearn-git-guide-ext для синхронизации:
git remote add ext git@github.com:kontur-courses/ulearn-git-guide-ext.git
Убедись, что удаленный репозиторий был добавлен: git remote -v
Выполни в терминале команду git fetch ext, чтобы получить коммиты из добавленного удаленного репозитория.
Убедись, что в истории появилась ветка ext/guide-feature из удаленного репозитория,
а также несколько новых коммитов
Теперь история коммитов должна выглядеть так:

Ветка guide-feature нового удаленного репозитория содержит много полезных изменений.
Их хочется перенести в основную ветку, но сразу же объединить в один коммит.
Это удобно сделать с помощью интерактивного rebase.
Переключись обычным образом на ветку guide-feature, полученную из нового репозитория.
Git автоматически создаст локальную ветку с именем guide-feature, которая будет указывать на тот же коммит, что и ext/guide-feature.
Выполни интерактивный rebase guide-feature на main: git rebase -i main, после чего откроется текстовый редактор.
В текстовом редакторе описан сценарий действий для rebase. Сейчас он заключается в том, что надо взять (pick)
и переместить на новое место все коммиты последовательно: сначала первый, затем второй и т.д. Все как обычно.
Ниже сценария приведены комментарии по возможным действиям с коммитами.
Прочитай, что делает reword, squash и fixup.
В первой строчке файла замени pick на reword, а последующих строчках замени pick на fixup.
Сохрани изменения и закрой файл со сценарием. После этого сценарий начнет выполняться.
Сразу же редактор откроется снова, потому что команде reword требуется новое сообщение для коммита.
В открывшемся редакторе замени текущее сообщение Add guide markup на новое сообщение Add guide extensions и закрой редактор.
Убедись, что ветка guide-feature теперь ссылается на новый коммит с названием Add guide extensions.
А внутри этого коммита объединены все изменения скопированных коммитов.
Переключись на ветку main и влей в нее изменения из guide-feature. Влитие получится в режиме fast-forward.
Теперь история коммитов должна выглядеть так:

Когда из какой-то ветки хочется забрать только один коммит, удобно использовать cherry-pick. Достань с помощью cherry-pick еще один полезный коммит из нового удаленного репозитория.
Надо достать для ветки main изменения из коммита Add runner из ветки solved.
В этом случае нужен только один коммит, который находится между другими — значит подойдет cherry-pick.
Достань в main эту вишенку с помощью хэша, тега runner или графического интерфейса.
Как достать одиночный коммит из другой ветки с помощью cherry-pick в Git Bash? | в Git Graph в VS Code?
Убедись, что в ветке main появилась копия коммита Add runner.
Добавь тег v0.5 на текущий коммит.
Теперь история коммитов должна выглядеть так:

Наконец пришло время отправлять наработки по Гайду в твой репозиторий на GitHub. Добавь новый раздел и отправь основную ветку на GitHub.
Создай новый файл r2.md со следующим содержимым:
# R2. Отправка версий
> Любое изменение в удаленном репозитории — это push
> Обычный push будет успешным, если локальная ветка является потомком удаленной ветки
> Нельзя делать force push with lease в общие ветки, а force push вообще делать нельзя
- `git push <remote> <local_branch>:<remote_branch>` — отправить коммиты из локальной ветки `<local_branch>` в удаленный репозиторий, а затем переместить ветку `<remote_branch>` на коммит, соответствующий `<local_branch>`
- `git push` = `git push origin HEAD` — отправить коммиты из текущей локальной ветки в основной удаленный репозиторий, а затем переместить соответствующую ветку удаленного репозитория на коммит, на который указывает текущая локальная ветка
- `git push -f` — выполнить push, даже если локальная ветка не является потомком удаленной ветки
- `git push --force-with-lease` — выполнить push, даже если локальная ветка не является потомком удаленной ветки, но при условии, что удаленная ветка не сдвигалась (всегда использовать вместо предыдущей команды)
- `git push --all <remote>` — отправить все локальные ветки в удаленный репозиторий
- `git push <remote> -d <branch|tag>` — удалить ветку или тег в удаленном репозитории
- `git push <remote> tag <tag>` — отправить тег в удаленный репозиторий
- `git push <remote> --tags` — отправить все локальные теги в удаленный репозиторий
- `git push --mirror` — выполнить агрессивный push для всех тегов, веток и HEAD, подходит для создания удаленной копии локального репозитория
Закоммить изменения с сообщением Hotfix. Add r2.md.
Сделай push локальной ветки main в main из origin с помощью git push без каких-либо опций.
Теперь история коммитов должна выглядеть так:

Главная ветка отправлена, но это не все, что есть в локальном репозитории. Чтобы разом отправить все наработки в удаленный репозиторий есть зеркальный push. Однократно это стоит сделать.
Добавь тег v0.6 на текущий коммит.
Отправь все коммиты, ветки и теги в удаленный репозиторий origin с помощью команды git push --mirror origin.
Теперь история коммитов должна выглядеть так:

Новые ветки надо пушить в удаленный репозиторий несколько иначе. Добавь новый раздел в новой ветке, а затем отправь эту ветку на GitHub.
Создай ветку tracking-feature и переключись на нее.
Создай новый файл r3.md со следующим содержимым:
# R3. Сопоставление веток
> Сопоставление локальных и удаленных веток бывает либо по имени ветки, либо по настройкам отслеживания у локальных веток
> Сопоставление веток обеспечивает работу push без параметров
> Сопоставление веток необходимо для выставления ссылки FETCH_HEAD после fetch, которая используется в команде pull
Закоммить изменения с сообщением Add r3.md
Попробуй сделать обычный git push без параметров этой ветки.
Увы, это не сработает, потому что ветки в удаленном репозитории еще нет.
Чтобы она появилась, а также локальная ветка стала ее отслеживать, нужно выполнить команду git push -u origin tracking-feature:tracking-feature. Но Git поймет и более краткие варианты: git push -u origin tracking-feature или git push -u origin HEAD.
Выполни push любым из этих способов.
Теперь история коммитов должна выглядеть так:

Исправлять коммиты, опубликованные в удаленном репозитории, сложнее, чем неопубликованные. Тут уже не обойтись без force push with lease.
Текущей веткой должна быть tracking-feature. Если это не так — переключись на нее.
Замени содержимое r3.md на следующее:
# R3. Сопоставление веток
> Сопоставление локальных и удаленных веток бывает либо по имени ветки, либо по настройкам отслеживания у локальных веток
> Сопоставление веток обеспечивает работу push без параметров
> Сопоставление веток необходимо для выставления ссылки FETCH_HEAD после fetch, которая используется в команде pull
- `git branch -vv` — вывести список локальных веток с указанием удаленных веток, которые отслеживаются этими локальными ветками
- `git branch -u <remote>/<remote_branch>` — задать текущей локальной ветке новую вышестоящую ветку, т.е. удаленную ветку, которую эта локальная ветка будет отслеживать
- `git branch --unset-upstream` — удалить отслеживание у текущей локальной ветки
- `git push -u origin HEAD` — создать удаленную ветку, соответствующую локальной, сделать так, чтобы локальная ветка отслеживала удаленную ветку, затем добавить изменения из локальной ветки в удаленный репозиторий
- `git checkout <remote_branch>` — создать локальную ветку, отслеживающую удаленную ветку, затем переместить HEAD на нее
- `git pull` = `git pull origin` — получить содержимое основного удаленного репозитория, а затем влить изменения из удаленной ветки в соответствующую локальную ветку
- `git pull --ff-only` — получить содержимое, а затем влить, если возможен fast-forward merge
- `git pull --rebase` — получить содержимое, а затем выполнить rebase локальной ветки на удаленную ветку
- `git pull --rebase --autostash` — сохранить локальные изменения, получить содержимое, выполнить rebase локальной ветки на удаленную ветку, применить сохраненные изменения
- `git config --global push.default simple` — задать simple-режим сопоставления веток при push. Это режим по умолчанию в Git 2.0 и выше
- `git config --global push.autoSetupRemote true` — автонастройка отслеживания при push новых веток
- `<branch>@{u}` — вышестоящая ветка для `<branch>`, например, `main@{u}`
- `HEAD@{u}` = `@{u}` — вышестоящая ветка для текущей ветки
Добавь изменения в Commit index, а затем выполни amend commit, чтобы не создавать лишний коммит.
Как сделать amend commit в Git Bash? | в VS Code?
Обрати внимание, что старый коммит остался видимым, ведь на него ссылается origin/main
Если сейчас выполнить push, то он завешится ошибкой, т.к. коммит, на который ссылается main, не является потомком коммита, на который ссылается origin/main.
Поэтому выполни push с опцией force-with-lease: git push --force-with-lease.
Теперь история коммитов должна выглядеть так:

Гайд почти закончен! Остается объединить последнюю ветку с основной, добавить тег и отправить все на GitHub!
Переключись на ветку main.
Влей ветку tracking-feature, но сделай это в режиме запрета fast-forward, чтобы появился коммит слияния.
Как влить ветку с обязательным созданием коммита, даже если возможен fast-forward в Git Bash? | в Git Graph в VS Code?
Отправь ветку main в удаленный репозиторий.
Добавь тег v1.0 на текущий коммит и отправь его в удаленный репозиторий.
Как отправить отдельный тег в удаленный репозиторий в Git Bash?









