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

خواندن تاریخچه

Reading history:
log, show, diff, and blame

تاریخچه وقتی مفید است که بتوانی از آن سؤال بپرسی. این فصل دربارهٔ اسکرول‌کردن صدها commit نیست؛ از یک باگ امروز قدم‌به‌قدم به تغییری می‌رسی که احتمالاً آن را وارد کرده است.

Earlier chapters established that a commit is a snapshot and history links commits together. Now we have a broken configuration. We will not begin by guessing or blaming someone; we will query the graph for evidence and learn which change, in which version, had what effect.

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

تنظیم امروز خراب است؛ از کجا سرنخ بگیریم؟Today's configuration is broken. Where do we look?

صبح برنامه را باز می‌کنی و تنظیمات خراب است. می‌دانی دیروز کار می‌کرد، اما نمی‌دانی چه کسی، چه زمانی و همراه با چه تغییرهای دیگری این خط را عوض کرده. به‌جای گشتن تصادفی، تاریخچه را مثل یک پرونده بررسی می‌کنیم.

The service will not start, and the latest error points to timeout=0. We know the value is wrong, but several questions remain: when did it enter the file, in which commit, under what earlier filename, and what else changed with it? Does the person who last touched the line know why?

در فصل اول نگاهی کوتاه به git log و git show داشتیم؛ فصل دوم هم نشان داد commit یک عکس فوری است، نه فهرست تغییرات ذخیره‌شده. اینجا همان مدل را به ابزار تحقیق وصل می‌کنیم: اول سؤال را دقیق می‌کنیم، بعد نمایی از تاریخچه می‌گیریم، و سرنخ را تا patch دنبال می‌کنیم.

Chapter 01 introduced git log and git show; Chapter 02 showed that a commit is a snapshot, not a stored list of changes. Here we connect that model to investigation: make the question precise, get a view of history, then follow the lead down to a patch.

log خود گراف نیست؛ یک پرس‌وجو از گراف استA log is a query over the graph, not the graph itself

git log واقعیت یگانه‌ای به نام «تاریخچه» را چاپ نمی‌کند؛ commitهای قابل‌دسترسی از اشاره‌گر شروع را با فیلترهای انتخابی نشان می‌دهد. اشاره‌گر، مسیر و گزینه‌ها می‌توانند نتیجه را محدود کنند. خروجی خالی ممکن است یعنی پرس‌وجویت چیزی پیدا نکرده، نه اینکه مخزن هیچ commitی ندارد.

git log does not print one canonical object called “the history”; it shows commits reachable from a starting ref under your selected filters. Refs, paths, and options can narrow the result. Empty output may mean the query found nothing, not that the repository has no commits.

A linear commit history with HEAD, HEAD tilde one, and HEAD caret pointing to snapshots C1 · initial config · service.conftimeout=30 C2 · add retry · service.conffeature commit C3 · rename to app.confsame settings, new path C4 · set timeout=0 · app.confsuspect change C5 · README note · unrelatedHEAD → C5 · HEAD~1 / HEAD^ → C4 HEADHEAD~1 = HEAD^

نمودار ۱ — کارت‌ها عکس فوریهای پی‌درپی‌اند و فلش‌ها مسیر زمانی از commit قدیمی‌تر به فرزند تازه‌تر را نشان می‌دهند. در این تاریخچهٔ خطی، HEAD^ و HEAD~1 هر دو والد اول commit فعلی‌اند؛ در merge شمارهٔ والد مهم می‌شود.

Diagram 1 — The cards are successive snapshots; arrows show progression from an older commit to its newer child. In this linear history, HEAD^ and HEAD~1 both name the current commit's first parent; parent numbers matter at merges.

log را برای یک سؤال مشخص اجرا کنRun log to answer a specific question

برای شروع، یک نمای فشرده از commitهای اخیر می‌گیریم. پیام‌ها نامزد را نشان می‌دهند، نه جزئیات باگ را. بعد گراف و اشاره‌گرها را اضافه می‌کنیم تا بفهمیم HEAD کجاست و تاریخچه خطی است یا شاخه دارد.

Start with a compact view of recent commits. Subjects suggest a candidate; they do not reveal the bug's details. Then add graph shape and refs to see where HEAD is and whether history is linear or branched.

bash · first map of the repository
git log --oneline -5
9bd41a2 (HEAD -> main) Document restart procedure
41fe88c Set timeout to zero
b70c215 Rename service config
0ca6e10 Add retry setting
178a4d0 Add initial service config
git log --oneline --graph --decorate --all

شناسه‌ها و نام شاخه نمونه‌اند و در مخزن تو فرق می‌کنند. commit مشکوک دوم است؛ HEAD روی تغییر README ایستاده. --graph اتصال commitها را با خط و شاخه نشان می‌دهد، --decorate نام اشاره‌گرها را کنار commit می‌آورد، و --all اشاره‌گرهای محلی را هم برای این پرس‌وجو در نظر می‌گیرد. این گزینه‌ها فقط نمایی خواناتر می‌سازند؛ محتوای patch هنوز معلوم نیست.

IDs are illustrative and will differ in your repository. The suspect is second; HEAD sits on the README change. --graph draws parent connections, --decorate adds ref names beside commits, and --all includes local refs in the query. These options improve the view; the patch is still unknown.

