GitHub の芝生で草文字を描く

# GitHub の芝生で草文字を描く

## はじめに

GitHub の contribution graph 、いわゆる芝生で絵や文字を書くという有名な遊びがあります。

![git-turf-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97350/d0c64ed8-a107-d5e2-54b5-3848feea20c3.png)

様々なツールやサービスなどがあると思いますが、自分でも作ってみようかと思いたちました。

その時に調べたことを、メモしておきます。

ちなみに、一般的な日本語では「草文字」は「そうもじ」と呼んで草書体の文字を指すようですが、ここでは芝生で描く文字、つまり花文字（banner）の草バージョンのことを「くさもじ」と呼んでいます。

## どうやって草を生やすか

GitHub の contribution graph とは、GitHub のプロフィール画面に表示されるカレンダーのようなもので、デフォルトブランチや github-pages 用ブランチへのコミットや pull request、あるいは issue の作成などの活動をしている場合に、その日の色が濃くなるようになっています。

> On your profile page, certain actions count as contributions:
>
> * Committing to a repository's default branch or gh-pages branch
> * Opening an issue
> * Proposing a pull request
> * Submitting a pull request review

* GitHub Help: [Viewing contributions on your profile](https://help.github.com/en/articles/viewing-contributions-on-your-profile)

また、すべてのコミットが対象ではなく以下の条件を全て満たす必要があるようです。

> Commits will appear on your contributions graph if they meet all of the following conditions:
>
> * The email address used for the commits is associated with your GitHub account.
> * The commits were made in a standalone repository, not a fork.
> * The commits were made:
>   * In the repository's default branch (usually master)
>   * In the gh-pages branch (for repositories with Project Pages sites)

ポイントはコミット時のメールアドレスが、GitHub アカウントと一致している必要がある、というところでしょうか。

また、以下のうちのいずれか１つが該当する必要があります。

> In addition, **at least one** of the following must be true:
>
> * You are a collaborator on the repository or are a member of the organization that owns the repository.
> * You have forked the repository.
> * You have opened a pull request or issue in the repository.
> * You have starred the repository.

通常は自分のリポジトリが対象なので、問題ないかと。

> Commit was made less than 24 hours ago

またコミットしてから（サーバに push してから？）最大で24時間待つ必要があるようです。試してみた際にはほぼ即座に反映されていましたが、場合によっては遅くなる可能性があると思ったほうが良いかと。

* GitHub Help: [Why are my contributions not showing up on my profile?](https://help.github.com/en/articles/why-are-my-contributions-not-showing-up-on-my-profile)

ちなみに、自分のメールアドレスを公開したくない場合には、コミット用のダミーメールアドレスを設定することができるようです。いずれにしても、サーバ側のメールアドレスの設定と、コミット時のメールアドレスを一致させる必要があります。

* GitHub Help: [Setting your commit email address](https://help.github.com/en/articles/setting-your-commit-email-address)

また、コミットに関しては commit date と、author date を持っていますが、 contribution graph には author date を使って反映されるようです。

> On your profile page, the author date is used to calculate when a commit was made.

* GitHub Help: [Troubleshooting commits on your timeline](https://help.github.com/en/articles/troubleshooting-commits-on-your-timeline)

また、git のコミット日付は情報として、UNIX の通算秒とタイムゾーンを持っており、日付は該当コミットのタイムゾーンで判断するようです。

> * Commits use the time zone information in the commit timestamp.

* GitHub Help: [Viewing contributions on your profile](https://help.github.com/en/articles/viewing-contributions-on-your-profile)

つまり、`2019-09-01T00:00:00+0900` と `2019-08-31T15:00:00+0000` はタイムゾーンが違うだけで、同じタイミングですが contribution graph 上では前者のコミットは 2019-09-01 として扱い、後者のコミットは 2019-08-31 として扱うようです。

git コマンドでコミットする場合 `--date` で author 日付を指定できるようです。

```shell-session
git commit --date 2010-01-01 -m "message"
```

日付の指定には何通りかあるようです。

* git 内部形式での指定。UNIX 通算秒とタイムゾーンの指定

```shell-session
$ date -d 2018-01-01 '+%s'
1514732400
$ git commit --date '@1514732400+0900' --allow-empty -m "message"
[master (root-commit) 52cb391] message
 Date: Mon Jan 1 00:00:00 2018 +0900
```

* RFC 2822 形式での指定。

```shell-session
$ date --rfc-email -d 2018-01-02
Tue, 02 Jan 2018 00:00:00 +0900
$ git commit --date 'Tue, 02 Jan 2018 00:00:00 +0900' --allow-empty -m "message2"
[master 50882e4] message2
 Date: Tue Jan 2 00:00:00 2018 +0900
```

* ISO 8601 形式での指定。この場合、タイムゾーンは指定せず、ローカルのタイムゾーンが使われるようです。時刻を指定しない場合には現在時刻が使われるらしい。

```shell-session
$ git commit --date '2018-01-03T00:00:00' --allow-empty -m "message3"
[master 42cc61d] message3
 Date: Wed Jan 3 00:00:00 2018 +0900
```

* 他にも `YYYY.MM.DD` や `MM/DD/YYYY` や　`DD.MM.YYYY` が使えるらしい

* Git 公式 [git-commit](https://git-scm.com/docs/git-commit)

python から呼び出すなら以下のような感じでしょうか。import は後で使う物も記載しています。

```python
import subprocess
from datetime import date, datetime, time, timedelta
from typing import Dict, Iterable

def do_commit(commit_datetime: datetime) -> None:
    command = [
        "git",
        "commit",
        "-qm",
        "_",
        "--allow-empty",
        "--date={}".format(commit_datetime.isoformat()),
    ]
    subprocess.check_call(command)
```

## 文字データの形式を考える

contribution graph に文字を描くための、文字データ（フォント、グリフ）の持ち方を考えます。

contribution graph は日曜日から始まる1週間が縦1列になっているため、縦1列を1byteとします（最上位bitは使用しない）。

例えば以下のようなフォントを用意します。

```python
fonts = {
    "H": bytes.fromhex("7f0808087f00"),
    "e": bytes.fromhex("3854541800"),
    "l": bytes.fromhex("017f00"),
    "o": bytes.fromhex("3844443800")
}
```

これを基にメッセージからバイト列に変換して

```python
def convert_bitmap(fonts: Dict[str, bytes], message: str) -> bytes:
    bitmap = bytearray()
    for ch in message:
        if ch in fonts:
            bitmap += fonts[ch]
    return bytes(bitmap)
```

画面に表示するには以下のような関数を作って呼び出します。

```python
def show_bitmap(bitmap: bytes) -> None:
    for i in range(7):
        mask = 1 << i
        for b in bitmap:
            ch = "#" if b & mask else " "
            print(ch, end="")
        print()
```

```python
>>> bitmap = convert_bitmap(fonts, "Hello")
>>> show_bitmap(bitmap)
#   #      ## ##
#   #       #  #
#   #  ##   #  #  ##
##### #  #  #  # #  #
#   # ####  #  # #  #
#   # #     #  # #  #
#   #  ##   #  #  ##
```

コミットする場合には、縦横の順番を入れ替えて以下のような感じでしょうか。

```python
def target_days(bitmap: bytes) -> Iterable[int]:
    days = 0
    for b in bitmap:
        for i in range(7):
            if b & (1 << i):
                yield days
            days += 1


def commit_message(start_datetime: datetime, bitmap: bytes) -> None:
    for days in target_days(bitmap):
        commit_datetime = start_datetime + timedelta(days=days)
        do_commit(commit_datetime)
```

今回は使っていませんが、進捗状況を表示したい場合には tqdm を追加して `target_days(bitmap)` を `tqdm(list(target_days(bitmap)))` とすれば大丈夫でしょうか。

## 文字データを作る

文字データをすべて作る気力はないので、先人の成果物をお借りします。

contribution graph は縦が1週間分なので、 7 ドットの字形が必要になります。

一般的なフォントは、並べても文字がくっつかないようデザインされているため、縦 8 ドットのフォントが使えるのではないかと思います。

調べてみたところ、以下の「美咲フォント」の「美咲ゴシック」が良いのではないかと思いました。

https://littlelimit.net/misaki.htm

ポケコン用のフォントとしてデザインされているようですが、これをお借りします。

実際にこのフォントデータを変換して、上記の文字データを作ってみたところ、いわゆる「全角文字」については問題ありませんでした（画数の多い文字はつぶれてしまうけど、ドット数が少ないので仕方がない）。

いわゆる「半角文字」については、少々潰れすぎな気がするので、「美咲フォント」の関連フォントである「k6x8フォント」をベースに加工しました。

このフォントは横幅が 6 ドットの、「美咲フォント」よりもさらに幅が狭いフォントですが、「k6x8フォント」のいわゆる「全角文字」を、いわゆる「半角文字」代わりに使おうかと思います。

さらに、縦1列が全て空白の列を詰めることで、プロポーショナルフォント風に変換しました。

ちなみに、これらのフォントのライセンスを確認したところ、かなり自由度の高い内容になっているようです。作者や関連する方々に感謝します。

https://littlelimit.net/font.htm

## プログラム作成

上記の関数などを組み合わせて、プログラムを作成しました。

名前は `github-banner` とかにしようかと思いましたが、git コマンドのサブコマンドとしても使えるように、また後半の`banner` は同名のものがほかにもたくさんありそうなので、`git-turf` コマンドとしました。

https://github.com/yoshi389111/git-turf

```shell-session
$ git turf --date "2015-12-27" "Hello,世界"
#   #      ## ##          # # #   #####
#   #       #  #          # # #   # # #
#   #  ##   #  #  ##     #######  #####
##### #  #  #  # #  #     # # #   # # #
#   # ####  #  # #  # ##  # ###   #####
#   # #     #  # #  #  #  #      # # # #
#   #  ##   #  #  ##  #   ######  #  #
$ git push
```

![git-turf-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97350/d0c64ed8-a107-d5e2-54b5-3848feea20c3.png)

上記の例では、1年間なにもしていないところに書き込んだら、1日に1回のコミットしかしていないのにかなり色が濃くなってしまいました。他にコミットがあると閾値が上がって、薄い色になると思います。

当然ですが、ステージングされているファイルがあると、git-turf コマンド実行時に一緒にコミットされてしまうのご注意ください。

基本的には新しいリポジトリを作ってそこで遊んで、終わったらリポジトリごと削除するのが面倒がなくてよいと思います。