GrabDuck

Рисуем коммитами на Гитхабе

:

[Пятничное]

Всегда хотел сделать свой график активности пользовательского профиля на Гитхабе. Например, выкладывать коммиты каждый день так, чтобы через год этот график превратился в какую-нибудь картинку, пусть и с ограничением по размерам в 52×7 квадратиков-пикселей (52 недели в году × 7 дней в неделе).

Проблема была в том, что даже при полной автоматизации процесса всё равно ждать целый год. А тут я почитал документацию Гитхаба и понял, что задача решается проще и более того — за один раз. А значит, надо делать не откладывая. Обычно названия проектам придумывать сложно, но тут оно пришло само. Кай рисовал льдинками, а Герда рисует коммитами!

График коммитов на Гитхабе в виде картинки


Как это работает

Используется существующее на данный момент (преднамеренное?) поведение Гитхаба при построении графика активности, учитывающее предоставленные клиентом локальные даты коммитов. Подробнее эту тему можно изучить в справке сервиса.

А поскольку Гитхаб доверяет локальным датам, указанным в коммите, можно отправить ему сколько угодно коммитов с какими угодно датами:

echo "С Новым годом тебя, часовой пояс +3!" >> gerda.md
git add gerda.md
git commit -m "Правки к Новому году" --date="0001-01-01T00:00:00+0300"

Таким образом, наша задача состоит в автоматизации самого процесса создания коммитов с нужными датами. Говоря проще, надо написать скрипт, который генерирует скрипт, который создаёт нужные коммиты. По пути ещё надо не забыть, что график профиля использует недели с воскресным, а не понедельничным первым днём.

Лучше всего скриптам скармливать картинки в более-менее человекочитаемом формате, например, массивом строк или вообще реальные файлы изображений. Я для простоты парсинга сделал строки. Гитхаб отображает пять разных оттенков цвета в графике, чем темнее оттенок, тем больше активность пользователя в заданный день. Возьмём для примера три:


  • пробел — отсутствие оттенка — никакой активности в заданный день,
  • звёздочка * — светлый оттенок — средняя активность в заданный день,
  • решётка # — тёмный оттенок — большая активность в заданный день.

Например, напишем слово „ПРИВЕТ“ (используем звёздочку в качестве антиалиасинга для „плавных“ переходов):

$commits = [
    /* columns
    '      10→|      20→|      30→|      40→|      50→|  ' ← exactly 52 characters */
    ' #######  #####*   #     #  #####   ######  ####### ',  // Sun
    ' #     #  #     #  #    ##  #    #  #          #    ',  // Mon
    ' #     #  #     #  #   # #  #    *  #          #    ',  // Tue
    ' #     #  #####*   #  #  #  #####   #####      #    ',  // Wed
    ' #     #  #        # #   #  #    #  #          #    ',  // Thu
    ' #     #  #        ##    #  #    #  #          #    ',  // Fri
    ' #     #  #        #     #  #####*  ######     #    ',  // Sat
];

Лучше оставлять по паре недель (пробелов) слева и справа — чтобы меньше мешала текущая активность.

Теперь самое сложное — разобрать этот массив, соотнеся каждый символ в нём с каким-то определённым днём. У меня получилось так:

/**
 * Генерация набора коммитов.
 *
 * @param  string[]   $map          Карта коммитов в виде массива из 7 строк по 52 символа каждая
 * @param  \DateTime  $firstSunday  Дата последнего воскресенья год назад
 * @return array
 */
function generateCommits($map, $firstSunday)
{
    $commits = [];
    $count = 7 * 52;
    $date = clone $firstSunday;

    // Идём по всем символам карты коммитов, вычисляя неделю и день недели
    for ($day = 0, $weekDay = 0; $day < $count; $day++) {
        $week = intval($day / 7);
        $char = substr($map[$weekDay], $week, 1);
        if ($char !== ' ') {
            $commits[$date->format('Y-m-d')] = $char === '#' ? 20 : 10;
        }
        // Переходим к следующему дню
        $date->add(new DateInterval('P1D'));
        $weekDay = ($weekDay + 1) % 7;
    }

    return $commits;
}