برای دیدن فقط فایل‌های درگیر، --name-status را می‌گیریم؛ برای اندازهٔ تقریبی تغییر از --stat استفاده می‌کنیم. این دو پاسخ متفاوت دارند: یکی مسیر و نوع تغییر را می‌گوید و دیگری شمار تقریبی خط‌ها را.

Use --name-status to list paths involved, or --stat for approximate change size. They answer different questions: one gives paths and change types; the other summarizes line counts.

bash · inspect change shape
git log --stat -3
git log --name-status -4
A\tREADME.md
M\tapp.conf
R100\tservice.conf\tapp.conf

در این خروجی نمونه، R100 یعنی Git دو مسیر را با شباهت کامل جفت کرده و تغییرنام را در همین نما گزارش می‌کند. تغییرنام یک شیء یا عمل مستقل ذخیره‌شده در commit نیست؛ تشخیص آن از مقایسهٔ عکس فوریها می‌آید و ممکن است با تغییر محتوا فرق کند.

In this representative output, R100 means Git paired two paths with full similarity and reports a rename in this view. A rename is not a distinct object or stored operation in the commit; it is inferred by comparing snapshots and can vary with content.

سؤالQuestionنمای مناسبUseful viewچه چیزی هنوز معلوم نیست؟What remains unknown?
گراف اخیر چه شکلی است؟What does recent history look like?git log --oneline --graph --decorate --allpatch دقیق هر commitThe exact patch for each commit
کدام فایل‌ها تغییر کردند؟Which files changed?git log --name-statusخط‌های دقیق تغییرکردهThe exact changed lines
تغییر تقریباً چقدر است؟How large is the change?git log --statاثر رفتاری و دلیل تغییرBehavioral effect and reason
A debugging question narrows through graph, path, commit, diff, and verification Question: when did timeout become 0?start with the symptom 1 · map commits: log --graph 2 · narrow to one file and its history 3 · inspect commit and exact diff 4 · verify the cause; state what is uncertain

نمودار ۲ — هر گام دامنهٔ جست‌وجو را کوچک‌تر می‌کند؛ پیکان‌ها ترتیب بررسی‌اند، نه تضمین اینکه commit پیدا‌شده حتماً علت باگ است.

Diagram 2 — Each step narrows the search. Arrows show investigation order, not a guarantee that the found commit caused the bug.

نسخهٔ تاریخچهها را دقیق نام ببرName revisions precisely

برای بازکردن commit مشکوک لازم نیست شناسهٔ کامل را حفظ کنی. HEAD جایی است که اشاره‌گر جاری می‌رسد؛ در تاریخچهٔ خطی، HEAD~1 یک گام روی زنجیرهٔ والد اول به عقب و HEAD~2 دو گام است. HEAD^ والد اول مستقیم است، پس در commit تک‌والدی با HEAD~1 یکی است.

You need not memorize a full ID to inspect the suspect. HEAD is where the current ref points; in linear history, HEAD~1 is one first-parent step back and HEAD~2 is two. HEAD^ is the direct first parent, so on a single-parent commit it equals HEAD~1.

در merge، ^ والد اول و ^2 والد دوم را می‌خواهد. شناسهٔ کوتاه هم فقط تا وقتی کافی است که در همین مخزن مبهم نباشد؛ اگر Git گفت ambiguous، چند نویسهٔ بیشتر بگیر. برای commit آخر در برابر والدش می‌توانی git diff HEAD^ HEAD بنویسی.

At a merge, ^ names the first parent and ^2 the second. An abbreviated ID works only while it is unambiguous in this repository; use more characters if Git reports ambiguity. To compare the latest commit with its parent, write git diff HEAD^ HEAD.

bash · choose useful fields and scope
git log --format='%h %ad %an <%ae> %s' --date=short -4
9bd41a2 2025-04-12 Mina Example <mina@example.test> Document restart procedure
41fe88c 2025-04-11 Arman Example <arman@example.test> Set timeout to zero
git show --format=fuller --stat HEAD~1
git show --format= --word-diff=plain HEAD~1 -- app.conf

قالب سفارشی ردیف را می‌چیند: %h شناسهٔ کوتاه، %ad تاریخ نویسنده، %an نام نویسنده و %s عنوان پیام. در خروجی fuller نویسنده کسی است که تغییر را نوشته و ثبت‌کننده کسی که commit را ثبت کرده؛ این دو می‌توانند متفاوت باشند، مثلاً وقتی دیگری patch را اعمال می‌کند.

A custom format arranges fields: %h abbreviated ID, %ad author date, %an author name, and %s subject. In fuller output, the author wrote the change and the committer recorded the commit; they can differ, for example when someone applies another person's patch.

git show یک commit را با فراداده و patch آن می‌خواند؛ در فرمان آخر، --format= سرآیند را حذف کرده‌ایم تا تغییر واژه‌ای فایل را ببینیم. --word-diff یا --unified=0 برای یک مقدار کوچک خواناتر است، اما زمینه را کم می‌کند. پیام commit ادعای نویسنده است؛ patch محتوای مقایسه‌شده را نشان می‌دهد.

git show reads a commit with its metadata and patch; the last command uses --format= to omit the header and focus on changed words. --word-diff or --unified=0 can help with a small value, but reduce context. A commit message is the author's claim; the patch shows compared content.

