···280280 self.walker.step_with_nodes(&self.blocks, self.process)
281281 }
282282283283+ /// Get the next key and CID from the walk, without fetching record blocks.
284284+ ///
285285+ /// Record CIDs come directly from MST node entries — record blocks are never
286286+ /// looked up. MST node blocks are still fetched to traverse the tree.
287287+ ///
288288+ /// Returns `Ok(None)` when the walk is complete. Returns
289289+ /// `Err(WalkError::MissingNode)` if a child MST node block is absent.
290290+ pub fn next_keys(&mut self) -> Result<Option<(RepoPath, Cid)>, WalkError> {
291291+ self.walker.step_keys(&self.blocks)
292292+ }
293293+294294+ /// Collect up to `n` key+CID pairs, without fetching record blocks.
295295+ ///
296296+ /// Like [`next_keys`] but collects up to `n` pairs in one call.
297297+ ///
298298+ /// Returns `Ok(None)` when the walk is complete. Returns
299299+ /// `Err(WalkError::MissingNode)` if a child MST node block is absent.
300300+ pub fn next_chunk_keys(&mut self, n: usize) -> Result<Option<Vec<(RepoPath, Cid)>>, WalkError> {
301301+ let mut out = Vec::with_capacity(n);
302302+ for _ in 0..n {
303303+ match self.walker.step_keys(&self.blocks)? {
304304+ Some(pair) => out.push(pair),
305305+ None => break,
306306+ }
307307+ }
308308+ if out.is_empty() {
309309+ Ok(None)
310310+ } else {
311311+ Ok(Some(out))
312312+ }
313313+ }
314314+283315 /// Collect up to `n` items (records, missing items, and node blocks).
284316 ///
285317 /// Like [`next_chunk`] but also includes `WalkItem::Node`. The chunk
+27-29
src/slice.rs
···147147 return Ok(Some(out));
148148 }
149149150150- loop {
151151- match self.mem_car.next()? {
152152- None => {
150150+ match self.mem_car.next()? {
151151+ None => {
152152+ self.done = true;
153153+ validate_upper(self.following_key.as_deref(), &self.upper)?;
154154+ Ok(None)
155155+ }
156156+ Some(WalkItem::Node { .. }) => unreachable!("step() never emits Node"),
157157+ Some(WalkItem::MissingSubtree { cid }) => {
158158+ // Any missing subtree after the range starts is an error:
159159+ // we can't prove the range is complete without it.
160160+ Err(SliceError::MissingNode { cid })
161161+ }
162162+ Some(WalkItem::MissingRecord { key, cid }) => {
163163+ if is_after(&key, &self.upper) {
164164+ self.following_key = Some(key);
153165 self.done = true;
154166 validate_upper(self.following_key.as_deref(), &self.upper)?;
155155- return Ok(None);
156156- }
157157- Some(WalkItem::Node { .. }) => unreachable!("step() never emits Node"),
158158- Some(WalkItem::MissingSubtree { cid }) => {
159159- // Any missing subtree after the range starts is an error:
160160- // we can't prove the range is complete without it.
161161- return Err(SliceError::MissingNode { cid });
162162- }
163163- Some(WalkItem::MissingRecord { key, cid }) => {
164164- if is_after(&key, &self.upper) {
165165- self.following_key = Some(key);
166166- self.done = true;
167167- validate_upper(self.following_key.as_deref(), &self.upper)?;
168168- return Ok(None);
169169- } else {
170170- return Err(SliceError::IncompleteRange { key, cid });
171171- }
167167+ Ok(None)
168168+ } else {
169169+ Err(SliceError::IncompleteRange { key, cid })
172170 }
173173- Some(WalkItem::Record(out)) => {
174174- if is_after(&out.key, &self.upper) {
175175- self.following_key = Some(out.key);
176176- self.done = true;
177177- validate_upper(self.following_key.as_deref(), &self.upper)?;
178178- return Ok(None);
179179- } else {
180180- return Ok(Some(out));
181181- }
171171+ }
172172+ Some(WalkItem::Record(out)) => {
173173+ if is_after(&out.key, &self.upper) {
174174+ self.following_key = Some(out.key);
175175+ self.done = true;
176176+ validate_upper(self.following_key.as_deref(), &self.upper)?;
177177+ Ok(None)
178178+ } else {
179179+ Ok(Some(out))
182180 }
183181 }
184182 }
+55
src/walk.rs
···302302 Ok(None)
303303 }
304304305305+ /// Like [`step`], but skips record block lookups entirely.
306306+ ///
307307+ /// Returns the key and CID of each record directly from the MST node entries.
308308+ /// MST node blocks are still fetched to traverse the tree structure.
309309+ ///
310310+ /// Returns `Err(WalkError::MissingNode)` if a child MST node block is absent.
311311+ pub fn step_keys(
312312+ &mut self,
313313+ blocks: &HashMap<ObjectLink, MaybeProcessedBlock>,
314314+ ) -> Result<Option<(RepoPath, Cid)>, WalkError> {
315315+ while let Some(NodeThing { link, kind }) = self.next_todo() {
316316+ match kind {
317317+ ThingKind::Record(key) => {
318318+ if Some(&key) <= self.prev_key.as_ref() {
319319+ return Err(WalkError::MstError(MstError::KeyOutOfOrder {
320320+ key,
321321+ prev: self.prev_key.clone().unwrap_or("[no prev key]".to_string()),
322322+ }));
323323+ }
324324+ self.prev_key = Some(key.clone());
325325+ return Ok(Some((key, link.into())));
326326+ }
327327+ ThingKind::ChildNode => {
328328+ let Some(mpb) = blocks.get(&link) else {
329329+ return Err(WalkError::MissingNode {
330330+ cid: Box::new(link.into()),
331331+ });
332332+ };
333333+ let MaybeProcessedBlock::Raw(data) = mpb else {
334334+ return Err(WalkError::BadCommitFingerprint);
335335+ };
336336+ let node: MstNode =
337337+ serde_ipld_dagcbor::from_slice(data).map_err(WalkError::BadCommit)?;
338338+ if node.is_empty() {
339339+ return Err(WalkError::MstError(MstError::EmptyNode));
340340+ }
341341+ let current_layer = self.root_layer - (self.todo.len() - 1) as u32;
342342+ let next_layer = current_layer
343343+ .checked_sub(1)
344344+ .ok_or(MstError::LayerUnderflow)?;
345345+ if let Some(d) = node.layer
346346+ && d != next_layer
347347+ {
348348+ return Err(WalkError::MstError(MstError::WrongLayer {
349349+ layer: d,
350350+ expected: next_layer,
351351+ }));
352352+ }
353353+ self.todo.push(node.things);
354354+ }
355355+ }
356356+ }
357357+ Ok(None)
358358+ }
359359+305360 /// Skip forward to the first record at or after `target`, without emitting anything.
306361 ///
307362 /// Uses the tree structure to skip entire subtrees that are provably before `target`,