diff --git a/crates/server/src/yas_git.rs b/crates/server/src/yas_git.rs index 9f629be9..1790b7a9 100644 --- a/crates/server/src/yas_git.rs +++ b/crates/server/src/yas_git.rs @@ -1081,14 +1081,12 @@ fn run_query( .map_err(native_error)?; let records = match response { yas_git::native::PatchResult::Text(data) => { - let object = endpoint_content_object(right) + let object = match endpoint_content_object(right) .or_else(|| endpoint_content_object(left)) - .ok_or_else(|| { - Error::new( - Status::Unsupported, - "text patch between mutable endpoints has no content object", - ) - })?; + { + Some(object) => object, + None => null_object(object_algorithm)?, + }; let byte_len = data.len() as u64; return Ok(query_page( vec![QueryItem::Content(OwnedContent { @@ -1896,6 +1894,20 @@ fn native_endpoint(endpoint: &wire::QueryEndpoint) -> Result Result { + let len = match object_algorithm { + value if value == yas_wire::schema::git::OBJECT_SHA1 as u8 => 20, + value if value == yas_wire::schema::git::OBJECT_SHA256 as u8 => 32, + _ => return Err(Error::new(Status::Internal, "unknown Git object algorithm")), + }; + Ok(wire::ObjectId { + algorithm: object_algorithm, + bytes: vec![0; len], + }) +} + fn endpoint_content_object(endpoint: &wire::QueryEndpoint) -> Option { match endpoint { wire::QueryEndpoint::Commit(object) @@ -2274,6 +2286,31 @@ mod tests { .any(|record| matches!(record, QueryItem::Record(wire::QueryRecord::PatchRow(_)))) ); + let text = run_query( + Some(&repository), + algorithm, + &query(wire::QueryBody::Patch { + left: wire::QueryEndpoint::Index, + right: wire::QueryEndpoint::Worktree, + path: None, + context_lines: 3, + rename_threshold: 50, + max_bytes: 1024 * 1024, + flags: yas_wire::schema::git::PATCH_TEXT as u16, + }), + None, + &cancel, + ) + .unwrap(); + match text.records.as_slice() { + [QueryItem::Content(OwnedContent { object, bytes, .. })] => { + assert!(object.bytes.iter().all(|byte| *byte == 0)); + let text = String::from_utf8_lossy(bytes); + assert!(text.contains("-hello\n+hello native YAS\n"), "{text}"); + } + other => panic!("unexpected index/worktree patch page: {other:?}"), + } + for body in [ wire::QueryBody::Index { path: None, diff --git a/docs/design/git.md b/docs/design/git.md index 7e24b825..936bf93f 100644 --- a/docs/design/git.md +++ b/docs/design/git.md @@ -205,7 +205,10 @@ the tree via gix) with intraline span refinement on modified line pairs — word- or character-granular, over raw or whitespace-normalized text, per request flags; binary detection short-circuits to `BINARY`. The row records are engine-agnostic by design, so a syntax-aware engine can -replace the alignment later, purely server-side. +replace the alignment later, purely server-side. A `PATCH_TEXT` answer's +content object is the right endpoint's object, else the left's; between two +mutable endpoints (INDEX, WORKTREE, EMPTY) it is the null object ID, as +`git diff` prints for the worktree side. Nothing runs under the session mutex; Results and Events interleave with Terminal, Surface, Media, and FS traffic through the per-session writer.