کدنامهمرجع‌های مهندسی نرم‌افزار، به فارسی
گیت · فصل ۲Git · Chapter 2

مدل داده:
blob، tree، commit

Git's data model:
blob, tree, and commit

فصل قبل دیدیم git add حتی قبل از commit، محتوا را وارد پایگاه‌دادهٔ Git می‌کند. حالا درِ موتور را باز می‌کنیم: اسم فایل کجا می‌رود، پوشه چطور ساخته می‌شود و یک commit چطور همهٔ این تکه‌ها را به تاریخچه وصل می‌کند؟

In Chapter 01, we saw that git add can write file content to the object database before any commit exists. Now: how does Git combine filenames, nested directories, content, and history metadata into a commit?

۱۰بخشsections
۸۵دقیقهminزمان مطالعهreading time
۱۸تمرین با پاسخsolved exercises
۴نمودارdiagrams

از چند فایل تا یک commitFrom files to a commit

یک پوشهٔ کوچک را تصور کن: دو فایل و یک زیرپوشه. Git باید هم محتوای فایل‌ها را نگه دارد، هم اسم و جای آن‌ها را، و هم بداند این عکس فوری بعد از کدام commit آمده. به‌جای حفظ جواب، مسیر را از بایت‌های فایل تا commit با دست دنبال می‌کنیم.

Imagine a folder with two files and one subdirectory. Git must retain file bytes, know their names and locations, and record which snapshot came before. If you think it creates one giant project file, we'll test that idea in a moment.

File bytes form a blob, trees organize entries, and a commit records the root tree file bytescontent blobserialized bytes treenames + modes + IDs commitroot tree + metadata arrows mean references; each object has its own ID

نمودار ۱ — blob نام فایل را نمی‌داند؛ tree نام و جای آن را ثبت می‌کند و commit به tree ریشه و فراداده اشاره دارد.

Diagram 1 — A blob does not know its filename. A tree records names and locations; a commit points to the root tree and metadata.

مسیر اصلی: بایت‌های فایل به blob، فهرست پوشه به tree، و tree ریشه همراه با والد و اطلاعات نویسنده، ثبت‌کننده و پیام به commit می‌رسد. اسم انسانی مثل شاخه یا tag هم اشاره‌گری است که به شیء اشاره می‌کند؛ خودش عکس فوری نیست.

The path is: file bytes become a blob; a directory listing becomes a tree; and the root tree plus parent, author, committer, and message form a commit. A human name such as a branch or tag is a ref to an object, not the snapshot itself.

blob: محتوا بدون اسم فایلA blob: content without a filename

آیا مسیر فایل در شناسه‌اش دخیل است؟ hash-object می‌تواند شناسه را حساب کند؛ با -w همان شیء را هم در مخزن می‌نویسد. اول فقط حسابش کنیم.

Does a file path contribute to its ID? hash-object computes an ID; -w also writes the object. First, compute only:

bash · no repository required
printf 'hello\n' | git hash-object --stdin
ce013625030ba8dba906f756967f9e9ca394464a

این شناسه یک blob برای همین شش بایت در مخزن SHA-1 است؛ بدون -w چیزی ذخیره نشده. Git فقط متن خام را hash نمی‌کند: سرآیند مفهومی blob 6\0 می‌آید—نوع، فاصله، اندازه برحسب بایت، NUL و بعد محتوا. برای شیءهای دیگر هم نوع و محتوای serialized آن‌ها دخیل است.

This is the blob ID for these six bytes in a SHA-1 repository; without -w, nothing is stored. Git does not hash raw text alone: a conceptual header blob 6\0 comes first—type, space, byte size, NUL, then content. Other object types include their type and serialized content too.

یک بایت را عوض کنیم. قبل از اجرا حدس بزن: آیا شناسه همان می‌ماند؟ هر دو فرمان فقط حساب می‌کنند و چیزی ذخیره نمی‌کنند.

Change one byte. Predict first: will the ID stay the same? Both commands only compute; neither stores an object.

bash · compare object IDs
printf 'hello\n' | git hash-object --stdin
ce013625030ba8dba906f756967f9e9ca394464a
printf 'hellO\n' | git hash-object --stdin
a different object ID

شناسه عوض شد، چون بایت محتوا عوض شده است. این مشاهده را به تضمین ریاضیِ «برخورد ناممکن است» تبدیل نکن؛ برای کاربرد معمول، Git به hash مقاوم به برخورد تکیه می‌کند.

The ID changes because the content bytes changed. Do not turn this observation into a mathematical claim that collisions are impossible; Git relies on a collision-resistant hash for practical use.

قالب شناسهObject format

SHA-1 قالب پیش‌فرض رایج است؛ Git از مخزن SHA-256 هم پشتیبانی می‌کند، اگر در نسخه فعال باشد. مستند فعلی می‌گوید این دو قالب مخزن فعلاً با هم سازگار نیستند. پس شناسه چهل‌نویسه‌ای بالا مخصوص SHA-1 است؛ طول شناسه بخشی از تعریف blob نیست.

SHA-1 is the common default; Git also supports SHA-256 repositories when enabled. Current documentation says the two repository formats are not currently interoperable. The 40-character ID above is specific to SHA-1; ID length is not part of the blob definition.

حالا در یک مخزن آزمایشی شیء را بنویس و نوع و اندازه‌اش را بپرس:

Now write the object in a disposable repository and inspect its type and size:

bash · disposable repository
git init -q object-lab
cd object-lab
blob=$(printf 'hello\n' | git hash-object -w --stdin)
git cat-file -t "$blob"
blob
git cat-file -s "$blob"
6
printf '%s\n' "$blob"
ce013625030ba8dba906f756967f9e9ca394464a
printf '.git/objects/%s/%s\n' "$(printf '%s' "$blob" | cut -c1-2)" "$(printf '%s' "$blob" | cut -c3-)"
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
git cat-file -p "$blob"
hello

