Git:世界を変えたコードコミット

要約: Linuxコミュニティの歴史の中で最大のGitコード提出を選択した場合、それはGitツールプロジェクト自体の最初のコード提出でなければなりません。
吾诗已成。无论大神的震怒,还是山崩地裂,都不能把它化为无形!
—— 奥维德《变形记》

バックグラウンド

最大かつ最も成功したオープンソースプロジェクトとして、Linuxは世界中からプログラマーの貢献を集めてきました。これまでに、20,000人を超える開発者がLinuxカーネルにコードを提出しています。

驚いたことに、プロジェクトの最初の10年間(1991〜 2002年)、プロジェクト管理者としてのLinusは構成管理ツールを使用しませんでしたが、パッチを通じて全員から提出されたコードを手動でマージしました。Linusが手動処理を好むわけではありませんが、商用のクリアケースであれ、オープンソースのcvs、svnなどであれ、ソフトウェア構成管理ツール(SCM)に非常にこだわりがあるため、彼の目には入りません。

彼の意見では、Linuxカーネルプロジェクトの開発と使用に対応できるバージョン制御システムは、いくつかの条件を満たす必要があります。1)高速2)マルチブランチシナリオのサポート(数千のブランチ並列開発シナリオ)3)分散4)大規模なサポートが可能事業。Linusが最終的に彼の要件を基本的に満たすツールであるBitKeeperを見つけたのは、2002年になってからでした。また、BitKeeperは商用ツールであり、Linuxコミュニティに無料で使用できるようにしたいと考えていますが、デコンパイルの規定に準拠していることを確認する必要があります。BitKeeperが提供するデフォルトのインターフェースは、明らかにコミュニティユーザーのすべてのニーズを満たすことはできません。コミュニティ開発者はBitKeeperを逆コンパイルし、非公開のインターフェースを使用したため、BitKeeperは無料で使用できるようにライセンスを撤回しました。最後の手段として、Linusは10日間の休日を使用して、DVCS-Gitを実装し、コミュニティ開発者にプッシュしました。

設計

Gitは、世界中のソフトウェア開発者にとって標準構成と呼ばれています。Gitの導入と使用法については言うまでもなく、今日はGitの内部実装について説明したいと思います。しかし、この記事を読む前に、質問をさせてください。gitを設計する(またはgitを再設計する)場合、どのように設計しますか?最初のバージョンで実装する準備ができている機能は何ですか?この記事を読んだ後、あなた自身の考えを比較してください。議論するメッセージを残してようこそ。

Gitの内部実装を学ぶための最良の方法は、Linusの最初のコード送信を確認し、gitプロジェクトの最初の送信ノードをチェックアウトすることです(ブログ「オープンソースコードを読むためのヒント」を参照)。コードベースにはほんのわずかしかないことがわかります。 2つのファイル:README、ビルドスクリプトMakefile、およびいくつかのCソースファイル。このコミットの発言も非常に特別です。地獄の情報マネージャーである「git」の最初の改訂です。

commit e83c5163316f89bfbde7d9ab23ca2e25604af290
Author: Linus Torvalds <[email protected]>
Date:   Thu Apr 7 15:13:13 2005 -0700

    Initial revision of "git", the information manager from hell

READMEで、LinusはGitの設計アイデアを詳細に説明しました。一見複雑なGit作業の場合、Linusの設計では、オブジェクトの抽象化は2つだけです。1)オブジェクトデータベース(「オブジェクトデータベース」)、2)現在のディレクトリキャッシュ(「現在のディレクトリキャッシュ」)。

Gitの本質は、一連のファイルオブジェクトコレクションです。コードファイルはオブジェクトであり、ファイルディレクトリツリーはオブジェクトであり、コミットもオブジェクトです。これらのファイルオブジェクトの名前はコンテンツのSHA1値であり、SHA1ハッシュアルゴリズムの値は40ビットです。Linusは、最初の2桁をフォルダーとして使用し、最後の38桁をファイル名として使用します。.gitディレクトリ内のオブジェクトには、2文字/デジタル名のディレクトリが多数あり、38桁のハッシュ名のファイルが多数格納されています。これはすべてGitの情報です。

Linusは、<タグasciiコード表現>(blob / tree / commit)+ <スペース> + <長さasciiコード表現> + <\ 0> + <バイナリデータコンテンツ>に従ってオブジェクトのデータ構造を定義します。 xxdコマンドは、objectsディレクトリ内のオブジェクトファイルを調べます(zlibによる解凍)。たとえば、ツリーオブジェクトファイルの内容は次のとおりです。

