পাঠ ৩০ · ৫৮-এর মধ্যে · মডিউল ৭
Home / Courses / Software Engineering Principles & Git / Git ফাউন্ডেশন

git init, add, commit — থ্রি-ট্রি আর্কিটেকচার

git init, add, commit — the three-tree architecture
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • Git-এর থ্রি-ট্রি মডেল — Working Directory, Staging Area, Repository — প্রতিটির নির্দিষ্ট ভূমিকা
  • Staging area কেন দরকার — শুধু একটি বাড়তি ধাপ নয়, একটি genuine, প্র্যাকটিক্যাল সুবিধা
  • git init, git add, git commit ঠিক কোন ট্রি বদলায়, কীভাবে
  • একটি বাস্তব Repo সিমুলেশন বানিয়ে edit → stage → commit → edit-again ফ্লো হাতে-কলমে ট্রেস করা

১ · Git-এর থ্রি-ট্রি মডেল

এখান থেকে M7-M9-এর প্রতিটি পাঠের ভিত্তি — Git-এর তিনটি "ট্রি" (state/স্ন্যাপশট) ঠিক কী, একদম নির্দিষ্টভাবে জানা প্রয়োজন। বাস্তব git বাইনারি এই ব্রাউজার-স্যান্ডবক্সে (Pyodide) চলে না — তাই এই পাঠ থেকে শুরু করে M7-M9 জুড়ে আমরা Git-এর অভ্যন্তরীণ অবজেক্ট মডেল Python data structure দিয়ে from-scratch সিমুলেট করব, বাস্তব git-এর আচরণের সাথে নিখুঁতভাবে মিলিয়ে — কখনোই real git "রান" করার দাবি না করে।

Working DirectoryWorking Directoryডিস্কে থাকা আসল ফাইলগুলো, যা আপনি এখন এডিট করছেন।
আপনি এখন যে ফাইলগুলো এডিট করছেন, ডিস্কে থাকা তাদের বর্তমান অবস্থা।
Staging AreaStaging Area (Index)পরের কমিটে যেতে প্রস্তুত হিসেবে git add-এর মাধ্যমে চিহ্নিত পরিবর্তনের একটি স্ন্যাপশট। (Index)
পরের কমিটে যেতে প্রস্তুত হিসেবে git add-এর মাধ্যমে বেছে নেওয়া পরিবর্তনের একটি ইন্টারমিডিয়েট স্ন্যাপশট।
Repository (কমিট-ইতিহাস)
স্থায়ীভাবে সেভ করা স্ন্যাপশট (কমিট) — প্রতিটি কমিট তার parent কমিটকে পয়েন্ট করে (L01-এর প্রিভিউর সরাসরি পুনর্ব্যবহার)।

Staging Area-এর genuine উদ্দেশ্যটি স্পষ্টভাবে বলা দরকার — এটি নিছক একটি বাড়তি, বিরক্তিকর ধাপ নয়। এটি আপনাকে সুনির্দিষ্টভাবে বেছে নিতে দেয় ঠিক কোন পরিবর্তনগুলো পরের কমিটে যাবে, এমনকি যদি আপনার Working Directory-তে একসাথে একাধিক unrelated পরিবর্তন থেকে থাকে — যেমন একটি বাগ-ফিক্সের পরিবর্তন শুধু stage করে একটি focused কমিট বানানো, আর অসম্পর্কিত work-in-progress পরিবর্তন unstaged রেখে দেওয়া পরের কমিটের জন্য।

২ · তিনটি মূল কমান্ড, নির্দিষ্টভাবে

এই তিনটি কমান্ড এই থ্রি-ট্রি মডেলের সাথে একদম সরাসরি মিলিয়ে বলা যায়:

git init
একটি নতুন, খালি রিপোজিটরি তৈরি করে — একটি খালি "ট্রি ৩" (Repository) দিয়ে শুরু।
git add <file>
ফাইলের বর্তমান অবস্থা Working Directory থেকে Staging Area-তে কপি করে।
git commit
Staging Area-তে যা কিছু আছে তা নিয়ে একটি নতুন কমিট তৈরি করে, তার parent বসায় আগের HEAD-এ, HEAD-কে নতুন কমিটে সরায়।
Working Directory ডিস্কের আসল ফাইল git add Staging Area পরের কমিটের জন্য বাছাই git commit Repository স্থায়ী কমিট-ইতিহাস
git add কপি করে Working Directory থেকে Staging Area-তে। git commit Staging Area-র একটি COPY নিয়ে Repository-তে একটি নতুন, স্থায়ী কমিট বানায় — এবং তারপর Staging Area খালি করে দেয়।

৩ · একটি বাস্তব Repo সিমুলেশন

