অধ্যায় 1 · পেশাদারের মতো Git ও GitHub
GitHub-এ টিম হিসেবে কাজ
- পৃষ্ঠা 2 / 22
- 26 মিনিট পড়া
একা কাজ করলে Git একটা টাইম মেশিন। টিমে এসে এটা হয়ে যায় সমন্বয়ের ব্যবস্থা: কয়েকজন মানুষ প্রতিদিন একই কোড বদলাচ্ছেন, অথচ GitHub-এ থাকা শেয়ার করা কপিটাকে সবসময় চালু অবস্থায় রাখতে হবে। AI টিমে কাজটা আরও কঠিন হয়, সহজ নয়: একজন ডেটা সায়েন্টিস্ট প্রম্পট টিউন করছেন, একজন ব্যাকএন্ড ডেভেলপার সেই প্রম্পট যে API দিয়ে সার্ভ হয় তা বদলাচ্ছেন, আরেকজন ইভ্যালুয়েশন স্ক্রিপ্ট আপডেট করছেন — আর তিনটা পরিবর্তনই একই বিকেলে main-এ পৌঁছাচ্ছে।
এই পাতায় সেই বিকেলটাই অভিনয় করে দেখানো হবে। দুজন ডেভেলপার, নাদিয়া আর রফিক, একটা শেয়ার করা রিমোটের দুটো ক্লোন দিয়ে একই রিপোজিটরিতে কাজ করবেন; তৃতীয়জন, মিতু, অবদান রাখবেন একটা ফর্ক (fork) থেকে। প্রতিটা Git কমান্ড সত্যিই চালানো হয়েছে। GitHub-এর যে অংশগুলো শুধু ব্রাউজারেই থাকে (পুল রিকোয়েস্ট, রিভিউ, রুলসেট), সেগুলো ধাপে ধাপে ব্যাখ্যা করা হয়েছে, সাথে আছে সেগুলো চালানোর আসল ফাইল আর gh কমান্ড।
যা শিখবেন
- রিমোট-ট্র্যাকিং ব্রাঞ্চ, আর
fetch,pull,pull --rebaseওpull --ff-only-এর আসল পার্থক্য - পুশ ফিরিয়ে দেওয়া হলে কী করবেন, আর ফর্ক ও
upstreamরিমোট কীভাবে কাজ করে - পুল রিকোয়েস্টের পুরো চক্র: ড্রাফট, বর্ণনা, রিভিউ, সাজেশন, ফিডব্যাক, মার্জের পদ্ধতি, পরিষ্কার করা
- ইস্যু, লেবেল, মাইলস্টোন আর প্রজেক্ট; PR টেমপ্লেট আর
CODEOWNERS - রুলসেট আর রিকোয়ার্ড চেক দিয়ে
mainরক্ষা করা, আর রিলিজ নোটসহ রিলিজ প্রকাশ করা
দুজন ডেভেলপার, একটা রিমোট
Git-এর চোখে GitHub-এর একটা রিপোজিটরি হলো একটা বেয়ার রিপোজিটরি (bare repository — ইতিহাস আছে, কিন্তু কাজের ফোল্ডার নেই), যেখানে সবাই পুশ করে আর যেখান থেকে ফেচ করে। আপনার নিজের ডিস্কে রাখা একটা বেয়ার রিপোজিটরিও হুবহু একইভাবে আচরণ করে, তাই এখানে সেটাকেই "GitHub" ধরা হচ্ছে, আর প্রত্যেক ডেভেলপারকে দেওয়া হচ্ছে নিজের একটা ক্লোন:
~/remotes/sentiment-api.git ("GitHub")
/ \
push / fetch push / fetch
/ \
~/team/nadia (Nadia's laptop) ~/team/rafiq (Rafiq's laptop)নাদিয়া শেয়ার করা রিপোজিটরি আর নিজের ক্লোন বানান:
mkdir -p ~/remotes ~/team
git init --bare -q ~/remotes/sentiment-api.git
cd ~/team
git clone ~/remotes/sentiment-api.git nadiaCloning into 'nadia'...
warning: You appear to have cloned an empty repository.
done.প্রজেক্টটা একটা ছোট্ট সেন্টিমেন্ট স্কোরার — এমন একটা বেসলাইন, যা AI টিম একটা LLM ক্লাসিফায়ারের পাশে রাখে সেটার ফলাফল যাচাই করতে:
cd ~/team/nadia# file: sentiment.py
POSITIVE = {"good", "great", "love", "excellent"}
NEGATIVE = {"bad", "poor", "hate", "terrible"}
def score(text: str) -> int:
"""Positive words minus negative words."""
words = text.lower().split()
return sum(w in POSITIVE for w in words) - sum(w in NEGATIVE for w in words)git add sentiment.py
git commit -q -m "Add word-list sentiment scorer"
git push -u origin mainTo /home/nadia/remotes/sentiment-api.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.রফিক "তাঁর ল্যাপটপে" একই রিমোট ক্লোন করেন। এখানে দুজন একই মেশিন ব্যবহার করছেন বলে তিনি নিজের নাম আর ইমেইল সেট করেন শুধু এই ক্লোনের জন্য (--global ছাড়া):
cd ~/team
git clone -q ~/remotes/sentiment-api.git rafiq
cd rafiq
git config user.name "Rafiq Islam"
git config user.email "rafiq@example.com"
git log --oneline9bc4968 Add word-list sentiment scorerরিমোট-ট্র্যাকিং ব্রাঞ্চ
প্রতিটা ক্লোনে তিন ধরনের ব্রাঞ্চ পয়েন্টার থাকে, আর টিমের বেশিরভাগ বিভ্রান্তি আসে এগুলো গুলিয়ে ফেলা থেকে:
| নাম | এটা কী | কে এটা সরায় |
|---|---|---|
main | আপনার লোকাল ব্রাঞ্চ | আপনি, যখন কমিট, মার্জ বা রিবেস করেন |
origin/main | একটা রিমোট-ট্র্যাকিং ব্রাঞ্চ: শেষবার রিমোটের সাথে কথা বলার সময় সেখানে main কোথায় ছিল, তার স্মৃতি | শুধু fetch, pull আর push |
রিমোটের main | আসল শেয়ার করা ব্রাঞ্চ | যে-ই পুশ করেন |
main আর origin/main-এর মধ্যে যোগসূত্রটা হলো আপস্ট্রিম (ট্র্যাকিং) সেটিং, যা push -u বা clone তৈরি করেছে। git branch -vv সেটা দেখায়:
git branch -vv
git branch -r* main 9bc4968 [origin/main] Add word-list sentiment scorer
origin/HEAD -> origin/main
origin/maingit remote show origin সরাসরি রিমোটকে জিজ্ঞেস করে তুলনা করে:
git remote show origin* remote origin
Fetch URL: /home/nadia/remotes/sentiment-api.git
Push URL: /home/nadia/remotes/sentiment-api.git
HEAD branch: main
Remote branch:
main tracked
Local branch configured for 'git pull':
main merges with remote main
Local ref configured for 'git push':
main pushes to main (up to date)fetch বনাম pull
রফিক একটা শব্দ যোগ করে পুশ করেন:
sed -i 's/"excellent"}/"excellent", "amazing"}/' sentiment.py
git commit -q -am "Count 'amazing' as positive"
git push -qনাদিয়া এর মধ্যে রিমোটের সাথে আর কথা বলেননি। তাঁর git status এখনো বলছে সব হালনাগাদ, কারণ এটা main-কে তুলনা করে শুধু তাঁর জমা রাখা origin/main-এর সাথে:
cd ~/team/nadia
git status -sb## main...origin/maingit fetch নতুন কমিট ডাউনলোড করে আর রিমোট-ট্র্যাকিং ব্রাঞ্চগুলো সরায়। আপনার ব্রাঞ্চ বা ফাইলে এটা কখনো হাত দেয় না, তাই এটা সবসময় নিরাপদ:
git fetch
git status -sb
git log --oneline main..origin/mainFrom /home/nadia/remotes/sentiment-api
9bc4968..135759b main -> origin/main
## main...origin/main [behind 1]
135759b Count 'amazing' as positivemain..origin/main মানে "যে কমিটগুলো origin/main-এ আছে কিন্তু main-এ নেই": ঠিক যা আপনি এখন নিতে যাচ্ছেন। পরিবর্তনটা নিজে দেখতে git diff main origin/main। সন্তুষ্ট হলে নিয়ে নিন। নাদিয়ার নিজের কোনো নতুন কমিট নেই, তাই ফাস্ট-ফরওয়ার্ড সম্ভব:
git merge --ff-only origin/mainUpdating 9bc4968..135759b
Fast-forward
sentiment.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)git pull আসলে এই দুটো ধাপ একসাথে: fetch, তারপর একীভূত করা। কীভাবে একীভূত করবে, সেটা আপনার পছন্দ:
| কমান্ড | কী করে | কখন ব্যবহার করবেন |
|---|---|---|
git fetch | ডাউনলোড করে, origin/* সরায়, আর কিছু বদলায় না | যেকোনো সময়; রিভিউ, তুলনা বা সিদ্ধান্তের আগে |
git pull --ff-only | ফেচ, তারপর শুধু ফাস্ট-ফরওয়ার্ড; দুই দিক আলাদা হয়ে গেলে এরর দিয়ে থেমে যায় | main-এ, যেখানে আপনার কোনো লোকাল কমিট থাকারই কথা নয় |
git pull --rebase | ফেচ, তারপর আপনার লোকাল কমিটগুলো রিমোটের কমিটের ওপরে আবার বসায় | নিজের ফিচার ব্রাঞ্চে, সরল ইতিহাস রাখতে |
git pull (মার্জ) | ফেচ, তারপর মার্জ; দুই দিক আলাদা হয়ে গেলে একটা মার্জ কমিট বানায় | যখন ব্রাঞ্চটা শেয়ার করা আর নতুন করে লেখা চলবে না |
Git 2.27 থেকে আলাদা হয়ে যাওয়া (diverged) ব্রাঞ্চে সাধারণ git pull আপনাকে বেছে নিতে বলে; একবারই ডিফল্ট ঠিক করে দিন: git config --global pull.rebase true (রিবেস) অথবা git config --global pull.ff only (ফাস্ট-ফরওয়ার্ড ছাড়া কিছুই নয়)।
পুশ ফিরিয়ে দেওয়া হলে
এবার দুজনেই একই সময়ে কাজ করছেন। রফিক একটা নেগেটিভ শব্দ যোগ করে আগে পুশ করেন:
cd ~/team/rafiq
git pull -q --ff-only
sed -i 's/"terrible"}/"terrible", "awful"}/' sentiment.py
git commit -q -am "Count 'awful' as negative"
git push -qএদিকে নাদিয়া একটা README লিখে পুশ করতে যান:
cd ~/team/nadia
echo "# Sentiment API" > README.md
git add README.md
git commit -q -m "Add README"
git pushTo /home/nadia/remotes/sentiment-api.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/home/nadia/remotes/sentiment-api.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.রিমোট রাজি হয় না, কারণ তাঁর পুশ নিলে রফিকের কমিটটা হারিয়ে যেত: নাদিয়ার main-এ সেটা নেই। এটা টিমকে রক্ষা করছে Git — জোর করে পার করার মতো কোনো এরর নয়। কখনো git push --force দিয়ে এর জবাব দেবেন না। ফেচ করুন, নিজের কাজ ওপরে বসান, আবার পুশ করুন:
git pull -q --rebase
git push -q
git log --oneline --graph* c9dd2d3 Add README
* 0ae0fca Count 'awful' as negative
* 135759b Count 'amazing' as positive
* 9bc4968 Add word-list sentiment scorerইতিহাস এখন একটা সরলরেখা: নাদিয়ার README কমিটটা রফিকের কমিটের পরে আবার বসানো হয়েছে (প্যারেন্ট বদলেছে বলে এর হ্যাশও নতুন)। দুজনে একই লাইন বদলালে রিবেস একটা কনফ্লিক্টে থেমে যেত; তখন বিগিনার পাতা ব্রাঞ্চ, মার্জ ও মার্জ কনফ্লিক্ট-এর মতো করে সমাধান করে git add আর git rebase --continue চালাতেন।
ফর্ক আর upstream রিমোট
নাদিয়া আর রফিক শেয়ার করা রিপোজিটরিতে পুশ করতে পারেন; তাঁরা কোলাবোরেটর। মিতু একজন শিক্ষার্থী, একটা ওপেন-সোর্স প্রজেক্টে কিছু ঠিক করতে চান, কিন্তু তাঁর লেখার অনুমতি নেই। GitHub-এ তিনি Fork-এ ক্লিক করেন: GitHub তাঁর অ্যাকাউন্টে রিপোজিটরির একটা কপি বানিয়ে দেয়, যেখানে তিনি পুশ করতে পারেন, আর নিজের ফর্ক থেকে মূল রিপোজিটরিতে পুল রিকোয়েস্ট খোলেন। ফর্ক আসলে একই ইতিহাসসহ আরেকটা বেয়ার রিপোজিটরি মাত্র:
git clone -q --bare ~/remotes/sentiment-api.git ~/remotes/mitu-sentiment-api.git
cd ~/team
git clone -q ~/remotes/mitu-sentiment-api.git mitu
cd mitu
git config user.name "Mitu Das"
git config user.email "mitu@example.com"
git remote add upstream ~/remotes/sentiment-api.git
git remote -vorigin /home/nadia/remotes/mitu-sentiment-api.git (fetch)
origin /home/nadia/remotes/mitu-sentiment-api.git (push)
upstream /home/nadia/remotes/sentiment-api.git (fetch)
upstream /home/nadia/remotes/sentiment-api.git (push)প্রচলিত নিয়ম: origin হলো আপনার ফর্ক (সেখানে পুশ করেন), upstream হলো মূল প্রজেক্ট (সেখান থেকে শুধু ফেচ করেন)। ফর্ক নিজে নিজে হালনাগাদ হয় না। মূল প্রজেক্ট এগিয়ে গেলে মিতু নিজের ফর্ক সিঙ্ক করেন:
cd ~/team/nadia
echo "Scores short English texts as positive or negative." >> README.md
git commit -q -am "Describe the project in the README"
git push -q
cd ~/team/mitu
git fetch -q upstream
git switch -q main
git merge --ff-only upstream/main
git push -q origin main
git log --oneline -1Updating c9dd2d3..d7da0ba
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
d7da0ba Describe the project in the READMEGitHub-এ Sync fork বোতাম বা gh repo sync ফর্কের ডিফল্ট ব্রাঞ্চের জন্য একই কাজ করে। ফর্কের main-কে upstream-এর হুবহু কপি রাখুন আর নিজের সব কাজ করুন ব্রাঞ্চে; তাহলে সিঙ্ক সবসময় একটা ফাস্ট-ফরওয়ার্ড।
পুল রিকোয়েস্টের ওয়ার্কফ্লো
টিমের কেউ ফিচার সরাসরি main-এ পুশ করেন না। প্রতিটা পরিবর্তন যায় একটা পুল রিকোয়েস্ট (PR) দিয়ে: একটা ব্রাঞ্চ, সাথে GitHub-এ একটা পাতা, যেখানে টিম পরিবর্তনটা নিয়ে আলোচনা করে, টেস্টের ফল দেখে, আর শেষে মার্জ করে। একটা ভালো চক্র দেখতে এমন:
issue #2 -> branch -> draft PR -> ready for review -> review
|
merge (squash) <- approved <- push fixes <- changes requested
|
delete branch, close issue #2, everyone pulls mainএকটা ব্রাঞ্চ আর একটা ড্রাফট PR
ইস্যু #2 বলছে "স্কোর পড়া কঠিন; একটা লেবেল ফেরত দিন"। রফিক কাজটা নেন একটা ফিচার ব্রাঞ্চে:
cd ~/team/rafiq
git pull -q --ff-only
git switch -c feature/labelSwitched to a new branch 'feature/label'cat >> sentiment.py <<'EOF'
def label(text: str) -> str:
"""'positive', 'negative' or 'neutral'."""
s = score(text)
if s > 0:
return "positive"
if s < 0:
return "negative"
return "neutral"
EOF
git commit -q -am "Add label() for positive/negative/neutral"
git push -u origin feature/labelTo /home/nadia/remotes/sentiment-api.git
* [new branch] feature/label -> feature/label
branch 'feature/label' set up to track 'origin/feature/label'.GitHub-এ একটা হলুদ ব্যানার Compare & pull request দেখায়। রফিক base হিসেবে main আর compare হিসেবে feature/label বাছেন, একটা বর্ণনা লেখেন, আর কাজ শেষ হওয়ার আগেই মতামত চান বলে বোতামের মেনু থেকে Create draft pull request বেছে নেন। ড্রাফট PR মার্জ করা যায় না, আর কোড ওনারদের কাছে তখনো রিভিউর অনুরোধ যায় না; Ready for review চাপলে এটা সাধারণ PR হয়ে যায়। রিভিউয়াররা বর্ণনা থেকেই শুরু করেন, তাই এটা চারটা প্রশ্নের উত্তর দেয়:
Add label() for positive/negative/neutral
Closes #2
What and why
score() returns a number that API clients have to interpret themselves.
label() maps it to "positive", "negative" or "neutral".
How to test
python -c "from sentiment import label; print(label('great course'))"
Risks
None for existing callers: score() is unchanged.Closes #2 একটা ক্লোজিং কিওয়ার্ড (close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved): PR-টা ডিফল্ট ব্রাঞ্চে মার্জ হলে ইস্যু #2 নিজে থেকেই বন্ধ হয়, আর PR-এর সাথে লিংক হয়ে যায়।
রিভিউ: আগে লোকালি, তারপর GitHub-এ
নাদিয়া রিভিউ করছেন। ছোট পরিবর্তনের জন্য GitHub-এ ডিফ পড়াই যথেষ্ট; এর চেয়ে বড় কিছু হলে ব্রাঞ্চটা চেকআউট করে চালিয়ে দেখুন। রিমোট ব্রাঞ্চের নাম দিয়ে সাধারণ একটা git switch চালালে সেটাকে ট্র্যাক করা একটা লোকাল ব্রাঞ্চ তৈরি হয়:
cd ~/team/nadia
git fetch -q
git switch feature/label
git log --oneline main..feature/label
python -c "from sentiment import label; print([label(t) for t in ['great course', 'awful wifi', 'it is ok']])"Switched to a new branch 'feature/label'
branch 'feature/label' set up to track 'origin/feature/label'.
acb6356 Add label() for positive/negative/neutral
['positive', 'negative', 'neutral']PR-এর Files changed ট্যাবে তিনি নির্দিষ্ট লাইনে মন্তব্য রাখেন, তারপর তিন ধরনের যেকোনো একটা হিসেবে রিভিউ জমা দেন:
| রিভিউ | অর্থ |
|---|---|
| Comment | রায় ছাড়া মতামত |
| Approve | "মার্জ করুন" (প্রয়োজনীয় অনুমোদনের সংখ্যায় গোনা হয়) |
| Request changes | মার্জ আটকে রাখে (যখন অনুমোদন বাধ্যতামূলক), যতক্ষণ না রিভিউয়ার অনুমোদন দেন বা রিভিউটা বাতিল (dismiss) করা হয় |
ছোট সংশোধনের জন্য তিনি কথায় বর্ণনা দেন না: লাইন মন্তব্যের ভেতরে suggestion ভাষার একটা কোড ব্লক লিখে একটা সাজেশন দেন। GitHub এটাকে ডিফ হিসেবে দেখায়, আর রফিক Commit suggestion দিয়ে সেটা প্রয়োগ করতে পারেন (বা কয়েকটা একসাথে এক কমিটে):
Magic numbers make the thresholds hard to tune. Could we name them?
```suggestion
if s > POSITIVE_THRESHOLD:
```তিনি Request changes জমা দেন, সারাংশে লেখেন "থ্রেশহোল্ডগুলোর নাম দিন, যাতে ML টিম এগুলো টিউন করতে পারে"।
ফিডব্যাকের সমাধান
রফিক একই ব্রাঞ্চে আরও কমিট পুশ করে জবাব দেন; PR নিজে থেকেই হালনাগাদ হয়। প্রতিটা মন্তব্যের উত্তর দিয়ে তিনি Resolve conversation চাপেন, তারপর Re-request review। রিভিউ চলার সময় ছোট, আলাদা কমিট আবার রিভিউ করা সহজ — পুরো কমিট নতুন করে লেখার চেয়ে; আর শেষে স্কোয়াশ মার্জ (নিচে দেখুন) এগুলো গুছিয়ে দেয়:
cd ~/team/rafiq
sed -i 's/^NEGATIVE = .*/&\nPOSITIVE_THRESHOLD = 0\nNEGATIVE_THRESHOLD = 0/' sentiment.py
sed -i 's/if s > 0:/if s > POSITIVE_THRESHOLD:/; s/if s < 0:/if s < NEGATIVE_THRESHOLD:/' sentiment.py
git commit -q -am "Name the label thresholds"
git push -q
git log --oneline main..feature/labelfd2277b Name the label thresholds
acb6356 Add label() for positive/negative/neutralনাদিয়া অনুমোদন দেন। অনুমোদন আর সবুজ চেক থাকলে PR মার্জ করা যায়।
মার্জ: তিনটা বোতাম
| পদ্ধতি | main-এর ইতিহাস | কোথায় ভালো |
|---|---|---|
| Create a merge commit | ব্রাঞ্চের সব কমিট, সাথে একটা মার্জ কমিট | প্রতিটা ধাপ রেখে দিতে; দীর্ঘদিন চলা ব্রাঞ্চ |
| Squash and merge | সব পরিবর্তন নিয়ে একটাই নতুন কমিট | বেশিরভাগ ফিচার PR: এক PR = এক কমিট, রিভার্ট করাও সহজ |
| Rebase and merge | ব্রাঞ্চের কমিটগুলো একটা একটা করে আবার বসানো, মার্জ কমিট ছাড়া | যে PR-এর প্রতিটা কমিট নিজেই পরিষ্কার আর অর্থবহ |
টিম যে পদ্ধতি ব্যবহার করে না, রিপোজিটরির অ্যাডমিন সেটা বন্ধ করে দিতে পারেন। নাদিয়া Squash and merge চাপলে GitHub যা করে, লোকালি তা হলো git merge --squash আর তারপর একটা কমিট। GitHub মেসেজে PR নম্বরটা যোগ করে দেয়। (Squash commit -- not updating HEAD মানে পরিবর্তনগুলো স্টেজ করা হয়েছে, কিন্তু এখনো কমিট হয়নি; git commit সেই একটা নতুন কমিট বানায়।)
cd ~/team/nadia
git switch -q main
git pull -q --ff-only
git merge --squash -q feature/label
git commit -q -m "Add label() for positive/negative/neutral (#3)"
git push -q
git log --onelineSquash commit -- not updating HEAD
bce912a Add label() for positive/negative/neutral (#3)
d7da0ba Describe the project in the README
c9dd2d3 Add README
0ae0fca Count 'awful' as negative
135759b Count 'amazing' as positive
9bc4968 Add word-list sentiment scorerমার্জের পর GitHub Delete branch দেখায় (রিপোজিটরির সেটিং চালু থাকলে নিজেই মুছে দেয়)। কমান্ড লাইন থেকে একটা রিমোট ব্রাঞ্চ মোছা:
git push origin --delete feature/labelTo /home/nadia/remotes/sentiment-api.git
- [deleted] feature/labelরফিকের ক্লোন এখনো মুছে ফেলা ব্রাঞ্চটা মনে রেখেছে। fetch --prune (এখানে pull --prune) বাসি রিমোট-ট্র্যাকিং ব্রাঞ্চ সরিয়ে দেয়, আর -vv দেখায় যে তাঁর লোকাল ব্রাঞ্চের আপস্ট্রিম এখন gone। মার্জটা স্কোয়াশ ছিল বলে তাঁর ব্রাঞ্চের কমিটগুলো main-এ নেই, তাই Git -d দিয়ে মুছতে রাজি হয় না; PR মার্জ হয়েছে নিশ্চিত হয়ে -D দিয়ে মুছুন:
cd ~/team/rafiq
git switch -q main
git pull -q --prune
git branch -vv
git branch -d feature/label feature/label fd2277b [origin/feature/label: gone] Name the label thresholds
* main bce912a [origin/main] Add label() for positive/negative/neutral (#3)
error: the branch 'feature/label' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/label'git branch -D feature/labelDeleted branch feature/label (was fd2277b).টিমের ফাইল: PR টেমপ্লেট আর CODEOWNERS
রিপোজিটরির কিছু ফাইল আসলে GitHub-এর জন্য নির্দেশনা। এগুলো থাকে .github/-এ আর কোডের সাথেই চলে, তাই কোডের মতোই এগুলোর রিভিউ হয়।
পুল রিকোয়েস্ট টেমপ্লেট
.github/pull_request_template.md থাকলে প্রতিটা নতুন PR-এর বর্ণনা শুরু হয় এর বিষয়বস্তু দিয়ে। একটা AI প্রজেক্টে জরুরি চেকগুলো কেউ যেন ভুলে না যান, তা নিশ্চিত করার সবচেয়ে সস্তা জায়গা এটাই:
cd ~/team/nadia
mkdir -p .github
cat > .github/pull_request_template.md <<'EOF'
## What and why
<!-- One or two sentences. Link the issue: "Closes #12". -->
## How to test
- [ ] `python -m pytest` passes
## AI checks
- [ ] No secrets, datasets or model files committed
- [ ] Prompt or model change: evaluation results attached
- [ ] API response shape unchanged, or the change is versioned
EOFএকই ধরনের অন্য ফাইল: .github/ISSUE_TEMPLATE/*.yml (বাধ্যতামূলক ঘরসহ ইস্যু ফর্ম, যেমন বাগ রিপোর্টে "model version"), CONTRIBUTING.md (কেউ ইস্যু বা PR খুললে এর লিংক দেখানো হয়) আর SECURITY.md (দুর্বলতা গোপনে কীভাবে জানাবেন)।
CODEOWNERS
CODEOWNERS ফাইল প্রতিটা পাথকে তার দায়িত্বে থাকা মানুষ বা টিমের সাথে মেলায়। কোনো PR একটা পাথ ছুঁলে তার মালিকদের কাছে নিজে থেকেই রিভিউর অনুরোধ যায়; "require review from Code Owners" নিয়ম চালু থাকলে তাঁদের একজনের অনুমোদন ছাড়া PR মার্জ হয় না। GitHub ফাইলটা খোঁজে PR-এর base ব্রাঞ্চে, প্রথমে .github/-এ, তারপর রিপোজিটরির রুটে, তারপর docs/-এ:
# file: .github/CODEOWNERS
# Pattern, then one or more owners (@user or @org/team). The LAST matching line wins.
* @nadia-rahman
/app/ @nadia-rahman @rafiq-islam
/prompts/ @bitbyte-ai/ml-team
/models/ @bitbyte-ai/ml-team
/evals/ @bitbyte-ai/ml-team @mitu-das
/.github/ @nadia-rahman
/.github/CODEOWNERS @nadia-rahman @rafiq-islam
uv.lockযা খেয়াল করবেন:
- শেষ মিলটাই খাটে।
/prompts/-এর একটা ফাইল*আর/prompts/দুটোর সাথেই মেলে; পরের লাইনটা সিদ্ধান্ত নেয়, তাই প্রম্পটের মালিক ML টিম। সাধারণ নিয়ম আগে লিখুন, নির্দিষ্টগুলো পরে। - মালিক ছাড়া প্যাটার্ন (
uv.lock) মানে এর কোনো মালিক নেই, তাই Dependabot-এর শুধু-লকফাইলের PR কাউকে বিরক্ত করে না। - মালিকদের রিপোজিটরিতে লেখার অনুমতি থাকতে হবে, আর টিমকে visible হতে হবে; নইলে লাইনটা চুপচাপ উপেক্ষিত হয় (ফাইলের পাতায় GitHub একটা এরর দেখায়)।
- সিনট্যাক্স
.gitignore-এর কাছাকাছি, কিন্তু!দিয়ে বাদ দেওয়া আর[a-z]রেঞ্জ কাজ করে না। - ফাইলটাকেও রক্ষা করুন (শেষের আগের লাইন): যিনি
CODEOWNERSবদলাতে পারেন, তিনি নিজেকে রিভিউ থেকে সরিয়েও দিতে পারেন।
রিলিজ নোটের বিভাগ
মার্জ হওয়া PR থেকে GitHub রিলিজ নোট লিখে দিতে পারে। .github/release.yml সেগুলোকে লেবেল অনুযায়ী ভাগ করে:
# file: .github/release.yml
changelog:
exclude:
labels:
- ignore-for-release
categories:
- title: Breaking changes
labels:
- breaking-change
- title: New features
labels:
- enhancement
- title: Bug fixes
labels:
- bug
- title: Other changes
labels:
- "*"এই ফাইলগুলোও বাকি সবকিছুর মতো PR দিয়েই যায়। এখানে নাদিয়া সরাসরি কমিট করছেন, কারণ সুরক্ষা এখনো চালু করা হয়নি:
git add .github
git commit -q -m "Add PR template, CODEOWNERS and release-note categories"
git push -q
git ls-files .github.github/CODEOWNERS
.github/pull_request_template.md
.github/release.ymlmain রক্ষা করা: ব্রাঞ্চ প্রোটেকশন আর রুলসেট
"কেউ main-এ পুশ করবে না" জাতীয় সমঝোতা টিকে থাকে কেবল তখনই, যখন সার্ভার তা প্রয়োগ করে। একটা বেয়ার রিপোজিটরি নিয়ম প্রয়োগ করতে পারে একটা সার্ভার-সাইড হুক দিয়ে: একটা স্ক্রিপ্ট, যা পুশ গ্রহণের আগে রিমোট চালায়। GitHub আপনাকে হুক বসাতে দেয় না; বদলে এমন সেটিং দেয় যা একই কাজ করে। এগুলো কী করে তা হাতে-কলমে বুঝতে, এই pre-receive হুকটা main-এ সরাসরি পুশ ফিরিয়ে দেয়:
cat > ~/remotes/sentiment-api.git/hooks/pre-receive <<'EOF'
#!/bin/sh
# কোনো ref বদলানোর আগে সার্ভারে চলে। শূন্য ছাড়া অন্য exit কোড পুরো পুশটাই ফিরিয়ে দেয়।
while read old new ref; do
if [ "$ref" = "refs/heads/main" ]; then
echo "main is protected: push a branch and open a pull request" >&2
exit 1
fi
done
EOF
chmod +x ~/remotes/sentiment-api.git/hooks/pre-receive
cd ~/team/rafiq
git pull -q --ff-only
echo "venv/" > .gitignore
git add .gitignore
git commit -q -m "Ignore venv"
git pushremote: main is protected: push a branch and open a pull request
To /home/nadia/remotes/sentiment-api.git
! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to '/home/nadia/remotes/sentiment-api.git'রফিকের কমিটটা এখনো তাঁর ক্লোনে আছে; তিনি সেটাকে একটা ব্রাঞ্চে সরান, নিজের main-কে রিমোটেরটার জায়গায় ফিরিয়ে আনেন, আর তার বদলে ব্রাঞ্চটা পুশ করেন, যা হুক অনুমতি দেয় (মেসেজটা শুধু নিশ্চিত করছে যে নতুন করে বানানো main এখনো origin/main-কে ট্র্যাক করছে):
git switch -q -c chore/gitignore
git branch -f main origin/main
git push -q -u origin chore/gitignore
git branch -vvbranch 'main' set up to track 'origin/main'.
* chore/gitignore ccde8df [origin/chore/gitignore] Ignore venv
main da45ba7 [origin/main] Add PR template, CODEOWNERS and release-note categoriesGitHub-এ এই সুরক্ষা, আর এর চেয়ে অনেক বেশি কিছু, পাবেন দুটো ফিচার থেকে। দুটোই একসাথে প্রযোজ্য হতে পারে, আর তখন সবচেয়ে কড়া নিয়মটাই খাটে:
| ব্রাঞ্চ প্রোটেকশন রুল (ক্লাসিক) | রুলসেট (সুপারিশকৃত) | |
|---|---|---|
| কোথায় | Settings → Branches | Settings → Rules → Rulesets |
| লক্ষ্য | প্রতি ব্রাঞ্চ প্যাটার্নে একটা রুল | ব্রাঞ্চ বা ট্যাগ, প্যাটার্ন দিয়ে বা "default branch"; Team/Enterprise-এ পুরো অর্গানাইজেশনের রুলসেট |
| স্তর | একটা ব্রাঞ্চে কেবল একটা রুল খাটে | একাধিক রুলসেট একসাথে খাটে |
| না মুছে বন্ধ করা | যায় না | যায়: enforcement Active / Disabled (Enterprise-এ Evaluate-ও) |
| ব্যতিক্রম | "do not allow bypassing" চেকবক্স | স্পষ্ট একটা bypass তালিকা (রোল, টিম, অ্যাপ) |
| কারা দেখতে পান | অ্যাডমিনরা | পড়ার অনুমতি আছে এমন যে কেউ |
| প্ল্যান | পাবলিক রিপোজিটরিতে ফ্রি; প্রাইভেটে লাগে Pro, Team বা Enterprise | পাবলিক রিপোজিটরিতে ফ্রি; প্রাইভেটে লাগে Pro, Team বা Enterprise |
main-এর জন্য শুরু করার মতো একটা ভালো রুলসেট:
| নিয়ম | কেন |
|---|---|
| Restrict deletions | কেউ ভুল করে main মুছে ফেলবে না |
| Block force pushes | প্রকাশিত ইতিহাস নতুন করে লেখা যাবে না |
| Require a pull request before merging: ১টা অনুমোদন, নতুন কমিট পুশ হলে পুরোনো অনুমোদন বাতিল, Code Owners-এর রিভিউ বাধ্যতামূলক, সব আলোচনা resolve করা বাধ্যতামূলক | প্রতিটা পরিবর্তন দ্বিতীয় একজন দেখেন, আর অনুমোদনটা চূড়ান্ত কোডের জন্য, আগের কোনো সংস্করণের জন্য নয় |
Require status checks to pass (যেমন test), মার্জের আগে ব্রাঞ্চ হালনাগাদ | ঠিক যে কোড মার্জ হবে, তার ওপরেই CI সবুজ হতে হবে (সেই CI আপনি বানাবেন GitHub Actions দিয়ে CI/CD পাতায়) |
| ঐচ্ছিক: linear history, signed commits, merge queue | সরল ইতিহাস; যাচাই করা লেখক; ব্যস্ত রিপোজিটরি, যেখানে নইলে PR-গুলো একে অন্যের সাথে পাল্লা দেয় |
রুলসেট JSON হিসেবে এক্সপোর্ট আর ইমপোর্ট করা যায় (Import a ruleset বোতাম, বা REST API), তাই একটা টিম নিজের স্ট্যান্ডার্ড রুলসেট একটা রিপোজিটরিতে রেখে সবখানে প্রয়োগ করতে পারে। ওপরের রুলসেটটাই API-এর ফরম্যাটে:
{
"name": "protect-main",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] }
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 1,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": true,
"require_last_push_approval": false,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [{ "context": "test" }]
}
}
],
"bypass_actors": []
}bypass তালিকা খালি মানে নিয়মগুলো অ্যাডমিনদের ওপরেও খাটে। আসল কথাটা এখানেই:
mainভাঙার সবচেয়ে সাধারণ উপায় হলো কোনো অ্যাডমিনের "চট করে একটা ফিক্স" সরাসরি পুশ করা। জরুরি অবস্থার জন্য পথ লাগলে একটা রোলকে bypass দিন, আর কে সেটা ব্যবহার করলেন তা রিভিউ করুন।
ইস্যু, লেবেল, মাইলস্টোন আর প্রজেক্ট
PR বহন করে কোড; ইস্যু বহন করে সেই কাজ, যা থেকে কোড আসে। GitHub-এর পরিকল্পনার ফিচারগুলো হালকা, আর সেজন্যই ছোট AI টিমের প্রায়ই আর কিছু লাগে না:
| ফিচার | কীসের জন্য | একটা AI প্রজেক্টে উদাহরণ |
|---|---|---|
| ইস্যু | একটা বাগ, কাজ বা আইডিয়া, আলোচনাসহ | "ব্যঙ্গাত্মক রিভিউতে মডেল neutral দেয়" |
| সাব-ইস্যু | বড় একটা ইস্যুকে ট্র্যাক করা যায় এমন টুকরোয় ভাঙা | "বাংলা সাপোর্ট যোগ" → টোকেনাইজার, শব্দতালিকা, ইভ্যালুয়েশন সেট |
| লেবেল | ফিল্টার আর রিলিজ নোটের জন্য একটা ট্যাগ | bug, enhancement, data, prompt, good first issue |
| মাইলস্টোন | নির্দিষ্ট তারিখসহ কিছু ইস্যু আর PR-এর একটা সেট | "v1.0 — পাবলিক বেটা" |
| অ্যাসাইনি | কাজটা কে করছেন | একজন মানুষ, যাতে কোনো কাজ "সবার" না হয়ে যায় |
| প্রজেক্ট | ইস্যু আর PR জুড়ে (এমনকি একাধিক রিপোজিটরি জুড়ে) একটা বোর্ড, টেবিল বা রোডম্যাপ, নিজস্ব ফিল্ডসহ | কলাম Todo / In progress / In review / Done; একটা ফিল্ড "Eval score" |
একই ওয়ার্কফ্লো gh CLI দিয়ে
ওপরের সবকিছুই টার্মিনাল থেকে করা যায় GitHub-এর gh CLI (ভার্সন 2.x) দিয়ে। এই কমান্ডগুলো github.com-এর সাথে কথা বলে, তাই আসল একটা রিপোজিটরি আর আগে gh auth login লাগে; এখানে এগুলো দেখানো হয়েছে, চালানো হয়নি:
gh auth login # ব্রাউজারে লগইন; git-এর জন্যও টোকেন রাখে
gh repo fork bitbyte-ai/sentiment-api --clone # ফর্ক, ক্লোন, আর upstream রিমোট যোগ
gh label create data --color 0e8a16 --description "Datasets and data pipelines"
gh issue create --title "Return a label, not just a score" --label enhancement --milestone "v1.0"
gh pr create --draft --base main --title "Add label() for positive/negative/neutral" --body-file pr.md
gh pr ready 3 # ড্রাফট -> রিভিউয়ের জন্য প্রস্তুত
gh pr checkout 3 # কারও PR লোকালি রিভিউ (ফর্ক থেকে এলেও)
gh pr review 3 --request-changes --body "Please name the thresholds."
gh pr review 3 --approve
gh pr checks 3 --watch # CI-এর জন্য অপেক্ষা
gh pr merge 3 --squash --delete-branch
gh api --method POST repos/bitbyte-ai/sentiment-api/rulesets --input ruleset.json
gh release create v1.0.0 --generate-notes # মার্জ হওয়া PR থেকে নোটসহ রিলিজভেতরে ভেতরে একটা PR আসলে একটা ref মাত্র। GitHub প্রতিটা PR-এর head প্রকাশ করে refs/pull/<number>/head হিসেবে, তাই সাধারণ Git দিয়েও সেটা ফেচ করা যায়: git fetch origin pull/3/head:pr-3।
রিলিজ আর রিলিজ নোট
GitHub-এ একটা রিলিজ হলো একটা অ্যানোটেটেড ট্যাগ, সাথে নোট আর ঐচ্ছিক ফাইলসহ একটা পাতা (একটা wheel, একটা মডেল কার্ড, একটা Docker ইমেজের লিংক)। ট্যাগের অংশটা সাধারণ Git। নাদিয়া নিজের main হালনাগাদ করে ট্যাগ দেন:
cd ~/team/nadia
git pull -q --ff-only
git log --oneline
git tag -a v1.0.0 -m "First public version: score() and label()"
git push origin v1.0.0da45ba7 Add PR template, CODEOWNERS and release-note categories
bce912a Add label() for positive/negative/neutral (#3)
d7da0ba Describe the project in the README
c9dd2d3 Add README
0ae0fca Count 'awful' as negative
135759b Count 'amazing' as positive
9bc4968 Add word-list sentiment scorer
To /home/nadia/remotes/sentiment-api.git
* [new tag] v1.0.0 -> v1.0.0একটু থামুন: লগে রফিকের .gitignore কমিটটা নেই। তাঁর PR এখনো কেউ মার্জ করেননি, তাই সেটা রিলিজেও নেই — আর ঠিক এটাই হওয়া উচিত। হুক ট্যাগটা অনুমতি দিয়েছে, কারণ সেটা শুধু refs/heads/main পাহারা দেয়; GitHub-এ একটা ট্যাগ রুলসেট (যেমন "শুধু মেইনটেইনাররা v* ট্যাগ বানাতে পারবেন, আর কেউ সেগুলো সরাতে পারবেন না") একইভাবে রিলিজ রক্ষা করে।
GitHub-এ Releases → Draft a new release-এ গিয়ে v1.0.0 বেছে Generate release notes চাপুন: GitHub আগের রিলিজের পর থেকে মার্জ হওয়া PR-গুলোর তালিকা দেয়, .github/release.yml-এর বিভাগ অনুযায়ী ভাগ করে, আর নতুন অবদানকারীদের নাম উল্লেখ করে। GitHub ছাড়া এর কাঁচামাল হলো ট্যাগ পর্যন্ত লগ:
git log --oneline --no-merges v1.0.0 --format="- %s (%an)"- Add PR template, CODEOWNERS and release-note categories (Nadia Rahman)
- Add label() for positive/negative/neutral (#3) (Nadia Rahman)
- Describe the project in the README (Nadia Rahman)
- Add README (Nadia Rahman)
- Count 'awful' as negative (Rafiq Islam)
- Count 'amazing' as positive (Rafiq Islam)
- Add word-list sentiment scorer (Nadia Rahman)অন্যরা যা ব্যবহার করে, তার জন্য সেমান্টিক ভার্সন ব্যবহার করুন: MAJOR.MINOR.PATCH, যেখানে আপনার API-এর রিকোয়েস্ট বা রেসপন্সে ভাঙন ধরানো পরিবর্তন হলে MAJOR বাড়ে। একটা AI সার্ভিসে মডেল বা প্রম্পট বদলালে নোটে সেটা লিখুন, কোড না বদলালেও: ক্লায়েন্টরা ভিন্ন উত্তর দেখবেন।
বাস্তবে কোথায় কাজে লাগে
- মিশ্র ভূমিকার AI টিম। CODEOWNERS প্রম্পট আর মডেলের পরিবর্তন পাঠায় ML টিমের কাছে, আর API-এর পরিবর্তন ব্যাকএন্ড ডেভেলপারদের কাছে; ফলে দুটোই ছোঁয়া একটা PR-এর প্রতিটা অংশ ঠিক মানুষটাই রিভিউ করেন।
- মানের দরজা হিসেবে রিকোয়ার্ড চেক। টিমগুলো ইউনিট টেস্টের পাশে একটা ইভ্যালুয়েশন জব যোগ করে ("গোল্ডেন সেটে অ্যাকুরেসি ১ পয়েন্টের বেশি কমতে পারবে না"); এটাকে রিকোয়ার্ড চেক বানালে এমন প্রম্পট-টুইক আটকে যায়, যা চুপচাপ উত্তর নষ্ট করে।
- ওপেন-সোর্স AI লাইব্রেরি (Hugging Face Transformers, LangChain, এমনকি FastAPI) পুরোপুরি চলে ফর্ক, PR,
good first issue-এর মতো লেবেল আর স্বয়ংক্রিয় রিলিজ নোটের ওপর। আপনার প্রথম অবদানও হুবহু ওপরের ফর্ক ওয়ার্কফ্লো মেনে চলবে। - কোডিং এজেন্ট। AI কোডিং অ্যাসিস্ট্যান্টরাও PR খোলে। রুলসেট, বাধ্যতামূলক রিভিউ আর CODEOWNERS তাদের ওপরও মানুষের মতোই খাটে — এগুলো রাখার আরেকটা কারণ।
সাধারণ ভুল
- ফেচ না করে
git status-এ ভরসা। "Up to date" মানে "শেষবার যা ফেচ করেছি, তার সাথে মিল আছে"। কিছু ঠিক করার আগেgit fetchচালান (বা PR-এর পাতা দেখুন)। - পুশ ফিরিয়ে দিলে
--forceদিয়ে জবাব। এতে সহকর্মীদের কমিট রিমোট থেকে মুছে যায়।git pull --rebaseকরে আবার পুশ করুন; নিজের ব্রাঞ্চ সত্যিই নতুন করে লিখে থাকলে--force-with-leaseব্যবহার করুন। - ফর্কের
main-এ কাজ করা। তখন ফর্কটা upstream থেকে আলাদা হয়ে যায়, আর প্রতিটা সিঙ্ক হয়ে দাঁড়ায় একটা মার্জ।mainপরিষ্কার রাখুন, কাজ করুন ব্রাঞ্চে। - বিশাল PR। ১,০০০ লাইনের ডিফ রিভিউয়াররা চোখ বুলিয়েই অনুমোদন দেন। PR ছোট রাখুন (কয়েকশো লাইন), প্রতিটার একটাই উদ্দেশ্য; ড্রাফট আগেভাগে খুলুন।
- ফুটোওয়ালা সুরক্ষা। বাধ্যতামূলক রিভিউ আছে কিন্তু "dismiss stale approvals" নেই — তাহলে অনুমোদনের পর কেউ নতুন কোড পুশ করতে পারেন; CODEOWNERS ফাইল কেউ রক্ষা না করলে যে কেউ মালিকদের সরিয়ে দিতে পারেন। নিজের রুলসেট কোডের মতোই রিভিউ করুন।
নিজে চেষ্টা করুন
- সহজ: রফিকের ক্লোনে
main-এ একটা কমিট করুন কিন্তু পুশ করবেন না, ফেচ করুন, আর একটাই কমান্ডে দেখান তাঁরmainorigin/main-এর চেয়ে কত কমিট এগিয়ে আর কত কমিট পিছিয়ে। (ইঙ্গিত:git rev-list --left-right --count।) - মাঝারি: মিতু নিজের ফর্কের একটা ব্রাঞ্চে একটা লেখা ঠিক করে সেখানে পুশ করেন। মেইনটেইনার হিসেবে নাদিয়া মিতুর ফর্ককে স্থায়ী রিমোট না বানিয়েই লোকালি রিভিউ করতে চান। ফর্কের পাথ থেকে সরাসরি ব্রাঞ্চটা
review/mituনামের একটা লোকাল ব্রাঞ্চে ফেচ করুন, আর এর যে কমিটগুলোmain-এ নেই সেগুলোর তালিকা দেখান। - কঠিন: সার্ভার-সাইড হুকটা এমনভাবে বাড়ান, যাতে এটা যেকোনো ব্রাঞ্চে ফোর্স পুশও (non-fast-forward আপডেট) ফিরিয়ে দেয়, কিন্তু ব্রাঞ্চ বানানো আর মোছা তখনো চলে। নতুন করে লেখা একটা ব্রাঞ্চ দিয়ে প্রমাণ করুন। (ইঙ্গিত: নতুন ব্রাঞ্চে
oldহলো চল্লিশটা শূন্য;git merge-base --is-ancestor "$old" "$new"সফল হলে আপডেটটা ফাস্ট-ফরওয়ার্ড।)
উত্তর
# সহজ
cd ~/team/rafiq
git switch -q main
echo "Contributions welcome." >> README.md
git commit -q -am "Invite contributions"
git fetch -q
git rev-list --left-right --count main...origin/main1 0বাঁয়ের সংখ্যা গোনে শুধু main-এ থাকা কমিট (এগিয়ে), ডানেরটা শুধু origin/main-এ থাকা কমিট (পিছিয়ে)। git status -sb একই জিনিস দেখায় [ahead 1] হিসেবে।
# মাঝারি
cd ~/team/mitu
git switch -q -c fix/readme-typo
sed -i 's/Scores short/Score short/' README.md
git commit -q -am "Fix README wording"
git push -q origin fix/readme-typo
cd ~/team/nadia
git fetch -q ~/remotes/mitu-sentiment-api.git fix/readme-typo:review/mitu
git log --oneline main..review/mitu1e3bafb Fix README wording# কঠিন
cat > ~/remotes/sentiment-api.git/hooks/pre-receive <<'EOF'
#!/bin/sh
zero=0000000000000000000000000000000000000000
while read old new ref; do
if [ "$ref" = "refs/heads/main" ]; then
echo "main is protected: push a branch and open a pull request" >&2
exit 1
fi
case "$ref" in
refs/heads/*)
if [ "$old" != "$zero" ] && [ "$new" != "$zero" ] && ! git merge-base --is-ancestor "$old" "$new"; then
echo "force push to ${ref#refs/heads/} rejected" >&2
exit 1
fi
;;
esac
done
EOF
cd ~/team/rafiq
git switch -q chore/gitignore
git commit -q --amend -m "Ignore the venv folder"
git push --forceremote: force push to chore/gitignore rejected
To /home/nadia/remotes/sentiment-api.git
! [remote rejected] chore/gitignore -> chore/gitignore (pre-receive hook declined)
error: failed to push some refs to '/home/nadia/remotes/sentiment-api.git'সংশোধিত (amend করা) কমিটটা পুরোনোটার বংশধর নয়, তাই হুক সেটা ফিরিয়ে দেয়। বানানো (old সব শূন্য) আর মোছা (new সব শূন্য) পার হয়ে যায়। GitHub-এ রুলসেটের "Block force pushes" ঠিক এই কাজটাই করে।
সারসংক্ষেপ
origin/mainহলো রিমোট সম্পর্কে আপনার ক্লোনের স্মৃতি; শুধুfetch,pullআরpushএটা হালনাগাদ করে। বিচার করার আগে ফেচ করুন।- পুশ ফিরিয়ে দেওয়া মানে অন্য কেউ আগে পুশ করেছেন:
git pull --rebase, তারপর পুশ। শেয়ার করা ব্রাঞ্চে কখনো ফোর্স পুশ নয়। - ফর্ক হলো সার্ভারে থাকা কপি:
originআপনার,upstreamমূল প্রজেক্ট; ফর্কেরmain-কে ফাস্ট-ফরওয়ার্ড কপি হিসেবে রাখুন। - প্রতিটা পরিবর্তন যায় ব্রাঞ্চ → (ড্রাফট) PR → রিভিউ → মার্জ পথে; স্কোয়াশ মার্জে প্রতি PR-এ একটা কমিট; মার্জ হওয়া ব্রাঞ্চ মুছুন আর prune করুন।
- টিমের নিয়ম থাকে ফাইল আর সেটিংয়ে: PR টেমপ্লেট,
CODEOWNERS(শেষ মিলটাই খাটে), বাধ্যতামূলক রিভিউ আর চেকসহ রুলসেট, আর অ্যানোটেটেড ট্যাগ থেকে বানানো রিলিজ।
এরপর: AI প্রজেক্টে Git: ডেটা, মডেল, LFS, DVC ও ফাঁস হওয়া সিক্রেট পাতায় থাকছে AI রিপোজিটরি কোথায় আলাদা: বড় ডেটা আর মডেল ফাইল, পুনরুৎপাদনযোগ্য এনভায়রনমেন্ট, আর কোনো key ইতিহাসে ঢুকে গেলে কী করবেন।