بازگشت به وبلاگ

.gitignore کار نمی‌کند؟ این رفعِ واقعی است

۲۶ شهریور ۱۴۰۵

TL;DR: اگر .gitignore کار نمی‌کند، فایل تقریباً همیشه از قبل tracked است. Git فایل‌هایی را نادیده می‌گیرد که هرگز ندیده — فایل حاضر در index به هر قاعده‌ای که بنویسید مصون است. حقیقت را با git check-ignore -v <path> بگیرید: خروجی بی‌صدا یعنی مسیر tracked است و قاعده‌ای اعمال نمی‌شود. با git rm --cached <path> درستش کنید، commit بزنید، و قاعده ignore از همان commit به بعد کار می‌کند. ترتیب قواعد، تله‌های نفی و سرخ‌پرنده‌های VS Code بقیه موارد را توضیح می‌دهند.

چرا .gitignore کار نمی‌کند؟

یک سازوکار بیشتر موارد را پوشش می‌دهد: فایل قبل از وجود قاعده commit شده بود. .gitignore فیلتری نیست که فایل‌ها را از دید git پنهان کند — قاعده‌ای است درباره اینکه git add از حالت untracked چه بردارد. وقتی فایلی در index است، git تغییرات محتوایش را تا ابد دنبال می‌کند تا شما صریح untrackش کنید. ویرایش .gitignore بعد از ماجرا برای فایل‌های tracked هیچ عوض نمی‌کند؛ به همین دلیل دنباله کلاسیک «commit زدن .env، وحشت، اضافه کردن .env به .gitignore، commit دوباره» با هر push باز هم secret را می‌فرستد.

این همه را گاز می‌گیرد چون git تقریباً همه‌جا هست — نظرسنجی 2022 Stack Overflow استفاده از Git را بیش از 93٪ توسعه‌دهندگان حرفه‌ای گزارش کرده (survey.stackoverflow.co) — و هر یک از آن توسعه‌دهندگان بالاخره برای فایلی که هفته پیش commit زده قاعده می‌نویسد.

بقیه این نوشته موارد اقلیت را پوشش می‌دهد: اشتباه در ترتیب قواعد، تله‌های نفی، لبه‌های پوشه‌ای و سؤال IDE. ولی اول health check بخش بعد را بزنید — بیش از نُه بار از ده، همان تحقیق را تمام می‌کند.

چطور بفهمم کدام قاعده gitignore با فایلم match می‌شود؟

git check-ignore ابزار تشخیص است، و سکوتش خودِ تشخیص:

# Prints the matching rule + file + line number when a rule applies
git check-ignore -v debug.log
# .gitignore:3:*.log    debug.log

# Prints NOTHING when no rule applies — the file is tracked (or no rule exists)
git check-ignore -v src/.env
# (silence = .gitignore is not ignoring this path, whatever you wrote)

# Exit codes: 0 = ignored, 1 = not ignored — scriptable
git check-ignore -q debug.log && echo "ignored" || echo "tracked or unruly"

خواندن خروجی -v: منبع قاعده (.gitignore، .git/info/exclude، یا فایل global شما)، بعد line-number:pattern، بعد مسیر. اگر قاعده‌ای بعدی غافلگیرتان کرد، تقدم را یادتان باشد: آخرین قاعده match‌شده می‌برد، پس !important.log بعد از *.log همان یک فایل را دوباره شامل می‌کند.

چرا gitignore برای فایل‌هایی که قبلاً commit شده‌اند کار نمی‌کند؟

فایل را untrack کنید، روی دیسک نگهش دارید، حذف را commit کنید:

# Untrack one file (the local copy survives — --cached touches the index only)
git rm --cached .env
git commit -m "stop tracking .env"

# Verify the ignore rule now applies
git check-ignore -v .env

از این commit به بعد، .gitignore صاحب مسیر است: ویرایش‌هایش دیگر در git status دیده نمی‌شوند و git add . دیگر برنمی‌دارد. فایل اما در history می‌ماند — اگر secret بوده، حذفش از آخرین commit کافی نیست. چرخاندن اعتبار رفع واقعی است؛ بازنویسی history رفع آرایشی (و لغو آخرین commit در Git: تغییرات بماند فقط وقتی کمک می‌کند که commit بد هنوز نوک باشد).

