shad-go/gitfame/README.md

15 KiB
Raw Blame History

gitfame

В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов git репозитория.

✗ gitfame --repository=. --extensions='.go,.md' --order-by=lines
Name                   Lines Commits Files
Joe Tsai               12154 92      49
colinnewell            130   1       1
Roger Peppe            59    1       2
A. Ishikawa            36    1       1
Tobias Klauser         33    1       2
178inaba               11    2       4
Kyle Lemons            11    1       1
Dmitri Shuralyov       8     1       2
ferhat elmas           7     1       4
Christian Muehlhaeuser 6     3       4
k.nakada               5     1       3
LMMilewski             5     1       2
Ernest Galbrun         3     1       1
Ross Light             2     1       1
Chris Morrow           1     1       1
Fiisio                 1     1       1

Статистики

  • Количество строк
  • Количество коммитов
  • Количество файлов

Все статистики считаются для состояния репозитория на момент конкретного коммита.

Интерфейс

Утилита должна печатать результат в stdout. При использовании невалидного значения флага или любой другой ошибке программа должна завершаться с ненулевым кодом возврата.

Расчёт может занимать довольно длительное время. Хорошим качеством подобной утилиты является отображение прогресса (вынесенное за флаг). Прогресс можно печатать в stderr в произвольном формате.

Расчёт

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

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

Нужную информацию можно получить с помощью команды 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.

Для вызова команд git можно использовать os/exec.

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

Флаги

Утилита должна поддерживать следующий набор флагов:

--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

encoding/csv

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}]

encoding/json

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/*'

Для работы с Glob'ом в стандартной библиотеке есть path/filepath.

--restrict-to — набор Glob паттернов, исключающий все файлы, не удовлетворяющие ни одному из паттернов набора

Тесты

Команда для запуска тестов:

go test -v ./gitfame/test/integration/...

В /tests/integration/testdata/bundles лежат запакованные git репозитории. Каждый интеграционный тест ссылается на какой-нибудь бандл.

Как создать свой bundle? Находясь в git репозитории выполнить

git bundle create my.bundle --all

Как распаковать bundle? Находясь в пустой директории.

git clone /path/to/my.bundle .

Сборка приложения

Как собрать приложение?

(cd gitfame/cmd/gitfame && go build .)

В gitfame/cmd/gitfame появится исполняемый файл с именем gitfame.

Как собрать приложение и установить его в GOPATH/bin?

go install ./gitfame/cmd/gitfame/...

Чтобы вызывать установленный бинарь без указания полного пути, нужно добавить GOPATH/bin в PATH.

export PATH=$GOPATH/bin:$PATH

После этого gitfame будет доступен всюду.

В реализации вы можете использовать сторонние библиотеки, которые уже доступны в нашем go.mod. Менять go.mod нельзя.

Весь репозиторий курса - это один go модуль с одним go.mod файлом в корне. Корню репозитория соответствует импорт путь gitlab.com/slon/shad-go. Если вы создадите пакет в директории gitfame/internal/git, то импортировать его нужно будет по пути gitlab.com/slon/shad-go/gitfame/internal/git.

Code review comments

Прочитайте и исправьте распространённые ошибки.

Ненавязчивые предложения

Project layout

В go есть набор рекомендаций по организации структуры проекта. И подпроект этого задания уже частично ему следует. Например, main.go, который вам нужно реализовать, лежит в cmd/gitfame, интеграционные тесты в /test.

В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне. Здесь же, для ознакомления предлагаем изучить общепринятый подход. В частности, писать реализацию в internal или pkg (в чём разница?).

Cli

Можно познакомиться с spf13/cobra, популярной библиотекой для написания cli. В этой задаче cobra поможет распарсить аргументы, написать подробный help message, сделать алиасы для флагов.

В cobra используется библиотека pflag для работы с флагами. Библиотеку можно использовать и отдельно от cobra. pflag может побольше, чем стандартный flag, в частности, в pflag есть полезные для решаемой задачи флаги для работы с аргументами-массивами.

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.