Zum Inhalt springen
CADCAMFUN
Doku

Dokumentation

Projektgeschichte

Lokale Git-kompatible Projekt-Snapshots, strukturelle Unterschiede, überprüfbare Zweige und optionale GitHub-Spiegelung.

Automatisch aus dem Englischen übersetzt. Englisches Original ansehen

Arbeitsexemplar und Geschichte

Die SQLite-Datensätze der Anwendung sind die aktuelle Arbeitskopie des Projekts. @cadcamfun/vcs speichert Git-kompatible Blobs, Bäume, Commits und Refs über ein injiziertes Objekt-Backend; Der Sidecar behält dieses Backend in SQLite bei. Verlaufsvorgänge funktionieren lokal ohne eine ausführbare Git-Datei oder ein GitHub-Konto.

Ein Commit erstellt einen Snapshot des Projekts und der zugehörigen Design-/Fertigungsressourcen. Zweige verweisen auf Commits, während HEAD den ausgecheckten Zweig identifiziert. Ressourcenversionstabellen existieren neben diesem Verlauf auf Projektebene. Eine Zeichnungsversion und ein Projekt-Commit haben unterschiedliche Geltungsbereiche.

Commit- und Branch-Workflow

  1. Lesen GET /api/projects/:id/vcs/status um den aktuellen Zweig, HEAD und geänderte Pfade anzuzeigen.
  2. Erstellen Sie einen Zweig mit POST /vcs/branches, dann schau es dir an mit POST /vcs/checkout. Verzweigungspfade beziehen sich hier auf das API-Präfix des Projekts.
  3. Bearbeiten Sie Projektressourcen und verpflichten Sie sich dazu POST /vcs/commit und eine nicht leere Nachricht.
  4. Überprüfen GET /vcs/log und GET /vcs/diff. Das Protokoll unterstützt eine Revision, ein Limit und einen optionalen nachverfolgten Pfad.
  5. Öffnen Sie eine Projekt-Pull-Anfrage, überprüfen Sie die strukturellen Änderungen und führen Sie sie zusammen, nachdem Sie blockierende Überprüfungen oder Konflikte gelöst haben.

Auschecken, Wiederherstellen und Vorgänge, die den Arbeitszweig verschieben, verweigern bei Bedarf eine fehlerhafte Arbeitskopie. Bei einer Wiederherstellung wird ein neues Commit aufgezeichnet, das den früheren Inhalt darstellt und optional auf ausgewählte Pfade beschränkt ist. Es bewahrt die spätere Geschichte, anstatt sie neu zu schreiben.

Snapshot-Inhalte

Layout des Projekt-Repositorys
project.json
drawings/<id>.json
components/<id>.json
toolpaths/<id>.json
materials/<id>.json
tools/<id>.json
machine-configs/<id>.json
costs/<id>.json
.cadcamfun/workflows/<name>.yml

Eine JSON-Datei pro Entität hält nicht verwandte Änderungen getrennt. Der Snapshot umfasst Bibliothekselemente, auf die durch Werkzeugwege und Produktionskostenschätzungen verwiesen wird, sodass wiederhergestellte Fertigungsdaten ihre Abhängigkeiten beibehalten können. Als Dateinamen verwendete Entitätsbezeichner sind auf sichere Buchstaben, Ziffern, Unterstriche und Bindestriche mit maximal 128 Zeichen beschränkt.

JSON-Schlüssel werden rekursiv sortiert und Dateien verwenden stabiles Pretty-Printing mit einer abschließenden neuen Zeile. Arrays behalten ihre Reihenfolge. Zusätzliche Textdateien können mit einem Snapshot auf Paketebene übertragen werden. Der öffentliche Commit-Endpunkt akzeptiert zusätzliche Dateien nur unter .cadcamfun/workflows/ mit einem sicheren YAML-Dateinamen.

Objekte verwenden Byte für Byte das lose Objektformat von Git, sodass Objekt-IDs mit denen übereinstimmen, die von berechnet wurden git. Baumeinträge sind entweder Dateien (Modus 100644) oder Verzeichnisse (Modus 40000). Eine Commit-Signatur zeichnet Name, E-Mail, Unix-Sekunden-Zeit und a auf +hhmm/-hhmm versetzt. Zweige werden darunter gespeichert refs/heads/, und der Standardzweig eines Projekts ist main (packages/vcs/src/object.ts, repository.ts).