নিচের কোড সেলে Git-এর থ্রি-ট্রি মডেলের একটি genuine, runnable Python সিমুলেশন — তিনটি স্পষ্ট dict (working_directory, staging_area, commits) দিয়ে। এটি real git-এর অভ্যন্তরীণ অ্যালগরিদমের একটি from-scratch সিমুলেশন, বাস্তব git-এর আচরণের সাথে নিখুঁতভাবে মিলিয়ে — কোনো real git বাইনারি এখানে চলছে না।

Python
# Git-এর থ্রি-ট্রি অভ্যন্তরীণ মডেলের from-scratch সিমুলেশন -- বাস্তব git বাইনারি নেই,
# তাই working_directory, staging_area, commits -- তিনটি dict দিয়ে সরাসরি বাস্তবায়ন।

def make_repo():
    return {
        "working_directory": {},   # filename -> content
        "staging_area": {},        # filename -> content (শুরুতে খালি)
        "commits": {},             # hash -> {message, snapshot, parent}
        "head": None,              # সর্বশেষ কমিটের hash, শুরুতে কোনো কমিট নেই
    }

def git_add(repo, filename):
    # working directory থেকে ফাইলের বর্তমান কনটেন্ট staging area-তে কপি করা
    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"])  # staging area-র একটি COPY, রেফারেন্স নয়
    repo["commits"][commit_hash] = {
        "message": message,
        "snapshot": snapshot,
        "parent": repo["head"],
    }
    repo["head"] = commit_hash
    repo["staging_area"] = {}   # বাস্তব git-এর মতোই কমিটের পর staging area খালি হয়ে যায়
    return commit_hash

def print_trees(repo, label):
    print(f"-- {label} --")
    print("Working Directory:", repo["working_directory"])
    print("Staging Area:     ", repo["staging_area"])
    if repo["head"]:
        head_snapshot = repo["commits"][repo["head"]]["snapshot"]
        print("HEAD কমিট:        ", repo["head"], "->", head_snapshot)
    else:
        print("HEAD কমিট:         (এখনও কোনো কমিট নেই)")
    print()


repo = make_repo()   # git init

# ধাপ ১: readme.txt ফাইল এডিট করা (শুধু working directory-তে)
repo["working_directory"]["readme.txt"] = "Hello v1"
print_trees(repo, "ধাপ ১: readme.txt এডিট করা হলো (working directory)")

# ধাপ ২: স্টেজ করা
git_add(repo, "readme.txt")
print_trees(repo, "ধাপ ২: git add readme.txt")

# ধাপ ৩: কমিট করা
c1 = git_commit(repo, "প্রথম কমিট: readme যোগ করা হলো")
print_trees(repo, f"ধাপ ৩: git commit -> {c1}")

# ধাপ ৪: আবার এডিট করা, কিন্তু এবার স্টেজ না করা
repo["working_directory"]["readme.txt"] = "Hello v2 (staged করা হয়নি)"
print_trees(repo, "ধাপ ৪: readme.txt আবার এডিট, কিন্তু git add করা হয়নি")

    
ধাপ ৪-এর আউটপুট মনোযোগ দিয়ে দেখুন — Working Directory-তে এখন "Hello v2 (staged করা হয়নি)" আছে, কিন্তু c1 কমিটের snapshot এখনও "Hello v1"-ই দেখাচ্ছে। এটিই git_add আর git_commit-এর আলাদা ভূমিকার সবচেয়ে concrete প্রমাণ — দ্বিতীয় এডিটটি কখনো stage হয়নি, তাই সেটি কোনো কমিটেই যায়নি, যতক্ষণ না কেউ আবার git_add কল করে।
মূল কথা · Key takeaway

Git-এর প্রতিটি অপারেশন আসলে এই তিনটি ট্রির মধ্যে ডেটা কপি করার একটি নির্দিষ্ট নিয়ম। git add Working Directory → Staging Area কপি করে; git commit Staging Area-র একটি COPY নিয়ে Repository-তে একটি নতুন, স্থায়ী কমিট বানায়, তারপর Staging Area খালি করে দেয়। এই মডেলটিই M7-এর বাকি পাঠগুলোর (L31-L33) এবং M8-M9-এর ব্রাঞ্চিং/মার্জিং/রিমোট আলোচনার ভিত্তি — এখান থেকে আমরা শুধু এই একই তিনটি dict-এর ওপর নতুন কমান্ড/ফাংশন যোগ করে যাব।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কোড সেলের ধাপ ৪-এ, যদি এখনই আরেকবার git_commit(repo, "দ্বিতীয় কমিট") কল করা হয়, তাহলে নতুন কমিটের snapshot-এ কী থাকবে?

