उदाहरण के लिए, मान लीजिए हम एक लॉगिन फॉर्म को लागू करने वाली फीचर पर काम कर रहे हैं, और हमने निम्नलिखित चार कमिट्स बनाए:

फीचर पूरा हो जाने पर, पूरे प्रोजेक्ट के संदर्भ में ये कमिट्स बहुत सूक्ष्म होते हैं। हमें भविष्य में यह जानने की आवश्यकता नहीं है कि विकास के दौरान कोई बग आया था जिसे ठीक कर दिया गया। मेन ब्रांच में साफ इतिहास सुनिश्चित करने के लिए, हम इन कमिट्स को एक ही कमिट में स्क्वैश कर देते हैं:

Git में कमिट्स को कैसे स्क्वैश करें: इंटरैक्टिव रीबेस
कमिट्स को स्क्वैश करने का सबसे आम तरीका इंटरैक्टिव रीबेस का उपयोग करना है। हम इसे इस कमांड से शुरू करते हैं:
git rebase -i HEAD~<number_of_commits>
<number_of_commits> को उन कमिट्स की संख्या से बदलें जिन्हें हम स्क्वैश करना चाहते हैं।
हमारे मामले में चार कमिट्स हैं, इसलिए कमांड होगा:
git rebase -i HEAD~4
यह कमांड चलाने पर एक इंटरैक्टिव कमांड-लाइन एडिटर खुल जाएगा:

ऊपरी भाग में कमिट्स दिखते हैं, जबकि निचला हिस्सा कमिट्स को स्क्वैश करने के तरीके पर टिप्पणियाँ रखता है।
हमें चार कमिट्स दिखाई देते हैं। प्रत्येक के लिए हमें तय करना है कि कौन-सा कमांड चलाएँ। यहाँ हम pick (p) और squash (s) कमांड्स पर ध्यान देते हैं। इन चारों को एक कमिट में स्क्वैश करने के लिए, हम पहले को पिक कर सकते हैं और बाकी तीनों को स्क्वैश कर सकते हैं।
हम कमांड्स लागू करने के लिए हर कमिट के पहले लिखे टेक्स्ट को बदलते हैं—विशेष रूप से दूसरे, तीसरे और चौथे कमिट के लिए pick को s या squash में बदलते हैं। ये संपादन करने के लिए, हमें कीबोर्ड पर i दबाकर कमांड-लाइन टेक्स्ट एडिटर में “INSERT” मोड में प्रवेश करना होगा:

i दबाने के बाद, नीचे -- INSERT -- टेक्स्ट दिखाई देगा, जो दर्शाता है कि हम इनसर्ट मोड में आ गए हैं। अब हम एरो कीज़ से कर्सर हिला सकते हैं, अक्षर मिटा सकते हैं और सामान्य टेक्स्ट एडिटर की तरह टाइप कर सकते हैं:

परिवर्तनों से संतुष्ट होने पर, कीबोर्ड पर Esc दबाकर इनसर्ट मोड से बाहर निकलें। अगला कदम परिवर्तनों को सहेजना और एडिटर से बाहर निकलना है। इसके लिए, पहले : दबाएँ ताकि एडिटर को पता चले कि हम कोई कमांड चलाना चाहते हैं:

अब एडिटर के नीचे सेमीकोलन : दिखाई देगा जो हमें कमांड टाइप करने के लिए प्रेरित करता है। परिवर्तनों को सहेजने के लिए हम w कमांड का उपयोग करते हैं, जो "write" का संक्षेप है। एडिटर बंद करने के लिए q का उपयोग करें, जो "quit" का संक्षेप है। इन कमांड्स को मिलाकर wq भी टाइप किया जा सकता है:

कमांड चलाने के लिए Enter दबाएँ। यह क्रिया वर्तमान एडिटर को बंद कर देगी और एक नया एडिटर खोलेगी जहाँ हम नए स्क्वैश किए गए कमिट के लिए कमिट संदेश दर्ज कर सकते हैं। एडिटर उन चार कमिट्स के संदेशों को मिलाकर एक डिफॉल्ट संदेश दिखाएगा जिन्हें हम स्क्वैश कर रहे हैं:

मैं सुझाव देता हूँ कि संदेश को संशोधित कर लें ताकि यह उन संयुक्त कमिट्स से किए गए परिवर्तनों को सटीक रूप से दर्शाए—आखिर स्क्वैश करने का उद्देश्य एक साफ और आसानी से पढ़े जाने योग्य इतिहास बनाए रखना ही है।
एडिटर के साथ इंटरैक्ट करने और संदेश संपादित करने के लिए, हम फिर से i दबाकर एडिट मोड में जाते हैं और मनचाहा संपादन करते हैं।

इस मामले में, हम कमिट संदेश को "Implement login form." से बदल देते हैं। एडिट मोड से बाहर निकलने के लिए Esc दबाएँ। फिर : दबाकर, wq कमांड टाइप कर, और Enter दबाकर परिवर्तन सहेजें।
कमिट इतिहास कैसे देखें
आमतौर पर, पूरे कमिट इतिहास को याद रखना चुनौतीपूर्ण हो सकता है। कमिट इतिहास देखने के लिए हम git log कमांड का उपयोग कर सकते हैं। बताए गए उदाहरण में, स्क्वैश करने से पहले git log चलाने पर यह दिखेगा:

कमिट्स की सूची में नेविगेट करने के लिए ऊपर और नीचे एरो कीज़ का उपयोग करें। बाहर निकलने के लिए q दबाएँ।
git log का उपयोग कर हम स्क्वैश की सफलता की पुष्टि कर सकते हैं। स्क्वैश के बाद इसे चलाने पर नए संदेश के साथ एक ही कमिट दिखाई देगा:

स्क्वैश किए गए कमिट को पुश करना
ऊपर दिया गया कमांड लोकल रिपोजिटरी पर कार्य करेगा। रिमोट रिपोजिटरी को अपडेट करने के लिए हमें अपने बदलाव पुश करने होंगे। लेकिन, क्योंकि हमने कमिट इतिहास बदल दिया है, हमें --force विकल्प का उपयोग कर फोर्स पुश करना होगा:
git push --force origin feature/login-form
फोर्स पुश रिमोट ब्रांच पर कमिट इतिहास को ओवरराइट कर देगा और उस ब्रांच पर काम कर रहे अन्य लोगों के काम में बाधा डाल सकता है। ऐसा करने से पहले टीम से संवाद करना अच्छी प्रथा है।
फोर्स पुश करने का एक सुरक्षित तरीका, जो सहयोगियों में व्यवधान के जोखिम को कम करता है, --force-with-lease विकल्प का उपयोग करना है:
git push --force-with-lease origin feature/login-form
यह विकल्प सुनिश्चित करता है कि हम केवल तभी फोर्स पुश करें जब हमारी अंतिम fetch या pull के बाद रिमोट ब्रांच अपडेट न हुई हो।
विशिष्ट कमिट्स को स्क्वैश करना
मान लीजिए हमारे पास पाँच कमिट्स हैं:

मान लें कि हम कमिट 1, 2, और 5 को रखना चाहते हैं और कमिट 3 और 4 को स्क्वैश करना चाहते हैं।

इंटरैक्टिव रीबेस का उपयोग करते समय, स्क्वैश के लिए चिह्नित कमिट्स सीधे पहले वाले कमिट के साथ मिलाए जाते हैं। इस मामले में, हमें Commit4 को स्क्वैश करना है ताकि वह Commit3 में विलय हो जाए।
ऐसा करने के लिए, हमें उन दोनों कमिट्स को शामिल करते हुए इंटरैक्टिव रीबेस शुरू करना होगा। यहाँ तीन कमिट्स पर्याप्त हैं, इसलिए हम यह कमांड उपयोग करते हैं:
git rebase -i HEAD~3
फिर, हम Commit4 को s पर सेट करते हैं ताकि वह Commit3 के साथ स्क्वैश हो जाए:

यह कमांड चलाने और कमिट्स सूचीबद्ध करने के बाद, हम देखते हैं कि कमिट 3 और 4 आपस में स्क्वैश हो गए हैं जबकि बाकी बिना बदले रहे।

किसी विशेष कमिट से स्क्वैश करना
कमांड git rebase -i HEAD~3 में HEAD सबसे हाल के कमिट का संक्षिप्त नाम है। ~3 सिंटैक्स किसी कमिट के पूर्वज को निर्दिष्ट करने के लिए उपयोग होता है। उदाहरण के लिए, HEAD~1 HEAD कमिट के पैरेंट को संदर्भित करता है।