-t نوع را نشان می‌دهد، -s اندازهٔ محتوا را و -p نمایش خوانا را. دو حرف اول شناسه پوشه و باقی‌اش نام فایل جدا هستند. اما مسیر مستقل فقط یک شکل ذخیره‌سازی است؛ بعداً شیء ممکن است وارد pack شود. blob در هیچ‌کدام از این خروجی‌ها اسم فایل ندارد.

-t shows type, -s content size, and -p readable content. The first two ID characters name the loose-object directory; the rest name its file. But a standalone path is only one storage form; the object may later be packed. None of these blob views includes a filename.

tree اسم و مجوز و شناسه را کنار هم می‌گذاردA tree combines names, modes, and IDs

اگر blob اسم را نمی‌داند، «این محتوا در notes/today.txt است» کجا ثبت می‌شود؟ در tree. هر مدخل نام، مجوز، نوع و شناسه دارد؛ مسیر تو‌در‌تو با کنار هم گذاشتن مدخل‌های چند tree ساخته می‌شود.

If a blob does not know its name, where does Git record “this content is notes/today.txt”? In a tree. Each entry has a name, mode, type, and ID; nested paths compose entries across trees.

Root tree contains a README blob and a nested notes tree root treeentries: name + mode + ID 100644 · blob · a1b2…README.md 040000 · tree · c3d4…notes/ 100644 · blob · e5f6…today.txt

نمودار ۲ — نام پوشه و نام فایل در دو سطح کنار هم مسیر کامل را می‌سازند؛ پیکان‌ها اشارهٔ tree به شیء را نشان می‌دهند.

Diagram 2 — Directory and filename entries compose a path across two levels; arrows show tree-to-object references.

خروجی git ls-tree این مدخل‌ها را خوانا می‌کند. 100644 فایل معمولی، 100755 اجرایی، 120000 symlink، 040000 پوشه و 160000 gitlink یک submodule است. این مجوزها Unix-like هستند و همهٔ owner/group و مجوزهای محلی را کپی نمی‌کنند.

git ls-tree renders these entries readably. 100644 is a regular file, 100755 executable, 120000 symlink, 040000 directory, and 160000 a submodule gitlink. These Unix-like modes do not copy every local owner/group and permission bit.

mode/typemode/typeمعناMeaning
100644 blob / 100755 blobفایل معمولی / اجراییRegular / executable file
120000 blobsymlink؛ blob متن مقصد را نگه می‌داردSymlink; blob stores its target text
040000 treeزیرپوشه؛ اشاره به tree دیگرDirectory; points to another tree
160000 commitgitlink به commit یک submoduleGitlink to a submodule commit

محتوای یکسان را زیر دو اسم ناحیه‌ آماده‌سازی کن و شناسهها را مقایسه کن. ls-files --stage ناحیه‌ آماده‌سازی را می‌خواند؛ هنوز commit نداریم.

Stage identical content under two names and compare IDs. ls-files --stage reads the index; there is no commit yet.

bash · disposable repository
mkdir -p assets
printf 'shared note\n' > assets/one.txt
cp assets/one.txt assets/two.txt
git add assets/one.txt assets/two.txt
git ls-files --stage
100644 a1b2c3d4... 0\tassets/one.txt
100644 a1b2c3d4... 0\tassets/two.txt
git commit -m 'Add two names for one note'
git ls-tree HEAD:assets
100644 blob a1b2c3d4...    one.txt
100644 blob a1b2c3d4...    two.txt

دو مسیر به یک blob شناسه می‌رسند. اگر تغییرنام کنی، blob می‌تواند همان بماند، اما tree نام تازه‌ای دارد و شناسهٔ tree عوض می‌شود. ناحیه‌ آماده‌سازی قبل از commit فهرستی تخت از مسیرهاست؛ commit آن را به treeهای تو‌در‌تو تبدیل می‌کند.

Both paths reach one blob ID. A rename can leave the blob unchanged while changing the tree entry and tree ID. Before commit, the index is a flat path list; commit turns it into nested trees.

commit: عکس فوری به‌علاوهٔ تاریخچهA commit: snapshot plus history

tree شکل پوشه را می‌داند، نه اینکه چه کسی عکس فوری را نوشته یا بعد از کدام commit آمده. یک مخزن کوچک می‌سازیم، با مسیر معمول add و commit، بعد نتیجه را از پایگاه اشیا می‌خوانیم.

A tree knows the directory shape, not who authored the snapshot or which commit came before. We'll use the normal add/commit workflow, then inspect the object database.

bash · disposable object-lab
git config user.name 'Ada Example'
git config user.email 'ada@example.test'
mkdir -p notes
printf '# Field notes\n' > README.md
printf 'first observation\n' > notes/today.txt
git add README.md notes/today.txt
git commit -m 'Record first observation'
[main (root-commit) 31a6f2c] Record first observation
 2 files changed, 2 insertions(+)
 create mode 100644 README.md
 create mode 100644 notes/today.txt
git cat-file -p HEAD
tree 8c01d4e...
author Ada Example <ada@example.test> 1750000000 +0330
committer Ada Example <ada@example.test> 1750000000 +0330

Record first observation

شناسه و زمان نمونه‌اند؛ در مخزن تو فرق می‌کنند. خط tree tree ریشه را معرفی می‌کند. commit اول والد ندارد؛ commit معمولی بعدی یک والد دارد و merge می‌تواند چند والد داشته باشد. نویسنده تغییر را نوشته؛ ثبت‌کننده آن را در این commit ثبت کرده، پس این دو می‌توانند متفاوت باشند.

The sample ID and timestamp are illustrative. The tree line names the root tree. The initial commit has no parent; an ordinary later commit has one and a merge may have several. The author wrote the change; the committer recorded it, so they can differ.

