مدل داده:
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?
از چند فایل تا یک 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.
نمودار ۱ — 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:
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.
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.
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:
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.
نمودار ۲ — نام پوشه و نام فایل در دو سطح کنار هم مسیر کامل را میسازند؛ پیکانها اشارهٔ 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 blob | symlink؛ blob متن مقصد را نگه میداردSymlink; blob stores its target text |
040000 tree | زیرپوشه؛ اشاره به tree دیگرDirectory; points to another tree |
160000 commit | gitlink به 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.
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.
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:
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.
نمودار ۳ — 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 |
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.
نمودار ۴ — 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.
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 name | git cat-file -e <oid>؛ ID را بازبینی کن | اینکه شیء هیچوقت ذخیره نشدهThat it was never stored |
| پیشوند کوتاه چند نتیجه داردShort prefix has several matches | git rev-parse --disambiguate=<prefix> | اینکه پیشوند یکتاستThat the prefix is unique |
| نام فایل در blob نیستFilename absent from blob | tree را با git ls-tree بخوانInspect tree with git ls-tree | اینکه blob با مسیر فعلی ساخته شدهThat blob was created at this path |
| فایل جدا در مسیر نیستLoose file absent | git cat-file -t <oid>؛ git count-objects -v | اینکه شیء حذف شدهThat the object was deleted |
| دو commit پیام یکسان دارندTwo commits share a message | git 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 و نام و مجوز در tree است.
No; content is in the blob, name and mode in the tree.
ممکن است بستهبندیشده باشد؛ با شناسه از Git بپرس.
It may be packed; ask Git by ID.
tree، والدها، هویتها و زمان هم دخیلاند.
Tree, parents, identities, and timestamps contribute too.
محتوای بیتغییر میتواند دوباره استفاده شود.
Unchanged content can be reused.
مدل منطقی عکس فوری است؛ 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.
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….
برای ذخیرهٔ ورودی 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
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.
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.
تفسیر کن: 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.
یک مدخل 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.
نام 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.
دو مسیر آمادهشده هستند. چطور ثابت میکنی قبل از 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.
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.
نویسنده و ثبتکننده متفاوتاند. آیا 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.
دو 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.
یک بایت را عوض و شناسه را دوباره حساب کن. چه چیزی را نباید از این آزمایش نتیجه بگیری؟
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
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.
مخزن 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.
فایل جدا را بعد از نگهداری پیدا نمیکنی، ولی 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.
کدام دقیق است: «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.
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.
از 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
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.
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.
عکس فوری تازه فقط 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
از 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.
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
- HEAD را شناسایی کنIdentify HEAD
git rev-parse HEADوgit cat-file -t HEADرا اجرا کن؛ شناسه و نوع را ثبت کن.Run
git rev-parse HEADandgit cat-file -t HEAD; record ID and type. - فراداده را بخوانRead metadata
git cat-file -p HEAD؛ tree، والد، نویسنده، ثبتکننده، زمان و پیام را از خروجی ثبت کن.git cat-file -p HEAD; record tree, parent, author, committer, timestamp, and message from output. - مسیر تودرتو را دنبال کنFollow the nested path
از
git ls-tree HEADبهreportsو سپسreports/assetsبرو؛ مجوز/نوع/شناسهها را یادداشت کن.Use
git ls-tree HEADto reachreports, then inspectreports/assets; note modes, types, and IDs. - متن عکس فوری را ثابت کنProve snapshot content
blob مربوط به
brief.txtرا باgit cat-file -p <blob-id>بخوان: باید Thursday باشد، نه Friday. فایل پوشه کاری را باcatنخوان.Read the
brief.txtblob withgit cat-file -p <blob-id>: it should say Thursday, not Friday. Do not read the working-tree file withcat. - 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
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. |
- بگویی 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?