公開:2025年4月14日
2分で読めます
初めて行われたコミット、初期リリースのユニークな特徴、そしてgit-push(1)のデフォルト動作の変更によって生じた混乱について、一緒に振り返っていきましょう。
Gitプロジェクトはちょうど20周年を迎えました。20年の間にさまざまなことがありました。Gitの概念的なデザインは、その登場以来大きく変わってはいないものの、ユーザーによるGitの操作方法は大きく変化しました。GitLabは、この重要な技術をベースにサービスを構築し、その歴史の一部であることを誇りに思っています。
それでは、Gitの歴史をたどり、長年にわたってどのように進化を遂げてきたかを一緒に見ていきましょう。
初めてのコミットは、2005年4月7日にLinuxカーネルの生みの親であるLinus Torvalds氏によって行われました。e83c5163316 (Initial revision of "git", the information manager from hell, 2005-04-07)
ご覧のとおり、このコミットには多くのファイルは含まれていませんでした。
$ git ls-tree e83c5163316
100644 blob a6bba79ba1f46a1bbf7773449c3bd2bb9bf48e8b Makefile
100644 blob 27577f76849c09d3405397244eb3d8ae1d11b0f3 README
100644 blob 98a32a9ad39883c6d05a000a68511d4b1ee2b3c7 cache.h
100644 blob 74a0a234dd346fff51c773aa57d82fc4b83a8557 cat-file.c
100644 blob 840307af0cfaab31555795ce7175d5e9c9f981a0 commit-tree.c
100644 blob 25dc13fe101b219f74007f3194b787dd99e863da init-db.c
100644 blob c924a6e0fc4c36bad6f23cb87ee59518c771f936 read-cache.c
100644 blob 1b47742d8cbc0d98903777758b7b519980e7499e read-tree.c
100644 blob b8522886a15db861508fb6d03d4d88d6de912a4b show-diff.c
100644 blob 5085a5cb53ee52e1886ff6d46c609bdb2fc6d6cd update-cache.c
100644 blob 921f981353229db0c56103a52609d35aff16f41b write-tree.c
ビルドインフラストラクチャに加え、最初のコミットでは以下の7つのトップレベルコマンドが提供されていました。
init-db
:新たなGitリポジトリを初期化update-cache
:インデックスにファイルを追加write-tree
:インデックスの内容を取得し、それをもとに新たなツリーを作成read-tree
:ツリーオブジェクトを読み込むcommit-tree
:ツリーからコミットを作成cat-file
:特定のオブジェクトを一時ファイルに読み込むなお、この時点では、git
コマンド自体存在していませんでした。代わりに、上記のコマンドを直接実行する必要がありました。
では、試しにリポジトリを新規作成してみましょう。
$ mkdir repo
$ cd repo
$ init-db
defaulting to private storage area
$ ls -a
. .. .dircache
みなさんにはまったくなじみがないと思います。.git
ディレクトリではなく、.dircache
ディレクトリが使用されていました。では、プライベートストレージ領域はどこでしょうか?
初期のGitのデザインでは、オブジェクトストレージ領域は「共有」と「プライベート」に分かれていました。このオブジェクトストレージ領域には、コミットやblobなども含め、あらゆるGitオブジェクトが格納されていました。
init-db
は、デフォルトではプライベートオブジェクトストレージ領域を作成します。これは、領域の作成先の管理ディレクトリ専用として使用されていました。一方、同じオブジェクトを二重に保存する必要がないように、「共有」オブジェクトストレージ領域を使用して、複数の管理ディレクトリ間でオブジェクトの内容を共有していました。
リポジトリの作成後は、どのようにコミットを作成していたのでしょうか?作成方法は、現在利用可能なgit add . && git commit
ほどシンプルではありませんでした。その代わりに、以下の方法で行っていました。
update-cache
を呼び出してインデックスを更新する。write-tree
を呼び出して新規ツリーを書き込む。インデックスに追加済みのすべての内容をもとに作成される。commit-tree
を呼び出して、コミットオブジェクトを書き込む。それでは、リポジトリにコミットを作成してみましょう。
$ echo content-1 >file-a
$ update-cache file-a
$ echo content-2 >file-b
$ update-cache file-b
$ write-tree
3f143dfb48f2d84936626e2e5402e1f10c2050fb
$ export COMMITTER_NAME="Patrick Steinhardt"
$ export [email protected]
$ echo "commit message" | commit-tree 3f143dfb48f2d84936626e2e5402e1f10c2050fb
Committing initial tree 3f143dfb48f2d84936626e2e5402e1f10c2050fb
5f8e928066c03cebe5fd0a0cc1b93d058155b969
人間工学的な方法とは言えないものの、コミットは作成されます。それでは、生成されたコミットを見てみましょう。
$ cat-file 5f8e928066c03cebe5fd0a0cc1b93d058155b969
temp_git_file_rlTXtE: commit
$ cat temp_git_file_rlTXtE
tree 3f143dfb48f2d84936626e2e5402e1f10c2050fb
author Patrick Steinhardt <[email protected]> Wed Mar 26 13:10:16 2025
committer Patrick Steinhardt <[email protected]> Wed Mar 26 13:10:16 2025
commit message
注目していただきたいのは、cat-file
は内容を直接表示せず、まずは一時ファイルに書き出す点です。しかしながら、ファイルの内容は、まさに現代的なコミットと同じように見えます。
ファイルの作成後、どのようにステータスを確認していたのでしょうか?おそらくお察しのとおりで、show-diff
を使用していました。
$ show-diff
file-a: ok
file-b: ok
$ echo modified-content >file-a
$ show-diff
--- - 2025-03-26 13:14:53.457611094 +0100
+++ file-a 2025-03-26 13:14:52.230085756 +0100
@@ -1 +1 @@
-content-1
+modified-content
file-a: 46d8be14cdec97aac6a769fdbce4db340e888bf8
file-b: ok
驚くべきことに、すでにshow-diff
では、変更されたファイルの新旧の状態を比較して差分を取得していました。しかも面白いことに、GitではこれをUNIXのdiff(1)ツールを使用するという簡単な方法で実現していました。
つまり、これらはすべて必要最低限の機能しか備えていなかったものの、履歴の追跡に必要なすべての役割を果たしていました。以下のように制限は多数ありました。
.dircache
ディレクトリを同期させる必要があった。Gitの最初のテストリリースは、バージョン0.99でした。バージョン0.99は、最初のコミットからわずか2か月後にリリースされたものの、すでに1,076件のコミットが追加されていました。約50人のデベロッパーが開発に携わっており、最も頻繁にコミットを行っていたのはTorvalds氏自身でした。トーバルズ氏に続く勢いで、コミット件数の多かったコミッターは、現在メンテナーを務めている濱野純氏でした。
最初のコミット以降、多数の変更が加えられました。
.dircache
ディレクトリの名前が.git
に変更されました。ただし、最も重要かつ顕著な変化は、トップレベルのgit
コマンドとそのサブコマンドが導入されたことでした。興味深いことに、「配管(plumbing)」コマンドと「磁器(porcelain)」コマンドの概念が導入されたのも、このリリースです。
Gitには現在でもこの分け方は採用されており、git(1)
にも概説されています。しかしながら、「磁器」ツールの大半はShellスクリプトからC言語に書き直されたため、これらの2つのカテゴリ間の境界線はかなり曖昧になってきています。
Torvalds氏がGitに取り掛かった理由は、バージョン管理システムが好きだったためではなく、Linuxカーネル開発のためにBitKeeperの代替ツールを必要としていたためです。そのため、Gitのメンテナンスをずっと続けるつもりはなく、信頼できる人が現れるまで、メンテナーを務めようと考えていました。
その条件に当てはまったのが、濱野純氏でした。濱野氏は、Torvalds氏が最初のコミットを行った約1週間後にGitの開発に参加しました。Git 0.99のリリース後にはすでに数百件のコミットを行っていました。そのため、2005年7月26日に、Torvalds氏は濱野氏をGitプロジェクトの新たなメンテナーに任命しました。Torvalds氏は引き続きGitにコントリビュートしているものの、徐々にGitプロジェクトへの関わりは薄れていきました。これは、Linuxプロジェクトの責任者として多忙を極めているため、当然のことでした。
現在でも、引き続き濱野氏がGitプロジェクトを率いています。
濱野氏は、2025年12月21日にGitの最初のメジャーリリースを行いました。興味深いことに、バージョン0.99から1.0の間には、34回もリリースが行われました(0.99.1~0.99.7、0.99.7a~0.99.7d、0.99.8~0.99.8g、0.99.9~0.99.9n)。
0.99以降の特に重要なマイルストーンのひとつは、おそらく2つのツリーを相互にマージできるgit-merge(1)
コマンドの追加でしょう。それまでは基本的にファイルのマージを行う場合、ファイルごとにスクリプトを作成する必要があったことを考えると、非常に対照的です。
もう1つの大きな変化は、リモートリポジトリの省略記法が導入されたことです。すでにGitからリモートリポジトリを操作することはできましたが、変更をフェッチする際に毎回、対象のリポジトリのURLを指定する必要がありました。通常は同じリモートリポジトリと何度もやり取りを行うため、これはユーザーにとってかなり使い勝手の悪い仕様でした。
現在のremoteコマンドの仕組みはご存知だと思いますが、当時の仕組みはまだ大きく異なっていました。リモートリポジトリを管理するためのgit-remote(1)
コマンドはまだ存在しておらず、リモートリポジトリの情報は.git/config
ファイルに保存すらされていませんでした。実のところ、バージョン0.99.2でremoteコマンドが最初に導入された際、Gitにはconfigファイル自体ありませんでした。
代わりに、.git/branches
に直接ファイルを書き込んでリモートリポジトリの設定を行う必要がありました。今となってはあまり直感的な方法とは思えません。しかしながら、この仕組みは今でも動作します。
$ git init repo --
Initialized empty Git repository in /tmp/repo/.git/
$ cd repo
$ mkdir .git/branches
$ echo https://gitlab.com/git-scm/git.git >.git/branches/origin
$ git fetch origin refs/heads/master
それだけではなく、その直後にGitバージョン0.99.5でディレクトリ名が「remotes」に変更されたため、現在のGitクライアントではリモートリポジトリの設定方法が全部で3つあります。
おそらく多くの方は、.git/branches
も.git/remotes
も使ったことがないと思います。これらはそれぞれ、2005年、および2011年以降、非推奨化されています。また、最終的にこれらのディレクトリは、Git 3.0で削除される予定です。
2007年に、Gitの最初のロゴが作成されました。これは、単に3つの緑のプラス記号の上に3つの赤いマイナス記号が配置された構成(git diff
の出力がどのように見えるかを表していた)だったため、ロゴと呼べるかどうかについては、議論の余地があります。
それから少し経った2008年に、ウェブサイトgit-scm.comが公開されました。
2012年に、Scott Chacon氏とJason Long氏によって、Gitのウェブサイトはリニューアルされました。ご覧のように現在の外観にかなり近くなりました。
再デザインされたウェブサイトには、Jason Long氏がデザインし、今でも使用されている新しい赤橙色のロゴが目立つように掲載されていました。
Git 1.0のリリース時点で、すでに現在のGitとかなり同じようになってきたため、ここでGitの歴史をたどる旅の歩みをGit 2.0まで進めます。Git 1.0の約10年後にリリースされたこのバージョンは、中央ワークフローに後方互換性のない変更を意図的に含めた最初のリリースでした。
git-push(1)
のデフォルトの動作このリリースで最も混乱を招いたのは、間違いなくgit-push(1)
のデフォルトの動作が変更されたことです。
リモートリポジトリへのプッシュ時に何をプッシュするかを具体的に指定しなかった場合、Gitは以下のいずれかのアクションを取る可能性がありました。
現在のGitは、いわゆる「シンプル」な手法を採用しており、上記の3番目のアクションを行います。しかしながら、Git 2.0より前のバージョンにおけるデフォルトの動作は、「マッチング」手法を使用した上記の最後のアクションでした。
「マッチング」手法は、現在採用されている手法と比べて、はるかにリスクがありました。プッシュする前に毎回、リモート側に対応するブランチがあるすべてのローカルブランチをプッシュしても問題がないか確認する必要がありました。そうしないと、意図せずに変更がプッシュされてしまう可能性がありました。そこでリスクを軽減し、Gitを使い始めたばかりのユーザーにとって使い勝手をよくするために「シンプル」手法が採用されました。
git-add(1)
もう1つの大きな変化は、削除された追跡済みファイルに対するgit-add(1)
のデフォルトの動作が変更されたことです。Git 2.0より前のバージョンでは、git-add(1)
は削除済みのファイルを自動的にステージングしませんでした。そのため、コミットに含めるには、git-rm(1)
を使用して削除済みのファイルを1つずつ手動で追加する必要がありました。Git 2.0ではこの動作が変更され、git-add(1)
を実行すると、削除済みのファイルもインデックスに追加されるようになりました。
おそらくみなさんGitを日々活用しているかと思いますので、Gitの現在の仕組みについて細かくはここでは取り上げません。まだ活用していない方向けには、利用開始に役立つチュートリアルが多数用意されています。現在の仕組みについて説明する代わりに、Git誕生から20年経った今でも機能するように取り組んでくださったGitコミュニティの業績を称えたいと思います。
Gitは、その歴史の中で以下の実績を達成してきました。
また、GitプロジェクトはGoogle Summer of Code(GSOC)やOutreachyにも参加しており、新たなコントリビューターが着実に増えています。このような新たなコントリビューターのおかげで、Gitプロジェクトは長期的に健全な状態を保てます。
この場を借りて、すべてのコントリビューターに心からお礼申し上げます。みなさんのコントリビュートのおかげで、Gitが実現しました。
Gitがバージョン管理システムの競争で事実上勝利を収めたと言っても、異論はほとんどないでしょう。Gitは大きな市場シェアを占めており、Git以外のバージョン管理システムを使用しているオープンソースプロジェクトはほとんどありません。そう考えると、Gitが多くのことを正しく成し遂げてきたことは明らかです。
とは言っても、Gitの開発は完結したわけではなく、依存として多くの課題が残されています。その例が、以下のような技術的な課題です。
それとは別に、以下のような社会的な課題もあります。
取り組むべき作業は常にあります。次の20年もGitが素晴らしいバージョン管理システムであり続けられるよう、私たちGitLabもコントリビュートできることを誇りに思います。