Сложное слияние и метки. Командная строка
Метки (теги)
Указатели на коммиты, лежат в .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.13/keyword.py keyword.py 4 $ git add . 5 $ git commit -a -m "Initial commit" 6 $ git branch second 7 $ grep -Ev "except|False" /usr/lib64/python3.13/keyword.py > keyword.py 8 $ git commit -a -m "False+except" 9 $ git checkout second 10 $ grep -Ev "finally|yield" /usr/lib64/python3.13/keyword.py > keyword.py 11 $ git commit -a -m "finally+yield"
Итак, у нас есть три состояния файла 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
]c/[c для перехода по контекстам
zo для открытия свёрток (см. другие folding-команды)
diffget/diffput с параметрами LO, RE, BA
:cquit для выходя с ошибкой
для тех, у кого не четыре полушария
, есть gvimdiff3 (только MERGED), gvimdiff2 (LOCAL, MERGED, REMOTE, без общего предка) и gvimdiff1 (LOCAL и REMOTE — вот это непонятно зачем) - Утилиты из списка выше позволяют «накликивать» изменения
Пример на том же репозитории:
просто удалим merge-коммит (git reset --hard HEAD~)
вызовем git merge
вызовем git mergetool --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 from pathlib import Path 3 from shlex import split 4 import readline 5 6 class CmdFile(cmd.Cmd): 7 prompt = f"{Path.cwd()}> " 8 9 def do_list(self, arg): 10 """List all files""" 11 self.columnize([p.name for p in Path(arg).glob("*")]) 12 13 def do_size(self, arg): 14 """Print files size""" 15 for name in split(arg): 16 if (path := Path(name)).exists(): 17 print(f"{path}: {path.stat().st_size}") 18 19 def complete_size(self, text, line, begidx, endidx): 20 return [str(p) for p in Path("").glob(f"{text}*")] 21 22 def do_EOF(self, arg): 23 return -1 24 25 if __name__ == "__main__": 26 readline.set_completer_delims('" ') 27 CmdFile().cmdloop()
8 Если обработчик возвращает не None, это «код ошибки» и cmdloop() останавливается
do_EOF() добавляет обработчик конца ввода (Ctrl+D в Linux)
По умолчанию подстановка настроена на идентификаторы Pythop, так что вместо пути к файлу мы видим только имя. Но после set_completer_delims('" ') Readline считает все остальные символы «буквами».
- Организует REPL
- TUI:
curses — встроенный в Python!
Как синтез TUI+cmdline: Python Prompt Toolkit
Д/З
Почитать про shlex и cmd; изучить API Python Cowsay
Наиболее сложная часть — что именно получают на вход методы complete_COMMAND(), разберитесь с этим (см. пример из лекции)
Создать в репозитории с Д/З подкаталог 04_MergetoolCommandline (по последнему фрагменту URL данной лекции) и поместить туда решение следующей задачи:
Переработать Домашнее задание из лекции 2 (возможно, лучше переписать заново), организовав командную строку с помощью cmd. Разбор командной строки реализовть с помощью shlex
Команды и их параметры проще всего сделать совпадающими с соответствующими функциями. Должны поддерживаться как минимум команды list_cows, make_bubble, cowsay и cowthink.
Пример синтаксиса cowsay (не обязательно реализовывать именно его; в квадратных скобках указаны необязательные параметры):
cowsay сообщение [название [параметр=значение …]] reply ответ [название [[параметр=значение …]]
сообщение и ответ — это реплики двух персонажей
название — это название коровы (должно поддерживаться достраивание)
параметр=значение — это eyes и tongue, а значение — строка
twocows> cowsay "Hi there" moose eyes="^^" reply "Ahoy!" sheep __________ < Ahoy! > < Hi there > ------- ---------- \ \ \ \ \_\_ _/_/ __ \ \__/ UooU\.'@@@@@@`. (^^)\_______ \__/(@@@@@@@@@@) (__)\ )\/\ (@@@@@@@@) ||----w | `YY~~~~YY' || || || ||
К каждой команде должна быть достаточная справка (help COMMAND)
все остальные усложнения — только если захочется)
Разработку вести согласно дисциплине оформления коммитов в подкаталоге 04_MergetoolCommandline отчётного репозитория по Д/З
Предполагается, что модуль python-cowsay устанавливается в окружение с помощью pipenv, в каталоге должен присутствовать соответствующий Pipfile

Чуть ли не половина перечисленного — проприетарные поделия