فقط یادداشت را عوض می‌کنیم و عکس فوری دوم را می‌سازیم:

Change only the note and create a second snapshot:

bash · same repository
printf 'second observation\n' > notes/today.txt
git add notes/today.txt
git commit -m 'Record second observation'
git cat-file -p HEAD
tree 9a7b3c1...
parent 31a6f2c...
author Ada Example <ada@example.test> ...
committer Ada Example <ada@example.test> ...

Record second observation
git ls-tree HEAD
100644 blob 7d8e9f0...    README.md
040000 tree 4a5b6c7...    notes
git ls-tree HEAD:notes
100644 blob 2f3a4b5...    today.txt

از HEAD به tree ریشه و بعد به پوشه رسیدیم. blob فایل تغییرکرده شناسه تازه دارد؛ treeهای مسیر هم عوض‌اند. blob README که عوض نشده می‌تواند در هر دو عکس فوری همان شناسه را داشته باشد.

We followed HEAD to the root and nested tree. The changed file's blob gets a new ID, as do trees along its path. Unchanged README can reuse its ID in both snapshots.

Two commits have different trees but share the unchanged README blob commit C1tree T1 · no parent commit C2tree T2 · parent C1 parent tree T1 tree T2 same README blob

نمودار ۳ — commit دوم به اول وصل است؛ هر عکس فوری tree خودش را دارد، اما blob بدون تغییر README قابل استفادهٔ دوباره است.

Diagram 3 — The second commit links to the first; each snapshot has its tree, while the unchanged README blob can be reused.

پیام یکسان یعنی commit یکسان نیست. tree، والدها، نویسنده، ثبت‌کننده، زمان و پیام در محتوای commit هستند و روی شناسه اثر می‌گذارند. مدل منطقی Git عکس فوری است و diff از مقایسهٔ عکس فوریها ساخته می‌شود؛ این به معنی مدل‌کردن تاریخچه به شکل diff نیست.

Same message does not mean same commit. Tree, parents, author, committer, timestamps, and message contribute to the ID. Git's logical model is snapshots; a diff is computed by comparing snapshots, not used as the history model.

tag سبک و نشانه‌دارLightweight and annotated tags

tag همیشه اشاره‌گری با نام انسانی است؛ اما اشاره‌گر سبک مستقیم به شیء هدف می‌رسد، معمولاً commit. tag نشانه‌دار یک شیء جدا از نوع tag می‌سازد که پیام، زمان و tagger را نگه می‌دارد و خودش به هدف اشاره می‌کند.

A tag is a human-named ref, but a lightweight ref points directly to its target, usually a commit. An annotated tag creates a separate tag object with message, date, and tagger, which points to the target.

نوعKindاشاره‌گر به چه چیزی می‌رسد؟Ref targetفرادادهٔ مستقل؟Separate metadata?
سبکLightweightمستقیم به شیءDirectly to an objectخیرNo
نشانه‌دارAnnotatedبه tag شیء، سپس به هدفTo tag object, then targetپیام، سازنده، زمان؛ گاهی امضاMessage, tagger, date; optional signature
bash · disposable repository
git tag v1-light
git tag -a v1-release -m 'First inspected release'
git show-ref --tags
9c8d7e6... refs/tags/v1-light
6d7e8f9... refs/tags/v1-release
git cat-file -t refs/tags/v1-light
commit
tag_oid=$(git show-ref --hash refs/tags/v1-release)
git cat-file -t "$tag_oid"
tag
git cat-file -p "$tag_oid"
object 9c8d7e6...
type commit
tag v1-release
tagger Ada Example <ada@example.test> ...

First inspected release

در نمونه، اشاره‌گر سبک مستقیم به commit می‌رسد؛ اشاره‌گر نشانه‌دار به tag object. اینجا فقط ساختار را می‌خوانیم؛ گردش‌کار روزمرهٔ tag در فصل ۱۱ می‌آید.

Here the lightweight ref points directly to a commit; the annotated ref points to a tag object. We are only inspecting structure; Chapter 11 covers day-to-day tag workflows.

Annotated tag ref points to a tag object and then a commit refs/tags/v1name / ref tag objecttagger + message committarget lightweight: ref ─────────────────────→ commit

نمودار ۴ — tag نشانه‌دار دو اشاره دارد؛ tag سبک اشاره‌گر را مستقیم به شیء هدف می‌رساند.

Diagram 4 — An annotated tag uses two references; a lightweight ref points directly to its target.

شیء را با فایل فیزیکی‌اش یکی ندانDo not confuse an object with its physical file

شیء تازه معمولاً اول به‌شکل جدا در .git/objects نوشته می‌شود. Git می‌تواند بعداً آن‌ها را در packfile جمع کند و از delta compression برای صرفه‌جویی استفاده کند. این شکل فیزیکی ذخیره‌سازی است؛ مدل منطقی هنوز شیءهای blob/tree/commit با شناسههای خودشان است.

New objects are commonly written loose under .git/objects. Git can later consolidate them into packfiles and use delta compression to save space. That is physical storage; the logical model remains blobs, trees, and commits with their own IDs.

bash · inspect storage
git count-objects -vH
count: 12
size: 48.00 KiB
in-pack: 0
packs: 0
size-pack: 0 bytes
# پس از maintenance، بخشی از خروجی ممکن است چنین شود:
git count-objects -vH
count: 0
size: 0 bytes
in-pack: 12
packs: 1
size-pack: 3.20 KiB

اعداد نمونه‌اند. count/size دربارهٔ جداهاست و in-pack/size-pack دربارهٔ packها. اگر فایل شیء را در مسیر قبلی پیدا نکردی، اول git cat-file را امتحان کن؛ ممکن است شیء بسته‌بندی‌شده باشد. نبود فایل مستقل در مسیر جدا به معنی نبود شیء نیست.