В результате получаем ассоциативный массив (словарь), где ключ — день недели, а значение — количество коммитов, необходимых в этот день:

$commits = [
    // ...
    '2016-01-31' => 10,
    '2016-02-01' => 20,
    '2016-02-02' => 20,
    '2016-02-03' => 10,
    // ...
]

Дальше всё совсем просто: проходим по этому словарю и пишем в результирующий шелл-скрипт нужное количество коммитов для каждого дня (для часового пояса я использовал московское время). В качестве подопытного файла используем маркдаун-документ, в текст которого с каждым коммитом добавляем день или какую-нибудь другую информацию (у меня это маркированный список с номером коммита в этот день):

git init

echo "# Gerda" > gerda.md

echo "\n## 2016-01-31" >> gerda.md
    echo "* Gerda №1" >> gerda.md
    git add gerda.md
    git commit -m "Gerda №1" --date="2016-01-31T12:00:00+0300"
    # ... так 10 раз ...

echo "\n## 2016-02-01" >> gerda.md
    echo "* Gerda №1" >> gerda.md
    git add gerda.md
    git commit -m "Gerda №1" --date="2016-02-01T12:00:00+0300"
    # ... так 20 раз ...

echo "\n## 2016-02-02" >> gerda.md
    echo "* Gerda №1" >> gerda.md
    git add gerda.md
    git commit -m "Gerda №1" --date="2016-02-02T12:00:00+0300"
    # ... так 20 раз ...

echo "\n## 2016-02-03" >> gerda.md
    echo "* Gerda №1" >> gerda.md
    git add gerda.md
    git commit -m "Gerda №1" --date="2016-02-03T12:00:00+0300"
    # ... так 10 раз ...

Программирование логики готово. Вынесем все нужные настройки в один файл settings.php, чтобы отделить данные от логики:

// Streak graph picture
$commits = [ ... ];

// Repository origin
$origin = 'https://github.com/maximal/gerda.git';

// Output shell file
$commandFile = 'repo' . DIRECTORY_SEPARATOR . 'make-commits.sh';

Разумеется, чтобы Гитхаб понял, от какого пользователя были произведены коммиты, в настройках Гита должны стоять ваш логин и почтовый ящик:

# ~/.gitconfig
[user]
    name  = my_github_username
    email = my_github_email@ya.ru
### ...

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

Валидация

Ой, пятница от радости выпрыгнула за ограничение в 52 символа. Поправим:

Генерация скрипта с коммитами

Скрипт с коммитами создан, запустим его:

cd repo
./make-commits.sh

По окончании работы скрипта (как правило, там пара тысяч коммитов: придётся подождать минутку), Гитхаб спросит ваш пароль. Вводить пароли в чужие (а иногда и в свои) скрипты некруто, поэтому, если хотите, можете завершить скрипт на этом шаге и запушить потом самостоятельно (в сгенерированном скрипте честно-честно только команда git push):

git remote add origin https://github.com/<MY>/<REPO>.git
git push -u origin master -f

Ждём пару минут (разумно полагать, что эти данные у Гитхаба кешируются), обновляем страницу профиля.

Готово.


Ссылки

Весь исходный код и его описание лежат на (сюрприз!) Гитхабе: https://github.com/sijeko/gerda
Будут пулреквесты — заходите на огонёк.

Живая картинка активности на примере моего профиля: https://github.com/maximal


Минусы

Чего программа не умеет, и что можно доработать:


  • Скрипт не учитывает уже существующую Гитхаб-активность, поэтому при плотной текущей занятости картинка,
    скорее всего, не получится, или, в лучшем случае, будет смазана.
  • Вся „активность“ получается сконцентрированной в одном репозитории и в одном файле,
    поэтому если ваша цель — максимальная скрытность, этот скрипт — не вариант.
  • Если надо перерисовать картинку, репозиторий придётся сначала удалить на Гитхабе
    и потом создать новый с таким же именем — не очень удобно.
  • Программа распознаёт три градации цвета, Гитхаб отображает пять — можно улучшить.
  • Фиксированный часовой пояс (+3:00, московское время) — можно сделать конфигурируемым.
  • Для коммитов используется пользователь по умолчанию из конфигурации Гита — можно сделать изменяемым в наших настройках.