feat: upgrade panproto to v0.28.0, use incremental import and typed XRPC responses - All panproto deps bumped from v0.27.3 to v0.28.0 - receive_pack: switch to import_git_repo_incremental with persistent marks file (.git-import-marks) so repeated pushes only import new commits instead of walking the entire history - list_commits: return panproto_xrpc::ListCommitsResult (typed) instead of hand-built serde_json::Value - diff_commits: return panproto_xrpc::DiffCommitsResult (typed) with FileDiff structs; fix contextLines query param deserialization (camelCase to match frontend) - Handle new Value::List variant in search_structural

Author: Aaron Steven White
Commit b2707f62cc5c83500c49d51bcd9d6dad82bb0e69
Parent: f9664e89d9
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
9 files changed +295 -228
@@ -621,6 +621,7 @@ dependencies = [
621621  "panproto-protocols",
622622  "panproto-schema",
623623  "panproto-vcs",
624+ "panproto-xrpc",
624625  "reqwest",
625626  "rmp-serde",
626627  "serde",
@@ -2490,8 +2491,8 @@ dependencies = [
24902491 
24912492 [[package]]
24922493 name = "panproto-check"
2493-version = "0.27.3"
2494-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2494+version = "0.28.0"
2495+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
24952496 dependencies = [
24962497  "panproto-gat",
24972498  "panproto-lens",
@@ -2505,8 +2506,8 @@ dependencies = [
25052506 
25062507 [[package]]
25072508 name = "panproto-core"
2508-version = "0.27.3"
2509-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2509+version = "0.28.0"
2510+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25102511 dependencies = [
25112512  "panproto-check",
25122513  "panproto-gat",
@@ -2521,8 +2522,8 @@ dependencies = [
25212522 
25222523 [[package]]
25232524 name = "panproto-expr"
2524-version = "0.27.3"
2525-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2525+version = "0.28.0"
2526+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25262527 dependencies = [
25272528  "rustc-hash",
25282529  "serde",
@@ -2531,8 +2532,8 @@ dependencies = [
25312532 
25322533 [[package]]
25332534 name = "panproto-expr-parser"
2534-version = "0.27.3"
2535-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2535+version = "0.28.0"
2536+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25362537 dependencies = [
25372538  "chumsky",
25382539  "logos 0.16.1",
@@ -2541,8 +2542,8 @@ dependencies = [
25412542 
25422543 [[package]]
25432544 name = "panproto-gat"
2544-version = "0.27.3"
2545-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2545+version = "0.28.0"
2546+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25462547 dependencies = [
25472548  "panproto-expr",
25482549  "rustc-hash",
@@ -2552,8 +2553,8 @@ dependencies = [
25522553 
25532554 [[package]]
25542555 name = "panproto-git"
2555-version = "0.27.3"
2556-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2556+version = "0.28.0"
2557+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25572558 dependencies = [
25582559  "git2",
25592560  "miette",
@@ -2570,8 +2571,8 @@ dependencies = [
25702571 
25712572 [[package]]
25722573 name = "panproto-grammars"
2573-version = "0.27.3"
2574-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2574+version = "0.28.0"
2575+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25752576 dependencies = [
25762577  "cc",
25772578  "serde",
@@ -2582,8 +2583,8 @@ dependencies = [
25822583 
25832584 [[package]]
25842585 name = "panproto-inst"
2585-version = "0.27.3"
2586-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2586+version = "0.28.0"
2587+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
25872588 dependencies = [
25882589  "bumpalo",
25892590  "panproto-expr",
@@ -2598,8 +2599,8 @@ dependencies = [
25982599 
25992600 [[package]]
26002601 name = "panproto-io"
2601-version = "0.27.3"
2602-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2602+version = "0.28.0"
2603+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26032604 dependencies = [
26042605  "bumpalo",
26052606  "memchr",
@@ -2621,8 +2622,8 @@ dependencies = [
26212622 
26222623 [[package]]
26232624 name = "panproto-lens"
2624-version = "0.27.3"
2625-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2625+version = "0.28.0"
2626+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26262627 dependencies = [
26272628  "panproto-expr",
26282629  "panproto-gat",
@@ -2638,8 +2639,8 @@ dependencies = [
26382639 
26392640 [[package]]
26402641 name = "panproto-lens-dsl"
2641-version = "0.27.3"
2642-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2642+version = "0.28.0"
2643+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26432644 dependencies = [
26442645  "miette",
26452646  "nickel-lang",
@@ -2657,8 +2658,8 @@ dependencies = [
26572658 
26582659 [[package]]
26592660 name = "panproto-mig"
2660-version = "0.27.3"
2661-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2661+version = "0.28.0"
2662+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26622663 dependencies = [
26632664  "panproto-expr",
26642665  "panproto-gat",
@@ -2671,8 +2672,8 @@ dependencies = [
26712672 
26722673 [[package]]
26732674 name = "panproto-parse"
2674-version = "0.27.3"
2675-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2675+version = "0.28.0"
2676+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26762677 dependencies = [
26772678  "memchr",
26782679  "miette",
@@ -2690,8 +2691,8 @@ dependencies = [
26902691 
26912692 [[package]]
26922693 name = "panproto-project"
2693-version = "0.27.3"
2694-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2694+version = "0.28.0"
2695+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
26952696 dependencies = [
26962697  "blake3",
26972698  "globset",
@@ -2710,8 +2711,8 @@ dependencies = [
27102711 
27112712 [[package]]
27122713 name = "panproto-protocols"
2713-version = "0.27.3"
2714-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2714+version = "0.28.0"
2715+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
27152716 dependencies = [
27162717  "blake3",
27172718  "panproto-gat",
@@ -2725,8 +2726,8 @@ dependencies = [
27252726 
27262727 [[package]]
27272728 name = "panproto-schema"
2728-version = "0.27.3"
2729-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2729+version = "0.28.0"
2730+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
27302731 dependencies = [
27312732  "panproto-expr",
27322733  "panproto-gat",
@@ -2738,8 +2739,8 @@ dependencies = [
27382739 
27392740 [[package]]
27402741 name = "panproto-vcs"
2741-version = "0.27.3"
2742-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2742+version = "0.28.0"
2743+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
27432744 dependencies = [
27442745  "blake3",
27452746  "panproto-check",
@@ -2759,8 +2760,8 @@ dependencies = [
27592760 
27602761 [[package]]
27612762 name = "panproto-xrpc"
2762-version = "0.27.3"
2763-source = "git+https://github.com/panproto/panproto.git?tag=v0.27.3#8d55ceface4d59752903f996c1ae34935b50cbea"
2763+version = "0.28.0"
2764+source = "git+https://github.com/panproto/panproto.git?tag=v0.28.0#4db61dee8a553b6abd0905408b116d175f394a44"
27642765 dependencies = [
27652766  "hex",
27662767  "miette",
@@ -13,24 +13,24 @@ license = "AGPL-3.0-or-later"
1313 repository = "https://github.com/cospan-dev/cospan"
1414 
1515 [workspace.dependencies]
16-# panproto v0.27.3
17-panproto-core = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
18-panproto-vcs = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
19-panproto-schema = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
20-panproto-check = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
21-panproto-lens = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
22-panproto-lens-dsl = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
23-panproto-protocols = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
24-panproto-io = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
25-panproto-inst = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
26-panproto-gat = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
27-panproto-mig = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
28-panproto-expr = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
29-panproto-expr-parser = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
30-panproto-xrpc = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
31-panproto-parse = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3", features = ["group-all"] }
32-panproto-git = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
33-panproto-project = { git = "https://github.com/panproto/panproto.git", tag = "v0.27.3" }
16+# panproto v0.28.0
17+panproto-core = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
18+panproto-vcs = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
19+panproto-schema = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
20+panproto-check = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
21+panproto-lens = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
22+panproto-lens-dsl = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
23+panproto-protocols = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
24+panproto-io = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
25+panproto-inst = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
26+panproto-gat = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
27+panproto-mig = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
28+panproto-expr = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
29+panproto-expr-parser = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
30+panproto-xrpc = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
31+panproto-parse = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0", features = ["group-all"] }
32+panproto-git = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
33+panproto-project = { git = "https://github.com/panproto/panproto.git", tag = "v0.28.0" }
3434 git2 = "0.20"
3535 
3636 # Web framework
@@ -163,6 +163,9 @@ fn value_to_json(v: &panproto_inst::value::Value) -> serde_json::Value {
163163                 .collect();
164164             serde_json::Value::Object(m)
165165         }
166+        Value::List(items) => {
167+            serde_json::Value::Array(items.iter().map(value_to_json).collect())
168+        }
166169     }
167170 }
168171 
@@ -21,6 +21,7 @@ panproto-lens.workspace = true
2121 panproto-parse.workspace = true
2222 panproto-git.workspace = true
2323 panproto-vcs.workspace = true
24+panproto-xrpc.workspace = true
2425 git2.workspace = true
2526 
2627 axum.workspace = true
@@ -214,20 +214,17 @@ pub async fn git_receive_pack(
214214 
215215     drop(store_guard);
216216 
217-    // 5. Import into panproto-vcs asynchronously. The push response is
218-    //    sent immediately; the import runs in the background. This is
219-    //    necessary because import_git_repo walks the entire commit
220-    //    history (tracked upstream at panproto/panproto#26) and can
221-    //    take minutes for repos with 100+ commits.
217+    // 5. Import into panproto-vcs asynchronously using incremental
218+    //    import. The marks file tracks which git OIDs have already been
219+    //    translated, so repeated pushes only import new commits.
222220     if !import_tasks.is_empty() {
223221         let store_clone = state.store.clone();
224222         let did_clone = did.clone();
225223         let repo_clone = repo.clone();
226224         tokio::task::spawn_blocking(move || {
227-            // Open the stores under the lock, then DROP the lock before
228-            // the expensive import. FsStore is file-backed so concurrent
229-            // reads (listCommits, diffCommits) work fine while we write.
230-            let (mirror, mut vcs_store) = {
225+            // Open the stores under the lock, load marks, then DROP the
226+            // lock before the expensive import.
227+            let (mirror, mut vcs_store, known) = {
231228                 let store_guard = store_clone.blocking_lock();
232229                 let mirror = match store_guard.open_or_init_git_mirror(&did_clone, &repo_clone) {
233230                     Ok(m) => m,
@@ -243,17 +240,31 @@ pub async fn git_receive_pack(
243240                         return;
244241                     }
245242                 };
246-                (mirror, vcs_store)
243+                let known = store_guard.load_import_marks(&did_clone, &repo_clone);
244+                (mirror, vcs_store, known)
247245                 // store_guard dropped here: lock released
248246             };
249247             for (new_oid, refname) in &import_tasks {
250-                match panproto_git::import_git_repo(&mirror, &mut vcs_store, new_oid) {
248+                match panproto_git::import_git_repo_incremental(
249+                    &mirror,
250+                    &mut vcs_store,
251+                    new_oid,
252+                    &known,
253+                ) {
251254                     Ok(result) => {
252255                         let _ = panproto_vcs::Store::set_ref(&mut vcs_store, refname, result.head_id);
256+                        // Persist the new OID mappings for future incremental imports.
257+                        let store_guard = store_clone.blocking_lock();
258+                        store_guard.save_import_marks(
259+                            &did_clone,
260+                            &repo_clone,
261+                            &result.oid_map,
262+                        );
263+                        drop(store_guard);
253264                         tracing::info!(
254265                             did = %did_clone, repo = %repo_clone, %refname,
255266                             commits = result.commit_count,
256-                            "background: imported git commits into panproto-vcs"
267+                            "background: incrementally imported git commits into panproto-vcs"
257268                         );
258269                     }
259270                     Err(e) => {
cospan · schematic version control on atproto built on AT Protocol