فرمانCommandمقایسه/نمایشComparison or viewمرز نتیجه‌گیریLimit
git show Cفراداده، پیام و patch commitCommit metadata, message, and patchرفتار برنامه را به‌تنهایی ثابت نمی‌کند.Does not prove runtime behavior alone.
git diff A B -- FILEعکس فوری A به عکس فوری B، برای یک فایلSnapshot A to B for one fileترتیب A و B جهت patch را تعیین می‌کند.The order of A and B sets patch direction.
git show --stat Cفهرست فایل‌ها و شمار تقریبی تغییرFiles and approximate change countsمتن تغییرها را نمایش نمی‌دهد.Does not show changed text.
A diff compares two named snapshots and computes an old-to-new patch without editing them Snapshot Atimeout = 30HEAD~2 · before Snapshot Btimeout = 0HEAD~1 · after compare A → B computed diff-30 → +0 git diff A B -- app.conf · edits neither snapshot

نمودار ۳ — A قدیمی و B جدید است؛ خط حذف‌شده از A و خط افزوده‌شده در B می‌آید. جابه‌جاکردن دو ورودی، جهت patch را برعکس می‌کند.

Diagram 3 — A is older and B is newer; removed lines come from A and added lines appear in B. Swapping the inputs reverses patch direction.

وقتی فایل تغییرنام شده، تاریخش را تا کجا می‌بینی؟How far does a renamed file's history go?

حالا می‌دانیم مقدار خراب بعد از تغییرنام وارد app.conf شده. git log -- app.conf تاریخچه را به همین مسیر محدود می‌کند. برای دنبال‌کردن یک تغییرنام ساده، --follow را امتحان می‌کنیم؛ این گزینه محدودیت دارد و جای بررسی گراف یا mergeها را نمی‌گیرد.

We know the bad value entered app.conf after a rename. git log -- app.conf limits history to that path. Try --follow to trace a simple rename; it has limits and does not replace understanding the graph or merges.

bash · compare path queries
git log --oneline -- app.conf
41fe88c Set timeout to zero
b70c215 Rename service config
git log --oneline --follow -- app.conf
41fe88c Set timeout to zero
b70c215 Rename service config
0ca6e10 Add retry setting
178a4d0 Add initial service config

خروجی به تاریخچه و نسخهٔ Git بستگی دارد؛ در این آزمایش --follow مسیر را قبل از نام قبلی هم پیدا می‌کند. این گزینه فقط برای یک فایل است و تغییرنام را از شباهت محتوا حدس می‌زند. تغییرهای بزرگ هم‌زمان با تغییرنام یا تاریخچهٔ شاخه‌دار می‌توانند ردگیری را پیچیده کنند.

Output depends on history and Git version; in this lab, --follow reaches commits before the old name. It applies to one file and infers a rename from content similarity. Large edits alongside the rename or branched history can complicate tracing.

اگر خروجی خالی شد، اول نام مسیر را در عکس فوری انتخاب‌شده بررسی کن: git ls-tree -r --name-only HEAD. بعد نام قدیمی و فیلترها را امتحان کن. نتیجهٔ خالی به‌تنهایی نمی‌گوید فایل چرا حذف شده یا اصلاً در تاریخچهٔ قابل‌دسترست بوده است.

If output is empty, first check paths in the selected snapshot with git ls-tree -r --name-only HEAD. Then try the old name and review filters. Empty output alone does not explain why a file was deleted or whether it exists in reachable history.

تغییرنام رابطه‌ای است که Git حدس می‌زندRename is a relationship Git infers

در عکس فوری، نام قبلی حذف و نام تازه اضافه شده؛ Git هنگام نمایش از شباهت محتوا تغییرنام را تشخیص می‌دهد. بنابراین --follow ابزار مفیدی است، نه سندی برای یک عملیات تغییرنام جداگانه که جایی ذخیره شده باشد.

In snapshots, the old name is removed and the new one added; Git infers a rename from content similarity when displaying history. So --follow is useful, but not evidence of a separately stored rename operation.

وقتی خروجی با انتظارمان نمی‌خواندWhen output does not match expectation

وقتی تاریخچه جواب مورد انتظار را نمی‌دهد، معمولاً مشکل از «Git خراب شده» نیست؛ سؤال ما دقیق نبوده. شاید بازه اشتباه است، شاید مسیر فایل بعد از تغییرنام عوض شده، یا شاید blame فقط آخرین ویرایش را نشان می‌دهد. قبل از عوض‌کردن دستور، خود سؤال را دوباره روشن کن.

In a history investigation, an empty command is rarely the end; it is a signal to revisit the question or scope. Change one assumption at a time to learn which one was wrong.

log خالی شد

Log is empty

مسیر، شاخه یا نسخهٔ تاریخچه را بررسی کن و مطمئن شو مسیر بعد از -- آمده. بعد git log --oneline را بدون مسیر بزن. نتیجهٔ خالی فقط می‌گوید این پرس‌وجو چیزی برنگرداند.

Check the path, branch, or revision, and ensure the path follows --. Then try git log --oneline without a path. Empty output means only that this query returned nothing.

شناسهٔ کوتاه ambiguous است

Short ID is ambiguous

در همان مخزن چند شیء پیشوند یکسان دارند. چند نویسهٔ بیشتر از log بردار و commit را با git rev-parse --verify 'ID^{commit}' بررسی کن.

Multiple objects in this repository share the prefix. Use more characters from log and verify the commit with git rev-parse --verify 'ID^{commit}'.

blame به commit قالب‌بندی اشاره می‌کند

Blame points to formatting

