Git Flow ব্রাঞ্চিং স্ট্র্যাটেজি
এই পাঠে যা শিখবেন
- Git Flow-এর পাঁচ ধরনের ব্রাঞ্চ ও প্রতিটির নির্দিষ্ট ভূমিকা
- ফিচার ও রিলিজ ব্রাঞ্চ ঠিক কোথা থেকে শুরু হয় এবং কোথায় merge হয়
- Git Flow কখন উপযুক্ত, কখন এটি অপ্রয়োজনীয়ভাবে ভারী
- Python দিয়ে একটি বাস্তবসম্মত
start_feature/finish_feature/start_release/finish_releaseসিকোয়েন্স সিমুলেট ও যাচাই করা
১ · Git Flow কী
Git FlowGit FlowVincent Driessen-এর প্রস্তাবিত একটি নির্দিষ্ট, স্ট্রাকচার্ড ব্রাঞ্চিং স্ট্র্যাটেজি যেখানে main, develop, feature, release ও hotfix ব্রাঞ্চের প্রতিটির একটি সুনির্দিষ্ট ভূমিকা আছে। হলো একটি সুনির্দিষ্ট, ব্যাপকভাবে পরিচিত ব্রাঞ্চিং কনভেনশন — মূলত Vincent Driessen প্রস্তাব করেছিলেন (একটি বাস্তব ঐতিহাসিক উৎস, উল্লেখ করার মতো)। এটি M8-এর ব্রাঞ্চিং/মার্জিং মেকানিক্সের ওপর ভিত্তি করে বিভিন্ন ধরনের ব্রাঞ্চের জন্য নির্দিষ্ট ভূমিকা নির্ধারণ করে দেয়, যাতে একটি টিমের সবাই একই কনভেনশন অনুসরণ করে।
২ · পাঁচটি ব্রাঞ্চ টাইপ
সবসময় প্রোডাকশন-রেডি, রিলিজড কোড ধরে রাখে।
সম্পন্ন ফিচার জমা হওয়ার কেন্দ্রীয় ইন্টিগ্রেশন ব্রাঞ্চ, দুই রিলিজের মাঝখানে।
একটি চলমান ফিচারের জন্য একটি — develop থেকে branch, সম্পন্ন হলে develop-এ merge।
রিলিজ প্রস্তুতির জন্য — develop থেকে branch, চূড়ান্ত টেস্ট/বাগফিক্সের পর main ও develop উভয়েতেই merge।
জরুরি প্রোডাকশন ফিক্সের জন্য — সরাসরি main থেকে branch, main ও develop উভয়েতেই merge।
৩ · কখন উপযুক্ত, কখন ভারী
সৎ ট্রেড-অফ: এই স্ট্রাকচার্ড, একাধিক-ব্রাঞ্চ মডেল সেই প্রজেক্টগুলোর জন্য ভালো কাজ করে যেখানে শিডিউল্ড, ভার্সন-নাম্বারড রিলিজ আছে (যেমন ইনস্টল-করা সফটওয়্যার যার নির্দিষ্ট ভার্সন নম্বর থাকে) — কিন্তু কন্টিনিউয়াস ডিপ্লয়মেন্ট করা প্রজেক্টের জন্য এটি সত্যিই অপ্রয়োজনীয়ভাবে ভারী (M9/L42-এর ট্রাংক-বেসড ডেভেলপমেন্ট, একটি হালকা বিকল্প, সেখানে বিস্তারিত দেখব)। Git Flow-কে সব প্রজেক্টের জন্য "একমাত্র সঠিক" পদ্ধতি হিসেবে দেখানো ঠিক নয়।
নিচের কোড সেলে L30/L34/L37-এর ঠিক একই Repo মডেলের ওপর ভিত্তি করে একটি
GitFlowRepo-স্টাইল সিমুলেশন লেখা হয়েছে — start_feature/finish_feature
এবং start_release/finish_release ফাংশনসহ। বাস্তব git বাইনারি এই স্যান্ডবক্সে
নেই, তাই এই সবকিছু commit/branch/merge-এর প্রকৃত অভ্যন্তরীণ আচরণের একটি নির্ভুল Python সিমুলেশন।
# Git Flow সিমুলেশন -- L30/L34/L37-এর ঠিক একই Repo মডেলের ওপর ভিত্তি করে।
def make_repo():
return {
"working_directory": {}, "staging_area": {}, "commits": {},
"branches": {"main": None}, "head": "main",
}
def _new_hash(repo):
return f"c{len(repo['commits']) + 1}"
def current_commit(repo):
if repo["head"] in repo["branches"]:
return repo["branches"][repo["head"]]
return repo["head"]
def git_add(repo, filename):
repo["staging_area"][filename] = repo["working_directory"][filename]
def git_commit(repo, message):
parent = current_commit(repo)
h = _new_hash(repo)
repo["commits"][h] = {"message": message, "snapshot": dict(repo["staging_area"]), "parent": parent}
repo["branches"][repo["head"]] = h
repo["staging_area"] = {}
return h
def is_ancestor(repo, ancestor_hash, descendant_hash):
current = descendant_hash
while current is not None:
if current == ancestor_hash:
return True
current = repo["commits"][current]["parent"]
return False
def merge_into(repo, source_branch, target_branch):
source_tip = repo["branches"][source_branch]
target_tip = repo["branches"][target_branch]
if target_tip is None or is_ancestor(repo, target_tip, source_tip):
repo["branches"][target_branch] = source_tip
return "fast-forward"
if is_ancestor(repo, source_tip, target_tip):
return "already-up-to-date"
h = _new_hash(repo)
repo["commits"][h] = {
"message": f"Merge {source_branch} into {target_branch}",
"snapshot": dict(repo["commits"][source_tip]["snapshot"]),
"parent": target_tip,
"parents": [target_tip, source_tip],
}
repo["branches"][target_branch] = h
return "merge-commit"
def commit_on(repo, branch, message, files):
repo["head"] = branch
for name, content in files.items():
repo["working_directory"][name] = content
git_add(repo, name)
return git_commit(repo, message)
def start_feature(repo, name):
branch = f"feature/{name}"
repo["branches"][branch] = repo["branches"]["develop"]
return branch
def finish_feature(repo, name):
branch = f"feature/{name}"
result = merge_into(repo, branch, "develop")
del repo["branches"][branch]
return result
def start_release(repo, version):
branch = f"release/{version}"
repo["branches"][branch] = repo["branches"]["develop"]
return branch
def finish_release(repo, version):
branch = f"release/{version}"
r1 = merge_into(repo, branch, "main")
r2 = merge_into(repo, branch, "develop")
del repo["branches"][branch]
return r1, r2
def log_branch(repo, branch):
messages = []
current = repo["branches"][branch]
while current is not None:
messages.append(repo["commits"][current]["message"])
current = repo["commits"][current]["parent"]
return list(reversed(messages))
repo = make_repo()
repo["branches"]["develop"] = None
commit_on(repo, "main", "প্রজেক্ট শুরু", {"app.py": "v0"})
repo["branches"]["develop"] = repo["branches"]["main"]
start_feature(repo, "login")
commit_on(repo, "feature/login", "লগইন ফিচার যোগ", {"login.py": "v1"})
print("feature/login শেষ:", finish_feature(repo, "login"))
start_feature(repo, "signup")
commit_on(repo, "feature/signup", "সাইনআপ ফিচার যোগ", {"signup.py": "v1"})
print("feature/signup শেষ:", finish_feature(repo, "signup"))
start_release(repo, "1.0")
commit_on(repo, "release/1.0", "রিলিজ ১.০-এর জন্য ভার্সন বাম্প", {"VERSION": "1.0"})
print("release/1.0 শেষ (main, develop):", finish_release(repo, "1.0"))
print("\nচূড়ান্ত ব্রাঞ্চ পয়েন্টার:")
for b, tip in repo["branches"].items():
print(f" {b:16s} -> {tip}")
print("\nmain-এর কমিট হিস্ট্রি (পুরনো -> নতুন):")
for m in log_branch(repo, "main"):
print(" -", m)
print("\ndevelop-এর কমিট হিস্ট্রি (পুরনো -> নতুন):")
for m in log_branch(repo, "develop"):
print(" -", m)
main_has_both_features = (
"লগইন ফিচার যোগ" in log_branch(repo, "main")
and "সাইনআপ ফিচার যোগ" in log_branch(repo, "main")
)
develop_has_release_bump = "রিলিজ ১.০-এর জন্য ভার্সন বাম্প" in log_branch(repo, "develop")
print("\nmain-এ কি দুটো ফিচারই পৌঁছেছে?", main_has_both_features)
print("develop কি রিলিজের ভার্সন-বাম্প commit-ও পেয়েছে?", develop_has_release_bump)
main ও develop শেষে একদম একই
commit চেইন শেয়ার করে। এই কারণেই log_branch(repo, "main")-এ দুটো ফিচার commit-ই দেখা
যায় এবং develop-এও রিলিজ ভার্সন-বাম্প commit দেখা যায় — কমিট হিস্ট্রি (parent চেইন ধরে
হাঁটা) দিয়ে এটি সরাসরি যাচাই করা হয়েছে, কেবল দাবি করে নয়।
Git Flow প্রতিটি ব্রাঞ্চ টাইপের জন্য একটি স্পষ্ট, পূর্বনির্ধারিত ভূমিকা দেয় — শিডিউল্ড রিলিজ-চক্র থাকা প্রজেক্টে এই স্পষ্টতা মূল্যবান। কিন্তু এটি একমাত্র বিকল্প নয় — পরের পাঠে (L42) আমরা ট্রাংক-বেসড ডেভেলপমেন্ট দেখব, যা কন্টিনিউয়াস ডিপ্লয়মেন্ট প্রজেক্টের জন্য একটি ইচ্ছাকৃতভাবে হালকা বিকল্প প্রস্তাব করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
কেন release/* ব্রাঞ্চ শেষে develop-এও merge করা হয়, শুধু main-এ নয়?
কারণ রিলিজ প্রস্তুতির সময় প্রায়ই শেষ মুহূর্তের বাগফিক্স/টেস্ট-সংশোধন release ব্রাঞ্চে করা হয় — যদি সেই পরিবর্তনগুলো শুধু main-এ merge করা হয়, develop সেগুলো কখনোই পাবে না, এবং পরবর্তী রিলিজে সেই একই বাগ আবার ফিরে আসতে পারে। develop-এও merge করলে নিশ্চিত হয় ভবিষ্যতের সব ফিচার-ওয়ার্ক এই শেষ-মুহূর্তের ফিক্সগুলোর ওপর ভিত্তি করে চলবে।
প্র ০২
হটফিক্স ব্রাঞ্চ কেন সরাসরি main থেকে শুরু হয়, develop থেকে নয়?
কারণ হটফিক্স একটি জরুরি প্রোডাকশন সমস্যার সমাধান — এবং develop-এ প্রায়ই এমন
অসম্পূর্ণ/এখনো-রিলিজ-না-হওয়া ফিচার-ওয়ার্ক থাকতে পারে যা এখনো প্রোডাকশনে যাওয়ার জন্য প্রস্তুত নয়।
যদি হটফিক্স develop থেকে শুরু হতো, সেই ফিক্সের সাথে অনিচ্ছাকৃতভাবে অসম্পূর্ণ কোডও প্রোডাকশনে চলে
যেতে পারত। main থেকে শুরু করলে হটফিক্সটি শুধুই বর্তমান প্রোডাকশন কোডের ওপর ভিত্তি করে তৈরি হয়,
অতিরিক্ত কোনো কিছু জড়ায় না।
প্র ০৩
কোড সেলে log_branch(repo, "main") এবং log_branch(repo, "develop") এই উদাহরণে identical কেন হলো — এটা কি সবসময় হবে?
না, সবসময় হবে না — এই নির্দিষ্ট উদাহরণে identical হয়েছে কারণ প্রতিটি merge fast-forward ছিল
(main বা develop কখনোই independently এগিয়ে যায়নি, তাই তাদের মধ্যে কখনো প্রকৃত ডাইভার্জেন্স হয়নি)।
বাস্তবে, যদি main-এ সরাসরি একটি হটফিক্স commit হতো যা তখনও develop-এ merge করা হয়নি,
অথবা develop-এ নতুন ফিচার commit হতো যা এখনো কোনো রিলিজে যায়নি, তাহলে দুই ব্রাঞ্চের
log_branch ফলাফল ভিন্ন হতো।
অনুশীলন
-
চিন্তা করুন: আপনার টিম প্রতিদিন একাধিকবার প্রোডাকশনে ডিপ্লয় করে (কন্টিনিউয়াস ডিপ্লয়মেন্ট)। Git Flow-এর
release/*ব্রাঞ্চ প্রক্রিয়া এই ধরনের টিমের জন্য কেন ঘর্ষণ (friction) তৈরি করতে পারে?কন্টিনিউয়াস ডিপ্লয়মেন্টে প্রতিটি ছোট পরিবর্তন দ্রুত প্রোডাকশনে যাওয়ার কথা — কিন্তু Git Flow-এর
release/*ধাপ একটি অতিরিক্ত, ম্যানুয়াল "রিলিজ প্রস্তুতি" পর্যায় যোগ করে (ব্রাঞ্চ তৈরি, চূড়ান্ত টেস্ট, main+develop উভয়েতে merge) যা প্রতিটি ছোট পরিবর্তনের জন্য অপ্রয়োজনীয় ওভারহেড তৈরি করে। এই কারণেই M9/L42-এর ট্রাংক-বেসড ডেভেলপমেন্ট (সরাসরি এক ট্রাংকে ঘন ঘন কমিট, ফিচার ফ্ল্যাগ দিয়ে অসম্পূর্ণ কাজ লুকানো) এই ধরনের প্রজেক্টের জন্য বেশি স্বাভাবিক ফিট। -
পরীক্ষা করুন: কোড সেলে
finish_release(repo, "1.0")কল করার পর একটি তৃতীয় ফিচার (start_feature/commit_on/finish_feature) যোগ করুন, তারপর আবারlog_branch(repo, "develop")প্রিন্ট করে দেখুন এতে নতুন commit-টি যুক্ত হয়েছে কিনা।হ্যাঁ, নতুন ফিচার commit-টি এখন
log_branch(repo, "develop")-এর তালিকায় সবচেয়ে নতুন (তালিকার শেষে) এন্ট্রি হিসেবে যুক্ত হবে, কারণfinish_featureসবসময়develop-এর বর্তমান tip-এর ওপর merge করে — এই ক্ষেত্রে সেটি রিলিজ ১.০-এর ভার্সন-বাম্প commit-এর পরের অবস্থান।main-এর হিস্ট্রি তখনও অপরিবর্তিত থাকবে, যতক্ষণ না একটি নতুন রিলিজ সেই ফিচারটিকেও main-এ নিয়ে আসে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — ট্রাংক-বেসড ডেভেলপমেন্ট ও ফিচার ফ্ল্যাগ, Git Flow-এর একটি হালকা বিকল্প।
-
Git ট্যাগ, রিলিজ ও সিমান্টিক ভার্সনিং L43
Git Flow-এর
release/*ব্রাঞ্চ থেকে তৈরি হওয়া প্রতিটি রিলিজ ঠিক কীভাবে একটি ভার্সন-নাম্বার ও ট্যাগ পায় — বিস্তারিত সেই পাঠে।