1 पॉइंट द्वारा GN⁺ 2 시간 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Git 2.54 और 2.55 में पेश किया गया प्रयोगात्मक git history कमांड, मौजूदा Git workflow को बनाए रखते हुए पुराने commits में बदलाव और उनसे निकली branches के पुनर्गठन को स्वचालित करता है
  • fixup staged बदलावों को किसी पुराने commit में मिलाता है, फिर उस commit से निकली सभी local branches को नए commit hash के अनुसार अपने-आप rebase करता है
  • reword पुराने commit message को बदलता है, और split commit के बदलावों को hunk स्तर पर बांटकर दो commits में विभाजित करता है, फिर बाद के commits और branches को दोबारा व्यवस्थित करता है
  • ये तीनों subcommands atomic processing के साथ काम करते हैं, जो conflict की संभावना वाले कामों को मना करके working tree को बीच में टूटे हुए हाल में जाने से बचाते हैं, लेकिन merge commits वाली history में काम नहीं करते
  • हालांकि यह jj की first-class conflict handling, operation log-आधारित undo, या working copy की commit modeling जैसी सुविधाएं नहीं देता, फिर भी बिना अलग install के Git core distribution में कई jj-style history editing features इस्तेमाल किए जा सकते हैं

मौजूदा Git history editing असुविधाजनक क्यों है

  • कई बदलावों को parallel में संभालने पर branches और commits के बीच लगातार आना-जाना पड़ता है, और rebase -i के दौरान working tree अधूरी स्थिति में फंस सकती है
  • Git के विकल्प jj को अक्सर इस समस्या के समाधान के रूप में बताया जाता है, लेकिन डेढ़ साल तक हर 3 महीने में कुछ दिनों के लिए आजमाने पर भी अंततः वापस मौजूदा Git workflow पर लौटना पड़ा
  • git history Git core distribution में शामिल एक experimental command है, इसलिए बिना अलग tool install किए इसे आजमाया जा सकता है
    • Git 2.54 ने अप्रैल में reword और split पेश किए
    • Git 2.55 ने जून में fixup जोड़ा
  • subcommands तीन हैं: fixup, reword, split

fixup: पुराने commit और उससे निकली branches को साथ में ठीक करना

  • git history fixup पुराने commit की गलती को ठीक करता है और उस commit से निकली branches को अपने-आप rebase करता है
  • सामान्य सुधार प्रक्रिया की तरह git add से बदलाव staged करने के बाद git history fixup <commit> चलाने पर staged बदलाव target commit में मिल जाते हैं
  • यह git commit --fixup और autosquash rebase के संयोजन जैसा है, लेकिन फर्क यह है कि यह target commit को शामिल करने वाली दूसरी branches को भी update करता है
  • जहां git rebase --update-refs केवल मौजूदा rebase range के अंदर वाले refs को move करता है, वहीं git history target commit से निकली सभी local branches को ढूंढकर दोबारा लिखता है
    • जरूरत हो तो update का दायरा केवल current branch तक सीमित किया जा सकता है
    • merge commits वाली history में यह काम नहीं करता, इसलिए कुछ Git usage patterns में इसे लागू करना मुश्किल है
  • B में सुधार मिलाने पर नए hash वाला B* बनता है, और उसके ऊपर के C और D भी क्रमशः C*, D* के रूप में फिर से बनाए जाते हैं
    • feat-1 और feat-2 के branch tips भी नए commits के साथ आगे बढ़ जाते हैं

