رفتن به محتوا
LoopX

تاریخچه و تفاوت‌ها

توی این درس یاد می‌گیری تاریخچه‌ی یک پروژه را مثل یک کارآگاه بخوانی: با git log و فرمت‌ها و فیلترهایش (--oneline، --graph، --author، --since، -S)، git show برای دیدن یک commit، git diff و git diff --staged برای دیدن تفاوت‌ها، و git blame برای دانستن اینکه هر خط را چه کسی و کی نوشته. تمرین: پیدا کن یک خط مشخص در کدام commit اضافه شده.

مسئله: «این را چه کسی خراب کرد؟»

Section titled “مسئله: «این را چه کسی خراب کرد؟»”

یک تاریخچه‌ی Git فقط برای برگشتن نیست؛ حافظه‌ی پروژه است. وقتی چیزی خراب می‌شود، سؤال‌ها همیشه همان‌هاست: چه چیزی عوض شده؟ کی؟ چه کسی؟ چرا؟ آیا آن خط همیشه این‌طور بود؟ Git جواب همه را دارد، به شرطی که بلد باشی بپرسی.

تشبیه: دوربین مداربسته‌ی پروژه

Section titled “تشبیه: دوربین مداربسته‌ی پروژه”

git log فیلم کامل دوربین است، commit به commit. git show یک فریم را بزرگ می‌کند. git diff دو فریم را کنار هم می‌گذارد و فقط تفاوت را نشان می‌دهد. و git blame روی هر خط از کد می‌گوید «این را چه کسی و در کدام فریم آنجا گذاشت».

هر commit به قبلی‌اش اشاره می‌کند. برای اشاره به commit های اخیر لازم نیست هش بنویسی: HEAD آخرین است، HEAD~1 یکی قبل‌تر، HEAD~2 دو تا قبل‌تر.

برای تمرین به یک تاریخچه‌ی غنی‌تر از «یک commit» نیاز داریم: چند نفر، چند روز. یک پروژه‌ی کوچک با شش commit از سه نویسنده می‌سازم. چون commit ها همین الآن ساخته می‌شوند، با متغیرهای محیطی GIT_AUTHOR_NAME، GIT_AUTHOR_EMAIL و GIT_AUTHOR_DATE نویسنده و تاریخ هر کدام را مشخص کرده‌ام (روی یک پروژه‌ی واقعی خودکار ثبت می‌شوند؛ این فقط برای ساختن نمونه است):

Terminal window
mkdir -p ~/gitlab/shop && cd ~/gitlab/shop && git init -q
c() { # c نام ایمیل «چند روز قبل» پیام
local d; d=$(date -d "$3 days ago" --iso-8601=seconds)
GIT_AUTHOR_NAME="$1" GIT_AUTHOR_EMAIL="$2" GIT_AUTHOR_DATE="$d" GIT_COMMITTER_DATE="$d" git commit -qam "$4"
}
printf 'print("Hello")\n' > app.py
git add app.py && c Ali ali@example.com 6 "Add greeting script"
printf '\ndef total(price, qty):\n return price * qty\n' >> app.py
c Sara sara@example.com 5 "Add price calculation"
sed -i 's/return price \* qty/return price * qty * 0.9/' app.py
c Reza reza@example.com 4 "Apply 10 percent discount to total"
printf '# Shop\nA tiny price calculator.\n' > README.md
git add README.md && c Sara sara@example.com 3 "Add README"
sed -i 's/print("Hello")/print("Welcome to the shop")/' app.py
c Ali ali@example.com 2 "Change greeting to a welcome message"
sed -i 's/return price \* qty \* 0.9/return round(price * qty * 0.9, 2)/' app.py
c Reza reza@example.com 1 "Round the total to two decimals"
git log --oneline | wc -l
cat app.py
خروجی
6
print("Welcome to the shop")
def total(price, qty):
return round(price * qty * 0.9, 2)