patch را بخوان؛ در صورت نیاز git blame -w FILE و -M/-C را امتحان کن. بعد تاریخچه را با log محدود به مسیر و --follow دنبال کن؛ اگر هنوز مبهم است، همان عدم‌قطعیت را بنویس.

Read the patch; if useful, try git blame -w FILE and -M/-C. Then trace path history with log and --follow; if uncertainty remains, record it.

diff برعکس انتظار است

Diff is reversed

در git diff A B حذف‌ها از A و افزوده‌ها در B هستند. هر دو سمت را با git show A:FILE و git show B:FILE نمونه‌برداری کن تا جهت را تأیید کنی.

In git diff A B, removals come from A and additions appear in B. Sample both sides with git show A:FILE and git show B:FILE to confirm direction.

تغییرنام یا فایل حذف‌شده گیجت کرده

Rename or deleted path is confusing

مسیرهای عکس فوری انتخاب‌شده را فهرست کن، بعد نام قدیم و جدید را جدا بررسی کن. --follow فقط برای یک فایل کار می‌کند و تشخیص تغییرنام از شباهت می‌آید؛ با تغییر بزرگ ممکن است از دست برود.

List paths in the selected snapshot, then inspect old and new names separately. --follow works for one file and relies on similarity-based rename detection; a large rewrite can defeat it.

تمرین‌ها: از خواندن پیام تا ساختن پروندهExercises: from reading a message to building a case

این تمرین‌ها مثل پروندهٔ بررسی‌اند. به جای اینکه یک دستور را از حفظ بزنی، از خودت بپرس دنبال چه چیزی هستی: یک commit خاص؟ زمان ورود یک عبارت؟ تفاوت دو عکس فوری؟ یا آخرین کسی که این خط را عوض کرده؟ بعد ابزار مناسب را انتخاب کن.

Predict from the evidence before opening a solution. No history-rewriting command is needed; use the project's disposable repository for experiments.

تمرین ۴٫۱Exercise 4.1پایهCore۳ دقیقه3 min

خروجی می‌گوید 7ac91f0 (HEAD -> main) Fix retry text. چه می‌دانی و چه را هنوز نمی‌دانی؟

The output says 7ac91f0 (HEAD -> main) Fix retry text. What do you know, and what remains unknown?

راهنماییHint

پیام کوتاه، patch نیست.

A subject is not a patch.

پاسخ و دلیلSolution

HEAD و اشاره‌گر شاخهٔ main به آن commit می‌رسند؛ فقط شناسهٔ کوتاه و عنوان را داری. فایل و متن تغییر معلوم نیست؛ git show 7ac91f0 قدم بعدی است.

HEAD and the main ref reach that commit; you only have its short ID and subject. Changed files and content are unknown; inspect with git show 7ac91f0.

تمرین ۴٫۲Exercise 4.2پایهCore۳ دقیقه3 min

سه commit آخر: می‌خواهی فقط مسیر و نوع تغییر را ببینی، نه patch. کدام گزینه؟

For the last three commits, you need only paths and change types, not patches. Which option?

راهنماییHint

نام تغییر را از شمار خط‌ها جدا کن.

Separate change names from line counts.

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

git log --name-status -3. برای تعداد تقریبی خط‌های افزوده/حذف‌شده --stat مناسب‌تر است؛ هیچ‌کدام patch واقعی را جایگزین نمی‌کند.

git log --name-status -3. Use --stat for approximate additions/deletions; neither option replaces the actual patch.

تمرین ۴٫۳Exercise 4.3پایهCore۳ دقیقه3 min

در تاریخچهٔ خطی، HEAD~2 کجاست و با HEAD^ چه فرقی دارد؟

In linear history, where is HEAD~2, and how does it differ from HEAD^?

راهنماییHint

دو گام در برابر یک والد مستقیم.

Two steps versus one direct parent.

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

HEAD~2 دو گام روی والد اول می‌رود؛ HEAD^ والد اول مستقیم است و روی commit تک‌والدی برابر HEAD~1 می‌شود. در merge، ^2 والد دوم است.

HEAD~2 takes two first-parent steps; HEAD^ is the direct first parent and equals HEAD~1 on a single-parent commit. At a merge, ^2 is the second parent.

تمرین ۴٫۴Exercise 4.4پایهCore۴ دقیقه4 min

git diff A B -- app.conf خط timeout=30 را حذف و timeout=0 را اضافه می‌کند. کدام عکس فوری قدیمی است؟

git diff A B -- app.conf removes timeout=30 and adds timeout=0. Which snapshot is older?

راهنماییHint

منفی از سمت اول می‌آید.

The removed line comes from the first side.

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

A مبدأ قدیمی و B مقصد است. اگر جایشان را عوض کنی، روایت patch معکوس می‌شود. برای commit آخر در برابر والدش، git diff HEAD^ HEAD -- app.conf را بزن.

A is the older source and B the destination. Swapping them reverses the patch. Compare the latest commit with its parent using git diff HEAD^ HEAD -- app.conf.

تمرین ۴٫۵Exercise 4.5کاربردیApplied۴ دقیقه4 min

چرا log محدود به app.conf ممکن است commitهای قدیمی‌تر از تغییرنام را جا بیندازد؟

Why might a log query limited to app.conf omit commits from before the rename?

راهنماییHint

مسیر قبلی را به یاد بیاور.

Consider the earlier path.

بررسی جوابSolution

pathspec ساده تاریخ را به نام فعلی محدود می‌کند. برای یک فایل git log --follow -- app.conf را امتحان کن؛ تغییرنام از شباهت حدس زده می‌شود و تضمین کامل برای همهٔ تاریخچه‌ها نیست.

