lens: return unreadable comment refs from the shared listings
commit
7c2c4c9lens: return unreadable comment refs from the shared listings
list_projected and list_for_document now report what they cannot read back, as list_all already does (lens.parity: no caller may drop them silently). git ents comment list prints a trailing note per unreadable ref; porcelain stays rows-only for format stability; the lens drops them deliberately and says so, since they name no document.
Assisted-by: Claude:claude-fable-5
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
crates/cli/ents-lens/src/lens.rs
@@ -127,14 +127,21 @@
state: Some("open".to_owned()),
context: None,
};
- Ok(comment::list_for_document(
+ let (rows, unreadable) = comment::list_for_document(
self.refs.as_ref(),
&self.objects,
&self.path,
&rel,
buffer,
&filter,
- )?)
+ )?;
+ // An unreadable comment ref names no document, so it has no lens,
+ // diagnostic, or hover to appear in -- the web listing's
+ // disclosure and `git ents comment list`'s trailing note are the
+ // surfaces that report it; here it is dropped deliberately, not
+ // silently (the library returns it either way, `lens.parity`).
+ let _ = unreadable;
+ Ok(rows)
}
/// The code lenses for `uri` (`lens.lenses`): three per open comment
crates/cli/git-ents/src/exe.rs
@@ -215,8 +215,11 @@
}
};
let filter = ents_forge::comment::ListFilter { state, context };
- let rows = commands::comment::list_projected(&root, worktree, &filter)?;
+ let (rows, unreadable) = commands::comment::list_projected(&root, worktree, &filter)?;
if porcelain {
+ // Porcelain stays rows-only for format stability; a tool
+ // that wants the unreadable refs takes them from
+ // `comment::list_projected` itself.
let _ = write!(out, "{}", commands::comment::porcelain(&rows));
} else {
for row in rows {
@@ -228,6 +231,9 @@
row.comment.body
);
}
+ for entry in unreadable {
+ let _ = writeln!(out, "! {}\tunreadable: {}", entry.refname, entry.error);
+ }
}
}
CommentAction::Add {
crates/cli/git-ents/tests/comment.rs
@@ -113,7 +113,7 @@
state: Some("open".to_owned()),
context: None,
};
- let rows = comment::list_projected(&root, true, &open).expect("lists");
+ let (rows, _unreadable) = comment::list_projected(&root, true, &open).expect("lists");
let rendered = comment::porcelain(&rows);
let expected = format!(
"{id} open current file.txt:5-5\ncontext issues/42\n\tthis new line looks wrong\n\t\n\tsecond paragraph\n"
@@ -123,9 +123,10 @@
// Resolve through the CLI surface; the open listing no longer shows
// it, the unfiltered one shows it resolved.
comment::set_state(&root, &id, true, Some(fixture.key_path.clone())).expect("resolves");
- let rows = comment::list_projected(&root, true, &open).expect("lists");
+ let (rows, _unreadable) = comment::list_projected(&root, true, &open).expect("lists");
assert!(rows.is_empty(), "a resolved comment is not open");
- let all = comment::list_projected(&root, true, &ListFilter::default()).expect("lists");
+ let (all, _unreadable) =
+ comment::list_projected(&root, true, &ListFilter::default()).expect("lists");
assert_eq!(all.len(), 1);
assert_eq!(all[0].comment.state, "resolved");
}
@@ -149,7 +150,8 @@
)
.expect("replies");
- let rows = comment::list_projected(&root, false, &ListFilter::default()).expect("lists");
+ let (rows, _unreadable) =
+ comment::list_projected(&root, false, &ListFilter::default()).expect("lists");
let rendered = comment::porcelain(&rows);
let records: Vec<&str> = rendered.split("\n\n").collect();
assert_eq!(records.len(), 2);
crates/forge/ents-forge/tests/conversations.rs
@@ -320,7 +320,7 @@
.collect();
std::fs::write(fixture.path().join("file.txt"), dirty).unwrap();
- let open_only = comment::list_projected(
+ let (open_only, _unreadable) = comment::list_projected(
&fixture.refs,
&fixture.objects,
fixture.path(),
@@ -341,7 +341,7 @@
})
);
- let by_context = comment::list_projected(
+ let (by_context, _unreadable) = comment::list_projected(
&fixture.refs,
&fixture.objects,
fixture.path(),
crates/cli/git-ents/src/commands/comment.rs
@@ -26,7 +26,9 @@
/// `git ents comment list [--worktree] [--state ...] [--context ...]`:
/// matching comments with each anchor projected onto the working tree
-/// (with `worktree`) or `HEAD`.
+/// (with `worktree`) or `HEAD`, plus the refs whose stored tree this
+/// build could not read back (reported after the listing, never
+/// silently dropped).
///
/// # Errors
///
@@ -35,7 +37,7 @@
root: &LocalRoot,
worktree: bool,
filter: &ListFilter,
-) -> Result<Vec<Listed>> {
+) -> Result<(Vec<Listed>, Vec<ents_forge::Unreadable>)> {
Ok(comment::list_projected(
&root.refs,
&root.objects,
crates/forge/ents-forge/src/comment/command.rs
@@ -161,7 +161,10 @@
/// every matching comment, each anchor projected onto the working tree
/// (`anchor.working-tree`) when `worktree` is set, onto `HEAD` otherwise —
/// the listing `lens.parity` requires to be one library call shared by the
-/// CLI's machine-readable form, the web UI, and the editor lens.
+/// CLI's machine-readable form, the web UI, and the editor lens. Refs
+/// whose stored tree this build cannot read back come back as the second
+/// element, exactly as [`list_all`] reports them, so no caller can drop
+/// them silently.
///
/// # Errors
///
@@ -174,10 +177,11 @@
repo_path: &std::path::Path,
worktree: bool,
filter: &ListFilter,
-) -> Result<Vec<Listed>> {
+) -> Result<(Vec<Listed>, Vec<crate::Unreadable>)> {
let repo = gix::open(repo_path)?;
let mut out = Vec::new();
- for (id, comment) in list(refs, objects)? {
+ let (all, unreadable) = list_all(refs, objects)?;
+ for (id, comment) in all {
if let Some(state) = &filter.state
&& comment.state != *state
{
@@ -207,7 +211,7 @@
projection,
});
}
- Ok(out)
+ Ok((out, unreadable))
}
/// What `git ents comment add` writes, before the mechanism-side
@@ -549,10 +553,11 @@
target_path: &str,
buffer: Option<&[u8]>,
filter: &ListFilter,
-) -> Result<Vec<Listed>> {
+) -> Result<(Vec<Listed>, Vec<crate::Unreadable>)> {
let repo = gix::open(repo_path)?;
let mut out = Vec::new();
- for (id, comment) in list(refs, objects)? {
+ let (all, unreadable) = list_all(refs, objects)?;
+ for (id, comment) in all {
if let Some(state) = &filter.state
&& comment.state != *state
{
@@ -578,7 +583,7 @@
projection: Some(projection),
});
}
- Ok(out)
+ Ok((out, unreadable))
}
/// The thread rooted at `root_id` (`model.comment-thread`): the comment