مثال ۱: git log و فرمت‌های اصلی

Section titled “مثال ۱: git log و فرمت‌های اصلی”
Terminal window
cd ~/gitlab/shop
echo "=== git log (کامل، دو commit آخر):"
git log -n 2
echo "=== git log --oneline (یک خط برای هر commit):"
git log --oneline
echo "=== با آمار فایل‌ها (--stat) برای سه commit:"
git log --stat -n 3 --format='%h %an: %s'
خروجی
=== git log (کامل، دو commit آخر):
commit 44478ec45f778277890da9e5151912c86c60ea9c
Author: Reza <reza@example.com>
Date: Sat Oct 3 08:12:45 2026 +0000
Round the total to two decimals
commit 2a061efca9e14553bc046142c157f3af3a5bfab9
Author: Ali <ali@example.com>
Date: Fri Oct 2 08:12:45 2026 +0000
Change greeting to a welcome message
=== git log --oneline (یک خط برای هر commit):
44478ec Round the total to two decimals
2a061ef Change greeting to a welcome message
f39a552 Add README
ac77b84 Apply 10 percent discount to total
445e7b1 Add price calculation
afa6f60 Add greeting script
=== با آمار فایل‌ها (--stat) برای سه commit:
44478ec Reza: Round the total to two decimals
app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
2a061ef Ali: Change greeting to a welcome message
app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
f39a552 Sara: Add README
README.md | 2 ++
1 file changed, 2 insertions(+)

پیش‌فرض git log از جدید به قدیم و با ۴ چیز در هر commit است: شناسه‌ی کامل (hash)، نویسنده، تاریخ، پیام. در ترمینال واقعی خروجی طولانی در یک pager (less) باز می‌شود: با Space ورق بزن و با q خارج شو. (در این درس خروجی را مستقیم می‌بینی.) فرمت‌ها:

گزینه کار
-n 3 یا -3 فقط ۳ commit آخر
--oneline هر commit یک خط: هش کوتاه + عنوان
--stat فایل‌های عوض‌شده و تعداد خط‌ها
-p (--patch) خود تغییر خط‌به‌خط (diff)
--graph شاخه‌ها را با خط و دایره نشان بده
--decorate نام شاخه و tag کنار commit (پیش‌فرض روشن است)
--all همه‌ی شاخه‌ها، نه فقط شاخه‌ی فعلی
--format='...' قالب دلخواه (%h هش کوتاه، %an نویسنده، %ar زمان نسبی، %s عنوان)

مثال ۲: قالب دلخواه و آمار

Section titled “مثال ۲: قالب دلخواه و آمار”
Terminal window
cd ~/gitlab/shop
echo "=== قالب دلخواه:"
git log --format='%h | %<(6)%an | %<(14)%ar | %s'
echo "=== چند commit دارد؟"
git rev-list --count HEAD
echo "=== هر نفر چند commit زده؟ (shortlog)"
git shortlog -sn HEAD
خروجی
=== قالب دلخواه:
44478ec | Reza | 24 hours ago | Round the total to two decimals
2a061ef | Ali | 2 days ago | Change greeting to a welcome message
f39a552 | Sara | 3 days ago | Add README
ac77b84 | Reza | 4 days ago | Apply 10 percent discount to total
445e7b1 | Sara | 5 days ago | Add price calculation
afa6f60 | Ali | 6 days ago | Add greeting script
=== چند commit دارد؟
6
=== هر نفر چند commit زده؟ (shortlog)
2 Ali
2 Reza
2 Sara

--format با علامت‌های % ساخته می‌شود (git help log، بخش PRETTY FORMATS؛ %<(6) یعنی ستونی به عرض ۶). git shortlog -sn HEAD نویسنده‌ها را بر اساس تعداد commit مرتب می‌کند (-s فقط تعداد، -n مرتب بر اساس عدد؛ HEAD را صریح نوشته‌ام چون وقتی ورودی ترمینال نیست shortlog منتظر ورودی می‌ماند)، و git rev-list --count HEAD تعداد commit ها را برای اسکریپت می‌دهد.