A plain pathspec limits history to the current name. Try git log --follow -- app.conf for one file; rename is inferred from similarity, not guaranteed for every history shape.

تمرین ۴٫۶Exercise 4.6کاربردیApplied۴ دقیقه4 min

کدام فرمان نام و تاریخ نویسنده و ثبت‌کننده را برای یک commit کنار هم نشان می‌دهد؟

Which command shows author and committer names and dates together for one commit?

راهنماییHint

نمای کامل‌تر show را به یاد بیاور.

Think of show's fuller format.

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

git show --format=fuller COMMIT. نویسنده تغییر را نوشته و ثبت‌کننده آن را ثبت کرده؛ این‌ها ممکن است دو نفر باشند. blame معمولاً نویسنده را نشان می‌دهد.

git show --format=fuller COMMIT. The author wrote the change and the committer recorded it; they can be different people. Blame usually shows the author.

تا اینجا تاریخچه را خواندی. از اینجا تمرین‌ها ازت می‌خواهند سؤال مبهم را به سؤال دقیق تبدیل کنی: «چه کسی خراب کرد؟» کافی نیست؛ باید بگویی دنبال کدام تغییر، کدام فایل و کدام بازه‌ای.

So far you have read history. From here the exercises ask you to turn a vague question into a precise one: “who broke it?” is not enough; name the change, path, and time range you are investigating.

تمرین ۴٫۷Exercise 4.7کاربردیApplied۴ دقیقه4 min

blame خط خراب را به یک نفر نسبت می‌دهد. چرا هنوز نباید بگویی او عمداً باگ ساخته؟

Blame attributes the bad line to someone. Why can you not yet say they intentionally introduced the bug?

راهنماییHint

کدام بخش خروجی دربارهٔ نیت است؟

Which field tells you intent?

پاسخ و دلیلSolution

هیچ‌کدام. blame ردیابی خط و commit است؛ قالب‌بندی هم می‌تواند نسبت‌دادن را جابه‌جا کند. patch، آزمون رفتاری و زمینهٔ تصمیم را جدا بررسی کن؛ نیت از attribution درنمی‌آید.

None. Blame traces a line and commit; formatting can also shift attribution. Inspect the patch, behavioral tests, and decision context separately. Intent does not follow from attribution.

تمرین ۴٫۸Exercise 4.8کاربردیApplied۴ دقیقه4 min

عنوان commit می‌گوید «افزایش timeout»، ولی patch مقدار را از ۳۰ به صفر می‌برد. کدام شاهد اثر تغییر است و چه چیزی هنوز باید سنجیده شود؟

The subject says “raise timeout,” but the patch changes 30 to 0. Which is evidence of the actual content change, and what still needs testing?

راهنماییHint

پیام را با patch مقایسه کن.

Compare the message with the patch.

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

patch نشان می‌دهد عکس فوری واقعاً چه متنی عوض کرده؛ عنوان می‌گوید نویسنده چه توضیحی داده. برای دانستن اثر صفر بر سرویس، باید معنی تنظیم را در برنامه یا آزمون اجرا کنی.

The patch shows what text changed in the snapshot; the subject records the author's explanation. To know what zero does to the service, inspect application semantics or run a test.

تمرین ۴٫۹Exercise 4.9کاربردیApplied۵ دقیقه5 min

بعد از commit قالب‌بندی، blame بیشتر خط‌ها را به همان commit نسبت می‌دهد. دو بررسی کم‌خطر پیشنهاد کن.

After a formatting commit, blame attributes most lines to it. Suggest two low-risk checks.

راهنماییHint

یک گزینهٔ whitespace و خود patch.

A whitespace option and the patch itself.

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

git blame -w FILE را برای نادیده‌گرفتن whitespace امتحان کن و patch را با git show FORMAT-COMMIT -- FILE بخوان. اگر خط‌ها منتقل شده‌اند، -M را هم بیازمای. نتیجهٔ blame را با patch تأیید کن.

Try git blame -w FILE to ignore whitespace and inspect the patch with git show FORMAT-COMMIT -- FILE. If lines moved, try -M. Confirm blame against the patch.

تمرین ۴٫۱۰Exercise 4.10کاربردیApplied۵ دقیقه5 min

یک متن دقیق فقط جابه‌جا شده و تعداد رخدادش ثابت مانده، اما خط patch با regex تو جور است. کدام‌یک از -S و -G آن commit را می‌گیرد؟

Exact text moved and its occurrence count stayed constant, but a changed patch line matches your regex. Which of -S and -G finds the commit?

راهنماییHint

یکی count را می‌سنجد و دیگری خط patch را.

One checks counts; the other checks patch lines.

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

-G چون regex را روی خط‌های افزوده/حذف‌شده می‌سنجد. -S count رخداد رشتهٔ دقیق را مقایسه می‌کند؛ با count ثابت ممکن است آن commit را نگیرد.

-G matches because it tests regexes against added/removed lines. -S compares occurrence counts of exact text and may omit the commit when the count is unchanged.

تمرین ۴٫۱۱Exercise 4.11کاربردیApplied۴ دقیقه4 min

برای «فقط نام فایل و وضعیت تغییر» --stat مناسب‌تر است یا --name-status؟

For “just filenames and change status,” is --stat or --name-status better?

راهنماییHint

شمار خط‌ها لازم نیست.