برای مخزنی پر از آشغال commit‌شده قبلی — خروجی بیلد، ریزه‌های ادیتور، node_modules ای که زود لغزیده داخل — untrack انبوه دو خط است:

git rm -r --cached .
git add .
git commit -m "apply .gitignore to tracked files"

این index را با قواعد فعلی از نو می‌سازد: مسیرهای نادیده‌شده می‌افتند بیرون، بقیه بدون تغییر دوباره اضافه می‌شوند. diff چشمگیر می‌زند (هزاران حذف) ولی از دیسک هیچ چیز پاک نمی‌کند. اگر هدف حذف آن فایل‌هاست نه فقط untrack، قلمرو git clean -fdx است — ببینید حذف فایل‌های untracked در Git: راهنمای امن git clean.

Git فایل‌هایی را نادیده می‌گیرد که هرگز ندیده. فایل حاضر در index مصون از .gitignore است — هیچ قاعده‌ای نوشتن، آن را از دید git نمی‌اندازد.

چرا gitignore برای پوشه کار نمی‌کند؟

سه تله مخصوص پوشه:

1. اسلش انتهایی برای نیت مهم است، نه برای match. هم build و هم build/ یک دایرکتوری را می‌گیرند، ولی build/ مستند می‌کند که منظورتان فقط دایرکتوری است — فایلی به نام build از آن جان به در می‌برد. اما در شمول دوباره، تقارن می‌شکند (تله بعدی).

2. نفی نمی‌تواند فایل‌های داخل یک دایرکتوری حذف‌شده را نجات دهد. مستندات git بی‌ابهام است: «اگر دایرکتوری والد فایلی حذف‌شده باشد، شمول دوباره آن فایل ممکن نیست» (git-scm.com/docs/gitignore). این تصمیم کارایی است — git دایرکتوری‌های حذف‌شده را کلاً رد می‌کند به‌جای گشتن درشان. پس این کار نمی‌کند:

build/
!build/keep.me        # dead rule — git never looks inside build/

رفع این است که محتوا را حذف کنید، نه دایرکتوری را:

build/*
!build/keep.me        # works — build/ itself is still open for inspection

3. فایل‌های .gitignore تودرتو در قلمرو خودشان می‌برند. قواعد در subdir/.gitignore برای مسیرهای زیر subdir روی فایل ریشه غلبه می‌کنند. وقتی git check-ignore -v منبعی غیر از انتظارتان نام می‌برد، معمولاً همین است.

چرا gitignore در VS Code کار نمی‌کند؟

تقریباً هیچ‌وقت به‌دلیل VS Code. نمای Source Control همان index ای را می‌خواند که git می‌خواند، پس نشانه یکسان است: فایل از قبل commit شده و هیچ ری‌استارت IDE ای index را عوض نمی‌کند. دو واقعیت مجاور VS Code دانستنی‌اند:

  • خاکستری در Explorer = نادیده‌شده؛ نارنجی/زرد = tracked با تغییرات. فایلی که بعد از نادیده‌گرفتن، modified نشان می‌دهد تأیید شماست که tracked است — رفع git rm --cached بالا را بزنید.
  • غایب بودن .gitignore در فهرست changed یعنی قاعده کار می‌کند — اصلاً به‌عنوان untracked ظاهر نمی‌شود. مردم اغلب گزارش «VS Code gitignore من را نادیده می‌گیرد» می‌دهند وقتی git status در CLI با نمای SCM کهنه اختلاف دارد؛ قبل از مقصر دانستن git، پنجره را reload کنید (Cmd/Ctrl+Shift+P → «Reload Window»).

.gitignore در مقابل .git/info/exclude در مقابل global: کدام کجا؟

فایلقلمروcommit می‌شود؟برای
.gitignore (مخزن)هر کسی که clone می‌کندبلهخروجی بیلد، وابستگی‌ها، .env — قواعد مشترک
.git/info/excludeفقط clone شمانهشلوغی شخصی: .scratch/، ریزه‌های ادیتور
core.excludesFile (global)همه مخزن‌های شمانهآشغال سیستم‌عامل: .DS_Store، Thumbs.db، *.swp
.gitignore + نفیمخزنبلهشمول دوباره استثناهای config ی tracked

فایل global همان است که بیشتر توسعه‌دهندگان هرگز ست نمی‌کنند و باید:

git config --global core.excludesFile ~/.gitignore_global
printf '.DS_Store\nThumbs.db\n*.swp\n' >> ~/.gitignore_global

قاعده خانه‌ای که ارزش کپی دارد: اگر قاعده به کل تیم سود می‌رساند، جای آن مخزن است؛ اگر فقط به شما، جای آن exclude یا فایل global است. commit کردن قواعد شخصی است که .gitignore ها 300 خطی می‌شوند و هیچ‌کس نمی‌داند کدام نیمه هنوز مهم است.

چطور تغییرات یک فایل tracked را نادیده بگیرم؟

گاهی یک فایل tracked را می‌خواهید (قالب config، فایل تنظیمات IDE) ولی می‌خواهید ویرایش‌های محلی در git status دیده نشوند. دو فلگ روی git update-index، که هیچ‌کدام جای کار تیمی نیست:

git update-index --skip-worktree config/local.dev   # local edits go quiet
git update-index --no-skip-worktree config/local.dev  # ...and back

--skip-worktree قابل‌دفاع است — می‌گوید «نسخه محلی من آگاهانه وا رفته». پسرخاله‌اش --assume-unchanged قول کارایی است («این فایل عوض نمی‌شود»)، نه سازوکار ignore، و git ممکن است بی‌سروصدا نقضش کند. هر دو فلگ موقع pull جلوی همه بلند می‌شکنند اگر upstream هم فایل را عوض کرده باشد — جواب‌های ماندگار، config ی فقط-محلی از بدو تولد از راه exclude است، یا فایل قالب (config.example) که git دنبالش می‌رود و شما کپی‌اش می‌کنید.

چک سلامت 30 ثانیه‌ای gitignore

git check-ignore -v <path>        # which rule? (silence = tracked, no rule)
git ls-files --error-unmatch <path>  # is it tracked at all?
git rm --cached <path>            # untrack, keep on disk
git commit -m "stop tracking <path>"
git check-ignore -v <path>        # rule now shows

تشخیص، untrack، راستی‌آزمایی. الگوی پشت هر گزارش «gitignore کار نمی‌کند» همان یک فایل با دو کلاه است — tracked در یک سمت index، نادیده در سمت دیگر — و یک فلگ --cached کلاه دوم را برمی‌دارد.

FAQ

چرا .gitignore کار نمی‌کند؟

در بیشتر موارد فایل از قبل tracked است. Git فقط فایل‌های untracked را نادیده می‌گیرد؛ فایل حاضر در index تا وقتی با git rm --cached untrack و commit نکنید، مصون از .gitignore است.

چطور بفهمم کدام قاعده gitignore با یک فایل match می‌شود؟

git check-ignore -v <path> را اجرا کنید. خط دقیق .gitignore و شماره قاعده را چاپ می‌کند، یا وقتی فایل tracked است و قاعده‌ای اعمال نمی‌شود بی‌صدا خارج می‌شود.

آیا git rm --cached فایل محلی من را پاک می‌کند؟

نه. --cached فایل را فقط از index برمی‌دارد؛ نسخه روی دیسک می‌ماند. حذف را commit کنید تا فایل untracked شود، از آن پس .gitignore اعمال می‌شود.

چرا gitignore نمی‌تواند فایلی داخل پوشه نادیده‌شده را دوباره شامل کند؟

Git به دلایل کارایی کل دایرکتوری‌های حذف‌شده را رد می‌کند. طبق مستندات git، اگر دایرکتوری والد فایلی حذف‌شده باشد، شمول دوباره آن فایل ممکن نیست.

— mrsaynothing

— mrsaynothing

یادداشت‌های میدانی درباره AI، لینوکس و self-hosting.

این نوشته را در dev.to بحث کنید dev.to ↗

آموزش بعدی با ایمیل

هر نوشته یک ایمیل. درستش کن، برو سراغ بعدی.

self-hosted · بدون واسطه‌های ثالث · لغو اشتراک با یک کلیک

این چیست؟

Ollama از GPU استفاده نمی‌کند؟ رفعش روی Linux و Windows و WSL

این نوشته‌ها را می‌پسندید؟ این همان کاری است که برای زندگی از آن درمی‌آورم. استخدامم کنید