Git, ब्रांचेस और Pull Requests — टीमें कैसे काम करती हैं
git init से push और pull तक, .gitignore से .venv और .env को सुरक्षित रखना, छोटी फ़ीचर ब्रांचेस, साफ़ कमिट संदेश और Pull Requests — साथ ही कमिट संदेश जांचने वाला पायथन टूल।
आप कैसे सीखना चाहते हैं?
वह समस्या जिसे हम हल कर रहे हैं
एक एआई इंजीनियर ने एक हफ़्ता टिकट-ट्राएज (ticket-triage) प्रोजेक्ट पर काम करते हुए बिताया: प्रॉम्प्ट बदला, कीवर्ड्स की लिस्ट बढ़ाई, एक फ़ंक्शन को दोबारा लिखा। शुक्रवार को परिणाम सोमवार की तुलना में बदतर आए। किस बदलाव की वजह से ऐसा हुआ? फ़ोल्डर में triage.py, triage_v2.py, triage_final.py और triage_final_REAL.py फ़ाइलें मौजूद हैं — और किसी को नहीं पता कि किस फ़ाइल में क्या बदला गया था।
इसी बीच, एक साझा फ़ोल्डर (shared folder) में काम कर रहे सहकर्मी ने उसी फ़ाइल को एडिट करके उनका काम मिटा दिया। और एक महीने पहले किसी ने त्वरित परीक्षण के लिए सीधे कोड में एक API key पेस्ट कर दी थी, जो उस साझा फ़ोल्डर के ज़रिए दस अन्य लोगों तक पहुँच गई।
इन तीनों समस्याओं का एक ही समाधान है, और दुनिया की लगभग हर सॉफ़्टवेयर टीम इसका उपयोग करती है: Git के ज़रिए इतिहास (history) सुरक्षित रखना, प्रत्येक काम को अपनी अलग ब्रांच (branch) पर करना, और मुख्य कोडबेस में कुछ भी शामिल करने से पहले Pull Request के ज़रिए किसी अन्य साथी से उसकी समीक्षा (code review) कराना। यह अध्याय इस पूरे चक्र को सिखाता है — और अंत में हम एक ऐसा पायथन टूल लिखेंगे जो समीक्षक (reviewer) के एक महत्वपूर्ण काम को स्वचालित (automate) कर देता है।
इस अध्याय के अंत में आप कर पाएंगे
git init,add,commit,pushऔरpullका उपयोग करके किसी प्रोजेक्ट का इतिहास रखना और साझा करना.gitignoreकी मदद से.venvऔर.envको रिपॉजिटरी से बाहर रखना, और ग़लती से कमिट हो जाने पर उसे ठीक करना- छोटी और अल्पकालिक (short-lived) फ़ीचर ब्रांचेस पर काम करना और एक साफ़-सुथरा Pull Request खोलना
- ऐसे कमिट संदेश लिखना जिन्हें समीक्षक आसानी से समझ और पढ़ सकें
subprocessके ज़रिएgit logको पढ़कर कमिट संदेशों की वैधता जांचने वाला एक पायथन टूल लिखना
ज़रूरी शर्तें: एआई इंजीनियर ट्रैक, और uv के साथ डेव एनवायरनमेंट्स — विशेष रूप से uv init और uv run।
पहली बार का सेटअप
Git यह रिकॉर्ड रखता है कि प्रत्येक कमिट किसने किया है। इसलिए पहली बार काम शुरू करते समय आपको अपनी पहचान दर्ज करनी होती है। साथ ही, Git को यह निर्देश देना भी उचित रहता है कि वह नई रिपॉजिटरी की मुख्य ब्रांच का नाम main रखे, क्योंकि आजकल अधिकांश टीमें इसी का उपयोग करती हैं:
git config --global user.name "Nadia Karim"
git config --global user.email "[email protected]"
git config --global init.defaultBranch mainयहाँ अपने नाम और उस ईमेल पते का उपयोग करें जिससे आपका कोड-होस्टिंग प्लेटफ़ॉर्म (जैसे GitHub) अकाउंट पंजीकृत है। अंतिम लाइन के बिना, पुराने Git वर्ज़न्स मुख्य ब्रांच का नाम master रख देते हैं — जो काम तो करता है, लेकिन टीम के अन्य सदस्यों के वर्कफ़्लो से मेल नहीं खाएगा।
तीन जगहें: फ़ोल्डर, स्टेजिंग और इतिहास
पिछले अध्याय में uv init ने अपने आप एक Git रिपॉजिटरी बना दी थी। इस अध्याय का उदाहरण प्रोजेक्ट भी उसी तरह शुरू होता है:
uv init ticket-triage --python 3.12
cd ticket-triageGit के काम करने का तरीका तीन मुख्य जगहों पर आधारित होता है, और प्रत्येक कमांड किसी चीज़ को एक जगह से दूसरी जगह ले जाती है:
- वर्किंग ट्री (The working tree) — आपका फ़ोल्डर, जहाँ आप फ़ाइलों को एडिट करते हैं
- स्टेजिंग एरिया (The staging area) — अगली कमिट में क्या-क्या शामिल होगा उसकी सूची;
git addफ़ाइलों को यहाँ जोड़ता है - इतिहास (History) — कमिट्स की स्थायी श्रृंखला;
git commitस्टेजिंग एरिया की सामग्री को एक स्थायी कमिट में बदल देता है
स्टेजिंग के लिए एक अलग चरण की आवश्यकता क्यों है? क्योंकि किसी टास्क के दौरान हो सकता है कि आपने पाँच फ़ाइलों में बदलाव किए हों — जिनमें से दो फ़ाइलें किसी बग को ठीक करती हैं और तीन फ़ाइलें एक नया फ़ीचर जोड़ती हैं। स्टेजिंग एरिया आपको उन्हें दो अलग-अलग, अर्थपूर्ण कमिट्स में बाँटने की सुविधा देता है — जो कोड समीक्षक के लिए बेहद मूल्यवान होता है।
वर्तमान स्थिति देखने के लिए git status कमांड है, या संक्षिप्त रूप में git status --short।
.gitignore: जो कभी रिपॉजिटरी में नहीं जाना चाहिए
एआई प्रोजेक्ट्स में लगभग हमेशा एक .env फ़ाइल होती है जिसमें API keys रखी जाती हैं। मान लेते हैं कि प्रोजेक्ट में ऐसी ही एक फ़ाइल बनाई गई है (यहाँ sk-... केवल एक प्लेसहोल्डर है, वास्तविक key नहीं):
LLM_API_KEY=sk-...uv init द्वारा बनाई गई .gitignore फ़ाइल में .venv तो शामिल होता है, लेकिन .env नहीं। इसलिए पहला काम इसे फ़ाइल के अंत में जोड़ना है:
# Secrets
.envअब एक बार uv run main.py चलाएं (ताकि .venv और uv.lock तैयार हो जाएँ) और स्थिति की जाँच करें:
git status --short
?? .gitignore
?? .python-version
?? README.md
?? main.py
?? pyproject.toml
?? uv.lock?? का अर्थ है कि Git फ़ाइल को देख तो रहा है लेकिन अभी उसे ट्रैक नहीं कर रहा है। यहाँ सबसे महत्वपूर्ण बात यह है कि इस सूची में क्या नहीं है: कोई .venv नहीं है, कोई .env नहीं है। दोनों फ़ाइलें आपके फ़ोल्डर में मौजूद हैं, लेकिन Git उन्हें अनदेखा कर रहा है। (वास्तव में .venv के पास सुरक्षा की दो परतें होती हैं: जब uv एनवायरनमेंट बनाता है, तो वह उसके अंदर केवल * वाली एक छोटी .gitignore फ़ाइल भी छोड़ देता है।) और सूची में uv.lock मौजूद है — इसे कमिट किया जाना चाहिए, क्योंकि इसी के ज़रिए सहकर्मियों को बिल्कुल वही एनवायरनमेंट मिलता है।
तो सहकर्मियों को कैसे पता चलेगा कि उन्हें कौन सी keys की आवश्यकता है? इसका सामान्य तरीका एक .env.example फ़ाइल को कमिट करना है जिसमें keys के नाम तो होते हैं लेकिन मान (values) खाली होते हैं। कोई भी नया व्यक्ति इसे .env के रूप में कॉपी कर लेता है और अपनी व्यक्तिगत keys भर देता है।
एक बार कमिट किया गया सीक्रेट (secret) अगली कमिट में फ़ाइल डिलीट करने से नहीं मिटता। Git का मूल काम ही हर पिछली स्थिति को याद रखना है, इसलिए वह key पिछले कमिट में हमेशा के लिए दर्ज रहती है — कोई भी व्यक्तिgit showके ज़रिए उसे देख सकता है। यदि कोई key किसी रिमोट पर पुश हो चुकी है, तो एकमात्र सुरक्षित उपाय यह है कि संबंधित सेवा (service) के डैशबोर्ड पर जाएँ, उस key को रद्द (revoke) करें, और एक नई key जनरेट करें। यही कारण है कि फ़ाइल बनने से पहले ही.envको.gitignoreमें जोड़ देना अनिवार्य है।
पहला कमिट, और अच्छे कमिट संदेश
git add .
git commit -m "chore: scaffold project with uv"
[main (root-commit) 0f78e59] chore: scaffold project with uv
6 files changed, 35 insertions(+)
create mode 100644 .gitignore
create mode 100644 .python-version
create mode 100644 README.md
create mode 100644 main.py
create mode 100644 pyproject.toml
create mode 100644 uv.lock0f78e59 कमिट की पहचान है (उसके हैश की शुरुआत) — यह आपकी मशीन पर अलग होगी, क्योंकि इसमें समय और लेखक की जानकारी भी शामिल होती है।
कमिट संदेश भविष्य के पाठक के लिए लिखा जाता है — छह महीने बाद आपके अपने लिए, और आज के कोड समीक्षक के लिए। कई टीमें Conventional Commits नामक एक मानक शैली का पालन करती हैं:
type(scope): short summary in the imperative- type बताता है कि यह किस प्रकार का बदलाव है:
feat(नया फ़ीचर या क्षमता),fix(बग फिक्स),docs(दस्तावेज़ीकरण),refactor(कोड पुनर्गठन),test(परीक्षण),chore(रखरखाव या कॉन्फ़िगरेशन) - scope वैकल्पिक होता है, कोष्ठक में लिखा जाता है, और प्रोजेक्ट के उस हिस्से का नाम बताता है:
feat(triage): ... - summary आज्ञासूचक (imperative mood) रूप में लिखा जाता है ("add", "fix", "remove" — न कि "added" या "fixes"), 72 अक्षरों के भीतर, और अंत में कोई पूर्णविराम नहीं लगाया जाता
आमने-सामने तुलना करने पर अंतर साफ़ दिखता है:
wip
fixed stuff
Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals
fix(triage): lowercase ticket text before keyword matchingपहले तीन संदेशों पर Git कोई एरर नहीं देता — Git किसी भी टेक्स्ट को स्वीकार कर लेता है। लेकिन git log में पहले दो संदेश कुछ नहीं बताते, और तीसरा इतना लंबा है कि एक पंक्ति की सूची में कट जाता है। अंतिम संदेश को पढ़कर समीक्षक तुरंत समझ जाता है कि क्या बदला गया और कहाँ बदला गया। यदि "क्यों बदला गया" की व्याख्या के लिए अधिक जगह चाहिए, तो वह एक खाली पंक्ति के बाद कमिट संदेश के मुख्य भाग (body) में लिखी जाती है।
ब्रांचेस: हर काम का अपना अलग कमरा
main ब्रांच टीम की सबसे महत्वपूर्ण संपत्ति होती है: यहाँ मौजूद कोड हमेशा काम करने योग्य होना चाहिए और किसी भी समय डिप्लॉय किया जा सकता है। इसलिए कोई भी सीधे main पर काम नहीं करता। प्रत्येक कार्य को एक नई ब्रांच मिलती है:
git switch -c feat/priority-score
Switched to a new branch 'feat/priority-score'यहाँ triage.py लिखी जाती है:
def priority(text: str) -> str:
urgent_words = ("outage", "down", "breach")
if any(word in text for word in urgent_words):
return "high"
return "normal"git add triage.py
git commit -m "feat(triage): add keyword-based priority"
[feat/priority-score ded4456] feat(triage): add keyword-based priority
1 file changed, 5 insertions(+)
create mode 100644 triage.pyब्रांच के नाम भी इसी शैली का पालन करते हैं: feat/..., fix/..., docs/... — सूची देखते ही एक नज़र में समझ आ जाता है कि प्रत्येक ब्रांच किस बारे में है।
ब्रांचिंग रणनीति पर एक महत्वपूर्ण बात। एक समय था जब कई टीमें कई दीर्घकालिक ब्रांचेस — develop, release, hotfix — रखती थीं और जटिल नियमों के तहत कोड को उनके बीच स्थानांतरित करती थीं। अब अधिकांश टीमें सरल रास्ता अपनाती हैं: एक main ब्रांच, और उससे निकलने वाली छोटी, अल्पकालिक फ़ीचर ब्रांचेस जिनकी समीक्षा करके उन्हें एक या दो दिन में वापस मर्ज कर दिया जाता है। कोई ब्रांच जितनी अधिक समय तक अलग रहती है, main उतना ही आगे निकल जाती है, और वापस लाते समय मर्ज कॉन्फ्लिक्ट्स (conflicts) उतने ही बड़े होते हैं। यह कोर्स इसी सरल और आधुनिक रास्ते का पालन करता है।
रिमोट्स: push और pull
अब तक सब कुछ आपकी अपनी मशीन पर था। टीम के साथ साझा करने के लिए एक रिमोट (remote) रिपॉजिटरी की आवश्यकता होती है — आमतौर पर GitHub या किसी अन्य समान सेवा पर। वहाँ रिपॉजिटरी बनाने पर आपको एक वेब पता मिलता है, और आप लिखते हैं:
git remote add origin https://github.com/<your-account>/ticket-triage.gitइस अध्याय के सभी उदाहरणों को बिना किसी ऑनलाइन सेवा के परखा गया है: एक पड़ोसी फ़ोल्डर में एक "bare" रिपॉजिटरी बनाई गई और उसे रिमोट के रूप में उपयोग किया गया। Git की नज़र में दोनों एक ही चीज़ हैं, और आप अपनी मशीन पर भी इस तरह अभ्यास कर सकते हैं:
git init --bare ../remote.git
git remote add origin ../remote.gitपहली बार पुश करते समय -u फ़्लैग की ज़रूरत होती है ताकि Git को पता चले कि यह लोकल ब्रांच किस रिमोट ब्रांच से जुड़ी है:
git push -u origin main
branch 'main' set up to track 'origin/main'.
To ../remote.git
* [new branch] main -> mainफ़ीचर ब्रांच को भी उसी तरह पुश किया जाता है:
git push -u origin feat/priority-score
branch 'feat/priority-score' set up to track 'origin/feat/priority-score'.
To ../remote.git
* [new branch] feat/priority-score -> feat/priority-scoreअब कोई सहकर्मी रिपॉजिटरी को क्लोन करता है:
git clone remote.git teammate
Cloning into 'teammate'...
done.उनके फ़ोल्डर में README.md, main.py, pyproject.toml, uv.lock (और छिपी हुई फ़ाइलें) आ जाती हैं — लेकिन न तो .venv आता है और न ही .env। यह बिल्कुल सही है: वे अपना स्वयं का एनवायरनमेंट बनाने के लिए uv sync चलाते हैं, और .env.example की मदद से अपनी .env फ़ाइल तैयार करते हैं।
फ़ीचर के main में मर्ज होने के बाद (वास्तविक काम में यह तब होता है जब Pull Request मर्ज की जाती है — नीचे देखें), सहकर्मी नया कोड प्राप्त करते हैं:
git pull
Updating 0f78e59..5f802b4
Fast-forward
triage.py | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 triage.py(वास्तविक आउटपुट रिमोट के पूरे पाथ वाली From ... लाइन से शुरू होता है; इसे यहाँ छोड़ दिया गया है।) Fast-forward का अर्थ है कि सहकर्मी के पास अपने कोई नए कमिट नहीं थे, इसलिए Git ने उनकी main को सीधे आगे बढ़ा दिया।
इश्यूज़ और पुल रिक्वेस्ट्स (Issues and Pull Requests)
किसी कंपनी में, काम आमतौर पर एक इश्यू (issue) से शुरू होता है — कोड-होस्टिंग सेवा पर दर्ज किसी समस्या का क्रमांकित विवरण। उदाहरण के लिए: #12 — Uppercase keywords are not detected (tickets saying "OUTAGE" come through as normal).
इसके बाद पूरा चक्र इस प्रकार चलता है:
mainसे एक नई ब्रांच बनाएं:git switch -c fix/uppercase-keywords- छोटे, अर्थपूर्ण कमिट्स करें
- ब्रांच को रिमोट पर पुश करें
- सेवा की वेबसाइट पर, या कमांड लाइन से, एक Pull Request (PR) खोलें — एक ऐसा अनुरोध जो कहता है "मेरी ब्रांच के बदलावों को
mainमें शामिल करें" - समीक्षक टिप्पणियाँ (review comments) देता है, और आप उसी ब्रांच पर नए कमिट्स करके उनका समाधान करते हैं
- स्वीकृति (approval) मिलने और स्वचालित जाँचों (CI) के सफल होने के बाद, PR को मर्ज कर दिया जाता है और ब्रांच को डिलीट कर दिया जाता है
GitHub के कमांड-लाइन टूल gh के साथ, चौथा चरण ऐसा दिखता है (इसका वास्तविक आउटपुट आपके अकाउंट और रिपॉजिटरी पर निर्भर करता है, इसलिए यहाँ नहीं दिखाया गया है):
gh pr create --base main --title "fix(triage): detect uppercase keywords" --body-file PR.mdएक साफ़ और स्पष्ट PR विवरण (PR.md) समीक्षक के तीन बुनियादी सवालों का जवाब देता है — क्या बदला, क्यों बदला, और इसका परीक्षण कैसे किया गया:
## What
Lowercase ticket text before matching urgent keywords.
## Why
Tickets written in capitals ("OUTAGE") were scored as normal.
Closes #12
## How I tested
priority("Service DOWN") returned "normal" on main, "high" on this branch
uv run check_commits.py -> 0 of 2 commit messages need workCloses #12 लाइन एक सुविधाजनक सुविधा है: जब PR को डिफ़ॉल्ट ब्रांच में मर्ज किया जाता है, तो प्लेटफ़ॉर्म अपने आप इश्यू #12 को बंद कर देता है।
मेंटर्स कैसे रिव्यू करते हैं
इस कोर्स के प्रत्येक व्यावहारिक अभ्यास को एक समीक्षक की नज़र से देखा जाएगा, और किसी वास्तविक कंपनी में यही प्रश्न सामने आते हैं:
- क्या PR छोटी और एकल-उद्देश्यीय (single-purpose) है? एक फ़ीचर, एक बग, या एक रीफ़ैक्टरिंग — तीनों एक साथ नहीं। कुछ सौ पंक्तियों से अधिक होने पर रिव्यू की गुणवत्ता गिर जाती है, क्योंकि लोग बड़े बदलावों (diffs) में अपना ध्यान केंद्रित नहीं रख पाते।
- क्या कमिट संदेश एक सुसंगत इतिहास की तरह पढ़े जा सकते हैं?
wipऔरfixसे भरा इतिहास किसी के काम नहीं आता। - क्या कोई ऐसी चीज़ शामिल हो गई है जो नहीं होनी चाहिए थी?
.venv,.env,__pycache__, बड़ी डेटा फ़ाइलें — यदि इनमें से कुछ भी diff में दिखता है, तो PR वापस लौटा दी जाती है। - क्या ज़रूरी चीज़ें मौजूद हैं? यदि कोई नया पैकेज जोड़ा गया था, तो
pyproject.tomlऔरuv.lockके बदलाव diff में दिखने चाहिए। - क्या विवरण में बताया गया है कि इसका परीक्षण कैसे किया गया?
यदि अंतिम कमिट का संदेश ग़लत है, तो आप पुश करने से पहले उसे बदल सकते हैं:
git commit --amend -m "docs: describe the project in README"
git log --oneline -1
a18cc1a docs: describe the project in READMEपुश करने के बाद कमिट बदलने का मतलब इतिहास को दोबारा लिखना होता है, जिससे सहकर्मियों के लिए समस्याएँ पैदा हो सकती हैं; उस स्थिति में एक नया कमिट करना बेहतर होता है। कई टीमें PRs को "squash" करके मर्ज करती हैं — यानी ब्रांच के सभी कमिट्स एक में जुड़ जाते हैं, और PR का शीर्षक उसका कमिट संदेश बन जाता है। ऐसी स्थिति में PR का शीर्षक भी उसी मानक शैली में होना चाहिए।
एक संपूर्ण उदाहरण
समीक्षक के कार्यों में से एक काम मशीन को सौंपा जा सकता है: यह जांचना कि ब्रांच पर प्रत्येक कमिट संदेश नियमों का पालन करता है या नहीं। टीमें CI में इस तरह की जाँचें चलाती हैं, जिससे PR खुलते ही परिणाम सामने आ जाता है। नीचे दी गई check_commits.py ठीक यही काम करती है — यह git log चलाती है और केवल वर्तमान ब्रांच के उन कमिट्स को पढ़ती है जो main पर मौजूद नहीं हैं:
import re
import subprocess
import sys
ALLOWED_TYPES = {"feat", "fix", "docs", "refactor", "test", "chore"}
SUBJECT = re.compile(r"^(?P<type>[a-z]+)(\([a-z0-9-]+\))?: (?P<summary>.+)$")
MAX_LENGTH = 72
VAGUE = {"wip", "fix", "update", "changes", "stuff", "misc"}
def problems_in(subject: str) -> list[str]:
found = []
if subject.strip().lower() in VAGUE:
found.append("too vague for a reviewer")
if len(subject) > MAX_LENGTH:
found.append(f"longer than {MAX_LENGTH} characters ({len(subject)})")
match = SUBJECT.match(subject)
if match is None:
found.append("missing 'type: summary' prefix")
elif match["type"] not in ALLOWED_TYPES:
found.append(f"unknown type '{match['type']}'")
if subject.endswith("."):
found.append("ends with a period")
return found
def branch_subjects(base: str) -> list[str]:
result = subprocess.run(
["git", "log", "--reverse", "--format=%s", f"{base}..HEAD"],
capture_output=True,
encoding="utf-8",
)
if result.returncode != 0:
raise SystemExit(f"git log failed: {result.stderr.strip()}")
return result.stdout.splitlines()
def main() -> int:
subjects = branch_subjects("main")
failures = 0
for subject in subjects:
problems = problems_in(subject)
print(("ok " if not problems else "FAIL") + " " + subject)
for problem in problems:
print(f" - {problem}")
failures += 1 if problems else 0
print(f"{failures} of {len(subjects)} commit messages need work")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())इसका परीक्षण करने के लिए, main से पाँच कमिट्स वाली एक ब्रांच बनाई गई — जिनमें से कुछ में जानबूझकर ख़राब संदेश रखे गए। (--allow-empty किसी फ़ाइल को बदले बिना कमिट बना देता है; संदेशों की जाँच के लिए यह पर्याप्त है। वास्तविक काम में प्रत्येक कमिट कोड बदलाव लेकर आता है।)
git switch -c fix/uppercase-keywords
git commit --allow-empty -m "wip"
git commit --allow-empty -m "Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals"
git commit --allow-empty -m "fix(triage): lowercase text before matching."
git commit --allow-empty -m "feature: add tests"
git commit --allow-empty -m "test(triage): cover uppercase keywords"फिर, रिपॉजिटरी के अंदर से:
uv run check_commits.pyFAIL wip
- too vague for a reviewer
- missing 'type: summary' prefix
FAIL Fixed the bug where priority returned normal for tickets that say OUTAGE in capitals
- longer than 72 characters (84)
- missing 'type: summary' prefix
FAIL fix(triage): lowercase text before matching.
- ends with a period
FAIL feature: add tests
- unknown type 'feature'
ok test(triage): cover uppercase keywords
4 of 5 commit messages need workप्रोग्राम का एग्ज़िट कोड 1 है, क्योंकि कम से कम एक संदेश विफल हुआ। जब सब कुछ सही होता है, तो यह 0 होता है।
इस स्क्रिप्ट के महत्वपूर्ण तकनीकी निर्णय:
main..HEAD — यह दो बिंदुओं वाली रेंज (range) बताती है "वे कमिट्स जो HEAD में हैं लेकिन main में नहीं हैं"। दूसरे शब्दों में, केवल इस ब्रांच का अपना काम। पूरे इतिहास की जाँच करने से आपकी PR उन पुराने कमिट्स के कारण विफल हो जाती जो महीनों पहले अन्य लोगों ने लिखे थे। --reverse उन्हें सबसे पुराने से नए के क्रम में व्यवस्थित करता है, जिस क्रम में काम किया गया था।
--format=%s — %s का अर्थ है कमिट की केवल पहली पंक्ति (विषय/subject)। कोई हैश, तारीख या लेखक नहीं — इसलिए पार्स करने की कोई आवश्यकता नहीं; प्रत्येक पंक्ति एक संदेश है।
encoding="utf-8" — text=True के साथ, पायथन ऑपरेटिंग सिस्टम की डिफ़ॉल्ट एन्कोडिंग का उपयोग करता है, जो Windows पर अक्सर UTF-8 नहीं होती। यदि कोई कमिट संदेश में हिंदी, बंगाली या इमोजी लिखता है, तो आउटपुट टूट सकता है। स्पष्ट एन्कोडिंग तय करने से यह अनिश्चितता समाप्त हो जाती है।
check=True के बजाय अपना स्वयं का संदेश — जब git विफल होता है (रिपॉजिटरी के बाहर चलाने पर, या main नाम की कोई ब्रांच न होने पर), तो स्क्रिप्ट लंबे पायथन ट्रेसबैक के बजाय git का अपना संक्षिप्त एरर संदेश दिखाती है। SystemExit में स्ट्रिंग पास करने से पायथन उसे stderr में प्रिंट करता है और एग्ज़िट कोड 1 के साथ रुक जाता है।
एग्ज़िट कोड — CI टूल आउटपुट को नहीं पढ़ता; वह केवल यह देखता है कि प्रोग्राम 0 कोड के साथ समाप्त हुआ या नहीं। इसलिए main() एक संख्या लौटाता है, और sys.exit उसे ऑपरेटिंग सिस्टम को सौंप देता है।
जब यह काम न करे
Author identity unknown और उसके बाद fatal: unable to auto-detect email address Git को नहीं पता कि कमिट पर किसका नाम लिखना है। अध्याय की शुरुआत में दिए गए git config --global user.name ... और user.email ... कमांड एक बार चलाएं।
error: src refspec main does not match any अभी तक कोई कमिट नहीं किया गया है, इसलिए main ब्रांच वास्तव में अस्तित्व में नहीं है — पुश करने के लिए कुछ नहीं है। पहले git add और git commit करें, फिर पुश करें। यही संदेश तब भी दिखाई देता है यदि ब्रांच का नाम वास्तव में master हो; git branch से जांचें कि आप किस ब्रांच पर हैं।
fatal: The current branch feat/x has no upstream branch. एक नई ब्रांच को पहली बार पुश किया जा रहा है, और Git को नहीं पता कि इसे किस रिमोट ब्रांच में भेजना है। एरर संदेश खुद समाधान बताता है: git push --set-upstream origin feat/x (या संक्षेप में git push -u origin feat/x)। इसके बाद साधारण git push ही पर्याप्त होगा।
! [rejected] main -> main (fetch first) आपके द्वारा पिछली बार फ़ेच करने के बाद किसी अन्य व्यक्ति ने उसी ब्रांच पर नया कोड पुश कर दिया है। पहले git pull के ज़रिए उनके काम को शामिल करें, फिर पुश करें। इसे ठीक करने के लिए कभी भी git push --force न चलाएं — यह आपके सहकर्मी के काम को पूरी तरह मिटा देता है।
warning: in the working copy of 'main.py', LF will be replaced by CRLF the next time Git touches it Windows पर यह एक चेतावनी है, एरर नहीं। Windows और macOS/Linux लाइनों को अलग-अलग कैरेक्टर्स (CRLF बनाम LF) से समाप्त करते हैं, और Git आपको बता रहा है कि वह उनका सामंजस्य बैठाएगा। कमिट सफलतापूर्वक पूरा हो गया है।
.env ग़लती से कमिट हो गया git rm --cached .env चलाएं — यह फ़ाइल को डिस्क पर सुरक्षित रखता है और केवल Git की ट्रैकिंग से हटाता है (आउटपुट rm '.env' होगा) — फिर .env को .gitignore में जोड़ें और कमिट करें। लेकिन याद रखें, key अभी भी पुराने कमिट में मौजूद है। यदि इसे पुश किया जा चुका है, तो key को रद्द करने और एक नई key बनाने के अलावा कोई अन्य सुरक्षित विकल्प नहीं है।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
Three messages are checked against the chapter's SUBJECT pattern. What prints?
import re
SUBJECT = re.compile(r"^(?P<type>[a-z]+)(\([a-z0-9-]+\))?: (?P<summary>.+)$")
for subject in ["fix: handle empty input", "Fix: handle empty input", "fix:handle empty input"]:
print(SUBJECT.match(subject) is not None)- A`True`, `False`, `True`
- B`True`, `True`, `True`
- C`True`, `True`, `False`
- D`True`, `False`, `False`
A colleague "fixed" this function in check_commits.py. Now CI is green even on a branch with wip and feature: add tests commits, and the output is 0 of 0 commit messages need work. Where is the bug?
import subprocess
def branch_subjects(base: str) -> list[str]:
result = subprocess.run(
["git", "log", "--reverse", "--format=%s", f"HEAD..{base}"],
capture_output=True,
encoding="utf-8",
)
if result.returncode != 0:
raise SystemExit(f"git log failed: {result.stderr.strip()}")
return result.stdout.splitlines()- AThe range is reversed: `HEAD..main` means "in `main` but not on this branch", so none of the branch's own commits come back; it should be `main..HEAD`
- B`--reverse` drops commits; removing it will show them
- CThe `returncode` check is discarding every commit; it needs `check=True`
- DWith `encoding="utf-8"`, `git` produces no output; it needs `text=True`
A PR description contains Closes #12. What does it do?
- AWhen the PR is merged into the default branch, the code-hosting service closes issue #12 by itself and keeps the two linked
- BIssue #12 is closed as soon as the PR is opened
- CNothing — it is just a note for the reviewer; no machine reads it
- DGit deletes commit number 12 from history
प्ले प्रो का हिस्सा है
+30 XP इंतज़ार में
प्रो के साथ यह अध्याय खेलें
पढ़ना मुफ़्त है, और पूरा अध्याय खुला है। हर मुफ़्त कोर्स के पहले 5 अध्याय मुफ़्त में खेले जा सकते हैं। अध्याय 6 से प्रश्नों के उत्तर देना, XP कमाना और स्ट्रीक बढ़ाना प्रो का हिस्सा है।
प्रो से प्ले अनलॉक करेंआपकी बारी
अपनी मशीन पर एक प्रोजेक्ट बनाएं और पूरे चक्र को एक बार चलाएं:
uv init my-triage --python 3.12से प्रोजेक्ट बनाएं,.envको.gitignoreमें जोड़ें, एक.env.exampleफ़ाइल रखें (जिसमें केवलLLM_API_KEY=हो), पुष्टि करें किgit status --shortमें.envया.venvनहीं दिख रहा है, और पहला कमिट करें- एक बेयर (bare) रिपॉजिटरी को रिमोट के रूप में जोड़ें (
git init --bare ../my-triage-remote.git) औरmainको पुश करें feat/priority-scoreनामक एक ब्रांच बनाएं और इस अध्याय केtriage.pyऔरcheck_commits.pyको ठीक तीन कमिट्स में जोड़ें, जिनमें से प्रत्येक नियमों का पालन करता हो; ब्रांच को पुश करें- एक
PR.mdलिखें — What, Why, How I tested — और यदि आपके पास कोड-होस्टिंग अकाउंट है, तो इस विवरण के साथ वहाँ एक वास्तविक PR खोलें
ब्रांच पर, uv run check_commits.py ठीक इस पंक्ति के साथ समाप्त होना चाहिए, जिसके पहले तीन ok पंक्तियाँ हों:
0 of 3 commit messages need workफिर जानबूझकर एक ख़राब कमिट करें (git commit --allow-empty -m "wip"), इसे दोबारा चलाकर देखें कि कौन सी दो कमियाँ पकड़ी जाती हैं, और इसे git commit --amend -m "..." से ठीक करें। या यह आज़माएँ: स्क्रिप्ट में main..HEAD को HEAD..main से बदलें, देखें कि यह क्या प्रिंट करता है, और समझें कि CI जाँच के लिए यह सबसे ख़तरनाक प्रकार की ग़लती क्यों है।
चरण 6 / 6
चुनौती — अध्याय की क्विज़
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
प्रो के साथ खेलें