Udgivelsen af projektet mergiraf 0.4, der udvikler en driver til Git med implementeringen af muligheden for tredobbelt sammensmeltning, er offentliggjort. Mergiraf understøtter løsning af forskellige former for konflikter ved sammenfletning og kan anvendes til forskellige programmeringssprog og filformater. Det er muligt både at kalde mergiraf separat for at håndtere konflikter, der opstår ved brug af standard Git, samt at erstatte Git's sammenfletningsbehandler for at udvide funktionerne i kommandoer som merge, revert, rebase og cherry-pick. Koden distribueres under GPLv3-licensen. I den nye version er der tilføjet understøttelse af språkene Python, TOML, Scala og Typescript, samt optimering af ydeevnen.
Nedenfor er en detaljeret beskrivelse af de problemer, der løses ved hjælp af mergiraf:
Software er et fremragende eksempel på et ekstremt komplekst system. Komplekse systemer har ét fælles træk - de er KOMPLEKSE - og man kan ikke forvente, at det ønskede komplekse adfærd opstår af sig selv, tilfældigt. I stedet udvikler disse systemer sig over tid, skridt for skridt, og hver mutation bliver nøje kontrolleret på hvert trin. For at opnå dette kræves der en veldefineret struktur og de rette værktøjer. Evolutionen af ethvert komplekst system kan visualiseres som et rettet træ, hvor roden repræsenterer et tomt sæt funktioner, og hver node - undtagen roden - er resultatet af anvendelsen af en mutation på sin forælder.
I konteksten af produkter kaldes hver node en 'version', som repræsenterer et bestemt sæt funktioner og anti-funktioner. Enhver ændring af dette sæt betragtes som en mutation, der danner en kant i vores rettede acykliske graf. Disse funktioner er i sagens natur abstrakte; de afspejler ikke direkte, hvordan fysiske systemer fungerer, men viser snarere, hvordan fornuftige agenter opfatter nytten ved disse systemer. For at oversætte ideer til eksisterende implementeringer i den virkelige verden, skal man opnå dybdegående indsigt i *tilstrækkeligt* lave detaljer, et sprog, hvormed man kan udtrykke og forklare præcist, hvordan alt fungerer. I softwareudvikling er disse lavniveauer normalt repræsenteret ved kildekoden.
For gradually bringing the source code to a state that exhibits the required behavior and documenting how they achieved it, programmers represent their work in terms of snapshots and changesets. A snapshot represents a specific state of the product with all low-level details, while a changeset signifies the transition between snapshots. Typically, snapshots are generated from individual changesets to their parents, which is why these snapshots almost always mark what the changesets that created them do, making these terms frequently interchangeable.
Sometimes there are snapshots resulting from multiple transitions—merging commits. These are difficult to work with, so they are generally avoided. Modern open-source version control systems like Git offer very basic capabilities for managing development workflows. They allow developers to organize snapshots as directed acyclic graphs, annotate them with comments, and change their order when necessary.
This functionality enables developers to write a semantically meaningful history of the project, which is crucial for debugging and addressing questions like 'Why was this low-level detail (e.g., variable) introduced?', 'Approximately what percentage is my contribution to this project?', 'Who was compromised, and when did the backdoor implementations occur?', 'What low-level change broke this feature (even though it shouldn't have, we checked everything!?)'
Versionsstyringssystemer supplerer dette med begrebet grene — et lavniveau begreb, der blot betyder et kontinuerligt fragment af lavniveaus historien om et projekt, semantisk betydningsfuld for udvikleren. Grener bruges typisk til specifik implementering af funktioner, nogle gange oprettes flere grene til forskellige kandidater til implementering af den samme funktion. Ved at bruge arbejdsprocesser med grene (som i realiteten er mainstream og standard for udvikling, anvendes overalt), kan hver enkelt udvikler effektivt håndtere mange konflikterende projektsgrene, hver med forskellige grader af beredskab eller kvalitet. Dette giver udviklerne mulighed for at kombinere resultaterne af deres og andres arbejde uden konstant at skulle gentage hele processen manuelt.
Normalt oprettes der en hovedgren, der repræsenterer det 'officielle' produkt, hvorfra sideskud bliver udsprunget for hver funktion, som regelmæssigt (ideelt set — efter hver commit) synkroniseres med hovedgrenen, hvilket gør det muligt for udviklerne at arbejde med den nyeste version af produktet og på samme tid implementere funktioner, som de i øjeblikket udvikler, ved at opdage problemer, der opstår fra andres handlinger så tidligt som muligt.
Ved forsøg på at kombinere funktioner fra forskellige snapshots (som blot er at finde den fælles forfader og anvende changesets, der skaber dem, sekventielt oven på en anden, kaldes denne operation rebase; merging er næsten som rebase, det strukturerer blot commit-grafen på en anden måde, hvilket gør det besværligt at manipulere med dem, derfor forsøger man at undgå merging til fordel for rebase) opstår der problemer. Moderne versionsstyringssystemer (VCS) bruger interne algoritmer til at slå ændringer sammen, der simpelthen opdeler filer i separate linjer, betragter hver linje som et symbol, og filer som deres sekvenser, og anvender derefter algoritmer til at kombinere dem, som stammer fra bioinformatik.
Desværre har denne linje-for-linje fremstilling af kildekoden intet at gøre med dens indhold. Dens eneste fordel er, at den er enkel og universel. Uoverensstemmelser fører til konflikter og er en konstant kilde til hovedpine for udviklere. At løse konflikter kræver, at udvikleren grundigt gennemgår begge versioner af koden, ikke kun de afsnit, der er markeret som 'ændrede' eller 'konfliktfyldte' af den linje-for-linje sammenligningsalgoritme, men muligt også hele projektet.
Udvikleren skal forstå ændringerne, manuelt skrive den sammensatte kode og løse eventuelle uoverensstemmelser. Problemerne bliver mange, når linje-for-linje værktøjet forkert identificerer ændringer, hvilket ofte sker ved store ændringer, herunder trivielle, såsom omformatering af koden. Hvis efterfølgende ændringer ikke kan anvendes på den manuelt sammenflettede kode, bliver situationen til et rent mareridt. På trods af de skræmmende tilfælde fungerer linje-for-linje algoritmen i de fleste tilfælde, især hvis udviklerne aktivt forsøger ikke at skabe problemer for den. En måde at minimere sådanne problemer på er at kræve, at kildekoden behandles med kanoniseringsværktøjer som black.
Det rigtige svar på de skræmmende tilfælde (og generelt ikke kun for dem, linje-for-linje algoritmen er en heuristik, der trivielt kan føre til ikke-fungerende kode, for eksempel har en udvikler omdøbt en variabel, mens en anden samtidig skrev noget nyt kode, der bruger den variabel; der vil ikke være nogen konflikter ved sammenfletning/ombase, men resultatet bliver ikke-fungerende) er at bruge den rigtige interne model.
På trods af at forskningen på dette område har været i gang i næsten 30 år og har ført til udviklingen af flere proprietære kommercielle produkter, var denne forskning indtil for nylig ikke blevet omsat til praktisk anvendelige produkter med åbent kildekode. Størstedelen af open-source løsninger begyndte at udvikle sig i begyndelsen af 2010'erne og var primært fokuseret på Java.
Den mest fremtrædende frie implementering fra denne periode, GumTree, blev skabt af en forsker med en akademisk baggrund. Den er skrevet i Java, har sin egen abstrakte interne repræsentation, der går forud for treesitter, og har backend-løsninger, både baseret på treesitter og andre værktøjer til at parse kildekode til abstrakte repræsentationer. Dette system kan kun generere (i form af tekstlogfiler over begivenheder, og der er også en API, der kan kaldes trivielt fra ethvert programmeringssprog, der har bindings til Java) og visualisere ændringer. Dog er det ikke anvendeligt til at sammenflette ændringer, ligesom det ikke kan bruges til at se de genererede diff-filer ud af boksen (det er dog sandsynligt, at upload af diffs kan implementeres via API'en).
En nyere og mere praktisk implementering af difftastic er skrevet i Rust, er baseret på treesitter og fokuserer på at generere fremhævede diffs i konsollen. Dette system er også rettet mod visualisering af diffs og sigter overhovedet ikke mod at sammenflette ændringer eller anvende patches.
For nylig er projektet mergiraf dukket op og er i aktiv udvikling. Dette værktøj, skrevet i Rust (optager 21 MiB!), er også baseret på treesitter, som allerede er blevet en standard for parsere af kontekstfrie grammatikker i udviklingsværktøjer, ligesom LLVM er blevet det for optimering af lavniveau instruktioner. I modsætning til konkurrenterne tilbyder mergiraf funktioner til automatisk løsning af sammenfletningskonflikter i stedet for at generere diffs. Under overfladen bruger mergiraf til generation af patches en implementering af algoritmen anvendt i GumTree, og til anvendelse en implementering af algoritmen, der anvendes i spork, tilpasset treesitter-strukturer.
Desværre er serialisering af patches til filer, der senere kan anvendes, ikke implementeret (men det er sandsynligt, at det kan implementeres ved at parse de eventlogs, der genereres af GumTree). En anden lovende metode til anvendelse af forskellene kan være at anvende forskellene ikke gennem patches, men gennem refaktoriseringens funktionalitet fra LSP-servere, hvilket kan hjælpe med at opdage konflikter på projektets overordnede niveau. Visualisering understøttes kun for konflikter.
Eksempel på arbejde: den fælles forfader «base.py» (indrykning med tabulatorer, unødvendig linje i starten) foo = 1 def main(): print(foo + 2 + 3) «a.py» (indrykningen er stadig med tabulatorer, 2 unødvendige linjer i starten i stedet for én, til debugging print bruges biblioteket icecream, klassen «baz» er tilføjet: from icecream import ic foo = 1 def main(): ic(foo + 2 + 3) class baz: def __init__(self): «»»baz»»» «b.py» (variablen «foo» er blevet omdøbt til «bar», behandlet med «black» efter ændringerne, hvilket resulterer i, at indrykningerne nu er med mellemrum og unødvendige linjer er skåret væk): bar = 1 def main(): print(bar + 2 + 3) Kald .\/mergiraf merge .\/base.py .\/a.py .\/b.py -x a.py -y b.py -s base.py -o .\/res.py giver følgende resultat from icecream import ic bar = 1 def main(): ic(bar + 2 + 3) class baz: def __init__(self): «»»baz»»» (til debugging print bruges biblioteket «icecream», variablen «foo» er blevet omdøbt til «bar», behandlet med «black» efter ændringerne, hvilket resulterer i, at indrykningerne er med mellemrum og unødvendige linjer er skåret væk, en blanding af tabulatorer og mellemrum til indrykning, men i tilladt format).
Her ses samtidig et begrænsning ved værktøjet. Dokumentstilen konfigureres normalt i filer «.editorconfig», og globale ændringer i stilen, såsom skift fra tabulatorer til mellemrum og anvendelse af black-stilen, som det blev gjort i «b.py», følges normalt af ændringer i «.editorconfig». Derfor skal værktøjet for mere korrekt anvendelse af sådanne ændringer have en koncept for global standardstil og være i stand til at hente indstillinger fra «.editorconfig».
Kilde: opennet.ru
