3 Punkte von GN⁺ 2024-03-22 | 1 Kommentare | Auf WhatsApp teilen
  • Difftastic ist ein CLI-Diff-Tool, das Dateien nicht zeilenweise, sondern anhand der Syntaxstruktur von Code vergleicht
  • Intern wird Code mit tree-sitter geparst, sodass tatsächlich geänderte Ausdrücke besser erkannt werden, auch wenn sich Zeilen nur durch Formatierung verschoben haben
  • Auch wenn ein Code-Formatter Elemente auf mehrere Zeilen aufteilt oder ein Wrapper hinzugefügt wurde, kann es geänderte Strukturen und unveränderte innere Inhalte getrennt darstellen
  • Anleitungen zur Git-Konfiguration und Installationsdokumentation sind verfügbar; unterstützt werden viele Programmiersprachen sowie strukturierte Textformate wie JSON und YAML
  • Unter der MIT-Lizenz veröffentlicht, kann es heruntergeladen, verändert und geteilt werden und eignet sich dazu, bestehende Diff-Workflows um strukturelle Vergleiche zu ergänzen

Vergleicht Syntaxstrukturen statt Zeilen

  • Difftastic ist ein CLI-Diff-Tool, das Dateien anhand der Syntax vergleicht
  • Der Fokus liegt darauf, Ergebnisse zu zeigen, die die Codestruktur besser verstehen als ein gewöhnlicher zeilenbasierter Diff
  • Code wird mit tree-sitter geparst
    • Auch innere Ausdrücke, die in einem zeilenorientierten Diff wie geändert aussehen könnten, lassen sich daraufhin unterscheiden, ob sie tatsächlich geändert wurden
  • Selbst wenn ein Code-Formatter Code auf mehrere Zeilen aufteilt, kann es die tatsächlichen Änderungen anzeigen
  • Wird ein Wrapper hinzugefügt, ordnet es die Trennzeichen korrekt zu
    • Selbst wenn sich zugleich der innere Inhalt ändert, kann der hinzugefügte Wrapper angezeigt werden
  • Statt der Diff-Hunk-Syntax wie @@ -5,6 +5,7 @@ zeigt es die tatsächlichen Zeilennummern der Dateien vor und nach der Änderung

Unterstützte Sprachen und Git-Integration

  • Projektmaterialien sind auf GitHub, im Manual und unter Install verfügbar
  • Beispiele für unterstützte Programmiersprachen sind:
    • C++, C#, Clojure, Dart, Erlang, Go, Haskell, Java, JavaScript, Kotlin, Lisp, Lua, OCaml, PHP, Python, R, Ruby, Rust, Scala, TypeScript
    • Die vollständige Liste findet sich in der Liste der unterstützten Sprachen im Manual
  • Beispiele für unterstützte Dateiformate sind:
  • Es kann zusammen mit Git verwendet werden; das Manual enthält eine Anleitung zur Git-Konfiguration
  • Difftastic ist unter der MIT-Lizenz veröffentlicht und kann heruntergeladen, verändert und geteilt werden

