- PVSM.RU - https://www.pvsm.ru -
Некоторое время назад на отличной конференции maintainerati [1] я пообщался с несколькими друзьями-мейнтейнерами о масштабировании по-настоящему больших проектов open source и о том, как GitHub подталкивает проекты к определённому способу масштабирования. У ядра Linux абсолютно иная модель, которую мейнтейнеры-пользователи GitHub не понимают. Думаю, стоит объяснить, почему и как она работает и чем отличается.
Ещё одной причиной для написания этого текста стала дискуссия на HN [2] по поводу моего выступления «Мейнтейнеры не масштабируются [3]», где самый популярный комментарий сводился к вопросу «Почему эти динозавры не используют современные средства разработки?». Несколько известных разработчиков ядра энергично защищали списки рассылки и предложение патчей через механизм, похожий на пулл-реквесты GitHub, Но по крайней мере несколько разработчиков графической подсистемы хотели бы использовать более современный инструментарий, который гораздо легче автоматизировать скриптами. Проблема в том, что GitHub не поддерживает тот способ, которым ядро Linux масштабируется на огромное число контрибуторов, и поэтому мы просто не можем перейти на него, даже для нескольких подсистем. И дело не в
Git классный, потому что каждый может очень легко сделать форк, создать свою ветку и изменять код. И если в итоге у вас получится нечто стоящее, вы создаёте пулл-реквест для основного репозитория и его рассматривают, тестируют и осуществляют слияние. И GitHub классный, потому что он представил подходящий UI, чтобы эти сложные вещи было приятно и просто находить и изучать, так что новичкам гораздо легче войти в курс.
Но если проект в итоге стал чрезвычайно успешным и никакое количество тегов, меток, сортировки, ботов и автоматизации уже не способны справляться со всеми пулл-реквестами и проблемами в репозитории, то приходит время снова разделить проект на более управляемые части. Более важно, что с определённым размером и возрастом проекта разным частям понадобятся разные правила и процессы: у сверкающей новой экспериментальной библиотеки иная стабильность и критерии CI, чем у основного кода, а может у вас в наличии мусорный бак legacy с кучей исключённых плагинов, которые уже не поддерживаются, но их нельзя удалять. Так или иначе, придётся разделить огромный проект на подпроекты, каждый с собственным процессом и критерием слияния патчей и с собственным репозиторием, где работают свои пулл-реквесты и отслеживание проблем. Обычно нужно от нескольких десятков до нескольких сотен контрибуторов, работающих полный рабочий день, чтобы головная боль от проекта выросла до такой степени, что станет необходимо разделение на части.
Почти все проекты на GitHub решают проблему путём разделения единого дерева исходников на множество различных проектов, каждый со своей отдельной функциональностью. Обычно это приводит к появлению ряда проектов, которые считаются ядром, плюс куча плагинов, библиотек и расширений. Всё связывается каким-то типа плагином или пакетным менеджером, который в некоторых случаях напрямую получает код из репозиториев GitHub.
Поскольку почти каждый большой проект устроен таким образом, я не думаю, что нам стоит углубляться в преимущества такого подхода. Но я хотел бы подчеркнуть некоторые из проблем, которые возникают в этой ситуации:
Конечно, всё это требует не такой уж большой работы, и многие проекты отлично справляются с управлением. Но здесь всё равно требуется больше усилий, чем простой пулл-реквест в единый репозиторий. Очень простые операции рефакторинга (как простое совместное использование единственной новой функции) происходят реже, и за долгое время такие издержки накапливаются. За исключением тех случаев, когда вы по примеру node.js создаёте репозитории для каждой функции, а затем по сути меняете Git на npm в качестве системы управления исходным кодом, и это тоже кажется странным.
Ядро Linux — один из нескольких известных мне проектов, который не разделён таким образом. Прежде чем мы рассмотрим, как он работает (ядро — это гигантский проект и он просто не может работать без некоторой структуры подпроектов), мне кажется, интересно посмотреть, зачем в Git нужны пулл-реквесты. На GitHub это единственный способ разработчикам внести предложенные патчи в общий код. Но изменения в ядре приходят как патчи в почтовом списке рассылки, даже через долгое время после внедрения и широкого использования Git.
Но уже первая версия Git поддерживала пулл-реквесты. Аудиторией этих первых, достаточно сырых, релизов были мейнтейнеры ядра, Git написали для решения мейнтейнерских проблем Линуса. Очевидно, Git был нужен и полезен, но не для обработки изменений от отдельных разработчиков: даже сегодня, а тем более тогда, пулл-реквесты использовались для обработки изменений целой подсистемы, синхронизации отрефакторенного кода или схожих сквозных изменений между различными подпроектами. Как пример, сетевой пулл-реквест 4.12 от Дэйва Миллера [5], представленный Линусом [6], содержит более 2000 коммитов от 600 разработчиков и кучу слияний для пулл-реквестов от подчинённых мейнтейнеров. Но почти все патчи сами по себе представлены мейнтейнерами и выбраны из почтовых списков рассылки, а не самими авторами. Такова особенность разработки ядра, что авторы в основном не коммитят патчи в общие репозитории — и вот почему Git отдельно учитывает автора патча и автора коммита.
Инновацией и улучшением в GitHub было использование пулл-реквестов для всего подряд, вплоть до отдельных патчей. Но не для этого пулл-реквесты изначально создавались.
На первый взгляд, ядро выглядит как единый репозиторий, где всё в одном месте в репозитории у Линуса. Но это далеко не так:
На первый взгляд это выглядит просто как изощрённый способ заполнить у каждого дисковое пространство кучей ерунды, которая ему не интересна, но есть и много сопутствующих незначительных преимуществ, которые накладываются друг на друга:
Огромное преимущество того, что рефакторинг и обмен кодом стал проще — не нужно тащить с собой кучу легаси-мусора. Это детально объясняется в документе об отсутствии бреда со стабильными API [11].
В общем, думаю, это гораздо более мощная модель, потому что вы всегда можете откатиться и делать всё также, как с многочисленными разъединёнными репозиториями. Чёрт, есть даже драйверы ядра, которые находятся в своём собственном репозитории, отдельном от основного ядра, как проприетарный драйвер Nvidia. Ну, это просто как клей исходного кода вокруг блоба, но поскольку он может содержать никаких частей ядра по юридическим причинам, это прекрасный пример.
Да и нет.
На первый взгляд ядро Linux выглядит как монорепозиторий, потому что в нём есть всё. И многие знают по собственному опыту, что монорепозитории доставлют много проблем, потому что с определённого размера они просто не могут масштабироваться.
Но если посмотреть ближе, такая модель очень, очень далека от единого репозитория Git. Одна только upstream-подсистема и репозитории драйверов уже дают вам несколько сотен. Если посмотреть на всю экосистему целиком, включая аппаратных вендоров, дистрибутивы, другие ОС на базе Linux и отдельные продукты, вы легко насчитаете несколько тысяч основных репозиториев и ещё много-много дополнительных. И это без учёта репозиториев Git чисто для личного использования отдельными разработчиками.
Ключевое отличие в том, что в Linux единая файловая иерархия как общее пространство имён для всего, но очень много различных репозиториев для всевозможных нужд и проектов. Это монодерево с многочисленными репозиториями, а не монорепозиторий.
Прежде чем я начну объяснять, почему GitHub не способен в данный момент обеспечить такой рабочий процесс, по крайней мере, если вы хотите сохранить преимущества GitHub UI и интеграции, нужно посмотреть на некоторые примеры, как это работает на практике. Если вкратце, то всё делается через пулл-реквесты Git между мейнтейнерами.
Простой случай — это прохождение изменений через иерархию мейнтейнеров, пока они в конце концов не осядут в дереве там, где нужно. Это легко, потому что пулл-реквест всегда идёт из одного репозитория в другой, так что его можно провести с нынешним GitHub UI.
Гораздо веселее с кросс-подсистемными изменениями, потому что тогда пулл-реквесты из ациклического графа превращаются в сетку. На первом этапе нужно рассмотреть изменения и протестировать их всеми вовлечёнными подсистемами и мейнтейнерами. В рабочем процессе GitHub это означает пулл-реквесты одновременно в многочисленные репозитории, с единым потоком обсуждения между ними. В разработке ядра этот этап осуществляется путём представления патча в кучу разных списков рассылки с указанием мейнтейнеров в качестве получателей.
Слияние отличается от рассмотрения патча. Здесь уже выбирается одна из подсистем как основная, она получает все пулл-реквесты, а все остальные мейнтейнеры соглашаются с таким вариантом слияния. Обычно выбирают ту подсистему, которую сильнее всего затрагивают изменения, но иногда выбирают ту, в которой уже идёт какая-то работа, которая конфликтует с пулл-реквестом. Иногда создают абсолютно новый репозиторий и команду мейнтейнеров. Это часто происходит для функциональности, которая распространяется на всё дерево и не очень аккуратно содержится в нескольких файлах и директориях в одном месте. Недавний пример — дерево отображений DMA [12], которое пытается объединить работу, до сих пор распределённую среди драйверов, мейнтейнеров платформы и групп поддержки архитектуры.
Но иногда есть многочисленные подсистемы, которые конфликтуют с набором изменений и которым всем нужно как-то решить нетривиальный конфликт слияния. В этом случае патчи не применяются напрямую (пулл-реквест Rebase на GitHub), а вместо этого используется пулл-реквест только с необходимыми патчами, на основании коммита, общего для всех подсистем — его вносят во все деревья подсистем. Такая общая база важна, чтобы избежать загрязнения дерева подсистем ненужными изменениями. Поскольку дальнейшие пуллы касаются только специфических тем, эти ветки обычно называют тематическими ветками.
В качестве примера могу привести поддержку audio-over-HDMI, в этот процесс я был вовлечён непосредственно. Она касается и графической подсистемы, и подсистемы звуковых драйверов. Одинаковые коммиты из одного и того же пулл-реквеста вошли в графический драйвер Intel [13], а также в звуковую подсистему [14].
Совершенно другой пример, что это не безумие — единственный сравнимый проект ОС [15] в мире тоже выбрал монодерево с потоком коммитов примерно как в Linux. Я говорю о ребятах с таким гигантским деревом, что им даже пришлось написать полностью новую виртуальную файловую систему GVFS [16] для его поддержки…
К сожалению, GitHub не обеспечивает поддержку такого рабочего процесса, по крайней мере, не нативно с GitHub UI. Конечно, это можно сделать просто с чистым инструментарием Git, но тогда вы возвращаетесь к патчам в списке рассылки и пулл-реквестам по почте, которые выполняются вручную. Я считаю, это единственная причина, почему сообщество разработчиков ядра ничего не выиграет от перехода на GitHub. Есть также небольшая проблема, что несколько ведущих мейнтейнеров настроены категорически против GitHub в целом, но это уже не технический вопрос. И дело не только в ядре Linux. Дело в том, что в принципе все гигантские проекты на GitHub имеют проблемы с масштабированием, потому что GitHub на самом деле не даёт им возможности масштабироваться на многочисленные репозитории, привязанные к монодереву.
Итак, у меня есть запрос только одной фичи на GitHub:
Пожалуйста, реализуйте пулл-реквесты и трекинг багов, охватывающие различные репозитории одного монодерева.
Простая идея, огромные последствия.
Во-первых, нужно сделать возможными многочисленные форки того же репозитория в одной организации. Просто посмотрите на git.kernel.org [8], большинство их репозиториев не являются личными. И даже если вы поддерживаете разные организации, например, для разных подсистем, требование наличия организации для каждого репозитория — глупое и избыточное, оно без какой-либо необходимости до предела затрудняет доступ и управление пользователями. Например, в графической подсистеме у нас было бы по одному репозиторию для каждого тестового набора userspace, общая библиотека userspace и общий набор инструментов и скриптов, которые используются мейнтейнерами и разработчиками, это GitHub поддерживается. Но потом вы добавите общий репозиторий подсистемы, плюс репозиторий для ключевой функциональности подсистемы и дополнительные репозитории для каждого крупного драйвера. Это всё форки, которые GitHub не делает. И у каждого из этих репозиториев будет куча веток: по крайней мере, одна для работы над функциями, а другая для исправления багов в текущем релизе.
Объединение всех веток в репозитарий не предлагать, поскольку смысл раздела на репозитории в том, чтобы разделить также пулл-реквесты и баги.
Родственный вопрос: нужно иметь возможность устанавливать связи между форками постфактум. Для новых проектов, которые всегда были на GitHub, это не является проблемой. Но Linux сможет перемещать максимум одну подсистему за раз, и на GitHub уже есть масса репозиториев Linux, которые не являются правильными форками друг друга.
Пулл-реквесты нужно привязать к нескольким репозиториям одновременно, сохраняя единый общий поток обсуждения. Вы уже можете переназначить пулл-реквест на другую ветку репозитория, но не на несколько репозиториев одновременно. Переназначение пулл-реквестов действительно важно, поскольку новые участники проекта будут просто создавать пулл-реквесты к тому, что они считают основным репозиторием. Боты затем могут перетасовать их с учётом всех репозиториев, перечисленных в файле MAINTAINERS для того набора файлов и изменений, которые содержит репозиторий. Когда я беседовал с сотрудниками GitHub, то сначала предложил им реализовать это напрямую. Но думаю, здесь всё можно автоматизировать скриптами, так что лучше будет оставить это только для индивидуальных проектов, поскольку тут нет единого стандарта.
Тут ещё довольно мерзкая проблема UI, потому что список патчей может отличаться в зависимости от ветки, куда идёт пулл-реквест. Но это не всегда ошибка пользователя, ведь какой-то из репозиториев уже может применить какие-то патчи.
Кроме того, статус пулл-реквеста должен отличаться для каждого репозитория. Один мейнтейнер может закрыть его, не принимая, поскольку решили, что его примет другая подсистема, в то время как другой мейнтейнер может произвести слияние и закрыть вопрос. В другом дереве могут даже закрыть пулл-реквест как невалидный, поскольку он неприменим для старой версии или форка от вендора. Ещё веселее, пулл-реквест может пройти через слияние несколько раз, с разными коммитами в каждой подсистеме.
Как и пулл-реквесты, баги могут относиться ко многим репозиториям, и нужно иметь возможность их перемещать. В качестве примера приведём баг, который сначала сообщили для репозитория ядра. После сортировки стало ясно, что это баг драйвера, который всё ещё присутствует в последней ветке разработки и, следовательно, относится к этому репозиторию, плюс к основной upstream-ветке и может к парочке других.
Статусы опять же должны быть отдельными, поскольку после появления багфикса в одном репозитории он не становится сразу доступен для всех остальных. Может даже потребуется портировать его на предыдущие версии ядер и дистрибутивов, а кто-то может решить, что баг не стоит того и закроет его как WONTFIX, даже если он помечен как успешно разрешённый в соответствующем репозитории подсистемы.
Ядро Linux не собирается переходить на GitHub. Но переход на модель масштабирования Linux как монодерева с многочисленными репозиториями станет хорошей концепцией для GitHub и поможет всем очень крупным проектам, которые уже размещаются там. Мне кажется, это даст им новый и более эффективный способ решать свои уникальные проблемы.
Автор: m1rko
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/git/262828
Ссылки в тексте:
[1] maintainerati: https://maintainerati.org/
[2] дискуссия на HN: https://news.ycombinator.com/item?id=13444560
[3] Мейнтейнеры не масштабируются: https://habrahabr.ru/post/320092/
[4] хостинге: https://www.reg.ru/?rlink=reflink-717
[5] сетевой пулл-реквест 4.12 от Дэйва Миллера: https://lkml.org/lkml/2017/5/2/508
[6] представленный Линусом: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8d65b08debc7e62b2c6032d7fe7389d895b92cbc
[7] стабильных ядер: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/
[8] kernel.org: https://git.kernel.org/
[9] дерева интеграции linux-next: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/
[10] MAINTAINERS: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/MAINTAINERS
[11] об отсутствии бреда со стабильными API: http://blog.ffwll.ch/2017/08/github-why-cant-host-the-kernel.html
[12] дерево отображений DMA: http://git.infradead.org/users/hch/dma-mapping.git/commit/2e7d1098c00caebc8e31c4d338a49e88c979dd2b
[13] вошли в графический драйвер Intel: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2844659842017c981f6e6f74aca3a7ebe10edebb
[14] в звуковую подсистему: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3c95e0c5a6fa36406fe5ba9a2d85a11c1483bfd0
[15] единственный сравнимый проект ОС: https://www.microsoft.com/windows/
[16] GVFS: https://github.com/Microsoft/GVFS
[17] Источник: https://habrahabr.ru/post/336470/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.