Diff- und Merge-Semantik

Das Repository berechnet eine Zusammenführungsbasis, verarbeitet unveränderte Seiten direkt und führt strukturelle Drei-Wege-Zusammenführungen für geändertes JSON durch. Unabhängige Bearbeitungen von Objekteigenschaften können ohne Konflikte zusammengeführt werden. Gleichzeitige inkompatible Bearbeitungen desselben Skalars oder Arrays werden über JSON-Zeiger gemeldet. Arrays werden nicht Element für Element zusammengeführt.

Zu den Entitätsunterschieden gehören Pfad, Ressourcenart, Bezeichner und Name, sofern verfügbar, Status „Hinzugefügt/geändert/gelöscht“ und Eigenschaftsänderungen mit Vorher/Nachher-Werten. Nicht-JSON-Dateien verwenden Konfliktverhalten auf Dateiebene. Eine Repository-Zusammenführung mit Konflikten gibt deren Pfade und Zeiger zurück, ohne einen zusammengeführten Commit zu schreiben.

Pull-Anfragen und Berechtigungen

Projekt-Pull-Anfragen live unter /api/projects/:id/pulls. Beim Öffnen werden Titel, Hauptteil, Kopf, Basis und Autorentyp aufgezeichnet. Zu den Detailantworten gehören die Pull-Anfrage, Überprüfungen und Änderungen. Unterstützung für Bewertungen comment, approve, und request-changes, mit optionalem Pfad- und Eigenschaftszeiger.

Das letzte Nicht-Kommentar-Urteil pro Prüfer steuert einen Anforderungs-Änderungsblock. Eine Zusammenführung wird abgelehnt, solange in einem solchen Urteil Änderungen gefordert werden. Die Umsetzung bedarf keiner positiven Zustimmung, wenn kein Sperrurteil vorliegt. Zusammengeführte Pull-Request-Diffs verwenden den aufgezeichneten Merge-Commit, sodass eine spätere Verzweigungsbewegung diesen Überprüfungsdatensatz nicht ändert.

Für das Lesen des Verlaufs, das Festschreiben, das Verzweigen, das Öffnen von Pull-Requests und das Überprüfen ist Projektzugriff erforderlich. Für das Verschieben von Arbeitskopien und die Wartung gemeinsam genutzter Zweige ist der Eigentümer oder ein Organisationsadministrator/-manager erforderlich, wie vom Handler erzwungen. Fehlerhafte oder widersprüchliche Vorgänge geben 409 zurück; geschützte Aktionen geben 403 zurück; fehlende Referenzen oder Ressourcen geben 404 zurück; andere ungültige Vorgänge geben 400 zurück.

Ein KI-Akteur kann kein Commit, keine Wiederherstellung oder keine Zusammenführung durchführen main. Der Assistent verwendet einen Vorschlagszweig und öffnet eine Pull-Anfrage zur Überprüfung durch eine Person. Audit- und Benachrichtigungsaufzeichnungen identifizieren vom Assistenten vorgenommene Änderungen.

Optionaler GitHub-Spiegel

GET und PUT /api/projects/:id/vcs/remote lesen oder einstellen owner/repo GitHub-Spiegel; null trennt es. Push-and-Pull-Nutzung POST /vcs/push und /vcs/pull, mit dem GitHub-Token des Benutzers x-github-token für diesen Anruf.

Push überträgt erreichbare Objekte und aktualisiert den Remote-Zweig, ohne nicht abgerufene Remote-Commits zu überschreiben. Pull ruft den Remote-Zweig ab und führt ihn lokal zusammen, wodurch eine fehlerhafte Arbeitskopie abgelehnt wird. Abweichende inkompatible JSON-Bearbeitungen führen zu Konflikten, anstatt lokale Inhalte stillschweigend zu ersetzen.

Der Sidecar-Transport verwendet GitHub REST-Objekt-/Ref-Operationen. Das GitHub-Token wird pro Anfrage bereitgestellt und nicht als Projektdaten gespeichert. Eine lokale Pull-Anfrage ist ein Anwendungsüberprüfungsdatensatz. Es handelt sich nicht automatisch um eine GitHub-Pull-Anfrage.

Schnappschüsse und Wiederherstellung

