Система управления версиями Git. Система управления версиями git и российский сервис хранения исходного кода gitflic
Скачать 3.56 Mb.
|
2.19.4. Отмена действия команды git reset Если по какой-то причине была выполнена нежелательная команда git reset, то все изменения можно отменить, выполнив команду: git reset ORIG_HEAD Отмена возможна, пока не будет выполнена ещё одна команда git re- set, которая изменит значение указателя ORIG_HEAD. 2.20. Удаление веток 74 Ветки – это, как правило, «черновики». Они нужны, чтобы что-то про- верить, разобраться в сложной ситуации, создать временную «заплатку» и т.п. После завершения работы с веткой её, как правило, удаляют. Бывают и исключения. Например, ветка, в которой готовится новый релиз, имеет шанс не только остаться в репозитории, но со временем стать основной. Но всё- таки ветки часто удаляют. Для удаления ветки используется одна из двух команд: − git branch –d <имя ветки>; − git branch –D <имя ветки>. Команда с ключом –d удалит ветку только в том случае, если она была слита с другой (про слияние см. следующий раздел). Если слияние не вы- полнялось, то Git остановит операцию удаления ветки. Команда с ключом –D удалит ветку без проверки, была ли она слита с другой или нет. В Git ничего не теряется и не удаляется безвозвратно. Единожды попав в директорию .git/objects, объект остаётся в ней на всё время существования ре- позитория. Для обращения к объекту необходимо знать несколько первых цифр его хеш-значения. Ветка представляет собой цепочку коммитов, каждый из ко- торых ссылается на предыдущий. Попасть на «удалённую» ветку можно, зная хеш-значение её последнего коммита и указав его в команде git checkout <хеш- значение>. После выполнения такой команды возникнет состояние «Свобод- ный указатель» (detached head), разобранное в разделе 2.18. Необходимо помнить, что все изменения, не добавленные в коммит, при удалении ветки будут потеряны, так как они не попадут ни в один из объектов в директории .git/objects. 2.21. Слияние веток, принципы выполнения операции При создании репозитория в нём существует одна ветка, которой можно присвоить имя в ходе выполнения команды git init. Это главная ветка проекта. Исходя из практики использования Git, в главной ветке сохраняется только про- веренный код. Выполнение этого кода не должно приводить к проблемам, ко- торые необходимо решать в срочном порядке (наличие некритичных проблем, в некоторых случаях, допускается). При такой технологии работы над проек- том, для разработки кода каждого модуля создаётся отдельная ветка (назовём её 75 рабочей веткой модуля). В процессе написания кода модуля все коммиты со- храняются только в рабочей ветке модуля. После того, как модуль разработан и протестирован, финальная версия кода (финальный коммит) добавляется в глав- ную ветку. Структура репозитория, соответствующая описанной технологии работы над проектом, показана на рисунке 2.14. Рисунок 2.14 – Структура проекта при использовании веток На рисунке 2.14 термин «рабочая ветка» заменён на термин «целевая ветка». В рабочей ветке модуля могут быть дополнительные ответвления, например, для разработки кода функции. Принцип остаётся неизменным. Код создаётся в рабочей ветке функции, тестируется и сливается с рабочей веткой модуля. В общем случае ветка, в которой хранится финальный код, называется целевой. Последней целевой веткой в этой иерархической струк- туре является главная ветка. В ситуации (рисунки 2.14а и 2.14б) слияние будет происходить по- разному. В Git существуют два термина для обозначения слияния. 1. Явное слияние выполняется в случае, когда в целевой ветке были коммиты после создания рабочей ветки (рисунок 2.14б). 2. Неявное слияние выполняется в случае, когда в целевой ветке не 76 было коммитов после создания рабочей ветки (рисунок 2.14а). При выполнении неявного слияния указатель целевой ветки перено- сится на последний коммит рабочей ветки (рисунок 2.15а). Новый коммит не создаётся. Рисунок 2.15 – Явное и неявное слияние При выполнении явного слияния создаётся новый коммит, у которого два родителя (рисунок 2.15б). Такой коммит обозначается термином merge- commit. Сделать возврат из merge-commit сложно, поэтому его нужно созда- вать, когда вероятность наличия ошибок в коде рабочей ветки практически исключена. Возможность выполнения явного и неявного слияния ограничена: − неявное слияние возможно создать, только если в целевой ветке не выполнялись коммиты, после присоединения к ней рабочей ветки. Это огра- ничение снять невозможно; − явное слияние возможно выполнить, если отсутствуют конфликты. 77 Под конфликтом понимается ситуация, когда в рабочей и целевой ветке при- сутствуют файлы с одинаковым именем и различным содержанием. Кон- фликт возможно устранить, поэтому явное слияние, в отличие от неявного, можно выполнить всегда (приложив дополнительные усилия, если требу- ется устранить конфликт). 2.22. Слияние веток, команда git merge Команда git merge выполняет слияние веток. Формат команды: git merge <ключи> <рабочая ветка> При слиянии текущей веткой должна быть целевая. По логике Git из- менения, сделанные в рабочей ветке, «вливаются» в целевую. Возможные значения ключей: --ff – выполнить неявное слияние, если возможно. Другое название яв- ного слияния fast-foreard; --no-ff – отключить возможность неявного слияния, выполнять явное слияние; --ff-only – выполнять только неявное слияние, если это невозможно, то остановить выполнение команды git merge. --strategy – стратегия выполнения явного слияния. Git поддерживает следующие стратегии слияния: − resolve; − recursive; − ours; − octopus; − subtree. Неявное слияние не вызывает никаких сложностей, оно или выполня- ется, или не выполняется. С явным слиянием не так просто. Сложности та- ятся в стратегии выполнения слияния. По умолчанию используется страте- гия resolve, и большинство пользователей Git это устраивает. Рассмотрим эту стратегию, а с остальными читатель сможет познакомиться самостоя- тельно (например, в статье А. Прокопюк, 2013). 78 Цель любой из стратегий – построить дерево рабочей директории, ко- торое будет записано в merge-commit. Стратегия resolve заключается в вы- полнении следующих пяти шагов. 1. Поиск общего предка. Например, при выполнении явного слияния на рисунке 2.15б общим предком будет коммит ГВ3. 2. Поиск файлов, изменившихся в сливаемых ветках относительно общего предка. 3. Блоки, оставшиеся без изменений, включаются в дерево как неиз- менившиеся. 4. Блоки, изменившиеся только в одной из сливаемых веток, добав- ляются в дерево как изменившиеся. 5. Блоки, изменившиеся в обеих ветках, добавляются в дерево только при условии, если изменения в них одинаковые. Если изменения различные, то возникает ситуация «конфликт», и выполнение команды git merge при- останавливается. Программист, выполняющий слияние, должен или устра- нить конфликт и продолжить слияние, или отменить слияние. Недостаток стратегии resolve проявляется в случае, если в рабочей ветке продолжается редактирование кода и создание новых коммитов. В этой ситуации через некоторое время появится необходимость выполнить ещё одно слияние. Окажется, что для нового слияния общий предок тот же, что и для предыдущего. В результате придётся повторно устранять кон- фликты. В остальных 4 стратегиях сделаны попытки устранить указанный недостаток, но идеальное решение пока не найдено. 2.22.1. Устранение конфликтов слияния Рассмотрим на примере, как устраняются файловые конфликты, воз- никающие при использовании стратегии слияния resolve. Возникновение конфликта означает, что имеются две различные версии файла, и Git не знает, какую из них добавить в рабочую директорию merge-commit. Git про- сит программиста, выполняющего слияние, дать ему указания, какую вер- сию включать в рабочую директорию. Предположим, что в проекте есть файл с именем Test_merge.txt. На рисунке 2.16 показано, как изменялось содержимое этого файла. 79 Рисунок 2.16 – Пример слияния веток с конфликтом Выполним слияние веток Main_branch и Working_branch: /c/Learn_git (Working_branch) $ git checkout 'Main_branch' Switched to branch 'Main_branch' /c/Learn_git (Main_branch) $ git merge 'Working_branch' Auto-merging Test_Merge.txt CONFLICT (content): Merge conflict in Test_Merge.txt Automatic merge failed; fix conflicts and then commit the result. В строке “CONFLICT (content): Merge conflict in Test_Merge.txt”Git сообщил, что возник конфликт в содержимом (content) файла Test_Merge.txt. В строке “Automatic merge failed; fix conflicts and then commit the re- sult.” Git сообшил, что автоматически устранить конфликт не удалось, и просит устранить конфликт и создать коммит. В сложившейся ситуации возможны три варианта действий: 1. Отредактировать конфликтующие файлы, добившись того, чтобы они были одинаковыми. 2. Выбрать один из двух файлов. 3. Прервать слияние веток, выполнив команду git merge –abort. Рассмотрим первые два варианта. Ручное редактирование конфликтующих файлов. Файл Test_Merge. 80 txt в текущей рабочей директории выглядит следующим образом: Начало редактирования <<<<<<< HEAD Продолжение редактирования ======= Продолжение редактирования Окончание редактирования >>>>>>> Working_branch Git разметил текст, чтобы помочь в устранении конфликта. Весь текст, находящийся до строчки <<<<<<< HEAD, одинаковый в конфликтующих файлах. Текст между строчками <<<<<<< HEAD и ======= есть только в файле, находящемся в целевой ветке (HEAD). В нашем случае это ветка Main_branch. Текст между строчками ======= и >>>>>>> Working_branch есть только в файле, находящемся в рабочей ветке. В нашем случае это ветка Working_branch. Для устранения конфликта необходимо отредактировать конфликту- ющий файл, находящийся в текущей директории. Не обязательно оставлять в файле текст, который поместил в него Git. Можно ввести новый текст, ни- как не связанный с конфликтующими файлами. Обязательно нужно удалить разметку <<<<<<< HEAD, =======, >>>>>>> Working_branch. После окончания редактирования и сохранения файла конфликт счита- ется устранённым, и можно завершить слияние командой git merge –continue. В сложных случаях устранению конфликта может помочь наличие в редактируемом файле общего предка. Чтобы Git добавлял общего предка в заготовку для устранения конфликта, необходимо изменить настройки Git, выполнив команду: git config –global merge.conflictstyle diff3 Остановим предыдущее слияние, выполнив команду git merge –abort. И снова запустим процесс слияния командой git merge ‘Working_branch’. Сообщение о том, что выполнить слияние невозможно, не изменится. Изме- нится содержание файла Test_Merge.txt. Теперь оно выглядит следующим образом: <<<<<<< HEAD Начало редактирования Продолжение редактирования ||||||| e83aaf9 Начало редактирования ======= Начало редактирования Продолжение редактирования 81 Окончание редактирования >>>>>>> Working_branch Текст между строчкой ||||||| e83aaf9 и ======= представляет собой об- щего предка. Цифры e83aaf9 – это хеш-значение коммита, в котором нахо- дится общий предок конфликтующих файлов. Из сравнения общего предка с конфликтующими файлами видно, что: − в файл Test_Merge.txt, находящийся в целевой ветке (HEAD), была добавлена строчка «Продолжение редактирования»; − в файл Test_Merge.txt, находящийся в рабочей ветке (Working_branch), были добавлены две строчки «Продолжение редактиро- вания» и «Окончание редактирования». Изменим файл Test_Merge.txt так, чтобы он содержал текст, который должен быть добавлен в merge-commit. Например, так: Начало редактирования Продолжение редактирования (добавлено в целевой ветке) Окончание редактирования (добавлено в рабочей ветке). Добавим изменённый файл Test_Merge.txt в индекс (git add Test_Merge.txt) и продолжим процесс слияния, выполнив команду git merge – continue. В процессе выполнения последней команды автоматически откро- ется редактор для того, чтобы можно было ввести комментарий к merge- commit. Результат слияния: $ git add Test_Merge.txt /c/Learn_git (Main_branch|MERGING) $ git merge --continue [Main_branch 2e9bb1b] Merge branch 'Working_branch' into Main_branch Слияние прошло успешно. Был создан merge-commit с хеш-значением 2e9bb1b, находящийся в ветке Main_branch. В объекте merge-commit содер- жится следующая информация: /c/Learn_git (Main_branch) $ git cat-file -p 2e9bb1b tree cc7737e7d54a2bb5b64bee090ab0b48a09f2a776 parent a4649d09a31c61b7a4d382f0d7ab27fd61b27249 parent 93bc4e1c6548223a5d6b40c3f11de56b7f1837d2 author Git_Book Merge branch 'Working_branch' into Main_branch Существенным является то, что merge-commit содержит ссылки на два родителя (две строчки parent). Выбор одного из двух конфликтующих файлов. Для разрешения 82 конфликта путём выбора одного из двух файлов предусмотрены две ко- манды: − git checkout –ours <имя файла>. При выполнении этой команды в текущую рабочую директорию будет скопирован конфликтующий файл из целевой ветки. Он заменит тот, который Git подготовил для ручного устра- нения конфликта; − git checkout –their <имя файла>. При выполнении этой команды в текущую рабочую директорию будет скопирован конфликтующий файл из рабочей ветки. Он заменит тот, который Git подготовил для ручного устра- нения конфликта. После выбора файла его необходимо добавить в индекс (git add <имя файла>) и продолжить выполнение слияния (git mege –continue). 2.23. Перенос начала ветки, команда git rebase Перед тем, как продолжить, необходимо определить, что такое начало ветки и что такое конец ветки. Начало ветки – это коммит, самый близкий к ветке, из которой «растёт» текущая. Конец ветки – это последний коммит в текущей ветке. Обычно при структуре дерева коммитов, соответствующей рисунку 2.14а, выполняется неявное слияние, а при структуре дерева коммитов, соответствующей рисунку 2.14б, выполняется явное слияние. Однако, бы- вают случаи, когда при структуре дерева коммитов, изображённой на ри- сунке 2.17, необходимо добавить в целевую ветку коммиты ВМ1 и ВМ2. Выполнить такую операцию невозможно ни с помощью явного слияния, ни с помощью неявного слияния. 83 Рисунок 2.17 – Структура дерева коммитов до преобразования Для решения поставленной задачи Git предлагает команду git rebase, которая может изменить структуру дерева коммитов, как показано на ри- сунке 2.18. Рисунок 2.18 – Структура дерева коммитов после преобразования После такого преобразования появляется возможность выполнить не- явное слияние, перенеся указатель целевой ветки на коммит ВМ2-Н. В ре- зультате коммиты ВМ1-Н и ВМ2-М окажутся включёнными в целевую ветку. У коммитов ВМ1-Н, ВМ2-Н хеш-значения не совпадают с хеш-зна- чениями коммитов ВМ1, ВМ2, однако изменение состояния рабочей дирек- тории и индекса описываются так же, как в дереве на рисунке 2.17. Формат команды git rebase: git rebase <ключи> <целевая ветка> Команда git rebase выполняется, когда текущей веткой является рабочая. 84 Возможные значения ключей: --abort – используется при возникновении конфликтов для прерыва- ния выполнения команды git rebase; --continue – используется для продолжения выполнения команды git rebase после устранения конфликта; --skip – позволяет пропустить текущий коммит при устранении кон- фликтов. В процессе преобразования дерева коммитов командой git rebase вы- полняются следующие шаги: 1. Определяется база преобразования, для чего производится поиск общего коммита двух веток (на рисунке 2.17 это коммит ГВ3). 2. Вычисляется разница между предпоследним коммитом целевой ветки и первым коммитом рабочей ветки: d1 = ВМ1 – ГВ3. 3. Добавляется разница d1 к коммиту ГВ4, в результате получается коммит ВМ1-Н. 4. Для каждой пары коммитов в рабочей ветке вычисляется разница и добавляется к последнему преобразованному коммиту. Для примера (ри- сунок 2.17) будут выполнены следующие преобразования: d2 = ВМ2 – ВМ1; ВМ2-Н = ВМ1-Н + d2. 5. После преобразования всех коммитов указатель рабочей ветки пе- реносится на последний из преобразованных коммитов. 6. Создаётся указатель ORIG_HEAD, указывающий на последний коммит в оригинальной рабочей ветке. Для примера (рисунок 2.17) результат преобразования, выполненного командой git rebase, показан на рисунке 2.19. Когда в процессе добавления разницы возникает конфликт, выполне- ние преобразования приостанавливается до тех пор, пока конфликт не будет устранён. Устранение конфликта происходит так, как описано в разделе 2.22.1. 85 Рисунок 2.19 – Результат выполнения команды rebase Если в процессе анализа конфликтной ситуации оказывается, что файлы из текущего коммита можно не включать в рабочую директорию (они полностью замещаются файлами из следующего коммита), то текущее преобразование можно пропустить, выполнив команду git rebase –skip. Прервать выполнение команды git rebase возможно только в случае её приостановки для устранения конфликта. Прерывание осуществляется ко- мандой git rebase –break. Однако даже после завершения преобразования возможно вернуть дерево коммитов в состояние, в котором оно находилось до начала преобразования. Для этого используется указатель ORIG_HEAD и команда: git reset –hard ORIG_HEAD 2.24. Копирование коммита, команда git cherry-pick В процессе работы над проектом может возникнуть ситуация, когда необходимо скопировать коммит из одной ветки в другую. Например, два разработчика создают части программного модуля, ведя работу в разных ветках. Одному из них необходимо получить код своего коллеги, в котором 86 описывается общий интерфейс. При этом сливать ветки пока рано. Или дру- гой пример. В результате ошибочных действий был потерян важный ком- мит. Его хеш-значение известно. Исправить ситуацию может копирование «потерянного» коммита в текущую ветку. Команда, позволяющая копировать коммиты, называется git cherry-pick. |