Merge Conflict: কেন হয়, কীভাবে সমাধান করবেন
Conflict আসলে কখন হয়
Git লাইন-ভিত্তিক three-way merge করে: দুই branch-এর common ancestor (base) নিয়ে দেখে কে কী বদলেছে। এক পক্ষ বদলালে সেটাই নেয় — automatic। কিন্তু দুই পক্ষই একই ফাইলের একই জায়গা ভিন্নভাবে বদলালে Git আর সিদ্ধান্ত নিতে পারে না, কারণ Git কোডের অর্থ বোঝে না। তখনই conflict — এবং সিদ্ধান্তটা আপনার হাতে ছেড়ে দেয়।
ধাপে ধাপে সমাধান
১. Merge শুরু ও conflict শনাক্ত
git checkout feature
git merge main
# Auto-merging OrderService.java
# CONFLICT (content): Merge conflict in OrderService.java
# Automatic merge failed; fix conflicts and then commit the result.
git status
# Unmerged paths:
# both modified: src/main/java/OrderService.java
২. ফাইল খুলে conflict marker বুঝুন
public BigDecimal calculateDiscount(Order order) {
<<<<<<< HEAD
return order.getTotal().multiply(LOYALTY_RATE);
=======
return discountPolicy.apply(order.getTotal());
>>>>>>> main
}
<<<<<<< HEADথেকে=======পর্যন্ত: আপনার বর্তমান branch-এর ভার্সন=======থেকে>>>>>>> mainপর্যন্ত: যে branch merge করছেন তার ভার্সন
একটা কাজের টিপ: git config merge.conflictStyle zdiff3 সেট করলে মাঝখানে base ভার্সনও দেখায় — অর্থাৎ মূল কোডটা কী ছিল। তখন বোঝা অনেক সহজ হয় কে কী উদ্দেশ্যে বদলেছিল। এটা underrated একটা সেটিং।
৩. সমাধান করুন — এটাই আসল কাজ
এখানে সিনিয়র আর জুনিয়রের পার্থক্য পরিষ্কার হয়। অপশন তিনটা নয়, চারটা:
- আপনার ভার্সন রাখা
- ওদের ভার্সন রাখা
- দুটোই রাখা (যেমন দুজন দুটো আলাদা import বা method যোগ করেছে)
- দুই পরিবর্তনের উদ্দেশ্য বুঝে নতুন কোড লেখা — বাস্তবে প্রায়ই এটাই সঠিক উত্তর
উপরের উদাহরণে হয়তো সঠিক সমাধান: discountPolicy.apply()-এর ভেতরে loyalty rate ঢোকানো — অর্থাৎ marker মুছে যান্ত্রিকভাবে এক পক্ষ নেওয়া নয়, দুই ইনটেন্ট মেশানো। প্রয়োজনে যিনি অন্য পরিবর্তনটা করেছেন তাকে জিজ্ঞেস করুন — git log main -- OrderService.java বা git blame দিয়ে দেখে নিন কমিটটা কার, কী context-এ।
সব marker (<<<<<<<, =======, >>>>>>>) মুছে ফেলতে ভুলবেন না — এগুলো রয়ে গেলে কোড compile-ই হবে না।
৪. যাচাই, stage, commit
# আগে বিল্ড আর টেস্ট — conflict "textually" মেটা মানেই "semantically" সঠিক না
mvn test
git add OrderService.java
git merge --continue # বা git commit
এখানে একটা সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ কথা: conflict-ছাড়া merge-ও ভুল হতে পারে। এক branch-এ কেউ method-এর নাম বদলালো, আরেক branch-এ কেউ পুরোনো নামে নতুন call যোগ করলো — Git খুশিমনে merge করবে, কারণ লাইনগুলো আলাদা জায়গায়। এটাকে বলে semantic conflict — ধরা পড়ে শুধু compile/test-এ। তাই merge-এর পর টেস্ট চালানো ঐচ্ছিক নয়।
ভুল হয়ে গেলে? ঘাবড়াবেন না:
git merge --abort # merge শুরুর আগের অবস্থায় ফেরত
git checkout --merge file # নির্দিষ্ট ফাইলে আবার marker ফিরিয়ে আনা
Rebase-এর সময় হলে একই জিনিস, শুধু প্রতিটা commit replay-এর সময় আলাদা করে conflict আসতে পারে — সমাধান করে git rebase --continue, ছেড়ে দিতে চাইলে git rebase --abort।
টুলিং: হাতে ফাইল সম্পাদনার বদলে git mergetool, IntelliJ-র চমৎকার three-pane merge UI, বা VS Code-এর merge editor — যেটায় স্বচ্ছন্দ। IntelliJ-রটা বিশেষভাবে ভালো কারণ এটা "magic resolve" দিয়ে non-conflicting অংশ নিজেই মিলিয়ে দেয়।
আরেকটা লুকানো রত্ন: git rerere (reuse recorded resolution)। enable করলে (git config rerere.enabled true) Git আপনার conflict-সমাধান মনে রাখে — একই conflict আবার এলে (যেমন লম্বা branch বারবার rebase করলে) নিজেই apply করে দেয়।
টিমে Conflict কমানোর কৌশল
Conflict-এর মূল কারণ একটাই সমীকরণ: একই কোডে × একই সময়ে × আলাদা লোক। কমানোর সব কৌশল এই তিনটার কোনো একটাকে আক্রমণ করে।
১. Branch-এর আয়ু ছোট রাখুন (সবচেয়ে কার্যকর)
Conflict-এর সম্ভাবনা branch-এর বয়সের সাথে চক্রবৃদ্ধিতে বাড়ে। তিন সপ্তাহের branch মানে merge-day-তে রক্তক্ষয়ী যুদ্ধ। ছোট PR, ঘন ঘন merge — এক-দুই দিনের মধ্যে main-এ ঢুকে যাওয়া। বড় feature হলে feature flag-এর পেছনে অসম্পূর্ণ কোড main-এ merge করতে থাকুন — এটাই trunk-based development-এর মূল দর্শন।
২. নিয়মিত main থেকে sync করুন
আগের আলোচনার সাথে মিলিয়ে: লম্বা-চলা branch-এ প্রতিদিন git rebase main (বা merge) করুন। এতে conflict ছোট ছোট কিস্তিতে আসে — একবারে পাহাড় না হয়ে। আর rerere চালু থাকলে বারবার একই সমাধান করতে হয় না।
৩. কাজ ভাগ করুন কোড-সীমানা ধরে
দুজন একই sprint-এ একই class-এ কাজ করা মানে conflict নিজে ডেকে আনা। Planning-এর সময়ই খেয়াল রাখুন কারা কোন module ছুঁচ্ছে। এখানে আর্কিটেকচারের সাথে সরাসরি সম্পর্ক আছে — Conway's Law উল্টোদিকেও কাজ করে: ভালোভাবে module-ভাগ করা কোডবেস (আপনার ক্ষেত্রে যেমন আলাদা microservice, আলাদা bounded context) এমনিতেই conflict কমায়, কারণ টিমগুলোর কাজের জায়গা আলাদা।
৪. যান্ত্রিক conflict-এর উৎস দূর করুন
কিছু conflict কোডের দ্বন্দ্ব না, স্রেফ যন্ত্রণা:
- Formatting যুদ্ধ: টিমে এক formatter (Spotless/google-java-format), CI-তে enforce। কেউ পুরো ফাইল reformat করলে শত লাইনের ভুয়া conflict — এটা নিয়মে বন্ধ করুন। বড় reformat করতে হলে আলাদা, শুধু-formatting PR।
- সবাই-ছোঁয়-এমন ফাইল: এক central config, এক জায়ান্ট
routesফাইল — এগুলো conflict চুম্বক। ভেঙে ছোট করুন, বা append-only কাঠামো রাখুন। - Lock ফাইল / generated কোড:
package-lock.json-জাতীয় ফাইলে conflict হলে হাতে না মিটিয়ে regenerate করুন।.gitattributes-এ merge strategy সেট করা যায়।
৫. যোগাযোগ — সবচেয়ে কম প্রযুক্তিগত, সবচেয়ে বেশি ফলদায়ক
"আমি OrderService বড় করে refactor করছি, কেউ ওদিকে হাত দিও না দুদিন" — standup-এ এক লাইনের এই ঘোষণা ঘণ্টাখানেকের conflict-resolution বাঁচায়। বড় refactoring (rename, move, package restructure) আলাদা PR-এ, ছোট করে, আগে থেকে জানিয়ে।
Interview-সারমর্ম
এক প্যারায় বলতে চাইলে: "Conflict মানে Git দুই পক্ষের intent বুঝতে পারছে না — তাই সমাধানের মূল কাজ যান্ত্রিক নয়, দুই পরিবর্তনের উদ্দেশ্য বুঝে সেগুলো মেলানো, এবং তারপর টেস্ট চালিয়ে semantic correctness নিশ্চিত করা। আর টিম-লেভেলে conflict কমানোর সেরা উপায় প্রযুক্তিগত কৌশলের আগে process: ছোট branch, ঘন ঘন integration, module-ধরে কাজ ভাগ, আর জোরালো যোগাযোগ।"