snapshotToFiles konvertiert einen Projekt-Snapshot in kanonische Dateiinhalte; filesToSnapshot rekonstruiert die Domänenlisten. ProjectVcsRepo liest die aktuellen Datenbankressourcen und wendet wiederhergestellte Snapshots an. Bei der Wiederherstellung des gesamten Projekts können mehrere zusammengehörige Tabellen geändert werden. Überprüfen Sie daher den Unterschied und löschen Sie die Arbeitskopie, bevor Sie den Verlauf wechseln.

Transport- und Objekt-APIs

Das VCS-Paket exportiert Objektkodierung und -lesen, Repository, store-Adapter, kanonische JSON- und Merge-Helfer, Snapshot-Konvertierung, Entitätsunterschiede, GitHub-Synchronisierung und Transportschnittstellen. Tests können ein In-Memory-Objekt-Backend oder einen gefälschten Transport injizieren, anstatt auf das Repository eines Benutzers zuzugreifen.

Einstiegspunkte für die Implementierung

packages/vcs/src/repository.ts besitzt Commits, Refs, Traversal, Restaurierung und Merge; json.ts besitzt strukturelle Zusammenführung; snapshot.ts definiert verfolgte Ressourcen; diff.ts beschreibt Entitätsänderungen; github.ts und transport.ts spiegelung implementieren. Sidecar project-vcs.ts fügt Akteurpolitik und Persistenz hinzu, während http/vcs.ts liefert Transportgenehmigungen und Veranstaltungen.

Geschichtlicher Betrieb und Automatisierung

Workflowdefinitionen werden mit dem Projekt versioniert und für festgeschriebene Dateien ausgeführt. Ein Commit kann diese Workflows auslösen, nachdem der Verlauf aufgezeichnet wurde. Ein fehlgeschlagener Workflow macht den Commit nicht rückgängig. Halten Sie beim Diagnostizieren des Status eines Projekts die Speicherung lokaler Ressourcen, Projekt-Commits, Workflow-Ergebnisse und Remote-Synchronisierung unterschiedlich.

Probieren Sie das Repository ohne Anwendungsdaten aus

Mit der Object-Store-Seam kann eine Integrationsübung Commits und Branches vollständig im Speicher durchführen. Führen Sie dies in einem Arbeitsbereich aus, der davon abhängt @cadcamfun/vcs, wie zum Beispiel apps/sidecar. Die Projektdatenbank des Desktops wird dadurch nicht berührt.

Zwei Commits und ein Vorschlagszweig
import { Repository, memoryBackend, canonicalJson } from "@cadcamfun/vcs"

const repo = new Repository(memoryBackend())
const author = { name: "Workshop", email: "workshop@example.invalid", time: 0, tz: "+0000" }
repo.commit({ "project.json": canonicalJson({ name: "Blank", revision: 1 }) }, {
  message: "Initial blank", author
})
repo.createBranch("proposal/wider-blank")
repo.checkout("proposal/wider-blank")
repo.commit({ "project.json": canonicalJson({ name: "Blank", revision: 2 }) }, {
  message: "Record revised blank", author
})
console.log(repo.currentBranch()) // proposal/wider-blank
console.log(repo.log().length) // 2

Repository.commit erhält den vollständigen Dateibaum für diesen Commit, keinen Patch. Der Sidecar erstellt diesen Baum aus Arbeitsdatenbankdatensätzen und übertragenen Textdateien. Bei einer Integration auf Paketebene müssen die Dateien erhalten bleiben, die beibehalten werden sollen. Das In-Memory-Beispiel wendet keine Sidecar-Besitz- oder KI-Akteursrichtlinie an; Diese werden vom Projektdienst und der HTTP-Ebene erzwungen.

Quelle: packages/vcs/src/store.ts, repository.ts, und apps/sidecar/src/services/project-vcs.ts.

Überprüfen, wiederherstellen und synchronisieren Sie gezielt

Notieren Sie Änderungen an der Arbeitskopie, bevor Sie den Zweig wechseln. Für einen neuen Zweig ist ein auflösbarer Quell-Commit erforderlich. Initialisieren Sie daher vor dem Verzweigen ein nicht festgeschriebenes Projekt mit dem ersten Commit. Durch das Erstellen eines Zweigs wird nur ein Verweis erstellt. Beim Auschecken handelt es sich um einen separaten Vorgang, der seinen Snapshot auf die Arbeitsdatenbank anwendet.

