Взаимодействие на основе патчей. Исследование кода
Ещё про работу с историей
Статья на Хабре про стратегии git merge
Раздельное добавление ханков
- Откат истории (повторение):
Команды git-reset (и git reset --hard)
git commit --interactive (ALT: пакет perl-Git)
Работа с патчами и наборами патчей
Немного о формате
Патчи и Git:
git-format-patch и git-am / git-apply
Замечание: git не умеет в fuzzy (и правильно!)
⇒ иногда уместнее patch -u или patch --git
- Серия коммитов ⇒ серия патчей, условная нестрогость порядка
Патч или набор с точки зрения GIT — это сериализация коммитов, превращение их в пригодный для передачи формат.
BTW: difflib
(если успеем) Организация взаимодействия при совместной разработке
Классическая модель (если не будет хватать времени, рассмотрим только её):
- Один публичный репозиторий
- Разработчики синхронизуются с ним, merge request-ом является письмо с приложенным набором патчей
Майнтейнер публичного репозитория делает git am и ревью
Открытая модель:
- «Precious source» — публичный репозиторий, в который делается только pull
- Индивидуальные публичные репозитории всех участников
- Оповещение о готовности индивидуального репозиотрия к merge-у (pull request) со стороны исполнителя
Майнтейнер precious source делает git pull и ревью
Модель общего хостинга:
В плане использования GIT совпадает с открытой моделью
- Все дополнительные инструменты разработки могут быть привязаны к git или даже управляться тегами, но в самом git отсутствуют
- Например, у понятия merge/pull request даже нет чёткого определения
Выделяют «pull request» (GitHub) или «merge request» (GitLab) в качестве отдельного информационного объекта и обеспечивают (полу)автоматическую его обработку.
- Важно: понятие merge/pull request в самом git отсутствует, и у него нет чёткого определения
- Предоставляет тематическую социальную сеть с привязкой к исходным текстам и информационным объектам процесса разработки
- Предоставляет технологические ресурсы по сборке и тестированию
Централизованная модель:
- Один репозиторий для всех
- Чёткие конвенции для push и pull
- Использование веток в т. ч. для индивидуальной разработки
- Важно: раздельных прав доступа на части репозитория в git нет, это или договорённости или дополнительные свойства площадки
- А ещё это сводит на нет все достоинства DVCS
Ветки и индивидуальные репозитории — ортогональные сущности:
- Ветки — для эшелонирования работ (например, разработки новой функциональности или разделения на devel - texting - deployment)
- Индивидуальные репозитории — для разделения областей ответственности по исполнителям
Исследование кода
В прошлом Д/З упоминался inspect (но там он не нужен).
Анализ кода: is*()
Доступ к исходным текстам: get*()
- Номера строк, имя файла и т. п.
- Комментарии, докстринги
- аннотации
- Сигнатуры, исследование функций
Парсинг и исследование синтаксиса — ast
Обмазка вокруг встроенного модуля _ast,может меняться от релиза к релизу
- Можно почитать исходный код (ссылка в начале документации)
- Там же можно почитать полную БНФ
- Результат — дерево разбора объектов различного типа (конструкций python)
- номера строк, исходный текст узла и т. п.
dump() и unparse()
Пример разбора дерева «на скорую руку»:
1 def astformat(node):
2 if isinstance(node, ast.AST):
3 args = []
4 for field in node._fields:
5 value = getattr(node, field)
6 args.append(astformat(value))
7 return f"{node.__class__.__name__} {''.join(args)}\n"
8 elif isinstance(node, list):
9 return "".join(astformat(x) for x in node)
10 return str(node)
Сравнение текстов: difflib
Аналог утилиты diff: python3.10/Tools/scripts/diff.py
.unified_diff()
.ndiff() (с внутристроковыми изменениями)
TODO
(Если успеем) Доступ к байт-коду — dis
Примеры dis.dis()
Д/З
Написать программу dubfinder.py, которая исследует модули на наличие похожих функций или методов с учётом переименования объектов, наличия произвольных комментариев и изменений в форматировании.
Параметры командной строки: dubfinder.py модуль1 необязательный модуль2 …
- Программа выводит пары функций, исходный текст которых «очень похож» (см. ниже критерии)
Вывод: для dubfinder.py spiral (spiral.py):
$ python3 dubfinder.py spiral spiral.Spiral.__add__ : spiral.Spiral.__sub__ spiral.Spiral.__mul__ : spiral.Spiral.__rmul__
Вывод: для dubfinder.py spiral spiral2 (spiral.py и spiral2.py).
spiral.Spiral.__add__ spiral2.Spiral.__add__ spiral.Spiral.__init__ spiral2.Spiral.__init__ spiral.Spiral.__iter__ spiral2.Spiral.__iter__ spiral.Spiral.__len__ spiral2.Spiral.__len__ spiral.Spiral.__mul__ spiral.Spiral.__rmul__ spiral.Spiral.__rmul__ spiral2.Spiral.__mul__ spiral.Spiral.__str__ spiral2.Spiral.__str__ spiral.Spiral.__sub__ spiral2.Spiral.__sub__ spiral.Spiral._square spiral2.Spiral._show spiral.main spiral2.master spiral2.Spiral.__add__ spiral2.Spiral.__sub__ spiral2.Spiral.__mul__ spiral2.Spiral.__rmul__
Требования по выводу не вполне чёткие — он должен быть упорядочен лексикографически, но что делать, если «одинаковых» функций более двух, не регламентировано
Полное совпадение __mul__ и __rmul__ в одном модуле
Частичное совпадение __add__ и __sub__ в одном модуле
- Полное совпадение соответствующих функций и методов в обоих модулях
- Допущения относительно проверяемых модулей (для простоты):
- Не используются docstring-и и аннотации
- Не используются подмодули
Не используются инструкции вида from модуль import что-то
- Возможный вариант реализации (я сделал так, но теперь мне кажется, что он более сложный):
Модули разрешено import-тить (с помощью importlib)
Затем рекурсивно просмотрим их с помощью inspect.getmembers() и составим список определённых в них функций
Разрешено игнорировать классы, имя которых начинается с "__" (например, не стоит рекурсивно заходить в что_то.__class__)
Для каждой функции с помощью inspect.getsource() получаем исходный текст.
С помощью ast.parse() превращаем этот текст в дерево разбора (для методов надо предварительно применить textwrap.dedent()
Заменить в дереве все идентификаторы на один (например, на "_")
Я просто прошёлся по ast.walk() и подменил все атрибуты 'name', 'id', 'arg', 'attr' у тех узлов, у которых они были
Собрать обратно препарированный текст с помощью ast.unparse()
Разумеется, комментарии при этом исчезают, а вместо имён везде стоит "_"
Составим словарик вида {найденная_функция: препарат}
Сравним все препараты друг с другом (это n²/2, увы) и для каждой функции подберём наиболее «близкую» с помощью difflib.SequenceMatcher.ratio()
Если это ratio() > 0.95 — пара считается «похожей»
Есть и другой вариант решения: вместо importlib и inspect использовать тот же ast, и вручную обходить дерево. Возможно, он проще.
В отчётном репозитории с Д/З создать подкаталог 05_DiffPatchAST и поместить туда решение