[Python, Git] Нетривиальное слияние репозиториев с помощью git-filter-repo
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Это вторая часть истории про слияние репозиториев. Суть проблемы вкратце такова: надо слить репозиторий с подрепозиторием с сохранением истории. Решение на gitpython работало за 6 часов и выдавало удовлетворительный результат. Но переизбыток свободного времени и гвоздь в жопе врождённая любознательность привели меня к знакомству с волшебным миром git-filter-repo.Что такое git-filter-repo?Это замечательная программа, которая вызывает git fast-export с одной стороны, git fast-import с другой стороны, а между ними позволяет делать всякую фильтрацию. git fast-export, в свою очередь, просто берёт все объекты git-репозитория (а git-репозиторий представляет из себя не более чем объектную базу данных с объектами фиксированного вида) и печатает их текстом в стандартный вывод, так, чтобы для любого объекта Х, все объекты на который он ссылается выводились раньше самого объекта Х. git fast-import, как не трудно догадаться, делает обратную операцию: берёт эту простыню и раскидывает по файлам в папке .git.Последствия написания статейНаписав прошлую статью, я понял, что всё это построение подграфа — лишняя операция. Ведь место, куда вставляются коммиты из secondary-репозитория очень легко найти: это те коммиты, где меняется файл secondary.version. Причём диапазон secondary коммитов считается как список (secondary.version до изменения)..(secondary.version после изменения). И, соответственно, можно просто бежать по графу коммитов и сразу делать необходимые операции. Переписав скрипт с учётом этого знания, удалось сократить время работы до примерно 3-х часов. Но, и это не предел. Ведь теперь коммиты оригинального репозитория обрабатываются все подряд линейно, то есть именно так, как их выводит git fast-export. А значит, можно использовать git-filter-repo.Не только приложениеОчень быстро выяснилось, что использовать git filter-repo, как приложение не получится. Дело в том, что такой режим годится только для относительно простой фильтрации без состояния. Либо надо делать какие-то зубодробительные коллбеки с сохранением состояния бог знает где. Истинно, это не путь Дао. Однако, мудрый автор git filter-repo предусмотрел использование его как библиотеки. Библиотека состоит преимущественно из класса FastExportParser, который из вывода git fast-export создаёт объекты соответствующих классов (Blob, Commit и т.д.), каждый из которых имеет метод dump() для форматирования в вид приемлемый для git fast-import. ИтогоБерём класс RepoFilter как пример, создаём вход (git fast-export) и выход (git fast-import) для FastExportParser'а.
fep_cmd = ['git', '-C', args.source, 'fast-export', '--show-original-ids', '--progress=128'
, '--signed-tags=strip', '--tag-of-filtered-object=rewrite', '--mark-tags'
, '--fake-missing-tagger', '--reference-excluded-parents', '--all']
fep = subproc.Popen(fep_cmd, bufsize=-1, stdout=subproc.PIPE)
inpt = fep.stdout
fip_cmd = ['git', '-C', args.target, '-c', 'core.ignorecase=false'
, 'fast-import', '--force']
fip = subproc.Popen(fip_cmd, bufsize=-1, stdin=subproc.PIPE, stdout=subproc.PIPE)
otpt = fip.stdin
processor = Processor(args.source, args.secondary, otpt, fip.stdout)
parser = gfr.FastExportParser(blob_callback = processor.blob_callback
, commit_callback = processor.commit_callback
, progress_callback = processor.progress_cb)
parser.run(inpt, otpt)
otpt.close()
inpt.close()
И пишем класс Processor c необходимыми коллбеками. Самый главный коллбек — коммитный. Там как раз и обнаруживается что конкретный коммит меняет secondary.version. Что, кстати, теперь намного проще сделать, так как git fast-export сразу выдаёт только те файлы которые поменялись в конкретном коммите. После того, как изменение обнаружено, запускается новый git fast-export с новым экземпляром FastExportParser'а, который забирает коммиты уже из secondary репозитория, проводит с ними минимальную работу и пишет в тот же самый поток git fast-import. Причём, многие блобы попадают в git fast-import по многу раз, но это не причиняет никаких неудобств, ведь все объекты git адресуются своими хэшами, и, соответсвенно, добавление объекта во второй раз не меняет репозиторий (такие операции называются идемпотентными). Класс Commit и коммитный коллбек выглядит вот так.
class Commit(_GitElementWithId):
"""
This class defines our representation of commit elements. Commit elements
contain all the information associated with a commit.
"""
def __init__(self, branch,
author_name, author_email, author_date,
committer_name, committer_email, committer_date,
message,
file_changes,
parents,
original_id = None,
encoding = None, # encoding for message; None implies UTF-8
**kwargs):
pass
class Processor :
def process_commit(self, commit) :
idx = len(commit.file_changes)
commit.message = self.message_reformat(commit.message)
while idx > 0 :
idx -= 1
fc = commit.file_changes[idx]
if fc.type == b'M' :
process_meth = self.MAP.get(fc.filename)
if process_meth is not None:
commit.file_changes.pop(idx)
process_meth(commit, fc)
Как видите, process_commit вызывает методы, в зависимости от имени файла. Метод для secondary.version:
def process_secondary_version(self, commit) :
parent = commit.original_id.decode('ascii')
res = subproc.run(['git', '-C', self.prepo, 'cat-file'
, 'blob', f'{parent}^:secondary.version']
, capture_output = True)
hsh_from = res.stdout[:40].decode('ascii')
if len(hsh_from) < 40 :
sr_range = f'{hsh}^!'
else :
sr_range = f'{hsh_from}..{hsh}'
self.sub_parsed = False
self.super_commit = commit
self.sub_top_hsh = hsh.encode('ascii')
sub_fep_cmd = ['git', '-C', self.srepo, 'fast-export', '--show-original-ids'
, '--signed-tags=strip', '--tag-of-filtered-object=drop'
, '--import-marks=secondary.marks'
, sr_range]
sub_fep = subproc.Popen(sub_fep_cmd, bufsize=-1, stdout=subproc.PIPE)
self.sub_parser.run(sub_fep.stdout, self.output)
sub_fep.stdout.close()
ЭпилогНовая реализация работает за 10 минут вместо 6-и часов. Мораль: правильные алгоритмы, это конечно хорошо, но лучшие инструменты дают лучшие результаты
===========
Источник:
habr.com
===========
Похожие новости:
- [Занимательные задачки, Python, Алгоритмы, Читальный зал, Лайфхаки для гиков] Извлечение троих: Как найти пасхалки в книгах Стивена Кинга с помощью NLP алгоритмов
- [Open source, Python] Dramatiq как современная альтернатива Celery: больше нет проблем с версиями и поддержкой Windows
- [Usability] Надо ли дизайнеру разбираться в верстке?
- [Python, API] Сохраняем комментарии youtube в csv
- [Python, Машинное обучение] Эволюция OLEG AI. Нейросеть, утечки памяти, нагрузка
- [Python, Алгоритмы, Big Data, Машинное обучение, Искусственный интеллект] Data Phoenix Digest — 01.07.2021
- [Python, Алгоритмы, Big Data, Машинное обучение, Искусственный интеллект] Data Phoenix Digest — 01.07.2021
- [Python, PDF] Tesseract OCR, выделение распознанного текста на изображении
- [JavaScript, Программирование, Конференции, Видеоконференцсвязь] Видеочат с возможностью совместного редактирования текста при помощи Twilio Sync (перевод)
- [Python, Машинное обучение, Искусственный интеллект, TensorFlow] TensorFlow vs PyTorch в 2021: сравнение фреймворков глубокого обучения
Теги для поиска: #_python, #_git, #_gitfilterrepo, #_python, #_git, #_python, #_git
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:43
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Это вторая часть истории про слияние репозиториев. Суть проблемы вкратце такова: надо слить репозиторий с подрепозиторием с сохранением истории. Решение на gitpython работало за 6 часов и выдавало удовлетворительный результат. Но переизбыток свободного времени и гвоздь в жопе врождённая любознательность привели меня к знакомству с волшебным миром git-filter-repo.Что такое git-filter-repo?Это замечательная программа, которая вызывает git fast-export с одной стороны, git fast-import с другой стороны, а между ними позволяет делать всякую фильтрацию. git fast-export, в свою очередь, просто берёт все объекты git-репозитория (а git-репозиторий представляет из себя не более чем объектную базу данных с объектами фиксированного вида) и печатает их текстом в стандартный вывод, так, чтобы для любого объекта Х, все объекты на который он ссылается выводились раньше самого объекта Х. git fast-import, как не трудно догадаться, делает обратную операцию: берёт эту простыню и раскидывает по файлам в папке .git.Последствия написания статейНаписав прошлую статью, я понял, что всё это построение подграфа — лишняя операция. Ведь место, куда вставляются коммиты из secondary-репозитория очень легко найти: это те коммиты, где меняется файл secondary.version. Причём диапазон secondary коммитов считается как список (secondary.version до изменения)..(secondary.version после изменения). И, соответственно, можно просто бежать по графу коммитов и сразу делать необходимые операции. Переписав скрипт с учётом этого знания, удалось сократить время работы до примерно 3-х часов. Но, и это не предел. Ведь теперь коммиты оригинального репозитория обрабатываются все подряд линейно, то есть именно так, как их выводит git fast-export. А значит, можно использовать git-filter-repo.Не только приложениеОчень быстро выяснилось, что использовать git filter-repo, как приложение не получится. Дело в том, что такой режим годится только для относительно простой фильтрации без состояния. Либо надо делать какие-то зубодробительные коллбеки с сохранением состояния бог знает где. Истинно, это не путь Дао. Однако, мудрый автор git filter-repo предусмотрел использование его как библиотеки. Библиотека состоит преимущественно из класса FastExportParser, который из вывода git fast-export создаёт объекты соответствующих классов (Blob, Commit и т.д.), каждый из которых имеет метод dump() для форматирования в вид приемлемый для git fast-import. ИтогоБерём класс RepoFilter как пример, создаём вход (git fast-export) и выход (git fast-import) для FastExportParser'а. fep_cmd = ['git', '-C', args.source, 'fast-export', '--show-original-ids', '--progress=128'
, '--signed-tags=strip', '--tag-of-filtered-object=rewrite', '--mark-tags' , '--fake-missing-tagger', '--reference-excluded-parents', '--all'] fep = subproc.Popen(fep_cmd, bufsize=-1, stdout=subproc.PIPE) inpt = fep.stdout fip_cmd = ['git', '-C', args.target, '-c', 'core.ignorecase=false' , 'fast-import', '--force'] fip = subproc.Popen(fip_cmd, bufsize=-1, stdin=subproc.PIPE, stdout=subproc.PIPE) otpt = fip.stdin processor = Processor(args.source, args.secondary, otpt, fip.stdout) parser = gfr.FastExportParser(blob_callback = processor.blob_callback , commit_callback = processor.commit_callback , progress_callback = processor.progress_cb) parser.run(inpt, otpt) otpt.close() inpt.close() class Commit(_GitElementWithId):
""" This class defines our representation of commit elements. Commit elements contain all the information associated with a commit. """ def __init__(self, branch, author_name, author_email, author_date, committer_name, committer_email, committer_date, message, file_changes, parents, original_id = None, encoding = None, # encoding for message; None implies UTF-8 **kwargs): pass class Processor : def process_commit(self, commit) : idx = len(commit.file_changes) commit.message = self.message_reformat(commit.message) while idx > 0 : idx -= 1 fc = commit.file_changes[idx] if fc.type == b'M' : process_meth = self.MAP.get(fc.filename) if process_meth is not None: commit.file_changes.pop(idx) process_meth(commit, fc) def process_secondary_version(self, commit) :
parent = commit.original_id.decode('ascii') res = subproc.run(['git', '-C', self.prepo, 'cat-file' , 'blob', f'{parent}^:secondary.version'] , capture_output = True) hsh_from = res.stdout[:40].decode('ascii') if len(hsh_from) < 40 : sr_range = f'{hsh}^!' else : sr_range = f'{hsh_from}..{hsh}' self.sub_parsed = False self.super_commit = commit self.sub_top_hsh = hsh.encode('ascii') sub_fep_cmd = ['git', '-C', self.srepo, 'fast-export', '--show-original-ids' , '--signed-tags=strip', '--tag-of-filtered-object=drop' , '--import-marks=secondary.marks' , sr_range] sub_fep = subproc.Popen(sub_fep_cmd, bufsize=-1, stdout=subproc.PIPE) self.sub_parser.run(sub_fep.stdout, self.output) sub_fep.stdout.close() =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:43
Часовой пояс: UTC + 5