The numbers are illustrative. count/size describe loose objects; in-pack/size-pack describe packs. If the old path is absent, try git cat-file first—the object may be packed. No standalone loose file does not mean no object.

از خطا به سؤال تشخیصیTurn an error into a diagnostic question

اگر چیزی با مدل ذهنی‌ات جور درنمی‌آید، مخزن را دست‌کاری نکن. اول سؤال را کوچک کن: این شناسه واقعاً وجود دارد؟ نوعش چیست؟ از کدام tree به آن می‌رسیم؟ بیشتر خطاهای این بخش وقتی روشن می‌شوند که دوباره زنجیرهٔ «commit → tree → blob» را آرام و مرحله‌به‌مرحله دنبال کنی.

If Git rejects a short ID or a filename is absent from a blob, do not randomly change the repository. Narrow the question: does the ID exist? Is it unique? What type is it? Which tree points to it?

نشانهSymptomبررسی بعدیNext checkهنوز ثابت نشدهNot yet proven
Not a valid object namegit cat-file -e <oid>؛ ID را بازبینی کناین‌که شیء هیچ‌وقت ذخیره نشدهThat it was never stored
پیشوند کوتاه چند نتیجه داردShort prefix has several matchesgit rev-parse --disambiguate=<prefix>این‌که پیشوند یکتاستThat the prefix is unique
نام فایل در blob نیستFilename absent from blobtree را با git ls-tree بخوانInspect tree with git ls-treeاین‌که blob با مسیر فعلی ساخته شدهThat blob was created at this path
فایل جدا در مسیر نیستLoose file absentgit cat-file -t <oid>؛ git count-objects -vاین‌که شیء حذف شدهThat the object was deleted
دو commit پیام یکسان دارندTwo commits share a messagegit cat-file -p <commit>این‌که شناسه آن‌ها یکی استThat their IDs are equal

اگر cat-file شیء را پیدا نکند، فقط می‌دانیم در پایگاه اشیای در دسترس این مخزن با ورودی داده‌شده پیدا نشده؛ شناسه شاید ناقص باشد، شیء در مخزن دیگری باشد یا هرگز ذخیره نشده باشد. بدون شاهدی از نسخهٔ ذخیره‌شده، قول بازیابی نده.

If cat-file cannot find it, we only know it was not found with this input in stores available to this repository. The ID may be incomplete, the object may live elsewhere, or it may never have been stored. Do not promise recovery without evidence of a stored copy.

پنج فرض که می‌توانیم حالا بیازماییمFive assumptions we can test
«blob اسم فایل را دارد.»“A blob contains its filename.”

نه؛ محتوا در blob و نام و مجوز در tree است.

No; content is in the blob, name and mode in the tree.

«نبود فایل جدا یعنی شیء گم شده.»“No loose file means the object is gone.”

ممکن است بسته‌بندی‌شده باشد؛ با شناسه از Git بپرس.

It may be packed; ask Git by ID.

«پیام یکسان یعنی commit یکسان.»“Same message means same commit.”

tree، والدها، هویت‌ها و زمان هم دخیل‌اند.

Tree, parents, identities, and timestamps contribute too.

«هر عکس فوری همهٔ blobها را کپی می‌کند.»“Every snapshot copies all blobs.”

محتوای بی‌تغییر می‌تواند دوباره استفاده شود.

Unchanged content can be reused.

«مدل شیء یعنی diff ذخیره می‌شود.»“The object model means diffs are stored.”

مدل منطقی عکس فوری است؛ diff با مقایسه ساخته می‌شود. delta در pack بهینه‌سازی فیزیکی است.

The logical model is snapshots; diffs are computed. Pack deltas are physical optimization.

تمرین‌هاExercises

اینجا قرار نیست اسم blob و tree را از حفظ پس بدهی. هر تمرین یک سرنخ می‌دهد و ازت می‌خواهد از روی همان سرنخ بفهمی Git چه چیزی ذخیره کرده. قبل از اجرای دستور جواب خودت را حدس بزن؛ بعد خروجی را مثل مدرک بخوان، نه مثل عددی که فقط باید با پاسخ ما یکی باشد.

Eighteen exercises, from reading one object to tracing several layers. Try each first; later exercises combine multiple clues.

تمرین‌های حل‌شدهSolved۰ / ۱۸
تمرین ۲٫۱Exercise 2.1مبتدیBeginner۳ دقیقه3 min

printf 'hello\n' | git hash-object --stdin چه می‌کند؟ آیا شیء را ذخیره هم می‌کند؟

What does printf 'hello\n' | git hash-object --stdin do? Does it store the object?

معیار موفقیت: محاسبهٔ شناسه را از نوشتن جدا کنی.

Success: distinguish computing an ID from writing it.

راهنماییHint

آیا -w هست؟

Is -w present?

پاسخ و دلیلSolution

شناسه یک blob را حساب و چاپ می‌کند؛ بدون -w در پایگاه اشیا نمی‌نویسد. نمونهٔ SHA-1 با ce013625… شروع می‌شود.

It computes and prints a blob ID; without -w, it does not write to the object database. The sample SHA-1 begins ce013625….

تمرین ۲٫۲Exercise 2.2مبتدیBeginner۳ دقیقه3 min

برای ذخیرهٔ ورودی stdin چه گزینه‌ای می‌افزایی؟ بعد چطور نوع شیء را می‌خوانی؟

Which option stores stdin content? Then how do you read the object's type?

معیار موفقیت: blob ذخیره و قابل‌بررسی باشد.

Success: the blob is stored and inspectable.

راهنماییHint

گزینهٔ نوشتن و پرسیدن نوع جداست.

Writing and querying type are separate.

بیا بازش کنیمSolution
bash
oid=$(printf 'note\n' | git hash-object -w --stdin)
git cat-file -t "$oid"
blob