একটি খালি snapshot ({}) — কারণ ধাপ ৩-এর কমিটের পর Staging Area খালি হয়ে গিয়েছিল, আর ধাপ ৪-এর এডিটটি কখনো git_add দিয়ে স্টেজ করা হয়নি। git_commit সবসময় Staging Area-র বর্তমান অবস্থা থেকে স্ন্যাপশট নেয়, Working Directory থেকে সরাসরি নয় — এটিই এই পাঠের সবচেয়ে গুরুত্বপূর্ণ, প্রায়ই ভুল বোঝা আচরণ।

প্র ০২ git_commit ফাংশনে snapshot = dict(repo["staging_area"]) লেখা হয়েছে, সরাসরি repo["staging_area"] রেফারেন্স রাখা হয়নি কেন?

কারণ যদি সরাসরি রেফারেন্স রাখা হতো, তাহলে পরে repo["staging_area"] = {} লাইনটি একই dict object-কে খালি করে দিত, যেটি কমিটের snapshot হিসেবেও ব্যবহৃত হচ্ছিল — ফলে পুরোনো কমিটের snapshot-ও খালি হয়ে যেত! dict(...) দিয়ে একটি স্বাধীন COPY বানানোয়, Staging Area পরে খালি হলেও কমিটের snapshot-এ তার নিজস্ব, স্থায়ী কপি থেকে যায় — ঠিক real git যেভাবে প্রতিটি কমিটকে immutable রাখে।

প্র ০৩ যদি ধাপ ৪-এর পরে আবার git_add(repo, "readme.txt") কল করা হয়, staging area-তে কী দেখা যাবে, এবং তারপর git_commit কল করলে নতুন কমিটের parent কী হবে?

git_add কল করলে staging area-তে {"readme.txt": "Hello v2 (staged করা হয়নি)"} যোগ হবে (এখন এটি স্টেজড)। তারপর git_commit কল করলে একটি নতুন কমিট (c2) তৈরি হবে, যার parent হবে c1 (কারণ repo["head"] তখন c1-ই ছিল) — ঠিক L01-এর প্রিভিউ কোড সেলের মতোই, প্রতিটি নতুন কমিট আগের HEAD-কে parent হিসেবে ধরে রাখে।

অনুশীলন

  1. চিন্তা করুন: একজন ডেভেলপার একটি বাগ-ফিক্স আর একটি অসম্পূর্ণ নতুন ফিচার — দুটোই একসাথে একই ফাইলে এডিট করে ফেলেছে, কিন্তু শুধু বাগ-ফিক্সটুকু এখনই কমিট করতে চায়। Staging area কীভাবে এই পরিস্থিতিতে ব্যবহারিকভাবে সাহায্য করে তা ব্যাখ্যা করুন।

    Git-এ git add -p-জাতীয় টুল দিয়ে একটি ফাইলের নির্দিষ্ট অংশ (hunk) বেছে stage করা যায় — শুধু বাগ-ফিক্সের লাইনগুলো staging area-তে যোগ করে কমিট করা যাবে, আর অসম্পূর্ণ ফিচারের লাইনগুলো Working Directory-তেই unstaged থেকে যাবে, পরে আলাদা কমিটের জন্য অপেক্ষা করবে। এই লেভেলের নিয়ন্ত্রণ সম্ভবই হতো না যদি git commit সরাসরি Working Directory থেকে স্ন্যাপশট নিত।

  2. পরীক্ষা করুন: উপরের কোড সেলে ধাপ ৪-এর পরে একটি নতুন ফাইল notes.txt working directory-তে যোগ করুন, সেটিকে git_add ও git_commit করুন (নতুন মেসেজ দিয়ে) — নতুন কমিটের snapshot-এ কি শুধু notes.txt থাকবে, নাকি readme.txt-ও (v1 অবস্থায়) থাকবে? Run চেপে যাচাই করুন।

    নতুন কমিটের snapshot-এ শুধু notes.txt থাকবে — কারণ ধাপ ৩-এর কমিটের পর staging area খালি হয়ে গিয়েছিল, আর readme.txt কখনো আবার stage করা হয়নি। প্রতিটি কমিটের snapshot শুধু সেই মুহূর্তে staging area-তে যা ছিল তা-ই ধারণ করে — আগের কমিটের পুরো ফাইল-সেট নয়, এটি এই পাঠের সিমুলেশনের একটি সরলীকরণ যা বাস্তব git-এর "প্রতিটি কমিট একটি সম্পূর্ণ ট্রি স্ন্যাপশট" আচরণকে দেখানোর জন্য যথেষ্ট নয় বলে মনে রাখা জরুরি -- L31-এ git_status/git_diff যোগ করার সময় এই সীমাবদ্ধতাটি আরও স্পষ্ট হবে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ভার্সন কন্ট্রোল বেসিকস