Vergleichen Sie bei einer Pull-Anfrage den Kopf mit seiner Zusammenführungsbasis und überprüfen Sie die Unterschiede auf Eigenschaftsebene. Durch einen späteren Kommentar wird ein vorheriges Urteil zu Änderungsanträgen nicht gelöscht: Der Prüfer muss ein nachfolgendes Genehmigungsurteil einreichen. Ein zusammengeführter PR kann entsprechend der Repository-Abstammung vorspulen oder einen Zusammenführungs-Commit erstellen. Überprüfen Sie den aufgezeichneten Merge-Commit, anstatt von einer festen Anzahl von Eltern auszugehen.

Beim Zusammenführen mit der ausgecheckten Basis benötigt der Dienst eine saubere Arbeitskopie und wendet den neuen Basis-Snapshot an. Wenn ein anderer Zweig ausgecheckt wird, wird durch das Zusammenführen der PR-Basis die Referenz dieses Zweigs aktualisiert, ohne die aktuelle Arbeitsdatenbank zu ersetzen. Bei einem anschließenden Checkout wird die aktualisierte Basis geladen.

Push-and-Pull-Synchronisierung des festgeschriebenen Verlaufs. Nicht gespeicherte UI-Inhalte und nicht festgeschriebene Datenbankstatus sind keine Remote-Snapshots. Eine Remote-Ahead-Ablehnung ist eine Anforderung, zuerst etwas abzurufen/zusammenzuführen; Es handelt sich nicht um eine Anweisung zum Überschreiben des Remote-Zweigs.

Quelle: packages/vcs/src/repository.ts, github.ts, und apps/sidecar/src/services/project-vcs.ts.

Wiederherstellung nach Verlaufsfehlern

VersagenNächste Aktion
NothingToCommitVergleichen Sie Datenbankarbeitsdateien mit HEAD. Durch das erneute Speichern derselben Ressourcenwerte wird kein anderer Baum erstellt.
Ungültiger oder vorhandener ZweigWählen Sie einen neuen sicheren Referenznamen. Namen dürfen keinen Traversal, doppelte Schrägstriche, Referenzausdrucksfragmente oder ein nachgestelltes Schrägstrich-/.lock-Suffix enthalten.
Nicht festgeschriebene ÄnderungenÜbernehmen Sie die beabsichtigten Ressourcen oder stellen Sie sie absichtlich wieder her/verwerfen Sie sie vor dem Auschecken, Abrufen oder einem geschützten Arbeitskopievorgang.
Zusammenführungskonflikt mit Pfad/ZeigernÜberprüfen Sie diese Eigenschaften in Basis-/Kopfverläufen, erstellen Sie eine überprüfte Lösung und versuchen Sie es erneut. Durch die fehlgeschlagene Repository-Zusammenführung wurde kein teilweiser Zusammenführungs-Commit erstellt.
Ein Rezensent hat Änderungen angefordertLösen Sie das Feedback und erhalten Sie von diesem Rezensenten ein aktualisiertes Urteil ohne Kommentar.
KI-Akteure können nicht auf Hauptebene agierenVerwenden Sie einen KI-Vorschlagszweig und eine von Menschen überprüfte Pull-Anfrage. Durch das Ändern eines Bestätigungsflags wird die Akteurbeschränkung nicht aufgehoben.

Quelle: packages/vcs/src/repository.ts, apps/sidecar/src/services/project-vcs.ts, und http/vcs.ts. Der API-Referenz listet die tatsächlichen Anforderungs- und Antwortschemata auf.

Repository-Layout

Ein Projekt wird im Repository als eine JSON-Datei pro Entität gespeichert. Beim Bearbeiten einer Zeichnung wird daher niemals eine Werkzeugwegdatei berührt, und das Layout liest sich natürlich auf GitHub. snapshotToFiles schreibt dieses Layout von a ProjectSnapshot, und filesToSnapshot liest es zurück.

