Свой git-хостинг внутри трекера: как мы встроили просмотр кода, диффы и деплой в систему управления проектами
Код проекта живёт в git, а работа над проектом - в трекере: задачи, сроки, переписка с клиентом, тайм-трекинг. И между ними - пропасть. Чтобы посмотреть, что именно поменялось в последнем коммите, разработчик уходит в отдельный git-сервис с чужим интерфейсом, а тимлид, который хочет быстро глянуть диффы по задаче, туда обычно и не доходит. Мы закрыли этот разрыв: встроили полноценный просмотр репозиториев прямо в наш трекер проектов. Коммиты, ветки, файлы, диффы как в настоящем git-хостинге - в одном окне с задачами. В этом кейсе разбираем, как это устроено на нативном git и почему мы заодно отказались от стороннего git-сервера в деплое.
Задача: код и работа над ним - в одном продукте
Мы развиваем собственный трекер - систему управления проектами, задачами и временем. В нём уже живут проекты, канбан, тайм-трекинг, вики, чаты и даже почта. Не хватало кода. Для хранения репозиториев мы использовали стороннее self-hosted git-решение (Gitea), но у него два минуса: отдельный интерфейс, в который надо переключаться, и лишний сервис в инфраструктуре. Мы захотели:
Смотреть репозитории внутри трекера: коммиты, ветки, файлы в любой ветке, диффы - не выходя из системы, где уже ведётся работа.
Свой интерфейс, а не чужой - под нашу дизайн-систему, без визуального разнобоя между разделами.
Без обёрток над чужим API: не «встроить Gitea в iframe», а сделать собственное хранилище и собственный просмотр.
Минус один сервис в инфраструктуре: меньше движущихся частей - меньше точек отказа.
Решение: всё на нативном git
Ключевое инженерное решение кейса - не писать свою реализацию git и не тащить JavaScript-порт git в зависимости. Движок у нас уже есть: это сам git, установленный на сервере. Бэкенд трекера обращается к нему напрямую - запускает git как процесс и читает результат. Никаких новых тяжёлых зависимостей, полная совместимость с любым репозиторием, предсказуемое поведение.
Что это даёт на практике: трекер читает историю коммитов, содержимое веток, дерево файлов и диффы ровно так, как их видит сам git. Мы написали собственный парсер унифицированного диффа - он превращает «сырой» вывод git в структуру файлы → блоки изменений → строки, которую уже удобно рисовать в интерфейсе с подсветкой и номерами строк.
Диффы - как в настоящем git-хостинге
Главное в работе с кодом - быстро понять, что изменилось. Поэтому диффам мы уделили больше всего внимания и сделали их привычными для тех, кто живёт в GitHub и GitLab:
Дерево изменённых файлов с количеством добавленных и удалённых строк по каждому файлу - сразу видно масштаб и где искать.
Добавленные строки - зелёным, удалённые - красным, с номерами строк до и после. Привычная и читаемая раскраска, а не блёклый «дефолт».
Подсветка синтаксиса прямо в диффе и при просмотре файлов - код читается, а не сливается в серую массу.
Клик по файлу в дереве прокручивает прямо к его изменениям - не нужно листать огромный дифф руками.
Просмотр файлов в любой ветке
Помимо диффов, можно просто ходить по репозиторию: переключить ветку, раскрыть дерево папок и открыть любой файл - с подсветкой синтаксиса. Удобно, когда нужно быстро свериться с актуальным состоянием кода или показать коллеге конкретный кусок, не клонируя репозиторий к себе.
Картинки в диффах - до и после
Отдельно сделали то, чего часто не хватает: изменения картинок видно глазами. Если в коммите поменяли логотип, иконку или фотографию, дифф показывает их рядом - «было» и «стало», - а не просто строчку «бинарный файл изменён». Для команды, где дизайн и фронтенд идут вместе, это экономит кучу переключений между инструментами.
Подключение репозиториев и состояние в ссылке
Репозитории подключаются в пару кликов: администратор указывает имя и путь к git-каталогу на сервере - ветка по умолчанию определится сама. Так в трекер заехали все рабочие репозитории команды.
Ещё один важный для удобства момент - состояние раздела зашито в адрес страницы: выбранный репозиторий, ветка, вкладка, открытый коммит или файл. Ссылку на конкретный дифф или файл можно скинуть коллеге в задачу, и он откроет ровно то же самое. Раздел открывается уже готовым с сервера (серверный рендеринг), без «моргания» и догрузки после открытия страницы.
Бонус: мы вообще ушли со стороннего git в деплое
Раз уж репозитории читаются нативным git на нашем сервере, логично было сделать следующий шаг и убрать сторонний git-сервис из процесса выкладки. Теперь у нас свой git прямо на боевом сервере и деплой по принципу «запушил - выкатилось»:
Центральный репозиторий (bare-репозиторий) лежит на нашем сервере - в него и пушим, как раньше в сторонний сервис.
Хук на стороне сервера ловит push в нужную ветку и сам обновляет код, ставит зависимости, собирает приложение и перезапускает процессы. Ветка для теста - выкатывается на тестовый контур, основная - на прод.
Миграции базы накатываются автоматически при старте. От разработчика - один git push, всё остальное делает сервер.
В результате из цепочки выпал целый внешний сервис: одной движущейся частью в инфраструктуре меньше, а поведение деплоя теперь полностью наше и прозрачное.
Стек и инженерные решения
Раздел кода - модуль внутри монорепозитория трекера, поэтому интерфейс, бэкенд и база едины, а код нативно соседствует с задачами и проектами.
Бэкенд: NestJS. Чтение репозиториев - обращение к нативному git как к процессу (без сторонних git-библиотек), плюс собственный парсер унифицированного диффа в структуру для отрисовки.
Фронтенд: Nuxt 3 с серверным рендерингом (SSR). Раздел и открытый дифф/файл приходят готовыми с сервера; подсветка синтаксиса - на клиенте.
Состояние в URL: репозиторий, ветка, вкладка, коммит и файл зашиты в адрес - ссылки на конкретные изменения шарятся и переживают перезагрузку.
Деплой: свой bare-репозиторий на сервере + серверный хук push-to-deploy, без внешнего git-сервиса.
Отдельно повозились с серверным рендерингом за авторизацией и согласованием дат между сервером и браузером - это классические подводные камни SSR, которые мы закрыли, чтобы раздел открывался мгновенно и без ошибок гидратации.
Результат
Код теперь живёт там же, где работа над ним. Тимлид и менеджер видят коммиты и диффы рядом с задачами и не идут за этим в отдельный сервис; разработчик переключается между «что в задаче» и «что в коде» в одном окне.
Просмотр репозиториев внутри трекера: коммиты, ветки, файлы, диффы с подсветкой и картинками - в едином интерфейсе.
Свой интерфейс под нашу дизайн-систему, без переключения в чужой сервис.
Минус сервис в инфраструктуре: хранение кода и деплой - на нативном git, своё и прозрачное.
Шарящиеся ссылки на дифф или файл, которые удобно кидать прямо в задачу.
Это наш типичный подход: вместо набора разрозненных сервисов - единая система, где всё связано. Так же мы недавно встроили в трекер полноценную почту с клиентами, а вообще делаем веб-системы и автоматизацию под задачи бизнеса - от CRM и личных кабинетов до внутренних инструментов, которые экономят команде часы рутины.
Частые вопросы
Это обёртка над GitHub или GitLab?
Нет. Это собственное хранилище репозиториев на нашем сервере и собственный просмотр на нативном git, а не интеграция с чужим хостингом. Поэтому интерфейс полностью наш и данные - у нас.
Зачем своё, если есть GitHub/GitLab/Gitea?
Чтобы код был в одном продукте с задачами, сроками и перепиской, под единым интерфейсом, и чтобы убрать лишний сервис из инфраструктуры. Для команды, которая живёт в трекере, это меньше переключений и меньше движущихся частей.
Можно ли так же сделать для нашей команды?
Да. Подход переносимый: нативный git как движок, свой просмотр и свой push-to-deploy. Объём зависит от того, что нужно - только просмотр коммитов и диффов или ещё и собственный хостинг с выкладкой.
Сколько стоит такая разработка?
Зависит от глубины: базовый просмотр репозиториев и диффов - одна история, полноценный git-хостинг со своим деплоем - другая. Оценку даём после короткой аналитики ваших сценариев.
Нужна система, где код, задачи и процессы собраны в один продукт - или встроенный инструмент под ваши процессы? Обсудим задачу и предложим решение: разработка веб-систем и автоматизация.