- Git स्रोत के distributed repository के रूप में सफल रहा है, लेकिन distributed workflow को संभालने का तरीका बाद में जोड़े गए समाधान के अधिक करीब है, इसलिए इसकी सीमाएँ सामने आती हैं
- Git के commit और branch अपने आप आगे आने वाले commits, amend history, rebase history, और छोड़ी गई स्थिति को व्यक्त नहीं कर सकते
- Stacked PR में बाद के PR ढूँढने और stack को बनाए रखते हुए rebase करना पड़ता है, लेकिन Git के लिए इस संबंध को भरोसेमंद तरीके से समझना कठिन है
- Git staging, unstaged, file system, HEAD जैसी mutable state को commit और branch के बाहर रखता है, जिससे इसे सीखना और इस्तेमाल करना जटिल हो जाता है
- merge से पहले कई PR को साथ लेकर चलने वाले asynchronous development flow में Git का पीछे की ओर देखने वाला immutable history model बार-बार समस्याएँ पैदा करता है
Git की दो भूमिकाएँ
- Git एक distributed source repository भी है और साथ ही distributed workflow tool के रूप में भी इस्तेमाल होता है
- source repository के रूप में यह बहुत सफल रहा है, लेकिन distributed workflow को संभालने का तरीका ज़्यादातर बाद में जोड़े गए उपायों जैसा है
- asynchronous development, East River Source Control के शब्दों में, लगभग एक बुनियादी शर्त है; यह केवल अलग-अलग time zone में collaboration तक सीमित नहीं, बल्कि अपने ही साथ समय के अंतराल में काम करने पर भी लागू होता है
- jj एक ऐसा टूल है जो Git की सीमाओं को और साफ़ दिखाता है, और जिन्हें लगता है कि Git पर्याप्त है, वे शायद jj को गंभीरता से आज़माएँ ही नहीं
Git के बेसिक मॉडल से छूट जाने वाले संबंध
- Git की सोच के केंद्र में commit और branch होते हैं
- commit source code और history को रखने वाला immutable object है
- branch log के साथ जुड़ा हुआ mutable pointer है
- एक typical Git diagram commits को
C1, C2, C3 की तरह दिखाता है, जिससे क्रम और संबंध स्पष्ट लगते हैं, लेकिन असली repository में commit नाम hash या message जैसे होते हैं, इसलिए ऐसा क्रमिक संबंध सिस्टम के भीतर मौजूद नहीं होता
- rebase के बाद
C2 और C2’ जैसी notation इंसानों के लिए आसान व्याख्या है, लेकिन Git यह नहीं जानता कि दोनों commits एक-दूसरे के अनुरूप हैं
- किसी खास commit के बाद आने वाले commit ढूँढने के लिए सभी branches को खंगालकर उस commit तक पहुँचने वाले path पर मौजूद commits ढूँढने पड़ते हैं, इसलिए यह आसान नहीं है
Git में “C” नहीं है
- Git commit अपने बारे में यह जानकारी नहीं जान सकता
-
बाद के commit
- amend के बाद नए commit से पुराने commit तक जाने वाली संशोधन history
-
rebase history
- क्या वह commit छोड़ दिया गया है या नहीं
- branch की भी सीमाएँ हैं
- branch में history की अवधारणा तो है, लेकिन इस पर भरोसा करना मुश्किल है कि वह code change के साथ 1:1 correspondence रखती है
- branches आपस में संबंध नहीं रखतीं; उदाहरण के लिए
trunk से wp/bugfix को भरोसेमंद तरीके से नहीं ढूँढा जा सकता
trunk से wp/bugfix की ओर कोई forward reference नहीं है, इसलिए यह reachable relation भी नहीं है
- Git diagrams इंसानों को क्रम और correspondence दिखाते हैं, लेकिन वे असली tool की क्षमताओं को बढ़ा-चढ़ाकर दिखा सकते हैं
Stacked PR कठिन क्यों है
- अलग-अलग time zone में काम करने वाले लोगों के साथ collaborate करते हुए, अगर review से पहले merge नहीं करना है, तो काम को CPU की तरह pipeline करना पड़ता है
- एक PR बनाकर review खत्म होने तक इंतज़ार करने के बजाय, पहले PR के ऊपर दूसरा PR और फिर उसके ऊपर अगला PR बनाकर कई sequential PR को एक साथ review में भेजने के तरीके को Stacked PR कहा जाता है
- Git के कारण Stacked PR संरचना को भरोसेमंद ढंग से संभालना मुश्किल हो जाता है
Fix key entry race के ऊपर Refactor key entry code जैसा बाद का PR बनाया जाए, और फिर trunk को fetch करके update किया जाए, तो stack बनाए रखते हुए rebase करना पड़ता है
- Git बाद के commits को नहीं जानता, इसलिए
Fix key entry race से Refactor key entry code को आसानी से नहीं देखा जा सकता
- commit छोड़ा गया हो सकता है, इसलिए बाद का commit दिख भी जाए तो यह जानना मुश्किल है कि वह latest state है या नहीं
- branch का इस्तेमाल PR की तरह किया जाता है, लेकिन इस flow में गलती से overwrite करना आसान है
- Graphite जैसे stacking tools Git के ऊपर यह काम कर सकते हैं, लेकिन वे Git के commit या branch को खुद मज़बूत नहीं बना सकते
- अलग branch metadata repository बनानी पड़ती है और उसे Git के साथ sync करना पड़ता है
- अगर user सीधे Git को manipulate करे, तो वह repository Git state से out of sync हो सकती है
mutable state commit के बाहर है
- Git की कई समस्याएँ इस बात से निकलती हैं कि वह mutability को सीधे model नहीं करता
- Git के editing workflow में commit और branch के बाहर अलग state मौजूद होती है
- Staging या index, working copy से बना source snapshot है, और नया commit इसी से बनता है
- Unstaged, index और file system के बीच के अंतर को दिखाने वाला दूसरा diff है
- file system में checkout की गई सामग्री होती है, और इसमें staged तथा unstaged changes जुड़ती हैं
- HEAD वह स्थान है जहाँ नया commit बनाया जाता है
- stash staging और unstaged changes को save और restore करने वाले अलग repository की तरह काम करता है
- checkout को किसी दूसरे commit या branch पर बदलने पर Git file system को नई जगह के अनुसार समायोजित करते हुए भी staging या unstaged diff को बचाए रखने की कोशिश करता है
- यह प्रक्रिया, भले ही commands अलग हों, लेकिन arrow relation के लिहाज़ से staging को नई base पर ले जाने वाले rebase जैसे रूप की होती है
सब कुछ commit के रूप में model करना कठिन क्यों है
- Staging और working copy में भी साफ़ ancestor होते हैं और वे source code रखते हैं, इसलिए अगर केवल static state देखी जाए तो उन्हें commit की तरह दिखाया जा सकता है
- लेकिन commit ID, content के hash पर आधारित होती है, इसलिए अगर commit mutable हो तो उसकी ID लगातार बदलती रहेगी
- अगर staging और working copy को लगातार एक ही “चीज़” के रूप में refer करना हो, तो उन्हें commit नहीं बल्कि branch की तरह संभालना होगा, लेकिन branch की अपनी सीमाएँ हैं जिनकी चर्चा पहले हो चुकी है
- यह जटिलता असली समस्याओं में बदल जाती है
- Git को सीखना और इस्तेमाल करना कठिन हो जाता है, क्योंकि वही अवधारणाएँ दोनों तरफ अलग-अलग मौजूद हैं
- पूरी repository state, clone से मिलने वाली state से काफ़ी अलग हो जाती है, इसलिए export करना अटपटा हो जाता है
- समय के साथ बदलने वाले change sets वाले asynchronous flow अच्छी तरह काम नहीं करते
- mutable हिस्से की system merge को व्यक्त नहीं कर पाती, इसलिए कुछ मामलों में असली workflow को दर्शाना संभव नहीं होता
जहाँ Git असली workflow को व्यक्त नहीं कर पाता
- नई feature branch पर, अभी commit किए बिना development करते समय, आप device पर development में बाधा डालने वाला bug खोज सकते हैं
- अगर वह bug नई feature को रोकता नहीं, लेकिन development को परेशान करता है, तो आप काम को stash करके नई branch पर जा सकते हैं, reproduction test और fix बना सकते हैं, और फिर PR भेज सकते हैं
- उसके बाद जब आप फिर नई feature branch पर लौटते हैं, तो विकल्प सीमित रह जाते हैं
new-feature को वास्तव में dependency न होने वाले bugfix के ऊपर rebase करके review चलाएँ
- development के दौरान
new-feature को bugfix के ऊपर rebase करके इस्तेमाल करें, और branch submit करने से पहले उस rebase को वापस undo कर दें
- Git के साथ “editing workspace में bugfix का सारा code और पहले से committed new feature code दोनों साथ होने चाहिए” जैसी state को व्यक्त नहीं किया जा सकता
- यही आवश्यकता, merge न हुए PR के साथ compatibility testing जैसी और कठिन समस्याओं में भी उसी संरचना के साथ सामने आती है
- Jujutsu megamerges जैसे सही tools के साथ कई PR को parallel में बनाए रखते हुए भी editing space में उन्हें साथ इस्तेमाल किया जा सकता है
Git अब पर्याप्त नहीं है
- 2000 के शुरुआती दशक के version control tools इस्तेमाल और management, दोनों में कठिन थे, उनकी quality असंगत थी, और Subversion को भी कष्टदायक माना जाता था
- उस समय local में पूरी repository की copy रखने की माँग आम नहीं थी, और local branch बनाने की चाहत भी सार्वभौमिक नहीं थी
- file locking से परेशान लोग बहुत थे, लेकिन कुछ लोगों को file locking ज़रूरी लगती थी, और वे यह भी पूछते थे कि Git में individual file या directory को lock किया जा सकता है या नहीं
- open source जैसी distributed workflow का प्रत्यक्ष अनुभव रखने वालों के लिए DVCS पुराने घावों पर पट्टी जैसा लगा था
- आज meaningful distributed workflow में काम करने वालों के लिए Git का पीछे की ओर देखने वाला immutable history model बार-बार समस्याओं का स्रोत बनता है
- Meta जैसी कंपनियाँ लगभग 10 साल से Git से काफ़ी आगे निकली हुई internal systems का इस्तेमाल करती आ रही हैं
- “अब Git को Claude छू लेता है” जैसी प्रवृत्ति इन alternatives को निरर्थक नहीं बना देती
- LLM इस्तेमाल करते हुए ऐसा लगता है कि engineers अब एक ही machine के भीतर भी पहले से अधिक asynchronous development कर रहे हैं
1 टिप्पणियां
Lobste.rs की राय
अच्छा होता अगर लेख में दिखाया जाता कि jj इन समस्याओं को कैसे हल करता है
jj उपयोगकर्ताओं के लिए यह शायद स्पष्ट हो, लेकिन संभव है कि वे लोग इस लेख के मुख्य पाठक न हों
लेख में Git के ठीक न होने के सबूत के तौर पर जिन फीचर्स का ज़िक्र किया गया है, उनकी मुझे व्यक्तिगत रूप से कभी ज़रूरत नहीं पड़ी
लगा कि शायद सिर्फ़ मैं ही ऐसा हूँ
किसी टूल की अहम बातों में से एक यह है कि वह एक dynamic system का हिस्सा होता है। टूल जिन कामों को संभव बनाता है, वे “मैं क्या कर सकता हूँ” इस विश्वास को प्रभावित करते हैं, और वही विश्वास फिर उस टूल के बारे में हमारी सोच और उसके विकास की दिशा को बदलता है
जब कोई टूल मौजूदा स्थिति को हिलाता है, तो क्या किया जा सकता है इस बारे में विश्वास और अपेक्षाएँ भी साथ बदलती हैं
दिलचस्प लगता है, लेकिन diagram देखते ही चक्कर-सा आने लगता है
इस बात के बारे में कि आज की स्थिति 2000 के शुरुआती दशक जितनी गंभीर नहीं है और Git से पहले के version control systems की सीमाएँ काफ़ी स्पष्ट थीं, Darcs Git से पहले आया था और उसने snapshot-आधारित version control की कुछ समस्याओं को मूल रूप से ठीक किया था
शुरुआत में performance खराब होने की वजह से वह पीछे रह गया, लेकिन बाद में performance सुधरी और लोग लौटकर दोबारा देखने नहीं आए। दूसरे version control systems भी दिलचस्प काम कर रहे हैं, इसलिए “Git नहीं तो Jujutsu” को ही एकमात्र विकल्प की तरह न पेश किया जाए। इस तरह की दलील बहुत बार दिखती है
वह भी data model की ही समस्या है
jj इसे कैसे संभालता है? https://www.billjings.com/posts/title/git-is-not-fine/RealityEx23.png
jj new A Bका उपयोग करने पर working-copy commit के कई parent हो सकते हैं, इसलिए वह merge commit की तरह काम करता हैइस वजह से working copy में दोनों parent के बदलाव आ जाते हैं, और आप उस merge के ऊपर काम जारी रख सकते हैं या किसी एक commit में amend कर सकते हैं
अभी भी Git ज़्यादा पसंद है, और लेखक में bias दिखता है
jj newचलाना है, और फिरgitऔरjjको मिलाकर इस्तेमाल किया जा सकता हैGit हमेशा parent commit की ओर इशारा करता है, और अभी का
jj commitworking tree के uncommitted changes की तरह दिखाई देता हैमैंने
jjइसी तरह सीखा। rebase संभालना या tree को इधर-उधर ले जाना जैसे कामों में, जिनमेंjjअच्छा है, मैंjjइस्तेमाल करता था; और रोज़मर्रा के उन कामों में, जिनके लिए मुझे अभीjjका समकक्ष command नहीं पता था या जहाँgit blameजैसे Git commands पहले याद आते थे, मैंgitcommands ही इस्तेमाल करता रहासच में रोज़ इस्तेमाल करने से पहले तक यह ठीक से महसूस नहीं हुआ कि
jjबेहतर क्यों है; सिर्फ़ पढ़कर मुझे भी लगता था, “क्या यह फीचर सच में ज़रूरी है?” या “यह तो Git से भी पहले से किया जा सकता है”बेशक
jjकी भी कमियाँ हैं। अगर नवीनतम.gitignoreन हो, तो binary files गलती से commit में शामिल हो सकती हैं। अच्छी बात यह है कि बहुत बड़ी file जोड़ने परjjचेतावनी देता है, लेकिन छोटी files बच निकल सकती हैंdebugging के दौरान अगर मौजूदा directory में tracked files या log files हों, तो वे भी शामिल हो सकती हैं, इसलिए tree में बड़े बदलाव करने के बाद diffstat पूरा देख लेना अच्छा रहता है
खासकर अगर आप
jjसे bisect कर रहे हों और उस commit से पुराने commit को test करें जिसमें.gitignoreअपडेट किया गया था, तो समस्या हो सकती है। bisect के लिए शायद read-only mode होना चाहिए