/ project.json zeichnungen/<id>.jsoncomponents/<id>.json toolpaths/<id>.jsonmaterials/<id>.json tools/<id>.jsonmachine-configs/<id>.json cost/<id>.json .cadcamfun/workflows/<Name>.yml eine Datei pro Entität → Unterschiede und Zusammenführungen bleiben lokal bei den Änderungen
Snapshot-ListeVerzeichnisInhalt
projectproject.jsonProjektmetadaten.
drawings, components, toolpathsgleiche NamenDesign- und Fertigungsunternehmen.
materials, tools, machineConfigsmaterials/, tools/, machine-configs/Bibliothekselemente, auf die die Werkzeugwege verweisen, werden mit dem Projekt versioniert, sodass ein wiederhergestellter Werkzeugweg sie weiterhin findet.
costscosts/Produktionskostenschätzungen der Werkzeugwege.
extrajeder WegWörtlich gespeicherte Textdateien: Arbeitsabläufe, README, generierter G-Code.

Festschreiben, Verzweigen, Vergleichen und Zusammenführen im Code

Der Repository die Klasse funktioniert auf jedem Speicher-Backend. Der Sidecar unterstützt es mit SQLite (Tabellen GitObject und GitRef); memoryBackend() wird für Tests und Experimente verwendet. Das folgende Beispiel ist vollständig und seine Ausgabe wird in Kommentaren angezeigt.

history-demo.ts
import { Repository, memoryBackend, snapshotToFiles, describeDiff } from "@cadcamfun/vcs"

const repo = new Repository(memoryBackend())
const me = { name: "Ada", email: "ada@example.com", time: 1767225600, tz: "+0100" }
const files = (width: number, height: number) => snapshotToFiles({
  project: { id: "p1", name: "Bracket" },
  drawings: [{ id: "d1", name: "Plate", width, height }],
  components: [], toolpaths: [],
})

const base = repo.commit(files(40, 25), { message: "base", author: me })
repo.currentBranch()                    // "main"

repo.createBranch("a"); repo.createBranch("b")
repo.checkout("a"); const a = repo.commit(files(50, 25), { message: "a: width 50", author: me })
repo.checkout("b"); repo.commit(files(40, 30), { message: "b: height 30", author: me })

describeDiff(repo, base, a)
// [{ path: "drawings/d1.json", kind: "drawings", id: "d1", name: "Plate",
//    status: "modified", changes: [{ pointer: "/width", before: 40, after: 50 }] }]

repo.mergeInto("a", "b", me)            // { kind: "merge", oid: "46e7…" }
// different properties of the same entity merge cleanly

repo.checkout("b"); repo.commit(files(60, 30), { message: "b: width 60", author: me })
repo.mergeInto("a", "b", me)
// { kind: "conflict", conflicts: [{ path: "drawings/d1.json", pointers: ["/width"] }] }

repo.createBranch("bad..name")          // throws: Invalid branch name bad..name
repo.commit(sameFilesAsHead, { message: "x", author: me })
// throws: No changes since the last commit   (unless allowEmpty: true)

Zusammenführungen erfolgen auf der Ebene der JSON-Eigenschaften in drei Richtungen. Wenn beide Seiten unterschiedliche Eigenschaften einer Entität geändert haben, ist die Zusammenführung erfolgreich. Wenn beide dieselbe Eigenschaft in unterschiedliche Werte geändert haben, ist das Ergebnis a conflict das RFC-6901-Zeiger in jeder Datei auflistet. Arrays gleicher Länge werden pro Index verglichen, sodass ein verschobener Scheitelpunkt als eine Änderung angezeigt wird. Arrays unterschiedlicher Länge werden als Ganzes verglichen.

Ergebnis zusammenführenBedeutung
up-to-dateNichts zum Zusammenführen; gibt den aktuellen Commit zurück.
fast-forwardDer Verzweigungszeiger bewegte sich vorwärts; Es wurde kein Merge-Commit erstellt.
mergeEin Merge-Commit wurde erstellt.
conflictKein Commit; conflicts listet aufzulösende Pfade und Zeiger auf.
Repository-MethodeZweck
commit(files, { message, author, allowEmpty? })Schreiben Sie einen vollständigen Baum als neues Commit für den aktuellen Zweig.
log(rev = "HEAD", limit?) / fileLog(path, rev?, limit?)Verlauf des Zweigs / einer Datei.
createBranch(name, from = "HEAD"), deleteBranch, checkoutFilialleitung. Namen dürfen nicht enthalten .. oder //, ende in / oder .lock, oder enthalten @{.
tag(name, rev = "HEAD"), tags()Leichte Tags unten refs/tags/.
diff(from, to)Änderungen auf Dateiebene: hinzugefügt, geändert, gelöscht, mit Blob-IDs.
restorePaths(rev, paths, author, message?)Neues Commit, das ausgewählte Dateien aus einer älteren Revision wiederherstellt.
revertTo(rev, author, message?)Neues Commit, dessen Baum einer älteren Revision entspricht; Geschichte wird nicht neu geschrieben.
merge(theirs, author) / mergeInto(into, theirs, author)Drei-Wege-Zusammenführung in den aktuellen Zweig / einen benannten Zweig.
isAncestor, mergeBaseDiagrammabfragen.