-w می‌نویسد و cat-file -t نوع را نشان می‌دهد.

-w writes; cat-file -t shows the type.

تمرین ۲٫۳Exercise 2.3مبتدیBeginner۴ دقیقه4 min

blob را با cat-file -p خواندی اما اسم فایل را ندیدی. چه چیزی را بررسی می‌کنی؟

You read a blob with cat-file -p but saw no filename. What do you inspect?

معیار موفقیت: نقش blob و tree را تفکیک کنی.

Success: distinguish blob and tree roles.

راهنماییHint

اسم در شیء فهرست‌کننده است.

The name is in the listing object.

راه‌حل و توضیحSolution

tree والد را با git ls-tree <tree-id> بخوان. blob محتوا را دارد؛ tree نام، مجوز، نوع و شناسه را وصل می‌کند.

Inspect the parent tree with git ls-tree <tree-id>. The blob has content; the tree connects name, mode, type, and ID.

تمرین ۲٫۴Exercise 2.4مبتدیBeginner۴ دقیقه4 min

تفسیر کن: 040000 tree abcd… notes.

Interpret: 040000 tree abcd… notes.

معیار موفقیت: مجوز، نوع، شناسه و نام را بخوانی.

Success: read mode, type, ID, and name.

راهنماییHint

نوع tree است.

The type is tree.

چرا این جواب درست استSolution

این مدخل زیرپوشه‌ای به نام notes با مجوز 040000 و شناسه کوتاه‌شدهٔ tree را نشان می‌دهد. با git ls-tree HEAD:notes داخلش را بخوان.

This entry names a notes subdirectory with mode 040000 and the abbreviated tree ID. Inspect it with git ls-tree HEAD:notes.

تمرین ۲٫۵Exercise 2.5مبتدیBeginner۴ دقیقه4 min

یک مدخل 100755 blob می‌بینی. چه چیزی می‌فهمی و چه چیزی را نه؟

A tree entry says 100755 blob. What can and cannot you infer?

معیار موفقیت: مجوز را بیش‌ازحد تفسیر نکنی.

Success: avoid overinterpreting the mode.

راهنماییHint

این همهٔ مجوزهای ماشین نیست.

It is not every local permission bit.

بررسی جوابSolution

Git آن را فایل اجرایی می‌شناسد و به blob وصل می‌کند. از مجوز نمی‌توان مالک، گروه یا همهٔ مجوزهای محلی را فهمید.

Git records it as an executable file pointing to a blob. The mode does not reveal owner, group, or every local permission.

تمرین ۲٫۶Exercise 2.6میانیIntermediate۵ دقیقه5 min

نام notes/today.txt را عوض می‌کنی، محتوا ثابت است. کدام شناسه می‌تواند بماند و کدام‌ها عوض می‌شوند؟

You rename notes/today.txt without changing content. Which ID may remain and which change?

معیار موفقیت: تغییر نام را از تغییر محتوا جدا کنی.

Success: separate rename from content change.

راهنماییHint

کدام شیء اسم رد را نگه می‌دارد؟

Which object stores the entry name?

پاسخ پیشنهادیSolution

blob می‌تواند شناسه قبلی را نگه دارد. tree پوشه باید نام جدید را ثبت کند؛ پس tree آن پوشه و tree ریشه عوض می‌شوند. commit تازه هم tree جدید دارد و شناسه تازه می‌گیرد.

The blob can keep its ID. The directory tree records the new name, changing it and the root tree. The new commit names the new tree and gets a new ID.

تا اینجا بیشتر از روی یک شناسه به یک شیء رسیدی. از اینجا به بعد جهت را برعکس هم می‌کنیم: از commit شروع می‌کنیم و باید راه رسیدن به فایل را پیدا کنی. اگر زنجیره را گم کردی، نوع هر شیء را دوباره بپرس.

So far you mostly started with an object ID and identified an object. From here we also walk the other direction: start at a commit and find the path to file content. If you lose the chain, ask Git for each object type again.

تمرین ۲٫۷Exercise 2.7میانیIntermediate۵ دقیقه5 min

دو مسیر آماده‌شده هستند. چطور ثابت می‌کنی قبل از commit به blob یکسان می‌رسند؟

Two paths are staged. How do you prove they point to one blob before commit?

معیار موفقیت: ناحیه‌ آماده‌سازی را بخوانی نه HEAD.

Success: inspect index, not HEAD.

راهنماییHint

ستون دوم خروجی staging را نگاه کن.

Inspect the staging output's second column.

پاسخ و دلیلSolution

با git ls-files --stage؛ ستون دوم blob شناسه است. شناسههای برابر یعنی هر دو ناحیه‌ آماده‌سازی رد به محتوای یکسان اشاره دارند، نه اینکه commit آینده حتماً همین بماند.

Use git ls-files --stage; column two is the blob ID. Equal IDs mean both index entries reference identical content, not that a future commit is guaranteed to stay that way.

تمرین ۲٫۸Exercise 2.8میانیIntermediate۵ دقیقه5 min

commit اول والد ندارد؛ بعدی یک والد دارد. اولی خراب است؟ دومی چه چیزی ثبت کرده؟

The first commit has no parent; the next has one. Is the first broken? What does the second record?

معیار موفقیت: root commit را بشناسی.

Success: recognize a root commit.

راهنماییHint

اولین commit سابقه‌ای برای اشاره ندارد.

The initial commit has no earlier history to reference.

بیا بازش کنیمSolution

اولی root commit است و نبودن والد طبیعی است. دومی پیوند تاریخچه به اولی را ثبت می‌کند؛ merge ممکن است چند والد داشته باشد.

The first is a root commit, so no parent is expected. The second links history to it; a merge may have multiple parents.

تمرین ۲٫۹Exercise 2.9میانیIntermediate۵ دقیقه5 min

