はじめに
こんにちは!株式会社 BTM の坂本です!
私も含め、エンジニアの皆さんなら毎日使っている技術がありますよね?
そう、Git です。
毎日使っている Git くんが何をしているか知っていますか?
毎日 Git くんに感謝していますか?
バージョン管理してくれるのが当たり前だと思っていませんか?
今回は Git の内部構造について調査しました。
Git くんが機嫌を損ねてコマンドが実行できなくなった場合に備えて対処法を学びましょう。
今回の環境は以下になります。
- Git 2.25.1
- Python 3.11.5
本記事内で.git内のファイル直接変更していますが、実際のプロジェクトで直接操作はしないでください。
Git オブジェクトとは
Git では Git オブジェクトというものを使ってバージョンを管理しています。1
今回は以下の Git オブジェクトについて、それぞれの概要と実際の中身を見ていきましょう。
- Blob オブジェクト
- Tree オブジェクト
- Commit オブジェクト
共通事項として、これらのオブジェクトは以下の形式で.git/objects
フォルダに保存されています。
- フォルダ名:ルールに従って SHA-1 ハッシュ化した先頭 2 文字
- ファイル名:ルールに従って SHA-1 ハッシュ化した 3 文字目以降
- ファイル内容:ファイル内容を zlib で可逆圧縮したもの2
ハッシュ化する内容はオブジェクト毎に以下のように決まっています。
- Blob オブジェクト
blob {file_size}\0{file_content}
- Tree オブジェクト
tree {tree_size}\0{tree_content}
- Commit オブジェクト
commit {commit_size}\0{commit_content}
ファイルサイズとファイル内容の先頭にオブジェクトの種別を付けたものをハッシュ化したものですね。
まだいまいちよくわからないと思うので、それぞれのオブジェクトについて見ていきます。
Blob オブジェクト
Blob オブジェクトはファイルの内容を格納するオブジェクトです。
ファイル名やメタデータなどは含まれていないので、本当に中身のテキストデータだけを格納しています。
ファイル名については次の Tree オブジェクトで解決します。
Tree オブジェクト
Tree オブジェクトは Blob オブジェクトや Tree オブジェクトを格納するオブジェクトです。
Tree オブジェクトがフォルダ、Blob オブジェクトがファイルと考えると分かりやすいですね。
フォルダの中にフォルダが存在することもあるので、Tree の中に Tree を入れることができます。
また、Blob オブジェクトに対してファイル名やモード(パーミッション)を設定します。
Commit オブジェクト
Commit オブジェクトは Commit 時の情報を格納するオブジェクトです。
Commit 時の Tree オブジェクト、親 Commit や author, committer の情報、Commit コメントを保持しています。
また、この Commit オブジェクトの Hash 値こそが、皆さんがよく目にする Commit 番号になります。
上記のオブジェクトを組み合わせることで、Commit に対してディレクトリ構成を保存することができ、前の Commit との比較ができるようになります。
Git コマンドをつかって Git 操作する
実際にどのような Git オブジェクトが作成されるのか、以下の作業を実際に行いつつ、確認していきましょう。
- git 初期化
- sample1.txt の作成・コミット
- sample2.txt の作成・コミット
git 初期化
git init
.git
フォルダが作成され、他の git コマンドが実行できる環境が整えられます。
この時点ではまだオブジェクトは作成されていません。
sample1.txt の作成・コミット
sample1.txt の中身は以下にして作成・コミットを実行します。
text1
$ git commit
[master (root-commit) f5dcc40] feat: add sample
1 file changed, 1 insertion(+)
create mode 100644 sample1.txt
git log
コマンドでコミット番号を確認することができます。
$ git log
commit f5dcc409e6878ac4be99556122a52a8cc5233fdb (HEAD -> master)
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 11:46:53 2024 +0900
feat: add sample
では早速このコミット番号から Commit オブジェクトの中身を確認してみましょう。
先ほど記載したとおり、このコミット番号こそが Commit オブジェクトをハッシュ化した時の名前になります。
データの実態としては.git/objects
下に以下の規則で配置されています。
- フォルダ名: Hash 値の先頭 2 文字
- 今回の場合は
f5
フォルダ
- 今回の場合は
- ファイル名: Hash 値の 3 文字目以降
- 今回の場合は
dcc409e6878ac4be99556122a52a8cc5233fdb
ファイル
- 今回の場合は
これらのファイルは zlib により可逆圧縮されているため、そのまま確認することはできませんが、git cat-file
コマンドで Git オブジェクトの中身を確認することができます。
Commit オブジェクト確認
$ git cat-file -p f5dcc409e6878ac4be99556122a52a8cc5233fdb
tree 2820df800c98a1065b6ddb623919c535fe520dd7
author t-sakamoto <xxxx@xxxx> 1719629213 +0900
committer t-sakamoto <xxxx@xxxx> 1719629213 +0900
feat: add sample
以下の内容を確認することができます。
- tree : Commit した Tree オブジェクト
- author : Commit した author の user.name, user.email, UNIX 時間, タイムゾーン
- committer : Commit した committer の user.name, user.email, UNIX 時間, タイムゾーン
- (空行)
- コミットコメント
今回は初回コミットなので、親 Commit オブジェクトは記載されていません。(sample2.txt のコミット時に確認します)
Tree オブジェクト確認
Commit オブジェクトに紐づいている Tree オブジェクトを確認してみましょう。
$ git cat-file -p 2820df800c98a1065b6ddb623919c535fe520dd7
100644 blob 156511ae0d8a20e685576022288231cea230248b sample1.txt
以下の内容を確認することができます。
- モード :
- 100644 : 通常のファイル
- 100755 : 実行可能ファイル
- 120000 : シンボリックリンク
- ...
- オブジェクトの種類 : blob, tree
- オブジェクトの Hash 値
- Blob・Tree オブジェクトの名前(ファイル名・フォルダ名)
作成した sample1.txt が紐づいていますね。
Blob オブジェクト確認
続いて先ほどの sample1.txt の Blob オブジェクトを確認します。
$ git cat-file -p 156511ae0d8a20e685576022288231cea230248b
text1
こちらにはファイルの中身だけが入っているのがわかりますね。
sample2.txt の作成・コミット
ではファイルを追加した場合にどのようなオブジェクトが生成されるのか見てみましょう。
sample2.txt の中身は以下にして作成・コミットを実行します。
text2
$ git commit
[master 13d6976] feat: add sample2
1 file changed, 1 insertion(+)
create mode 100644 sample2.txt
先ほどと同様にログから Commit オブジェクトを確認してみます。
$ git log
commit 13d6976836db0b11084630798f5c48e7168f5dde (HEAD -> master)
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 12:02:52 2024 +0900
feat: add sample2
commit f5dcc409e6878ac4be99556122a52a8cc5233fdb
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 11:46:53 2024 +0900
feat: add sample
Commit オブジェクト確認
$ git cat-file -p 13d6976836db0b11084630798f5c48e7168f5dde
tree 0bf31a3bf6696a899dafd7e91d587a365ea36703
parent f5dcc409e6878ac4be99556122a52a8cc5233fdb
author t-sakamoto <xxxx@xxxx> 1719630172 +0900
committer t-sakamoto <xxxx@xxxx> 1719630172 +0900
feat: add sample2
初回コミットと違い、親 Commit が存在するので parent に sample1.txt の Commit オブジェクトf5dcc409e6878ac4be99556122a52a8cc5233fdb
が記載されていますね。
もちろん、parent の Commit オブジェクトは中身は先ほどと同じものが確認できます。
$ git cat-file -p f5dcc409e6878ac4be99556122a52a8cc5233fdb
tree 2820df800c98a1065b6ddb623919c535fe520dd7
author t-sakamoto <xxxx@xxxx> 1719629213 +0900
committer t-sakamoto <xxxx@xxxx> 1719629213 +0900
feat: add sample
Tree オブジェクト確認
$ git cat-file -p 0bf31a3bf6696a899dafd7e91d587a365ea36703
100644 blob 156511ae0d8a20e685576022288231cea230248b sample1.txt
100644 blob 009b64bae3ba6955fcd9df43f7483b4d14477d63 sample2.txt
sample1.txt に加えて sample2.txt が追加されているのが確認できます。
内部処理がわかったということは?
以上で Commit 時に Git 内部でどのような処理が行われているのか分かりました。
このロジックが分かったということは、Git コマンドを使わずに再現することができるのではないでしょうか?
試してみましょう。Git がいつも機嫌がいいとは限りません。
Git コマンドを使わずに Git 操作する
先ほど確認した Git オブジェクトを再現できるように、Blob, Tree, Commit オブジェクトを、Git コマンドを使用せずに自作していきます。
自作の作業イメージは以下です。
- sample1.txt 作成・コミットの再現
- Blob オブジェクトの自作
- Tree オブジェクトの自作
- Commit オブジェクトの自作
- Commit 内容の反映
- sample2.txt 作成・コミットの再現
- Blob オブジェクトの自作
- Tree オブジェクトの自作
- Commit オブジェクトの自作
- Commit 内容の反映
各オブジェクトのハッシュ値は、ハッシュ化する内容が同じであれば変わらないので、コマンド実行で作成したオブジェクトと同じものになるかも確認します。
先ほどとは別のディレクトリで git init
して基本の構成だけ作りましょう。
sample1.txt の作成・コミットの再現
Blob オブジェクトの作成
先ほど確認した以下の Blob オブジェクトと同じ結果を目指します。
$ git cat-file -p 156511ae0d8a20e685576022288231cea230248b
text1
ひとまず、Blob オブジェクトにする sample1.txt を作成しましょう。コミットは不要です。
sample1.txt
text1
Blob の Hash 化のフォーマットは以下です。
blob <file_size>\0<file_content>
今回は python で Blob オブジェクトの Hash 値を生成して zlib 圧縮したものを.git/objects
に配置します。3
import hashlib
import os
import zlib
def create_git_object(file_path):
try:
# ファイルの内容を読み込む
with open(file_path, "rb") as file:
input_data = file.read()
# blobオブジェクトのヘッダーを作成
blob_header = b"blob " + str(len(input_data)).encode() + b'\0'
# データをヘッダーとともに結合
blob_data = blob_header + input_data
# データをSHA-1でハッシュ化
sha1_hash = hashlib.sha1(blob_data).hexdigest()
print(f"BlobオブジェクトのSHA-1ハッシュ: {sha1_hash}")
# Gitのオブジェクトディレクトリに配置するパスを作成
object_dir = os.path.join(".git", "objects", sha1_hash[:2])
object_path = os.path.join(object_dir, sha1_hash[2:])
# ディレクトリが存在しない場合は作成する
os.makedirs(object_dir, exist_ok=True)
# データを圧縮
compressed_data = zlib.compress(blob_data)
# 圧縮データをファイルに書き込む
with open(object_path, "wb") as git_object_file:
git_object_file.write(compressed_data)
print(f"Blobオブジェクトを作成しました: {object_path}")
except FileNotFoundError:
print(f"Error: ファイル '{file_path}' が見つかりませんでした。")
# ファイルパスを指定してBlobオブジェクトを作成
file_path = "./sample1.txt"
create_git_object(file_path)
ソースは ChatGPT にいい感じのものを作ってもらいました。
ファイルパスは作成したファイルを指定します。
file_path = "./sample1.txt"
実行結果は以下です。
$ python zlib-blob.py
BlobオブジェクトのSHA-1ハッシュ: 156511ae0d8a20e685576022288231cea230248b
Blobオブジェクトを作成しました: .git/objects/15/6511ae0d8a20e685576022288231cea230248b
$ git cat-file -p 156511ae0d8a20e685576022288231cea230248b
text1
Hash 値も実行結果も、コマンド実行時に作成されたものと全く同じ Blob オブジェクトを作ることができました!
Tree オブジェクトの作成
次はこちらの再現です。
$ git cat-file -p 2820df800c98a1065b6ddb623919c535fe520dd7
100644 blob 156511ae0d8a20e685576022288231cea230248b sample1.txt
Tree オブジェクトの Hash 値を生成して zlib 圧縮したものを.git/objects
に配置します。
import hashlib
import os
import zlib
def create_tree_object(entries):
# エントリをソートする(バイト順)
sorted_entries = sorted(entries)
# treeオブジェクトを構築する
tree_content = b"".join(sorted_entries)
# ハッシュ計算
sha1_hash = hashlib.sha1()
tree_data = b"tree " + str(len(tree_content)).encode() + b"\0" + tree_content
sha1_hash.update(tree_data)
sha1_hash = sha1_hash.hexdigest()
print(f"TreeオブジェクトのSHA-1ハッシュ: {sha1_hash}")
# オブジェクトを.git/objectsに保存
create_git_object(tree_data, sha1_hash)
def create_git_object(tree_data, sha1_hash):
# Gitのオブジェクトディレクトリに配置するパスを作成
object_dir = os.path.join(".git", "objects", sha1_hash[:2])
object_path = os.path.join(object_dir, sha1_hash[2:])
# ディレクトリが存在しない場合は作成する
os.makedirs(object_dir, exist_ok=True)
# データを圧縮
compressed_data = zlib.compress(tree_data)
# 圧縮データをファイルに書き込む
with open(object_path, "wb") as git_object_file:
git_object_file.write(compressed_data)
print(f"Treeオブジェクトを作成しました: {object_path}")
# 複数のファイルエントリを準備
files = [
{"file_name": "sample1.txt", "hash": "156511ae0d8a20e685576022288231cea230248b"}
]
# エントリリストを作成
entries = []
for file in files:
entry = b"100644 " + file["file_name"].encode() + b"\0" + bytes.fromhex(file["hash"])
entries.append(entry)
# Treeオブジェクトを作成
create_tree_object(entries)
ファイル名と先ほど生成した Blob オブジェクトの Hash 値を設定して実行します。
files = [
{"file_name": "sample1.txt", "hash": "156511ae0d8a20e685576022288231cea230248b"}
]
実行結果
$ python zlib-tree.py
TreeオブジェクトのSHA-1ハッシュ: 2820df800c98a1065b6ddb623919c535fe520dd7
Treeオブジェクトを作成しました: .git/objects/28/20df800c98a1065b6ddb623919c535fe520dd7
git cat-file -p 2820df800c98a1065b6ddb623919c535fe520dd7
100644 blob 156511ae0d8a20e685576022288231cea230248b sample1.txt
こちらも同じ結果が確認でき、Tree オブジェクトが作成できました!
Commit オブジェクトの作成
次に Commit オブジェクトの再現です。(Hash 値を同じにするために Unix タイムスタンプは同じ値を使用しています。)
$ git cat-file -p f5dcc409e6878ac4be99556122a52a8cc5233fdb
tree 2820df800c98a1065b6ddb623919c535fe520dd7
author t-sakamoto <xxxx@xxxx> 1719629213 +0900
committer t-sakamoto <xxxx@xxxx> 1719629213 +0900
feat: add sample
Commit オブジェクトの Hash 値を生成して zlib 圧縮したものを.git/objects
に配置します。
import hashlib
import os
import zlib
def create_commit_object(tree_hash, parent_hashes, author, committer, commit_message):
# コミットの情報を文字列で構築する
commit_content = "tree {}\n".format(tree_hash)
if parent_hashes:
parent_str = "\nparent {}".format("\nparent ".join(parent_hashes))
commit_content += parent_str + "\n"
commit_content += "author {}\n".format(author)
commit_content += "committer {}\n\n".format(committer)
commit_content += "{}\n".format(commit_message)
# ハッシュ計算
sha1_hash = hashlib.sha1()
commit_data = b"commit " + str(len(commit_content)).encode() + b"\0" + commit_content.encode()
sha1_hash.update(commit_data)
commit_hash = sha1_hash.hexdigest()
print("CommitオブジェクトのSHA-1ハッシュ:", commit_hash)
create_git_object(commit_data, commit_hash)
def create_git_object(data, sha1_hash):
# Gitのオブジェクトディレクトリに配置するパスを作成
object_dir = os.path.join(".git", "objects", sha1_hash[:2])
object_path = os.path.join(object_dir, sha1_hash[2:])
# ディレクトリが存在しない場合は作成する
os.makedirs(object_dir, exist_ok=True)
# データを圧縮
compressed_data = zlib.compress(data)
# 圧縮データをファイルに書き込む
with open(object_path, "wb") as git_object_file:
git_object_file.write(compressed_data)
print(f"Commitオブジェクトを作成しました: {object_path}")
# データ
tree_hash = "2820df800c98a1065b6ddb623919c535fe520dd7" # ツリーオブジェクトのSHA-1ハッシュ
parent_hashes = [] # 親コミットのハッシュ(初回コミットなら空リスト)
author = "t-sakamoto <xxxx@xxxx> 1719629213 +0900"
committer = "t-sakamoto <xxxx@xxxx> 1719629213 +0900"
commit_message = "feat: add sample"
# コミットオブジェクトの作成とSHA-1ハッシュの計算
commit_hash = create_commit_object(tree_hash, parent_hashes, author, committer, commit_message)
Tree オブジェクトは先ほど生成したものを設定し、初回 Commit なので親 Commit はなしで実行します。
tree_hash = "2820df800c98a1065b6ddb623919c535fe520dd7" # ツリーオブジェクトのSHA-1ハッシュ
parent_hashes = [] # 親コミットのハッシュ(初回コミットなら空リスト)
author = "t-sakamoto <xxxx@xxxx> 1719629213 +0900"
committer = "t-sakamoto <xxxx@xxxx> 1719629213 +0900"
commit_message = "feat: add sample"
実行結果
$ python zlib-commit.py
CommitオブジェクトのSHA-1ハッシュ: f5dcc409e6878ac4be99556122a52a8cc5233fdb
Commitオブジェクトを作成しました: .git/objects/f5/dcc409e6878ac4be99556122a52a8cc5233fdb
$ git cat-file -p f5dcc409e6878ac4be99556122a52a8cc5233fdb
tree 2820df800c98a1065b6ddb623919c535fe520dd7
author t-sakamoto <xxxx@xxxx> 1719629213 +0900
committer t-sakamoto <xxxx@xxxx> 1719629213 +0900
feat: add sample
こちらも同じ結果が確認でき、Commit オブジェクトが作成できました!
Commit 反映
Commit オブジェクトの作成はできましたが、この時点ではまだブランチには紐づけられていません。
$ git log
fatal: your current branch 'master' does not have any commits yet
git commit コマンド実行時は実際には以下の処理がされています。
- 各 Git オブジェクトの作成
-
.git/logs
への反映 -
.git/refs
への反映
1 は完了しているので、2,3 の 設定しましょう。
.git
に以下の構成を用意します。
.git
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
└── refs
└── heads
└── master
各ファイルに以下の内容を設定します
.git/logs/HEAD
直前の Commit と現在の Commit、ユーザー情報や Commit コメントなどを記載します。
0000000000000000000000000000000000000000 f5dcc409e6878ac4be99556122a52a8cc5233fdb sakamoto <xxxx@xxxx> 1719629213 +0900 commit (initial): feat: add sample
.git/logs/refs/heads/master
master ブランチで作業しているので、HEAD と同じ内容で大丈夫です。
0000000000000000000000000000000000000000 f5dcc409e6878ac4be99556122a52a8cc5233fdb sakamoto <xxxx@xxxx> 1719629213 +0900 commit (initial): feat: add sample
.git/refs/heads/master
先ほど作成したコミットオブジェクトのハッシュ値(コミット番号)を記載します。
f5dcc409e6878ac4be99556122a52a8cc5233fdb
以上で master ブランチと commit の紐づけがを完了です。
git log
で確認してみましょう。
$ git log
commit f5dcc409e6878ac4be99556122a52a8cc5233fdb (HEAD -> master)
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 11:46:53 2024 +0900
feat: add sample
無事に git コマンドを使用せずに再現することができました!
図にするとこんな感じですね。
sample2.txt の作成・コミットの再現
次に、sample2.txt を作成して自作コミットします。
基本的な操作は同じなので、設定値と結果だけ記載していきます。
Blob オブジェクト作成
file_path = "./sample2.txt" # ファイル内容は text2
$ python zlib-blob.py
BlobオブジェクトのSHA-1ハッシュ: 009b64bae3ba6955fcd9df43f7483b4d14477d63
Blobオブジェクトを作成しました: .git/objects/00/9b64bae3ba6955fcd9df43f7483b4d14477d63
Tree オブジェクト作成
files = [
{"file_name": "sample1.txt", "hash": "156511ae0d8a20e685576022288231cea230248b"},
{"file_name": "sample2.txt", "hash": "009b64bae3ba6955fcd9df43f7483b4d14477d63"}
]
$ python zlib-tree.py
TreeオブジェクトのSHA-1ハッシュ: 0bf31a3bf6696a899dafd7e91d587a365ea36703
Treeオブジェクトを作成しました: .git/objects/0b/f31a3bf6696a899dafd7e91d587a365ea36703
Commit オブジェクト作成
tree_hash = "0bf31a3bf6696a899dafd7e91d587a365ea36703" # ツリーオブジェクトのSHA-1ハッシュ
parent_hashes = ["f5dcc409e6878ac4be99556122a52a8cc5233fdb"] # 親コミットのハッシュ(初回コミットなら空リスト)
author = "t-sakamoto <xxxx@xxxx> 1719630172 +0900"
committer = "t-sakamoto <xxxx@xxxx> 1719630172 +0900"
commit_message = "feat: add sample2"
$ python zlib-commit.py
CommitオブジェクトのSHA-1ハッシュ: 13d6976836db0b11084630798f5c48e7168f5dde
Commitオブジェクトを作成しました: .git/objects/13/d6976836db0b11084630798f5c48e7168f5dde
Commit 反映
.git/logs/HEAD
, .git/logs/refs/heads/master
に以下を追加しましょう。(ログなので、上書きではなく追加になります)
f5dcc409e6878ac4be99556122a52a8cc5233fdb 13d6976836db0b11084630798f5c48e7168f5dde t-sakamoto <xxxx@xxxx> 1719630172 +0900 commit: feat: add sample2
.git/refs/heads/master
を最新のコミットに上書きすればコミット完了です。
13d6976836db0b11084630798f5c48e7168f5dde
git log
で確認してみましょう。
$ git log
commit 13d6976836db0b11084630798f5c48e7168f5dde (HEAD -> master)
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 12:02:52 2024 +0900
feat: add sample2
commit f5dcc409e6878ac4be99556122a52a8cc5233fdb
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 11:46:53 2024 +0900
feat: add sample
問題なくコミットできています!
図にするとこんな感じですね。
ファイル更新
これまではファイル追加だけでしたが、sample2.txt の内容を変更の commit を再現しましょう。
とは言ってもやることは同じです。
Blob オブジェクト作成
./sample2.txt
を以下に変更します。
text2
add text
$ python zlib-blob.py
GitオブジェクトのSHA-1ハッシュ: 2800e4d18fb4ad972594f9cf9e01d94bf3c02bb6
Gitオブジェクトを作成しました: .git/objects/28/00e4d18fb4ad972594f9cf9e01d94bf3c02bb6
Tree オブジェクト作成
files = [
{"file_name": "sample1.txt", "hash": "156511ae0d8a20e685576022288231cea230248b"},
{"file_name": "sample2.txt", "hash": "2800e4d18fb4ad972594f9cf9e01d94bf3c02bb6"}
]
$ python zlib-tree.py
TreeオブジェクトのSHA-1ハッシュ: ce51f5548e1bbe8cbdf6b094cfdfba01928b1970
Treeオブジェクトを作成しました: .git/objects/ce/51f5548e1bbe8cbdf6b094cfdfba01928b1970
Commit オブジェクト作成
tree_hash = "ce51f5548e1bbe8cbdf6b094cfdfba01928b1970" # ツリーオブジェクトのSHA-1ハッシュ
parent_hashes = ["13d6976836db0b11084630798f5c48e7168f5dde"] # 親コミットのハッシュ(初回コミットなら空リスト)
author = "t-sakamoto <xxxx@xxxx> 1719662760 +0900"
committer = "t-sakamoto <xxxx@xxxx> 1719662760 +0900"
commit_message = "feat: add text"
$ python zlib-commit.py
CommitオブジェクトのSHA-1ハッシュ: 224cb346501c44e85506221b95122f1cdc8cc553
Commitオブジェクトを作成しました: .git/objects/22/4cb346501c44e85506221b95122f1cdc8cc553
Commit 反映
.git/logs/HEAD
, .git/logs/refs/heads/master
に以下を追加しましょう。(ログなので、上書きではなく追加になります)
13d6976836db0b11084630798f5c48e7168f5dde 224cb346501c44e85506221b95122f1cdc8cc553 t-sakamoto <xxxx@xxxx> 1719662760 +0900 commit: feat: add text
.git/refs/heads/master
を最新の Commit に上書きします。
224cb346501c44e85506221b95122f1cdc8cc553
差分も問題なさそうですね(text2 の行は改行を入れていなかったので差分に含まれています)。
$ git diff HEAD^
diff --git a/sample2.txt b/sample2.txt
index 009b64b..2800e4d 100644
--- a/sample2.txt
+++ b/sample2.txt
@@ -1 +1,2 @@
-text2
\ No newline at end of file
+text2
+add text
\ No newline at end of file
branch 作成
これで基本的な git commit
の操作はできるようになりました。
次は branch 作成とマージを行ってみましょう。
まずは develop ブランチを作成しましょう。
以下のように develop ファイルを作成します。
├── .git
│ ├── HEAD
│ ├── branches
│ ├── info
│ │ └── exclude
│ ├── logs
│ │ ├── HEAD
│ │ └── refs
│ │ └── heads
│ │ ├── develop
│ │ └── master
│ └── refs
│ ├── heads
│ │ ├── develop
│ │ └── master
│ └── tags
├── sample1.txt
└── sample2.txt
各ファイルと内容を以下に設定します。
.git/logs/refs/heads/develop
初回 Commit 、最新 Commit などを記載します。
0000000000000000000000000000000000000000 224cb346501c44e85506221b95122f1cdc8cc553 t-sakamoto <xxxx@xxxx> 1719664681 +0900 branch: Created from HEAD
.git/logs/HEAD
(追加)
224cb346501c44e85506221b95122f1cdc8cc553 224cb346501c44e85506221b95122f1cdc8cc553 t-sakamoto <xxxx@xxxx> 1719664681 +0900 checkout: moving from master to develop
.git/refs/heads/develop
224cb346501c44e85506221b95122f1cdc8cc553
.git/HEAD
ref: refs/heads/develop
これで git branch
で現在のブランチを確認すると develop に移動できています。
$ git branch
* develop
master
sample3.txt 追加
develop ブランチではsample3.txt
を作成して Commit しましょう。
text3
Blob オブジェクト作成
$ python zlib-blob.py
GitオブジェクトのSHA-1ハッシュ: 1664584d9a5168247c12877b7fdd2f5549d1d1dd
Gitオブジェクトを作成しました: .git/objects/16/64584d9a5168247c12877b7fdd2f5549d1d1dd
Tree オブジェクト作成
$ python zlib-tree.py
TreeオブジェクトのSHA-1ハッシュ: 362792f6730916cd64398684592b671417aeaee1
Treeオブジェクトを作成しました: .git/objects/36/2792f6730916cd64398684592b671417aeaee1
Commit オブジェクト作成
$ python zlib-commit.py
CommitオブジェクトのSHA-1ハッシュ: 2b9b21cf1ffd08193127126d4c249d5f45cc013b
Commitオブジェクトを作成しました: .git/objects/2b/9b21cf1ffd08193127126d4c249d5f45cc013b
.git 更新(省略)
$ git log
commit 2b9b21cf1ffd08193127126d4c249d5f45cc013b (HEAD -> develop)
Author: t-sakamoto <xxxx@xxxx>
Date: Sat Jun 29 21:51:02 2024 +0900
feat: add sample3
branch マージと変更
.git/logs/refs/heads/master
, .git/logs/HEAD
に以下を追加
224cb346501c44e85506221b95122f1cdc8cc553 2b9b21cf1ffd08193127126d4c249d5f45cc013b t-sakamoto <xxxx@xxxx> 1719666790 +0900 merge develop: Fast-forward
.git/refs/heads/master
を以下に変更
2b9b21cf1ffd08193127126d4c249d5f45cc013b
.git/HEAD
を以下に変更して branch を master に移動
ref: refs/heads/master
これで Git コマンドを使用せずにコミット、ブランチ作成、マージができました!
まとめ
毎日使っている Git コマンドによって、実際に内部でどんなことが行われているのか、その一部が少しだけわかりました。
もっと自分には理解できないような複雑な処理をしているのではないかと最初は思っていましたが、蓋を開けてみれば理解できる程度にはシンプルなんだなあと意外な結果でした。
思い通りに Git 操作できない場合は、そのコマンドが Git オブジェクトやディレクトリに対してどんな操作をしているのか調べてみると解決の糸口が見つかるかもしれませんね!
株式会社 BTM ではエンジニアの採用をしております。
ご興味がある方はぜひコチラをご覧ください。
-
Git - Git オブジェクト
https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Git%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88 ↩ -
zlib Home Site
https://www.zlib.net/ ↩ -
zlib --- gzip 互換の圧縮 — Python 3.12.4 ドキュメント
https://docs.python.org/ja/3/library/zlib.html ↩