مثال ۳: گراف شاخه‌ها، --graph

Section titled “مثال ۳: گراف شاخه‌ها، --graph”

وقتی چند شاخه داریم، --graph ساختار را رسم می‌کند. یک شاخه‌ی ویژگی می‌سازم و با main ترکیبش می‌کنم (درس بعدی کامل توضیح می‌دهد؛ این‌جا فقط خروجی log مهم است):

Terminal window
mkdir -p ~/gitlab/graph && cd ~/gitlab/graph && git init -q
echo 1 > f && git add f && git commit -qm "Start"
echo 2 >> f && git commit -qam "Second on main"
git switch -qc feature
echo a > g && git add g && git commit -qm "Feature part 1"
echo b >> g && git commit -qam "Feature part 2"
git switch -q main
echo 3 >> f && git commit -qam "Third on main"
git merge -q --no-edit feature
echo "=== git log --oneline --graph --decorate:"
git log --oneline --graph --decorate
خروجی
=== git log --oneline --graph --decorate:
* f11fd6f (HEAD -> main) Merge branch 'feature'
|\
| * 114ad1a (feature) Feature part 2
| * bd7ba74 Feature part 1
* | cb081a4 Third on main
|/
* 2e534ea Second on main
* f7349cd Start

هر ستاره (*) یک commit است، خط‌های | و \ و / شاخه‌ها و ادغام را نشان می‌دهند، و داخل پرانتز می‌بینی HEAD، main و feature به کدام commit اشاره می‌کنند. این ترکیب (--oneline --graph --decorate) آن‌قدر مفید است که معمولاً برایش alias می‌سازند (درس قبل: alias.lg).

مثال ۴: فیلتر کردن تاریخچه

Section titled “مثال ۴: فیلتر کردن تاریخچه”

تاریخچه‌ی بزرگ را با فیلتر کوچک کن (روی پروژه‌ی shop):

Terminal window
cd ~/gitlab/shop
echo "=== فقط commit های «Sara»:"
git log --author=Sara --format='%h %an %s'
echo "=== فقط ۶۰ ساعت اخیر:"
git log --since="60 hours ago" --format='%h %ar %s'
echo "=== پیام‌هایی که کلمه‌ی discount دارند:"
git log --grep=discount --format='%h %s'
echo "=== تاریخچه‌ی فقط یک فایل (README.md):"
git log --format='%h %s' -- README.md
echo "=== commit هایی که تعداد «0.9» در کد را عوض کرده‌اند (-S، pickaxe):"
git log -S"0.9" --format='%h %an %s'
خروجی
=== فقط commit های «Sara»:
f39a552 Sara Add README
445e7b1 Sara Add price calculation
=== فقط ۶۰ ساعت اخیر:
44478ec 24 hours ago Round the total to two decimals
2a061ef 2 days ago Change greeting to a welcome message
=== پیام‌هایی که کلمه‌ی discount دارند:
ac77b84 Apply 10 percent discount to total
=== تاریخچه‌ی فقط یک فایل (README.md):
f39a552 Add README
=== commit هایی که تعداد «0.9» در کد را عوض کرده‌اند (-S، pickaxe):
ac77b84 Reza Apply 10 percent discount to total

آخری شگفت‌انگیز است: -S"متن" (به آن pickaxe یا کلنگ می‌گویند) commit هایی را پیدا می‌کند که تعداد دفعات آمدن آن متن را در کد تغییر داده‌اند؛ یعنی commit ای که آن را اضافه یا حذف کرده. این‌جا فقط commit تخفیف است، نه commit گردکردن (که 0.9 را داشت ولی تعدادش را عوض نکرد). گزینه‌های هم‌خانواده: -G"regex" (با عبارت منظم)، --author، --until، --no-merges، و ترکیب‌شان.