نویسنده و ثبت‌کننده متفاوت‌اند. آیا commit نامعتبر است؟

Author and committer differ. Is the commit invalid?

معیار موفقیت: نقش هردو را بگویی.

Success: explain both roles.

راهنماییHint

یکی تغییر را نوشته، دیگری ثبت کرده.

One wrote the change; the other recorded it.

راه‌حل و توضیحSolution

نامعتبر نیست. نویسنده نویسندهٔ تغییر است و ثبت‌کننده آن را در این commit ثبت کرده؛ هنگام اعمال patch ممکن است متفاوت باشند.

It is valid. The author wrote the change; the committer recorded it. They can differ when applying a patch.

تمرین ۲٫۱۰Exercise 2.10میانیIntermediate۵ دقیقه5 min

دو commit پیام یکسان دارند. برای توضیح شناسه متفاوت چه چیزهایی را مقایسه می‌کنی؟

Two commits share a message. What would you compare to explain different IDs?

معیار موفقیت: پیام را تمام محتوای commit فرض نکنی.

Success: do not treat the message as the whole commit.

راهنماییHint

خروجی کامل cat-file -p را بخوان.

Read the full cat-file -p output.

چرا این جواب درست استSolution

tree، والدها، نویسنده، ثبت‌کننده و زمان‌ها هم در محتوای commit هستند؛ پیام برابر به‌تنهایی شناسه برابر نمی‌سازد.

Tree, parents, author, committer, and timestamps are also part of commit content; equal messages alone do not yield equal IDs.

تمرین ۲٫۱۱Exercise 2.11میانیIntermediate۵ دقیقه5 min

یک بایت را عوض و شناسه را دوباره حساب کن. چه چیزی را نباید از این آزمایش نتیجه بگیری؟

Change one byte and recalculate the ID. What should you not infer from the experiment?

معیار موفقیت: مشاهده را با تضمین مطلق اشتباه نگیری.

Success: distinguish observation from absolute guarantee.

راهنماییHint

hash خروجی با طول محدود دارد.

A hash has a finite output space.

بررسی جوابSolution
bash
printf 'hello\n' | git hash-object --stdin
printf 'hellO\n' | git hash-object --stdin

در این نمونه شناسهها متفاوت‌اند؛ نمی‌توان گفت برخورد hash از نظر ریاضی ناممکن است. Git در عمل به hash مقاوم به برخورد تکیه می‌کند؛ آزمایش فقط نشان می‌دهد تغییر محتوا معمولاً شناسه را عوض می‌کند.

These IDs differ; this does not prove collisions mathematically impossible. Git relies in practice on collision resistance; the experiment shows that changing content ordinarily changes the ID.

تمرین ۲٫۱۲Exercise 2.12میانیIntermediate۵ دقیقه5 min

مخزن SHA-256 داری اما راهنما شناسه چهل‌نویسه‌ای نشان می‌دهد. مخزن خراب است؟

Your repository uses SHA-256 but a guide shows 40-character IDs. Is the repository broken?

معیار موفقیت: قالب hash را از مدل شیء جدا کنی.

Success: separate hash format from object model.

راهنماییHint

SHA-1 پیش‌فرض است؛ SHA-256 هم پشتیبانی می‌شود.

SHA-1 is default; SHA-256 is also supported.

پاسخ پیشنهادیSolution

نه. SHA-1 معمولاً شناسه چهل‌نویسه‌ای دارد و قالب پیش‌فرض است؛ SHA-256 شناسه بلندتری دارد. مدل blob/tree/commit یکسان است. مستند فعلی می‌گوید دو نوع مخزن فعلاً با هم سازگار نیستند.

No. SHA-1 is the default and typically has 40-character IDs; SHA-256 IDs are longer. The object model is unchanged. Current docs say the two repository formats are not interoperable yet.

تمرین‌های آخر عمداً چند مفهوم را روی هم می‌گذارند: tree، tag، pack و تاریخچه. عجله نکن. هر بار فقط یک رابطه را ثابت کن و بعد برو سراغ رابطهٔ بعدی.

The last exercises deliberately combine several ideas: trees, tags, packs, and history. Do not rush. Prove one relationship at a time, then move to the next.

تمرین ۲٫۱۳Exercise 2.13میانیIntermediate۵ دقیقه5 min

فایل جدا را بعد از نگهداری پیدا نمی‌کنی، ولی cat-file -p <oid> هنوز محتوا می‌دهد. توضیح محتمل چیست؟

After maintenance the loose file is absent, but cat-file -p <oid> returns content. What is likely?

معیار موفقیت: شیء منطقی را از محل فیزیکی تفکیک کنی.

Success: separate logical object from physical location.

راهنماییHint

به packfile فکر کن.

Think packfile.

پاسخ و دلیلSolution

احتمالاً شیء بسته‌بندی‌شده شده. git count-objects -v شمار جدا و بسته‌بندی‌شده را جدا می‌کند؛ نبود مسیر جدا به‌تنهایی شاهد حذف نیست.

The object is probably packed. git count-objects -v separates loose and packed counts; no loose path alone is not evidence of deletion.

تمرین ۲٫۱۴Exercise 2.14پیشرفتهAdvanced۶ دقیقه6 min

کدام دقیق است: «Git diff ذخیره می‌کند» یا «عکس فوری را مدل می‌کند و diff با مقایسه به دست می‌آید»؟ pack چه نقشی دارد؟

Which is accurate: “Git stores diffs” or “Git models snapshots and computes diffs by comparing them”? What does a pack do?

معیار موفقیت: مدل منطقی را از بهینه‌سازی فیزیکی جدا کنی.

Success: separate logical model from physical optimization.

راهنماییHint

delta در pack را از معنای عکس فوری جدا کن.

Separate pack deltas from snapshot semantics.

