.gitignore ও git config
এই পাঠে যা শিখবেন
- .gitignore কী এবং এটি ব্যবহার করার দুটি বাস্তব, গুরুত্বপূর্ণ কারণ
- .gitignore-এর প্যাটার্ন সিনট্যাক্স — এক্সাক্ট ম্যাচ, ওয়াইল্ডকার্ড, ডিরেক্টরি প্যাটার্ন, ব্যতিক্রম
- git config-এর তিনটি স্কোপ এবং তাদের override অগ্রাধিকার ক্রম
- নিজের হাতে লেখা একটি .gitignore প্যাটার্ন-ম্যাচার L31-এর
git_status-এর সাথে যুক্ত করে যাচাই
১ · .gitignore কেন দরকার
.gitignore.gitignoreরিপোজিটরির রুটে রাখা একটি বিশেষ ফাইল, যেখানে প্যাটার্ন লিখে বলে দেওয়া হয় কোন ফাইল/ফোল্ডার git কখনো ট্র্যাক করবে না বা git status-এ untracked হিসেবেও দেখাবে না।
হলো একটি বিশেষ ফাইল যেখানে এমন প্যাটার্নের তালিকা থাকে যেগুলোর সাথে মিলে যাওয়া যেকোনো ফাইল/ফোল্ডার git
কখনো ট্র্যাক করবে না। এর দুটি জেনুইন, বাস্তব গুরুত্ব আছে। প্রথমত, এটি git status-এর আউটপুট
পরিষ্কার রাখে — বিল্ড আর্টিফ্যাক্ট, ডিপেন্ডেন্সি ফোল্ডার, বা এডিটরের টেম্পোরারি ফাইল বারবার "untracked"
হিসেবে দেখাতে থাকলে আসল, প্রাসঙ্গিক পরিবর্তনগুলো খুঁজে পাওয়া কঠিন হয়ে যায়। দ্বিতীয়ত এবং সবচেয়ে
গুরুত্বপূর্ণ — এটি ভুলবশত সংবেদনশীল ফাইল (যেমন API কী বা পাসওয়ার্ড ধারণকারী একটি .env ফাইল)
কমিট হয়ে যাওয়া থেকে বাঁচায় — একটি বাস্তব, সাধারণ ও গুরুতর সিকিউরিটি ভুল, যা .gitignore প্রতিরোধ করে।
২ · প্যাটার্ন সিনট্যাক্স
secrets.env — ঠিক এই নামের ফাইলটিকেই ম্যাচ করে, আর কিছু নয়।*.log — * যেকোনো টেক্সটকে রিপ্রেজেন্ট করে, তাই .log-এ শেষ হওয়া যেকোনো ফাইল ম্যাচ হয়।node_modules/ — শেষে / থাকলে পুরো ডিরেক্টরি (এবং ভেতরের সবকিছু) ম্যাচ হয়।!important.log — ! দিয়ে শুরু হলে সেটি আগের কোনো বিস্তৃত প্যাটার্নের একটি নির্দিষ্ট ব্যতিক্রম তৈরি করে।৩ · git config — তিনটি স্কোপ
git configgit configgit-এর কনফিগারেশন সেট করার কমান্ড — user.name, user.email-এর মতো ভ্যালু নির্দিষ্ট স্কোপে (local/global/system) সেভ করে।
দিয়ে git-এর আচরণ কনফিগার করা হয় — যেমন প্রতিটি কমিটে যুক্ত হওয়া user.name/user.email
(সরাসরি L31-এর git log-এ দেখা author ফিল্ডের সাথে সম্পর্কিত)। এই কনফিগারেশন তিনটি ভিন্ন স্কোপে
সেট করা যায়: --local (শুধু বর্তমান রিপোজিটরিতে প্রযোজ্য), --global (এই ইউজারের
সব রিপোজিটরিতে প্রযোজ্য), এবং --system (এই মেশিনের সব ইউজারের জন্য প্রযোজ্য)। যখন একাধিক
স্কোপে একই সেটিং ভিন্নভাবে থাকে, local জেতে global-এর ওপর, global জেতে system-এর ওপর —
অর্থাৎ সবচেয়ে নির্দিষ্ট স্কোপ সবসময় জেতে।
একটি .env ফাইল একবার কমিট হয়ে গেলে এবং সেই কমিট কোথাও পুশ/শেয়ার হয়ে গেলে, শুধু
.gitignore-এ পরে যোগ করলেই সেই পুরনো কমিট থেকে ফাইলটি মুছে যায় না — history-তে এখনও থেকে
যায়। তাই .gitignore সবচেয়ে কার্যকর যখন এটি শুরু থেকেই সেট করা থাকে,
কোনো সিক্রেট ফাইল একবারও কমিট হওয়ার আগে।
৪ · কোড দিয়ে যাচাই
নিচের কোড সেলটি বাস্তব git বাইনারি চালায় না — এই ব্রাউজার-স্যান্ডবক্সে কোনো real git
ইনস্টল নেই — বরং এটি L30-L31-এর Repo মডেল (working_directory, staging_area,
commits, branches, head) রিইউজ করে .gitignore-এর আচরণ git-এর
আসল অ্যালগরিদম যেভাবে কাজ করে ঠিক সেভাবেই বিশ্বস্তভাবে সিমুলেট করছে। আমরা fnmatch/re
ব্যবহার না করে একদম শুরু থেকে প্যাটার্ন-ম্যাচিং লিখছি, তারপর সেটিকে git_status-এর সাথে যুক্ত
করছি — দেখানোর জন্য যে ইতিমধ্যে ট্র্যাক হওয়া, পরিবর্তিত app.py .gitignore-এ যাই থাকুক না
কেন সবসময় দেখা যাবে, কিন্তু কখনো-ট্র্যাক-না-হওয়া নতুন ফাইলগুলো .gitignore ম্যাচ করলে সম্পূর্ণ অদৃশ্য
হয়ে যাবে।
# git-এর থ্রি-ট্রি মডেল (L30-L31 রিইউজ, হুবহু একই) + .gitignore -- এই স্যান্ডবক্সে real git নেই,
# তাই এটি git-এর অভ্যন্তরীণ আচরণের একটি বিশ্বস্ত সিমুলেশন, real git চালানো নয়।
def make_repo():
return {"working_directory": {}, "staging_area": {}, "commits": {}, "head": None}
def git_add(repo, filename):
repo["staging_area"][filename] = repo["working_directory"][filename]
def git_commit(repo, message):
commit_hash = f"c{len(repo['commits']) + 1}"
snapshot = dict(repo["staging_area"])
repo["commits"][commit_hash] = {"message": message, "snapshot": snapshot, "parent": repo["head"]}
repo["head"] = commit_hash
repo["staging_area"] = {} # বাস্তব git-এর মতোই কমিটের পর staging area খালি হয়ে যায় (L30 রিইউজ)
return commit_hash
# ---- .gitignore প্যাটার্ন ম্যাচিং -- একদম শুরু থেকে, fnmatch/re ছাড়াই ----
def matches_gitignore_pattern(filename, pattern):
if pattern.endswith("/"):
dirname = pattern[:-1]
return filename == dirname or filename.startswith(dirname + "/")
if pattern.startswith("*"):
suffix = pattern[1:]
return filename.endswith(suffix)
return filename == pattern
def is_ignored(filename, gitignore_patterns):
for pattern in gitignore_patterns:
if matches_gitignore_pattern(filename, pattern):
return True
return False
# ---- git status, .gitignore সচেতন (L31-এর git_status-এর হুবহু একই কাঠামো, gitignore যোগ করে) ----
def git_status(repo, gitignore_patterns=None):
gitignore_patterns = gitignore_patterns or []
committed = repo["commits"][repo["head"]]["snapshot"] if repo["head"] else {}
all_files = set(repo["working_directory"]) | set(repo["staging_area"]) | set(committed)
status = {}
for f in sorted(all_files):
wd = repo["working_directory"].get(f)
staged = repo["staging_area"].get(f)
head = committed.get(f)
if f not in repo["staging_area"] and f not in committed:
if is_ignored(f, gitignore_patterns):
continue # গিটইগনোর করা + কখনো ট্র্যাক হয়নি -- status-এ একেবারেই দেখানো হয় না
status[f] = "untracked"
elif wd != staged:
status[f] = "modified (not staged)"
elif staged != head:
status[f] = "staged"
else:
status[f] = "no changes"
return status
# ---- ডেমো ----
repo = make_repo()
repo["working_directory"]["app.py"] = "print('hello world')"
git_add(repo, "app.py")
git_commit(repo, "প্রাথমিক কমিট: app.py যোগ করা হলো")
# app.py-তে working directory-তে আরেকটি পরিবর্তন হলো, কিন্তু staged হয়নি
repo["working_directory"]["app.py"] = "print('hello world!!')"
# তিনটি নতুন ফাইল, কখনো git add করা হয়নি
repo["working_directory"]["debug.log"] = "2026-08-02 ডিবাগ লগ..."
repo["working_directory"][".env"] = "API_KEY=super-secret-123"
repo["working_directory"]["node_modules/react.js"] = "// third-party bundle"
gitignore_patterns = ["*.log", "node_modules/", ".env"]
print("--- .gitignore প্যাটার্ন পরীক্ষা ---")
for f in ["debug.log", ".env", "node_modules/react.js", "app.py"]:
print(f" {f:26s} ignored? {is_ignored(f, gitignore_patterns)}")
print("\n--- git status (.gitignore ছাড়া) ---")
for f, s in git_status(repo).items():
print(f" {f}: {s}")
print("\n--- git status (.gitignore সহ) ---")
for f, s in git_status(repo, gitignore_patterns).items():
print(f" {f}: {s}")
debug.log, .env, ও
node_modules/react.js তিনটিই "untracked" হিসেবে দেখায়, আর app.py দেখায়
"modified (not staged)"। .gitignore যোগ করার পর সেই তিনটি ফাইল status থেকে সম্পূর্ণ অদৃশ্য
হয়ে যায় — কিন্তু app.py-র status অপরিবর্তিত থেকে যায়, কারণ সেটি ইতিমধ্যে ট্র্যাক হওয়া
একটি ফাইল, আর .gitignore শুধু কখনো-ট্র্যাক-না-হওয়া নতুন ফাইলের ওপরই কাজ করে।
.gitignore শুধু "নতুন, এখনো-না-চেনা" ফাইলগুলোর ওপর কাজ করে — ইতিমধ্যে ট্র্যাক হওয়া কোনো ফাইলের ওপর এর কোনো প্রভাব নেই। আর git config-এর তিন-স্কোপ সিস্টেম মনে রাখলে বোঝা সহজ হয় কেন কখনো কখনো একটি কমিটে অপ্রত্যাশিত author দেখা যায় — কোনো নির্দিষ্ট রিপোজিটরিতে local config global-কে override করছে কিনা, সেটাই প্রথম যাচাই করার জায়গা।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
কেন .gitignore-এ কোনো প্যাটার্ন যোগ করলেও ইতিমধ্যে কমিট হওয়া একটি ফাইল git status থেকে অদৃশ্য হয় না?
কারণ আমাদের git_status ফাংশনে is_ignored চেকটি শুধু সেই শাখাতেই কল হয় যেখানে
একটি ফাইল না staging_area-তে আছে, না কোনো commit-এর snapshot-এ আছে — অর্থাৎ ফাইলটি
সম্পূর্ণ নতুন, git এখনো কখনো তার সম্পর্কে জানেনি। একবার ফাইলটি staged বা কমিট হয়ে গেলে, git তাকে
ইতিমধ্যে ট্র্যাক করছে — .gitignore শুধু "না-দেখা" ফাইলকে ইগনোর করে, ট্র্যাক হওয়া ফাইলকে "আনট্র্যাক" করে
না।
প্র ০২
*.log প্যাটার্নটি আমাদের matches_gitignore_pattern-এ "app.log" আর "server/error.log"-এর ক্ষেত্রে কীভাবে কাজ করবে?
দুটোতেই ম্যাচ করবে — কারণ আমাদের লজিক শুধু filename.endswith(".log") চেক করে, ফাইলটি কোন
ডিরেক্টরিতে আছে সেটা বিবেচনা করে না। "server/error.log".endswith(".log") সত্য, তাই
পাথের যেকোনো অংশে থাকুক, .log-এ শেষ হওয়া যেকোনো ফাইল এই প্যাটার্নে ধরা পড়বে — ঠিক আসল
git-এর সহজ, লিডিং-স্ল্যাশ-ছাড়া wildcard প্যাটার্নের আচরণের মতোই।
প্র ০৩
--local ও --global git config-এর মধ্যে conflict হলে কোনটা জেতে, এবং এই ডিজাইনটা কেন যৌক্তিক?
--local জেতে। এটি যৌক্তিক কারণ একজন ডেভেলপারের ব্যক্তিগত রিপোতে হয়তো
user.email = personal@example.com global-এ সেট আছে, কিন্তু একটি নির্দিষ্ট অফিস প্রজেক্টের
রিপোতে সেই প্রজেক্টের অফিসিয়াল ইমেইল ব্যবহার করা দরকার — সবচেয়ে নির্দিষ্ট স্কোপ (এই একটি রিপো) সবসময়
বেশি প্রাসঙ্গিক তথ্য বহন করে, তাই বেশি সাধারণ স্কোপকে (global, system) override করাটাই সঠিক ডিজাইন।
অনুশীলন
-
চিন্তা করুন:
gitignore_patterns-এ"!important.log"-এর মতো একটি ব্যতিক্রম প্যাটার্ন সাপোর্ট করতে হলেis_ignoredফাংশনটি ঠিক কীভাবে বদলাতে হবে?বর্তমান
is_ignoredপ্রথম ম্যাচ পেলেইTrueরিটার্ন করে দেয়, প্যাটার্নের ক্রম বিবেচনা করে না। ব্যতিক্রম সাপোর্ট করতে হলে সবগুলো প্যাটার্ন ক্রমানুসারে (তালিকার শেষ পর্যন্ত) যাচাই করতে হবে, এবং সবশেষে যে প্যাটার্নটি ম্যাচ করে (তা ইগনোর করার প্যাটার্ন হোক বা!-দিয়ে-শুরু ব্যতিক্রম) সেটিই চূড়ান্ত সিদ্ধান্ত নেবে — যদি শেষ ম্যাচটি!-দিয়ে শুরু হয়, ফাইলটি ইগনোর করা হবে না, তা আগে অন্য কোনো broader প্যাটার্নে ম্যাচ করলেও। -
পরীক্ষা করুন: কোড সেলে
gitignore_patterns-এ"app.py"(এক্সাক্ট নাম) যোগ করে Run চাপুন —app.py-র status-এ কী পরিবর্তন আসে?কোনো পরিবর্তন আসবে না —
app.pyএখনও "modified (not staged)" হিসেবেই দেখাবে। এটি প্র ০১-এর উত্তরটিই আবার নিশ্চিত করে: যেহেতুapp.pyইতিমধ্যে কমিট হয়ে আছে (staging_area ও commit history উভয়েই তার এন্ট্রি আছে),is_ignoredচেকটি তার জন্য কখনোই কল হয় না — .gitignore-এ থাকুক বা না থাকুক, তা এই ফাইলের status-কে প্রভাবিত করে না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — পরিবর্তন পূর্বাবস্থায় ফেরানো (restore, reset, revert) — খুব শীঘ্রই।
-
L31 · git status, diff ও log পূর্বশর্ত
এই পাঠের
git_statusফাংশনটিই এখানে .gitignore-সচেতন সংস্করণে সম্প্রসারিত হলো। - সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps ও আরও অনেক কোর্স — সব এক জায়গায়।