مثال ۵: git show، یک commit از نزدیک

Section titled “مثال ۵: git show، یک commit از نزدیک”
Terminal window
cd ~/gitlab/shop
echo "=== git show HEAD~3 (چهارمین commit از آخر؛ تخفیف):"
git show HEAD~3 --format='%h %an (%ar)%n%s%n'
echo "=== فایل در آن زمان (git show commit:مسیر):"
git show HEAD~3:app.py
خروجی
=== git show HEAD~3 (چهارمین commit از آخر؛ تخفیف):
ac77b84 Reza (4 days ago)
Apply 10 percent discount to total
diff --git a/app.py b/app.py
index decd307..c5b551b 100644
--- a/app.py
+++ b/app.py
@@ -1,4 +1,4 @@
print("Hello")
def total(price, qty):
- return price * qty
+ return price * qty * 0.9
=== فایل در آن زمان (git show commit:مسیر):
print("Hello")
def total(price, qty):
return price * qty * 0.9

git show دو چیز نشان می‌دهد: پیام و نویسنده و تغییر دقیق (diff آن commit). و با شکل commit:فایل محتوای یک فایل را همان‌طور که در آن commit بود چاپ می‌کند (بدون اینکه چیزی در پوشه‌ی کاری عوض شود). HEAD~3 یعنی «سه commit قبل از HEAD». شکل‌های دیگر آدرس‌دهی: هش کوتاه (git show 1a2b3c4)، HEAD^ (والد، مثل HEAD~1)، و نام شاخه یا tag.

مثال ۶: git diff، چه چیزی تغییر کرده؟

Section titled “مثال ۶: git diff، چه چیزی تغییر کرده؟”

git diff دو چیز را با هم مقایسه می‌کند. بدون آرگومان: پوشه‌ی کاری با ناحیه‌ی staging. با --staged: staging با آخرین commit. ببین هر کدام چه می‌گوید:

Terminal window
cd ~/gitlab/shop
echo "total = total(10, 3)" >> app.py
echo "=== ۱) git diff (پوشه‌ی کاری ← staging):"
git diff
git add app.py
echo "=== ۲) بعد از add، دوباره git diff:"
git diff
echo "(خالی! چون staging و پوشه‌ی کاری یکی‌اند)"
echo "=== ۳) git diff --staged (staging ← آخرین commit):"
git diff --staged
echo "print(total)" >> app.py
echo "=== ۴) git diff HEAD (پوشه‌ی کاری ← آخرین commit؛ هر دو تغییر):"
git diff HEAD
خروجی
=== ۱) git diff (پوشه‌ی کاری ← staging):
diff --git a/app.py b/app.py
index 2be6b70..b6f34a9 100644
--- a/app.py
+++ b/app.py
@@ -2,3 +2,4 @@ print("Welcome to the shop")
def total(price, qty):
return round(price * qty * 0.9, 2)
+total = total(10, 3)
=== ۲) بعد از add، دوباره git diff:
(خالی! چون staging و پوشه‌ی کاری یکی‌اند)
=== ۳) git diff --staged (staging ← آخرین commit):
diff --git a/app.py b/app.py
index 2be6b70..b6f34a9 100644
--- a/app.py
+++ b/app.py
@@ -2,3 +2,4 @@ print("Welcome to the shop")
def total(price, qty):
return round(price * qty * 0.9, 2)
+total = total(10, 3)
=== ۴) git diff HEAD (پوشه‌ی کاری ← آخرین commit؛ هر دو تغییر):
diff --git a/app.py b/app.py
index 2be6b70..a67b7bd 100644
--- a/app.py
+++ b/app.py
@@ -2,3 +2,5 @@ print("Welcome to the shop")
def total(price, qty):
return round(price * qty * 0.9, 2)
+total = total(10, 3)
+print(total)

یک خروجی diff را این‌طور بخوان:

بخش معنی
diff --git a/app.py b/app.py فایل a (قبل) در مقابل b (بعد)
--- a/app.py و +++ b/app.py فایل قدیم و جدید
@@ -2,3 +2,4 @@ hunk: از خط ۲، سه خط در قدیم ← از خط ۲، چهار خط در جدید
خط‌های با - (قرمز) در قدیم بود، حذف شد
خط‌های با + (سبز) اضافه شد
خط‌های بدون علامت بافت اطراف (برای فهمیدن محل)
git diff بدون آرگومان پوشه‌ی کاری را با staging مقایسه می‌کند؛ git diff --staged، staging را با آخرین commit؛ و git diff HEAD هر دو مرحله را با هم.

مثال ۷: diff بین دو commit و گزینه‌های مفید

Section titled “مثال ۷: diff بین دو commit و گزینه‌های مفید”
Terminal window
cd ~/gitlab/shop
# تغییرهای آزمایشی مثال قبل را برمی‌گردانم (git restore: درس «برگرداندن تغییرات»)
git restore --staged app.py && git restore app.py
echo "=== بین دو commit (قدیم..جدید):"
git diff HEAD~3 HEAD --stat
echo "=== فقط نام فایل‌های عوض‌شده:"
git diff HEAD~5 HEAD --name-only
echo "=== با دید «کلمه‌ای» (--word-diff):"
git diff HEAD~1 HEAD --word-diff
خروجی
=== بین دو commit (قدیم..جدید):
README.md | 2 ++
app.py | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
=== فقط نام فایل‌های عوض‌شده:
README.md
app.py
=== با دید «کلمه‌ای» (--word-diff):
diff --git a/app.py b/app.py
index 4e96212..2be6b70 100644
--- a/app.py
+++ b/app.py
@@ -1,4 +1,4 @@
print("Welcome to the shop")
def total(price, qty):
return [-price-]{+round(price+} * qty * [-0.9-]{+0.9, 2)+}

git diff قدیم جدید (یا قدیم..جدید) هر دو commit را مقایسه می‌کند. گزینه‌های پرکاربرد: --stat (خلاصه)، --name-only (فقط نام‌ها)، --name-status (نام با نوع تغییر: A/M/D)، --word-diff (تفاوت در سطح کلمه؛ برای متن عالی است)، -w (فاصله‌های اضافه را نادیده بگیر)، و -- مسیر برای محدودکردن به یک فایل.

مثال ۸: git blame، هر خط را چه کسی نوشت؟

Section titled “مثال ۸: git blame، هر خط را چه کسی نوشت؟”
Terminal window
cd ~/gitlab/shop
echo "=== git blame app.py:"
git blame app.py
echo "=== فقط خط‌های ۳ تا ۴ (-L):"
git blame -L 3,4 app.py
خروجی
=== git blame app.py:
2a061efc (Ali 2026-10-02 08:12:45 +0000 1) print("Welcome to the shop")
445e7b16 (Sara 2026-09-29 08:12:45 +0000 2)
445e7b16 (Sara 2026-09-29 08:12:45 +0000 3) def total(price, qty):
44478ec4 (Reza 2026-10-03 08:12:45 +0000 4) return round(price * qty * 0.9, 2)
=== فقط خط‌های ۳ تا ۴ (-L):
445e7b16 (Sara 2026-09-29 08:12:45 +0000 3) def total(price, qty):
44478ec4 (Reza 2026-10-03 08:12:45 +0000 4) return round(price * qty * 0.9, 2)

برای هر خط می‌بینی: هش commit ای که آن خط را آخرین بار عوض کرد، نویسنده، تاریخ و شماره‌ی خط. حالا تمرین اصلی: «خط return round(...) (خط ۴) در کدام commit اضافه شده؟». از blame هش را می‌گیری و با git show معنی‌اش را می‌خوانی:

Terminal window
cd ~/gitlab/shop
h=$(git blame -L 4,4 --porcelain app.py | head -1 | cut -d' ' -f1)
echo "هش commit ای که خط ۴ را آخر بار عوض کرد: ${h:0:7}"
git show -s --format='%h %an: %s (%ar)' $h
echo "--- ولی این خط از اول این‌طور نبود؛ کی «0.9» اضافه شد؟ (با -S)"
git log -S"0.9" --format='%h %an: %s (%ar)' | tail -1
خروجی
هش commit ای که خط ۴ را آخر بار عوض کرد: 44478ec
44478ec Reza: Round the total to two decimals (24 hours ago)
--- ولی این خط از اول این‌طور نبود؛ کی «0.9» اضافه شد؟ (با -S)
ac77b84 Reza: Apply 10 percent discount to total (4 days ago)

نکته‌ی مهم: git blame فقط آخرین commit ای را که خط را عوض کرده نشان می‌دهد. اگر دنبال commit ای هستی که یک ایده یا متن را برای اولین بار وارد کرد، با git log -S بگرد (یا blame را روی نسخه‌ی قدیمی‌تر اجرا کن: git blame HEAD~2 -- app.py).

پشت پرده: diff و log چطور کار می‌کنند؟

Section titled “پشت پرده: diff و log چطور کار می‌کنند؟”

Git diff نگه نمی‌دارد؛ هر commit یک عکس کامل است (درس اول). git diff و git show در لحظه‌ی درخواست دو عکس را برمی‌دارند و با یک الگوریتم (به‌صورت پیش‌فرض Myers) کوتاه‌ترین فهرست «حذف/اضافه» را حساب می‌کنند. git log هم از HEAD شروع می‌کند و پشت‌سرهم به والد (parent) هر commit می‌رود تا به اولین برسد. blame همین کار را برای هر خط انجام می‌دهد: عقب می‌رود تا جایی که آن خط آخرین بار عوض شده. ببین log دقیقاً از زنجیره‌ی والدها ساخته می‌شود:

Terminal window
cd ~/gitlab/shop
echo "=== والد هر commit (هش کوتاه ← والدش):"
git log --format='%h والد: %p %s' -n 3
echo "=== و diff حساب‌شده‌ی یک commit با نسخه‌ی بدون تفاوت:"
git diff HEAD HEAD && echo "(تفاوت یک commit با خودش خالی است)"
خروجی
=== والد هر commit (هش کوتاه ← والدش):
44478ec والد: 2a061ef Round the total to two decimals
2a061ef والد: f39a552 Change greeting to a welcome message
f39a552 والد: ac77b84 Add README
=== و diff حساب‌شده‌ی یک commit با نسخه‌ی بدون تفاوت:
(تفاوت یک commit با خودش خالی است)
دستور کار
git log --oneline تاریخچه‌ی کوتاه
git log --oneline --graph --all گراف همه‌ی شاخه‌ها
git log -p / --stat با تغییر کامل / با آمار
git log --author=نام / --since="2 days ago" فیلتر نویسنده / زمان
git log --grep=متن جستجو در پیام‌ها
git log -S"متن" commit هایی که تعداد آن متن را عوض کرده‌اند
git log -- مسیر تاریخچه‌ی یک فایل
git show commit / commit:فایل یک commit / محتوای فایل در آن commit
git diff پوشه‌ی کاری در برابر staging
git diff --staged staging در برابر آخرین commit
git diff HEAD پوشه‌ی کاری در برابر آخرین commit
git diff قدیم جدید دو commit
git blame فایل / -L 4,9 نویسنده‌ی هر خط / فقط چند خط
git shortlog -sn HEAD تعداد commit هر نفر
آدرس معنی
HEAD commit فعلی
HEAD~1 یا HEAD^ والد
HEAD~3 سه commit قبل
1a2b3c4 هش کوتاه (کافی است یکتا باشد)
main، v1.0 نام شاخه یا tag

