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

فایل .gitignore

توی این درس یاد می‌گیری چطور جلوی ورود فایل‌های اضافی و حساس به مخزن را بگیری: فایل .gitignore و الگوهایش (*.log، node_modules/، /build، !استثنا)، چرا node_modules و فایل‌های .env هرگز نباید commit شوند، چطور با git check-ignore -v بفهمی کدام قاعده یک فایل را نادیده گرفته، چطور فایلی را که قبلاً commit شده با git rm --cached از دنبال‌شدن دربیاوری، و ignore محلی و سراسری. تمرین: یک .gitignore کامل برای یک پروژه‌ی Node بنویس.

مسئله: چیزهایی که نباید وارد تاریخچه شوند

Section titled “مسئله: چیزهایی که نباید وارد تاریخچه شوند”

وقتی git add . می‌زنی، Git هر چه در پوشه هست را می‌بیند: پوشه‌ی node_modules با ده‌ها هزار فایل، خروجی build، فایل‌های لاگ، فایل‌های موقت ویرایشگر (.DS_Store، .idea/)، و بدتر از همه فایل‌های رمز (.env). این‌ها سه دردسر دارند:

  • حجم و شلوغی: مخزنی که صدها مگابایت چیزِ قابل‌بازسازی دارد، کند و پر از diff بی‌معنی می‌شود.
  • تداخل: فایل‌های ماشین‌محور (مثل مسیرهای شخصی در تنظیمات ویرایشگر) برای هر همکار فرق دارند و دائماً conflict می‌سازند.
  • امنیت: هر چه commit شود برای همیشه در تاریخچه می‌ماند. اگر رمز دیتابیس در یک commit رفت، پاک‌کردن فایل در commit بعدی کافی نیست.

تشبیه: فهرست «در چمدان نگذار»

Section titled “تشبیه: فهرست «در چمدان نگذار»”

قبل از سفر یک فهرست ممنوعه‌ها داری (مایعات زیاد، چاقو…) که اسکنر فرودگاه رد می‌کند. .gitignore همان فهرست است: به Git می‌گوید «این‌ها را حتی اگر در پوشه هستند، نبین و addشان نکن». فهرست را در خود مخزن نگه می‌داری و commit می‌کنی، تا همه‌ی هم‌تیمی‌ها همان قوانین را داشته باشند.

یک «پروژه‌ی Node» ساختگی می‌سازم که همه‌ی انواع فایل را دارد: کد، وابستگی‌ها، خروجی build، لاگ و رمز:

Terminal window
mkdir -p ~/gitlab/webapp && cd ~/gitlab/webapp && git init -q
mkdir -p src node_modules/express dist logs
echo "console.log('app')" > src/app.js
echo '{"name":"webapp"}' > package.json
echo "module.exports = {}" > node_modules/express/index.js
echo "bundle" > dist/app.min.js
echo "request failed" > logs/error.log
echo "DB_PASSWORD=super-secret-123" > .env
echo "mac junk" > .DS_Store
git status -s
خروجی
?? .DS_Store
?? .env
?? dist/
?? logs/
?? node_modules/
?? package.json
?? src/

مثال ۱: بدون .gitignore، همه‌چیز در خطر است

Section titled “مثال ۱: بدون .gitignore، همه‌چیز در خطر است”

وضعیت بالا را ببین: .env، node_modules/، dist/، logs/ و .DS_Store همه ?? (untracked) هستند و با اولین git add . وارد می‌شوند. حالا .gitignore را می‌نویسم:

Terminal window
cd ~/gitlab/webapp
cat > .gitignore <<'EOF'
# وابستگی‌ها
node_modules/
# خروجی build
dist/
# لاگ‌ها
*.log
# رمزها و تنظیمات محلی
.env
# فایل‌های سیستم‌عامل
.DS_Store
EOF
echo "--- وضعیت بعد از .gitignore:"
git status -s
echo "--- فایل‌های نادیده‌گرفته‌شده (--ignored):"
git status -s --ignored
خروجی
--- وضعیت بعد از .gitignore:
?? .gitignore
?? package.json
?? src/
--- فایل‌های نادیده‌گرفته‌شده (--ignored):
?? .gitignore
?? package.json
?? src/
!! .DS_Store
!! .env
!! dist/
!! logs/
!! node_modules/

حالا فقط .gitignore، package.json و src/ دیده می‌شوند. git status -s --ignored فهرست «نادیده‌ها» را با !! نشان می‌دهد؛ برای اطمینان از اینکه قاعده کار می‌کند عالی است. (خطی که با # شروع شود توضیح است و خط خالی نادیده گرفته می‌شود.) .gitignore را هم commit می‌کنم تا همه آن را داشته باشند:

Terminal window
cd ~/gitlab/webapp
git add .gitignore package.json src
git commit -qm "Add app source and .gitignore"
git status -s --ignored
git log --stat --format='%h %s'
خروجی
!! .DS_Store
!! .env
!! dist/
!! logs/
!! node_modules/
fa0678e Add app source and .gitignore
.gitignore | 14 ++++++++++++++
package.json | 1 +
src/app.js | 1 +
3 files changed, 16 insertions(+)

قاعده‌ها را از ساده به پیچیده ببین. هر خط یک الگو (pattern) است:

الگو معنی نمونه‌هایی که نادیده می‌گیرد
*.log هر فایل با پسوند .log در هر عمقی error.log، logs/a/b.log
node_modules/ پوشه‌ای با این نام (/ آخر = فقط پوشه) node_modules/…، packages/x/node_modules/…
/build فقط build در ریشه‌ی مخزن (/ اول = لنگر) build/؛ ولی src/build/ نه
build هر فایل یا پوشه‌ای با این نام در هر عمقی build، src/build
doc/*.pdf .pdf های مستقیم داخل doc doc/a.pdf؛ ولی doc/x/b.pdf نه
doc/**/*.pdf .pdf در هر عمقِ زیر doc doc/a.pdf، doc/x/y/b.pdf
temp? ? = دقیقاً یک نویسه temp1، tempX
*.[oa] یکی از نویسه‌های داخل کروشه x.o، x.a
!خاص.log استثنا: این را نادیده نگیر
\#file \ نویسه‌ی ویژه را عادی می‌کند فایلی به نام #file

برای دیدن اینکه کدام قاعده کدام فایل را گرفته، git check-ignore -v (v = verbose) را بزن. خروجی: فایل-قاعده:شماره‌ی-خط:الگو مسیر:

Terminal window
cd ~/gitlab/webapp
git check-ignore -v node_modules/express/index.js logs/error.log .env dist/app.min.js .DS_Store
echo "--- فایلی که نادیده گرفته نمی‌شود (کد 1 یعنی هیچ قاعده‌ای نگرفتش):"
git check-ignore -v src/app.js; echo "exit code: $?"
خروجی
.gitignore:2:node_modules/ node_modules/express/index.js
.gitignore:8:*.log logs/error.log
.gitignore:11:.env .env
.gitignore:5:dist/ dist/app.min.js
.gitignore:14:.DS_Store .DS_Store
--- فایلی که نادیده گرفته نمی‌شود (کد 1 یعنی هیچ قاعده‌ای نگرفتش):
exit code: 1

سه جزء دارد: فایل قاعده (.gitignore)، شماره‌ی خط (:2:) و الگوی منطبق، و آخر مسیر. هر وقت یک فایل «ناگهان» نادیده گرفته می‌شود یا نمی‌شود، check-ignore -v اولین دستوری است که باید بزنی.

مثال ۳: استثنا با ! و دام «پوشه‌ی ignore‌شده»

Section titled “مثال ۳: استثنا با ! و دام «پوشه‌ی ignore‌شده»”

گاهی می‌خواهی یک پوشه را نادیده بگیری ولی یک فایل خاصش را نگه داری. با ! استثنا می‌دهی. ولی یک دام معروف هست. اول شکل غلط:

Terminal window
mkdir -p ~/gitlab/trap && cd ~/gitlab/trap && git init -q
mkdir build && echo a > build/out.js && echo "keep me" > build/README.md
printf 'build/\n!build/README.md\n' > .gitignore
echo "--- شکل غلط (build/ و بعد !build/README.md):"
git status -s -uall
git check-ignore -v build/README.md
خروجی
--- شکل غلط (build/ و بعد !build/README.md):
?? .gitignore
.gitignore:1:build/ build/README.md

استثنا کار نکرد: README.md هنوز نادیده گرفته می‌شود. دلیل: وقتی خود پوشه ignore باشد، Git اصلاً داخلش را نمی‌خواند، پس به استثنا نمی‌رسد. شکل درست این است که محتوای پوشه را ignore کنی، نه خود پوشه را:

Terminal window
cd ~/gitlab/trap
printf 'build/*\n!build/README.md\n' > .gitignore
echo "--- شکل درست (build/* و بعد !build/README.md):"
git status -s -uall
خروجی
--- شکل درست (build/* و بعد !build/README.md):
?? .gitignore
?? build/README.md

حالا build/README.md دیده می‌شود و build/out.js هنوز نادیده است. قاعده: نمی‌شود فایلی را از پوشه‌ی ignore‌شده بازگرداند؛ به‌جای پوشه، محتوایش را با * ignore کن.

مثال ۴: فایلی که از قبل commit شده بود

Section titled “مثال ۴: فایلی که از قبل commit شده بود”

.gitignore فقط روی فایل‌های untracked اثر دارد. اگر فایلی را قبلاً commit کرده‌ای، بعداً هم که در .gitignore بنویسی، Git هنوز دنبالش می‌کند:

Terminal window
mkdir -p ~/gitlab/late && cd ~/gitlab/late && git init -q
echo "API_KEY=abc123" > config.local
echo "code" > app.js
git add . && git commit -qm "Initial commit (oops, config.local is in)"
echo "config.local" > .gitignore
echo "API_KEY=changed" > config.local
echo "--- config.local را به .gitignore اضافه کردم، ولی:"
git status -s
echo "--- هنوز دنبال می‌شود؟"
git ls-files
خروجی
--- config.local را به .gitignore اضافه کردم، ولی:
M config.local
?? .gitignore
--- هنوز دنبال می‌شود؟
app.js
config.local

config.local با اینکه در .gitignore است، هنوز tracked است ( M یعنی ویرایش‌شده و Git تغییرش را می‌بیند). راه‌حل: git rm --cached فایل را فقط از ردیابی Git درمی‌آورد و روی دیسک نگه می‌دارد:

Terminal window
cd ~/gitlab/late
git rm --cached config.local
git add .gitignore
git commit -qm "Stop tracking config.local"
echo "--- بعد:"
git status -s
git ls-files
echo "--- فایل روی دیسک هست؟"
cat config.local
خروجی
rm 'config.local'
--- بعد:
.gitignore
app.js
--- فایل روی دیسک هست؟
API_KEY=changed

rm --cached فقط ردیابی را برمی‌دارد (بدون --cached، فایل را از دیسک هم پاک می‌کرد). ولی هشدار بزرگ: نسخه‌ی قدیمی فایل (با API_KEY=abc123) هنوز در تاریخچه‌ی commit اول هست. اگر آن یک رمز واقعی بود، rm --cached کافی نیست: باید رمز را عوض کنی (مثال ۸).

مثال ۵: ignore محلی و سراسری

Section titled “مثال ۵: ignore محلی و سراسری”

سه جا برای قاعده‌ها هست. هر کدام برای یک هدف:

سه جا برای قاعده‌های ignore: .gitignore (در مخزن، مشترک بین همه)، .git/info/exclude (فقط روی این کلون، commit نمی‌شود) و فایل سراسری کاربر (برای همه‌ی مخزن‌ها). Git هر سه را با هم می‌خواند.
Terminal window
cd ~/gitlab/webapp
echo "scratch-notes.txt" >> .git/info/exclude
mkdir -p ~/.config/git
printf '.DS_Store\n*.swp\n.idea/\n' > ~/.config/git/ignore
echo "یادداشت شخصی" > scratch-notes.txt
echo "tmp" > .file.swp
echo "--- فایل‌های جدید ← نادیده‌اند؟ و توسط کدام فایل قاعده؟"
git check-ignore -v scratch-notes.txt .file.swp .DS_Store
echo "--- وضعیت (چیزی نمی‌ماند):"
git status -s
خروجی
--- فایل‌های جدید ← نادیده‌اند؟ و توسط کدام فایل قاعده؟
.git/info/exclude:7:scratch-notes.txt scratch-notes.txt
/home/ali/.config/git/ignore:2:*.swp .file.swp
.gitignore:14:.DS_Store .DS_Store
--- وضعیت (چیزی نمی‌ماند):
  • .gitignore: قاعده‌های پروژه (node_modules/، dist/، .env): همه‌ی تیم لازم دارند، پس commit می‌شود.
  • .git/info/exclude: قاعده‌های شخصی برای همین مخزن (مثلاً یادداشت‌های خودت)؛ commit نمی‌شود و مثل .gitignore کار می‌کند.
  • فایل سراسری (~/.config/git/ignore، یا هر مسیری که با git config --global core.excludesFile مسیر بدهی): فایل‌های ابزار و سیستم‌عامل تو (.DS_Store مک، *.swp ویم، .idea/). به‌جای اینکه هر پروژه این‌ها را بنویسد، یک بار برای همیشه.

قاعده‌ی خوب: چیزی که به پروژه مربوط است (dist/) در .gitignore؛ چیزی که به ماشین یا ابزار تو مربوط است (.DS_Store) در فایل سراسری.

مثال ۶: پوشه‌ی خالی و .gitkeep

Section titled “مثال ۶: پوشه‌ی خالی و .gitkeep”

Git فقط فایل نگه می‌دارد، نه پوشه. پوشه‌ی خالی اصلاً وارد مخزن نمی‌شود:

Terminal window
mkdir -p ~/gitlab/empty && cd ~/gitlab/empty && git init -q
mkdir uploads
echo "--- پوشه‌ی خالی uploads:"
git status -s; echo "(خالی: Git آن را نمی‌بیند)"
touch uploads/.gitkeep
echo "--- با یک فایل .gitkeep داخلش:"
git status -s -uall
خروجی
--- پوشه‌ی خالی uploads:
(خالی: Git آن را نمی‌بیند)
--- با یک فایل .gitkeep داخلش:
?? uploads/.gitkeep

قرارداد این است که یک فایل خالی با اسم .gitkeep داخل پوشه می‌گذارند (اسم خاصی ندارد؛ هر فایلی کار می‌کند). حالت رایج دیگر: پوشه‌ای که محتوایش ignore است ولی خودش باید در مخزن باشد (uploads/* و !uploads/.gitkeep، همان الگوی مثال ۳).

مثال ۷: یک .gitignore کامل برای پروژه‌ی Node

Section titled “مثال ۷: یک .gitignore کامل برای پروژه‌ی Node”

حالا تمرین اصلی: فایلی برای یک پروژه‌ی Node واقعی. هر بخش دلیلش را دارد:

Terminal window
mkdir -p ~/gitlab/nodeapp && cd ~/gitlab/nodeapp && git init -q
cat > .gitignore <<'EOF'
# وابستگی‌ها (با npm install بازسازی می‌شوند)
node_modules/
# خروجی build و کش
dist/
build/
.cache/
coverage/
# لاگ‌ها
*.log
npm-debug.log*
yarn-error.log*
# رمزها و تنظیمات محلی (ولی نمونه‌اش را نگه دار)
.env
.env.*
!.env.example
# ابزارهای ویرایشگر و سیستم‌عامل (بهتر است در ignore سراسری باشند)
.idea/
.vscode/
.DS_Store
Thumbs.db
EOF
mkdir -p src node_modules/x dist coverage logs .idea
echo a > src/index.js; echo '{}' > package.json; echo '{}' > package-lock.json
echo a > node_modules/x/i.js; echo a > dist/b.js; echo a > coverage/c.html
echo "a" > logs/app.log; echo "npm-debug.log" > npm-debug.log.1
echo "DB_PASSWORD=x" > .env; echo "DB_PASSWORD=" > .env.example; echo "T=1" > .env.local
echo a > .idea/w.xml; echo a > .DS_Store
echo "--- فایل‌هایی که Git می‌بیند:"
git status -s -uall
echo "--- و آنچه نادیده می‌گیرد:"
git status -s --ignored | grep '^!!'
خروجی
--- فایل‌هایی که Git می‌بیند:
?? .env.example
?? .gitignore
?? package-lock.json
?? package.json
?? src/index.js
--- و آنچه نادیده می‌گیرد:
!! .DS_Store
!! .env
!! .env.local
!! .idea/
!! coverage/
!! dist/
!! logs/
!! node_modules/
!! npm-debug.log.1

دقت کن .env.example دیده می‌شود (استثنا با !) و .env و .env.local نادیده‌اند. این الگوی معروفی است: یک فایل نمونه که کلیدها را بدون مقدار نشان می‌دهد در مخزن می‌ماند تا همکارت بداند چه چیزهایی را باید تنظیم کند، و مقدارهای واقعی هیچ‌وقت وارد نمی‌شوند. دو نکته‌ی دیگر: package-lock.json را commit کن (نسخه‌ی دقیق وابستگی‌ها را قفل می‌کند)؛ و node_modules/ را هرگز (با npm install بازسازی می‌شود). برای زبان‌های دیگر، GitHub یک مخزن رسمی از قالب‌های آماده دارد (github/gitignore) که برای شروع خوب است.

مثال ۸: رمز لو رفت، حالا چه؟

Section titled “مثال ۸: رمز لو رفت، حالا چه؟”

اگر رمز یا کلیدی را commit و push کردی، اول باید فرض کنی لو رفته است: همان لحظه رمز/کلید را در سرویس مربوطه عوض (rotate) یا باطل کن. بعد، برای اینکه از تاریخچه‌ی مخزن هم برود، باید تاریخچه را بازنویسی کنی، که یک عملیات سنگین و پرخطر است (کار همه‌ی همکارها را تغییر می‌دهد). ابزارهای رسمی آن git filter-repo و BFG Repo-Cleaner هستند. این‌ها را من اجرا نکرده‌ام (نصب خارجی دارند و روی تاریخچه‌ی مشترک خطرناک‌اند)؛ فقط بدان که راه امن همان اول عوض‌کردن رمز است، نه پنهان‌کردنش:

نمونه (اجرا نشده): پاک‌کردن یک فایل از کل تاریخچه
# ۱) همین الآن رمز را در سرویس مربوطه عوض کن (rotate)
# ۲) بعد، فایل را از تمام commit ها حذف کن (ابزار git-filter-repo باید نصب باشد):
git filter-repo --invert-paths --path .env
# ۳) و تاریخچه‌ی بازنویسی‌شده را به سرور بفرست (همه‌ی همکاران باید دوباره clone کنند):
git push --force-with-lease

پشت پرده: Git قاعده‌ها را چطور می‌خواند؟

Section titled “پشت پرده: Git قاعده‌ها را چطور می‌خواند؟”

Git قاعده‌ها را از چند جا می‌خواند و برای هر فایل آخرین الگوی منطبق برنده است. ترتیب اولویت (از بالا به پایین ضعیف‌تر می‌شود): ۱) قاعده‌های خط فرمان، ۲) .gitignore در همان پوشه‌ی فایل (و پوشه‌های بالاتر؛ نزدیک‌تر برنده است)، ۳) .git/info/exclude، ۴) فایل سراسری (core.excludesFile). هر فایل می‌تواند قاعده‌ی خودش را بر قاعده‌ی پوشه‌ی بالاتر ببرد:

Terminal window
mkdir -p ~/gitlab/nested && cd ~/gitlab/nested && git init -q
mkdir -p docs/drafts
printf '*.tmp\n' > .gitignore
printf '!keep.tmp\n' > docs/.gitignore
touch a.tmp keep.tmp docs/a.tmp docs/keep.tmp
echo "--- قاعده‌ی هر فایل (آخرین منطبق برنده است):"
git check-ignore -v a.tmp keep.tmp docs/a.tmp docs/keep.tmp
echo "--- فایل‌هایی که نادیده نیستند:"
git status -s -uall | grep -v gitignore
خروجی
--- قاعده‌ی هر فایل (آخرین منطبق برنده است):
.gitignore:1:*.tmp a.tmp
.gitignore:1:*.tmp keep.tmp
.gitignore:1:*.tmp docs/a.tmp
docs/.gitignore:1:!keep.tmp docs/keep.tmp
--- فایل‌هایی که نادیده نیستند:
?? docs/keep.tmp

docs/.gitignore فقط برای فایل‌های داخل docs/ قاعده‌ی نزدیک‌تر دارد: docs/keep.tmp استثنا می‌شود ولی keep.tmp در ریشه نه. دو نکته‌ی دیگر: (۱) قاعده‌ها فقط روی فایل‌های untracked اثر دارند (مثال ۴)، و (۲) git add -f فایل می‌تواند عمداً یک فایل ignore‌شده را اضافه کند (کم‌پیش می‌آید).

دستور کار
git status --ignored / -s --ignored فایل‌های نادیده‌گرفته‌شده را هم نشان بده (!!)
git check-ignore -v مسیر… کدام قاعده این مسیر را نادیده می‌گیرد؟
git rm --cached فایل فایل را از ردیابی دربیاور، روی دیسک بماند
git rm -r --cached پوشه همان برای پوشه
git ls-files فایل‌هایی که Git دنبال می‌کند
git add -f فایل افزودن اجباری فایل ignore‌شده
git clean -ndX فهرست فایل‌های ignore‌شده‌ای که clean پاک می‌کرد (-X = فقط ignore‌شده‌ها)
git config --global core.excludesFile مسیر فایل ignore سراسری
جای قاعده commit می‌شود؟ برای
.gitignore بله قاعده‌های پروژه (مشترک)
.git/info/exclude نه قاعده‌ی شخصی همین مخزن
~/.config/git/ignore نه فایل‌های ابزار و سیستم‌عامل خودت

۱) نوشتن در .gitignore بعد از commit شدن فایل

Section titled “۱) نوشتن در .gitignore بعد از commit شدن فایل”

مثال ۴: اثر ندارد. راه‌حل: git rm --cached؛ و اگر رمز بود، عوض‌کردنش.

۲) فراموش‌کردن commit کردن .gitignore

Section titled “۲) فراموش‌کردن commit کردن .gitignore”

اگر .gitignore را commit نکنی، فقط روی کامپیوتر خودت کار می‌کند و همکارت دوباره node_modules را add می‌کند. راه‌حل: .gitignore را زود (قبل از بقیه‌ی فایل‌ها) commit کن.

Terminal window
mkdir -p ~/gitlab/broad && cd ~/gitlab/broad && git init -q
printf 'build\n*.js\n' > .gitignore
mkdir -p src/build && echo a > src/build/x.txt && echo a > app.js && echo a > README.md
echo "--- چه چیزی دیده می‌شود؟"
git status -s -uall
خروجی
--- چه چیزی دیده می‌شود؟
?? .gitignore
?? README.md

*.js همه‌ی فایل‌های جاوااسکریپت را از جمله app.js (کد اصلی!) نادیده گرفت، و build بدون / اول، پوشه‌ی src/build را هم گرفت. راه‌حل: الگوها را دقیق بنویس (/dist/ به‌جای dist، *.min.js به‌جای *.js) و بعد از هر تغییر git status و check-ignore -v را بزن.

۴) توضیح در انتهای همان خط

Section titled “۴) توضیح در انتهای همان خط”
Terminal window
mkdir -p ~/gitlab/comment && cd ~/gitlab/comment && git init -q
printf '*.log # فایل‌های لاگ\n' > .gitignore
echo x > a.log
echo "--- a.log نادیده گرفته می‌شود؟"
git status -s
git check-ignore -v a.log; echo "exit code: $?"
خروجی
--- a.log نادیده گرفته می‌شود؟
?? .gitignore
?? a.log
exit code: 1

# فقط وقتی اول خط باشد توضیح است. اگر بعد از الگو بیاید، همه‌ی آن خط (با فاصله‌ها و توضیح) جزو الگو حساب می‌شود و دیگر با هیچ فایلی نمی‌خواند؛ پس a.log اصلاً ignore نشد (و check-ignore کد خروج ۱ داد). راه‌حل: توضیح را در خط جدا بنویس.

۵) فکر اینکه .gitignore امنیت است

Section titled “۵) فکر اینکه .gitignore امنیت است”

.gitignore فقط ورود تصادفی را می‌گیرد؛ حفاظت واقعی از رمز نیست (git add -f یا یک cp اشتباه آن را دور می‌زند). رمزها را باید از کد جدا نگه داری (متغیر محیطی یا یک مدیر رمز) و ابزارهای اسکن رمز (مثل pre-commit hook) در تیم به‌کار ببری.

✎ تمرینآسان

فایل‌های لاگ (*.log) را نادیده بگیر و با git check-ignore -v ثابت کن کدام قاعده یک فایل app.log را گرفته.

دیدن جواب
Terminal window
mkdir -p ~/gitlab/ex1 && cd ~/gitlab/ex1 && git init -q
echo "*.log" > .gitignore
echo x > app.log && echo y > main.py
git status -s
git check-ignore -v app.log
خروجی
?? .gitignore
?? main.py
.gitignore:1:*.log app.log
✎ تمرینمتوسط

تمرین اصلی: یک .gitignore کامل برای یک پروژه‌ی Node بنویس (وابستگی‌ها، خروجی build، لاگ‌ها، .env با استثنای .env.example، فایل‌های ویرایشگر). یک پروژه‌ی ساختگی با همه‌ی این فایل‌ها بساز، git add -A و commit بزن و با git ls-files ثابت کن فقط فایل‌های «واقعی» وارد شدند.

دیدن جواب
Terminal window
mkdir -p ~/gitlab/ex2 && cd ~/gitlab/ex2 && git init -q
cat > .gitignore <<'EOF'
node_modules/
dist/
coverage/
*.log
.env
.env.*
!.env.example
.idea/
.DS_Store
EOF
mkdir -p src node_modules/a dist coverage .idea
echo a > src/main.js; echo '{}' > package.json; echo '{}' > package-lock.json
echo a > node_modules/a/i.js; echo a > dist/o.js; echo a > coverage/c.html; echo a > .idea/w
echo x > debug.log; echo S=1 > .env; echo S= > .env.example; echo a > .DS_Store
git add -A && git commit -qm "Add project"
echo "--- فایل‌های وارد‌شده به مخزن:"
git ls-files
خروجی
--- فایل‌های وارد‌شده به مخزن:
.env.example
.gitignore
package-lock.json
package.json
src/main.js
✎ تمرینسخت

در پروژه‌ای settings.local از اول commit شده و می‌خواهی دیگر دنبال نشود، بدون پاک‌شدن از کامپیوتر خودت. بعد ثابت کن نسخه‌ی قدیمی همچنان در تاریخچه هست (و توضیح بده چرا این برای رمز خطرناک است).

دیدن جواب
Terminal window
mkdir -p ~/gitlab/ex3 && cd ~/gitlab/ex3 && git init -q
echo "TOKEN=old-secret" > settings.local && echo code > app.py
git add . && git commit -qm "Initial"
echo "settings.local" > .gitignore
git rm -q --cached settings.local
git add .gitignore && git commit -qm "Stop tracking settings.local"
echo "--- فایل هنوز روی دیسک است:"; cat settings.local
echo "--- ولی دیگر tracked نیست:"; git ls-files
echo "--- و نسخه‌ی قدیمی در commit اول هنوز هست:"
git show HEAD~1:settings.local
خروجی
--- فایل هنوز روی دیسک است:
TOKEN=old-secret
--- ولی دیگر tracked نیست:
.gitignore
app.py
--- و نسخه‌ی قدیمی در commit اول هنوز هست:
TOKEN=old-secret
⚡ بررسی سریع

فایل .env را بعد از commit شدن به .gitignore اضافه کردی. چه می‌شود؟

؟ آزمونک
  1. الگوی «node_modules/» چه چیزی را نادیده می‌گیرد؟

  2. کدام دستور نشان می‌دهد چرا یک فایل ignore شده است؟

  3. چرا «build/» و بعد «!build/README.md» کار نمی‌کند؟

  4. git rm --cached فایل چه می‌کند؟

  5. چرا .DS_Store یا .idea/ را بهتر است در ignore سراسری بگذاری؟

  • .gitignore به Git می‌گوید کدام فایل‌های untracked را نبیند: node_modules/، dist/، *.log، .env. آن را commit کن و زود بنویس.
  • الگوها: *.log، dir/ (فقط پوشه)، /build (فقط ریشه)، doc/**/*.pdf، !استثنا. # توضیح است.
  • git check-ignore -v مسیر کدام قاعده را نشان می‌دهد؛ git status -s --ignored فهرست نادیده‌ها (!!).
  • پوشه‌ی ignore‌شده را نمی‌شود با ! باز کرد؛ از build/* و !build/README.md استفاده کن.
  • فایل قبلاً commit شده را با git rm --cached فایل (و commit) از ردیابی دربیاور. ولی نسخه‌ی قدیمی در تاریخچه می‌ماند؛ برای رمز، اول عوض‌کردن رمز.
  • سه جا: .gitignore (مشترک)، .git/info/exclude (شخصی)، ~/.config/git/ignore (ابزار و سیستم‌عامل تو).
  • Git پوشه‌ی خالی نگه نمی‌دارد: .gitkeep. .env.example را نگه دار و .env را ignore کن.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
echo "node_modules/" >> .gitignoreیک الگو اضافه کن
git check-ignore -v مسیرکدام قاعده این را نادیده می‌گیرد؟
git status -s --ignoredنادیده‌ها را هم نشان بده
git rm --cached فایلاز ردیابی دربیاور، روی دیسک بماند
git rm -r --cached پوشههمان برای پوشه
build/* و !build/README.mdاستثنا داخل پوشه‌ی ignore‌شده
.env و !.env.exampleرمز ignore، نمونه‌اش tracked
touch پوشه/.gitkeepنگه‌داشتن پوشه‌ی خالی
git config --global core.excludesFile ~/.gitignore_globalignore سراسری
git add -f فایلافزودن اجباری فایل ignore‌شده