Von der Anwendung durchgesetzte Regeln

Der Projektverlaufsdienst des Sidecars fügt Regeln zusätzlich zum Repository hinzu:

FehlercodeWann
blockedEin KI-Akteur versucht, sich zu verpflichten oder zusammenzuführen main (die Nachricht sagt, dass man ein verwenden soll ai/* verzweigen und eine Pull-Anfrage öffnen), oder eine Pull-Anfrage wird zusammengeführt, während das letzte Urteil eines Prüfers vorliegt request-changes.
dirtyDie Arbeitskopie enthält nicht festgeschriebene Änderungen und der Vorgang würde sie überschreiben: „Nicht festgeschriebene Änderungen in N Datei(en); zuerst festschreiben oder verwerfen“.
conflictEine Zusammenführung führte zu Konflikten; Der Fehler trägt die Konfliktliste.
nothing-to-commitDer Baum ist seit dem letzten Commit unverändert.
not-foundUnbekannter Branch, Commit, Objekt oder Pull-Request.
remoteDie GitHub-Synchronisierung ist fehlgeschlagen.
invalidAndere ungültige Eingaben, zum Beispiel das Überprüfen oder Zusammenführen einer Pull-Anfrage, die nicht geöffnet ist.

Es zählt nur das letzte Nicht-Kommentar-Urteil jedes Rezensenten. Ein Prüfer, der Änderungen angefordert und später genehmigt hat, blockiert die Zusammenführung nicht mehr. Commits, Pull-Request-Ereignisse und Reviews werden als Live-Ereignisse veröffentlicht, sodass der Desktop und der Begleiter ohne Abfrage aktualisiert werden.

Byte-kompatibel mit Git

Objekt-IDs werden genau so berechnet, wie Git sie berechnet. Durch das Pushen an GitHub über die Git-Daten-API bleibt jede Commit-ID auf beiden Seiten identisch und a git clone der Spiegel zeigt den gleichen Verlauf wie die Anwendung. GitHub akzeptiert nur Fast-Forward-Ref-Updates; Zuerst lokal ziehen und zusammenführen, wenn die Fernbedienung verschoben wurde.

Ein echtes Commit-Objekt

POST /api/projects/:id/vcs/commit
{
  "oid": "44d2d03d23b27fbbc4b388e32e15dfb0f39fff87",
  "parents": [],
  "message": "First version\n",
  "author": { "name": "Local user", "email": "local@cadcamfun.local", "time": 1792037121 }
}
  • oid ist eine Hex-ID aus 40-Zeichen des in gespeicherten Commit-Objekts GitObject.
  • Der erste Commit hat keine Eltern; Ein Merge-Commit hat zwei.
  • Die Nachricht wird mit einem abschließenden Zeilenumbruch gespeichert, wie es bei Git der Fall ist.
  • Ohne ein x-session-token, der Autor ist das lokale Profil der Installation. time ist in Unix-Sekunden.
RouteKörperErforderlich
POST …/vcs/commitmessage, authorKind, filesmessage
POST …/vcs/branchesname, fromname
POST …/vcs/checkoutbranchbranch
POST …/vcs/restorerev, pathsrev
POST …/pullstitle, body, head, base, authorKindtitle, head

In 3D

Ein Commit-Diagramm Zum Orbit ziehen · zum Zoomen scrollen · zum Schwenken mit der rechten Maustaste ziehen
The last 14 commits of origin/main from git log --format=%h|%p: each pull request branch commit (back lane) is merged into main (front lane, orange merge commits), then the docs commits. A project history in the app is the same kind of graph: commits with parents, refs pointing at them.