FlowingDev

Diff, समझाया गया: Git और हर कोड रिव्यू का सीक्रेट सॉस

जानें कि कैसे diff एल्गोरिदम टेक्स्ट्स के बीच सटीक additions, deletions, और changes का पता लगाते हैं, जो वर्ज़न कंट्रोल और कोड कोलैबोरेशन के पीछे का मुख्य इंजन है।

टूल आज़माएँ: डिफ़ चेककर

एक वाक्य में

एक 'diff' दो फाइलों या टेक्स्ट के ब्लॉक्स के बीच सटीक अंतरों का एक कंप्यूटेड सारांश है, जो यह दिखाता है कि "पहले" से "बाद" तक पहुंचने के लिए वास्तव में क्या जोड़ा गया, हटाया गया, या बदला गया।

यह क्या समस्या हल करता है

1970 के दशक की शुरुआत में कंप्यूटिंग की दुनिया की कल्पना करें। स्टोरेज अविश्वसनीय रूप से महंगा है, और एक रिमोट कंप्यूटर से कनेक्शन एक ऐसे मॉडेम पर होता है जो एक आलसी घोंघे से भी धीमा है। आप बेल लैब्स में एक डेवलपर हैं, और आपको कैंपस के दूसरी तरफ एक सर्वर पर एक सोर्स कोड फ़ाइल को अपडेट करने की आवश्यकता है। फ़ाइल कुछ हज़ार लाइनों की है, लेकिन आपने उनमें से केवल तीन को बदला है।

क्या आप उस धीमी गति वाले कनेक्शन पर पूरी फ़ाइल फिर से भेजेंगे? बिलकुल नहीं। यह समय और संसाधनों की बर्बादी है। आप वास्तव में जो भेजना चाहते हैं, वह है सिर्फ बदलाव।

यह वही समस्या थी जिसके कारण डगलस मैकिलरॉय ने 1974 में यूनिक्स ऑपरेटिंग सिस्टम के लिए मूल diff कमांड बनाया था। उनका लक्ष्य एक ऐसा टूल बनाना था जो प्रोग्रामेटिक रूप से एक फ़ाइल को दूसरी में बदलने के लिए आवश्यक लाइन-बाय-लाइन परिवर्तनों का न्यूनतम सेट ढूंढ सके। इस diff कमांड का आउटपुट, एक "पैच" फ़ाइल, बहुत छोटी होती थी और इसे जल्दी से भेजा जा सकता था। प्राप्तकर्ता फिर एक साथी प्रोग्राम, patch का उपयोग करके इन परिवर्तनों को मूल फ़ाइल की अपनी प्रति पर लागू कर सकता था, जिससे वह अपडेट हो जाती थी।

यह सरल, शक्तिशाली विचार—बदलाव को ही डेटा के एक हिस्से के रूप में अलग करना—क्रांतिकारी था। यह Git, Subversion, और Mercurial जैसे सभी आधुनिक वर्ज़न कंट्रोल सिस्टम का मौलिक बिल्डिंग ब्लॉक है। यह कोड रिव्यू, डॉक्यूमेंट कोलैबोरेशन टूल्स (जैसे Google Docs का "Suggesting" मोड), और कॉन्फ़िगरेशन मैनेजमेंट सिस्टम के पीछे का इंजन है। यह किसी भी डिजिटल टेक्स्ट में विकास को ट्रैक करने और संवाद करने की मुख्य समस्या को हल करता है।

यह अंदर से कैसे काम करता है

पहली नज़र में, diffing आसान लगता है: बस दो टेक्स्ट को स्कैन करें और जो अलग है उसे फ्लैग करें। लेकिन इसे कुशलता से करने और अंतरों का सबसे छोटा, सबसे पठनीय सेट बनाने के लिए यह एक क्लासिक कंप्यूटर विज्ञान की समस्या है। इसका रहस्य यह खोजना नहीं है कि क्या अलग है, बल्कि यह खोजना है कि क्या समान है।

द लॉन्गेस्ट कॉमन सबसीक्वेंस (LCS)

अधिकांश diff एल्गोरिदम, जिसमें प्रसिद्ध हंट-मैकइल्वेन एल्गोरिदम भी शामिल है जिसने मूल diff को संचालित किया था, "लॉन्गेस्ट कॉमन सबसीक्वेंस" (LCS) समस्या को हल करने पर आधारित हैं।

एक subsequence आइटम्स का एक ऐसा sequence है जो मूल sequence में उसी क्रम में दिखाई देते हैं, लेकिन जरूरी नहीं कि एक-दूसरे के बगल में हों। LCS वह सबसे लंबा ऐसा subsequence है जो दो sequences में समान होता है।

आइए एक सरल, नॉन-कोड उदाहरण का उपयोग करें।

  • Original: The quick red fox
  • New: The slow red cat

एल्गोरिथ्म, लाइन-बाय-लाइन (या इस मामले में, शब्द-दर-शब्द) काम करते हुए, यह पता लगाता है कि लॉन्गेस्ट कॉमन सबसीक्वेंस है: The red.

एक बार जब LCS मिल जाता है, तो लॉजिक सरल होता है:

  • Original में कोई भी आइटम जो LCS में नहीं है, वह delete किया गया होगा। (quick, fox)
  • New टेक्स्ट में कोई भी आइटम जो "LCS" में नहीं है, वह add किया गया होगा। (slow, cat)

साझा कंटेंट के सबसे लंबे आधार (bedrock) को ढूंढकर, एल्गोरिथ्म इसके चारों ओर बदलाव के द्वीपों (islands of change) को स्पष्ट और संक्षिप्त रूप से पहचान सकता है। यह विधि अंतरों का एक न्यूनतम सेट उत्पन्न करती है, जो हम एक साफ, समझने योग्य diff के लिए चाहते हैं।

LCS से एक पठनीय Diff तक

बदलाव खोजना लड़ाई का केवल आधा हिस्सा है। दूसरा आधा हिस्सा उन्हें एक मानकीकृत, पठनीय प्रारूप में प्रस्तुत करना है। यदि आपने कभी GitHub पुल रिक्वेस्ट देखी है तो आपने शायद यह देखा होगा। सबसे आम प्रारूप "यूनिफाइड diff फॉर्मेट" है।

आइए इसे थोड़े अलग उदाहरण पर लागू करें:

  • File A (old):
    An apple a day.
    Keeps the doctor away.
    Or so they say.
    
  • File B (new):
    An apple a day,
    Keeps the doctor away.
    For what it's worth.
    

एक diff टूल कुछ इस तरह उत्पन्न करेगा:

--- a/file_a.txt
+++ b/file_b.txt
@@ -1,3 +1,3 @@
-An apple a day.
+An apple a day,
 Keeps the doctor away.
-Or so they say.
+For what it's worth.

आइए इसे तोड़कर समझते हैं:

  • --- a/file_a.txt: "from" फ़ाइल। - डिलीशन्स के स्रोत को इंगित करता है।
  • +++ b/file_b.txt: "to" फ़ाइल। + एडीशन्स के स्रोत को इंगित करता है।
  • @@ -1,3 +1,3 @@: यह "हंक हेडर" है। यह थोड़ा क्रिप्टिक है, लेकिन यह आपको संदर्भ देता है। -1,3 का अर्थ है "यह हंक मूल फ़ाइल में लाइन 1 से शुरू होता है और 3 लाइन लंबा है।" +1,3 का अर्थ है "यह हंक नई फ़ाइल में लाइन 1 से शुरू होता है और 3 लाइन लंबा है।"
  • एक स्पेस ( ) से शुरू होने वाली लाइनें कॉन्टेक्स्ट लाइन्स हैं। वे दोनों फाइलों में समान हैं और यह समझने में आपकी मदद करने के लिए दिखाई जाती हैं कि परिवर्तन कहाँ हुआ।
  • - से शुरू होने वाली लाइनें डिलीशन्स हैं। वे केवल "पहले" वाले टेक्स्ट में मौजूद हैं।
  • + से शुरू होने वाली लाइनें एडीशन्स हैं। वे केवल "बाद" वाले टेक्स्ट में मौजूद हैं।

टूल An apple a day. से An apple a day, में हुए बदलाव को एक ही लाइन के मॉडिफिकेशन के रूप में नहीं, बल्कि पुरानी लाइन के डिलीशन और नई लाइन के एडीशन के रूप में दिखाता है। यह लाइन-आधारित दृष्टिकोण अधिकांश पारंपरिक diff टूल्स की एक मुख्य विशेषता है।

प्लेन टेक्स्ट से परे: सिमेंटिक डिफ्स

एक मानक लाइन-आधारित diff गद्य या कोड के लिए बहुत अच्छा है, लेकिन यह JSON, XML, या YAML जैसे स्ट्रक्चर्ड डेटा के साथ विफल हो जाता है।

इस JSON पर विचार करें:

// Original
{
  "name": "Alex",
  "role": "Developer"
}

और यह वाला:

// New
{
  "role": "Developer",
  "name": "Alex"
}

एक टेक्स्ट-आधारित diff इसे पूरी तरह से मिटाने और फिर से लिखने के रूप में देखेगा:

-  "name": "Alex",
-  "role": "Developer"
+  "role": "Developer",
+  "name": "Alex"

यह तकनीकी रूप से सही है लेकिन अर्थ की दृष्टि से (semantically) बेकार है। JSON ऑब्जेक्ट में कीज़ (keys) का क्रम आमतौर पर कोई मायने नहीं रखता। एक सिमेंटिक diff टूल ज़्यादा स्मार्ट होता है। यह पहले टेक्स्ट को एक डेटा स्ट्रक्चर में पार्स (parse) करता है, फिर उन स्ट्रक्चर्स की तुलना करता है। यह सही ढंग से पहचान लेगा कि ये दो JSON ऑब्जेक्ट समान हैं, जिसके परिणामस्वरूप कोई अंतर नहीं होगा। यह कॉन्फ़िगरेशन फाइलों, API प्रतिक्रियाओं, या किसी अन्य स्ट्रक्चर्ड डेटा की तुलना करने के लिए महत्वपूर्ण है जहाँ आप केवल टेक्स्ट फॉर्मेटिंग के बजाय अर्थ की परवाह करते हैं।

वास्तविक दुनिया की कहानियाँ

एक-कैरेक्टर बग जिसने चेकआउट को क्रैश कर दिया

एक जूनियर डेवलपर एक नया पेमेंट प्रोवाइडर इंटीग्रेट कर रहा था। उन्होंने डॉक्यूमेंटेशन से उदाहरण API रिक्वेस्ट कॉपी किया, अपनी कीज़ डालीं, और उसे चलाया। यह विफल हो गया। उन्होंने फिर कोशिश की। विफल। उन्होंने घंटों अपने कोड और डॉक्यूमेंटेशन को घूरते हुए बिताए, यह विश्वास करते हुए कि वे समान थे। निराश होकर, उन्होंने "काम कर रहे" डॉक्यूमेंटेशन उदाहरण को एक diff चेकर के एक तरफ और अपने कोड को दूसरी तरफ चिपकाया।

पहले तो यह एक जैसा लगा। लेकिन फिर उन्होंने अपनी API की लाइन के अंत में एक सूक्ष्म हाइलाइट देखा। एक अकेला, अदृश्य ट्रेलिंग स्पेस। एक वेब पेज से कॉपी-पेस्ट ने इसे शामिल कर लिया था, और उनका कोड कर्तव्यनिष्ठा से इसे साथ भेज रहा था, जिससे की अमान्य हो रही थी। diff टूल, जिसने स्पेस को सिर्फ एक और कैरेक्टर के रूप में देखा, एकमात्र "आंखों की जोड़ी" थी जो इसे देख सकती थी।

सबक: एक diff आपका अंतिम माइक्रोस्कोप है। इसकी कोई धारणा नहीं होती है और यह आपको ठीक वही दिखाएगा जो वहां है, जिसमें वे अदृश्य कैरेक्टर्स भी शामिल हैं जो एक सिस्टम को घुटनों पर ला सकते हैं।

कॉन्फ़िगरेशन ड्रिफ्ट की गड़बड़ी

एक हाई-ट्रैफिक वेबसाइट पर अजीब, रुक-रुक कर आने वाली त्रुटियां होने लगीं। ऑन-कॉल इंजीनियर, माया, चकरा गई। आखिरी डिप्लॉयमेंट एक हफ्ता पहले हुआ था और स्थिर था। लॉग्स में कुछ भी स्पष्ट कारण की ओर इशारा नहीं कर रहा था। उसके स्पाइडी-सेंस ने उसे बताया कि सर्वर पर कुछ मैन्युअल रूप से बदला गया था।

उसने उनके Git रिपॉजिटरी से आधिकारिक Nginx कॉन्फ़िगरेशन फ़ाइल निकाली, फिर प्रोडक्शन सर्वर में SSH किया और वास्तविक चल रहे कॉन्फ़िगरेशन को कॉपी किया। उसने दोनों को एक diff टूल में पेस्ट किया। बिंगो। तीन लाइनें अलग थीं। किसी ने पिछले हफ्ते एक छोटी सी समस्या को ठीक करने के लिए सर्वर पर सीधे एक "अस्थायी" रीडायरेक्ट नियम जोड़ा था और इसके बारे में पूरी तरह से भूल गया था। यह "फिक्स" अब नए ट्रैफिक पैटर्न से टकरा रहा था। माया ने अनचाही लाइनों को हटा दिया, और त्रुटियां गायब हो गईं। टीम ने तुरंत Git के खिलाफ सर्वर कॉन्फ़िग का दैनिक ऑडिट करने की नीति लागू की।

सबक: आपका वर्ज़न कंट्रोल सिस्टम आपकी सच्चाई का स्रोत है। उस सच्चाई के स्रोत के खिलाफ वास्तविकता को diff करना "कॉन्फ़िगरेशन ड्रिफ्ट" का पता लगाने और अनधिकृत या भूले हुए परिवर्तनों को खोजने का सबसे अच्छा तरीका है।

"मुझे तो ठीक लग रहा है" कोड रिव्यू

एक सीनियर डेवलपर, बेन, को एक नए हायर से एक पुल रिक्वेस्ट मिली। शीर्षक था "अपडेट्स।" diff 20 फाइलों में 3,000 से अधिक लाइनों में लाल और हरे रंग का समुद्र था। इसमें एक नया फीचर, एक असंबंधित बग के लिए एक फिक्स, टैब से स्पेस में स्विच करने के लिए एक विशाल कोड रीफॉर्मेटिंग, और एक लाइब्रेरी अपग्रेड शामिल था। इसकी समीक्षा करना असंभव था। क्या बग फिक्स सही था? क्या नए फीचर ने कोई सुरक्षा छेद पेश किया? यह व्हाइटस्पेस परिवर्तनों के एक बर्फीले तूफान में छिपा हुआ था।

बेन ने एक विनम्र नोट के साथ PR को अस्वीकार कर दिया: "स्वागत है! एक PR का diff एक कहानी कहता है। यह वाला एक ही बार में चार अलग-अलग कहानियाँ कहने की कोशिश कर रहा है। क्या आप कृपया इसे चार अलग-अलग PR में विभाजित कर सकते हैं?" नए हायर ने ऐसा ही किया। रीफॉर्मेटिंग PR को तुरंत मंजूरी दे दी गई। बग फिक्स को सत्यापित करना आसान था। लाइब्रेरी अपग्रेड सीधा था। और अंत में नए फीचर की अपने आप में समीक्षा की जा सकती थी।

सबक: एक diff का मूल्य उसके आकार और जटिलता के व्युत्क्रमानुपाती होता है। एक एकल तार्किक परिवर्तन का प्रतिनिधित्व करने वाले छोटे, केंद्रित diffs की समीक्षा करना, समझना और बाद में डीबग करना आसान होता है।

