11 KiB
gitfame
В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов git репозитория.
Статистики
- Количество строк
- Количество коммитов
- Количество файлов
Все статистики считаются для состояния репозитория на момент конкретного коммита.
Расчёт
Каждой строке интересующего подмножества файлов репозитория сопоставляется последний коммит, модифицировавший эту строку. Пустым файлам сопоставляются последние менявшие их коммиты.
После этого для каждого уникального автора, получившегося множества коммитов, считается количество строк, уникальных коммитов и файлов, которые затрагивали коммиты автора.
Нужную информацию можно получить с помощью команды git blame
:
✗ git blame utf8/README.md
f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 1) # utf8*
f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 2)
f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 3) Задача объединяет несколько задач на строки и unicode.
f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 4)
f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 5) Задача считается решённой, если все подзадачи решены.
git blame
с флагом --porcelain
(см. git blame --help
) возвращает информацию в машиночитаемом формате.
Кроме того, этот формат схлопывает соседние строки относящиеся к одному коммиту,
что может сильно сократить размер результата. Поэтому использовать нужно его.
Стоит помнить, что не все файлы из директории git проекта обязательно принадлежат git репозиторию.
Получить список файлов git репозитория можно разными способами, например с помощью git ls-tree
.
Интерфейс
Утилита должна печатать результат в stdout. При использовании невалидного значения флага или любой другой ошибке программа должна завершаться с ненулевым кодом возврата.
Расчёт может занимать довольно длительное время. Хорошим качеством подобной утилиты является отображение прогресса (вынесенное за флаг). Прогресс можно печатать в stderr в произвольном формате.
Флаги
Утилита должна поддерживать следующий набор флагов:
--repository — путь до Git репозитория; по умолчанию текущая директория
--revision — указатель на коммит; HEAD по умолчанию
--order-by — ключ сортировки результатов, один из lines
(дефолт), commits
, files
.
По умолчанию результаты сортируются по убыванию ключа (lines, commits, files)
.
При равенстве ключей выше будет автор с лексикографически меньшим именем.
При использовании флага соответствующей поле в ключе перемещается на первое место.
--use-committer — булев флаг, заменяющий в расчётах автора (дефолт) на коммиттера
--format — формат вывода; один из tabular
(дефолт), csv
, json
, json-lines
;
tabular
:
Name Lines Commits Files
Joe Tsai 64 3 2
Ross Light 2 1 1
ferhat elmas 1 1 1
Human-readable формат. Для паддинга используется пробел. см. text/tabwriter.
csv
:
Name,Lines,Commits,Files
Joe Tsai,64,3,2
Ross Light,2,1,1
ferhat elmas,1,1,1
json
:
[{"name":"Joe Tsai","lines":64,"commits":3,"files":2},{"name":"Ross Light","lines":2,"commits":1,"files":1},{"name":"ferhat elmas","lines":1,"commits":1,"files":1}]
json-lines
:
{"name":"Joe Tsai","lines":64,"commits":3,"files":2}
{"name":"Ross Light","lines":2,"commits":1,"files":1}
{"name":"ferhat elmas","lines":1,"commits":1,"files":1}
--extensions — список расширений, сужающий список файлов в расчёте; множество ограничений разделяется запятыми, например, '.go,.md'
--languages — список языков (программирования, разметки и др.), сужающий список файлов в расчёте; множество ограничений разделяется запятыми, например 'go,markdown'
Принадлежность файла к языку программирования определяется с помощью его расширения. В configs/language_extensions.json лежит маппинг. Неизвестные языки никаких ограничений не накладывают. При их использовании можно написать warning в stderr.
--exclude — набор Glob паттернов, исключающих файлы из расчёта, например 'foo/*,bar/*'
--restrict-to — набор Glob паттернов, исключающий все файлы, не удовлетворяющие ни одному из паттернов набора
Тесты
В /tests/integration/testdata/bundles лежат запакованные git репозитории. Каждый интеграционный тест ссылается на какой-нибудь бандл.
Как создать bundle? Находясь в git репозитории выполнить
git bundle create my.bundle --all
Как распаковать bundle? Находясь в пустой директории.
git clone /path/to/my.bundle .
Ненавязчивые предложения
Project layout
В go есть набор рекомендаций по организации структуры проекта.
И подпроект этого задания уже частично ему следует. Например, main.go
, который вам нужно реализовать, лежит в cmd/gitfame,
интеграционные тесты в /test
.
В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне. Здесь же, для ознакомления предлагаем изучить общепринятый подход.
Cli
Можно познакомиться с spf13/cobra, популярной библиотекой для написания cli. В этой задаче cobra поможет распарсить аргументы, написать подробный help message, сделать алиасы для флагов.
Git ликбез
Вся информация взята из книги.
Смело советуем прочитать её всем, даже если в контексте задания вы знаете про Git достаточно.
Объекты Git хранятся в специальной базе данных Git Object Database
в директории .git.
В базе в сжатом виде хранятся объекты разных типов.
У каждого объекта есть SHA-1 хэш, а также небольшой header.
Несколько основных типов объектов:
- blob — соответствует файлу; хранит его данные (только содержимое)
- tree — соответствует директории; хранит список блобов и деревьев, а также их описание (имена файлов, типы, права доступа)
- commit — соответствует истории изменения дерева; хранит указатель на дерево, автора изменений, субъекта, добавившего изменения (committer), сообщение с описанием изменений, ссылку на предыдущие (родительские) коммиты
branch (ветка) — это не объект Git Object Database
, а всего лишь файл в директории .git/refs/heads/
с хэшом последнего для этой ветки коммита.
То есть ветка — это указатель на коммит.
head — это ссылка на коммит. В каждом репозитории по умолчанию есть head именем master.
HEAD — один выделенный head. Файл .git/HEAD
. Родитель следующего коммита.
HEAD может ссылаться на коммит напрямую (detached HEAD). Следующий коммит в таком случае не будет принадлежать никакой ветке.
Гораздо чаще HEAD ссылается на ветку. В таком случае следующий коммит "попадёт" в ту же ветку и продвинет HEAD. HEAD определяет текущую активную ветку.
revision (ревизия) — способ сослаться на Git объект.
Например, SHA-1 коммита — это ревизия на коммит,
HEAD@{5 minutes ago}
— это ревизия на последний коммит на момент 5 минут назад,
HEAD:README
— это ревизия на блоб.
Критерии сдачи
Решение должно проходить все тесты, так же как и в обычной задаче.
После прохождения тестов в табличке появится 0. Нужно успеть отправить решение до дедлайна! Проверяющие посмотрят на решение и заменят 0 на 1.