Release of the mergiraf 0.4 project has been published, which develops a driver for Git with the implementation of three-way merging capabilities. Mergiraf supports resolving various types of merge conflicts and can be used for different programming languages and file formats. It can be called separately to handle conflicts arising from the standard Git workflow, or it can replace the merge handler in Git to extend the capabilities of commands such as merge, revert, rebase, and cherry-pick. The code is distributed under the GPLv3 license. The new version adds support for Python, TOML, Scala, and Typescript, as well as performance optimizations.
Below is a detailed description of the problems addressed by mergiraf:
Software is a striking example of an extremely complex system. Complex systems share one common property â they are COMPLEX â and you cannot expect the necessary complex behavior to emerge by itself, randomly. Instead, these systems evolve over time, step by step, and each mutation is carefully verified at each stage. Achieving this requires a well-defined structure and appropriate tools. The evolution of any complex system can be visualized as a directed tree, where the root represents an empty set of functions, and every node â except for the root â is the result of applying a mutation to its parent.
In the context of products, each node is called a 'version,' representing a specific set of features and anti-features. Any change to this set is considered a mutation, forming an edge in our directed acyclic graph. These features are inherently abstract; they do not directly reflect how physical systems operate, but rather demonstrate how rational agents perceive the utility of these systems. To translate ideas into real-world implementations, it is necessary to roll up your sleeves and dive into *sufficiently* low-level details, in a language that can express and explain exactly how everything works. In software development, these low-level details are typically represented by source code.
För att gradvis föra koden till ett tillstÄnd som uppvisar det önskade beteendet och dokumentera hur de nÄdde dit presenterar programmerarna sitt arbete i termer av snapshots och changesets. En snapshot representerar ett visst tillstÄnd av produkten med alla detaljer pÄ lÄg nivÄ, medan en changeset betecknar övergÄngen mellan snapshots. Vanligtvis genereras snapshots av enskilda changesets frÄn deras förÀldrar, sÄ dessa snapshots mÀrks nÀstan alltid med det som changesets som skapade dem gör, varför dessa termer ofta anvÀnds omvÀxlande.
Ibland finns det snapshots som skapats frĂ„n flera övergĂ„ngar â sammanslagningar av commits. Dessa Ă€r svĂ„ra att arbeta med, sĂ„ de undviks vanligtvis. Moderna versionshanteringssystem med öppen kĂ€llkod, sĂ„som Git, tillhandahĂ„ller ganska grundlĂ€ggande funktioner för att hantera utvecklingsarbetsflöden. De tillĂ„ter utvecklare att organisera snapshots i riktade acykliska grafer, annotera dem med kommentarer och vid behov Ă€ndra deras ordning.
Denna funktionalitet gör det möjligt för utvecklare att skriva en semantiskt meningsfull historia för projektet, vilket Àr avgörande för felsökning och att besvara frÄgor som "Varför introducerades denna lÄg-nivÄ detalj (t.ex. en variabel)?", "Hur mycket procent av projektet bidrog jag till?", "Vem blev hackad med inbÀddningar och nÀr?", "Vilken lÄg-nivÄ Àndring bröt denna funktion (Àven om det egentligen inte borde ha, vi har kollat allt!?)"
Versionshanterssystem kompletterar denna koncept med grenen - ett lÄg nivÄ begrepp som helt enkelt betyder en kontinuerlig del av projektets lÄgnivÄhistoria, meningsfullt för utvecklaren. Grenar anvÀnds vanligtvis för specifika funktionaliteter, och det kan ibland skapas flera grenar för olika kandidater för implementering av samma funktion. Genom att anvÀnda arbetsflöden med grening (som faktiskt Àr mainstream och standard inom utveckling, anvÀnds överallt och allmÀnt), kan varje utvecklare effektivt hantera mÄnga konflikterande projektgrenar, var och en varierande i beredskapsgrad eller kvalitet. Detta gör det möjligt för utvecklare att kombinera resultaten av sina och andras arbeten utan att behöva skriva om allt manuellt varje gÄng.
Vanligtvis skapas en huvudgren som representerar den 'officiella' produkten, frÄn vilken sidogrener grenas för varje funktion, som regelbundet (idealiskt - efter varje commit) synkroniseras med huvudgrenen, vilket gör att utvecklarna kan arbeta med den mest aktuella versionen av produkten och samtidigt implementera funktioner som de för tillfÀllet utvecklar, och upptÀcker problem som uppstÄr frÄn andra utvecklares ÄtgÀrder sÄ tidigt som möjligt.
Vid försök att kombinera funktionerna hos olika snapshotar (vilket bara Àr en frÄga om att hitta en gemensam förfader och tillÀmpa changesets som skapas, sekventiellt ovanpÄ en annan, kallas denna ÄtgÀrd rebase, sammanslagning Àr nÀstan som rebase, bara strukturerar commit-grafen annorlunda vilket gör den svÄr att manipulera, sÄ man undviker sammanslagningar till förmÄn för rebases) uppstÄr problem. Moderna versionshanteringssystem (VCS) anvÀnder interna algoritmer för att slÄ samman förÀndringar, som helt enkelt bryter ner filer i individuella rader, betraktar varje rad som ett tecken och filerna som deras sekvenser, och tillÀmpar algoritmer för att sammanfoga dem, som har sitt ursprung i bioinformatik.
TyvÀrr har denna rad-för-rad-representation av koden inget att göra med dess innehÄll. Dess enda fördel Àr att den Àr enkel och universell. Diskrepanser leder till konflikter, vilket stÀndigt skapar huvudvÀrk för utvecklare. Att lösa konflikter krÀver att utvecklaren noggrant studerar bÄda versionerna av koden, inte bara de delar som markeras som "Àndrade" eller "konflikterande" av den rad-för-rad-jÀmförelsealgoritmen, utan möjligtvis hela projektet.
Utvecklaren mÄste förstÄ Àndringarna, manuellt skriva den sammanslagna koden och ÄtgÀrda alla avvikelser. Problemen blir fler nÀr radverktyget felaktigt identifierar Àndringar, vilket ofta hÀnder vid stora förÀndringar, inklusive triviala som omformatering av koden. Om efterföljande Àndringar inte kan tillÀmpas pÄ den manuellt sammanslagna koden förvandlas situationen till en total mardröm. Trots de skrÀmmande fallen fungerar rad-algoritmen i de flesta fall, sÀrskilt om utvecklarna aktivt försöker att inte skapa problem för den. Ett sÀtt att minimera sÄdana problem Àr att stÀlla krav pÄ att kÀllkoder bearbetas med kanonikaliseringsverktyg som black.
SjÀlvklart Àr den rÀtta lösningen pÄ skrÀmmande fall (och faktiskt inte bara för dem, rad-algoritmen Àr en heuristik, och den kan enkelt leda till icke-fungerande kod, till exempel om en utvecklare har döpt om en variabel medan en annan under tiden har skrivit en bit ny kod som anvÀnder den variabeln, sÄ kommer det inte att bli nÄgon sammanslagnings-/omloceringkonflikt, men resultatet blir icke-fungerande) att anvÀnda en korrekt intern modell.
Trots att forskning inom detta omrÄde har pÄgÄtt i nÀstan 30 Är, och lett till skapandet av flera proprietÀra kommersiella produkter, har denna forskning fram till nyligen inte omvandlats till praktiskt tillÀmpliga produkter med öppen kÀllkod. De flesta lösningar med öppen kÀllkod började utvecklas i början av 2010-talet och var frÀmst inriktade pÄ programmeringssprÄket Java.
Den mest framstÄende fria implementationen frÄn den perioden, GumTree, skapades av en forskare med akademisk bakgrund, Àr skriven i Java, har sin egen abstrakta interna representation som föregÄr treesitter, och har backends bÄde baserade pÄ treesitter och andra verktyg för att parsa kÀllkod till abstrakta representationer. Detta system kan bara generera (i form av en textlogg över hÀndelser, det finns ocksÄ ett API som kan enkelt anropas frÄn vilket programmeringssprÄk som helst som har bindningar till Java) och visualisera förÀndringar. DÀremot Àr det odugligt för att slÄ samman förÀndringar eller för att visa de genererade diff-filerna frÄn början (Àven om det troligtvis Àr möjligt att ladda upp diffs via API).
En yngre och mer praktiskt anvÀndbar implementation av difftastic Àr skriven i Rust, baserad pÄ treesitter, och fokuserar pÄ att generera markerade diffs i konsolen. Detta system Àr ocksÄ inriktat pÄ att visualisera diffs och syftar överhuvudtaget inte till att slÄ samman förÀndringar eller tillÀmpa patchar.
Nyligen har projektet mergiraf dykt upp och utvecklas aktivt. Detta verktyg, skrivet i Rust (tar upp 21 MiB!), Ă€r ocksĂ„ baserat pĂ„ treesitter, som redan har blivit en standard för parsers av kontextfria grammatik i utvecklingsverktyg, liksom LLVM har blivit för optimering av lĂ„gnivĂ„ instruktionrepresentationer. Till skillnad frĂ„n konkurrenterna erbjuder mergiraf funktioner för automatisk konfliktlösning vid sammanslagningar istĂ€llet för att generera diffs. Under huven anvĂ€nder mergiraf för att generera patchar en implementation av algoritmen som anvĂ€nds i GumTree, och för att tillĂ€mpa â en implementation av algoritmen som anvĂ€nds i spork, anpassad för treesitter-strukturer.
TyvÀrr Àr serialisering av patchar till filer som senare kan tillÀmpas inte implementerad (men det Àr fullt möjligt att det kan genomföras genom att parsa loggar över hÀndelser genererade av GumTree). Ett annat lovande sÀtt att anvÀnda skillnader kan vara att tillÀmpa skillnader inte genom patchar utan genom funktionaliteten för refaktorering av LSP-servrar, vilket kan hjÀlpa till med att upptÀcka konflikter pÄ projektens nivÄ. Visualisering stöds endast för konflikter.
Exempel pÄ arbete: allmÀn förfader «base.py» (indrag med tabbar, överflödig rad i början) foo = 1 def main(): print(foo + 2 + 3) «a.py» (indrag fortfarande med tabbar, 2 överflödiga rader i början istÀllet för en, för felsökningsutskrift anvÀnds biblioteket icecream, klass «baz» tillagd: from icecream import ic foo = 1 def main(): ic(foo + 2 + 3) class baz: def __init__(self): «»»baz»»» «b.py» (variabeln «foo» omdöpt till «bar», bearbetad med «black» efter Àndringar, som ett resultat Àr indrag med mellanslag och överflödiga rader borttagna): bar = 1 def main(): print(bar + 2 + 3) Anrop .\/mergiraf merge .\/base.py .\/a.py .\/b.py -x a.py -y b.py -s base.py -o .\/res.py ger följande resultat from icecream import ic bar = 1 def main(): ic(bar + 2 + 3) class baz: def __init__(self): «»»baz»»» (för felsökningsutskrift anvÀnds biblioteket «icecream», variabeln «foo» har omdöpts till «bar», bearbetad med «black» efter Àndringar, vilket resulterar i att indrag Àr med mellanslag och överflödiga rader har tagits bort, en blandning av tabbar och mellanslag för indrag, men i godkÀnt skick).
HÀr syns ocksÄ verktygets brist. Dokumentstil konfigureras vanligtvis i filer med «.editorconfig», och globala stilÀndringar, som att byta tabbar till mellanslag och anta black-stil som det gjordes i «b.py», Ätföljs vanligtvis av Àndringar i «.editorconfig». DÀrför bör verktyget för att korrekt tillÀmpa sÄdana Àndringar ha ett koncept för global stil «per standard» och kunna hÀmta instÀllningar frÄn «.editorconfig».
KĂ€lla: opennet.ru
