Генеалогическое древо внутри Git

    Поздравляю всех с днем программиста! Желаю больше ярких "коммитов", принятых "пулл-реквестов", меньше незапланированных "мержей" и чтобы ваши ветви жизни оставались актуальными как можно дольше. В качестве идейного подарка предлагаю реализацию генеалогического древа средствами системы контроля версий Git. Ну что же… звучит как план!


    Kochurkins


    Для тех, кто сразу все понял, выкладываю исходники генератора: GenealogyTreeInGit и сами генеалогические древа — мое и президентов США.


    Кроме того, я реализовал простой социальный граф. Он отображает не только степень родства, но и статус отношений между потомками, отображает такие события как свадьба, развод, рождение ребенка, а также вклады в отношения тех или иных сторон.


    Git


    Напомню, что Git — одна из самых популярных систем контроля версий. Она мощная: в ней можно фиксировать изменения (commit), создавать и сливать ветви (checkout и merge), сравнивать разные версии файлов (diff), вычислять авторов конкретных строк (blame), а также делать многие другие вещи.

    К счастью или к сожалению, Git в чем-то похож на государство-победителя: он позволяет менять историю, а именно менять даты, сообщения и авторов коммитов. Но это наоборот содействует в том, что позволяет добавлять членов семьи, как будто они авторы событий, сделанных в конкретную дату.


    Начал я с простого: написал несколько команд и вуаля — фрагмент древа готов. Отлично. Теперь надо это проделать со всей армией родственников. Я с радостью напишу для них 200 строчек команд, в которых можно запутаться, а для президентов — все 10К!



    Добавили меня в список идиотов? Вычеркивайте. Конечно же, я автоматизировал процесс и написал приложение для конвертации генеалогических данных в последовательность команд гита. Существуют несколько форматов таких данных, я выбрал GEDCOM.


    Gedcom


    GEDCOM — формат описания генеалогических древ. Довольно старый, но текстовый и в целом простой. Спецификация формата неплохо описана в интернете. Поддерживается чуть ли не всеми генеалогическими программами, поэтому примеров для него существует множество: древо президентов США, королевской династии, Шекспира.

    Я реализовал все это безобразие на .NET Core — он удобный и кроссплатформенный. Для парсинга и обработки GEDCOM есть несколько библиотек под C#, например GeneGenie.Gedcom, gedcomx-csharp. Я решил написать собственную на основе GedcomParser. Ну потому что она обладает фатальным недостатком… На самом деле, нет: мне захотелось самому лучше разобраться в формате и избавиться от всех зависимостей, что позволит при желании легко портировать проект на другие языки.


    Генерация команд


    Настал момент обхода извлеченных личностей в удобном нам формате и генерации команд Git для них. Было решено отсортировать все события в хронологическом порядке, а затем создавать ветки, мержить и коммитить их, продвигаясь в порядке возрастания дат. К сожалению, не у всех событий есть даты, поэтому пришлось изрядно повозиться чтобы отсортировать все события правильно. На носу 2^2^3, и я осознал что такой подход не совсем правильный, так как при обходе в глубину с датами возиться вообще не пришлось бы. Исправлюсь позже (но это не точно).


    Инициализация


    Все что требуется на этом этапе — инициализировать репозиторий:


    mkdir Family
    cd Family
    git init

    События


    В этой части скрипта обходятся и коммитятся все события. Для этого использовались такие команды:


    • git checkout --orphan branch_name
    • git merge @I1@ --allow-unrelated-histories --no-commit
    • git commit -m "msg" --date "" --author "name <email>" --allow-empty

    Первая, checkout, создает ветвь для каждой личности. Флаг --orphan позволяет создавать ветки-сироты, т.е. ветки не имеющие родителей. Ветка-сирота создается один раз — при последующем переключении checkout этот параметр опускается. В конечном итоге практически все коммиты имеют родителей, за исключением самых дальних предков, потому что для них более ранние неизвестны.


    Вторая команда, merge, объединяет родителей и создает ребенка. Будем писать в сообщении коммита Birth — рождение с соответствующим годом. Также указываем флаги --allow-unrelated-histories и --no-commit для возможности мержить ветки-сироты и для того, чтобы закоммитить изменения позже. Некоторые дети являются приемными, поэтому для них будем писать Adopted. Забавно, но Git позволяет создавать шведские семьи, т.е. мержить одновременно несколько веток. А еще ветки не имеют пола, что придется по душе любителям "родитель 1" и "родитель 2".


    Наконец, третья команда, commit, создает коммит с сообщением -m, датой --date и автором --author. Как уже писал, Git позволяет подменять сообщение, автора и дату коммита. Более того, Git позволяет создавать коммиты без файлов с помощью флага --allow-empty и без сообщений с помощью --allow-empty-message. У автора также необходимо указывать электронную почту, но Git принимает и пустую — нужно просто передать <>. К сожалению, Git не уважает стариков: дата коммита почему-то снизу ограничена 1 январем 1970 года — более ранняя дата будет неправильно отображаться. Однако все не так страшно: можно просто записывать реальную дату в описание. Тем не менее, Git верит в будущее и принимает даты в будущем — обратите внимание на моего сына Git. Матерей- и отцов-одиночек, кстати, тоже можно создавать.


    Социальный граф


    В социальном графе фиксируются и другие события помимо рождения: крещение, изменение места жительства, получение образования, женитьба, развод, смерть, похороны. После смерти ветвь попадает в цифровой рай в ветви невозможно появление последующих событий, разве что кроме похорон. На сервере такую ветвь вообще можно запечатать, т. е. сделать protected branch (не волнуйтесь: в будущем "воскресить" можно будет при необходимости).


    Событие "Свадьба" имеет двух предков — супругов. А "Развод" имеет одного предка — предыдущее событие "Свадьба". Над семейной жизнью надо работать как и над детьми, поэтому можно сказать что после свадьбы появляется еще и новый потомок — "отношения", которые заканчиваются после развода. Ну и возобновляются опять после следующих циклов свадьба-развод. Кроме того, в отношениях могут участвовать несколько человек (слияние нескольких веток).


    Финализация


    Добавим вишенку к торту: сделаем бекап репозитория и выгрузим всех людей на GitHub, GitLab, либо любой другой сервер, поддерживающий Git. Можно пушить все ветки одну за другой, но мы с помощью волшебной команды запушим их все, что намного быстрее и проще:


    git remote add origin https://gitlab.com/KvanTTT/Family.git
    git push origin --all -u

    Для генерации обычного генеалогического древа нужно передавать флаг --only-birth-events при запуске генератора. В этом случае будет создаваться по одному коммиту на человека (рождение). В противном случае будет генерироваться социальная сеть социальный граф.


    Примеры


    В качестве небольшого примера, который по крайней мере везде откроется, я создал свое генеалогическое древо, а в качестве большого примера — древо президентов США (2145 человек). Они доступны в репозиториях Kochurkins и Presidents соответственно. Для создания своего я использовал сервис geni.com, откуда экспортировал древо в GEDCOM. Сгенерированный скрипт создания генеалогического репозитория доступен в Gist.


    Presidents


    На GitHub, да и на GitLab можно перемещаться по предкам и потомкам. Это похоже на генеалогические вики-системы Familypedia или WeRelate. Правда гит(х|л)абы в чем-то более продвинуты: из них древа легко выкачиваются (с помощью команды --clone). А главное, можно открыть сразу весь граф. (В существующих генеалогических программах почему-то возникали сложности с открытием в полном объеме даже небольших графов.) Причем делать это можно с помощью разных инструментов (веб-сервис, Git Extesions, Sourcetree, GitKraken и других). Кроме того, эти сервисы можно использовать бесплатно, в отличие от большинства генеалогических.


    Примечательно, в гит*абах доступно даже какое-то подобие аналитики: можно узнать у кого самая инстаграмная жизнь насыщенная событиями жизнь. Ну или самая открытая: на вкладке Insights отображается список людей в порядке уменьшения коммитов.


    Pulse


    К сожалению, большие древа GitHub и GitLab отображают некорректно, однако хранят их правильно — можно затянуть репозиторий и убедиться в этом. Вот как выглядит мое дерево в веб-интерфейсе гитлаба:


    Kochurkins GitLab


    Проблемы


    Не очень ясно, как дополнять историю с корней. Пока что приходится генерировать ее полностью из GEDCOM-файла. Не исключаю что это можно сделать с помощью хитрого rebase — можете поэкспериментировать и рассказать в комментариях. Также было бы неплохо переписать код чтобы он работал "коммит-ориентированно", а не "событийно ориентированно", т.к. это естественней относительно гита: на самом деле, ветви в нем являются последовательностью коммитов, а не отдельными сущностями. Еще я думал, как можно привязать теги и подмодули, но пока что знаю как это лучше сделать.


    Заключение


    Если расширить идею генеалогических древ дальше, на веб-сервисы для разработчиков, то с помощью issues можно заводить разные глобальные задачи и распределять их по разным вехам (milestone): детство, юность, взрослая жизнь, старость.


    Помимо генеалогических древ, можно превращать другие буханки хлеба в троллейбусы кодировать в гите генеалогические древа языков программирования (это еще больше "по-кодерски"), синтаксические деревья и вообще любые древовидные структуры. Гит можно освоить и домохозяйкам, чтобы выстраивать взаимосвязи между героями бразильских сериалов :)


    Практическая польза: эта разминка помогает лучше понять устройство гита, его команды, а также формат описания генеалогических деревьев GEDCOM.


    Исходники самой статьи доступны на GitHub — присылайте pull request туда, если найдете ошибки или захотите дополнить контент. Для конвертации в формат habr.com используется библиотека MarkConv.

    • +40
    • 8,9k
    • 8
    Поделиться публикацией

    Похожие публикации

    Комментарии 8
      +5

      Можно пойти дальше, секвенировать свой геном, жены, детей, потом смержить две ветки, поменять в паре мест нестыковки и получить геном детей. Я пока не придумал, зачем… Но это было бы нереально круто:) и дорого(

        +1
        Чтобы заранее понимать баги будущего поколения хД
          +5
          Там много проблем, в т.ч. фундаментальных, которые просто развитием техники нерешаемы. Если что — я просто активно интересуюсь, профессионалы — поправьте, пожалуйста, если где-то наврал.

          1. Геном человека — это строка длиной 3 млрд из 2-букв символов, т.е. около 750 Мб (как раз на CD влезает). Для гита многовато, хотя и возможно.

          2. Если только гены хранить, а не всю ДНК, выходит 28 тыс. генов средней длиной 1000 букв, т.е. ~7Мб. Реально, но уже известно, что некодирующие участки ДНК сильно влияют на выработку белков. А без них человека в пробирке уж точно не вырастишь :)

          3. Плюс такое количество букв крайне сложно прочитать без ошибок, и в реально прочитанном полном геноме будут ошибки. К тому же, чтобы сделать чтение дешевле, обычно
          многократно читают относительно короткими кусками по 100-1000 нуклеотидов-букв, а потом специальными программами-ассемблерами «склеивают» их вместе, что тоже вносит ошибки (одна из основных проблем — тандемные повторы).
          Т.е. если через 20 лет прочитать геном того же человека, он будет слегка отличаться от прочитанного сейчас из-за уменьшившегося числа ошибок (если технологии продолжат развиваться).

          Переписывать всю историю, заново разрешая конфликты при merge — бррр!

          4. Плюс у нас 2 набора хромосом, один от папы и один от мамы. При создании сперматозоида они встают друг напротив друга, делятся на короткие участки, и обмениваются ими (этот процесс называется рекомбинация). Затем один такой набор хромосом упаковывается в сперматозоид. Аналогично — при создании яйцеклетки (кстати, к моменту рождения девочки все ее яйцеклетки уже сформированы, т.е. наполовину человек появляется еще в матке свой бабушки).

          5. Также потомок отличается от обоих родителей на ~50 нуклеотидов в случайных местах (мутации в процессе рекомбинации).

          П.п. 4 и 5 относительно хорошо ложатся на разрешение конфликтов при merge :)

          6. Во всех клетках есть митохондрии — маленькие клеточки, сотни миллионов лет жившие сами, а потом проникшие в крупные клетки и ставшие симбионтами со своим ДНК. Передаются только по материнской линии, т.к. из сперматозоида берется только основная ДНК. ОК, можно положить в отдельный файл и копировать всегда ребенку от матери.

          7. У бактерий очень распространен и крайне важен горизонтальный перенос генов, т.е. одна особь выбрасывает копию части своего генома из клетки, а другая подбирает и встраивает в свой. Причем такое есть и у многоклеточных — бделлоидных коловраток. Т.е. если и для других животных делать гит, то это будет бесконечная череда мержей, в которой крайне сложно будет разобраться.

          8. Есть люди-химеры. Это когда должны были родится двойняшки (две яйцеклетки созрели одновременно и были оплодотворены разными сперматозоидами), но на самых ранних этапах (2, 4, 8, 16 клеток) они слились в один организм. У такого человека левая рука и правая нога имеют один геном, а правая рука и левая нога — другой (его брата или сестры). Больше всего повезло тому брату/сестре, чьи яички/яичники — у него/нее будет потомство. Что писать в такой геном — не очень понятно. Геном яичек? А если левое — от одного, а правое — от другого?

          9. Кажется, у собак бывает так, что если два самца оплодотворят самку, то очень редко в яйцеклетку могут войти два сперматозоида, их геномы там сольются и вытеснят геном самки(!). Получится ребенок двух самцов, а самка его просто выносит, хотя к ней он не будет иметь никакого отношения (кроме крошечной митохондриальной ДНК). Получается мерж троих родителей! У людей, кстати, недавно такое тоже было сделано (искусственно) — для лечения редкого генетического заболевания, правда 2 матери и 1 отец.

          10. В процессе жизни человек накапливает мутации в ДНК. Т.е. через несколько лет геном может отличаться от сегодняшнего и в реальности. Рак, например, это сильно мутировавшие клетки хозяина, причем все по-разному и продолжающие быстро меняться.
          Как их записывать будем — вообще непонятно.

          11. Есть венерическая саркома собак. Это странный вид рака, когда при половом акте раковая клетка передается партнеру, и начинает размножаться в его организме. Геном опухоли псовый, но заметно отличающийся от генома носителя. Судя по всему, какая-то собака, жившая 6 тысяч лет назад, стала бессмертной, правда, вернувшись в одноклеточную форму жизни.
          Тоже непонятно как записывать такое в гите.

          12. Мутации идут по разным путям и с разной скоростью. Как я понимаю, предшественники сперматозоидов тоже не все абсолютно одинаковы. Если когда-нибудь захотим получить полную копию человека, это тоже будет важно.

          Это только то, что я смог вспомнить. В реальности проблем, в т.ч. фундаментальных гораздо больше.
          +2
          Однако, github не показывает даты коммитов до создания мира… :)
            +1
            Забавный пример использования… Я тут потихоньку пилю свое исследование. Собираюсь опубликовать здесь же, на хабре — методологию и некоторые выводы.

            Кстати, граф легко можно было бы отрисовать старым добрым graphViz…
            graphs.grevian.org/example
            stackoverflow.com/questions/2271704/family-tree-layout-with-dot-graphviz
              0
              Хм, интересно, а что за исследование? Публикуйте!

              Да, конечно, с Graphviz знаком — это хорошая, бесплатная, опенсорсная либа для рендеринга графов.
                +1
                Я еще не закончил, но написать уже можно. Анализ метрических книг одного села. Excel, sql.
                  +1
                  Предложу обратить внимание на методы, реализованные в GenoPro.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое