Skip to content

Latest commit

 

History

History
238 lines (200 loc) · 25.1 KB

lab5.md

File metadata and controls

238 lines (200 loc) · 25.1 KB
title author fontsize geometry paper lang template
Лабораторная работа 5
А.В. Родионов
14pt
margin=2cm
a4
russian
default.html

Вспомогательные инструменты Git

Ссылки на коммиты

Контрольная сумма (хэш) SHA-1 является стандартным способом идентификации коммита в истории Git. Как правило, достаточно указать ее первые несколько знаков, при условии, что они не дублируются с другими хэшами в базе и их количество не менее четырех. Просмотреть хэши коммитов в укороченном виде можно командой git log --abbrev-commit.

Ветки и тэги являются указателями на коммит, поэтому все команды, которые принимают хэш, также могут принимать имя ветки или тэга. Узнать хэш, на которую указывает ветка или тэг, можно командой git show <имя указателя> или, в более компактном виде, git rev-parse <имя указателя>.

Все изменения веток и указателя HEAD сохраняются в журналах, которые также можно использовать для навигации по коммитам. Журнал можно просмотреть командой git relog, записи в нем имеют вид <имя указателя>@{номер} и позволяют сослаться не на номер коммита, а на этап в истории ветки. В качестве номера можно использовать дату и время, в том числе относительные. Например, команда git show master@{yesterday} покажет состояние ветки master на день раньше текущего времени. Журналы изменений для веток носят локальный характер, они не переносятся между копиями репозитория при клонировании. По умолчанию, записи в них хранятся не более 90 дней.

Указатель коммита может принимать суффикс-модификатор ^, который означает, что ссылка идет не на сам коммит, а на его родителя. Например, git show HEAD^ покажет не последний коммит ветки, а идущий непосредственно перед ним. После ^ можно указать номер родительского коммита, если их было несколько, т.е. коммит был результатом множественного слияния. Например git show HEAD^2 сошлется на второго непосредственного родителя HEAD, если он у него есть. Несколько ^ подряд ссылаются на родителей родителей и т.д. Например, чтобы показать третий коммит от текущего, можно воспользоваться командой git show HEAD^^^. Чтобы не указывать длинные цепочки из символа ^, можно воспользоваться символом ~ и номером. Ссылка HEAD~3 указывает на тот же коммит, что и HEAD^^^.

Такие команды, как git log и git diff позволяют просматривать подмножество коммитов (диапазон), которое задается ссылками на коммит, разделенными двумя точками (..). Например, команда git log origin/master..HEAD означает, что Git должен выбрать все коммиты, доступные (являющиеся предками) указателю HEAD, но недоступные из указателя origin/master. В результате, можно увидеть, что добавилось в текущей ветке по сравнению с origin/master. Запрос на диапазон коммитов можно делать и без двух точек, просто перечисляя указатели в командной строке. Чтобы отсечь коммиты, недоступные из какого-либо указателя, перед ним ставится символ ^ или ключ --not. Список коммитов из предыдущего примера можно вывести командой git log ^origin/master HEAD или git log HEAD --not origin/master. Число указателей в командной строке не ограничено двумя, благодаря чему можно составлять достаточно сложные запросы к веткам.

Три точки между ссылками на коммиты означают, что нужно выбрать те из них, которые доступны из того или иного указателя, но не из обоих сразу. Например, можно посмотреть, в каких изменениях разошлись ветки master и feature командой git log master...feature. В этом случае, также может быть полезен ключ --left-right, который добавляет к записям журнала подсказку, символ < или >. С ее помощью можно увидеть, к какой из веток относится изменение.

Команды git reset и git checkout

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

  • ветку, на которую указывает HEAD
  • индекс изменений, которые войдут в следующий коммит
  • рабочее дерево проекта

В зависимости от указанных в командной строке параметров, команда git reset работает в одном из следующих режимов:

Режим --soft сбрасывает указатель ветки, на которую в данный момент указывает HEAD, в состояние, соответствующее переданному в командной строке коммиту. При этом не затрагивается индекс и рабочее дерево проекта, поэтому все изменения, сохраненные в индексе и еще не добавленные в него, могут образовать один или более новых коммитов. На практике, сброс в режиме --soft применяют, когда нужно заменить несколько последних коммитов в ветке на один. Для этого командой git log находят хэш последнего коммита, который не будет затронут, а затем выполняют git reset --soft <хэш найденного коммита>. Изменения, которые шли в истории коммитов за ним, будут отброшены, а текущее состояние дерева проекта можно будет добавить в индекс и сформировать коммит. Фактически, этот коммит будет представлять собой все изменения, которые накопились после выбранного, и заменит их собой в истории коммитов. Команда git commit --amend производит аналогичные действия, но только с последним коммитом в текущей ветке.

Режим --mixed сбрасывает не только указатель текущей ветки, но и состояние индекса в указанный в командной строке коммит. Если коммит не задан, то будет выбран последний на вершине текущей ветки (т.е. HEAD). Таким образом, команда git reset --mixed или, по умолчанию, просто git reset, отменяет действие предыдущих команд git add, очищая индекс, но не затрагивая других хранилищ проекта. В этом режиме, команду можно вызывать с именем файла или директории проекта. При этом указатель ветки не будет сдвинут, но состояние заданных файлов в индексе будет приведено в соответствие с заданным коммитом. Например, чтобы отменить добавление в индекс файла file.txt, достаточно выполнить git reset file.txt.

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

Команды git checkout <ветка или коммит> и git reset --hard <ветка или коммит> действуют похожим образом в том смысле, что рабочее дерево проекта приводится в состояние, соответствующее коммиту. Однако, в отличие от reset, git checkout не передвигает указатель ветки, а только HEAD, что позволяет не потерять коммиты, образующие ветку. Кроме того, если в дереве проекта имеются изменения, производится попытка их слияния с состоянием выбранного коммита.

Если передать команде git checkout файл или директорию, результат ее выполнения затронет как индекс, так и рабочее дерево. Содержимое файлов будет сброшено в заданное состояние, изменения утеряны как из рабочего дерева, так и из индекса, но указатель ветки и HEAD останутся прежними.

Команда git stash и "тайники"

При работе с проектом, часто возникает необходимость временно переключиться на какой-нибудь другой коммит или ветку. Если на этот момент в рабочем дереве проекта имеются изменения, они будут утеряны, что может быть нежелательно. Один из вариантов -- сформировать из них промежуточный коммит и сохранить его в ветке, а затем переключиться на новую. Изменения при этом сохранятся, но в истории коммитов появится лишняя запись, от которой впоследствии придется избавляться командой git commit --amend или git reset --soft.

Чтобы избежать ненужных вмешательств в историю коммитов, Git предоставляет команду git stash и механизм "тайников". Пусть в рабочем дереве проекта есть изменения, не добавленные в коммит. Для их сохранения нужно выполнить команду git stash save или просто git stash, в результате которой индекс и изменения в рабочем дереве будут сохранены в специальном хранилище-тайнике. Дерево проекта и индекс, при этом, окажутся сброшенными в вершину ветки. Затем, можно будет свободно сменить ветку и начать работу над ней. Если изменения потребуется вернуть из тайника, достаточно выполнить команду git stash apply. Причем, сохранить изменения можно в одной ветке, а применить их впоследствии -- к другой. По умолчанию, команда git stash apply не возвращает индекс в исходное состояние, а предоставляет пользователю самому добавлять в него файлы. Если это нежелательно, ее можно вызывать с ключом --index. Наоборот, если вызвать git stash save --keep-index, то в тайник сохранятся только непроиндексированные изменения. Это дает возможность сформировать коммит из текущего индекса, а несохраненные изменения записать в тайник, чтобы вернуться к ним позже. Ключ --include-untracked позволяет также спрятать в тайник файлы, которые есть в рабочем дереве, но не отслеживаются репозиторием.

Тайники организованы в виде стека, в который команда git stash добавляет очередную запись на вершину. Команда git stash pop не только применяет изменения из последнего тайника, но и удаляет его с вершины стека. Помимо собственно изменений, тайник содержит ссылку на коммит, от которого он был вызван. Благодаря этому, командой git stash branch <имя ветки> можно выполнить сразу несколько операций: создать ветку от сохраненного в тайнике коммита, применить к ней изменения из тайника и удалить тайник из стека.