आम गलतियाँ और जाल

  • व्हाइटस्पेस को अनदेखा करना। टैब से स्पेस में बदलाव या एक ट्रेलिंग न्यूलाइन का जुड़ना एक diff टूल के लिए एक विशाल, फ़ाइल-व्यापी परिवर्तन की तरह लग सकता है। जबकि कभी-कभी यह जानबूझकर होता है, यह अक्सर केवल शोर पैदा करता है जो वास्तविक, सार्थक परिवर्तनों को छुपाता है। अपने टूल्स को व्हाइटस्पेस परिवर्तनों को उचित रूप से अनदेखा करने या हाइलाइट करने के लिए कॉन्फ़िगर करें।
  • 'सिमेंटिक-ब्लाइंड' diff। जैसा कि पहले उल्लेख किया गया है, JSON या XML जैसे स्ट्रक्चर्ड डेटा पर एक प्लेन टेक्स्ट diff का उपयोग करना अविश्वसनीय रूप से भ्रामक हो सकता है। एट्रिब्यूट्स या कीज़ को फिर से व्यवस्थित करना एक बड़े बदलाव की तरह लग सकता है, जबकि कार्यात्मक रूप से कुछ भी अलग नहीं है। इन प्रारूपों के लिए हमेशा एक सिमेंटिक-अवेयर diff टूल का उपयोग करें।
  • संदर्भ (context) को भूल जाना। एक diff आपको दिखाता है कि क्या बदला, लेकिन यह कभी नहीं बताता कि क्यों बदला। यह कमिट संदेश या पुल रिक्वेस्ट विवरण का काम है। संदर्भ के बिना एक diff एक प्रश्न के बिना उत्तर की तरह है; यह आंकना मुश्किल है कि यह सही है या गलत।
  • 'फ्रेंकस्टीन' diffs बनाना। असंबंधित परिवर्तनों (एक बग फिक्स, एक फीचर, और एक टाइपो सुधार) को एक ही कमिट में डालना diff को पढ़ने के लिए एक दुःस्वप्न बना देता है। यह बाद में उन परिवर्तनों में से किसी एक को दूसरों को प्रभावित किए बिना वापस लेना असंभव बना देता है। प्रत्येक कमिट एक एकल, तार्किक, एटॉमिक परिवर्तन होना चाहिए।

यह आपके रडार पर क्यों होना चाहिए

एक मॉडर्न डेवलपर के लिए diffing को समझना वैकल्पिक नहीं है; यह उतना ही मौलिक है जितना कीबोर्ड का उपयोग करना जानना। आप दिन में कई बार, हर दिन diffs का सामना करेंगे:

  • जब आप अपने स्वयं के अनकमिटेड काम को देखने के लिए git status या git diff चलाते हैं।
  • जब आप अपने सहकर्मियों की समीक्षा के लिए एक पुल रिक्वेस्ट बनाते हैं।
  • जब आप किसी और के पुल रिक्वेस्ट की समीक्षा करते हैं।
  • जब आप यह पता लगाने के लिए git blame का उपयोग करते हैं कि कोड की एक विशिष्ट पंक्ति किसने और क्यों लिखी।
  • जब आप एक काम कर रहे कॉन्फ़िगरेशन की तुलना एक टूटे हुए कॉन्फ़िगरेशन से करके किसी समस्या को डीबग कर रहे हों।

गैर-डेवलपर्स के लिए भी, यह अवधारणा शक्तिशाली है। यह आपके Word डॉक्यूमेंट में 'Track Changes' जैसा है। यह आपके विकिपीडिया लेख का वर्ज़न हिस्ट्री है। यह देखने की क्षमता है कि ड्राफ्ट्स के बीच एक अनुबंध कैसे विकसित हुआ है। diffing को समझना यह समझना है कि हम डिजिटल दुनिया में परिवर्तन का प्रबंधन और संवाद कैसे करते हैं। यह प्रगति का एक ऑडिट करने योग्य, सत्यापन योग्य लॉग है।

और गहराई में जाएं

  • Wikipedia: Diff utility - diff टूल्स के इतिहास, एल्गोरिथ्म, और विभिन्न आउटपुट फॉर्मेट्स का एक शानदार अवलोकन।
  • An O(ND) Difference Algorithm and Its Variations (Eugene W. Myers) - एक कुशल diff एल्गोरिथ्म पर 1986 का एक मौलिक पेपर जो Git सहित आधुनिक टूल्स में अत्यधिक प्रभावशाली है।
  • Wikipedia: Longest common subsequence problem - उस मुख्य कंप्यूटर विज्ञान समस्या में एक गहरा गोता जिस पर अधिकांश diff एल्गोरिदम बने होते हैं।
  • Git Documentation for git-diff - सबसे आम diff टूल के लिए मैनुअल जिसे डेवलपर्स रोजाना इस्तेमाल करते हैं। यह उपलब्ध जबरदस्त शक्ति और कॉन्फ़िगर करने की क्षमता को दर्शाता है।

थ्योरी हो गई। अब हाथ आज़माइए — 100% आपके ब्राउज़र में।

टूल आज़माएँ: डिफ़ चेककर