atomic processing और conflict सीमाएं

  • तीनों subcommands पूरी तरह atomically process होते हैं, इसलिए working tree आधी-टूटी स्थिति में नहीं छूटती
  • atomicity इस तरह सुनिश्चित की जाती है कि जिन operations से conflict हो सकता है, उन्हें शुरू से ही मना कर दिया जाता है
  • इस वजह से यह jj की तुलना में कार्यात्मक रूप से सीमित है, क्योंकि jj conflicts को first-class object की तरह संभालता है और rebase के दौरान conflict state को बनाए रखकर बाद में resolve करने देता है
  • git history की सीमाएं जानबूझकर लगाई गई हैं ताकि history rewriting, state preservation operation में न बदल जाए
    • अगर भविष्य में Git first-class conflicts को support करे, तो ये सीमाएं हटाई जा सकती हैं

reword: working tree छुए बिना commit message बदलना

  • git history reword पुराने commit message को बदलता है और उसके ऊपर के commits व branches को अपने-आप फिर से व्यवस्थित करता है
  • git history reword <commit> चलाने पर editor में पुराना message खुलता है; उसे बदलकर save करने पर बाद का commit stack नए hash के हिसाब से दोबारा बन जाता है
  • iterative development के दौरान design बदल जाने पर पुराने commit message को मौजूदा structure के मुताबिक ठीक करने में यह उपयोगी है
  • tree content की जगह सिर्फ commit message बदलने को छोड़ दें तो यह fixup की तरह ही काम करता है
  • reword index या working tree को छुए बिना सिर्फ commit graph बदलता है
    • checkout न की गई branch के commits भी मौजूदा काम में बाधा डाले बिना बदले जा सकते हैं
    • B का message बदलने पर hash बदल जाता है, इसलिए बाद वाला C, C* के रूप में फिर बनता है और feat-1 भी नए commit के साथ आगे बढ़ता है

split: एक commit को दो हिस्सों में बांटना

  • git history split किसी commit के बदलावों को interactive तरीके से चुनकर दो commits में बांटता है
  • git history split <commit> चलाने पर target commit के बदलावों को hunk स्तर पर चुनने का prompt खुलता है
    • चुने गए और बनाए रखे गए hunks पहला commit बनाते हैं
    • बाकी hunks दूसरे commit में जाते हैं
  • यह git add -p जैसा है, लेकिन git rebase के साथ जटिल प्रक्रिया जोड़ने की जरूरत नहीं पड़ती
  • अगर असंबंधित दो बदलावों वाले B को बांटा जाए, तो B1 और B2 बनते हैं, और बाद वाला C इन दोनों commits के ऊपर C* के रूप में फिर से बनाया जाता है
  • तीनों commands में इसका उपयोग सबसे ज्यादा विशेष परिस्थितियों में होता है, लेकिन जब commit को सचमुच बांटना हो तब यह बहुत उपयोगी है

jj से फीचर अंतर