Командой git stash list можно просмотреть состояние стека и найти идентификаторы тайников, которые имеют вид stash@{номер}. Просмотреть тайник можно командой git stash show <идентификатор>. Также по идентификатору можно применить произвольный тайник из стека командой git stash apply <идентификатор> или git stash branch <имя ветки> <идентификатор>. Удалить один из тайников можно git stash drop <идентификатор>, а полностью очистить стек -- командой git stash clear.

Еще одна команда, которая изменяет рабочее дерево проекта -- git clean. С ее помощью можно удалить из проекта файлы, не отслеживаемые в репозитории, чтобы случайно не добавить их в коммит. С ключом -d, команда удаляет не только файлы, но и директории.

Перебазирование

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

Пусть имеется ветка develop со текущим состоянием проекта, и ветка feature с экспериментальными изменениями, которые были ответвлены от некоего коммита в прошлом. Стандартный способ добавить эти изменения в develop, это слияние, с образованием нового коммита. Однако, можно перенести коммиты из feature на вершину ветки develop так, как будто они были сделаны непосредственно в ней. Это достигается командами:

git checkout feature
git rebase develop

При перебазировании, Git определяет общего предка двух веток, в данном случае, feature и develop, находит разницу между вершиной develop и общим предком, а затем -- применяет эту разницу и все коммиты от общего предка до вершины feature к вершине develop. В результате, корень ветки feature, с учетом разницы, оказывается перенесен на вершину develop, а указатель develop остается на месте. После перебазирования, можно слить develop и feature путем перемотки вперед:

git checkout develop
git merge feature

После выполнения всех команд, состояние рабочего дерева проекта будет в точности таким же, как если бы было выполнено обычное слияние, но коммиты ветки feature будут образовывать не боковую ветку, а отрезок на линейной последовательности изменений в ветке develop. Если впоследствии удалить feature, уже будет невозможно определить, были ли изменения внесены отдельной веткой или добавлялись непосредственно в develop.

Перебазирование напрямую вмешивается в базу коммитов репозитория. В целом, эта операция считается безопасной, пока не затронуты изменения, уже отправленные во внешние репозитории. Если же это произошло, Git не позволит отправить новую порцию изменений командой git push, пока в командной строке не будет указан флаг --force. С другой стороны, измененные в основном репозитории ветки могут вызвать конфликты с локальными копиями других разработчиков, которые сохранили старую последовательность коммитов. Для разрешения этих конфликтов, первый прием изменений из перебазированного репозитория необходимо производить командой git pull --rebase.

Перебазирование ветки на саму себя возможно, но в общем случае никак на ней не отражается. Исключением является интерактивный режим выполнения команды, в котором Git предоставляет возможность выбирать коммиты и действия над ними из истории ветки. Например, если выполнить git rebase --interactive HEAD~5, то будет открыт редактор с текстом примерно следующего вида:

pick 4a4965e добавил лаб.4
pick e4e8c0c начало работы над rebase
pick a59fd2e добавлены разделы
pick 639e159 диапазоны коммитов
pick a1bb78f раздел про стэши

# Rebase a06e2d8..a1bb78f onto a06e2d8 (5 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Редактируя строки, которые не начинаются с символа #, можно удалять коммиты, менять их местами, а так же объединять несколько в один. В точках, помеченных словом edit, процесс перебазирования будет останавливаться. С помощью git commit --amend, для этих коммитов можно будет изменить сообщения в журнале. Для продолжения, после редактирования нужно будет выполнить команду git rebase --continue. В случае конфликтов, определенные коммиты можно будет пропустить командой git rebase --skip или отменить перебазирование полностью, командой git rebase --abort. Коммиты, отмеченные в редакторе как squash, будут объединены с идущими выше по списку. Их сообщения в журнале можно будет отредактировать до окончательной версии.

Задание для самостоятельной работы

  • Создайте репозиторий с несколькими коммитами путем клонирования одного из уже существующих
  • Просмотрите историю коммитов в различных диапазонах, используя команды git log, git diff и указатели на коммиты
  • Проверьте работу команды git reset в различных режимах
  • Найдите исходное положение ветки с помощью git reflog и верните указатель ветки в него
  • Сохраните изменения в тайниках и выполните основные действия с ними
  • Слейте две ветки с помощью перебазирования и проверьте результат командой git log
  • Проведите интерактивное перебазирование нескольких последних коммитов, опробуйте все предоставляемые в этом режиме возможности.