You do not need line counts.

بررسی جوابSolution

--name-status مسیر و نوع M/A/D/R را می‌دهد. --stat اندازهٔ تقریبی تغییر را خلاصه می‌کند و سؤال «وضعیت مسیر چیست؟» را مستقیم پاسخ نمی‌دهد.

--name-status gives paths and M/A/D/R change types. --stat summarizes approximate size and is less direct for path status.

تمرین ۴٫۱۲Exercise 4.12کاربردیApplied۵ دقیقه5 min

برای خواندن تغییر commit A روی یک فایل، چه فرمانی می‌زنی؟ اگر بخواهی دو عکس فوری را صریح مقایسه کنی چه؟

How do you inspect one file's change in commit A? How do you explicitly compare two snapshots?

راهنماییHint

show یک commit؛ diff دو سر.

Show takes a commit; diff takes two endpoints.

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

git show A -- FILE patch commit را نمایش می‌دهد؛ git diff A^ A -- FILE tree والد و commit را مقایسه می‌کند. در merge رفتار show می‌تواند تابع قالب diff merge باشد؛ آن را با یک commit عادی یکی نگیر.

git show A -- FILE displays that commit's patch; git diff A^ A -- FILE compares its parent tree with the commit. For merges, show output can use a merge-diff format; do not assume it is an ordinary commit.

تمرین‌های پایانی شبیه بررسی واقعی‌اند؛ ممکن است دو دستور سرنخ‌های متفاوت بدهند. قرار نیست یکی را کورکورانه باور کنی. خروجی‌ها را کنار هم بگذار و بگو هرکدام دقیقاً چه چیزی را ثابت می‌کنند.

The final exercises are closer to real investigations: two commands may give different clues. Do not blindly trust one. Put the outputs together and state what each one actually proves.

تمرین ۴٫۱۳Exercise 4.13تحلیلیAnalysis۵ دقیقه5 min

log معمولی دو commit و --follow چهار commit برای فایل نشان می‌دهد. چه نتیجه‌ای مجاز است و چه چیزی ثابت نشده؟

Ordinary log shows two commits for a file; --follow shows four. What can you conclude, and what is not proven?

راهنماییHint

دربارهٔ دامنهٔ پرس‌وجو فکر کن، نه تعداد شیءها.

Think query scope, not object count.

پاسخ و دلیلSolution

--follow مسیر محتمل تغییرنام را عقب دنبال کرده است. این ثابت نمی‌کند Git عمل تغییرنام جداگانه ذخیره کرده یا هر commit علت باگ بوده؛ patchها را زیر نام قدیم و جدید بخوان.

--follow traced a likely rename path backwards. This does not prove Git stored a separate rename operation or that any commit caused the bug; inspect patches under old and new names.

تمرین ۴٫۱۴Exercise 4.14تحلیلیAnalysis۵ دقیقه5 min

یک شناسهٔ کوتاه در مخزن A حل می‌شود، اما در مخزن B مبهم است. آیا یکی خراب است؟

A short ID resolves in repository A but is ambiguous in repository B. Is either repository broken?

راهنماییHint

پیشوند در مجموعهٔ شیءهای همان مخزن معنی دارد.

A prefix is resolved against that repository's objects.

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

نه؛ B چند شیء با پیشوند مشترک دارد و A احتمالاً فقط یکی. شناسهٔ بلندتر بگیر و مقصد commit را با git rev-parse --verify 'ID^{commit}' تأیید کن.

No. B has multiple objects with that prefix while A may have one. Use a longer ID and verify it resolves to a commit with git rev-parse --verify 'ID^{commit}'.

تمرین ۴٫۱۵Exercise 4.15تحلیلیAnalysis۶ دقیقه6 min

می‌خواهی بفهمی متن دقیق retry=false چه وقت وارد فایل شد؛ فایل تغییرنام شده. یک پرس‌وجو بنویس و یک محدودیتش را توضیح بده.

You want to learn when exact text retry=false entered a renamed file. Write a query and explain one limitation.

راهنماییHint

-S را با --follow و یک مسیر ترکیب کن.

Combine -S with --follow and one path.

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

git log --oneline --follow -S'retry=false' -- current-name. نتیجه فقط commitهای قابل‌دسترسی و مسیر منتخب را پوشش می‌دهد؛ فاصله، املایی یا تاریخچهٔ پیچیده ممکن است جواب را عوض کند.

Use git log --oneline --follow -S'retry=false' -- current-name. It covers only reachable commits and the selected path; whitespace, spelling, or complex history can change the result.

تمرین ۴٫۱۶Exercise 4.16تحلیلیAnalysis۶ دقیقه6 min

جست‌وجوی -S خالی است. سه بررسی کن تا بفهمی خطا در رشته است یا دامنهٔ query.

A -S search is empty. Suggest three checks to distinguish a bad string from a narrow query.

راهنماییHint

متن، نام مسیر و اشاره‌گرهای شروع را وارسی کن.

Check text, path name, and starting refs.

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

رشته را از patch کپی کن؛ نام فعلی و قدیم فایل را آزمایش کن؛ فیلتر مسیر را بردار یا اشاره‌گرهای مناسب را با --all بگنجان. باز هم فقط می‌توانی بگویی در محدودهٔ قابل‌دسترسی یافت نشد؛ اگر چیزی commit نشده باشد، Git نسخه‌ای از آن ندارد.