۱) git diff بعد از git add خالی است

Section titled “۱) git diff بعد از git add خالی است”
Terminal window
mkdir -p ~/gitlab/mistake && cd ~/gitlab/mistake && git init -q
echo a > a.txt && git add a.txt && git commit -qm "Add a"
echo b >> a.txt && git add a.txt
echo "--- git diff:"
git diff
echo "(خالی)"
echo "--- git diff --staged:"
git diff --staged
خروجی
--- git diff:
(خالی)
--- git diff --staged:
diff --git a/a.txt b/a.txt
index 7898192..422c2b7 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1,2 @@
a
+b

هیچ خطایی نیست؛ git diff بدون آرگومان فقط پوشه‌ی کاری را با staging مقایسه می‌کند و چون تازه add کرده‌ای، هیچ تفاوتی نیست. راه‌حل: برای دیدن آنچه قرار است commit شود git diff --staged (یا --cached).

۲) فکر اینکه blame مقصر را نشان می‌دهد

Section titled “۲) فکر اینکه blame مقصر را نشان می‌دهد”

git blame فقط می‌گوید چه کسی آن خط را آخرین بار لمس کرده، که ممکن است یک بازآرایی (فاصله‌گذاری، تغییر نام متغیر) باشد، نه نوشتن اصلی. راه‌حل: git blame -w (نادیده‌گرفتن فاصله‌ها) و -C (دنبال‌کردن کد جابه‌جاشده)، و کاربرد git log -S. و کارکرد blame پیدا کردن context است، نه مقصر؛ فرهنگ تیمی «blame-free» را حفظ کن.

وقتی git log طولانی است، خروجی در less باز می‌شود و کاربر تازه‌کار نمی‌داند چطور خارج شود. راه‌حل: کلید q. برای چاپ مستقیم: git --no-pager log یا git config --global core.pager cat.

Terminal window
cd ~/gitlab/shop
git show HEAD~99 2>&1 | head -3
git show nosuchbranch 2>&1 | head -3
خروجی
fatal: ambiguous argument 'HEAD~99': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
fatal: ambiguous argument 'nosuchbranch': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

fatal: ambiguous argument ... unknown revision or path not in the working tree: Git آن آدرس را نه به‌عنوان commit و نه به‌عنوان فایل می‌شناسد. راه‌حل: با git log --oneline هش یا HEAD~n درست را پیدا کن.

۵) فراموش‌کردن -- قبل از اسم فایل

Section titled “۵) فراموش‌کردن -- قبل از اسم فایل”

اگر اسم فایلی با اسم یک شاخه یکی باشد، Git گیج می‌شود. git log -- مسیر (دو خط تیره‌ی جدا) صریحاً می‌گوید «بعدش مسیر است، نه commit».

✎ تمرینآسان

شش commit اخیر shop را در یک خط هر کدام ببین، به‌همراه نام نویسنده و زمان نسبی (%ar).

دیدن جواب
Terminal window
cd ~/gitlab/shop
git log -n 6 --format='%h %an %ar: %s'
خروجی
44478ec Reza 24 hours ago: Round the total to two decimals
2a061ef Ali 2 days ago: Change greeting to a welcome message
f39a552 Sara 3 days ago: Add README
ac77b84 Reza 4 days ago: Apply 10 percent discount to total
445e7b1 Sara 5 days ago: Add price calculation
afa6f60 Ali 6 days ago: Add greeting script
✎ تمرینمتوسط

تمرین اصلی: پیدا کن یک خط مشخص در کدام commit اضافه شده. خط def total(price, qty): را چه کسی و در کدام commit وارد کرد؟ (از git blame و git log -S هر دو استفاده کن و جوابشان را مقایسه کن.)

