Сложное слияние и метки. Командная строка
Метки (теги)
Указатели на коммиты, лежат в .git/refs/tags/
- Выступают в роли commit-ish (как commit ID, ветки и ссылки относительно HEAD)
Можно запушить с ключом --tags (но по умолчанию локальны)
Аннотированный тег сопровождается специальным объектом-тегом в .git/objects/**/
git tag [commitish] -a тег -m Аннотация
- Можно подписывать электронной подписью
Две роли тегов:
- закладка (особенно аннотированные теги): как commitish не зависит от ветки / commit ID
- управление внешними инструментами (особенно подписанные теги) — например, автоматическая сборка и т. п.
Сложное слияние
При merge и rebase могут возникать конфликты: в двух историях изменён один и тот же контекст:
- Создадим заведомо конфликтующий коммиты на двух ветках
1 $ git init 2 Initialized empty Git repository in /home/george/example/.git/ 3 $ cp /usr/lib64/python3.12/keyword.py keyword.py 4 $ git add . 5 $ git commit -a -m "Initial commit" 6 [master (root-commit) 8ab1be9] Initial commit 7 1 file changed, 63 insertions(+) 8 create mode 100644 keyword.py 9 $ git branch second 10 $ git branch 11 * master 12 second 13 $ grep -Ev "except|False" /usr/lib64/python3.12/keyword.py > keyword.py 14 $ git diff 15 diff --git a/keyword.py b/keyword.py 16 index cc2b46b..9f30ffb 100644 17 --- a/keyword.py 18 +++ b/keyword.py 19 @@ -16,7 +16,6 @@ Alternatively, you can run 'make regen-keyword'. 20 __all__ = ["iskeyword", "issoftkeyword", "kwlist", "softkwlist"] 21 22 kwlist = [ 23 - 'False', 24 'None', 25 'True', 26 'and', 27 @@ -31,7 +30,6 @@ kwlist = [ 28 'del', 29 'elif', 30 'else', 31 - 'except', 32 'finally', 33 'for', 34 'from', 35 $ git commit -a -m "False+except" 36 [master f1fbdeb] False+except 37 1 file changed, 2 deletions(-) 38 $ git checkout second 39 Switched to branch 'second' 40 $ grep -Ev "finally|yield" /usr/lib64/python3.12/keyword.py > keyword.py 41 $ git diff 42 diff --git a/keyword.py b/keyword.py 43 index cc2b46b..251bd3a 100644 44 --- a/keyword.py 45 +++ b/keyword.py 46 @@ -32,7 +32,6 @@ kwlist = [ 47 'elif', 48 'else', 49 'except', 50 - 'finally', 51 'for', 52 'from', 53 'global', 54 @@ -50,7 +49,6 @@ kwlist = [ 55 'try', 56 'while', 57 'with', 58 - 'yield' 59 ] 60 61 softkwlist = [ 62 $ git commit -a -m "finally+yield" 63 [second 0804e39] finally+yield 64 1 file changed, 2 deletions(-) 65 $ git log --graph --pretty=oneline --abbrev-commit --all 66 * 0804e39 (HEAD -> second) finally+yield 67 | * f1fbdeb (master) False+except 68 |/ 69 * 8ab1be9 Initial commit 70
Итак, у нас есть три состояния файла keyword.py:
8ab1be9 (общий предок)
f1fbdeb (на ветке master) — без False и except
0804e39 (на ветке second) — без finally и yield
Контекст изменений для except и finally пересекается
- ⇒ при слиянии будут конфликты
- Попробуем объединить:
1 $ git branch 2 master 3 * second 4 $ git merge master 5 Auto-merging keyword.py 6 CONFLICT (content): Merge conflict in keyword.py 7 Automatic merge failed; fix conflicts and then commit the result. 8 $ grep -EC3 "<<<<|====|>>>>" keyword.py 9 'del', 10 'elif', 11 'else', 12 <<<<<<< HEAD 13 'except', 14 ======= 15 'finally', 16 >>>>>>> master 17 'for', 18 'from', 19 'global', 20
Часть изменений применены (про False и про yield), потому что контексты не пересекались, часть (про except и finally) — нет.
- Файл содержит вставки вида:
<<<<<<< HEAD … ======= … >>>>>>> master
- Это т. н. 3-way diff по схеме «общий предок + конфликтующие изменения»
Все неконфликтующие изменения из обеих веток применены
HEAD — это содержимое текущей ветка, master — с чем мержим
было бы неплохо ещё знать, что раньше-то было, но тут не показывается
Все "<<<<<<<", "=======" и ">>>>>>>" надо убрать (и ненужные изменения тоже)
- Получится merge commit с изменением, неравным тому, что делалось на ветках
Если вас удовлетворяют изменения, проделанные на ветке master, можно просто git checkout master keyword.py, но тогда пропадут все изменения, включая уже применённые.
Когда всё готово, делаем git commit -a:
1 $ vim keyword.py
2 …
3 $ git commit -a
4 [second 6568682] Merge branch 'master' into second
5 $ git log --graph --pretty=oneline --abbrev-commit --all
6 * 6568682 (HEAD -> second) Merge branch 'master' into second
7 |\
8 | * f1fbdeb (master) False+except
9 * | 0804e39 finally+yield
10 |/
11 * 8ab1be9 Initial commit
12
Если в историях больше одного коммита, merge надо продолжить с помощью git merge --continue
Если вы окончательно запутались (особенно в многокоммитных мержах), всё можно откатить назад с помощью git merge --abort
Mergetool
Инструмент, в котором есть , имеет общее название «merge tool».
Список: git mergetool --tool-help
- Запускается вместо ручного исправления конфликтов
*vimdiff показывает четыре окна:
«Эта» ветка (LOCAL)
Общий предок (BASE)
«Та» ветка (REMOTE)
Файл с конфликтами (MERGED, его и надо исправлять)
Могут остаться backup-файлы, их надо удалить git clean -f
для тех, у кого не четыре полушария , есть gvimdiff3 (только MERGED), gvimdiff2 (LOCAL, MERGED, REMOTE, без общего предка) и gvimdiff1 (LOCAL и REMOTE — вот это непонятно зачем)
- Другие утилиты позволяют «накликивать» изменения)
Пример на том же репозитории:
просто удалим merge-коммит (git reset --hard HEAD~)
вызовем git merge
вызовем git merge-tool --tool=gvimdiff
:diffget RE " get from REMOTE :diffget BA " get from BASE :diffget LO " get from LOCAL
Для постоянного вызова правильного mergetool:
$ git config --global merge.tool ваш_mergetool
Организация командной строки
На Windows не работает, замена — pyreadline3
- Просто проимпортить, и уже имеются:
- Редактирование ввода
- История
- Хранение истории (см. примеры в конце документации)
Дополнение в readline делается с помощью rlcompleter
Есть urwid, модный prompt-toolkit и т. п., но это уже оверкилл
shlex — это split / join / quote для командной строки, похожей на shell
- см. примеры в документации
потестируем split()
cmd — собственная командная строка!
- Организует REPL
class commandline(cmd.Cmd): — и поехали!
Можно обмазать readline или любым другим враппером
do_COMMAND(строка)
COMMAND — команда
префикс docstring — это встроенный help()
строку можно распарсить shlex-ом или более сложным парсером
complete_COMMAND(префикс, строка_целиком, начало_префикса, конец_префикса)
префикс — что достраиваем (введённый текст от конца команды до курсора)
строка_целиком — всё, что успели ввести (вместе с командой и текстом после курсора)
начало_префикса, конец_префикса — индекс префикса в строке_целиком (мало ли, может надо проверить команду или что после курсора)
- Пример
1 import cmd 2 3 DIGITS = {'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 4 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10} 5 TEENS = {'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15, 6 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19} 7 DECS = {'twenty': 20, 'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 8 'eighty': 80, 'ninety': 90} 9 ANY = DIGITS | TEENS | DECS 10 11 12 class numbername(cmd.Cmd): 13 prompt = "cmd>> " 14 15 def do_number(self, arg): 16 """Translate two-digit number from english""" 17 match arg.split(): 18 case [dec, digit]: 19 if dec in DECS and digit in DIGITS: 20 print(DECS[dec] + DIGITS[digit]) 21 case [single]: 22 if single in ANY: 23 print(ANY[single]) 24 25 def complete_number(self, text, line, begidx, endidx): 26 words = (line[:endidx] + ".").split() 27 DICT = [] 28 match len(words): 29 case 2: 30 DICT = ANY 31 case 3: 32 if words[1] in DECS: 33 DICT = DIGITS 34 return [c for c in DICT if c.startswith(text)] 35 36 37 if __name__ == '__main__': 38 numbername().cmdloop()
- Организует REPL
Д/З
Почитать про shlex и cmd; изучить API Python Cowsay
Наиболее сложная часть — что именно получают на вход методы complete_COMMAND(), разберитесь с этим (см. пример из лекции)
Создать в репозитории с Д/З подкаталог 04_MergetoolCommandline (по последнему фрагменту URL данной лекции) и поместить туда решение следующей задачи:
Переработать Домашнее задание из лекции 2 (возможно, лучше переписать заново), организовав командную строку с помощью cmd. Разбор командной строки делать с помощью shlex
Команды и их параметры проще всего сделать совпадающими с соответствующими функциями. Должны поддерживаться как минимум команды list_cows, make_bubble, cowsay и cowthink.
Параметры не надо реализовывать все. Например, для cowsay … достаточно четырёх:
message, cow, eyes, tongue
Везде, где это предусмотрено соответствующими функциями, должны подерживаться параметры по умолчанию (например, для cowsay достаточно только одного параметра — message)
К каждой команде должна быть достаточная справка (help COMMAND)
- Везде, где это возможно, должна поддерживаться подстановка вариантов (completion)
В частности, для eyes и tongue необходимо сделать несколько заранее заданных вариантов, и предлагать их
(в силу специфики библиотеки readline подстановка не-буквенных последовательностей может не работать, не боритесь с этим)
все остальные усложнения — только если захочется)
Разработку вести согласно дисциплине оформления коммитов в подкаталоге 04_MergetoolCommandline отчётного репозитория по Д/З
Предполагается, что модуль python-cowsay устанавливается в окружение с помощью pipenv, в каталоге должен присутствовать соответствующий Pipfile