Copy the string from a patch; try current and old filenames; remove the path filter or include suitable refs with --all. Even then, say only that it was not found in reachable scope. Git has no version of work that was never committed.

تمرین ۴٫۱۷Exercise 4.17پروندهCase analysis۷ دقیقه7 min

تغییر نامرتبط README در HEAD است و باگ در والد آن. برای مقایسهٔ فقط تنظیمات در commit باگ و والدش چه می‌نویسی؟ چرا git diff تنها کافی نیست؟

An unrelated README change is at HEAD and the bug is in its parent. How do you compare only config at the bug commit and its parent? Why is plain git diff insufficient?

راهنماییHint

هر دو سر مقایسه را نام ببر.

Name both comparison endpoints.

بررسی جوابSolution

در تاریخچهٔ خطی: git diff HEAD~2 HEAD~1 -- app.conf. git diff بدون نسخهٔ تاریخچه پوشه‌ کاری را با ناحیه‌ آماده‌سازی می‌سنجد، نه دو commit دلخواه.

In this linear history: git diff HEAD~2 HEAD~1 -- app.conf. Without revisions, git diff compares the working tree with the index, not arbitrary commits.

تمرین ۴٫۱۸Exercise 4.18پروندهٔ نهاییFinal case۸ دقیقه8 min

گراف یک commit مشکوک نشان می‌دهد، show تغییر ۳۰ به صفر را ثابت می‌کند و blame همان خط را به نویسنده نسبت می‌دهد. یک نتیجهٔ مجاز، یک اقدام بعدی و یک ادعای غیرمجاز بنویس.

The graph shows a suspect commit, show proves a 30-to-0 change, and blame attributes that line to an author. Write one supported conclusion, one next step, and one unsupported claim.

راهنماییHint

محتوا، رفتار و نیت سه چیز جدا هستند.

Content, behavior, and intent are different things.

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

مجاز: این commit مقدار ثبت‌شده را عوض کرده و blame خط جاری را به آن نسبت می‌دهد. قدم بعد: معنای صفر را در کد/آزمون بسنج. غیرمجاز: نویسنده عمداً سرویس را خراب کرد یا این تنها علت رخداد بود؛ برای این ادعا شواهد دیگری لازم است.

Supported: this commit changed the recorded value, and blame attributes the current line to it. Next: test what zero means in code/tests. Unsupported: the author intentionally broke the service or this was the sole cause; those claims need other evidence.

\n

پروژهٔ کوچک: چه کسی تنظیم را خراب کرد؟Mini project: who broke the configuration?

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

یک پروندهٔ مستند بساز، نه یک مقصر

Build a documented case, not a culprit

در پروژهٔ آخر فصل یک تنظیم خراب جلویت می‌گذاریم، اما commit مقصر را لو نمی‌دهیم. کارت این است که سرنخ‌ها را کم‌کم محدود کنی و در پایان بتوانی برای یک همکار توضیح بدهی «چرا فکر می‌کنم این تغییر مشکل را وارد کرده»، نه اینکه فقط یک شناسه تحویل بدهی.

A teammate reports a service that will not start: its timeout is zero. The file began as service.conf, was renamed, gained a retry setting, and has an unrelated README change at the end. You receive only the broken final state. Write down what you know and what remains unknown.

پرونده را در پوشهٔ جدا بساز

Create the case in a separate folder

این توالی فقط مخزن تازهٔ خودش را تغییر می‌دهد. آن را در ترمینال تازه اجرا کن، نه داخل مخزن کارت. پیام‌ها نمونه‌اند؛ شناسهها در مخزن تو فرق می‌کنند.

This sequence changes only the new repository it creates. Run it in a fresh terminal, not inside your work repository. Subjects are illustrative; IDs will differ.

bash · disposable configuration case
mkdir who-broke-config && cd who-broke-config
git init -q
git config user.name 'Nika Example'
git config user.email 'nika@example.test'
printf '[http]\ntimeout=30\n' > service.conf
git add service.conf && git commit -m 'Add initial service config'

printf '[http]\ntimeout=30\nretries=2\n' > service.conf
git add service.conf && git commit -m 'Add retry setting'

git mv service.conf app.conf
git commit -m 'Rename service config'

printf '[http]\ntimeout=0\nretries=2\n' > app.conf
git add app.conf && git commit -m 'Set timeout to zero'

printf '# Operations\nRestart service after config updates.\n' > README.md
git add README.md && git commit -m 'Document restart procedure'

تحقیق را با چند شاهد کامل کن