دیدن جواب
Terminal window
cd ~/gitlab/shop
echo "--- با blame (آخرین کسی که این خط را لمس کرد):"
git blame -L '/def total/,+1' app.py
echo "--- با log -S (اولین commit ای که این متن را اضافه کرد):"
git log -S"def total" --format='%h %an: %s (%ar)'
خروجی
--- با blame (آخرین کسی که این خط را لمس کرد):
445e7b16 (Sara 2026-09-29 08:12:45 +0000 3) def total(price, qty):
--- با log -S (اولین commit ای که این متن را اضافه کرد):
445e7b1 Sara: Add price calculation (5 days ago)
✎ تمرینسخت

فهرست یکتای همه‌ی فایل‌هایی را که در پنج commit آخر عوض شده‌اند بساز (هر نام یک بار)، و برای هر نویسنده تعداد خط‌های اضافه‌شده‌اش را در کل تاریخچه بشمار.

دیدن جواب
Terminal window
cd ~/gitlab/shop
echo "--- فایل‌های عوض‌شده در ۵ commit آخر (یکتا):"
git log -5 --name-only --format= | sort -u | grep -v '^$'
echo "--- خط‌های اضافه‌شده به تفکیک نویسنده (numstat):"
git log --numstat --format='AUTHOR:%an' | awk '/^AUTHOR:/ {a=substr($0,8); next} NF==3 {add[a]+=$1} END {for (k in add) print add[k], k}' | sort -rn
خروجی
--- فایل‌های عوض‌شده در ۵ commit آخر (یکتا):
README.md
app.py
--- خط‌های اضافه‌شده به تفکیک نویسنده (numstat):
5 Sara
2 Reza
2 Ali
⚡ بررسی سریع

بعد از git add file.txt، دستور git diff (بدون آرگومان) چه چیزی نشان می‌دهد؟

؟ آزمونک
  1. HEAD~2 یعنی چه؟

  2. کدام دستور فقط commit هایی را نشان می‌دهد که تعداد متن «0.9» را در کد عوض کرده‌اند (یعنی اضافه یا حذفش کرده‌اند)؟

  3. git diff --staged چه دو چیزی را مقایسه می‌کند؟

  4. git blame چه می‌گوید؟

  5. در خروجی diff یک خط با علامت + در ابتدا یعنی؟

  • git log: --oneline (کوتاه)، --graph --decorate --all (شاخه‌ها)، --stat/-p (تغییر)، --format='...' (قالب دلخواه)؛ فیلتر با --author، --since، --grep، -- مسیر و -S"متن".
  • git show commit پیام و تغییر یک commit را می‌دهد؛ git show commit:فایل محتوای فایل را در آن لحظه.
  • git diff (کاری ← staging)، git diff --staged (staging ← commit)، git diff HEAD (کاری ← commit)، git diff قدیم جدید (دو commit). خط‌های - حذف، + اضافه.
  • git blame فایل برای هر خط commit و نویسنده‌ی آخرین تغییر را می‌دهد؛ برای اولین ورود یک متن از git log -S استفاده کن.
  • آدرس‌دهی: HEAD، HEAD~n، هش کوتاه، نام شاخه یا tag. در pager با q خارج شو.
  • Git تفاوت ذخیره نمی‌کند؛ هر لحظه دو عکس را مقایسه می‌کند.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
git log --oneline --graph --allگراف کوتاه همه‌ی شاخه‌ها
git log -p -n 3سه commit آخر با تغییر کامل
git log --author=نام --since="1 week ago"فیلتر نویسنده و زمان
git log -S"متن"commit هایی که آن متن را اضافه/حذف کرده‌اند
git log -- فایلتاریخچه‌ی یک فایل
git show HEAD~2commit دوم از آخر
git show commit:فایلفایل در آن commit
git diff / git diff --staged / git diff HEADسه مقایسه‌ی اصلی
git diff قدیم جدید --statتفاوت دو commit، خلاصه
git blame -L 4,9 فایلنویسنده‌ی خط‌های ۴ تا ۹
git shortlog -sn HEADتعداد commit هر نفر