Поиск по коду — одна из тех функций, ценность которых ощущается мгновенно. Она либо есть и экономит часы, либо её нет — и ты начинаешь открывать файлы вручную, клонировать репозиторий, запускать find или средства своей IDE и вспоминать «где же это было».
Мы добавили в GitVerse поиск по коду в репозиториях. и сделали это быстро. Не потому что «срезали углы», а потому что опирались на инструмент, который десятилетиями решает задачу поиска по коду внутри Git: git grep. Пока другие поднимают тяжёлые поисковые платформы, возводят кластеры, строят индексаторы, мы выбрали простое и проверенное решение, которое работает прямо сейчас.

Git — не монолит, а конструктор

Если смотреть на Git поверхностно, он кажется достаточно монолитным: commit, push, merge. В целом. любому рядовому разработчику знания трёх-четырёх команд уже достаточно для повседневной работы, и совсем уже высшим пилотажем кажутся знания о том, что команда git pull – это последовательность команд git fetch и git merge, которую обычно вспоминают только на собеседовании.

Но если смотреть исторически, то Git — это набор небольших команд и форматов данных, которые хорошо комбинируются между собой. Это важная деталь: в Git много «простых кирпичиков», и именно из них собираются сложные сценарии.

Внутри репозитория Git хранит не «файлы как в папке директории», а базу объектов:

  • blob — содержимое файла (байты);

  • tree — структура каталогов: имена → (режим, тип, хеш объекта);

  • commit — снимок проекта (указатель на tree) + метаданные + родители;

  • tag — аннотированная ссылка на объект.

Все объекты адресуются по хешу (content-addressing). Если файл не менялся, то его blob остаётся тем же объектом, и Git не «пересохраняет» его заново, а переиспользует данные. Для хостинга это означает простую и практичную вещь: Git уже оптимизирован под работу с историей, деревьями и чтением контента.

Техническая вставка: объекты Git

Проверим, как оно работает на самом деле. Не будем же мы доверять статьям из интернета. Вот несколько команд, которые показывают, что Git — это в значительной степени работа с объектами и ссылками:

  • Узнать, на какой коммит указывает ветка: git rev-parse main

  • Посмотреть commit-объект как текст: git cat-file -p HEAD

  • Посмотреть дерево файлов на коммите: git ls-tree -r --name-only HEAD

  • Посмотреть содержимое файла на коммите (без checkout): git show HEAD:path/to/file.go

Смысл этих команд не в том, чтобы вы выполняли их каждый день, а в иллюстрации: Git из коробки умеет адресно и эффективно доставать «снимок репозитория» и конкретные файлы в нём.

Почему это напрямую относится к поиску

Поиск по коду на платформе — это, по сути, операция «найди совпадения в файлах в ветке X». И Git уже умеет:

  • взять дерево файлов для конкретного ref (ветка, тег, коммит);

  • обойти пути в этом дереве;

  • прочитать содержимое blob’ов;

  • применить к контенту фильтр (поиск строки/шаблона). Поэтому прежде чем строить отдельную инфраструктуру индексирования, мы задали простой вопрос: если Git уже так хорошо работает с данными репозитория, то можем ли мы дать пользователю нужную функцию быстрее, опираясь на готовые возможности?

git grep: поиск, который понимает репозиторий

git grep — это grep, встроенный в Git и понимающий его модель данных. Его ключевое отличие от обычного grep или ripgrep в том, что он умеет искать не только в текущей папке, но и в контенте на конкретной ветке или коммите, то есть в конкретном Git ref.

Какие режимы поиска бывают

В поиске обычно важны три сценария:

  • Обычный поиск строки (простая подстрока).

  • Поиск по регулярному выражению.

  • Более строгие режимы сопоставления (например, фиксированные строки или особые правила). В Git это выражается флагами и режимами, которые позволяют контролировать интерпретацию шаблона и поведение поиска.

Контекст вокруг совпадения

Почти всегда недостаточно просто увидеть «файл + номер строки». Нужен фрагмент кода вокруг: хотя бы пару строк, чтобы понять, это то место или нет. git grep поддерживает выдачу с номерами строк и контекстом.

Pathspec: как ограничить область поиска правильно

Репозитории содержат много «шумных» зон: зависимости, vendor, сборочные директории, большие файлы. Git даёт встроенный механизм, чтобы управлять местом поиска: pathspec. Он позволяет:

  • включать пути по glob-шаблонам;

  • исключать пути по glob-шаблонам;

  • комбинировать правила. И самое важное: это делает не внешний скрипт, а сам Git.

Техническая вставка: примеры git grep «по-взрослому»

Вот вам несколько примеров, которые хорошо иллюстрируют «репозиторный» характер поиска. Попробуйте их в каком-нибудь своем репозитории:

  • Поиск строки в текущей рабочей директории: git grep "Initialize" --

  • Поиск по конкретной ветке без checkout: git grep "Initialize" main --

  • Поиск по конкретному коммиту: git grep "Initialize" a1b2c3d --

  • Поиск с регулярным выражением (примерно): git grep -n -E "Init(ialize|ialise)" main --

  • Поиск с включениями и исключениями (идея, аналогичная pathspec): git grep "TODO" main -- "*.go" ":(exclude)*_test.go" Синтаксис pathspec в Git мощный и местами непривычный, но он решает задачу «искать там, где нужно» нативно и эффективно.

Что именно мы сделали в GitVerse

В GitVerse теперь доступен поиск по коду в репозитории. На практике это выглядит так:

  • вводите запрос (слово, фразу, регулярное выражение);

  • выбираете ветку (или используется ветка по умолчанию);

  • выбираете тип поиска, например, по регулярному выражению;

  • получаете постраничную выдачу совпадений. Технически на сервере мы сделали обвязку вокруг git grep:

  • проверяем запрос;

  • определяем ветку;

  • применяем режим поиска (обычный, по регулярному выражению, строгий);

  • ограничиваем области поиска;

  • возвращаем результаты с контекстом и номерами строк.

Такой подход даёт достаточно хорошую точность, не требуя читать целые файлы и не создавая лишней нагрузки.

Почему такой подход выгоден: быстро, просто, надёжно

«Большой» поиск обычно означает отдельный мир: индексатор, очереди, воркеры, хранение индекса, шардирование, репликация, мониторинг, продуманная схема прав доступа, стратегия обновления индекса на каждый push/force-push, работа с форками и т. д.
Это всё решаемо, но это уже отдельный продукт внутри продукта. И он не всегда нужен на первом шаге.
Мы выбрали подход, который дает максимальную ценность быстро:

  • Git уже хранит данные и умеет эффективно их читать;

  • git grep уже умеет искать по дереву на ветке;

  • нам осталось аккуратно встроить это в API, добавить фи��ьтры, контекст, пагинацию и нормальную выдачу. На реализацию и тестирование ушла примерно одна неделя. Это и есть наш «скейтборд»: он едет уже сейчас и решает реальную задачу.

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

Планы: дойти до «космического корабля», но итеративно

Мы не утверждаем, что текущая реализация — финальная точка. Это прочная база, от которой можно расти. Дальше мы планируем развивать поиск по мере роста нагрузки и запросов пользователей.

Мы хотим прийти к «космическому кораблю», но не через долгую стройку ради стройки, а через понятные работающие этапы. Сначала — скейтборд. Потом — велосипед. Дальше — по ситуации.