Build the investigation from several kinds of evidence

  1. موقعیت فعلی را ثبت کنRecord the current position

    git status --short و git log --oneline --graph --decorate را اجرا کن. معیار: درخت کاری تمیز و پنج commit؛ اگر نه، اول ساخت پرونده را بررسی کن.

    Run git status --short and git log --oneline --graph --decorate. Acceptance: clean working tree and five commits; if not, revisit setup.

  2. مسیر را از تغییرنام رد کنTrace across the rename

    git log --name-status -- app.conf و git log --follow --name-status -- app.conf را مقایسه کن. در گزارش بگو چرا دومی commitهای قدیمی‌تر را هم پیدا کرد و چرا تغییرنام یک استنباط است.

    Compare git log --name-status -- app.conf with git log --follow --name-status -- app.conf. Explain why the latter reaches older commits and why rename is an inference.

  3. commit نامزد را پیدا کن و patch را بخوانFind the candidate and read its patch

    git log --oneline --follow -S'timeout=0' -- app.conf را اجرا کن؛ سپس commit برگشتی را با git show COMMIT -- app.conf و git diff COMMIT^ COMMIT -- app.conf بررسی کن. جهت diff را بنویس.

    Run git log --oneline --follow -S'timeout=0' -- app.conf; inspect the returned commit with git show COMMIT -- app.conf and git diff COMMIT^ COMMIT -- app.conf. State diff direction.

  4. نسبت blame را با فراداده مقایسه کنCompare blame attribution with metadata

    git blame app.conf و git show --format=fuller COMMIT -- app.conf را بخوان. نویسنده، ثبت‌کننده و آخرین تغییر خط را ثبت کن، اما دربارهٔ نیت حکم نده.

    Read git blame app.conf and git show --format=fuller COMMIT -- app.conf. Record author, committer, and the line's last change, but make no claim about intent.

  5. مرز نتیجه‌گیری را مستند کنDocument the boundary of your conclusion

    فرمان -G را با -S مقایسه کن. گزارشت باید commit نامزد، فرمان‌ها، چند خط diff، سازگاری یا اختلاف blame، شواهد پشتیبان و یک عدم‌قطعیت را داشته باشد.

    Compare a -G query with -S. Your report needs the candidate commit, commands, a short diff excerpt, whether blame agrees, supporting evidence, and one uncertainty.

قبولی یعنی بتوانی نشان بدهی مقدار در کدام تغییر عوض شد و چرا این هنوز به‌تنهایی علت قطعی رفتار سرویس یا نیت نویسنده را ثابت نمی‌کند. برای علیت باید معنا برنامه یا آزمونش را هم بررسی کنی.

Pass when you can show which change altered the value and explain why that alone does not prove runtime causation or author intent. Causation requires application semantics or tests too.

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

شناسه را از خروجی -S بردار، حدس نزن. اگر تاریخچه قدیمی نیست، اول مسیر و بعد --follow را بررسی کن. اگر blame انگار خلاف patch حرف می‌زند، قالب‌بندی یا جابه‌جایی خط را بسنج.

Copy the ID from -S, do not guess. If older history is missing, check the path and then --follow. If blame seems to disagree with the patch, consider formatting or line movement.

چارچوب گزارش نهاییFinal report outline

«نامزد: … . آن را با … پیدا کردم. patch بین … و … نشان می‌دهد … . blame خط را به … نسبت می‌دهد؛ fuller می‌گوید نویسنده … و ثبت‌کننده … است. این شواهد ثابت می‌کنند مقدار در این commit عوض شده، اما نیت یا علت یگانه را ثابت نمی‌کنند. برای سنجش اثر، … را در برنامه/آزمون بررسی می‌کنم.» جاهای خالی را با دادهٔ مخزن خودت پر کن.

“Candidate: … . I found it with … . The patch between … and … shows … . Blame attributes the line to …; fuller output lists author … and committer … . This proves the value changed in this commit, not intent or sole causation. To test its effect, I will inspect … in the application/tests.” Fill in evidence from your repository.

نقشهٔ سریع برای بررسی تاریخچهQuick map for a history investigation

اگر می‌پرسی…If you ask…از این شروع کنStart hereمرزش را یادت بماندRemember its limit
گراف اخیر چه شکلی است؟What does recent history look like?git log --oneline --graph --decorate --allنمایی از commitهای قابل‌دسترسی و گزینه‌های پرس‌وجو است.A view of reachable commits under query options.
چه مسیرهایی تغییر کردند؟Which paths changed?git log --name-statusمحتوای patch را نشان نمی‌دهد.Does not show patch content.
دو عکس فوری چه فرقی دارند؟How do two snapshots differ?git diff A B -- PATHA مبدأ، B مقصد؛ ترتیب مهم است.A is source, B destination; order matters.
خط از کجا آمده؟Where did this line come from?git blame -L N,N -- PATHنسبت خط را می‌دهد، نه نیت و علیت را.Attributes a line, not intent or causation.
شمار رخداد متن دقیق کِی عوض شد؟When did exact text's count change?git log -S'TEXT' -- PATHفقط در دامنهٔ اشاره‌گر و مسیر همین پرس‌وجو.Only within this query's refs and path.
بعد از این فصل باید بتوانیBy the end, you can
  • از نمای گراف به commit مشکوک برسی و تغییرش را روی مسیر محدود بخوانی.
  • Move from a graph view to a suspect commit and inspect a path-limited change.
  • دو عکس فوری را با جهت صریح مقایسه کنی و فرق log، show و diff را توضیح بدهی.
  • Compare two named snapshots in a clear direction and distinguish log, show, and diff.
  • تغییرنام را محتاطانه دنبال کنی و محدودیت --follow را بشناسی.
  • Trace a rename cautiously and explain --follow's limits.
  • blame و -S/-G را به‌جا استفاده کنی، بی‌آنکه از داده بیش‌ازحد نتیجه بگیری.
  • Use blame and -S/-G appropriately without overclaiming from the evidence.

مرور سریع خوب است، ولی این فصل با حفظ پرچم‌های log یاد گرفته نمی‌شود. سؤال درست را انتخاب کن و بعد از Git بخواه فقط همان بخش از تاریخچه را نشانت بدهد.

You can now read a graph and follow a change down to its patch. Next is not history rewriting; it is changing the path of work. Chapter 05 shows that a branch is not a copy of the project—it is a movable name pointing into this graph.