بیا بازش کنیمSolution

گزارهٔ دوم دقیق است؛ Git diff را از مقایسهٔ عکس فوریها می‌سازد. pack ممکن است برای صرفه‌جویی شیء را فیزیکی به‌شکل delta ذخیره کند، اما این مدل منطقی شیءها نیست.

The second is accurate: Git computes diffs by comparing snapshots. A pack may physically store an object as a delta to save space, but that is not the logical object model.

تمرین ۲٫۱۵Exercise 2.15پیشرفتهAdvanced۶ دقیقه6 min

tag سبک و نشانه‌دار را روی یک commit ساخته‌ای. با چه شاهدی تفاوت ذخیره‌شده‌شان را نشان می‌دهی؟

You tagged one commit with lightweight and annotated tags. What evidence shows their storage difference?

معیار موفقیت: اشاره‌گر و نوع شیء را ببینی.

Success: inspect refs and object type.

راهنماییHint

show-ref و cat-file -t را جفت کن.

Pair show-ref and cat-file -t.

راه‌حل و توضیحSolution

git show-ref --tags شناسه ثبت‌شده در اشاره‌گرها را می‌دهد. نوع هدف سبک معمولاً commit است؛ شناسه اشاره‌گر نشانه‌دار را به cat-file -t بده تا tag ببینی. بعد -p فراداده‌اش را می‌خواند.

git show-ref --tags prints IDs stored in refs. A lightweight target is usually a commit; pass the annotated ref's ID to cat-file -t to see tag, then -p to read its metadata.

تمرین ۲٫۱۶Exercise 2.16پیشرفتهAdvanced۷ دقیقه7 min

از commit شناسه تا محتوای notes/today.txt مسیر دستوری بنویس، بدون خواندن پوشه‌ کاری.

Write the command path from a commit ID to notes/today.txt, without reading the working tree.

معیار موفقیت: commit ← tree ریشه ← tree تو‌در‌تو ← blob.

Success: commit → root tree → nested tree → blob.

راهنماییHint

خط tree در commit را دنبال کن.

Follow the commit's tree line.

چرا این جواب درست استSolution
bash
git cat-file -p <commit-id>
git ls-tree <commit-id>
git ls-tree <commit-id>:notes
git cat-file -p <blob-id>

هر خروجی شناسه مرحلهٔ بعد را می‌دهد. مسیر از ردهای tree ساخته می‌شود؛ blob محتوای عکس فوری را می‌دهد، نه لزوماً فایل فعلی دیسک.

Each output gives the next ID. Tree entries compose the path; the blob returns snapshot content, not necessarily the current disk file.

تمرین ۲٫۱۷Exercise 2.17پیشرفتهAdvanced۷ دقیقه7 min

git cat-file -e deadbeef خطا می‌دهد. آیا داده برای همیشه از بین رفته؟ این شاهد چه می‌گوید؟

git cat-file -e deadbeef errors. Is the data gone forever? What does this prove?

معیار موفقیت: نبودن فعلی را با هرگز ذخیره‌نشدن یکی نگیری.

Success: do not equate current absence with never stored.

راهنماییHint

فقط مخزنهای در دسترس این مخزن بررسی می‌شوند.

Only stores available to this repository are checked.

بررسی جوابSolution

نمی‌توانی بگویی برای همیشه از دست رفته یا حتماً بازیافتنی است. شناسه ممکن است ناقص باشد، شیء جای دیگری باشد یا ذخیره نشده باشد. فقط در پایگاه اشیای فعلی با این ورودی پیدا نشد؛ بدون نسخهٔ ذخیره‌شده قول بازیابی نده.

You cannot say it is gone forever or certainly recoverable. The ID may be incomplete, the object may be elsewhere, or it may never have been stored. It was not found in current stores with this input; do not promise recovery without a stored copy.

تمرین ۲٫۱۸Exercise 2.18پیشرفتهAdvanced۸ دقیقه8 min

عکس فوری تازه فقط notes/today.txt را عوض می‌کند. blob آن Y، tree پوشه B، root A؛ README به blob X می‌رسد. کدام شناسهها عوض می‌شوند و کدام می‌تواند بماند؟

A new snapshot changes only notes/today.txt. Its blob is Y, directory tree B, root A; README points to blob X. Which IDs change and which may remain?

معیار موفقیت: اثر تغییر را تا commit دنبال کنی.

Success: trace the change through the commit.

راهنماییHint

هر شیئی که محتوا یا اشاره‌اش عوض شود شناسه تازه می‌گیرد.

An object changes ID when its content or a reference changes.

پاسخ پیشنهادیSolution

Y عوض می‌شود چون بایت‌ها تغییر کرده‌اند؛ B به Y و A به B اشاره می‌کنند، پس هر دو عوض‌اند. X می‌تواند بماند چون README ثابت است. commit تازه tree و والد را ثبت می‌کند و شناسه تازه می‌گیرد.

Y changes because its bytes changed; B points to Y and A points to B, so both change. X can stay because README is unchanged. The new commit records a new tree and parent, so it gets a new ID.

پروژهٔ کوچک: کارآگاه شیءهاMini project: object detective

مخزن آزمایشی · حدود ۲۰ دقیقهDisposable repository · about 20 minutes

از HEAD تا یک خط متن

From HEAD to one line of text

حالا همهٔ قطعه‌ها را به هم وصل می‌کنیم. از HEAD شروع می‌کنی و بدون اینکه مستقیم فایل مقصد را باز کنی، از commit به tree و از tree به blob می‌رسی. اگر وسط راه گم شدی، برگرد و فقط یک سؤال بپرس: «این شناسه به چه نوع شیئی اشاره می‌کند و قدم بعدی از کجا پیدا می‌شود؟»

A teammate says the current report is under reports. Do not open the file; start from HEAD and prove what text the committed snapshot contains.