00000000: 7472 6565 2033 3700 3130 3036 3434 2068  tree 37.100644 h
00000010: 656c 6c6f 2e74 7874 0027 0c61 1ee7 2c56  ello.txt.'.a..,V
00000020: 7bc1 b2ab ec4c bc34 5bab 9f15 ba         {....L.4[....

オブジェクトには、BLOB、TREE、およびCHANGESETの3つのタイプがあります。

BLOB:  バイナリオブジェクト。これはGitによって保存されるファイルです。Gitは一部のVCS(SVNなど)のようにデルタ情報を保存しませんが、ファイルの各バージョンの完全な情報を保存します。たとえば、hello.cのコピーを送信してGitライブラリに入ると、hello.cの内容を完全に記録するためのBLOBファイルが生成されます。hello.cを変更した後、commitを送信すると、変更されたものを記録するための新しいBLOBファイルが生成されます。 hello.cのすべてのコンテンツ。Linusが設計されたとき、ファイル名、ファイル属性などのメタデータ情報なしで、ファイルのコンテンツのみがBLOBに記録されました。この情報は2番目のオブジェクトTREEに記録されました。

TREE: ディレクトリツリーオブジェクト。Linusの設計では、TREEオブジェクトは、BLOBオブジェクトのファイル名、ファイル属性、SHA1値情報など、タイムスライス内のディレクトリツリー情報を抽象化したものですが、履歴情報は含まれていません。この設計の利点は、2つの履歴レコードのTREEオブジェクトをすばやく比較でき、コンテンツを読み取ることはできませんが、SHA1値に従って、同じファイルと異なるファイルを表示できることです。

さらに、ファイル名と属性情報がTREEに記録されるため、BLOBオブジェクトを再利用して、ファイルの内容を変更せずに、ファイル属性の変更、ファイル名の変更、ディレクトリの移動のためのストレージリソースを節約できます。その後のGitの開発と進化において、TREEの設計が最適化され、ある時点でフォルダー情報の抽象化になりました。TREEには、そのサブディレクトリのTREEのオブジェクト情報(SHA1)が含まれています。このようにして、複雑なまたは深いレベルのディレクトリ構造を持つGitライブラリのストレージリソースを保存できます。履歴情報は、3番目のオブジェクトCHANGESETに記録されます。

ピクチャープロGitリポジトリから取得した  1

CHANGESET: オブジェクトをコミットします。CHANGESETオブジェクトは、送信のTREEオブジェクト情報(SHA1)と、コミッショナーやコミットメッセージなどの情報を記録します。他のSCM(ソフトウェア構成管理)ツールとは異なり、GitのCHANGESETオブジェクトは、ファイルの名前変更や属性変更操作を記録せず、ファイル変更などのデルタ情報も記録しません。CHANGESETは、親ノードのCHANGESETオブジェクトのSHA1値を記録します。 、このノードと親ノードのTREE情報を比較して差を求めます。

Linusでは、CHANGESET親ノードを設計するときに、ノードに最大16個の親ノードを含めることができます。3つ以上の親ノードのマージは非常に奇妙ですが、実際、Gitは3つ以上のブランチのマルチヘッドマージをサポートしています。

Linusは、3つのオブジェクトの設計説明の後で、信頼性(TRUST)を強調しました。Gitは設計に信頼性を含みませんが、Gitは構成管理ツールとして信頼できます。その理由は、すべてのオブジェクトがSHA1でエンコードされ(GoogleによるSHA1衝突攻撃の実装は後の話であり、Gitコミュニティも代わりにより信頼性の高いSHA256エンコードを使用する準備をしている)、オブジェクトのサインインプロセスは次のような署名ツールによって保証されているためです。 GPGツールなど。

Gitの3つの基本的なオブジェクトを理解すると、Linusは、元々Linus forGitによって設計された「オブジェクトデータベース」と「現在のディレクトリキャッシュ」の2つの抽象化をよく理解しています。次の図に示すように、元の作業ディレクトリに加えて、Gitには3つのレベルの抽象化があります。1つはコードを表示/書き込む現在の作業領域(作業ディレクトリ)であり、もう1つはGitリポジトリであり、Linusが言ったオブジェクトデータベースです。 、Gitウェアハウスに表示される.gitフォルダーに格納されているコンテンツ、Linusはデザインの最初のバージョンで.dircacheという名前を付けており、これら2つのストレージ抽象化には中間ステージング領域(ステージング領域)のレイヤーがあります。 git / indexに格納されている情報は、git addコマンドを実行すると、現在の変更がキャッシュ領域に追加されます。

Linusは、「現在のディレクトリキャッシュ」の設計について説明しました。キャッシュは、TREEオブジェクトと同様のコンテンツ構造を持つバイナリファイルです。TREEオブジェクトとの違いは、インデックスにネストされたインデックスオブジェクトが含まれなくなることです。つまり、現在変更されているディレクトリツリーのコンテンツがすべて1つになります。インデックスファイル。この設計には2つの利点があります。1。現在のワークスペース内のファイルが誤って削除された場合でも、キャッシュのコンテンツ全体をすばやく復元できます。すべてのファイルをキャッシュから復元できます。2。キャッシュと現在の作業をすばやく見つけることができます。ゾーンの内容に一貫性がないファイル。

開発者  2 として知っておくべきGitとGithubについてのこと

成し遂げる

Linusは、Gitの最初のコード送信でGitの最も基本的な機能を完了し、コンパイルして使用することができます。コードは非常に簡潔で、Makefileは合計でわずか848行です。興味のある同僚は、Linux環境が存在する限り、前の段落で説明した方法を使用して、Gitの最初のコミットをチェックアウトしてコンパイルと再生を開始できます。

因为依赖库版本的问题,需要对原始 Makefile 脚本做些小修改。Git 第一个版本依赖 openssl 和 zlib 两个库,需要手工安装这两个开发库。在 ubuntu 上执行: sudo apt install libssl-dev libz-dev ; 然后修改 makefile 在 LIBS= -lssl 行 中的 -lssl 改成 -lcrypto 并增加 -lz ;最后执行 make,忽略编译告警,会发现编出了7个可执行程序文件: init-db, update-cache, write-tree, commit-tree, cat-file, show-diff 和 read-tree.

下面分别简要介绍下这些可执行程序的实现:

(1)init-db: 初始化一个 git 本地仓库,这也就是我们现在每次初始化建立 git 库式敲击的 git init 命令。只不过一开始 Linus 建立的 仓库及 cache 文件夹名称叫 .dircache, 而不是我们现在所熟知的 .git 文件夹。

(2)update-cache: 输入文件路径,将该文件(或多个文件)加入缓冲区中。具体实现是:校验路径合法性,然后将文件计算 SHA1值,将文件内容加上 blob 头信息进行 zlib 压缩后写入到对象数据库(.dircache/objects)中;最后将文件路径、文件属性及 blob sha1 值更新到 .dircache/index 缓存文件中。

(3)write-tree: 将缓存的目录树信息生成 TREE 对象,并写入对象数据库中。TREE 对象的数据结构为: ‘tree ‘ + 长度 + \0 + 文件树列表。文件树列表中按照 文件属性 + 文件名 + \0 + SHA1 值结构存储。写入对象成功后,饭回该 TREE 对象的 SHA1 值。

(4)コミットツリー:TREEオブジェクト情報からコミットノードオブジェクトを生成し、バージョン履歴に送信します。具体的な実装は、送信するTREEオブジェクトのSHA1値を入力し、親コミットノード(最大16)を入力することを選択することです。コミットオブジェクト情報には、TREE、親ノード、コミッショナー、および作成者の名前、電子メール、日付情報が含まれ、最後に新しいものを書き込みます。ノードオブジェクトファイルをコミットし、コミットノードのSHA1値を返します。

(5)cat-file:すべてのオブジェクトファイルはzlibによって圧縮されるため、ファイルの内容を表示する場合は、このツールを使用して一時ファイルを解凍および生成し、オブジェクトファイルの内容を表示する必要があります。

(6)show-diff:ファイルの属性情報(変更時間、長さなど)もキャッシュデータ構造に保存されるため、現在のキャッシュと現在のワークスペースの違いをすばやく比較できます。これにより、ファイルが変更されたかどうかをすばやく比較できます。違いを示してください。

(7)read-tree:入力されたTREEオブジェクトSHA1値に従って、TREEのコンテンツ情報を出力および出力します。

これらはすべて、Gitの最初の利用可能なバージョンの7つのサブルーチンです。Gitを使用した可能性のある同僚は、次のように述べています。これが通常のGitコマンドと異なるのはなぜですか。Git add、git commitはどうですか?はい、元のGitデザインには、通常使用するgitコマンドはありません。

Gitの設計には、低レベルコマンド(配管コマンド)と高レベルコマンド(磁器コマンド)の2種類のコマンドがあります。当初、Linusは、オープンソースコミュニティのハッカー向けのUnix KISS原則に準拠するこれらのコマンドを設計しました。ハッカー自体がマスターであるため、水道管を固定するために袖をまくり上げて、これらのコマンドを配管コマンドと呼びます。後で引き継ぎました。 GitのJunioHamanoは、これらのコマンドは一般ユーザーにはあまり使い勝手が悪いと感じているため、さらに、使いやすく、より洗練されたインターフェイスを備えた高レベルのコマンドをカプセル化しました。これは、今日私たちが毎日使用しているgit add、gitcommitです。

Git addはupdate-cacheコマンドをカプセル化し、gitcommitはwrite-treeコマンドとcommit-treeコマンドをカプセル化します。興味のある方は基礎となるコマンドへのより詳細な概要については、あなたが見ることができます   でのGitの内部の章プロGitリポジトリ

具体的なコードの実装については、ここでは詳しく説明しません。Linusのコードスタイルは非常に簡潔であり、1行で完了できる場合は2行を記述しないでください。さらに、Linux APIの使用は当然のことながら卓越しています。私が最も感銘を受けたのは、mmapを使用してファイルとメモリのマッピングを確立する場所がたくさんあることです。これにより、メモリアプリケーション、ファイルの読み取りと書き込みの操作が不要になり、ツールのパフォーマンスが向上します。同僚が言ったように:Linusのコードがプログラミング仕様を満たしていないことに加えて、他に何も問題はないようです。ちなみに、LinusのインデントスタイルはTabキーです(暗示については、「タブまたはスペース文字、これは問題です」を参照してください)。

啓発

Linusが最初のgitcommitを送信した後、彼はgitツールをコミュニティにリリースしました。当時、コミュニティのJunio Hamanoという名前の開発者はこのツールが非常に興味深いと感じたので、コードをダウンロードし、合計で1244行のコードしかないことに気づき、彼を驚かせ、大きな関心を呼び起こしました。JunioはメーリングリストでLinusと通信し、マージなどの機能の追加を支援した後、gitの磨きを続けました。最後に、JunioはGitのメンテナンスを完全に引き継ぎ、LinusはLinuxカーネルプロジェクトのメンテナンスを継続するために戻りました。

史上最高のGitコード送信を選択した場合、それはGitツールプロジェクト自体の最初のコード送信である必要があります。このコードの提出は間違いなく画期的です。Linuxプロジェクトがオープンソースソフトウェアの成功に貢献し、ソフトウェア業界の展望を書き直した場合、Gitは世界中の開発者の働き方と書き方を変えました。Gitが生まれてから2年後、3人の若いプログラマーがサンフランシスコの居酒屋に座ってGitで何かをすることにしました。数か月後、GitHubが稼働しました。

記事の冒頭で述べた問題に戻ると、Gitを設計する場合、既存のツールエクスペリエンス(SVNの使用など)から設計を拡張することが推定されます。Gitに最初に触れたときでも、GitはSVN +分散型であると思いました。 。Gitの内部原理を理解し、Gitの初期コードを読んだ後、私は絶妙なデザインにため息をつきました。Gitの初期デザインと実装は、おそらく次のように(オープンソースの)ソフトウェア製品に影響を与える可能性があります。

1.問題点の問題を解決する:Gitの起源は、Linus自身とLinuxコミュニティの要求であり、これらの要求は、プロジェクトの共同開発(特にクロスリージョンプロジェクト)の共通の要求に一般化されています。ちなみに、ライナスは自分の悩みを解決し、大きな成果を上げました。

2.最小限の設計:ファイルの違いやバージョンの比較などを考慮して、Gitツ​​ールを設計するときにLinusは従来のSCMツールに縛られませんでしたが、git設計のアイデアを明確にするためにいくつかの基本的なオブジェクトを抽象化しました。

3. MVP(最小実行可能製品):誰もがこの概念を理解していますが、実際に操作するのは簡単ではありません。MVP構成管理ツールにはどのような機能が必要ですか?一般的に、コードの送信、履歴のトレース、バージョンの比較、ブランチのマージなどについて考えます。しかし、Linusはそれを分解し、基礎となる基本的な機能をすぐに実現したので、オープンソースコミュニティのハッカーだけがそれを使用できます。しかし、これで十分であり、ハッカーはその価値を発見し、貢献し続けました。

4.迅速なリリースと迅速な反復:これもLinuxカーネルの開発経験に由来します。LinusがGit MVPを実装した後、Linuxコミュニティのメーリングリストで発表され、コメントが求められ、繰り返し改善されます。

5.適切な後継者を見つける:大聖堂とバザールにも同様の見方があります。「プロジェクトに興味を失った場合、最後の責任はそれを有能な後継者に引き渡すことです。 「しかし、LinusがGitをJunioに与えたのは、彼が興味を失ったからではなく、Gitインフラストラクチャが確立された後、Junioが彼よりもリッチでユーザーフレンドリーな機能を実装するのに優れていることに気付いたからです。気軽にGitをJunioに渡してください。オープンソースプロジェクトに適した後継者を見つけるには、勇気と知恵の両方が必要です。

  1. Pro Git、10.2 Git内部-Gitオブジェクト:https:  //git-scm.com/book/en/v2/Git-Internals-Git-Objects↩

  2. 開発者として知っておく必要のあるGitとGithubに関する事項:https:  //medium.com/swlh/things-about-git-and-github-you-need-to-know-as-developer-907baa0bed79↩

 

クリックしてフォローし、Huawei Cloudの新しいテクノロジーについて初めて学びましょう〜

おすすめ

転載: blog.csdn.net/devcloud/article/details/108768685