shad-go/gitfame/README.md

253 lines
15 KiB
Markdown
Raw Normal View History

2021-02-28 17:34:02 +00:00
## gitfame
В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов git репозитория.
2022-03-10 19:12:24 +00:00
```
2022-03-10 19:45:15 +00:00
✗ gitfame --repository=. --extensions='.go,.md' --order-by=lines
2022-03-10 19:12:24 +00:00
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
```
2021-02-28 17:34:02 +00:00
### Статистики
* Количество строк
* Количество коммитов
* Количество файлов
Все статистики считаются для состояния репозитория на момент конкретного коммита.
2022-03-10 19:12:24 +00:00
### Интерфейс
Утилита должна печатать результат в stdout.
При использовании невалидного значения флага или любой другой ошибке программа должна завершаться с ненулевым кодом возврата.
Расчёт может занимать довольно длительное время.
Хорошим качеством подобной утилиты является отображение прогресса (вынесенное за флаг).
Прогресс можно печатать в stderr в произвольном формате.
2021-02-28 17:34:02 +00:00
#### Расчёт
Каждой строке интересующего подмножества файлов репозитория сопоставляется последний коммит, модифицировавший эту строку.
Пустым файлам сопоставляются последние менявшие их коммиты.
После этого для каждого уникального автора, получившегося множества коммитов,
считается количество строк, уникальных коммитов и файлов, которые затрагивали коммиты автора.
Нужную информацию можно получить с помощью команды `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`) возвращает информацию в машиночитаемом формате.
2022-03-06 02:06:00 +00:00
Кроме того, этот формат схлопывает соседние строки, относящиеся к одному коммиту,
2021-02-28 17:34:02 +00:00
что может сильно сократить размер результата. Поэтому использовать нужно его.
2021-03-01 21:17:33 +00:00
Стоит помнить, что не все файлы из директории git проекта обязательно принадлежат git репозиторию.
Получить список файлов git репозитория можно разными способами, например с помощью `git ls-tree`.
2021-03-06 17:54:38 +00:00
Для вызова команд git можно использовать [os/exec](https://golang.org/pkg/os/exec/).
В процессе работы скрипт не должен менять состояние репозитория ни в какой момент,
поскольку с репозиторием могут параллельно работать.
2021-02-28 17:34:02 +00:00
### Флаги
Утилита должна поддерживать следующий набор флагов:
**--repository** — путь до Git репозитория; по умолчанию текущая директория
**--revision** — указатель на коммит; HEAD по умолчанию
2022-03-06 02:06:00 +00:00
**--order-by** — ключ сортировки результатов; один из `lines` (дефолт), `commits`, `files`.
2021-02-28 17:34:02 +00:00
По умолчанию результаты сортируются по убыванию ключа `(lines, commits, files)`.
При равенстве ключей выше будет автор с лексикографически меньшим именем.
2021-03-10 22:28:29 +00:00
При использовании флага соответствующее поле в ключе перемещается на первое место.
2021-02-28 17:34:02 +00:00
**--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
```
2021-03-01 21:17:33 +00:00
[encoding/csv](https://golang.org/pkg/encoding/csv/)
2021-02-28 17:34:02 +00:00
`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}]
```
2021-03-01 21:17:33 +00:00
[encoding/json](https://golang.org/pkg/encoding/json/)
2021-02-28 17:34:02 +00:00
`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'`
2021-03-01 21:17:33 +00:00
**--languages** — список языков (программирования, разметки и др.), сужающий список файлов в расчёте; множество ограничений разделяется запятыми, например `'go,markdown'`
2021-02-28 17:34:02 +00:00
Принадлежность файла к языку программирования определяется с помощью его расширения.
2021-03-01 16:31:04 +00:00
В [configs/language_extensions.json](configs/language_extensions.json) лежит маппинг.
2021-02-28 17:34:02 +00:00
Неизвестные языки никаких ограничений не накладывают. При их использовании можно написать warning в stderr.
2021-03-01 21:17:33 +00:00
**--exclude** — набор [Glob](https://en.wikipedia.org/wiki/Glob_(programming)) паттернов, исключающих файлы из расчёта, например `'foo/*,bar/*'`
2021-02-28 17:34:02 +00:00
2021-03-06 17:54:38 +00:00
Для работы с Glob'ом в стандартной библиотеке есть [path/filepath](https://golang.org/pkg/path/filepath/).
2021-03-01 21:17:33 +00:00
**--restrict-to** — набор Glob паттернов, исключающий все файлы, не удовлетворяющие ни одному из паттернов набора
2021-02-28 17:34:02 +00:00
### Тесты
2022-03-06 02:06:00 +00:00
Команда для запуска тестов:
2021-03-06 17:54:38 +00:00
```
go test -v ./gitfame/test/integration/...
```
2021-02-28 17:34:02 +00:00
В [/tests/integration/testdata/bundles](test/integration/testdata/bundles) лежат запакованные git репозитории.
Каждый интеграционный тест ссылается на какой-нибудь бандл.
2022-03-10 19:12:24 +00:00
Как создать свой bundle? Находясь в git репозитории выполнить
2021-02-28 17:34:02 +00:00
```
git bundle create my.bundle --all
```
Как распаковать bundle? Находясь в пустой директории.
```
git clone /path/to/my.bundle .
```
2021-03-06 17:54:38 +00:00
### Сборка приложения
Как собрать приложение?
```
(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.mod` нельзя.
Весь репозиторий курса - это один go модуль с одним go.mod файлом в корне. Корню репозитория соответствует импорт путь `gitlab.com/slon/shad-go`. Если вы создадите пакет в директории `gitfame/internal/git`, то импортировать его нужно будет по пути `gitlab.com/slon/shad-go/gitfame/internal/git`.
2023-03-10 19:54:51 +00:00
### Code review comments
Прочитайте и исправьте [распространённые ошибки](../docs/gitfame_review_comments.md).
2021-02-28 17:34:02 +00:00
### Ненавязчивые предложения
#### Project layout
В go есть [набор рекомендаций](https://github.com/golang-standards/project-layout) по организации структуры проекта.
И подпроект этого задания уже частично ему следует. Например, `main.go`, который вам нужно реализовать, лежит в [cmd/gitfame](./cmd/gitfame),
интеграционные тесты в `/test`.
В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне.
Здесь же, для ознакомления предлагаем изучить общепринятый подход.
2022-03-10 19:12:24 +00:00
В частности, писать реализацию в internal или pkg (в чём разница?).
2021-02-28 17:34:02 +00:00
#### Cli
Можно познакомиться с [spf13/cobra](https://github.com/spf13/cobra),
популярной библиотекой для написания [cli](https://en.wikipedia.org/wiki/Command-line_interface).
В этой задаче cobra поможет распарсить аргументы, написать подробный help message, сделать алиасы для флагов.
2021-03-06 17:54:38 +00:00
В cobra используется библиотека [pflag](https://pkg.go.dev/github.com/spf13/pflag) для работы с флагами.
Библиотеку можно использовать и отдельно от cobra.
`pflag` может побольше, чем стандартный [flag](https://golang.org/pkg/flag/),
2022-03-06 02:06:00 +00:00
в частности, в `pflag` есть полезные для решаемой задачи флаги для работы с аргументами-массивами.
2021-03-06 17:54:38 +00:00
2021-02-28 17:34:02 +00:00
### 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/` с хэшом последнего для этой ветки коммита.
То есть ветка — это указатель на коммит.
2022-03-25 13:13:39 +00:00
**head** — это ссылка на коммит. В каждом репозитории по умолчанию есть **head** с именем **master**.
2021-02-28 17:34:02 +00:00
**HEAD** — один выделенный **head**. Файл `.git/HEAD`. Родитель следующего коммита.
**HEAD** может ссылаться на коммит напрямую (**detached HEAD**).
Следующий коммит в таком случае не будет принадлежать никакой ветке.
Гораздо чаще **HEAD** ссылается на ветку.
В таком случае следующий коммит "попадёт" в ту же ветку и продвинет **HEAD**.
**HEAD** определяет текущую активную ветку.
**revision** (ревизия) — способ сослаться на Git объект.
Например, SHA-1 коммита — это ревизия на коммит,
`HEAD@{5 minutes ago}` — это ревизия на последний коммит на момент 5 минут назад,
`HEAD:README` — это ревизия на блоб.
### Критерии сдачи
Решение должно проходить все тесты, так же как и в обычной задаче.
2021-03-01 21:17:33 +00:00
После прохождения тестов в табличке появится 0.
Нужно успеть отправить решение до дедлайна!
Проверяющие посмотрят на решение и заменят 0 на 1.