ساخت وضعیت

Create the state

در پوشهٔ جدا انجام بده؛ فایل آخر عمداً با عکس فوری فرق خواهد داشت.

Use a separate folder; the final working-tree file intentionally differs from the snapshot.

bash · disposable project
mkdir object-detective
cd object-detective
git init -q
git config user.name 'Ada Example'
git config user.email 'ada@example.test'
mkdir -p reports/assets
printf '# Project\n' > README.md
printf 'The launch moved to Thursday.\n' > reports/brief.txt
printf 'logo-v1\n' > reports/assets/mark.txt
git add README.md reports
git commit -m 'Add project report'
printf 'The launch moved to Friday.\n' > reports/brief.txt
git add reports/brief.txt
git commit -m 'Update launch date'
printf 'Uncommitted note\n' > reports/brief.txt

ماموریت و مدرک قبولی

Mission and acceptance evidence

  1. HEAD را شناسایی کنIdentify HEAD

    git rev-parse HEAD و git cat-file -t HEAD را اجرا کن؛ شناسه و نوع را ثبت کن.

    Run git rev-parse HEAD and git cat-file -t HEAD; record ID and type.

  2. فراداده را بخوانRead metadata

    git cat-file -p HEAD؛ tree، والد، نویسنده، ثبت‌کننده، زمان و پیام را از خروجی ثبت کن.

    git cat-file -p HEAD; record tree, parent, author, committer, timestamp, and message from output.

  3. مسیر تو‌در‌تو را دنبال کنFollow the nested path

    از git ls-tree HEAD به reports و سپس reports/assets برو؛ مجوز/نوع/شناسه‌ها را یادداشت کن.

    Use git ls-tree HEAD to reach reports, then inspect reports/assets; note modes, types, and IDs.

  4. متن عکس فوری را ثابت کنProve snapshot content

    blob مربوط به brief.txt را با git cat-file -p <blob-id> بخوان: باید Thursday باشد، نه Friday. فایل پوشه‌ کاری را با cat نخوان.

    Read the brief.txt blob with git cat-file -p <blob-id>: it should say Thursday, not Friday. Do not read the working-tree file with cat.

  5. blob مشترک را پیدا کنFind a reused blob

    با git log --oneline دو commit را پیدا کن؛ نشان بده README در هر دو tree یک blob شناسه دارد.

    Find both commits with git log --oneline; show README has one blob ID in both trees.

معیار قبولی: یادداشتی از شناسهها و مسیر شیءها، متن Thursday، و توضیح اینکه چرا Friday در پوشه‌ کاری است نه HEAD.

Acceptance: a note of IDs and object path, the Thursday content, and why Friday is in the working tree rather than HEAD.

راهنمایی پلکانیProgressive hints

از cat-file -p HEAD شروع کن. خط tree شناسهٔ ریشه است. هر رد در ls-tree شناسه مرحلهٔ بعد و نوع شیء را می‌دهد؛ در پایان باید به blob برسی.

Start with cat-file -p HEAD. Its tree line gives the root ID. Each ls-tree entry supplies the next ID and type; finish at a blob.

مسیر بررسی نمونهReference investigation
bash · Git objects only
git rev-parse HEAD
git cat-file -t HEAD
git cat-file -p HEAD
git ls-tree HEAD
git ls-tree HEAD:reports
git ls-tree HEAD:reports/assets
git cat-file -p <brief-blob-id>
git log --oneline
git ls-tree <older-commit>
git ls-tree <newer-commit>

شناسهها را از مخزن خودت بگیر. HEAD commit است؛ treeهای پی‌درپی به blob می‌رسند؛ محتوا The launch moved to Thursday. است. Friday فقط در پوشه‌ کاری است. README در هر دو عکس فوری می‌تواند همان blob شناسه را داشته باشد.

Use IDs from your repository. HEAD is a commit; successive trees lead to a blob containing The launch moved to Thursday. Friday exists only in the working tree. README can retain the same blob ID across snapshots.

این مدرک محتوای commitشده را ثابت می‌کند، نه وضعیت فعلی دیسک؛ همین تفاوت هدف پروژه بود.

This proves committed content, not current disk state—that distinction was the project's point.

نقشهٔ سریع شیءهاQuick object map

سؤالQuestionاز این شروع کنStart withیادت باشدRemember
نوع شیء چیست؟What is the type?git cat-file -t <oid>از ظاهر شناسه حدس نزن.Do not infer from ID shape.
محتوایش چیست؟What is its content?git cat-file -p <oid>خروجی به نوع بستگی دارد.Output depends on type.
tree چه نام‌هایی دارد؟Which names are in a tree?git ls-tree <tree-ish>نام در blob نیست.Names are not in blobs.
شیء جدا است یا بسته‌بندی‌شده؟Loose or packed?git count-objects -vHمسیر جدا تنها شکل ذخیره نیست.Loose path is not the only storage form.
بعد از این فصل باید بتوانیBy the end, you can
  • بگویی blob چه چیزی را نگه می‌دارد و tree چه چیزی به آن اضافه می‌کند.
  • Explain what a blob stores and what a tree adds.
  • از HEAD به commit، treeهای تو‌در‌تو و محتوای blob برسی.
  • Traverse from HEAD through commits and nested trees to blob content.
  • tag سبک را از نشانه‌دار و شیء منطقی را از pack تشخیص بدهی.
  • Distinguish lightweight from annotated tags and logical objects from packs.

این بخش برای مرور سریع است، نه جایگزین داستان فصل. اگر چیزی یادت رفت، به جای حفظ دستورها فقط زنجیره را به خاطر بیاور: محتوا در blob، اسم و مسیر در tree، و تاریخچه و والدها در commit.

Now we know what Git stores. Chapter 03 asks the next question: before a commit exists, where exactly are our changes?