mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-05 20:55:05 +00:00
op_store: add resolve_operation_id_prefix() trait method that uses readdir()
The OpStore backends should have a better way to look up operation by id than traversing from the op heads. The added method is similar to the commit Index one, but returns an OpStoreResult because the backend operation can fail. FWIW, if we want .shortest() in the op log template, we'll probably need a trait method that returns an OpIndex instead.
This commit is contained in:
parent
95ea352b0a
commit
837ac15052
3 changed files with 69 additions and 34 deletions
|
@ -25,7 +25,7 @@ use thiserror::Error;
|
|||
use crate::backend::{CommitId, Timestamp};
|
||||
use crate::content_hash::ContentHash;
|
||||
use crate::merge::Merge;
|
||||
use crate::object_id::{id_type, ObjectId};
|
||||
use crate::object_id::{id_type, HexPrefix, ObjectId, PrefixResolution};
|
||||
|
||||
content_hash! {
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
|
||||
|
@ -418,6 +418,12 @@ pub trait OpStore: Send + Sync + Debug {
|
|||
fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation>;
|
||||
|
||||
fn write_operation(&self, contents: &Operation) -> OpStoreResult<OperationId>;
|
||||
|
||||
/// Resolves an unambiguous operation ID prefix.
|
||||
fn resolve_operation_id_prefix(
|
||||
&self,
|
||||
prefix: &HexPrefix,
|
||||
) -> OpStoreResult<PrefixResolution<OperationId>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -22,7 +22,7 @@ use std::sync::Arc;
|
|||
use itertools::Itertools as _;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::object_id::HexPrefix;
|
||||
use crate::object_id::{HexPrefix, PrefixResolution};
|
||||
use crate::op_heads_store::{OpHeadResolutionError, OpHeadsStore};
|
||||
use crate::op_store::{OpStore, OpStoreError, OpStoreResult, OperationId};
|
||||
use crate::operation::Operation;
|
||||
|
@ -110,7 +110,7 @@ fn resolve_single_op(
|
|||
.transpose()?;
|
||||
let mut operation = match op_symbol {
|
||||
"@" => get_current_op(),
|
||||
s => resolve_single_op_from_store(op_store, op_heads_store, s),
|
||||
s => resolve_single_op_from_store(op_store, s),
|
||||
}?;
|
||||
for c in op_postfix.chars() {
|
||||
let mut neighbor_ops = match c {
|
||||
|
@ -129,7 +129,6 @@ fn resolve_single_op(
|
|||
|
||||
fn resolve_single_op_from_store(
|
||||
op_store: &Arc<dyn OpStore>,
|
||||
op_heads_store: &dyn OpHeadsStore,
|
||||
op_str: &str,
|
||||
) -> Result<Operation, OpsetEvaluationError> {
|
||||
if op_str.is_empty() {
|
||||
|
@ -137,34 +136,17 @@ fn resolve_single_op_from_store(
|
|||
}
|
||||
let prefix = HexPrefix::new(op_str)
|
||||
.ok_or_else(|| OpsetResolutionError::InvalidIdPrefix(op_str.to_owned()))?;
|
||||
if let Some(binary_op_id) = prefix.as_full_bytes() {
|
||||
let op_id = OperationId::from_bytes(binary_op_id);
|
||||
match op_store.read_operation(&op_id) {
|
||||
Ok(operation) => {
|
||||
return Ok(Operation::new(op_store.clone(), op_id, operation));
|
||||
}
|
||||
Err(OpStoreError::ObjectNotFound { .. }) => {
|
||||
// Fall through
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(OpsetEvaluationError::OpStore(err));
|
||||
}
|
||||
match op_store.resolve_operation_id_prefix(&prefix)? {
|
||||
PrefixResolution::NoMatch => {
|
||||
Err(OpsetResolutionError::NoSuchOperation(op_str.to_owned()).into())
|
||||
}
|
||||
PrefixResolution::SingleMatch(op_id) => {
|
||||
let data = op_store.read_operation(&op_id)?;
|
||||
Ok(Operation::new(op_store.clone(), op_id, data))
|
||||
}
|
||||
PrefixResolution::AmbiguousMatch => {
|
||||
Err(OpsetResolutionError::AmbiguousIdPrefix(op_str.to_owned()).into())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Extract to OpStore method where IDs can be resolved without loading
|
||||
// all operation data?
|
||||
let head_ops = get_current_head_ops(op_store, op_heads_store)?;
|
||||
let mut matches: Vec<_> = walk_ancestors(&head_ops)
|
||||
.filter_ok(|op| prefix.matches(op.id()))
|
||||
.take(2)
|
||||
.try_collect()?;
|
||||
if matches.is_empty() {
|
||||
Err(OpsetResolutionError::NoSuchOperation(op_str.to_owned()).into())
|
||||
} else if matches.len() == 1 {
|
||||
Ok(matches.pop().unwrap())
|
||||
} else {
|
||||
Err(OpsetResolutionError::AmbiguousIdPrefix(op_str.to_owned()).into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::io::{ErrorKind, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use prost::Message;
|
||||
use tempfile::NamedTempFile;
|
||||
|
@ -26,15 +26,18 @@ use thiserror::Error;
|
|||
|
||||
use crate::backend::{CommitId, MillisSinceEpoch, Timestamp};
|
||||
use crate::content_hash::blake2b_hash;
|
||||
use crate::file_util::persist_content_addressed_temp_file;
|
||||
use crate::file_util::{persist_content_addressed_temp_file, IoResultExt as _};
|
||||
use crate::merge::Merge;
|
||||
use crate::object_id::ObjectId;
|
||||
use crate::object_id::{HexPrefix, ObjectId, PrefixResolution};
|
||||
use crate::op_store::{
|
||||
OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata, RefTarget,
|
||||
RemoteRef, RemoteRefState, RemoteView, View, ViewId, WorkspaceId,
|
||||
};
|
||||
use crate::{git, op_store};
|
||||
|
||||
// BLAKE2b-512 hash length in bytes
|
||||
const OPERATION_ID_LENGTH: usize = 64;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Failed to read {kind} with ID {id}: {err}")]
|
||||
struct DecodeError {
|
||||
|
@ -148,6 +151,50 @@ impl OpStore for SimpleOpStore {
|
|||
.map_err(|err| io_to_write_error(err, "operation"))?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn resolve_operation_id_prefix(
|
||||
&self,
|
||||
prefix: &HexPrefix,
|
||||
) -> OpStoreResult<PrefixResolution<OperationId>> {
|
||||
let op_dir = self.path.join("operations");
|
||||
let find = || -> io::Result<_> {
|
||||
let hex_prefix = prefix.hex();
|
||||
if hex_prefix.len() == OPERATION_ID_LENGTH * 2 {
|
||||
// Fast path for full-length ID
|
||||
if op_dir.join(hex_prefix).try_exists()? {
|
||||
let id = OperationId::from_bytes(prefix.as_full_bytes().unwrap());
|
||||
return Ok(PrefixResolution::SingleMatch(id));
|
||||
} else {
|
||||
return Ok(PrefixResolution::NoMatch);
|
||||
}
|
||||
}
|
||||
|
||||
let mut matched = None;
|
||||
for entry in op_dir.read_dir()? {
|
||||
let Ok(name) = entry?.file_name().into_string() else {
|
||||
continue; // Skip invalid UTF-8
|
||||
};
|
||||
if !name.starts_with(&hex_prefix) {
|
||||
continue;
|
||||
}
|
||||
let Ok(id) = OperationId::try_from_hex(&name) else {
|
||||
continue; // Skip invalid hex
|
||||
};
|
||||
if matched.is_some() {
|
||||
return Ok(PrefixResolution::AmbiguousMatch);
|
||||
}
|
||||
matched = Some(id);
|
||||
}
|
||||
if let Some(id) = matched {
|
||||
Ok(PrefixResolution::SingleMatch(id))
|
||||
} else {
|
||||
Ok(PrefixResolution::NoMatch)
|
||||
}
|
||||
};
|
||||
find()
|
||||
.context(&op_dir)
|
||||
.map_err(|err| OpStoreError::Other(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn io_to_read_error(err: std::io::Error, id: &impl ObjectId) -> OpStoreError {
|
||||
|
|
Loading…
Reference in a new issue