git status, diff ও log
এই পাঠে যা শিখবেন
git statusঠিক কীভাবে তিনটি ট্রি তুলনা করে প্রতিটি ফাইলকে ক্যাটাগরাইজ করেgit diff-এর দুটি ভিন্ন মোড — unstaged বনাম--staged— এবং তাদের নির্দিষ্ট পার্থক্যgit logকীভাবে parent চেইন অনুসরণ করে ইতিহাস দেখায়- L30-এর
Repoমডেল সম্প্রসারিত করে বাস্তবgit_statusওgit_diffফাংশন লেখা
১ · git status — এই মুহূর্তে আমার অবস্থা কী?
L30-এর থ্রি-ট্রি মডেল এখন আমরা দৈনন্দিন ব্যবহারে কাজে লাগাব। git statusgit statusতিনটি ট্রি — Working Directory, Staging Area, ও সর্বশেষ কমিট — তুলনা করে প্রতিটি ফাইলের বর্তমান অবস্থা রিপোর্ট করে। হলো তিনটি ট্রির মধ্যে বর্তমান সম্পর্ক রিপোর্ট করার কমান্ড — কোন ফাইল Working Directory-তে বদলেছে কিন্তু এখনও stage হয়নি, কোনটি staged ও কমিট করার জন্য প্রস্তুত, আর কোনটি "untracked" (একদম নতুন ফাইল, যা git এখনও চেনেই না)। এটি genuinely সবচেয়ে বেশি চালানো git কমান্ড — কারণ এটি "আমার এই মুহূর্তের পরিস্থিতি কী?" প্রশ্নের সরাসরি উত্তর দেয়।
২ · git diff — ঠিক কী বদলেছে
git diffgit diffলাইন-বাই-লাইন সঠিক পার্থক্য দেখায় — ডিফল্টে Working Directory বনাম Staging Area, --staged দিলে Staging Area বনাম সর্বশেষ কমিট।
হলো সঠিক, লাইন-বাই-লাইন পার্থক্য দেখানোর কমান্ড — এখানে dual-mode আচরণটি স্পষ্টভাবে জানা জরুরি, এটি একটি
সাধারণ বিভ্রান্তির জায়গা:
git diff (ডিফল্ট)Working Directory বনাম Staging Area — অর্থাৎ unstaged পরিবর্তন দেখায়।
git diff --stagedStaging Area বনাম সর্বশেষ কমিট — অর্থাৎ staged, কমিট হওয়ার অপেক্ষায় থাকা পরিবর্তন দেখায়।
৩ · git log — কমিট-ইতিহাস
git loggit logparent পয়েন্টার ধরে পেছনের দিকে হেঁটে কমিট-ইতিহাস দেখায় — সাধারণত প্রতিটি কমিটের hash, author, date, ও message দেখিয়ে।
হলো কমিট-ইতিহাস দেখানোর কমান্ড — প্রতিটি কমিটের parent পয়েন্টার ধরে পেছনের দিকে হেঁটে (L01-এর
সেই সরলীকৃত প্রিভিউ মনে আছে? এটি এখন সেই একই ধারণার real, নির্ভুল ফরমালাইজেশন), সাধারণত প্রতিটি কমিটের hash,
লেখক, তারিখ, ও মেসেজ দেখিয়ে।
নিচের কোড সেলে L30-এর ঠিক একই Repo মডেল (একই make_repo, git_add,
git_commit) সম্প্রসারিত করে বাস্তব git_status, git_diff, ও
git_log ফাংশন লেখা হয়েছে।
# L30-এর ঠিক একই থ্রি-ট্রি Repo মডেল সম্প্রসারিত করা হচ্ছে -- বাস্তব git বাইনারি নেই,
# তাই status/diff/log নিজেরাই তিনটি dict তুলনা করে বাস্তবায়ন করা হলো।
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"] = {}
return commit_hash
def git_status(repo):
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:
status[f] = "untracked"
elif wd != staged:
status[f] = "modified (not staged)"
elif staged != head:
status[f] = "staged"
else:
status[f] = "no changes"
return status
def git_diff(repo, filename, staged=False):
committed = repo["commits"][repo["head"]]["snapshot"] if repo["head"] else {}
if staged:
old = committed.get(filename, "")
new = repo["staging_area"].get(filename, "")
else:
old = repo["staging_area"].get(filename, committed.get(filename, ""))
new = repo["working_directory"].get(filename, "")
old_lines, new_lines = old.split("\n"), new.split("\n")
added = [l for l in new_lines if l not in old_lines]
removed = [l for l in old_lines if l not in new_lines]
return {"added": added, "removed": removed}
def git_log(repo):
entries = []
current = repo["head"]
while current:
c = repo["commits"][current]
entries.append((current, c["message"]))
current = c["parent"]
return entries
# --- একটি বাস্তব multi-file পরিস্থিতি তৈরি করা হচ্ছে ---
repo = make_repo()
repo["working_directory"]["staged.txt"] = "v1"
repo["working_directory"]["modified.txt"] = "লাইন এক\nলাইন দুই\nলাইন তিন"
git_add(repo, "staged.txt")
git_add(repo, "modified.txt")
git_commit(repo, "প্রাথমিক কমিট")
repo["working_directory"]["staged.txt"] = "v2 (পরের কমিটের জন্য স্টেজড)"
git_add(repo, "staged.txt") # staged.txt: staged
repo["working_directory"]["modified.txt"] = "লাইন এক\nলাইন দুই - সম্পাদিত\nলাইন তিন" # modified, unstaged
repo["working_directory"]["untracked.txt"] = "একদম নতুন ফাইল" # untracked
print("== git status ==")
for f, s in git_status(repo).items():
print(f" {f}: {s}")
print()
print("== git diff (unstaged, modified.txt) ==")
d = git_diff(repo, "modified.txt")
print(" removed:", d["removed"])
print(" added: ", d["added"])
print()
print("== git diff --staged (staged.txt) ==")
d2 = git_diff(repo, "staged.txt", staged=True)
print(" removed:", d2["removed"])
print(" added: ", d2["added"])
print()
print("== git log ==")
for h, msg in git_log(repo):
print(f" {h} {msg}")
staged.txt stage করা হয়েছে এবং তারপর আর
Working Directory-তে বদলানো হয়নি, তাই এটি ঠিক staged। modified.txt
commit হওয়ার পর আবার এডিট হয়েছে কিন্তু git_add করা হয়নি, তাই এটি
modified (not staged)। untracked.txt কখনো git_add-ই করা হয়নি
এবং আগের কোনো কমিটেও ছিল না, তাই এটি untracked — git_status ফাংশনটি এই
তিনটিকে সঠিকভাবে আলাদা করেছে, শুধু তিনটি dict তুলনা করেই।
git status, git diff, ও git log — তিনটিই L30-এর থ্রি-ট্রি মডেলের
ওপর সরাসরি প্রশ্ন — কোনোটিই নতুন কোনো ডেটা তৈরি করে না, শুধু বিদ্যমান তিনটি ট্রি (বা তাদের parent চেইন)
তুলনা/ট্রাভার্স করে। এই তিনটি কমান্ড ভালোভাবে বোঝা মানে থ্রি-ট্রি মডেলটিই সত্যিকার অর্থে আয়ত্ত করা — এখান
থেকে M7-এর বাকি পাঠ (L32-L33) ও M8-এর ব্রাঞ্চিং একই ভিত্তির ওপর দাঁড়াবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
কোড সেলে modified.txt-কে "modified (not staged)" হিসেবে চেনার শর্তটি ছিল wd != staged। এখানে staged ভ্যারিয়েবলটির মান আসলে কোথা থেকে এসেছে, যেহেতু ফাইলটি এবার কখনো git_add করা হয়নি?
যেহেতু কমিটের পর staging area খালি হয়ে গিয়েছিল এবং modified.txt আর কখনো
git_add করা হয়নি, তাই repo["staging_area"].get("modified.txt") রিটার্ন করে
None। ফাংশনটির staged ভ্যারিয়েবলটির মান তাই None, যা Working
Directory-র বর্তমান কনটেন্টের সাথে মেলে না (wd != staged সত্য) — ফলে সঠিকভাবে "modified
(not staged)" ধরা পড়ে।
প্র ০২
git diff (ডিফল্ট, unstaged) আর git diff --staged — একই ফাইলে দুটো কমান্ড ভিন্ন ফলাফল দিতে পারে কেন, এবং এই পার্থক্যটা প্র্যাকটিক্যালি কখন গুরুত্বপূর্ণ?
কারণ তারা ভিন্ন জোড়া ট্রি তুলনা করে — ডিফল্ট git diff Working Directory বনাম Staging Area
তুলনা করে (আপনি এখনো stage করেননি এমন পরিবর্তন), আর --staged Staging Area বনাম সর্বশেষ
কমিট তুলনা করে (আপনি ইতিমধ্যে stage করেছেন এমন পরিবর্তন)। এটি গুরুত্বপূর্ণ যখন একই ফাইলে আপনি প্রথমে
কিছু বদল stage করেছেন, তারপর আরও বদল করেছেন কিন্তু সেগুলো stage করেননি — তখন দুটো কমান্ড সত্যিই
সম্পূর্ণ ভিন্ন পরিবর্তনের সেট দেখাবে।
প্র ০৩
কোড সেলে git_log মাত্র একটি কমিট (c1) দেখাচ্ছে, যদিও repo-তে অনেক পরিবর্তন হয়েছে। কেন?
কারণ পুরো স্ক্রিপ্টে git_commit মাত্র একবারই কল করা হয়েছে (প্রাথমিক কমিটে) — পরের সব
পরিবর্তন (staged.txt-এর নতুন এডিট ও তার stage, modified.txt-এর এডিট,
untracked.txt যোগ) কোনোটিই git_commit কল করে "চূড়ান্ত" করা হয়নি। L30-এর
মডেল অনুযায়ী git_log শুধু Repository ট্রি-র ওপর কাজ করে, Working
Directory বা Staging Area-র কোনো এখনো-committed-না-হওয়া অবস্থার ওপর নয়।
অনুশীলন
-
চিন্তা করুন: একজন নতুন ডেভেলপার একটি ফাইল এডিট করে সরাসরি
git commitচালিয়েছে, কিন্তু আগে কখনোgit addকরেনি — কমিট কি সফল হবে, এবং এর ফলাফল কী হতে পারে? (ইঙ্গিত: L30-এরgit_commitফাংশনটি কোথা থেকে স্ন্যাপশট নেয় তা মনে করুন।)যদি ফাইলটির জন্য এর আগে কখনো কিছু staged না থাকে, তাহলে সেই ফাইলটির পরিবর্তন কমিটে যাবেই না —
git commitসবসময় Staging Area থেকে স্ন্যাপশট নেয়, Working Directory থেকে সরাসরি নয় (বাস্তব git-এ, যদি স্টেজিং এরিয়াতে কোনো পরিবর্তনই না থাকে, git আসলে "nothing to commit" বলে সম্পূর্ণ প্রত্যাখ্যান করে)। এটিইgit statusএত ঘন ঘন চালানোর genuine, প্র্যাকটিক্যাল কারণ — কমিট করার আগে নিশ্চিত হওয়া ঠিক কী staged আছে। -
পরীক্ষা করুন: উপরের কোড সেলে
untracked.txt-এর ওপরgit_diff(repo, "untracked.txt")কল করে Run চাপুন —added/removedলিস্টে কী দেখা যায় এবং কেন, তা ব্যাখ্যা করুন।oldহবে খালি স্ট্রিং""(কারণuntracked.txtনা staging area-তে না কমিটে আছে), আরnewহবে Working Directory-র কনটেন্ট — ফলেremovedখালি থাকবে, আরadded-এ পুরো ফাইলের কনটেন্ট (লাইন হিসেবে ভাগ করা) দেখা যাবে, যেন পুরো ফাইলটিই "নতুন যোগ হওয়া" — যা যৌক্তিক, কারণ untracked ফাইল আসলে git-এর দৃষ্টিতে সম্পূর্ণ নতুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
-
পরবর্তী পাঠ · .gitignore ও git config L32
এই পাঠের
git_statusফাংশন সরাসরি ব্যবহার হবে — .gitignore কীভাবে ফাইলকে "untracked" তালিকা থেকে বাদ রাখে তা দেখাতে। - git init, add, commit — থ্রি-ট্রি আর্কিটেকচার L30 এই পাঠের ভিত্তি — Working Directory, Staging Area, Repository মডেলটি একবার ঝালিয়ে নিন।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M7 Git ফাউন্ডেশন চলছে — এরপর .gitignore, পরিবর্তন পূর্বাবস্থায় ফেরানো, ও M8-এর ব্রাঞ্চিং।