Documentazione
Storia del progetto
Istantanee di progetti locali compatibili con Git, differenze strutturali, rami rivedibili e mirroring GitHub opzionale.
Tradotto automaticamente dall'inglese. Vedi l'originale in inglese
Copia funzionante e storico
I record SQLite dell'applicazione rappresentano la copia di lavoro del progetto corrente. @cadcamfun/vcs memorizza BLOB, alberi, commit e riferimenti compatibili con Git tramite un backend di oggetti iniettati; il sidecar persiste quel backend in SQLite. Le operazioni sulla cronologia funzionano localmente senza un eseguibile Git o un account GitHub.
Un commit crea un'istantanea del progetto e delle relative risorse di progettazione/produzione. I rami puntano ai commit, mentre HEAD identifica il ramo estratto. Le tabelle delle versioni delle risorse coesistono con questa cronologia a livello di progetto; una versione del disegno e un commit del progetto hanno ambiti diversi.
Commit e ramo del flusso di lavoro
- Leggere
GET /api/projects/:id/vcs/statusper vedere il ramo corrente, HEAD e i percorsi modificati. - Crea un ramo con
POST /vcs/branches, quindi controlla conPOST /vcs/checkout. I percorsi dei rami qui sono relativi al prefisso API del progetto. - Modifica le risorse del progetto e impegnati
POST /vcs/commite un messaggio non vuoto. - Ispezionare
GET /vcs/logeGET /vcs/diff. Il registro supporta una revisione, un limite e un percorso tracciato opzionale. - Apri una richiesta pull di progetto, esamina le sue modifiche strutturali e unisci dopo aver risolto revisioni o conflitti che bloccano.
Il checkout, il ripristino e le operazioni che spostano il ramo di lavoro rifiutano una copia di lavoro sporca dove richiesto. Un ripristino registra un nuovo commit che rappresenta il contenuto precedente, facoltativamente limitato ai percorsi selezionati. Preserva la storia successiva invece di riscriverla.
Contenuti dell'istantanea
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 Un file JSON per entità mantiene separate le modifiche non correlate. Lo snapshot include elementi della libreria a cui fanno riferimento i percorsi utensile e le stime dei costi di produzione in modo che i dati di produzione ripristinati possano mantenere le proprie dipendenze. Gli identificatori di entità utilizzati come nomi di file sono limitati a lettere sicure, cifre, trattini bassi e trattini, con un massimo di 128 caratteri.
Le chiavi JSON vengono ordinate in modo ricorsivo e i file utilizzano una stampa stabile e carina con un ritorno a capo finale. Gli array mantengono il loro ordine. I file di testo aggiuntivi possono viaggiare con uno snapshot a livello di pacchetto; l'endpoint di commit pubblico accetta file aggiuntivi solo in .cadcamfun/workflows/ con un nome file YAML sicuro.
Gli oggetti utilizzano il formato oggetto sciolto di git byte per byte, quindi gli ID oggetto corrispondono a quelli calcolati da git. Le voci dell'albero sono file (mode 100644) o directory (modalità 40000). Una firma di commit registra il nome, l'e-mail, l'ora Unix-secondi e un file +hhmm/-hhmm offset. I rami sono archiviati sotto refs/heads/e il ramo predefinito di un progetto è main (packages/vcs/src/object.ts, repository.ts).
Semantica di differenze e fusioni
Il repository calcola una base di unione, gestisce direttamente i lati invariati ed esegue unioni strutturali a tre vie per JSON modificato. Le modifiche indipendenti alle proprietà dell'oggetto possono essere unite senza conflitti. Le modifiche simultanee incompatibili allo stesso scalare o array vengono segnalate tramite puntatori JSON; gli array non vengono uniti elemento per elemento.
Le differenze di entità includono percorso, tipo di risorsa, identificatore e nome quando disponibili, stato aggiunto/modificato/eliminato e modifiche alle proprietà con valori prima/dopo. I file non JSON utilizzano un comportamento di conflitto a livello di file. Un'unione di repository con conflitti restituisce i percorsi e i puntatori senza scrivere un commit unito.
Estrai richieste e autorizzazioni
Le richieste pull del progetto sono disponibili su /api/projects/:id/pulls. Aprendone uno si registrano il titolo, il corpo, la testa, la base e il tipo dell'autore. Le risposte dettagliate includono la richiesta pull, le revisioni e le modifiche. Supporto per le recensioni comment, approve, e request-changes, con percorso facoltativo e puntatore alla proprietà.
L'ultimo verdetto di mancato commento per revisore controlla un blocco di modifiche alle richieste. Una fusione viene rifiutata mentre qualsiasi verdetto richiede modifiche. L'implementazione non necessita di un'approvazione positiva quando non c'è un verdetto di blocco. Le differenze delle richieste pull unite utilizzano il commit di unione registrato in modo che lo spostamento successivo del ramo non modifichi il record di revisione.
La lettura della cronologia, il commit, la ramificazione, l'apertura delle richieste pull e la revisione richiedono l'accesso al progetto. Gli spostamenti della copia di lavoro e la manutenzione del ramo condiviso richiedono il proprietario o un amministratore/gestore dell'organizzazione come imposto dal gestore. Le operazioni sporche o in conflitto restituiscono 409; le azioni protette restituiscono 403; riferimenti o risorse mancanti restituiscono 404; altre operazioni non valide restituiscono 400.
Un attore AI non può impegnarsi, ripristinare o fondersi in main. L'assistente utilizza un ramo della proposta e apre una richiesta pull affinché una persona possa esaminarla. I record di controllo e notifica identificano le modifiche originate dall'assistente.
Mirror GitHub opzionale
GET e PUT /api/projects/:id/vcs/remote leggere o impostare un owner/repo Specchio GitHub; null lo stacca. Spingi e tira l'uso POST /vcs/push e /vcs/pull, con il token GitHub dell'utente inserito x-github-token per quella chiamata.
Il push trasferisce oggetti raggiungibili e aggiorna il ramo remoto senza sovrascrivere i commit remoti non estratti. Pull recupera il ramo remoto e lo unisce localmente, rifiutando una copia funzionante sporca. Le modifiche JSON divergenti e incompatibili restituiscono conflitti anziché sostituire silenziosamente il contenuto locale.
Il trasporto sidecar utilizza operazioni di oggetto/rif. GitHub REST. Il token GitHub viene fornito per richiesta e non viene archiviato come dati di progetto. Una richiesta pull locale è un record di revisione dell'applicazione; non è automaticamente una richiesta pull GitHub.
Istantanee e restauro
snapshotToFiles converte un'istantanea del progetto in contenuti di file canonici; filesToSnapshot ricostruisce le liste dei domini. ProjectVcsRepo legge le risorse del database correnti e applica gli snapshot ripristinati. Il ripristino dell'intero progetto può modificare più tabelle correlate, quindi rivedi le differenze e cancella la copia di lavoro prima di cambiare la cronologia.
API di trasporto e oggetti
Il pacchetto VCS esporta la codifica e la lettura degli oggetti, Repository, adattatori di archivio, JSON canonico e helper di unione, conversione di snapshot, differenze di entità, sincronizzazione GitHub e interfacce di trasporto. I test possono inserire un backend di oggetti in memoria o un trasporto falso invece di accedere al repository di un utente.
Punti di ingresso dell'implementazione
packages/vcs/src/repository.ts possiede commit, ref, attraversamento, ripristino e unione; json.ts possiede la fusione strutturale; snapshot.ts definisce le risorse tracciate; diff.ts descrive i cambiamenti di entità; github.ts e transport.ts implementare il mirroring. Sidecar project-vcs.ts aggiunge la politica e la persistenza dell'attore, mentre http/vcs.ts fornisce autorizzazioni trasporti ed eventi.
Operazioni storiche e automazione
Le definizioni del flusso di lavoro vengono sottoposte a versione con il progetto e vengono eseguite sui file sottoposti a commit. Un commit può attivare tali flussi di lavoro dopo che la cronologia è stata registrata; un flusso di lavoro non riuscito non annulla il commit. Mantieni distinti i salvataggi delle risorse locali, i commit del progetto, i risultati del flusso di lavoro e la sincronizzazione remota durante la diagnosi dello stato di un progetto.
Prova il repository senza dati dell'applicazione
La cucitura dell'archivio oggetti consente a un esercizio di integrazione di eseguire commit e ramificarsi interamente in memoria. Eseguilo all'interno di un'area di lavoro che dipende da @cadcamfun/vcs, ad esempio apps/sidecar. Non tocca il database dei progetti del desktop.
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 riceve l'albero completo dei file per quel commit, non una patch. Il sidecar costruisce questo albero dai record del database di lavoro e dai file di testo trasportati. Un'integrazione a livello di pacchetto deve preservare i file che intende conservare. L'esempio in memoria non applica la proprietà del sidecar o la policy degli attori IA; questi vengono applicati dal servizio del progetto e dal livello HTTP.
Fonte: packages/vcs/src/store.ts, repository.ts, e apps/sidecar/src/services/project-vcs.ts.
Rivedi, ripristina e sincronizza deliberatamente
Registra le modifiche alla copia di lavoro prima di cambiare ramo. Un nuovo ramo richiede un commit di origine risolvibile, quindi inizializza un progetto senza commit con il suo primo commit prima del ramo. La creazione di un ramo crea solo un riferimento; il checkout è un'operazione separata che applica la sua istantanea al database di lavoro.
Per una richiesta pull, confronta la testa con la sua base di unione ed esamina le differenze a livello di proprietà. Un commento successivo non cancella un precedente verdetto di richiesta di modifica: il revisore deve inviare un successivo verdetto di approvazione. Un PR unito può avanzare rapidamente o creare un commit di unione in base all'ascendenza del repository; ispezionare il commit di unione registrato invece di assumere un numero fisso di genitori.
Quando si unisce alla base estratta, il servizio richiede una copia funzionante pulita e applica la nuova istantanea di base. Quando viene estratto un altro ramo, l'unione della base del PR aggiorna il riferimento di quel ramo senza sostituire il database di lavoro corrente. Un successivo checkout carica la base aggiornata.
Push e pull sincronizzano la cronologia dei commit. Il contenuto dell'interfaccia utente non salvato e lo stato del database non salvato non sono snapshot remoti. Un rifiuto remote-ahead è una richiesta di recuperare/unire prima; non è un'istruzione per sovrascrivere il ramo remoto.
Fonte: packages/vcs/src/repository.ts, github.ts, e apps/sidecar/src/services/project-vcs.ts.
Recuperare dagli errori della cronologia
| Fallimento | Azione successiva |
|---|---|
NothingToCommit | Confronta i file di lavoro del database con HEAD. Il salvataggio successivo degli stessi valori delle risorse non crea un albero diverso. |
| Ramo non valido o esistente | Scegli un nuovo nome di riferimento sicuro. I nomi non possono contenere attraversamenti, doppie barre, frammenti di espressioni di riferimento o un suffisso barra/.lock finale. |
| Modifiche non vincolate | Impegnare o ripristinare/eliminare deliberatamente le risorse previste prima dell'estrazione, del pull o di un'operazione di copia di lavoro protetta. |
| Unisci conflitto con percorso/puntatori | Esamina tali proprietà nelle cronologie base/testa, crea una risoluzione rivista e riprova. L'unione del repository non riuscita non ha creato un commit dell'unione parziale. |
| Un revisore ha richiesto modifiche | Risolvi il feedback e ottieni un verdetto di non commento aggiornato da quel revisore. |
| Gli attori dell'IA non possono agire su main | Utilizza un ramo della proposta AI e una richiesta pull sottoposta a revisione umana. La modifica di un flag di conferma non rimuove la restrizione dell'attore. |
Fonte: packages/vcs/src/repository.ts, apps/sidecar/src/services/project-vcs.ts, e http/vcs.ts. IL Riferimento API elenca gli schemi di richiesta e risposta effettivi.
Disposizione del deposito
Un progetto viene archiviato nel repository come un file JSON per entità. La modifica di un disegno quindi non tocca mai un file del percorso utensile e il layout si legge naturalmente su GitHub. snapshotToFiles scrive questo layout da a ProjectSnapshot, e filesToSnapshot lo rilegge.
| Elenco delle istantanee | Direttorio | Contenuto |
|---|---|---|
project | project.json | Metadati del progetto. |
drawings, components, toolpaths | stessi nomi | Enti di progettazione e produzione. |
materials, tools, machineConfigs | materials/, tools/, machine-configs/ | Elementi della libreria a cui fanno riferimento i percorsi utensile, con versione con il progetto in modo che un percorso utensile ripristinato li trovi ancora. |
costs | costs/ | Stime dei costi di produzione dei percorsi utensile. |
extra | qualsiasi percorso | File di testo memorizzati alla lettera: flussi di lavoro, README, codice G generato. |
Commit, branch, diff e unisci nel codice
Il Repository la classe funziona su qualsiasi backend di archiviazione. Il sidecar lo supporta con SQLite (tables GitObject e GitRef); memoryBackend() viene utilizzato per test ed esperimenti. L'esempio seguente è completo e il suo output è mostrato nei commenti.
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) Le unioni sono a tre vie a livello delle proprietà JSON. Quando entrambe le parti modificano proprietà diverse di un'entità, l'unione ha esito positivo. Quando entrambi hanno modificato la stessa proprietà su valori diversi, il risultato è a conflict che elenca i puntatori RFC 6901 all'interno di ciascun file. Gli array di uguale lunghezza vengono confrontati per indice, quindi un vertice spostato viene visualizzato come una modifica; gli array di diversa lunghezza vengono confrontati nel loro insieme.
| Unisci risultato | Senso |
|---|---|
up-to-date | Niente da unire; restituisce il commit corrente. |
fast-forward | Il puntatore del ramo si spostò in avanti; non è stato creato alcun commit di unione. |
merge | È stato creato un commit di unione. |
conflict | Nessun impegno; conflicts elenca percorsi e puntatori da risolvere. |
| Metodo del deposito | Scopo |
|---|---|
commit(files, { message, author, allowEmpty? }) | Scrivi un albero completo come nuovo commit sul ramo corrente. |
log(rev = "HEAD", limit?) / fileLog(path, rev?, limit?) | Storia del ramo/di un file. |
createBranch(name, from = "HEAD"), deleteBranch, checkout | Gestione filiale. I nomi non devono contenere .. o //, termina con / o .locko contenere @{. |
tag(name, rev = "HEAD"), tags() | Tag leggeri sotto refs/tags/. |
diff(from, to) | Modifiche a livello di file: aggiunte, modificate, eliminate, con ID BLOB. |
restorePaths(rev, paths, author, message?) | Nuovo commit che ripristina i file selezionati da una revisione precedente. |
revertTo(rev, author, message?) | Nuovo commit il cui albero equivale a una revisione precedente; la storia non viene riscritta. |
merge(theirs, author) / mergeInto(into, theirs, author) | Unione a tre vie nel ramo corrente/in un ramo con nome. |
isAncestor, mergeBase | Interrogazioni grafiche. |
Regole applicate dall'applicazione
Il servizio di cronologia del progetto del sidecar aggiunge regole al repository:
| Codice di errore | Quando |
|---|---|
blocked | Un attore AI tenta di impegnarsi o fondersi main (il messaggio dice di usare un ai/* branch e aprire una richiesta pull), oppure una richiesta pull viene unita mentre lo è l'ultimo verdetto di un revisore request-changes. |
dirty | La copia di lavoro contiene modifiche non salvate e l'operazione le sovrascriverebbe: "Modifiche non salvate in N file; confermale o scartale prima". |
conflict | Una fusione ha prodotto conflitti; il fallimento porta l'elenco dei conflitti. |
nothing-to-commit | L'albero è rimasto invariato dall'ultimo commit. |
not-found | Ramo, commit, oggetto o richiesta pull sconosciuta. |
remote | Sincronizzazione GitHub non riuscita. |
invalid | Altro input non valido, ad esempio la revisione o l'unione di una richiesta pull non aperta. |
Conta solo l'ultimo verdetto di mancato commento di ciascun revisore. Un revisore che ha richiesto modifiche e successivamente le ha approvate non blocca più l'unione. I commit, gli eventi di richiesta pull e le revisioni vengono pubblicati come eventi live, quindi il desktop e il complementare si aggiornano senza polling.
Compatibile con byte con git
Gli ID oggetto vengono calcolati esattamente come li calcola git. L'invio a GitHub tramite l'API Git Data mantiene ogni ID commit identico su entrambi i lati e a git clone dello specchio mostra la stessa cronologia dell'applicazione. GitHub accetta solo aggiornamenti di riferimento con avanzamento rapido; estrarre e unire prima localmente quando il telecomando si è spostato.
Un vero oggetto di commit
{
"oid": "44d2d03d23b27fbbc4b388e32e15dfb0f39fff87",
"parents": [],
"message": "First version\n",
"author": { "name": "Local user", "email": "local@cadcamfun.local", "time": 1792037121 }
} oidè un ID esadecimale di 40 caratteri dell'oggetto commit memorizzato inGitObject.- Il primo commit non ha genitori; un commit di unione ne ha due.
- Il messaggio viene archiviato con un carattere di fine riga, come fa Git.
- Senza un
x-session-token, l'autore è il profilo locale dell'installazione.timeè in Unix secondi.
| Itinerario | Corpo | Necessario |
|---|---|---|
POST …/vcs/commit | message, authorKind, files | message |
POST …/vcs/branches | name, from | name |
POST …/vcs/checkout | branch | branch |
POST …/vcs/restore | rev, paths | rev |
POST …/pulls | title, body, head, base, authorKind | title, head |