167 lines
10 KiB
Markdown
167 lines
10 KiB
Markdown
|
## 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`) возвращает информацию в машиночитаемом формате.
|
|||
|
Кроме того, этот формат схлопывает соседние строки относящиеся к одному коммиту,
|
|||
|
что может сильно сократить размер результата. Поэтому использовать нужно его.
|
|||
|
|
|||
|
### Флаги
|
|||
|
|
|||
|
Утилита должна поддерживать следующий набор флагов:
|
|||
|
|
|||
|
**--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
|
|||
|
```
|
|||
|
|
|||
|
`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/langauage_extensions.json](configs/language_extensions.json) лежит маппинг.
|
|||
|
Неизвестные языки никаких ограничений не накладывают. При их использовании можно написать warning в stderr.
|
|||
|
|
|||
|
**--exclude** — набор [Glob](https://en.wikipedia.org/wiki/Glob_(programming)) паттернов, исключающих файлы из расчёта
|
|||
|
|
|||
|
**--restrict-to** — набор Glob паттернов, исключающий все файлы, не удовлетворяющие ни одному из паттерну из набора
|
|||
|
|
|||
|
### Тесты
|
|||
|
|
|||
|
В [/tests/integration/testdata/bundles](test/integration/testdata/bundles) лежат запакованные git репозитории.
|
|||
|
Каждый интеграционный тест ссылается на какой-нибудь бандл.
|
|||
|
|
|||
|
Как создать bundle? Находясь в git репозитории выполнить
|
|||
|
```
|
|||
|
git bundle create my.bundle --all
|
|||
|
```
|
|||
|
|
|||
|
Как распаковать bundle? Находясь в пустой директории.
|
|||
|
```
|
|||
|
git clone /path/to/my.bundle .
|
|||
|
```
|
|||
|
|
|||
|
### Ненавязчивые предложения
|
|||
|
|
|||
|
#### 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, сделать алиасы для флагов.
|
|||
|
|
|||
|
### 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` — это ревизия на блоб.
|
|||
|
|
|||
|
### Критерии сдачи
|
|||
|
|
|||
|
Решение должно проходить все тесты, так же как и в обычной задаче.
|