خواندن تاریخچه
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.
تنظیم امروز خراب است؛ از کجا سرنخ بگیریم؟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.
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.
نمودار ۱ — کارتها عکس فوریهای پیدرپیاند و فلشها مسیر زمانی از 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.
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.
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 --all | patch دقیق هر 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 |
نمودار ۲ — هر گام دامنهٔ جستوجو را کوچکتر میکند؛ پیکانها ترتیب بررسیاند، نه تضمین اینکه 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.
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 قدیمی و 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.
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 هنگام نمایش از شباهت محتوا تغییرنام را تشخیص میدهد. بنابراین --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.
blame میگوید خط آخرین بار کجا عوض شد، نه چراblame says where a line last changed, not why
میخواهیم ببینیم خط timeout=0 به کدام commit نسبت داده میشود. git blame برای هر خط نسخهٔ انتخابی، نسخهٔ تاریخچه و اطلاعات نویسنده تغییری را نشان میدهد که Git آن خط را به آن نسبت میدهد. این سرنخ است، نه حکم اخلاقی و نه شرح نیت.
We want to see which commit is attributed to timeout=0. For each line in a selected file version, git blame shows a revision and author information for the change Git attributes that line to. This is a lead, not a moral verdict or a statement of intent.
git blame -L 1,3 -- app.conf
^178a4d0 (Nika Example 2025-04-10 1) [http]
41fe88c (Nika Example 2025-04-10 2) timeout=0
0ca6e10 (Nika Example 2025-04-10 3) retries=2
git show --format=fuller 41fe88c -- app.confblame نسبت خط را به commit میدهد؛ اما یک commit قالببندی ممکن است خطها را دوباره نسبت بدهد. در چنین وضعی git blame -w FILE تفاوت whitespace را نادیده میگیرد و -M/-C میتواند جابهجایی/کپی خط را دنبال کند. این گزینهها heuristic هستند؛ patch را هم بخوان.
Blame attributes a line to a commit, but a formatting commit may change attribution. git blame -w FILE ignores whitespace differences, while -M/-C can trace moved/copied lines. These options are heuristic; inspect the patch too.
اسم blame کمی تند است؛ آن را «ردگیری خط» بخوان. Git میگوید آخرین تغییر قابلردگیری کجا بوده، نه چه کسی تصمیم را گرفته، چرا پذیرفته شده یا آیا همان تغییر علت خطای امروز است. git show --format=fuller نویسنده را از ثبتکننده جدا میکند؛ این دو ممکن است یک نفر نباشند.
The name blame sounds accusatory; think “line tracing.” Git shows the last traceable change, not who made a decision, why it was accepted, or whether it caused today's failure. git show --format=fuller separates author from committer; they may be different people.
نمودار ۴ — blame هر خط را به commit نسبت میدهد؛ پیکان نقطهچین یادآوری میکند که از این نسبتدادن نمیتوان انگیزه، تأیید یا علت قطعی باگ را نتیجه گرفت.
Diagram 4 — Blame attributes lines to commits; the dashed connector reminds us that attribution cannot establish motive, approval, or definitive causation.
اگر خط خراب در نسخهٔ فعلی حذف شده باشد، blame دیگر آن را نمایش نمیدهد. اینجا -S و -G سرنخ میدهند: اولی commitهایی را مییابد که تعداد رخداد یک رشتهٔ دقیق عوض شده؛ دومی commitهایی را که خط افزوده/حذفشدهشان با regex منطبق است.
If the bad line was deleted from the current version, blame cannot display it. -S and -G provide leads: the former finds commits where an exact string's occurrence count changed; the latter finds commits whose added or removed patch lines match a regex.
git log --oneline --follow -S'timeout=0' -- app.conf 41fe88c Set timeout to zero git log --oneline --follow -G'timeout[[:space:]]*=[[:space:]]*0' -- app.conf 41fe88c Set timeout to zero
در این پرونده هر دو commit را مییابند، اما سؤال یکی نیست: -S تغییر شمار رخدادهای متن دقیق را میسنجد؛ -G regex را روی خطهای patch امتحان میکند و فاصلههای متفاوت را هم میتواند بگیرد. اگر عبارتی جابهجا شود اما شمار رخدادهایش ثابت بماند، ممکن است -S آن commit را نگیرد و -G بگیرد.
Both find the commit here, but they ask different questions: -S checks the exact string's occurrence count; -G tests a regex against patch lines and can match spacing variations. If text moves while its count stays constant, -S may omit the commit while -G finds it.
اگر جستوجو نتیجه نداد، رشته را از patch واقعی بردار، مسیر قدیمی و جدید را بررسی کن و فیلتر نسخهٔ تاریخچه را بازبینی کن. نبودن نتیجه نمیگوید عبارت هیچوقت وجود نداشته؛ شاید commit نشده یا در اشارهگرهای قابلدسترسی فعلی نیست.
If the search finds nothing, copy the string from a real patch, check old and new paths, and review revision filters. No result does not prove the text never existed; it may never have been committed or may not be reachable from current refs.
| روشMethod | چه میجوید؟What it searches | محدودیت نزدیکNearby limitation |
|---|---|---|
git blame FILE | commit نسبتدادهشده به خط موجودCommit attributed to a current line | نیت را نمیگوید؛ refactor ممکن است منشأ را بپوشاند.No intent; refactoring can obscure origin. |
git log -S'TEXT' | تغییر تعداد رخداد متن دقیقChanged occurrence count of exact text | جابهجایی با شمار ثابت ممکن است جا بماند.A move with unchanged count can be missed. |
git log -G'REGEX' | خطهای patch منطبق با regexPatch lines matching a regex | regex یا مسیر محدود ممکن است ناقص باشد.A regex or path filter can be too narrow. |
وقتی خروجی با انتظارمان نمیخواند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.
خروجی میگوید 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.
سه 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.
در تاریخچهٔ خطی، 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.
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.
چرا 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.
کدام فرمان نام و تاریخ نویسنده و ثبتکننده را برای یک 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.
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.
عنوان 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.
بعد از 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.
یک متن دقیق فقط جابهجا شده و تعداد رخدادش ثابت مانده، اما خط 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.
برای «فقط نام فایل و وضعیت تغییر» --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.
برای خواندن تغییر 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.
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.
یک شناسهٔ کوتاه در مخزن 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}'.
میخواهی بفهمی متن دقیق 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.
جستوجوی -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.
تغییر نامرتبط 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.
گراف یک 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.
پروژهٔ کوچک: چه کسی تنظیم را خراب کرد؟Mini project: who broke the configuration?
یک پروندهٔ مستند بساز، نه یک مقصر
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.
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
- موقعیت فعلی را ثبت کنRecord the current position
git status --shortوgit log --oneline --graph --decorateرا اجرا کن. معیار: درخت کاری تمیز و پنج commit؛ اگر نه، اول ساخت پرونده را بررسی کن.Run
git status --shortandgit log --oneline --graph --decorate. Acceptance: clean working tree and five commits; if not, revisit setup. - مسیر را از تغییرنام رد کنTrace across the rename
git log --name-status -- app.confوgit log --follow --name-status -- app.confرا مقایسه کن. در گزارش بگو چرا دومی commitهای قدیمیتر را هم پیدا کرد و چرا تغییرنام یک استنباط است.Compare
git log --name-status -- app.confwithgit log --follow --name-status -- app.conf. Explain why the latter reaches older commits and why rename is an inference. - 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 withgit show COMMIT -- app.confandgit diff COMMIT^ COMMIT -- app.conf. State diff direction. - نسبت blame را با فراداده مقایسه کنCompare blame attribution with metadata
git blame app.confوgit show --format=fuller COMMIT -- app.confرا بخوان. نویسنده، ثبتکننده و آخرین تغییر خط را ثبت کن، اما دربارهٔ نیت حکم نده.Read
git blame app.confandgit show --format=fuller COMMIT -- app.conf. Record author, committer, and the line's last change, but make no claim about intent. - مرز نتیجهگیری را مستند کنDocument the boundary of your conclusion
فرمان
-Gرا با-Sمقایسه کن. گزارشت باید commit نامزد، فرمانها، چند خط diff، سازگاری یا اختلاف blame، شواهد پشتیبان و یک عدمقطعیت را داشته باشد.Compare a
-Gquery 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 -- PATH | A مبدأ، 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. |
- از نمای گراف به 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/-Gappropriately 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.