## 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`. Для вызова команд git можно использовать [os/exec](https://golang.org/pkg/os/exec/). В процессе работы скрипт не должен менять состояние репозитория ни в какой момент, поскольку с репозиторием могут параллельно работать. ### Интерфейс Утилита должна печатать результат в 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](https://golang.org/pkg/text/tabwriter/). `csv`: ``` Name,Lines,Commits,Files Joe Tsai,64,3,2 Ross Light,2,1,1 ferhat elmas,1,1,1 ``` [encoding/csv](https://golang.org/pkg/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](https://golang.org/pkg/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](configs/language_extensions.json) лежит маппинг. Неизвестные языки никаких ограничений не накладывают. При их использовании можно написать warning в stderr. **--exclude** — набор [Glob](https://en.wikipedia.org/wiki/Glob_(programming)) паттернов, исключающих файлы из расчёта, например `'foo/*,bar/*'` Для работы с Glob'ом в стандартной библиотеке есть [path/filepath](https://golang.org/pkg/path/filepath/). **--restrict-to** — набор Glob паттернов, исключающий все файлы, не удовлетворяющие ни одному из паттернов набора ### Тесты Команда для запуска тестов ``` go test -v ./gitfame/test/integration/... ``` В [/tests/integration/testdata/bundles](test/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` будет доступен всюду. ### Ненавязчивые предложения #### Project layout В go есть [набор рекомендаций](https://github.com/golang-standards/project-layout) по организации структуры проекта. И подпроект этого задания уже частично ему следует. Например, `main.go`, который вам нужно реализовать, лежит в [cmd/gitfame](./cmd/gitfame), интеграционные тесты в `/test`. В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне. Здесь же, для ознакомления предлагаем изучить общепринятый подход. #### Cli Можно познакомиться с [spf13/cobra](https://github.com/spf13/cobra), популярной библиотекой для написания [cli](https://en.wikipedia.org/wiki/Command-line_interface). В этой задаче cobra поможет распарсить аргументы, написать подробный help message, сделать алиасы для флагов. В cobra используется библиотека [pflag](https://pkg.go.dev/github.com/spf13/pflag) для работы с флагами. Библиотеку можно использовать и отдельно от cobra. `pflag` может побольше, чем стандартный [flag](https://golang.org/pkg/flag/), в частости, в `pflag` есть полезные для решаемой задачи флаги для работы с аргументами-массивами. Помимо библиотеки, в cobra есть ещё и бинарь (с именем cobra) для кодогенерации основы проекта. Его можно установить в `GOPATH` командой ``` go get -u github.com/spf13/cobra ``` Генерируемый sample проект использует [viper](https://pkg.go.dev/github.com/spf13/viper) (библиотеку для работы с конфигами) и [go-homedir](https://pkg.go.dev/github.com/mitchellh/go-homedir) (кроссплатформенную библиотеку для поиска домашней директории пользователя). В этой задаче эти библиотеки не нужны, поэтому их нет в зависимостях проекта. ### Git ликбез Вся информация взята из [книги](https://github.com/pluralsight/git-internals-pdf/releases/download/v2.0/peepcode-git.pdf). Смело советуем прочитать её всем, даже если в контексте задания вы знаете про 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.