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年目に...
ならねぇじゃん (終)
最古のコミットの作り方
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
コマンドに生の日付が直接与えられるのがミソということになる。
手順は、
-
git init
でリポジトリを用意する -
git add
でindex
に適当にファイルを追加する -
git write-tree
でindex
をtreeオブジェクトに変換する -
git commit-tree
でtreeオブジェクトを指すcommitオブジェクトを用意する (日付は環境変数GIT_AUTHOR_DATE
等で与える) -
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フォーマットと紛らわしいため。
-
https://stackoverflow.com/questions/5093533/git-ignores-git-author-date-is-this-a-bug/5093714#5093714
- SO の回答
-
https://github.com/git/git/blob/6e0cc6776106079ed4efa0cc9abace4107657abf/date.c#L637
- 実際のコード
/*
* 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 )がある。
今年でunix-history-repo https://t.co/19ZJ79xpAw は50 years agoに突入してるのか。。Git日付はUNIX時間なのでUNIX以前の歴史は記録できない。そのUNIXの歴史の最初が1970年6月30日、UNIX時間 15588000 (-0500) ということになっている。 pic.twitter.com/q1eQqboYQY
— okuoku (@okuoku) April 3, 2019
つまり、 Git的には1970年以前の人類はソフトウェアを書いていない ことになる。 これに対して、 Windowsのシステム時間は1601年1/1から始まる から、UNIXよりもWindowsの方がずっと歴史があるな! (実際には、WindowsのEpochはWindowsの開発開始付近ではなく、 単に開発開始時のグレゴリオ歴の400年周期が始まった年 というだけで決まっている。)
追記: 1969 年の謎
... ↑では散々1970年が最古の日付と書いてきたが、何故かGitHubのプロフィール上ではこの日付は1969年として扱われるようだ。
Svnミラーや他の方法を駆使すればもっと昔の日付にチャレンジできるかもしれない。
(add: このズレもたぶんタイムゾーンの都合だと思うんだけど、ローカル時刻を変えるだけではどうやっても1970年にすることはできなかった。GitHubの設置場所に依る?)