इंटरैक्टिव रीबेस में, विचार किए जाने वाले कमिट्स उस कमिट तक के सभी पूर्वजों को शामिल करते हैं जिसे कमांड में निर्दिष्ट किया गया है। ध्यान दें कि निर्दिष्ट कमिट स्वयं शामिल नहीं होता:

HEAD के बजाय, हम सीधे किसी कमिट हैश को निर्दिष्ट कर सकते हैं। उदाहरण के लिए, Commit2 का हैश dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf है, तो कमांड:
git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
Commit2 के बाद किए गए सभी कमिट्स को ध्यान में रखते हुए रीबेस शुरू करेगा। इसलिए, यदि हम किसी विशेष कमिट से रीबेस शुरू करना चाहते हैं और उस कमिट को शामिल भी करना चाहते हैं, तो हम यह कमांड उपयोग कर सकते हैं:
git rebase -i <commit-hash>~1
कमिट्स को स्क्वैश करते समय कॉन्फ्लिक्ट्स सुलझाना
जब हम कमिट्स को स्क्वैश करते हैं, तो हम कई कमिट परिवर्तनों को एक कमिट में मिलाते हैं, जिससे ओवरलैपिंग या काफी भिन्न परिवर्तनों की स्थिति में कॉन्फ्लिक्ट्स हो सकते हैं। यहाँ कुछ सामान्य परिदृश्य हैं जहाँ कॉन्फ्लिक्ट्स उत्पन्न हो सकते हैं:
- ओवरलैपिंग परिवर्तन: यदि स्क्वैश किए जा रहे दो या अधिक कमिट्स ने किसी फ़ाइल की एक ही पंक्तियों या आपस में संबंधित पंक्तियों को संशोधित किया है, तो Git इन परिवर्तनों को स्वतः सामंजस्य नहीं बिठा पाएगा।
- परिवर्तनों की भिन्न स्थिति: यदि एक कमिट किसी कोड को जोड़ता है और दूसरा कमिट उसी कोड को संशोधित या हटा देता है, तो इन कमिट्स को स्क्वैश करने पर कॉन्फ्लिक्ट्स पैदा हो सकते हैं जिनका समाधान करना होगा।
- रीनेमिंग और संशोधन: यदि कोई कमिट किसी फ़ाइल का नाम बदलता है और बाद के कमिट्स उसी फ़ाइल के पुराने नाम पर परिवर्तन करते हैं, तो इन कमिट्स को स्क्वैश करने से Git भ्रमित हो सकता है और कॉन्फ्लिक्ट उत्पन्न हो सकता है।
- बाइनरी फ़ाइलों में परिवर्तन: बाइनरी फ़ाइलें टेक्स्ट-आधारित डिफ टूल्स से अच्छी तरह मर्ज नहीं होतीं। यदि कई कमिट्स एक ही बाइनरी फ़ाइल को बदलते हैं और हम उन्हें स्क्वैश करने की कोशिश करते हैं, तो Git इन परिवर्तनों को स्वचालित रूप से नहीं मिला पाएगा और कॉन्फ्लिक्ट हो सकता है।
- जटिल इतिहास: यदि कमिट्स का इतिहास कई मर्ज, ब्रांचिंग या बीच में रीबेस के कारण जटिल है, तो स्क्वैश करने पर नॉन-लीनियर परिवर्तनों के कारण कॉन्फ्लिक्ट्स हो सकते हैं।
स्क्वैश करते समय, Git प्रत्येक परिवर्तन को एक-एक करके लागू करने की कोशिश करेगा। प्रक्रिया के दौरान यदि कॉन्फ्लिक्ट्स मिलते हैं, तो यह रुक जाएगा और हमें उन्हें सुलझाने देगा।
कॉन्फ्लिक्ट्स को <<<<<< और >>>>>> कॉन्फ्लिक्ट मार्कर्स से चिह्नित किया जाएगा। कॉन्फ्लिक्ट्स को संभालने के लिए, हमें फ़ाइलें खोलकर मैन्युअल रूप से तय करना होगा कि कोड के किस हिस्से को रखना है।
कॉन्फ्लिक्ट्स सुलझाने के बाद, हमें git add कमांड का उपयोग करके हल की गई फ़ाइलों को स्टेज करना होगा। फिर हम निम्न कमांड से रीबेस जारी रख सकते हैं:
git rebase --continue
Git कॉन्फ्लिक्ट्स पर अधिक जानकारी के लिए, इस ट्यूटोरियल को देखें: Git में मर्ज कॉन्फ्लिक्ट्स कैसे हल करें.
रीबेस के साथ स्क्वैश करने के विकल्प
git merge --squash कमांड, कई कमिट्स को एक कमिट में मिलाने के लिए git rebase -i का एक वैकल्पिक तरीका है। यह कमांड विशेष रूप से तब उपयोगी है जब हम किसी ब्रांच से बदलावों को मेन ब्रांच में मिलाना चाहते हैं और सभी व्यक्तिगत कमिट्स को एक में स्क्वैश करना चाहते हैं। यहाँ git merge के साथ स्क्वैश करने का एक अवलोकन है:
- हम उस लक्ष्य ब्रांच पर जाते हैं जिसमें हमें परिवर्तन शामिल करने हैं।
- हम
git merge --squash <branch-name>कमांड चलाते हैं और<branch-nameको ब्रांच के नाम से बदलते हैं। - हम
git commitके साथ बदलावों को कमिट करते हैं ताकि एक सिंगल कमिट बने जो फीचर ब्रांच के सभी परिवर्तनों का प्रतिनिधित्व करता हो।
उदाहरण के लिए, मान लें कि हम feature/login-form ब्रांच के बदलावों को main में एक ही कमिट के रूप में शामिल करना चाहते हैं:
git checkout main
git merge --squash feature-branch
git commit -m "Implement login form"
इस तरीके की git rebase -i की तुलना में ये सीमाएँ हैं:
- नियंत्रण का सूक्ष्म स्तर: व्यक्तिगत कमिट्स पर कम नियंत्रण। रीबेस के साथ हम चुन सकते हैं कि किन कमिट्स को मिलाना है, जबकि मर्ज सभी परिवर्तनों को एक कमिट में जोड़ने के लिए बाध्य करता है।
- बीच का इतिहास: मर्ज का उपयोग करते समय, फीचर ब्रांच का व्यक्तिगत कमिट इतिहास मेन ब्रांच में खो जाता है। इससे फीचर के विकास के दौरान किए गए क्रमिक परिवर्तनों को ट्रैक करना कठिन हो सकता है।
- प्री-कमिट समीक्षा: चूँकि यह सभी परिवर्तनों को एक ही परिवर्तन-समूह के रूप में स्टेज करता है, हम स्क्वैश करने से पहले प्रत्येक कमिट की अलग-अलग समीक्षा या परीक्षण नहीं कर सकते, जबकि इंटरैक्टिव रीबेस के दौरान प्रत्येक कमिट को क्रम में समीक्षा और परीक्षण किया जा सकता है।
निष्कर्ष
डेवलपमेंट वर्कफ़्लो में बार-बार और छोटे-छोटे कमिट्स शामिल करना सहयोग और स्पष्ट दस्तावेज़ीकरण को बढ़ावा देता है, लेकिन इससे प्रोजेक्ट का इतिहास अव्यवस्थित भी हो सकता है। कमिट्स को स्क्वैश करना संतुलन बनाता है—महत्वपूर्ण माइलस्टोन्स को सुरक्षित रखते हुए छोटे-छोटे क्रमिक परिवर्तनों के शोर को हटाता है।
Git के बारे में और जानने के लिए, मैं इन संसाधनों की सिफारिश करता हूँ:

"Commit early, commit often" Git का उपयोग करते समय सॉफ्टवेयर विकास की एक लोकप्रिय उक्ति है। ऐसा करने से हर परिवर्तन का अच्छा दस्तावेज़ीकरण होता है, सहयोग बेहतर होता है, और प्रोजेक्ट के विकास को ट्रैक करना आसान हो जाता है। हालांकि, इससे अत्यधिक संख्या में कमिट्स भी हो सकते हैं।
यहीं पर कमिट्स को स्क्वैश करना महत्वपूर्ण हो जाता है। कमिट्स को स्क्वैश करना कई कमिट प्रविष्टियों को एक एकीकृत कमिट में संयोजित करने की प्रक्रिया है।