1 टिप्पणियां

 
GN⁺ 2 시간 전
Hacker News की राय
  • Git असुविधाजनक लगता था, लेकिन मुफ्त में उपलब्ध Pro Git पुस्तक के पहले तीन अध्याय पढ़ने के बाद स्थिति बेहतर हुई। यह Git के अंदरूनी कामकाज के बारे में एक शानदार mental model देता है, और अच्छा हो या बुरा, Git का user interface उसकी internal structure को सीधे दर्शाता है, इसलिए इसे समझने के बाद सब कुछ समझ में आने लगा

    • मैं इस बात से सहमत नहीं हूँ कि user interface internal structure को सीधे दर्शाता है। उदाहरण के लिए, git checkout branch बदलते समय उन बदलावों को नहीं खोता जो अभी Git में नहीं गए हैं, लेकिन किसी फ़ाइल का दूसरा version restore करते समय बदलावों को वापस लेने के लिए डिज़ाइन किया गया है—यह internal structure को सीधे कैसे दर्शाता है?
      इससे अलग, भले ही आपको अंदरूनी तौर पर चाहिए क्या और कौन-सा command चाहिए यह पता हो, फिर भी सही command-line syntax ढूँढना मुश्किल बना देने वाली CLI की असंगतता भी Git को इस्तेमाल करना कठिन बनाती है
    • Git उतना भ्रमित करने वाला नहीं है जितना कहा जाता है, और इसे समझने के लिए बस कुछ घंटे देने होते हैं। यह software engineer का एक ज़रूरी tool है, इसलिए इसे न सीखना अफसोस की बात है, और अगर आप expert हैं तो सिर्फ git commit -am “Changes” काफ़ी नहीं है
    • मुझे भी Git commands उलझाऊ लगते थे, लेकिन Linus का Git की internal data structures समझाने वाला YouTube वीडियो देखने के बाद समझ आया कि यह बहुत सरल है। Tool की functionality data structures को कैसे manipulate करती है, इसे आसानी से map किया जा सकता था, और उल्टा Git जो higher-level abstraction देने की कोशिश करता है वही चीज़ों को और भ्रमित कर देता है
    • ऐसा नहीं है। Git commands का आधा हिस्सा low-level है और बाकी आधा high-level, इसलिए वे internal structure से 1:1 correspondence नहीं रखते
    • Mercurial का user experience सबसे अच्छा था, और उसे सीखने के लिए अलग से documentation पढ़ने की ज़रूरत नहीं पड़ी। Git इस्तेमाल करते समय भी मैं Mercurial की तरह ही काम करता रहा, और जब समस्या आती तो local repository मिटा देता—इसी तरह पूरे करियर में काम चला लिया
  • git rebase --abort मौजूद है, और rebase से पहले tag बनाकर बाद में git reset --hard $set_tag से वापस भी जाया जा सकता है। पिछली state गायब नहीं होती, इसलिए interactive rebase से डरने की कोई वजह नहीं है

    • मैं ठीक उसी काम के लिए temp या before-rebase नाम की branches अक्सर बना लेता हूँ। वे tag की तरह काम करती हैं, लेकिन branch को आगे-पीछे करना ज़्यादा आसान होता है, इसलिए बड़े rebase -i से पहले मैं आम तौर पर git branch before-rebase/some-feature चलाता हूँ
      वास्तव में git reset before-rebase की ज़रूरत मुझे बहुत कम पड़ी है, लेकिन rebase के बाद की branch से git log -p before-rebase की तुलना करके conflict resolution के दौरान हुए अनचाहे बदलाव जाँचने का काम मैंने कई बार किया है
    • अलग से safety tag बनाने की भी ज़रूरत नहीं है; reflog अपने-आप वही काम कर देता है
    • क्या आप git reflog इस्तेमाल करते हैं?
    • jj undo से आसान कुछ नहीं हो सकता
    • सही है। git rebase टाइप करने के बाद लगभग आधी बार मैं तुरंत --abort टाइप करता हूँ
  • मैं खुद को coder या programmer नहीं मानता, लेकिन Git सीखने से मुझे लगता है कि मेरी सोच में व्यवस्था की सुपरपावर आ गई। मैं इसका उपयोग वेबसाइट, design projects, electronics, composition, personal knowledge store, remote management scripts, config management, code snippets जैसी अनगिनत चीज़ों में करता हूँ, और भले यह परफ़ेक्ट न हो, फिर भी साथ काम करने वाले हर व्यक्ति को इसे सीखने की सलाह देता हूँ

    • मेरी संगठन क्षमता भी बहुत बेहतर हुई, लेकिन यह असर Git से ज़्यादा version control systems के पूरे विचार का है। Version control systems आपको अपनी बनाई चीज़ों को व्यवस्थित और सुरक्षित रखने देते हैं, और जब चाहें उतनी copies निकालने की सुविधा देते हैं, इसलिए आप बिना चिंता के काम कर सकते हैं
      David Allen की 『Getting Things Done』 में कहा गया है कि विचारों को इकट्ठा और व्यवस्थित करने के लिए एक विश्वसनीय system चाहिए। मैं tasks, projects और lists के लिए OmniFocus का उपयोग करता हूँ, लेकिन कंप्यूटर पर बनाए गए software और कुछ data को व्यवस्थित और सुरक्षित रखने के लिए मैं Git पर भरोसा करता हूँ
    • composition के लिए आप Git का इस्तेमाल कैसे करते हैं?
  • मुझे सिद्धांत रूप में ये commands बहुत पसंद हैं और jj के equivalent commands भी मैं हमेशा इस्तेमाल करता हूँ, लेकिन git history से बदले गए commits पर signature करने का तरीका मुझे नहीं मिला। Manual और Git source code देखने पर भी, अगर मौजूदा GPG-signed commit को git history reword HEAD~ से बदला जाए तो signatures सब गायब हो जाते हैं
    Commit signatures बनाए रखना चाहता था, इसलिए आखिरकार मुझे पारंपरिक git rebase -i पर लौटना पड़ा

    • यह एक बड़ी कमी लगती है और standard commit handling process को भी bypass करती दिखती है। Manual में भी git history को experimental feature कहा गया है, इसलिए इसे bug के रूप में report करना उचित होगा
  • नए कमांड के बिना भी मैं 10 साल से ज़्यादा समय से local Git commit history को बिना किसी बड़ी मुश्किल के काफी हद तक बदलता रहा हूँ। मुझे पूरा यक़ीन है कि Git और rebase के मुश्किल लगने की बड़ी वजह user interface की समस्या है, क्योंकि सही command-line options और उनके व्यवहार को खुद समझना पड़ता है
    Windows में TortoiseGit का rebase dialog शक्तिशाली होने के साथ इस्तेमाल में भी आसान था। Commit log में base commit पर right-click करके "Rebase onto this" चुनना होता है, और https://tortoisegit.org/docs/tortoisegit/tgit-dug-rebase.htm... की तरह commits का क्रम बदलना, split, squash, edit करना साफ़ तौर पर दिखता है, और बीच में cancel करने पर यह मूल स्थिति में लौट आता है
    हालांकि यह shell extension शैली पर आधारित है, इसलिए clicks कुछ ज़्यादा लगते हैं; tabs वाला standalone app होता तो बेहतर होता, लेकिन यह शायद TortoiseSVN से आई design principle है। मैंने Git Extensions भी थोड़ी देर इस्तेमाल किया, लेकिन या तो अक्सर इस्तेमाल होने वाले features नहीं मिले, या मैं पहले से TortoiseGit का इतना अभ्यस्त था कि उस पर टिक नहीं पाया। Log screen https://tortoisegit.org/docs/tortoisegit/tgit-dug-showlog.ht... में देखी जा सकती है

    • Windows से Linux पर आने के बाद जिन चीज़ों की कमी खली, उनमें एक integrated shell भी है, जहाँ हर program files/folders के right-click menu, TortoiseGit जैसे status icon overlays, और My Computer जैसी special objects जोड़ सकता था। मेरी समझ में यह XP पर अपने शिखर पर था, और Vista से Microsoft ने इसे ऐसे बदलना शुरू किया कि इसे extend करना कठिन हो गया
      लेकिन VC++6 का कोई menu सामान्य list की जगह folder जैसे shell object बनाकर file-open dialog में क्यों दिखाता था, यह आज भी समझ नहीं आता। वह project में components जोड़ने वाला wizard था, और dialog खुले रहने के दौरान मैं उस नकली folder को My Computer में ढूँढा करता था
    • पुराना workflow थोड़ा ज़्यादा झंझट वाला था, और नया command सचमुच अधिक सुविधाजनक है। fixup पहले से git commit --fixup या उससे भी पहले git rebase -i से संभव था, और reword भी git rebase -i से किया जा सकता था
      लगता है कि हर commit पर कौन-सा action करना है यह तय करने के लिए file edit करने की असुविधा पर काफी feedback जमा हुआ, इसलिए मौजूदा functionality को wrap करने वाला higher-level command जोड़ा गया
    • Git user interfaces में मैंने Magit के करीब आने वाला कोई tool नहीं देखा। यह गहरी functionality और सरल, stateful interface दोनों एक साथ देता है
      Command-line tools में अगला command चलाते ही पिछले output तक पहुँचना मुश्किल हो जाता है। git log में देखा गया commit अगर cherry-pick करना हो, तो उसका hash terminal में बचा न हो तो सिर्फ़ याददाश्त से टाइप नहीं किया जा सकता; लेकिन stateful interface कई interactions को parallel बनाए रखता है और पुरानी जानकारी को संभालकर रखता है
      Git में याद रखने लायक state information बहुत ज़्यादा होती है, जैसे current branch, पिछली branch, modified files, unpushed commits वगैरह। अगर shell plugin की branch display और autocomplete, कई tabs/window splits, tabs के बीच copy, autocomplete search जैसे आसपास के tools की आदत न हो, तो Git का इस्तेमाल बहुत अप्रिय हो सकता है
  • मेरे हिसाब से jj की असली कमजोरी सिर्फ़ Git integration है। यह file sets, revision sets और templates के लिए domain-specific language को औपचारिक रूप देता है और settings तथा CLI में उसे लगातार एक समान तरीके से इस्तेमाल करता है, साथ ही internal structure को सीधे उजागर किए बिना शक्तिशाली concepts को interface के रूप में देता है
    लेकिन यह इतना flexible है कि Git interoperability काफी हद तक conventions और discipline पर निर्भर करती है, और jj की लगभग हर state को Git में व्यक्त किया जा सकता है, इसलिए remote repository को बिगाड़ देना आसान है। इसे ठीक करने के लिए Git की गहरी समझ चाहिए
    अगर नया standalone development project हो, तो jj आज़माने लायक है। अनुकूल नज़रिए से देखें तो यह Subversion की तुलना में शुरुआती Git जैसा महसूस होता है; और अगर पहले jj के अपने तरीके की आदत पड़ जाए, तो बाद की कठिन Git integration के लिए भी शायद कुछ बेहतर तैयारी हो जाती है। संबंधित संदर्भ https://news.ycombinator.com/item?id=48903054 में है

    • सिद्धांत रूप में jj के लिए Git के अलावा दूसरे backends भी हैं, लेकिन इस समय production environment के लिए उपयुक्त बताया गया backend सिर्फ़ Git backend है। Git integration के बिना jj अभी वास्तविकता से ज़्यादा एक concept जैसा है
      मुझे jj और उसका version control mental model पसंद है, इसलिए मैं इसे इस्तेमाल करता रहूँगा, लेकिन कभी-कभी काम पूरा करने के लिए Git तक नीचे उतरना पड़ता है, इसलिए यह किसी नए configuration management system से ज़्यादा Git के ऊपर एक सुविधाजनक wrapper जैसा लगता है। अगर Git, jj workflow को support करने वाले commands जोड़ दे, तो jj को अलग से install करने की वजह ढूँढना मुश्किल होगा
  • git history fixup का सभी descendant branches को अपने-आप rebase कर देने वाला तरीका मेरे workflow से मेल नहीं खाता। मैं feature branch के हर version को myfeature.1, myfeature.2 की तरह सुरक्षित रखता हूँ, ताकि नए version में व्यवहार बदलने पर पुराना version ढूँढ सकूँ; git reflog इसका विकल्प नहीं हो सकता
    उदाहरण के लिए, अगर समस्या myfeature.58 और myfeature.59 के बीच आई हो, और हर feature में 15 commits हों, तो git range-diff myfeature.58~15..myfeature.58 myfeature.59~15..myfeature.59 से, base commit अलग होने पर भी, दोनों versions का commit-दर-commit अंतर देखा जा सकता है। इसलिए मैं किसी खास commit को शामिल करने वाली सभी branches के automatic rebase नहीं चाहता, और पुराने versions पर tags इस्तेमाल करने का तरीका भी मुझे खास जँचा नहीं

  • आधुनिक Git, अक्सर इस्तेमाल होने वाले git rebase --interactive के तीन कामों को कम friction वाले अलग commands के रूप में लागू करता है, और लगता है कि ये केवल तब काम करते हैं जब conflicts न हों

    • git history वाकई कितना उपयोगी है, इस पर मुझे संदेह है। मैं interactive rebase में commits को दृश्य रूप से खिसकाना पसंद करता हूँ, और मैं गलती से ऐसे orphaned commit hash को git history का target नहीं बनाना चाहूँगा जो current local branch से गायब हो चुका हो
  • मुझे समझ नहीं आता कि Git history को पूरी तरह चमकाने-सँवारने में इतना ज़ोर क्यों लगाया जाता है। वैसे भी कोई अलग-अलग commits दोबारा पढ़ता नहीं, इसलिए merge से पहले सबको squash कर दो

    • मैं इससे सख़्त असहमत हूँ। PR review करते समय मैं कम-से-कम commit history ज़रूर पढ़ता हूँ ताकि बदलावों का flow समझ सकूँ, और अगर वह "oops" और "fix" जैसे commits से भरी हो तो मैं उसे तुरंत reject कर देता हूँ
      commits काम की कहानी बताते हैं, इसलिए reviewer को intent समझने में अतिरिक्त मेहनत न करनी पड़े, इसके लिए पढ़ने लायक commit history देना चाहिए
    • मैं individual commits अक्सर पढ़ता हूँ। अच्छी तरह maintain किए गए repository में bug या regression track करने और author के change intent को समझने में यह बहुत उपयोगी होता है
    • जब यह जानना हो कि code ऐसा क्यों बना, तो उस code को introduce करने वाले commit का context देखना बहुत मददगार होता है। अगर एक ही विशाल commit में कई अलग-अलग बदलाव ठूँस दिए गए हों, तो उसकी उपयोगिता काफ़ी घट जाती है
    • यह सोच दिखाती है कि आप किसी बड़े team में काम नहीं करते। छुट्टी से लौटने पर मैं सबसे पहले Git history पढ़कर देखता हूँ कि इस दौरान क्या बदला, और CI टूटने पर भी यह काम आता है, इसलिए मैं लगभग हर दिन किसी-न-किसी रूप में history देखता हूँ
      अगर codebase और features बहुत छोटे हों तो सब कुछ squash करना ठीक हो सकता है, लेकिन मैं ऐसा तरीका पसंद करता हूँ जिसमें वास्तविक बदलावों का flow दिखे और commits को चुनिंदा तौर पर cherry-pick या revert किया जा सके
    • हर logical change के लिए एक commit के हिसाब से अच्छी तरह सजी history, regression को bisect करने में बेहद उपयोगी होती है
  • मेरी समझ यह थी कि अगर अभी D checkout किया हुआ है और B को B' में बदला जाए, तो git rebase --update-refs rebase range के बाहर वाले E को वैसे ही छोड़ देगा और सिर्फ़ B' → C' → D' बनाएगा, जबकि पुराना B → E बना रहेगा
    दूसरी ओर, क्या git history fixup E को भी E' में update करता है? अगर हाँ, तो क्या दशकों से आदत बने git rebase के साथ भी सभी descendant branches को update करने का कोई तरीका है?

    • commit hash में parent commit का hash भी शामिल होता है, इसलिए E को जस-का-तस नहीं छोड़ा जा सकता। मैंने खुद --update-refs सीधे इस्तेमाल नहीं किया है, लेकिन docs के अनुसार देखें तो E' तक बनने वाला तीसरा graph ही result होना चाहिए, यानी जो behavior आप चाहते हैं वह git rebase में पहले से संभव है
    • https://blog.hot-coffee.dev/en/blog/git_update_refs/ देखें