title | author | fontsize | geometry | paper | lang | template |
---|---|---|---|---|---|---|
Лабораторная работа 5 |
А.В. Родионов |
14pt |
margin=2cm |
a4 |
russian |
default.html |
Контрольная сумма (хэш) 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:
- ветку, на которую указывает
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 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
- Проведите интерактивное перебазирование нескольких последних коммитов, опробуйте все предоставляемые в этом режиме возможности.