1 Kommentare

 
GN⁺ 2024-03-22
Meinungen auf Hacker News
  • Manche wissen es vielleicht schon, aber dieses Tool ist auf tree-sitter(https://tree-sitter.github.io/tree-sitter/) aufgebaut und leistet fürs Parsing gewissermaßen das, was LSP für die Analyse geleistet hat.
    Es wandelt Code in einen abstrakten Syntaxbaum (AST) um und stellt eine Standardschnittstelle bereit, über die Clients wie Editoren oder Diff-Tools diesen AST nutzen können.
    Solche Tools müssen nicht Dutzende Sprachen direkt unterstützen; es reicht, tree-sitter zu unterstützen, und sie funktionieren automatisch mit allen Sprachen, die tree-sitter unterstützt.
    Wenn man eine neue Sprache entwickelt, erstellt man einen tree-sitter-Parser, und damit können alle Tools, die mit tree-sitter umgehen, diese Sprache unterstützen.
    Dank dieser beiden großen Innovationen explodieren die Verbesserungen solcher Tools geradezu. Editoren, Diff-Tools usw. können nun Dutzende bis Hunderte Sprachen unterstützen, ohne dass alle dieselbe Arbeit doppelt machen müssen – wirklich großartig.

    • Ich stimme voll und ganz zu, und um einen Kommentar von mir aus dem letzten Jahr wieder aufzugreifen: Es ist auch erwähnenswert, dass tree-sitter keine Abhängigkeiten hat.
      Zur Einordnung: Einige Leute aus unserem Team pflegen die OCaml-Bindings für tree-sitter und tragen im Rahmen der Arbeit an Semgrep auch häufig zu Grammatiken bei. Semgrep verwendet tree-sitter für Code-Suche und um Queries, die selbst Codefragmente sind, in AST-Matcher zu parsen.
      Wenn man einen Linter baut, muss man oft auch die Runtime der Zielsprache mitbringen. Wenn man zum Beispiel in Python mit dem eingebauten Modul ast einen Parser baut, muss man Sprachversion und Features aufeinander abstimmen; daher kann ein unter Python 2.7 laufendes Pylint keinen Python-3-Code parsen.
      Wenn man mehrere Sprachen unterstützen will, wird das viel lästiger, als man zunächst denkt.
      Vor tree-sitter war es oft am besten, die in die Sprache eingebauten AST-Tools zu verwenden, weil damit am ehesten gewährleistet war, dass sie mit der neuesten Syntax Schritt halten.
      Für mich liegt die Genialität von tree-sitter darin, dass es viel einfacher geworden ist, Sprachparser aktuell zu halten als mit traditionellen Grammatikansätzen.
      Wer mehr über die Designentscheidungen von tree-sitter wissen möchte, dem empfehle ich den Strange-Loop-Vortrag von Max Brunsfield sehr: https://www.youtube.com/watch?v=Jes3bD6P0To
      In der Folge wurden neben difftastic auch mehrere neue Tools wie neovim, Zed, Semgrep und die Code-Suche von GitHub auf tree-sitter aufgebaut.
    • Ich stimme zu, dass tree-sitter ein hervorragendes Tool ist, aber Grammatiken selbst zu schreiben kann extrem schwierig sein.
      Ich habe eine tree-sitter-Grammatik und ein Set von Highlighting-Queries für VHDL geschrieben, und es gab viele Schwierigkeiten, die VHDL-Grammatik in tree-sitter auszudrücken.
    • Ich halte diese Beschreibung nicht für korrekt. Es gibt keine gemeinsame Schnittstelle im Sinne von „tree-sitter sprechen“, und jeder tree-sitter-Parser gibt keinen standardisierten abstrakten Syntaxbaum aus, sondern jeweils unterschiedliche konkrete Syntaxbäume.
      LSP löst mit einer Standardschnittstelle für query-orientierte Compiler tatsächlich das M×N-Integrationsproblem zwischen M Editoren und N Sprachen.
      tree-sitter löst dieses Problem nicht wirklich, sondern macht es für Editoren oder Tools deutlich einfacher, N Integrationen zu schreiben.
    • Was mich an tree-sitter am meisten stört, ist, dass dieser Ansatz für mehrere Sprachen, die mich interessieren, nicht funktioniert.
      Common Lisp lässt sich ohne vollständige Lisp-Implementierung nicht parsen, die Haskell-Grammatik ist so komplex, dass die Grammatik unvollständig ist, C/C++ ist schon wegen des Präprozessors schwer korrekt zu parsen, und Perl-Parsing ist turingvollständig.
      Wie andernorts vorgeschlagen wurde, scheint es sinnvoller, ein Format zu definieren, das bestehende Parser als zusätzliche Ausgabe erzeugen können, statt Parser in einem neuen Ökosystem neu schreiben zu lassen.
    • Könnte man eine tree-sitter-Grammatik für natürliche Sprachen wie Englisch erstellen, sodass jeder Satz als Satzeinheit markiert wird?
      Dann könnte difftastic Änderungen nach Sätzen statt nach visuellen Zeilen anzeigen.
      Bei Essays sind Diffs nach visuellen Zeilen seltsam, weil geänderte Sätze meist mitten in einer visuellen Zeile beginnen.
  • Wenn man es über cargo installiert hat, aktualisiert man difftastic und verwandte Tools so:
    cargo install cargo-update
    cargo install-update --list
    cargo install-update --all
    Es gibt auch interessante Rust-Projekte, die sich über cargo installieren lassen.
    https://mise.jdx.dev/ mise-en-place ist eine schnelle und flexible Alternative zu https://asdf-vm.com/.
    https://github.com/ajeetdsouza/zoxide ist ein großartiger Ersatz für cd: Es speichert Orte, zu denen man gewechselt ist, und kann dann mit einer Teilübereinstimmung wie "z hel" nach "~/projects/helloworld" wechseln.
    https://github.com/bootandy/dust ist ein ergänzendes Tool zu du und zeigt, welche Verzeichnisse viel Speicherplatz belegen.

    • Drei weitere aufgeräumte Tools: https://github.com/eza-community/eza ist ein visuell ansprechenderes ls, https://github.com/ClementTsang/bottom ist ein htop mit Diagrammen, und https://github.com/sharkdp/bat ist ein cat mit Syntaxhervorhebung.
    • Ich mag zoxide wirklich sehr. lsd könnte man der Liste auch hinzufügen: ein hübscheres ls.
    • Ich habe mise-en-place ausprobiert, und genau so hätte ich mir asdf gewünscht.
    • Von den neuen du-artigen Tools gefällt mir dua-cli am besten. Es ist eine interaktive Terminal-UI, ein Klon von ncdu.
      Ich habe es gefunden, als ich nach etwas gesucht habe, weil ncdu keinen hellen Modus hat.
    • ncdu ist mit Abstand der beste Ersatz für du.
  • Ich verstehe kaum, warum Tools wie git so etwas nicht standardmäßig anbieten
    Ich nutze difft seit etwa einem Jahr, und mein größter Kritikpunkt ist, dass es schwerfällt, in Umgebungen ohne difft wieder zu anderen diff-Tools zurückzukehren
    Ich frage mich, ob es auch Forschung zu semantikbasierten diff-Tools gibt. Also etwa zu Tools, die Fälle behandeln, in denen sich die Syntax geändert hat, die Semantik aber gleich geblieben ist
    Als allgemeines Problem klingt das schwierig, aber bei kleinen DSLs oder Teilmengen mancher Sprachen könnte es möglich oder nützlich sein

    • Ich halte es für richtig, standardmäßig das alte diff zu verwenden
      Es ist auf fast jedem System, auf dem man git ausführen will, bereits vorhanden, schnell, klein, und jeder kennt die grundlegende Bedienung
      Trotzdem ist es gut, dass man diesen Standard leicht ändern kann
    • Ich arbeite an https://semanticdiff.com/; es erkennt grundlegende semantische Änderungen, etwa wenn Dezimalliterale in Hexadezimalzahlen geändert werden oder die Reihenfolge von Schlüsseln in einem JSON-Objekt geändert wird
      Es ist kein Kommandozeilen-Utility, sondern eine VS-Code-Erweiterung und eine GitHub App
      Wenn dich interessiert, wie es funktioniert und worin es sich von difftastic unterscheidet, siehe https://semanticdiff.com/blog/semanticdiff-vs-difftastic/
    • Difftastic ist ein nützliches Tool, aber meiner Erfahrung nach ist es zu langsam, um als Standardoption für ein so breit genutztes Tool wie git zu dienen
    • Um so etwas zu machen, wird ein diff-Tool praktisch zu einem Compiler
    • Ich probiere gerade difft mit git aus und es gefällt mir ziemlich gut
      Einer der Gründe, warum es meiner Meinung nach trotzdem nicht der Standard werden sollte, ist, dass es Unterschiede bei Whitespace verbirgt. Ob das konfigurierbar ist, habe ich noch nicht geprüft
  • Ich stimme der Erklärung völlig zu, dass gefragt wird, ob man die Syntax @@ -5,6 +5,7 @@ lesen kann, und dass Difftastic die tatsächlichen Zeilennummern der Dateien vor und nach der Änderung zeigt
    Ich habe es direkt eingebunden und git diff ausprobiert; es funktioniert sehr gut

    • Kann man diese Syntax etwa nicht lesen?
  • Dass GitHub so etwas nicht anbietet, wirkt wie eine große Lücke bei der Produktinnovation
    Selbst wenn sich nur die Einrückung ändert, gibt es keine hilfreiche Funktion, und meist wird es als riesiges Löschen und Hinzufügen angezeigt
    GitHubs diff-Viewer kann und sollte intelligenter sein

    • GitHub bekommt nicht einmal syntaxbewusste diffs richtig hin, geschweige denn semantikbasierte diffs
      Es kann tatsächlich nicht einmal unterscheiden, dass foo.cpp.in und foo.mk.in verschieden sind
      Jedes foo.t wird als Perl deklariert, ohne Möglichkeit, das zu korrigieren. Dazu gibt es sogar ein 10 Jahre altes Ticket
    • tree-sitter ist nicht auf Korrektheit, sondern auf Performance für den Einsatz in Editoren optimiert
      Tatsächlich empfehlen selbst die TS-Kernentwickler, sich nicht zu sehr um die Genauigkeit der Grammatik zu kümmern[1]
      Für GitHub oder Dienste in ähnlicher Position könnte diese Einschränkung fatal sein
      [1] https://github.com/tree-sitter/tree-sitter/issues/130#issuec...
    • GitHub hat eine Option, Whitespace in diffs zu ignorieren
  • Ich wollte auf dieses Tool umsteigen, aber es verwendete auch für unveränderte Teile eine merkwürdige Formatierung, sodass es sich viel zu unruhig anfühlte
    Am Ende bin ich wieder zu icdiff[0] zurückgekehrt
    [0]: https://github.com/jeffkaufman/icdiff

  • Ich hatte früher Semantic Merge[1] entdeckt, aber es war nicht Open Source
    Dieses Tool macht zwar kein Merging, sondern nur diffs, ist aber immerhin Open Source, und die diffs sind viel besser lesbar, daher habe ich es bereits als Standard gesetzt
    Ich frage mich, ob es Pläne gibt, es um Merge-Funktionalität zu erweitern
    [1] https://docs.plasticscm.com/semanticmerge

    • Laut README auf GitHub macht difftastic kein Merging
      AST-Merging ist ein schwieriges Problem, und difftastic behandelt es nicht
      Außerdem ist ein AST-diff aus Sicht eines Text-diffs ein verlustbehafteter Prozess. Difftastic ignoriert syntaktisch bedeutungslosen Whitespace, aber für Merging muss Whitespace nachverfolgt werden
    • Ich wollte Semantic Merge ebenfalls empfehlen
      Als ich in einem großen Team chaotische Refactorings an einem C#-Projekt gemacht habe, war es wirklich ein Retter in der Not
  • Frühere Diskussionen:
    https://news.ycombinator.com/item?id=27768861 (297 points | vor 3 Jahren | 61 Kommentare)
    https://news.ycombinator.com/item?id=32746258 (698 points | vor 2 Jahren | 90 Kommentare)
    https://news.ycombinator.com/item?id=30841244 (983 points | vor 2 Jahren | 219 Kommentare)

  • Es wirkt wie ein hervorragendes Tool, aber als ich es mit ein paar großen XML-Dateien getestet habe, wurden geänderte Zeilen in normalem Grün und geänderte Attribute in fettem Grün angezeigt, was visuell schwer zu unterscheiden war
    In der Dokumentation habe ich keine Möglichkeit gefunden, den diff-Stil zu ändern oder für fette Hervorhebungen eine andere Farbe festzulegen
    Ich frage mich, ob jemand weiß, wie das geht

    • Leider scheint Farbanpassung noch nicht unterstützt zu werden
      Ich habe in diesem Issue[1] kommentiert, dass ich diese Funktion unterstütze
      Wenn das, was du möchtest, in dieselbe Kategorie fällt, kannst du dort deine Meinung ergänzen; wenn du es für ausreichend anders hältst, könntest du auch ein neues Issue eröffnen
      [1]: https://github.com/Wilfred/difftastic/issues/611
  • Interessant ist, dass das Arch-Linux-Paket 7 MB groß ist, nach der Installation aber auf 80 MB entpackt wird, und allein das difft-Binary 78 MB ausmacht.
    Auf einem ZFS-Dataset mit aktivierter LZ4-Kompression soll du 17 MB anzeigen.
    Ich frage mich, warum man etwas, das sich im Binary so gut komprimieren lässt, nicht einfach komprimiert vorhält. Wenn es im RAM entpackt wird, könnte es scheinbar sogar schneller laden.

    • Weil gerade von Kompression die Rede war, habe ich neugierig nachgesehen: Mit btrfs zstd:1 sind es etwa 10 MB.
      Processed 1 file, 614 regular extents (614 refs), 0 inline.
      Type Perc Disk Usage Uncompressed Referenced
      TOTAL 14% 10M 77M 77M
      none 100% 1.1M 1.1M 1.1M
      zstd 12% 9.8M 76M 76M
    • Das dürfte mit dem Kommentar zusammenhängen, dass der Nim-Parser eine 60 MB große C-Datei sei, die sich nicht mergen lässt.
      Tree-sitter scheint enorm viel Code zu benötigen, und ein beträchtlicher Teil davon wirkt redundant oder komprimierbar.
    • Eine ausführbare Datei wird nicht erst komplett in den RAM geladen und dann gestartet.
      Sie wird speicherabgebildet und nur die tatsächlich verwendeten Teile werden bei Bedarf geladen.
      Wenn man ein großes Binary komprimiert, funktioniert Demand Paging deshalb nicht, was es eher langsamer macht.
      Ich habe das vor ein paar Jahren mit einer 280-MB-Windows-EXE getestet: Sie ließ sich zwar auf etwa 70 MB komprimieren, brauchte beim Start aber einige Sekunden länger als das Original.
      In manchen Szenarien, etwa wenn man ein Binary über das Netzwerk ausführt, kann das sinnvoll sein, aber meistens starten unkomprimierte Binaries schneller.