LoginSignup
32
17

More than 3 years have passed since last update.

Gitが表現できる最古の日付は何時か?

Last updated at Posted at 2019-06-29

tl;dr

実用的には 1970-01-01 00:00:00 +0000

実際のリポジトリ: https://github.com/okuoku/the-epoc-repo

追記: DO NOT TRY THIS AT HOME . これGitHubのプロフィールに何故か1969年の項目ができるようだ。(後述)

(没になった前置き)

今年2019年はUNIX生誕50周年で、ちょうど1969年の夏にPDP-7上でUNIX(の原型)が成立したあたりからカウントされている。UNIX日付を採用したGitで最古のコミットをpushすると51年目に...

SnapCrab_NoName_2019-6-30_3-26-45_No-00.png

ならねぇじゃん (終)

最古のコミットの作り方

git commit--date オプションでauthor dateを直接指定できる。

git commit --date="1970-01-01 00:00:00 +0000" -m temp

この方法だとcommitter dateは git commit した瞬間のものが採用されるため、committer dateを表示する箇所でも昔の日付を表示したければ GIT_COMMITTER_DATE 環境変数を設定した上で実行することになる。

plumbing コマンドを直接使う

(see comments。初出ではこちらを先に書いていた)

gitは細かい操作が全てコマンドに分かれており、コマンドライン上でかなり低レベルな操作を直接行える。重要なのは write-tree コマンドと commit-tree コマンドで、特に commit-tree コマンドに生の日付が直接与えられるのがミソということになる。

手順は、

  1. git init でリポジトリを用意する
  2. git addindex に適当にファイルを追加する
  3. git write-treeindex をtreeオブジェクトに変換する
  4. git commit-tree でtreeオブジェクトを指すcommitオブジェクトを用意する (日付は環境変数 GIT_AUTHOR_DATE 等で与える)
  5. git reset なりなんなりでHEADを更新する

のようになる。具体的には、

git init
git add README.txt
git write-tree > tree.txt
GIT_AUTHOR_DATE="1970-01-01 00:00:00 +0000" GIT_COMMITTER_DATE="1970-01-01 00:00:00 +0000" git commit-tree -F msg1.txt `cat tree.txt`

のような感じになる。

ここで与えている、 1970-01-01 00:00:00 +0000 の日付が、Gitで普通に表現できる最古の日付ということになる。これは UNIX Epoch と呼ばれる時刻で、Gitを含めUNIX上のプログラムの多くが採用している時刻系の 始端 にあたる。

このコミットを表示させると、時刻部分には Thu Jan 1 00:00:00 1970 +0000 とか 0 +0000 と表示される。後者が実際のGitオブジェクトで使われる形式で、UNIX時刻的にはゼロということになる。

人間にやさしい表示
$ git show 
commit e111c81454967d83488272a47c9422c446d6fa65
Author: okuoku <mjt@cltn.org>
Date:   Thu Jan 1 00:00:00 1970 +0000
人間に厳しい表示
$ git cat-file commit HEAD
tree 0f148df97147b603999860a1fed76de1f5ee3b02
author okuoku <mjt@cltn.org> 0 +0000
committer okuoku <mjt@cltn.org> 0 +0000

This is +0000 timezone offset commit

ちなみに、 git commit-tree のマニュアル( https://git-scm.com/docs/git-commit-tree )にあるように、 GIT_AUTHOR_DATE などはいくつかの日付フォーマットを受け入れ、UNIX時刻を直接書くこともできる。が、上で出てきた 0 +0000 は直接指定することはできない。 UNIX時刻表記は8ケタ以上必要 で、それ以下では認識されないようになっている。これは、通常のYYYYMMDDフォーマットと紛らわしいため。


    /*
     * Seconds since 1970? We trigger on that for any numbers with
     * more than 8 digits. This is because we don't want to rule out
     * numbers like 20070606 as a YYYYMMDD date.
     */
    if (num >= 100000000 && nodate(tm)) {
        time_t time = num;
        if (gmtime_r(&time, tm)) {
            *tm_gmt = 1;
            return end - date;
        }
    }

もっと古い時刻を入れるとどうなるのか

当然の疑問として、もっと古い時刻を入れるとどうなるのかというものがある。実際、JSTにおける1970/1/1深夜である、 1970-01-01 00:00:00 +0900 はコマンドとしては受け入れられ、実際にコミットを作ることもできる:

コミットを表示させたところ
$ git cat-file commit 321603c4a49a98b4bb0502640844d56ca145da71
tree c6babee1ec49d936cf7df8f20b309d5d8627ce20
author okuoku <mjt@cltn.org> 18446744073709519216 +0900
committer okuoku <mjt@cltn.org> 1561830314 +0900

ここでは、 18446744073709519216 と相当べらんめぇな数値が表示されているが、これを16進表記すると 0xffffffffffff8170 となり、いわゆるinteger overflowを起こしていることがわかる。UNIX時刻はふつう符号付きで使われる(ので32bitsでは 2038年問題 を起こす)。

一見コミット的には正常に行われているように見えるが、このようなコミットは git fsck を通らず、GitHubにはpushできない。

Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 979 bytes | 979.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
remote: error: object 32b0128451f66ca872431bc6c23fb2854b20e85d: badDateOverflow: invalid author/committer line - date causes integer overflow
remote: fatal: fsck error in packed object
error: remote unpack failed: index-pack abnormal exit
To github.com:okuoku/the-oldest-repo.git
 ! [remote rejected] master -> master (failed)
error: failed to push some refs to 'git@github.com:okuoku/the-oldest-repo.git'

このチェックは date_overflows 関数で行われていて、64bit符号無しで TIME_MAX に収まることと、システムの time_t に収まることの両方を要求している。UNIX Epochから見た負値、つまり1970/1/1 深夜 UTCよりも前の日付は後者の条件を満たさず(システムのtime_t で解釈すると異符号)、Git的には不正ということになる。

int date_overflows(timestamp_t t)
{
    time_t sys;

    /* If we overflowed our timestamp data type, that's bad... */
    if ((uintmax_t)t >= TIME_MAX)
        return 1;

    /*
     * ...but we also are going to feed the result to system
     * functions that expect time_t, which is often "signed long".
     * Make sure that we fit into time_t, as well.
     */
    sys = t;
    return t != sys || (t < 1) != (sys < 1);
}

実際のリポジトリ

今回GitHubにリポジトリを用意した( https://github.com/okuoku/the-epoc-repo )が、これは当然fakeで、実際の50年物のコードとしては例えばUNIX history repo( https://github.com/dspinellis/unix-history-repo )がある。

つまり、 Git的には1970年以前の人類はソフトウェアを書いていない ことになる。 これに対して、 Windowsのシステム時間は1601年1/1から始まる から、UNIXよりもWindowsの方がずっと歴史があるな! (実際には、WindowsのEpochはWindowsの開発開始付近ではなく、 単に開発開始時のグレゴリオ歴の400年周期が始まった年 というだけで決まっている。)

追記: 1969 年の謎

... ↑では散々1970年が最古の日付と書いてきたが、何故かGitHubのプロフィール上ではこの日付は1969年として扱われるようだ。

SnapCrab_NoName_2019-6-30_19-33-30_No-00.png

Svnミラーや他の方法を駆使すればもっと昔の日付にチャレンジできるかもしれない。

(add: このズレもたぶんタイムゾーンの都合だと思うんだけど、ローカル時刻を変えるだけではどうやっても1970年にすることはできなかった。GitHubの設置場所に依る?)

32
17
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
17